Skip to content

Commit 40b5e89

Browse files
committed
feat(gatsby-sharp): add gatsby-sharp as sharp abstraction
1 parent 33049c8 commit 40b5e89

File tree

7 files changed

+181
-0
lines changed

7 files changed

+181
-0
lines changed

packages/gatsby-sharp/.babelrc

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"presets": [["babel-preset-gatsby-package"]]
3+
}

packages/gatsby-sharp/package.json

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
{
2+
"name": "gatsby-sharp",
3+
"version": "0.0.1-next.0",
4+
"sideEffects": false,
5+
"keywords": [
6+
"gatsby",
7+
"sharp"
8+
],
9+
"main": "dist/index.js",
10+
"source": "src/index.ts",
11+
"files": [
12+
"dist/*"
13+
],
14+
"types": "dist/index.d.ts",
15+
"homepage": "https://github.com/gatsbyjs/gatsby/tree/master/packages/gatsby-sharp#readme",
16+
"dependencies": {
17+
"@types/sharp": "^0.29.4",
18+
"sharp": "^0.29.3"
19+
},
20+
"devDependencies": {
21+
"@babel/cli": "^7.10.4",
22+
"@babel/core": "^7.10.4",
23+
"cross-env": "^7.0.2"
24+
},
25+
"engines": {
26+
"node": ">=14.15.0"
27+
},
28+
"repository": {
29+
"type": "git",
30+
"url": "https://github.com/gatsbyjs/gatsby.git",
31+
"directory": "packages/gatsby-sharp"
32+
},
33+
"license": "MIT",
34+
"scripts": {
35+
"build": "babel src --out-file dist/index.js --ignore \"**/__tests__\" --extensions \".ts,.js\"",
36+
"typegen": "tsc --emitDeclarationOnly --declaration --declarationDir dist/",
37+
"prepare": "cross-env NODE_ENV=production npm-run-all -s build typegen",
38+
"watch": "babel src --out-file dist/index.js --ignore \"**/__tests__\" --extensions \".ts,.js\""
39+
}
40+
}
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
/** @jest-environment node */
2+
import { exec } from "child_process"
3+
4+
jest.mock(`child_process`, () => {
5+
return {
6+
exec: jest.fn(async (command, options, cb) => {
7+
setImmediate(() => {
8+
try {
9+
return cb(
10+
null,
11+
`> sharp@0.29.3 install C:\\Users\\Ward\\projects\\gatsby\\gatsby\\node_modules\\sharp\n`,
12+
``
13+
)
14+
} catch (err) {
15+
return cb(true, ``, err.message)
16+
}
17+
})
18+
}),
19+
}
20+
})
21+
22+
function getSharpInstance(): typeof import("../index") {
23+
let getSharpInstance
24+
jest.isolateModules(() => {
25+
getSharpInstance = require(`../index`)
26+
})
27+
28+
return getSharpInstance()
29+
}
30+
31+
describe(`getSharpInstance`, () => {
32+
beforeEach(() => {
33+
exec.mockClear()
34+
})
35+
36+
// jest mocking is making this impossible to test
37+
it(`should give you the bare sharp module`, async () => {
38+
const sharpInstance = await getSharpInstance()
39+
40+
expect(exec).not.toHaveBeenCalled()
41+
expect(sharpInstance).toBeDefined()
42+
expect(sharpInstance.versions).toBeDefined()
43+
})
44+
45+
it(
46+
`should rebuild sharp when binaries not found for current arch`,
47+
async () => {
48+
expect.assertions(3)
49+
50+
let called = false
51+
jest.doMock(`sharp`, () => {
52+
if (!called) {
53+
called = true
54+
throw new Error(`sharp failed to load`)
55+
}
56+
57+
return jest.requireActual(`sharp`)
58+
})
59+
60+
try {
61+
const sharpInstance = await getSharpInstance()
62+
expect(sharpInstance).toBeDefined()
63+
expect(sharpInstance.versions).toBeDefined()
64+
} catch (err) {
65+
// ignore
66+
}
67+
68+
expect(exec).toHaveBeenCalledWith(
69+
`npm rebuild sharp`,
70+
expect.anything(),
71+
expect.anything()
72+
)
73+
},
74+
60 * 1000
75+
)
76+
})

packages/gatsby-sharp/src/index.ts

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
const { exec } = require(`child_process`)
2+
const { createRequire } = require(`module`)
3+
4+
module.exports = async function getSharpInstance(): Promise<
5+
typeof import("sharp")
6+
> {
7+
try {
8+
return importSharp()
9+
} catch (err) {
10+
await rebuildSharp()
11+
12+
// Try importing again now we have rebuilt sharp
13+
return importSharp()
14+
}
15+
}
16+
17+
function importSharp(): typeof import("sharp") {
18+
const cleanRequire = createRequire(__filename)
19+
const sharp = cleanRequire(`sharp`)
20+
21+
sharp.simd(true)
22+
// Concurrency is handled by gatsby
23+
sharp.concurrency(1)
24+
25+
return sharp
26+
}
27+
28+
function rebuildSharp(): Promise<string> {
29+
return new Promise((resolve, reject) => {
30+
exec(
31+
`npm rebuild sharp`,
32+
{
33+
timeout: 60 * 1000,
34+
},
35+
(error, stdout, stderr) => {
36+
if (error) {
37+
if (error.killed) {
38+
console.log(`timeout reached`)
39+
}
40+
41+
return reject(stderr)
42+
}
43+
44+
return setImmediate(() => resolve(stdout))
45+
}
46+
)
47+
})
48+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"extends": "../../tsconfig.json",
3+
"exclude": [
4+
"src/__tests__",
5+
"src/__mocks__",
6+
"dist",
7+
]
8+
}

packages/gatsby/sharp.d.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import getSharpInstance from "gatsby-sharp"
2+
3+
export = getSharpInstance

packages/gatsby/sharp.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
"use strict"
2+
3+
module.exports = require('gatsby-sharp');

0 commit comments

Comments
 (0)