11const crypto = require ( `crypto` )
2- const { createWriteStream } = require ( `fs` )
32const { 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 ` )
98const 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
6576async 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
213210async 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