Skip to content

Commit 0a053a1

Browse files
committed
queue preview generation and cache results on disk
1 parent 0840f19 commit 0a053a1

File tree

2 files changed

+91
-62
lines changed

2 files changed

+91
-62
lines changed

packages/gatsby-transformer-sqip/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
"dependencies": {
1010
"babel-runtime": "^6.26.0",
1111
"fs-extra": "^4.0.2",
12+
"p-queue": "^2.3.0",
13+
"progress": "^2.0.0",
1214
"sqip": "^0.2.2"
1315
},
1416
"devDependencies": {

packages/gatsby-transformer-sqip/src/extend-node-type.js

Lines changed: 89 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,32 @@
11
const crypto = require(`crypto`)
2-
const { createWriteStream } = require(`fs`)
32
const { extname, join, resolve } = require(`path`)
43

5-
const {
6-
GraphQLString,
7-
GraphQLInt,
8-
} = require(`graphql`)
4+
const fs = require(`fs-extra`)
5+
const { GraphQLObjectType, GraphQLString, GraphQLInt } = require(`graphql`)
6+
const PQueue = require(`p-queue`)
7+
const ProgressBar = require(`progress`)
98
const sqip = require(`sqip`)
109

11-
module.exports = async (args) => {
10+
const SUPPORTED_NODES = [`ImageSharp`, `ContentfulAsset`]
11+
const queue = new PQueue({ concurrency: 1 })
12+
let bar
13+
14+
module.exports = async args => {
1215
const { type: { name } } = args
1316

17+
if (!SUPPORTED_NODES.includes(name)) {
18+
return {}
19+
}
20+
21+
// @todo progress bar does not show up some times
22+
bar = new ProgressBar(
23+
`Generating sqip's [:bar] :current/:total :percent :eta left`,
24+
{
25+
total: 0,
26+
width: process.stdout.columns || 40,
27+
}
28+
)
29+
1430
if (name === `ImageSharp`) {
1531
return sqipSharp(args)
1632
}
@@ -22,27 +38,21 @@ module.exports = async (args) => {
2238
return {}
2339
}
2440

25-
async function sqipSharp({ type, cache, getNodeAndSavePathDependency }) {
26-
if (type.name !== `ImageSharp`) {
27-
return {}
28-
}
29-
41+
async function sqipSharp({ type, cache, getNodeAndSavePathDependency, store }) {
3042
return {
3143
sqip: {
32-
type: GraphQLString,
33-
args: {
34-
blur: {
35-
type: GraphQLInt,
36-
defaultValue: 1,
37-
},
38-
numberOfPrimitives: {
39-
type: GraphQLInt,
40-
defaultValue: 10,
41-
},
42-
mode: {
43-
type: GraphQLInt,
44-
defaultValue: 0,
44+
type: new GraphQLObjectType({
45+
name: `Sqip`,
46+
fields: {
47+
svg: { type: GraphQLString },
48+
dataURI: { type: GraphQLString },
49+
dataURIbase64: { type: GraphQLString },
4550
},
51+
}),
52+
args: {
53+
blur: { type: GraphQLInt, defaultValue: 1 },
54+
numberOfPrimitives: { type: GraphQLInt, defaultValue: 10 },
55+
mode: { type: GraphQLInt, defaultValue: 0 },
4656
},
4757
async resolve(image, fieldArgs, context) {
4858
const { blur, numberOfPrimitives, mode } = fieldArgs
@@ -56,21 +66,20 @@ async function sqipSharp({ type, cache, getNodeAndSavePathDependency }) {
5666
numberOfPrimitives,
5767
blur,
5868
mode,
69+
store,
5970
})
6071
},
6172
},
6273
}
6374
}
6475

6576
async function sqipContentful({ type, store, cache }) {
66-
if (type.name !== `ContentfulAsset`) {
67-
return {}
68-
}
69-
70-
const fs = require(`fs-extra`)
77+
const { createWriteStream } = require(`fs`)
7178
const axios = require(`axios`)
7279

73-
const { schemes: { ImageResizingBehavior, ImageCropFocusType } } = require(`gatsby-source-contentful`)
80+
const {
81+
schemes: { ImageResizingBehavior, ImageCropFocusType },
82+
} = require(`gatsby-source-contentful`)
7483

7584
const cacheDir = join(
7685
store.getState().program.directory,
@@ -117,11 +126,7 @@ async function sqipContentful({ type, store, cache }) {
117126
},
118127
},
119128
async resolve(asset, fieldArgs, context) {
120-
const {
121-
id,
122-
file: { url, fileName, details, contentType },
123-
node_locale: locale,
124-
} = asset
129+
const { id, file: { url, fileName, details, contentType } } = asset
125130
const {
126131
blur,
127132
numberOfPrimitives,
@@ -137,19 +142,14 @@ async function sqipContentful({ type, store, cache }) {
137142
return null
138143
}
139144

140-
console.log(`Processing: ${id}-${locale}`)
141-
142145
// Downloading small version of the image with same aspect ratio
143146
const assetWidth = width || details.image.width
144147
const assetHeight = height || details.image.height
145148
const aspectRatio = assetHeight / assetWidth
146149
const previewWidth = 256
147150
const previewHeight = Math.floor(previewWidth * aspectRatio)
148151

149-
const params = [
150-
`w=${previewWidth}`,
151-
`h=${previewHeight}`,
152-
]
152+
const params = [`w=${previewWidth}`, `h=${previewHeight}`]
153153
if (resizingBehavior) {
154154
params.push(`fit=${resizingBehavior}`)
155155
}
@@ -175,12 +175,10 @@ async function sqipContentful({ type, store, cache }) {
175175

176176
const alreadyExists = await fs.pathExists(absolutePath)
177177

178-
console.log(`Calculated path: ${absolutePath}`)
179-
180178
if (!alreadyExists) {
181179
const previewUrl = `http:${url}?${params.join(`&`)}`
182180

183-
console.log(`Downloading: ${previewUrl}`)
181+
bar.interrupt(`Downloading: ${previewUrl}`)
184182

185183
const response = await axios({
186184
method: `get`,
@@ -191,9 +189,7 @@ async function sqipContentful({ type, store, cache }) {
191189
await new Promise((resolve, reject) => {
192190
const file = createWriteStream(absolutePath)
193191
response.data.pipe(file)
194-
file.on(`finish`, () => {
195-
resolve()
196-
})
192+
file.on(`finish`, resolve)
197193
file.on(`error`, reject)
198194
})
199195
}
@@ -204,14 +200,17 @@ async function sqipContentful({ type, store, cache }) {
204200
numberOfPrimitives,
205201
blur,
206202
mode,
203+
store,
207204
})
208205
},
209206
},
210207
}
211208
}
212209

213210
async function generateSqip(options) {
214-
const { cache, absolutePath, numberOfPrimitives, blur, mode } = options
211+
const { cache, absolutePath, numberOfPrimitives, blur, mode, store } = options
212+
213+
const dataDir = join(store.getState().program.directory, `.cache-sqip`)
215214

216215
// @todo add check if file actually exists
217216

@@ -228,22 +227,50 @@ async function generateSqip(options) {
228227
.digest(`hex`)
229228

230229
const cacheKey = `sqip-${optionsHash}`
231-
232-
let svgThumbnail = await cache.get(cacheKey)
233-
234-
if (!svgThumbnail) {
235-
console.log(`Calculating low quality svg thumbnail: ${absolutePath}`)
236-
const result = sqip(sqipOptions)
237-
// @todo make blur setting in sqip via PR
238-
svgThumbnail = result.final_svg.replace(new RegExp(`<feGaussianBlur stdDeviation="[0-9]+"\\s*/>`), `<feGaussianBlur stdDeviation="${sqipOptions.blur}" />`)
239-
240-
await cache.set(cacheKey, svgThumbnail)
241-
console.log(`done calculating primitive ${absolutePath}`)
230+
const cachePath = resolve(dataDir, `sqip-${optionsHash}.json`)
231+
let primitiveData = await cache.get(cacheKey)
232+
233+
await fs.ensureDir(dataDir)
234+
235+
bar.total++
236+
237+
if (!primitiveData) {
238+
if (await fs.exists(cachePath)) {
239+
const cacheData = await fs.readFile(cachePath)
240+
primitiveData = JSON.parse(cacheData)
241+
} else {
242+
const result = await queue.add(
243+
async () =>
244+
new Promise((resolve, reject) => {
245+
try {
246+
resolve(sqip(sqipOptions))
247+
} catch (error) {
248+
reject(error)
249+
}
250+
})
251+
)
252+
253+
// @todo make blur setting in sqip via PR
254+
result.final_svg = result.final_svg.replace(
255+
new RegExp(`<feGaussianBlur stdDeviation="[0-9]+"\\s*/>`),
256+
`<feGaussianBlur stdDeviation="${sqipOptions.blur}" />`
257+
)
258+
259+
primitiveData = {
260+
svg: result.final_svg,
261+
dataURI: encodeOptimizedSVGDataUri(result.final_svg),
262+
dataURIbase64: `data:image/svg+xml;base64,${result.svg_base64encoded}`,
263+
}
264+
265+
const json = JSON.stringify(primitiveData, null, 2)
266+
await fs.writeFile(cachePath, json)
267+
}
268+
269+
bar.tick()
270+
await cache.set(cacheKey, primitiveData)
242271
}
243272

244-
const dataURI = encodeOptimizedSVGDataUri(svgThumbnail)
245-
246-
return dataURI
273+
return primitiveData
247274
}
248275

249276
// https://codepen.io/tigt/post/optimizing-svgs-in-data-uris

0 commit comments

Comments
 (0)