UNPKG

15.9 kBJavaScriptView Raw
1"use strict";
2
3const Promise = require(`bluebird`);
4
5const {
6 GraphQLObjectType,
7 GraphQLList,
8 GraphQLBoolean,
9 GraphQLString,
10 GraphQLInt,
11 GraphQLFloat,
12 GraphQLNonNull
13} = require(`gatsby/graphql`);
14
15const {
16 queueImageResizing,
17 base64,
18 fluid,
19 fixed,
20 traceSVG
21} = require(`gatsby-plugin-sharp`);
22
23const sharp = require(`./safe-sharp`);
24
25const fs = require(`fs`);
26
27const fsExtra = require(`fs-extra`);
28
29const imageSize = require(`probe-image-size`);
30
31const path = require(`path`);
32
33const DEFAULT_PNG_COMPRESSION_SPEED = 4;
34
35const {
36 ImageFormatType,
37 ImageCropFocusType,
38 DuotoneGradientType,
39 PotraceTurnPolicyType,
40 PotraceType,
41 ImageFitType
42} = require(`./types`);
43
44function toArray(buf) {
45 const arr = new Array(buf.length);
46
47 for (let i = 0; i < buf.length; i++) {
48 arr[i] = buf[i];
49 }
50
51 return arr;
52}
53
54const getTracedSVG = async ({
55 file,
56 image,
57 fieldArgs,
58 cache,
59 reporter
60}) => traceSVG({
61 file,
62 args: { ...fieldArgs.traceSVG
63 },
64 fileArgs: fieldArgs,
65 cache,
66 reporter
67});
68
69const fixedNodeType = ({
70 pathPrefix,
71 getNodeAndSavePathDependency,
72 reporter,
73 name,
74 cache
75}) => {
76 return {
77 type: new GraphQLObjectType({
78 name: name,
79 fields: {
80 base64: {
81 type: GraphQLString
82 },
83 tracedSVG: {
84 type: GraphQLString,
85 resolve: parent => getTracedSVG({ ...parent,
86 cache,
87 reporter
88 })
89 },
90 aspectRatio: {
91 type: GraphQLFloat
92 },
93 width: {
94 type: new GraphQLNonNull(GraphQLFloat)
95 },
96 height: {
97 type: new GraphQLNonNull(GraphQLFloat)
98 },
99 src: {
100 type: new GraphQLNonNull(GraphQLString)
101 },
102 srcSet: {
103 type: new GraphQLNonNull(GraphQLString)
104 },
105 srcWebp: {
106 type: GraphQLString,
107 resolve: ({
108 file,
109 image,
110 fieldArgs
111 }) => {
112 // If the file is already in webp format or should explicitly
113 // be converted to webp, we do not create additional webp files
114 if (file.extension === `webp` || fieldArgs.toFormat === `webp`) {
115 return null;
116 }
117
118 const args = { ...fieldArgs,
119 pathPrefix,
120 toFormat: `webp`
121 };
122 return Promise.resolve(fixed({
123 file,
124 args,
125 reporter,
126 cache
127 })).then(({
128 src
129 }) => src);
130 }
131 },
132 srcSetWebp: {
133 type: GraphQLString,
134 resolve: ({
135 file,
136 image,
137 fieldArgs
138 }) => {
139 if (file.extension === `webp` || fieldArgs.toFormat === `webp`) {
140 return null;
141 }
142
143 const args = { ...fieldArgs,
144 pathPrefix,
145 toFormat: `webp`
146 };
147 return Promise.resolve(fixed({
148 file,
149 args,
150 reporter,
151 cache
152 })).then(({
153 srcSet
154 }) => srcSet);
155 }
156 },
157 originalName: {
158 type: GraphQLString
159 }
160 }
161 }),
162 args: {
163 width: {
164 type: GraphQLInt
165 },
166 height: {
167 type: GraphQLInt
168 },
169 base64Width: {
170 type: GraphQLInt
171 },
172 jpegProgressive: {
173 type: GraphQLBoolean,
174 defaultValue: true
175 },
176 pngCompressionSpeed: {
177 type: GraphQLInt,
178 defaultValue: DEFAULT_PNG_COMPRESSION_SPEED
179 },
180 grayscale: {
181 type: GraphQLBoolean,
182 defaultValue: false
183 },
184 duotone: {
185 type: DuotoneGradientType,
186 defaultValue: false
187 },
188 traceSVG: {
189 type: PotraceType,
190 defaultValue: false
191 },
192 quality: {
193 type: GraphQLInt
194 },
195 jpegQuality: {
196 type: GraphQLInt
197 },
198 pngQuality: {
199 type: GraphQLInt
200 },
201 webpQuality: {
202 type: GraphQLInt
203 },
204 toFormat: {
205 type: ImageFormatType,
206 defaultValue: ``
207 },
208 toFormatBase64: {
209 type: ImageFormatType,
210 defaultValue: ``
211 },
212 cropFocus: {
213 type: ImageCropFocusType,
214 defaultValue: sharp.strategy.attention
215 },
216 fit: {
217 type: ImageFitType,
218 defaultValue: sharp.fit.cover
219 },
220 background: {
221 type: GraphQLString,
222 defaultValue: `rgba(0,0,0,1)`
223 },
224 rotate: {
225 type: GraphQLInt,
226 defaultValue: 0
227 },
228 trim: {
229 type: GraphQLFloat,
230 defaultValue: false
231 }
232 },
233 resolve: (image, fieldArgs, context) => {
234 const file = getNodeAndSavePathDependency(image.parent, context.path);
235 const args = { ...fieldArgs,
236 pathPrefix
237 };
238 return Promise.resolve(fixed({
239 file,
240 args,
241 reporter,
242 cache
243 })).then(o => Object.assign({}, o, {
244 fieldArgs: args,
245 image,
246 file
247 }));
248 }
249 };
250};
251
252const fluidNodeType = ({
253 pathPrefix,
254 getNodeAndSavePathDependency,
255 reporter,
256 name,
257 cache
258}) => {
259 return {
260 type: new GraphQLObjectType({
261 name: name,
262 fields: {
263 base64: {
264 type: GraphQLString
265 },
266 tracedSVG: {
267 type: GraphQLString,
268 resolve: parent => getTracedSVG({ ...parent,
269 cache,
270 reporter
271 })
272 },
273 aspectRatio: {
274 type: new GraphQLNonNull(GraphQLFloat)
275 },
276 src: {
277 type: new GraphQLNonNull(GraphQLString)
278 },
279 srcSet: {
280 type: new GraphQLNonNull(GraphQLString)
281 },
282 srcWebp: {
283 type: GraphQLString,
284 resolve: ({
285 file,
286 image,
287 fieldArgs
288 }) => {
289 if (image.extension === `webp` || fieldArgs.toFormat === `webp`) {
290 return null;
291 }
292
293 const args = { ...fieldArgs,
294 pathPrefix,
295 toFormat: `webp`
296 };
297 return Promise.resolve(fluid({
298 file,
299 args,
300 reporter,
301 cache
302 })).then(({
303 src
304 }) => src);
305 }
306 },
307 srcSetWebp: {
308 type: GraphQLString,
309 resolve: ({
310 file,
311 image,
312 fieldArgs
313 }) => {
314 if (image.extension === `webp` || fieldArgs.toFormat === `webp`) {
315 return null;
316 }
317
318 const args = { ...fieldArgs,
319 pathPrefix,
320 toFormat: `webp`
321 };
322 return Promise.resolve(fluid({
323 file,
324 args,
325 reporter,
326 cache
327 })).then(({
328 srcSet
329 }) => srcSet);
330 }
331 },
332 sizes: {
333 type: new GraphQLNonNull(GraphQLString)
334 },
335 originalImg: {
336 type: GraphQLString
337 },
338 originalName: {
339 type: GraphQLString
340 },
341 presentationWidth: {
342 type: new GraphQLNonNull(GraphQLInt)
343 },
344 presentationHeight: {
345 type: new GraphQLNonNull(GraphQLInt)
346 }
347 }
348 }),
349 args: {
350 maxWidth: {
351 type: GraphQLInt
352 },
353 maxHeight: {
354 type: GraphQLInt
355 },
356 base64Width: {
357 type: GraphQLInt
358 },
359 grayscale: {
360 type: GraphQLBoolean,
361 defaultValue: false
362 },
363 jpegProgressive: {
364 type: GraphQLBoolean,
365 defaultValue: true
366 },
367 pngCompressionSpeed: {
368 type: GraphQLInt,
369 defaultValue: DEFAULT_PNG_COMPRESSION_SPEED
370 },
371 duotone: {
372 type: DuotoneGradientType,
373 defaultValue: false
374 },
375 traceSVG: {
376 type: PotraceType,
377 defaultValue: false
378 },
379 quality: {
380 type: GraphQLInt
381 },
382 jpegQuality: {
383 type: GraphQLInt
384 },
385 pngQuality: {
386 type: GraphQLInt
387 },
388 webpQuality: {
389 type: GraphQLInt
390 },
391 toFormat: {
392 type: ImageFormatType,
393 defaultValue: ``
394 },
395 toFormatBase64: {
396 type: ImageFormatType,
397 defaultValue: ``
398 },
399 cropFocus: {
400 type: ImageCropFocusType,
401 defaultValue: sharp.strategy.attention
402 },
403 fit: {
404 type: ImageFitType,
405 defaultValue: sharp.fit.cover
406 },
407 background: {
408 type: GraphQLString,
409 defaultValue: `rgba(0,0,0,1)`
410 },
411 rotate: {
412 type: GraphQLInt,
413 defaultValue: 0
414 },
415 trim: {
416 type: GraphQLFloat,
417 defaultValue: false
418 },
419 sizes: {
420 type: GraphQLString,
421 defaultValue: ``
422 },
423 srcSetBreakpoints: {
424 type: GraphQLList(GraphQLInt),
425 defaultValue: [],
426 description: `A list of image widths to be generated. Example: [ 200, 340, 520, 890 ]`
427 }
428 },
429 resolve: (image, fieldArgs, context) => {
430 const file = getNodeAndSavePathDependency(image.parent, context.path);
431 const args = { ...fieldArgs,
432 pathPrefix
433 };
434 return Promise.resolve(fluid({
435 file,
436 args,
437 reporter,
438 cache
439 })).then(o => Object.assign({}, o, {
440 fieldArgs: args,
441 image,
442 file
443 }));
444 }
445 };
446};
447/**
448 * Keeps track of asynchronous file copy to prevent sequence errors in the
449 * underlying fs-extra module during parallel copies of the same file
450 */
451
452
453const inProgressCopy = new Set();
454
455const createFields = ({
456 pathPrefix,
457 getNodeAndSavePathDependency,
458 reporter,
459 cache
460}) => {
461 const nodeOptions = {
462 pathPrefix,
463 getNodeAndSavePathDependency,
464 reporter,
465 cache
466 }; // TODO: Remove resolutionsNode and sizesNode for Gatsby v3
467
468 const fixedNode = fixedNodeType({
469 name: `ImageSharpFixed`,
470 ...nodeOptions
471 });
472 const resolutionsNode = fixedNodeType({
473 name: `ImageSharpResolutions`,
474 ...nodeOptions
475 });
476 resolutionsNode.deprecationReason = `Resolutions was deprecated in Gatsby v2. It's been renamed to "fixed" https://example.com/write-docs-and-fix-this-example-link`;
477 const fluidNode = fluidNodeType({
478 name: `ImageSharpFluid`,
479 ...nodeOptions
480 });
481 const sizesNode = fluidNodeType({
482 name: `ImageSharpSizes`,
483 ...nodeOptions
484 });
485 sizesNode.deprecationReason = `Sizes was deprecated in Gatsby v2. It's been renamed to "fluid" https://example.com/write-docs-and-fix-this-example-link`;
486 return {
487 fixed: fixedNode,
488 resolutions: resolutionsNode,
489 fluid: fluidNode,
490 sizes: sizesNode,
491 original: {
492 type: new GraphQLObjectType({
493 name: `ImageSharpOriginal`,
494 fields: {
495 width: {
496 type: GraphQLFloat
497 },
498 height: {
499 type: GraphQLFloat
500 },
501 src: {
502 type: GraphQLString
503 }
504 }
505 }),
506 args: {},
507
508 async resolve(image, fieldArgs, context) {
509 const details = getNodeAndSavePathDependency(image.parent, context.path);
510 const dimensions = imageSize.sync(toArray(fs.readFileSync(details.absolutePath)));
511 const imageName = `${details.name}-${image.internal.contentDigest}${details.ext}`;
512 const publicPath = path.join(process.cwd(), `public`, `static`, imageName);
513
514 if (!fsExtra.existsSync(publicPath) && !inProgressCopy.has(publicPath)) {
515 // keep track of in progress copy, we should rely on `existsSync` but
516 // a race condition exists between the exists check and the copy
517 inProgressCopy.add(publicPath);
518 fsExtra.copy(details.absolutePath, publicPath, err => {
519 // this is no longer in progress
520 inProgressCopy.delete(publicPath);
521
522 if (err) {
523 console.error(`error copying file from ${details.absolutePath} to ${publicPath}`, err);
524 }
525 });
526 }
527
528 return {
529 width: dimensions.width,
530 height: dimensions.height,
531 src: `${pathPrefix}/static/${imageName}`
532 };
533 }
534
535 },
536 resize: {
537 type: new GraphQLObjectType({
538 name: `ImageSharpResize`,
539 fields: {
540 src: {
541 type: GraphQLString
542 },
543 tracedSVG: {
544 type: GraphQLString,
545 resolve: parent => getTracedSVG({ ...parent,
546 cache,
547 reporter
548 })
549 },
550 width: {
551 type: GraphQLInt
552 },
553 height: {
554 type: GraphQLInt
555 },
556 aspectRatio: {
557 type: GraphQLFloat
558 },
559 originalName: {
560 type: GraphQLString
561 }
562 }
563 }),
564 args: {
565 width: {
566 type: GraphQLInt
567 },
568 height: {
569 type: GraphQLInt
570 },
571 quality: {
572 type: GraphQLInt
573 },
574 jpegQuality: {
575 type: GraphQLInt
576 },
577 pngQuality: {
578 type: GraphQLInt
579 },
580 webpQuality: {
581 type: GraphQLInt
582 },
583 jpegProgressive: {
584 type: GraphQLBoolean,
585 defaultValue: true
586 },
587 pngCompressionLevel: {
588 type: GraphQLInt,
589 defaultValue: 9
590 },
591 pngCompressionSpeed: {
592 type: GraphQLInt,
593 defaultValue: DEFAULT_PNG_COMPRESSION_SPEED
594 },
595 grayscale: {
596 type: GraphQLBoolean,
597 defaultValue: false
598 },
599 duotone: {
600 type: DuotoneGradientType,
601 defaultValue: false
602 },
603 base64: {
604 type: GraphQLBoolean,
605 defaultValue: false
606 },
607 traceSVG: {
608 type: PotraceType,
609 defaultValue: false
610 },
611 toFormat: {
612 type: ImageFormatType,
613 defaultValue: ``
614 },
615 cropFocus: {
616 type: ImageCropFocusType,
617 defaultValue: sharp.strategy.attention
618 },
619 fit: {
620 type: ImageFitType,
621 defaultValue: sharp.fit.cover
622 },
623 background: {
624 type: GraphQLString,
625 defaultValue: `rgba(0,0,0,1)`
626 },
627 rotate: {
628 type: GraphQLInt,
629 defaultValue: 0
630 },
631 trim: {
632 type: GraphQLFloat,
633 defaultValue: 0
634 }
635 },
636 resolve: (image, fieldArgs, context) => {
637 const file = getNodeAndSavePathDependency(image.parent, context.path);
638 const args = { ...fieldArgs,
639 pathPrefix
640 };
641 return new Promise(resolve => {
642 if (fieldArgs.base64) {
643 resolve(base64({
644 file,
645 cache
646 }));
647 } else {
648 const o = queueImageResizing({
649 file,
650 args
651 });
652 resolve(Object.assign({}, o, {
653 image,
654 file,
655 fieldArgs: args
656 }));
657 }
658 });
659 }
660 }
661 };
662};
663
664module.exports = ({
665 actions,
666 schema,
667 pathPrefix,
668 getNodeAndSavePathDependency,
669 reporter,
670 cache
671}) => {
672 const {
673 createTypes
674 } = actions;
675 const imageSharpType = schema.buildObjectType({
676 name: `ImageSharp`,
677 fields: createFields({
678 pathPrefix,
679 getNodeAndSavePathDependency,
680 reporter,
681 cache
682 }),
683 interfaces: [`Node`],
684 extensions: {
685 infer: true,
686 childOf: {
687 types: [`File`]
688 }
689 }
690 });
691
692 if (createTypes) {
693 createTypes([ImageFormatType, ImageFitType, ImageCropFocusType, DuotoneGradientType, PotraceTurnPolicyType, PotraceType, imageSharpType]);
694 }
695};
\No newline at end of file