From fb1a51b2fc5a468c2763ddaac0ffe0d48590e0d8 Mon Sep 17 00:00:00 2001 From: pieh Date: Thu, 24 Nov 2022 08:57:39 +0100 Subject: [PATCH 01/24] init --- examples/using-contentful/package.json | 3 +- .../gatsby-plugin-image/src/resolver-utils.ts | 6 +- packages/gatsby-plugin-sharp/package.json | 1 - packages/gatsby-plugin-sharp/src/index.js | 14 +- packages/gatsby-plugin-sharp/src/trace-svg.js | 187 ------------------ packages/gatsby-plugin-utils/package.json | 1 - .../graphql/gatsby-image-resolver.ts | 6 +- .../placeholder-handler.ts | 82 ++------ packages/gatsby-remark-images/package.json | 1 - .../gatsby-remark-images/src/gatsby-node.js | 32 +-- .../src/gatsby-plugin-image.js | 18 +- .../gatsby-transformer-sharp/package.json | 1 - .../src/customize-schema.js | 19 +- .../gatsby-transformer-sharp/src/types.ts | 13 +- yarn.lock | 12 -- 15 files changed, 81 insertions(+), 315 deletions(-) delete mode 100644 packages/gatsby-plugin-sharp/src/trace-svg.js diff --git a/examples/using-contentful/package.json b/examples/using-contentful/package.json index 4a04b83b91de1..f1a66f4698077 100644 --- a/examples/using-contentful/package.json +++ b/examples/using-contentful/package.json @@ -30,6 +30,7 @@ "scripts": { "develop": "gatsby develop", "build": "gatsby build", + "clean": "gatsby clean", "start": "gatsby serve" } -} \ No newline at end of file +} diff --git a/packages/gatsby-plugin-image/src/resolver-utils.ts b/packages/gatsby-plugin-image/src/resolver-utils.ts index 513b6daacca9d..5852d0e76d739 100644 --- a/packages/gatsby-plugin-image/src/resolver-utils.ts +++ b/packages/gatsby-plugin-image/src/resolver-utils.ts @@ -202,9 +202,9 @@ export function getGatsbyImageFieldConfig( type: ImagePlaceholderType.name, description: stripIndent` Format of generated placeholder image, displayed while the main image loads. - BLURRED: a blurred, low resolution image, encoded as a base64 data URI (default) - DOMINANT_COLOR: a solid color, calculated from the dominant color of the image. - TRACED_SVG: a low-resolution traced SVG of the image. + BLURRED: a blurred, low resolution image, encoded as a base64 data URI. + DOMINANT_COLOR: a solid color, calculated from the dominant color of the image (default). + TRACED_SVG: deprecated. Will use DOMINANT_COLOR. NONE: no placeholder. Set the argument "backgroundColor" to use a fixed background color.`, }, formats: { diff --git a/packages/gatsby-plugin-sharp/package.json b/packages/gatsby-plugin-sharp/package.json index a8a1313b8bdf2..cf2e03b8272dd 100644 --- a/packages/gatsby-plugin-sharp/package.json +++ b/packages/gatsby-plugin-sharp/package.json @@ -8,7 +8,6 @@ }, "dependencies": { "@babel/runtime": "^7.15.4", - "@gatsbyjs/potrace": "^2.3.0", "async": "^3.2.4", "bluebird": "^3.7.2", "debug": "^4.3.4", diff --git a/packages/gatsby-plugin-sharp/src/index.js b/packages/gatsby-plugin-sharp/src/index.js index f770efd3f5912..3068bd9d40d7a 100644 --- a/packages/gatsby-plugin-sharp/src/index.js +++ b/packages/gatsby-plugin-sharp/src/index.js @@ -15,7 +15,6 @@ const { createTransformObject, removeDefaultValues, } = require(`./plugin-options`) -const { memoizedTraceSVG, notMemoizedtraceSVG } = require(`./trace-svg`) const duotone = require(`./duotone`) const { IMAGE_PROCESSING_JOB_NAME } = require(`./gatsby-worker`) const { getDimensionsAndAspectRatio } = require(`./utils`) @@ -382,12 +381,17 @@ async function base64(arg) { return await memoizedBase64(arg) } +let didShow = false async function traceSVG(args) { - if (args.cache) { - // Not all transformer plugins are going to provide cache - return await cachifiedProcess(args, generateCacheKey, notMemoizedtraceSVG) + if (!didShow) { + console.trace( + `[gatsby-plugin-sharp traceSVG()] traceSVG is no longer supported, falling back to base64` + ) + didShow = true } - return await memoizedTraceSVG(args) + + const { src } = await base64(args) + return src } async function getTracedSVG({ file, options, cache, reporter }) { diff --git a/packages/gatsby-plugin-sharp/src/trace-svg.js b/packages/gatsby-plugin-sharp/src/trace-svg.js deleted file mode 100644 index d216b192425a9..0000000000000 --- a/packages/gatsby-plugin-sharp/src/trace-svg.js +++ /dev/null @@ -1,187 +0,0 @@ -const { promisify } = require(`bluebird`) -const fs = require(`fs-extra`) -const _ = require(`lodash`) -const tmpDir = require(`os`).tmpdir() -const path = require(`path`) -const sharp = require(`./safe-sharp`) -const filenamify = require(`filenamify`) -const duotone = require(`./duotone`) -const { getPluginOptions, healOptions } = require(`./plugin-options`) -const { reportError } = require(`./report-error`) -const { - createContentDigest, -} = require(`gatsby-core-utils/create-content-digest`) - -exports.notMemoizedPrepareTraceSVGInputFile = async ({ - file, - options, - tmpFilePath, - reporter, -}) => { - let pipeline - try { - pipeline = sharp() - - if (!options.rotate) { - pipeline.rotate() - } - fs.createReadStream(file.absolutePath).pipe(pipeline) - } catch (err) { - reportError(`Failed to process image ${file.absolutePath}`, err, reporter) - return - } - - pipeline - .resize(options.width, options.height, { - position: options.cropFocus, - }) - .png({ - compressionLevel: options.pngCompressionLevel, - adaptiveFiltering: false, - force: options.toFormat === `png`, - }) - .jpeg({ - quality: options.quality, - progressive: options.jpegProgressive, - force: options.toFormat === `jpg`, - }) - - // grayscale - if (options.grayscale) { - pipeline = pipeline.grayscale() - } - - // rotate - if (options.rotate && options.rotate !== 0) { - pipeline = pipeline.rotate(options.rotate) - } - - // duotone - if (options.duotone) { - pipeline = await duotone(options.duotone, options.toFormat, pipeline) - } - - await new Promise((resolve, reject) => - pipeline.toFile(tmpFilePath, err => { - if (err) { - return reject(err) - } - return resolve() - }) - ) -} - -const optimize = svg => { - const { optimize } = require(`svgo`) - const { data } = optimize(svg, { - multipass: true, - floatPrecision: 0, - plugins: [ - { - name: `preset-default`, - params: { - overrides: { - // disable removeViewBox plugin - removeViewBox: false, - }, - }, - }, - { - name: `addAttributesToSVGElement`, - params: { - attributes: [ - { - preserveAspectRatio: `none`, - }, - ], - }, - }, - ], - }) - return data -} - -exports.notMemoizedtraceSVG = async ({ file, args, fileArgs, reporter }) => { - const options = healOptions( - getPluginOptions(), - { - // use maxWidth/maxHeight as width/height if available - // if width/height is used in fileArgs, the maxWidth/maxHeight - // values will be overritten - ...(fileArgs && fileArgs.maxWidth && fileArgs.maxHeight - ? { - height: fileArgs.maxHeight, - width: fileArgs.maxWidth, - } - : {}), - ...fileArgs, - }, - file.extension - ) - - const optionsHash = createContentDigest(options) - - const tmpFilePath = path.join( - tmpDir, - filenamify(`${file.internal.contentDigest}-${file.name}-${optionsHash}`) + - `.${file.extension}` - ) - - await exports.memoizedPrepareTraceSVGInputFile({ - tmpFilePath, - file, - options, - reporter, - }) - - const svgToMiniDataURI = require(`mini-svg-data-uri`) - const potrace = require(`@gatsbyjs/potrace`) - const trace = promisify(potrace.trace) - - const defaultArgs = { - color: `lightgray`, - optTolerance: 0.4, - turdSize: 100, - turnPolicy: potrace.Potrace.TURNPOLICY_MAJORITY, - } - - const optionsSVG = _.defaults({}, args, defaultArgs) - - // `srcset` attribute rejects URIs with literal spaces - const encodeSpaces = str => str.replace(/ /gi, `%20`) - - return trace(tmpFilePath, optionsSVG) - .then(optimize) - .then(svgToMiniDataURI) - .then(encodeSpaces) -} - -let memoizedPrepareTraceSVGInputFile -let memoizedTraceSVG -const createMemoizedFunctions = () => { - exports.memoizedPrepareTraceSVGInputFile = memoizedPrepareTraceSVGInputFile = - _.memoize( - exports.notMemoizedPrepareTraceSVGInputFile, - ({ tmpFilePath }) => tmpFilePath - ) - - exports.memoizedTraceSVG = memoizedTraceSVG = _.memoize( - exports.notMemoizedtraceSVG, - ({ file, args, fileArgs }) => - `${file.internal.contentDigest}${JSON.stringify(args)}${JSON.stringify( - fileArgs - )}` - ) -} - -// This is very hacky, but memoized function are pretty tricky to spy on -// in tests ;( -createMemoizedFunctions() -exports.createMemoizedFunctions = () => { - createMemoizedFunctions() -} - -exports.clearMemoizeCaches = () => { - memoizedTraceSVG.cache.clear() - memoizedPrepareTraceSVGInputFile.cache.clear() -} diff --git a/packages/gatsby-plugin-utils/package.json b/packages/gatsby-plugin-utils/package.json index 6e7612a80f6ee..81cd1650b1480 100644 --- a/packages/gatsby-plugin-utils/package.json +++ b/packages/gatsby-plugin-utils/package.json @@ -47,7 +47,6 @@ "homepage": "https://github.com/gatsbyjs/gatsby/tree/master/packages/gatsby-plugin-utils#readme", "dependencies": { "@babel/runtime": "^7.15.4", - "@gatsbyjs/potrace": "^2.3.0", "fastq": "^1.13.0", "fs-extra": "^10.1.0", "gatsby-core-utils": "^4.2.0-next.0", diff --git a/packages/gatsby-plugin-utils/src/polyfill-remote-file/graphql/gatsby-image-resolver.ts b/packages/gatsby-plugin-utils/src/polyfill-remote-file/graphql/gatsby-image-resolver.ts index 495b29005e963..128ebc7f4cd1b 100644 --- a/packages/gatsby-plugin-utils/src/polyfill-remote-file/graphql/gatsby-image-resolver.ts +++ b/packages/gatsby-plugin-utils/src/polyfill-remote-file/graphql/gatsby-image-resolver.ts @@ -305,9 +305,9 @@ export function generateGatsbyImageFieldConfig( defaultValue: enums.placeholder.getField(`DOMINANT_COLOR`).value, description: stripIndent` Format of generated placeholder image, displayed while the main image loads. - BLURRED: a blurred, low resolution image, encoded as a base64 data URI (default) - DOMINANT_COLOR: a solid color, calculated from the dominant color of the image. - TRACED_SVG: a low-resolution traced SVG of the image. + BLURRED: a blurred, low resolution image, encoded as a base64 data URI + DOMINANT_COLOR: a solid color, calculated from the dominant color of the image (default). + TRACED_SVG: deprecated. Will use DOMINANT_COLOR. NONE: no placeholder. Set the argument "backgroundColor" to use a fixed background color.`, }, aspectRatio: { diff --git a/packages/gatsby-plugin-utils/src/polyfill-remote-file/placeholder-handler.ts b/packages/gatsby-plugin-utils/src/polyfill-remote-file/placeholder-handler.ts index f1ac0303349fd..42cbeac3cd0b8 100644 --- a/packages/gatsby-plugin-utils/src/polyfill-remote-file/placeholder-handler.ts +++ b/packages/gatsby-plugin-utils/src/polyfill-remote-file/placeholder-handler.ts @@ -34,6 +34,8 @@ const PLACEHOLDER_TRACED_WIDTH = 200 let tmpDir: string +let didShow = false + const queue = Queue< undefined, { @@ -66,6 +68,16 @@ const queue = Queue< httpHeaders, }) + if (type === PlaceholderType.TRACED_SVG) { + if (!didShow) { + console.trace( + `[gatsby-plugin-utils placeholder-handler queue handler] traceSVG is no longer supported, falling back to dominantColor` + ) + didShow = true + } + type = PlaceholderType.DOMINANT_COLOR + } + switch (type) { case PlaceholderType.BLURRED: { let buffer: Buffer @@ -99,76 +111,6 @@ const queue = Queue< : `rgba(0,0,0,0)` ) } - case PlaceholderType.TRACED_SVG: { - let buffer: Buffer - - try { - const fileStream = createReadStream(filePath) - const pipeline = sharp() - fileStream.pipe(pipeline) - buffer = await pipeline - .resize( - PLACEHOLDER_BASE64_WIDTH, - Math.ceil(PLACEHOLDER_BASE64_WIDTH / (width / height)) - ) - .toBuffer() - } catch (err) { - buffer = await readFile(filePath) - } - - const [{ trace, Potrace }, { optimize }, { default: svgToMiniDataURI }] = - await Promise.all([ - import(`@gatsbyjs/potrace`), - import(`svgo`), - import(`mini-svg-data-uri`), - ]) - - trace( - buffer, - { - color: `lightgray`, - optTolerance: 0.4, - turdSize: 100, - turnPolicy: Potrace.TURNPOLICY_MAJORITY, - }, - async (err, svg) => { - if (err) { - return cb(err) - } - - try { - const { data } = await optimize(svg, { - multipass: true, - floatPrecision: 0, - plugins: [ - { - name: `preset-default`, - params: { - overrides: { - // customize default plugin options - removeViewBox: false, - - // or disable plugins - addAttributesToSVGElement: { - attributes: [ - { - preserveAspectRatio: `none`, - }, - ], - }, - }, - }, - }, - ], - }) - - return cb(null, svgToMiniDataURI(data).replace(/ /gi, `%20`)) - } catch (err) { - return cb(err) - } - } - ) - } } }, QUEUE_CONCURRENCY) diff --git a/packages/gatsby-remark-images/package.json b/packages/gatsby-remark-images/package.json index 12830d574d837..59c8fa1b996fd 100644 --- a/packages/gatsby-remark-images/package.json +++ b/packages/gatsby-remark-images/package.json @@ -8,7 +8,6 @@ }, "dependencies": { "@babel/runtime": "^7.15.4", - "@gatsbyjs/potrace": "^2.3.0", "chalk": "^4.1.2", "cheerio": "^1.0.0-rc.10", "gatsby-core-utils": "^4.2.0-next.0", diff --git a/packages/gatsby-remark-images/src/gatsby-node.js b/packages/gatsby-remark-images/src/gatsby-node.js index 25f9f2f6de9a5..6b122385b73e6 100644 --- a/packages/gatsby-remark-images/src/gatsby-node.js +++ b/packages/gatsby-remark-images/src/gatsby-node.js @@ -1,5 +1,3 @@ -const { Potrace } = require(`@gatsbyjs/potrace`) - exports.pluginOptionsSchema = function ({ Joi }) { return Joi.object({ maxWidth: Joi.number() @@ -69,30 +67,34 @@ exports.pluginOptionsSchema = function ({ Joi }) { `TURNPOLICY_MINORITY`, `TURNPOLICY_MAJORITY`, // it also allow using actual policy values - Potrace.TURNPOLICY_BLACK, - Potrace.TURNPOLICY_WHITE, - Potrace.TURNPOLICY_LEFT, - Potrace.TURNPOLICY_RIGHT, - Potrace.TURNPOLICY_MINORITY, - Potrace.TURNPOLICY_MAJORITY + `black`, + `white`, + `left`, + `right`, + `minority`, + `majority` ) - .default(Potrace.TURNPOLICY_MAJORITY), + .default(`majority`), turdSize: Joi.number().default(100), alphaMax: Joi.number(), optCurve: Joi.boolean().default(true), optTolerance: Joi.number().default(0.4), threshold: Joi.alternatives() - .try( - Joi.number().min(0).max(255), - Joi.number().valid(Potrace.THRESHOLD_AUTO) - ) - .default(Potrace.THRESHOLD_AUTO), + .try(Joi.number().min(0).max(255), Joi.number().valid(-1)) + .default(-1), blackOnWhite: Joi.boolean().default(true), color: Joi.string().default(`lightgray`), background: Joi.string().default(`transparent`), }) ) - .default(false) + .custom(value => { + if (!!value && !process.env.GATSBY_WORKER_ID) { + console.warn( + `[gatsby-remark-images pluginOptions] traceSVG is no longer supported, falling back to blurred` + ) + } + return undefined + }) .description( `Use traced SVGs for placeholder images instead of the “blur up” effect. Pass true for traced SVGs with the default settings (seen here), or an object of options to override the default. For example, pass { color: "#F00", turnPolicy: "TURNPOLICY_MAJORITY" } to change the color of the trace to red and the turn policy to TURNPOLICY_MAJORITY. See node-potrace parameter documentation for a full listing and explanation of the available options.` ), diff --git a/packages/gatsby-source-contentful/src/gatsby-plugin-image.js b/packages/gatsby-source-contentful/src/gatsby-plugin-image.js index d642aff4ffc7a..6094aa539b640 100644 --- a/packages/gatsby-source-contentful/src/gatsby-plugin-image.js +++ b/packages/gatsby-source-contentful/src/gatsby-plugin-image.js @@ -252,6 +252,8 @@ export function generateImageSource( return { width, height, format: toFormat, src } } +let didShow = false + export async function resolveGatsbyImageData( image, options, @@ -286,6 +288,16 @@ export async function resolveGatsbyImageData( options = doMergeDefaults(options, defaults) + if (options.placeholder === `tracedSVG`) { + if (!didShow) { + console.trace( + `[gatsby-source-contentful resolveGatsbytImageData] traceSVG is no longer supported, falling back to dominantColor` + ) + didShow = true + } + options.placeholder = `dominantColor` + } + const { baseUrl, contentType, width, height } = getBasicImageProps( image, options @@ -336,11 +348,7 @@ export async function resolveGatsbyImageData( } if (options.placeholder === `tracedSVG`) { - placeholderDataURI = await getTracedSVG({ - image, - options, - cache, - }) + console.error(`this shouldn't happen`) } if (placeholderDataURI) { diff --git a/packages/gatsby-transformer-sharp/package.json b/packages/gatsby-transformer-sharp/package.json index b37b040158628..a135363d3c0a5 100644 --- a/packages/gatsby-transformer-sharp/package.json +++ b/packages/gatsby-transformer-sharp/package.json @@ -8,7 +8,6 @@ }, "dependencies": { "@babel/runtime": "^7.15.4", - "@gatsbyjs/potrace": "^2.3.0", "bluebird": "^3.7.2", "common-tags": "^1.8.2", "fs-extra": "^10.1.0", diff --git a/packages/gatsby-transformer-sharp/src/customize-schema.js b/packages/gatsby-transformer-sharp/src/customize-schema.js index 88fdb4ef1e199..29c991ed3f5bc 100644 --- a/packages/gatsby-transformer-sharp/src/customize-schema.js +++ b/packages/gatsby-transformer-sharp/src/customize-schema.js @@ -447,9 +447,9 @@ const imageNodeType = ({ type: ImagePlaceholderType, description: stripIndent` Format of generated placeholder image, displayed while the main image loads. - BLURRED: a blurred, low resolution image, encoded as a base64 data URI (default) - DOMINANT_COLOR: a solid color, calculated from the dominant color of the image. - TRACED_SVG: a low-resolution traced SVG of the image. + BLURRED: a blurred, low resolution image, encoded as a base64 data URI + DOMINANT_COLOR: a solid color, calculated from the dominant color of the image (default). + TRACED_SVG: deprecated. Will use DOMINANT_COLOR. NONE: no placeholder. Set "background" to use a fixed background color.`, }, blurredOptions: { @@ -529,6 +529,17 @@ const imageNodeType = ({ reporter.warn(`Please upgrade gatsby-plugin-sharp`) return null } + + if (fieldArgs?.placeholder === `tracedSVG`) { + if (!didShow) { + console.trace( + `[gatsby-transformer-sharp gatsbyImageData resolver] traceSVG is no longer supported, falling back to dominantColor` + ) + didShow = true + } + fieldArgs.placeholder = `dominantColor` + } + const imageData = await generateImageData({ file, args: fieldArgs, @@ -542,6 +553,8 @@ const imageNodeType = ({ } } +let didShow = false + /** * Keeps track of asynchronous file copy to prevent sequence errors in the * underlying fs-extra module during parallel copies of the same file diff --git a/packages/gatsby-transformer-sharp/src/types.ts b/packages/gatsby-transformer-sharp/src/types.ts index 9c00f3042eb88..a3f1310ea9473 100644 --- a/packages/gatsby-transformer-sharp/src/types.ts +++ b/packages/gatsby-transformer-sharp/src/types.ts @@ -8,7 +8,6 @@ import { GraphQLNonNull, GraphQLInputFieldConfigMap, } from "gatsby/graphql" -import { Potrace } from "@gatsbyjs/potrace" import type Sharp from "sharp" const sharp: typeof Sharp = require(`./safe-sharp`) @@ -161,12 +160,12 @@ export const DuotoneGradientType = new GraphQLInputObjectType({ export const PotraceTurnPolicyType = new GraphQLEnumType({ name: `PotraceTurnPolicy`, values: { - TURNPOLICY_BLACK: { value: Potrace.TURNPOLICY_BLACK }, - TURNPOLICY_WHITE: { value: Potrace.TURNPOLICY_WHITE }, - TURNPOLICY_LEFT: { value: Potrace.TURNPOLICY_LEFT }, - TURNPOLICY_RIGHT: { value: Potrace.TURNPOLICY_RIGHT }, - TURNPOLICY_MINORITY: { value: Potrace.TURNPOLICY_MINORITY }, - TURNPOLICY_MAJORITY: { value: Potrace.TURNPOLICY_MAJORITY }, + TURNPOLICY_BLACK: { value: `black` }, + TURNPOLICY_WHITE: { value: `white` }, + TURNPOLICY_LEFT: { value: `left` }, + TURNPOLICY_RIGHT: { value: `right` }, + TURNPOLICY_MINORITY: { value: `minority` }, + TURNPOLICY_MAJORITY: { value: `majority` }, }, }) diff --git a/yarn.lock b/yarn.lock index b1359fe682628..5961970211828 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1638,13 +1638,6 @@ unique-filename "^1.1.1" which "^1.3.1" -"@gatsbyjs/potrace@^2.3.0": - version "2.3.0" - resolved "https://registry.yarnpkg.com/@gatsbyjs/potrace/-/potrace-2.3.0.tgz#0ac22fb56a02ebc64ce55e4666c4b741cbf27377" - integrity sha512-72szhSY/4tPiPPOzq15CG6LW0s9FuWQ86gkLSUvBNoF0s+jsEdRaZmATYNjiY2Skg//EuyPLEqUQnXKXME0szg== - dependencies: - jimp-compact "^0.16.1-2" - "@gatsbyjs/reach-router@^2.0.0-v2.0": version "2.0.0-v2.0.2" resolved "https://registry.yarnpkg.com/@gatsbyjs/reach-router/-/reach-router-2.0.0-v2.0.2.tgz#6b7e4846e4c68113b8e454fd8ea027ffad5a68c8" @@ -14126,11 +14119,6 @@ jest@^27.4.4: import-local "^3.0.2" jest-cli "^27.4.4" -jimp-compact@^0.16.1-2: - version "0.16.1-2" - resolved "https://registry.yarnpkg.com/jimp-compact/-/jimp-compact-0.16.1-2.tgz#a82ff9a5a81f15a4b61b5e2e50fae6a43305e5a9" - integrity sha512-b2A3rRT1TITzqmaO70U2/uunCh43BQVq7BfRwGPkD5xj8/WZsR3sPTy9DENt+dNZGsel3zBEm1UtYegUxjZW7A== - joi@^14.3.1: version "14.3.1" resolved "https://registry.yarnpkg.com/joi/-/joi-14.3.1.tgz#164a262ec0b855466e0c35eea2a885ae8b6c703c" From f3ab4a1c6c697a16fa37ecec37d9f856d12acbf3 Mon Sep 17 00:00:00 2001 From: pieh Date: Thu, 24 Nov 2022 09:40:51 +0100 Subject: [PATCH 02/24] gri --- .../src/__tests__/__snapshots__/index.js.snap | 62 +++++++++---------- .../src/__tests__/index.js | 14 +---- packages/gatsby-remark-images/src/index.js | 27 +------- 3 files changed, 34 insertions(+), 69 deletions(-) diff --git a/packages/gatsby-remark-images/src/__tests__/__snapshots__/index.js.snap b/packages/gatsby-remark-images/src/__tests__/__snapshots__/index.js.snap index 138fbf30a3090..af0871b2661c1 100644 --- a/packages/gatsby-remark-images/src/__tests__/__snapshots__/index.js.snap +++ b/packages/gatsby-remark-images/src/__tests__/__snapshots__/index.js.snap @@ -124,6 +124,37 @@ exports[`disableBgImageOnAlpha does not disable background image on transparent " `; +exports[`it doesn't use tracedSVG placeholder (deprecated and fallback to base64) 1`] = ` +" + + + \\"image\\" + + " +`; + exports[`it handles goofy nesting properly 1`] = ` "" `; -exports[`it uses tracedSVG placeholder when enabled 1`] = ` -" - - - \\"image\\" - - " -`; - exports[`markdownCaptions display title in markdown as caption when showCaptions === true && markdownCaptions === true 1`] = ` "
{ }) const Remark = require(`remark`) -const { Potrace } = require(`@gatsbyjs/potrace`) const queryString = require(`query-string`) const cheerio = require(`cheerio`) const toHAST = require(`mdast-util-to-hast`) @@ -453,7 +452,7 @@ test(`it transforms images in markdown with query strings`, async () => { expect(node.value).not.toMatch(``) }) -test(`it uses tracedSVG placeholder when enabled`, async () => { +test(`it doesn't use tracedSVG placeholder (deprecated and fallback to base64)`, async () => { const imagePath = `images/my-image.jpeg` const content = ` ![image](./${imagePath}) @@ -469,16 +468,7 @@ test(`it uses tracedSVG placeholder when enabled`, async () => { expect(node.type).toBe(`html`) expect(node.value).toMatchSnapshot() expect(node.value).not.toMatch(``) - expect(mockTraceSVG).toBeCalledTimes(1) - - expect(mockTraceSVG).toBeCalledWith( - expect.objectContaining({ - // fileArgs cannot be left undefined or traceSVG errors - fileArgs: expect.any(Object), - // args containing Potrace constants should be translated to their values - args: { color: Potrace.COLOR_AUTO, turnPolicy: Potrace.TURNPOLICY_LEFT }, - }) - ) + expect(mockTraceSVG).toBeCalledTimes(0) }) describe(`showCaptions`, () => { diff --git a/packages/gatsby-remark-images/src/index.js b/packages/gatsby-remark-images/src/index.js index 0f07da46b026f..12009f0c13e0e 100644 --- a/packages/gatsby-remark-images/src/index.js +++ b/packages/gatsby-remark-images/src/index.js @@ -347,32 +347,7 @@ module.exports = ( `.trim() } - let placeholderImageData = fluidResult.base64 - - // if options.tracedSVG is enabled generate the traced SVG and use that as the placeholder image - if (options.tracedSVG) { - let args = typeof options.tracedSVG === `object` ? options.tracedSVG : {} - - // Translate Potrace constants (e.g. TURNPOLICY_LEFT, COLOR_AUTO) to the values Potrace expects - const { Potrace } = require(`@gatsbyjs/potrace`) - const argsKeys = Object.keys(args) - args = argsKeys.reduce((result, key) => { - const value = args[key] - result[key] = Potrace.hasOwnProperty(value) ? Potrace[value] : value - return result - }, {}) - - const tracedSVG = await traceSVG({ - file: imageNode, - args, - fileArgs: args, - cache, - reporter, - }) - - // Escape single quotes so the SVG data can be used in inline style attribute with single quotes - placeholderImageData = tracedSVG.replace(/'/g, `\\'`) - } + const placeholderImageData = fluidResult.base64 const ratio = `${(1 / fluidResult.aspectRatio) * 100}%` From e881fb4afb6a71e48aeb1068928524eea3425de3 Mon Sep 17 00:00:00 2001 From: pieh Date: Thu, 24 Nov 2022 09:43:45 +0100 Subject: [PATCH 03/24] update shopify snapshot --- .../__tests__/__snapshots__/create-resolvers.ts.snap | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/gatsby-source-shopify/__tests__/__snapshots__/create-resolvers.ts.snap b/packages/gatsby-source-shopify/__tests__/__snapshots__/create-resolvers.ts.snap index bf094215a8117..66a9f7a38d9b6 100644 --- a/packages/gatsby-source-shopify/__tests__/__snapshots__/create-resolvers.ts.snap +++ b/packages/gatsby-source-shopify/__tests__/__snapshots__/create-resolvers.ts.snap @@ -106,9 +106,9 @@ Default is [ 1, 2 ] for fixed images, meaning 1x, 2x, 3x, and [0.25, 0.5, 1, 2] }, "placeholder": Object { "description": "Format of generated placeholder image, displayed while the main image loads. -BLURRED: a blurred, low resolution image, encoded as a base64 data URI (default) -DOMINANT_COLOR: a solid color, calculated from the dominant color of the image. -TRACED_SVG: a low-resolution traced SVG of the image. +BLURRED: a blurred, low resolution image, encoded as a base64 data URI. +DOMINANT_COLOR: a solid color, calculated from the dominant color of the image (default). +TRACED_SVG: deprecated. Will use DOMINANT_COLOR. NONE: no placeholder. Set the argument \\"backgroundColor\\" to use a fixed background color.", "type": "GatsbyImagePlaceholder", }, @@ -212,9 +212,9 @@ Default is [ 1, 2 ] for fixed images, meaning 1x, 2x, 3x, and [0.25, 0.5, 1, 2] }, "placeholder": Object { "description": "Format of generated placeholder image, displayed while the main image loads. -BLURRED: a blurred, low resolution image, encoded as a base64 data URI (default) -DOMINANT_COLOR: a solid color, calculated from the dominant color of the image. -TRACED_SVG: a low-resolution traced SVG of the image. +BLURRED: a blurred, low resolution image, encoded as a base64 data URI. +DOMINANT_COLOR: a solid color, calculated from the dominant color of the image (default). +TRACED_SVG: deprecated. Will use DOMINANT_COLOR. NONE: no placeholder. Set the argument \\"backgroundColor\\" to use a fixed background color.", "type": "GatsbyImagePlaceholder", }, From 80f358d3487a94afcfc63ffb9f3009405c2ca278 Mon Sep 17 00:00:00 2001 From: pieh Date: Thu, 24 Nov 2022 10:22:53 +0100 Subject: [PATCH 04/24] gatsby-plugin-sharp tests --- .../src/__tests__/__snapshots__/index.js.snap | 8 +-- .../src/__tests__/index.js | 12 +++- packages/gatsby-plugin-sharp/src/index.js | 62 ++++++++++++------- 3 files changed, 51 insertions(+), 31 deletions(-) diff --git a/packages/gatsby-plugin-sharp/src/__tests__/__snapshots__/index.js.snap b/packages/gatsby-plugin-sharp/src/__tests__/__snapshots__/index.js.snap index ea17e500aba82..4036b74d4cb8c 100644 --- a/packages/gatsby-plugin-sharp/src/__tests__/__snapshots__/index.js.snap +++ b/packages/gatsby-plugin-sharp/src/__tests__/__snapshots__/index.js.snap @@ -1397,7 +1397,7 @@ Object { } `; -exports[`gatsby-plugin-sharp tracedSVG runs on demand 1`] = ` +exports[`gatsby-plugin-sharp tracedSVG runs on demand (and falls back to blurred): fixed 1`] = ` Object { "aspectRatio": 1, "base64": undefined, @@ -1405,12 +1405,12 @@ Object { "originalName": "test.png", "src": "/static/1234/7e516/test.png", "srcSet": "/static/1234/7e516/test.png 1x", - "tracedSVG": "data:image/svg+xml,%3csvg%20xmlns='http://www.w3.org/2000/svg'%20width='100'%20height='100'%20viewBox='0%200%20100%20100'%20preserveAspectRatio='none'%3e%3cpath%20d='M41%2024c-18%207-24%2029-11%2043%2015%2017%2044%208%2046-15%201-19-17-34-35-28'%20fill='red'%20fill-rule='evenodd'/%3e%3c/svg%3e", + "tracedSVG": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAIAAAAC64paAAAACXBIWXMAAAsTAAALEwEAmpwYAAABP0lEQVQ4y2P4r8ZANmKgvuZ/agy/VaHojyopmv+CNf/XQCCoCEHNf1VBqv+oM5yVY1giwbBakuG2AkjknzoW/eh2/tdgOCPHYM7OwMDAwAgiGFgYGMJ5GF4og43ApRmuk58JqpMJRjIwMBizMbxTZviPaj8ihCD6rThAStkgljJATWEHc3P5wT5SxdD8B2ztERmQIiYGdAAxSpaF4T2q5TDN4HCaLgZSxMyAE1yQgwY+Fs1zxQloviSPTTMktM7JgVzIiKEH4hElFoavKogAQgltiJA3F0gdOyPC58yw8GsUwhFgUMvVGR4oMqiwogQbhOHLxfBLDcVabIlEneGxEkMMLwMvTLc4M0OdEMN3VfRIxp48/6mDnPdCieGkHCiRflRh+K+JPXljz1IQJ0AzhjrRGQPZC5As+ZeuhQGRmgHU8mT34D0STQAAAABJRU5ErkJggg==", "width": 100, } `; -exports[`gatsby-plugin-sharp tracedSVG runs on demand 2`] = ` +exports[`gatsby-plugin-sharp tracedSVG runs on demand (and falls back to blurred): fluid 1`] = ` Object { "aspectRatio": 1, "base64": undefined, @@ -1425,6 +1425,6 @@ Object { /static/1234/a1812/test.png 50w, /static/1234/7e516/test.png 100w", "srcSetType": "image/png", - "tracedSVG": "data:image/svg+xml,%3csvg%20xmlns='http://www.w3.org/2000/svg'%20width='100'%20height='100'%20viewBox='0%200%20100%20100'%20preserveAspectRatio='none'%3e%3cpath%20d='M41%2024c-18%207-24%2029-11%2043%2015%2017%2044%208%2046-15%201-19-17-34-35-28'%20fill='red'%20fill-rule='evenodd'/%3e%3c/svg%3e", + "tracedSVG": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAIAAAAC64paAAAACXBIWXMAAAsTAAALEwEAmpwYAAABP0lEQVQ4y2P4r8ZANmKgvuZ/agy/VaHojyopmv+CNf/XQCCoCEHNf1VBqv+oM5yVY1giwbBakuG2AkjknzoW/eh2/tdgOCPHYM7OwMDAwAgiGFgYGMJ5GF4og43ApRmuk58JqpMJRjIwMBizMbxTZviPaj8ihCD6rThAStkgljJATWEHc3P5wT5SxdD8B2ztERmQIiYGdAAxSpaF4T2q5TDN4HCaLgZSxMyAE1yQgwY+Fs1zxQloviSPTTMktM7JgVzIiKEH4hElFoavKogAQgltiJA3F0gdOyPC58yw8GsUwhFgUMvVGR4oMqiwogQbhOHLxfBLDcVabIlEneGxEkMMLwMvTLc4M0OdEMN3VfRIxp48/6mDnPdCieGkHCiRflRh+K+JPXljz1IQJ0AzhjrRGQPZC5As+ZeuhQGRmgHU8mT34D0STQAAAABJRU5ErkJggg==", } `; diff --git a/packages/gatsby-plugin-sharp/src/__tests__/index.js b/packages/gatsby-plugin-sharp/src/__tests__/index.js index e0713939fa4d2..38f7ec1ece1a0 100644 --- a/packages/gatsby-plugin-sharp/src/__tests__/index.js +++ b/packages/gatsby-plugin-sharp/src/__tests__/index.js @@ -588,7 +588,7 @@ describe(`gatsby-plugin-sharp`, () => { expect(result.tracedSVG).toBeUndefined() }) - it(`runs on demand`, async () => { + it(`runs on demand (and falls back to blurred)`, async () => { const args = { maxWidth: 100, width: 100, @@ -602,14 +602,20 @@ describe(`gatsby-plugin-sharp`, () => { args, }) - expect(fixedSvg).toMatchSnapshot() + expect(fixedSvg).toMatchSnapshot(`fixed`) + + expect(fixedSvg.tracedSVG).toMatch(`data:image/png;base64`) + expect(fixedSvg.tracedSVG).not.toMatch(`data:image/svg+xml`) const fluidSvg = await fluid({ file, args, }) - expect(fluidSvg).toMatchSnapshot() + expect(fluidSvg).toMatchSnapshot(`fluid`) + + expect(fluidSvg.tracedSVG).toMatch(`data:image/png;base64`) + expect(fluidSvg.tracedSVG).not.toMatch(`data:image/svg+xml`) }) }) diff --git a/packages/gatsby-plugin-sharp/src/index.js b/packages/gatsby-plugin-sharp/src/index.js index 3068bd9d40d7a..2587821a042d1 100644 --- a/packages/gatsby-plugin-sharp/src/index.js +++ b/packages/gatsby-plugin-sharp/src/index.js @@ -394,20 +394,6 @@ async function traceSVG(args) { return src } -async function getTracedSVG({ file, options, cache, reporter }) { - if (options.generateTracedSVG && options.tracedSVG) { - const tracedSVG = await traceSVG({ - args: options.tracedSVG, - fileArgs: options, - file, - cache, - reporter, - }) - return tracedSVG - } - return undefined -} - async function stats({ file, reporter }) { let imgStats try { @@ -429,6 +415,8 @@ async function stats({ file, reporter }) { } } +let didShowFluid = false + async function fluid({ file, args = {}, reporter, cache }) { const options = healOptions(getPluginOptions(), args, file.extension) @@ -546,8 +534,17 @@ async function fluid({ file, args = {}, reporter, cache }) { reporter, }) + if (options.generateTracedSVG && options.tracedSVG) { + if (!didShowFluid) { + console.trace( + `[gatsby-plugin-sharp fluid()] traceSVG is no longer supported, falling back to blurred. See https://gatsby.dev/tracesvg-removal/` + ) + didShowFluid = true + } + } + let base64Image - if (options.base64) { + if (options.base64 || (options.generateTracedSVG && options.tracedSVG)) { const base64Width = options.base64Width const base64Height = Math.max( 1, @@ -570,8 +567,6 @@ async function fluid({ file, args = {}, reporter, cache }) { base64Image = await base64({ file, args: base64Args, reporter, cache }) } - const tracedSVG = await getTracedSVG({ options, file, cache, reporter }) - // Construct src and srcSet strings. const originalImg = _.maxBy(images, image => image.width).src const fallbackSrc = _.minBy(images, image => @@ -618,7 +613,7 @@ async function fluid({ file, args = {}, reporter, cache }) { `(max-width: ${presentationWidth}px) 100vw, ${presentationWidth}px` return { - base64: base64Image && base64Image.src, + base64: (options.base64 && base64Image && base64Image.src) || undefined, aspectRatio: images[0].aspectRatio, src: fallbackSrc, srcSet, @@ -629,10 +624,17 @@ async function fluid({ file, args = {}, reporter, cache }) { density, presentationWidth, presentationHeight, - tracedSVG, + tracedSVG: + (options.generateTracedSVG && + options.tracedSVG && + base64Image && + base64Image.src) || + undefined, } } +let didShowFixed = false + async function fixed({ file, args = {}, reporter, cache }) { const options = healOptions(getPluginOptions(), args, file.extension) @@ -685,8 +687,17 @@ async function fixed({ file, args = {}, reporter, cache }) { reporter, }) + if (options.generateTracedSVG && options.tracedSVG) { + if (!didShowFixed) { + console.trace( + `[gatsby-plugin-sharp fixed()] traceSVG is no longer supported, falling back to blurred. See https://gatsby.dev/tracesvg-removal/` + ) + didShowFixed = true + } + } + let base64Image - if (options.base64) { + if (options.base64 || (options.generateTracedSVG && options.tracedSVG)) { const base64Width = options.base64Width const base64Height = Math.max( 1, @@ -714,8 +725,6 @@ async function fixed({ file, args = {}, reporter, cache }) { }) } - const tracedSVG = await getTracedSVG({ options, file, reporter, cache }) - const fallbackSrc = images[0].src const srcSet = images .map((image, i) => { @@ -739,14 +748,19 @@ async function fixed({ file, args = {}, reporter, cache }) { const originalName = file.base return { - base64: base64Image && base64Image.src, + base64: (options.base64 && base64Image && base64Image.src) || undefined, aspectRatio: images[0].aspectRatio, width: images[0].width, height: images[0].height, src: fallbackSrc, srcSet, originalName: originalName, - tracedSVG, + tracedSVG: + (options.generateTracedSVG && + options.tracedSVG && + base64Image && + base64Image.src) || + undefined, } } From b49b2ebb5e3b8fb070ee7413d6e4df6c23b3d3de Mon Sep 17 00:00:00 2001 From: pieh Date: Thu, 24 Nov 2022 10:30:35 +0100 Subject: [PATCH 05/24] update gatsby-remark-images/gatsby-node tests --- .../src/__tests__/gatsby-node.js | 57 +++++++++++++++---- 1 file changed, 47 insertions(+), 10 deletions(-) diff --git a/packages/gatsby-remark-images/src/__tests__/gatsby-node.js b/packages/gatsby-remark-images/src/__tests__/gatsby-node.js index 8aeeaca9a7bf5..a385930e4c2dc 100644 --- a/packages/gatsby-remark-images/src/__tests__/gatsby-node.js +++ b/packages/gatsby-remark-images/src/__tests__/gatsby-node.js @@ -1,6 +1,13 @@ import { testPluginOptionsSchema } from "gatsby-plugin-utils" import { pluginOptionsSchema } from "../gatsby-node" -import { Potrace } from "@gatsbyjs/potrace" + +const warnSpy = jest.spyOn(console, `warn`).mockImplementation(() => { + // silence warnings +}) + +beforeEach(() => { + warnSpy.mockClear() +}) describe(`pluginOptionsSchema`, () => { it(`should provide meaningful errors when fields are invalid`, async () => { @@ -63,6 +70,10 @@ describe(`pluginOptionsSchema`, () => { }) expect(isValid).toBe(true) + + expect(warnSpy).toBeCalledWith( + `[gatsby-remark-images pluginOptions] traceSVG is no longer supported, falling back to blurred` + ) }) it(`should validate the withWebp prop`, async () => { @@ -126,6 +137,12 @@ describe(`pluginOptionsSchema`, () => { }) expect(isValid).toBe(true) + + if (booleanValue) { + expect(warnSpy).toBeCalledWith( + `[gatsby-remark-images pluginOptions] traceSVG is no longer supported, falling back to blurred` + ) + } }) }) @@ -133,7 +150,7 @@ describe(`pluginOptionsSchema`, () => { it(`should validate when all fields are set`, async () => { const { isValid } = await testPluginOptionsSchema(pluginOptionsSchema, { tracedSVG: { - turnPolicy: Potrace.TURNPOLICY_RIGHT, + turnPolicy: `TURNPOLICY_RIGHT`, turdSize: 50, alphaMax: 0.5, optCurve: false, @@ -146,12 +163,16 @@ describe(`pluginOptionsSchema`, () => { }) expect(isValid).toBe(true) + + expect(warnSpy).toBeCalledWith( + `[gatsby-remark-images pluginOptions] traceSVG is no longer supported, falling back to blurred` + ) }) it(`should validate when some fields are set`, async () => { const { isValid } = await testPluginOptionsSchema(pluginOptionsSchema, { tracedSVG: { - turnPolicy: Potrace.TURNPOLICY_RIGHT, + turnPolicy: `TURNPOLICY_RIGHT`, turdSize: 50, // alphaMax: 0.5, // optCurve: 0.2, @@ -164,6 +185,10 @@ describe(`pluginOptionsSchema`, () => { }) expect(isValid).toBe(true) + + expect(warnSpy).toBeCalledWith( + `[gatsby-remark-images pluginOptions] traceSVG is no longer supported, falling back to blurred` + ) }) it(`should fail validation when unknown fields are set`, async () => { @@ -201,15 +226,19 @@ describe(`pluginOptionsSchema`, () => { ) expect(isValid).toBe(true) + + expect(warnSpy).toBeCalledWith( + `[gatsby-remark-images pluginOptions] traceSVG is no longer supported, falling back to blurred` + ) }) it.each([ - Potrace.TURNPOLICY_BLACK, - Potrace.TURNPOLICY_WHITE, - Potrace.TURNPOLICY_LEFT, - Potrace.TURNPOLICY_RIGHT, - Potrace.TURNPOLICY_MINORITY, - Potrace.TURNPOLICY_MAJORITY, + `black`, + `white`, + `left`, + `TURNPOLICY_RIGHT`, + `minority`, + `majority`, ])(`supports setting by policy value (%s)`, async value => { const { isValid } = await testPluginOptionsSchema( pluginOptionsSchema, @@ -219,6 +248,10 @@ describe(`pluginOptionsSchema`, () => { ) expect(isValid).toBe(true) + + expect(warnSpy).toBeCalledWith( + `[gatsby-remark-images pluginOptions] traceSVG is no longer supported, falling back to blurred` + ) }) it(`Doesn't support arbitrary string values`, async () => { @@ -244,7 +277,7 @@ describe(`pluginOptionsSchema`, () => { [ `THRESHOLD_AUTO`, { - value: Potrace.THRESHOLD_AUTO, + value: -1, expectedIsValid: true, }, ], @@ -280,6 +313,10 @@ describe(`pluginOptionsSchema`, () => { ) expect(isValid).toBe(true) + + expect(warnSpy).toBeCalledWith( + `[gatsby-remark-images pluginOptions] traceSVG is no longer supported, falling back to blurred` + ) }) // invalid settings From 9f6e56deba72c84f9f4ebcbab72bcf1b23c9059e Mon Sep 17 00:00:00 2001 From: pieh Date: Thu, 24 Nov 2022 10:39:18 +0100 Subject: [PATCH 06/24] update gatsby-plugin-image tests --- .../__snapshots__/gatsby-plugin-image.js.snap | 14 ++++---------- .../src/__tests__/gatsby-plugin-image.js | 13 ++++++------- 2 files changed, 10 insertions(+), 17 deletions(-) diff --git a/packages/gatsby-source-contentful/src/__tests__/__snapshots__/gatsby-plugin-image.js.snap b/packages/gatsby-source-contentful/src/__tests__/__snapshots__/gatsby-plugin-image.js.snap index 4a949fcad7511..1e57c49704a85 100644 --- a/packages/gatsby-source-contentful/src/__tests__/__snapshots__/gatsby-plugin-image.js.snap +++ b/packages/gatsby-source-contentful/src/__tests__/__snapshots__/gatsby-plugin-image.js.snap @@ -57,9 +57,9 @@ https://images.ctfassets.net:443/k8iqpp6u0ior/3ljGfnpegOnBTFGhV07iC1/94257340bda } `; -exports[`gatsby-plugin-image defaults via gatsby-plugin-sharp custom placeholder tracedSVG 1`] = ` +exports[`gatsby-plugin-image defaults via gatsby-plugin-sharp custom placeholder tracedSVG (falls back to DOMINANT_COLOR) 1`] = ` Object { - "backgroundColor": undefined, + "backgroundColor": "#080808", "height": 338, "images": Object { "fallback": Object { @@ -80,9 +80,6 @@ https://images.ctfassets.net:443/k8iqpp6u0ior/3ljGfnpegOnBTFGhV07iC1/94257340bda ], }, "layout": "constrained", - "placeholder": Object { - "fallback": "data:image/svg+xml,%3csvg%20xmlns='http://www.w3.org/2000/svg'%20width='400'%20height='82'%20viewBox='0%200%20400%2082'%20preserveAspectRatio='none'%3e%3cpath%20d='M27%203C15%208%2010%2013%2010%2019c0%208%209%2011%2015%205%2010-8%2022-8%2032%200%204%204%209%204%2012%201C82%2012%2048-6%2027%203m305%207c-5%203-6%208-6%2029s0%2022%205%2022%205-1%205-16V32h4c4%200%206-2%206-5-1-3-2-3-6-3s-4%200-4-2c0-3%203-6%206-6%204%200%205-2%205-5-1-4-10-4-15-1m-130%205-1%2019v19l3%203c3%203%203%203%209%203l6%201v-4c0-3-1-3-4-4-4-1-5-4-5-13%200-7%201-7%203-7%206%201%209-3%207-7-1-2-2-2-5-2h-5v-4c0-4-1-5-4-5l-4%201m102%200c-2%202-1%2034%200%2038%203%206%207%208%2015%207%205%200%203-8-1-8s-5-2-5-11v-9h4c4%200%206-1%206-4%200-4-2-5-6-5s-4%200-4-3l-1-5h-8m87%200v45l4%201%204-1V15h-8M96%2024c-14%207-15%2027-2%2034%207%204%2019%203%2023-2%201-2%201-2-1-5-3-2-3-2-7-1-10%204-17-3-14-12%202-6%208-9%2014-6%203%201%208-2%208-5%200-4-16-7-21-3m32%200c-15%208-12%2032%204%2036%2015%204%2028-9%2024-24-3-11-17-17-28-12m49-1-5%202c-3%202-3%202-4%200s-2-2-4-2l-3%201-1%2015c-1%2019-1%2021%205%2021h4V49c1-13%201-14%205-17%204-2%206-1%2010%201%202%203%202%203%203%2015l1%2012h8V46c0-15-1-17-7-21-3-2-9-3-12-2m55%201c-15%207-15%2029%200%2035%207%203%2018%202%2023-2l2-2-2-3c-3-2-3-2-6-1-6%202-13%201-16-1-4-5-3-5%2012-5h14v-5c0-14-14-22-27-16m31%200-1%2018v17l2%201c6%202%208%200%208-12%200-13%202-16%2010-16%205%200%207%203%207%2016l1%2011c2%202%205%202%207%201%203-2%203-25-1-30-4-7-14-9-21-5-4%202-4%202-4%200-1-2-6-3-8-1m87%200c-3%202-2%2024%200%2029%204%208%2014%2010%2022%206l4-1c0%202%205%204%207%202%201-1%202-3%202-18V25l-4-1c-6%200-6%201-6%2013s-2%2015-9%2015-8-3-8-16c0-10%200-11-2-12h-6M131%2034c-6%205-3%2016%204%2018%209%201%2015-8%2010-16-3-6-9-7-14-2M14%2055c-9%205-5%2015%208%2022%2016%208%2034%205%2046-6%2010-9-1-22-11-13-10%208-22%208-31%200-4-4-9-5-12-3'%20fill='%23639'%20fill-rule='evenodd'/%3e%3c/svg%3e", - }, "width": "1646", } `; @@ -333,9 +330,9 @@ https://images.ctfassets.net:443/k8iqpp6u0ior/3ljGfnpegOnBTFGhV07iC1/94257340bda } `; -exports[`gatsby-plugin-image query arguments placeholder traced svg 1`] = ` +exports[`gatsby-plugin-image query arguments placeholder traced svg (falls back to DOMINANT_COLOR) 1`] = ` Object { - "backgroundColor": undefined, + "backgroundColor": "#080808", "height": 338, "images": Object { "fallback": Object { @@ -356,9 +353,6 @@ https://images.ctfassets.net:443/k8iqpp6u0ior/3ljGfnpegOnBTFGhV07iC1/94257340bda ], }, "layout": "constrained", - "placeholder": Object { - "fallback": "data:image/svg+xml,%3csvg%20xmlns='http://www.w3.org/2000/svg'%20width='400'%20height='82'%20viewBox='0%200%20400%2082'%20preserveAspectRatio='none'%3e%3cpath%20d='M27%203C15%208%2010%2013%2010%2019c0%208%209%2011%2015%205%2010-8%2022-8%2032%200%204%204%209%204%2012%201C82%2012%2048-6%2027%203m305%207c-5%203-6%208-6%2029s0%2022%205%2022%205-1%205-16V32h4c4%200%206-2%206-5-1-3-2-3-6-3s-4%200-4-2c0-3%203-6%206-6%204%200%205-2%205-5-1-4-10-4-15-1m-130%205-1%2019v19l3%203c3%203%203%203%209%203l6%201v-4c0-3-1-3-4-4-4-1-5-4-5-13%200-7%201-7%203-7%206%201%209-3%207-7-1-2-2-2-5-2h-5v-4c0-4-1-5-4-5l-4%201m102%200c-2%202-1%2034%200%2038%203%206%207%208%2015%207%205%200%203-8-1-8s-5-2-5-11v-9h4c4%200%206-1%206-4%200-4-2-5-6-5s-4%200-4-3l-1-5h-8m87%200v45l4%201%204-1V15h-8M96%2024c-14%207-15%2027-2%2034%207%204%2019%203%2023-2%201-2%201-2-1-5-3-2-3-2-7-1-10%204-17-3-14-12%202-6%208-9%2014-6%203%201%208-2%208-5%200-4-16-7-21-3m32%200c-15%208-12%2032%204%2036%2015%204%2028-9%2024-24-3-11-17-17-28-12m49-1-5%202c-3%202-3%202-4%200s-2-2-4-2l-3%201-1%2015c-1%2019-1%2021%205%2021h4V49c1-13%201-14%205-17%204-2%206-1%2010%201%202%203%202%203%203%2015l1%2012h8V46c0-15-1-17-7-21-3-2-9-3-12-2m55%201c-15%207-15%2029%200%2035%207%203%2018%202%2023-2l2-2-2-3c-3-2-3-2-6-1-6%202-13%201-16-1-4-5-3-5%2012-5h14v-5c0-14-14-22-27-16m31%200-1%2018v17l2%201c6%202%208%200%208-12%200-13%202-16%2010-16%205%200%207%203%207%2016l1%2011c2%202%205%202%207%201%203-2%203-25-1-30-4-7-14-9-21-5-4%202-4%202-4%200-1-2-6-3-8-1m87%200c-3%202-2%2024%200%2029%204%208%2014%2010%2022%206l4-1c0%202%205%204%207%202%201-1%202-3%202-18V25l-4-1c-6%200-6%201-6%2013s-2%2015-9%2015-8-3-8-16c0-10%200-11-2-12h-6M131%2034c-6%205-3%2016%204%2018%209%201%2015-8%2010-16-3-6-9-7-14-2M14%2055c-9%205-5%2015%208%2022%2016%208%2034%205%2046-6%2010-9-1-22-11-13-10%208-22%208-31%200-4-4-9-5-12-3'%20fill='%23d3d3d3'%20fill-rule='evenodd'/%3e%3c/svg%3e", - }, "width": "1646", } `; diff --git a/packages/gatsby-source-contentful/src/__tests__/gatsby-plugin-image.js b/packages/gatsby-source-contentful/src/__tests__/gatsby-plugin-image.js index b60b7139e628d..033ea67336beb 100644 --- a/packages/gatsby-source-contentful/src/__tests__/gatsby-plugin-image.js +++ b/packages/gatsby-source-contentful/src/__tests__/gatsby-plugin-image.js @@ -242,7 +242,7 @@ describe(`gatsby-plugin-image`, () => { expect(resp.placeholder.fallback).toMatch(/^data:image\/png;base64,.+/) expect(resp).toMatchSnapshot() }) - it(`placeholder traced svg`, async () => { + it(`placeholder traced svg (falls back to DOMINANT_COLOR)`, async () => { const resp = await extendedNodeType.gatsbyImageData.resolve( exampleImage, // @ts-ignore @@ -252,9 +252,8 @@ describe(`gatsby-plugin-image`, () => { null, null ) - expect(resp.backgroundColor).toEqual(undefined) - expect(resp.placeholder.fallback).toMatch(/^data:image\/svg\+xml,.+/) - expect(resp.placeholder.fallback).toContain(`fill='%23d3d3d3'`) + expect(resp.backgroundColor).toEqual(`#080808`) + expect(resp.placeholder).not.toBeDefined() expect(resp).toMatchSnapshot() }) }) @@ -302,7 +301,7 @@ describe(`gatsby-plugin-image`, () => { expect(resp).toMatchSnapshot() }) - it(`custom placeholder tracedSVG`, async () => { + it(`custom placeholder tracedSVG (falls back to DOMINANT_COLOR)`, async () => { setPluginOptions({ defaults: { placeholder: `tracedSVG`, @@ -319,8 +318,8 @@ describe(`gatsby-plugin-image`, () => { null, null ) - expect(resp.placeholder.fallback).toMatch(/^data:image\/svg\+xml,.+/) - expect(resp.placeholder.fallback).toContain(`fill='%23639'`) + expect(resp.backgroundColor).toEqual(`#080808`) + expect(resp.placeholder).not.toBeDefined() expect(resp).toMatchSnapshot() }) From 1559702d6d99049b1d6a9897a210edc7e6877f26 Mon Sep 17 00:00:00 2001 From: pieh Date: Thu, 24 Nov 2022 11:18:05 +0100 Subject: [PATCH 07/24] update schema print tests --- .../__tests__/__snapshots__/print.js.snap | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/packages/gatsby/src/schema/__tests__/__snapshots__/print.js.snap b/packages/gatsby/src/schema/__tests__/__snapshots__/print.js.snap index 6e7c067c27b9d..f51d99052d35e 100644 --- a/packages/gatsby/src/schema/__tests__/__snapshots__/print.js.snap +++ b/packages/gatsby/src/schema/__tests__/__snapshots__/print.js.snap @@ -122,9 +122,9 @@ interface RemoteFile { \\"\\"\\" Format of generated placeholder image, displayed while the main image loads. - BLURRED: a blurred, low resolution image, encoded as a base64 data URI (default) - DOMINANT_COLOR: a solid color, calculated from the dominant color of the image. - TRACED_SVG: a low-resolution traced SVG of the image. + BLURRED: a blurred, low resolution image, encoded as a base64 data URI + DOMINANT_COLOR: a solid color, calculated from the dominant color of the image (default). + TRACED_SVG: deprecated. Will use DOMINANT_COLOR. NONE: no placeholder. Set the argument \\"backgroundColor\\" to use a fixed background color. \\"\\"\\" placeholder: RemoteFilePlaceholder = DOMINANT_COLOR @@ -511,9 +511,9 @@ interface RemoteFile { \\"\\"\\" Format of generated placeholder image, displayed while the main image loads. - BLURRED: a blurred, low resolution image, encoded as a base64 data URI (default) - DOMINANT_COLOR: a solid color, calculated from the dominant color of the image. - TRACED_SVG: a low-resolution traced SVG of the image. + BLURRED: a blurred, low resolution image, encoded as a base64 data URI + DOMINANT_COLOR: a solid color, calculated from the dominant color of the image (default). + TRACED_SVG: deprecated. Will use DOMINANT_COLOR. NONE: no placeholder. Set the argument \\"backgroundColor\\" to use a fixed background color. \\"\\"\\" placeholder: RemoteFilePlaceholder = DOMINANT_COLOR @@ -910,9 +910,9 @@ interface RemoteFile { \\"\\"\\" Format of generated placeholder image, displayed while the main image loads. - BLURRED: a blurred, low resolution image, encoded as a base64 data URI (default) - DOMINANT_COLOR: a solid color, calculated from the dominant color of the image. - TRACED_SVG: a low-resolution traced SVG of the image. + BLURRED: a blurred, low resolution image, encoded as a base64 data URI + DOMINANT_COLOR: a solid color, calculated from the dominant color of the image (default). + TRACED_SVG: deprecated. Will use DOMINANT_COLOR. NONE: no placeholder. Set the argument \\"backgroundColor\\" to use a fixed background color. \\"\\"\\" placeholder: RemoteFilePlaceholder = DOMINANT_COLOR From 80e1cf135e34f5c36017f018be0635acfa805921 Mon Sep 17 00:00:00 2001 From: pieh Date: Thu, 24 Nov 2022 11:18:44 +0100 Subject: [PATCH 08/24] remove traceSVG unit tests as it was removed --- .../src/__tests__/trace-svg.js | 270 ------------------ 1 file changed, 270 deletions(-) delete mode 100644 packages/gatsby-plugin-sharp/src/__tests__/trace-svg.js diff --git a/packages/gatsby-plugin-sharp/src/__tests__/trace-svg.js b/packages/gatsby-plugin-sharp/src/__tests__/trace-svg.js deleted file mode 100644 index 11cf82ac17338..0000000000000 --- a/packages/gatsby-plugin-sharp/src/__tests__/trace-svg.js +++ /dev/null @@ -1,270 +0,0 @@ -jest.mock(`os`, () => { - const path = require(`path`) - - return { - ...jest.requireActual(`os`), - tmpdir: () => path.join(__dirname, `.cache`), - } -}) - -const path = require(`path`) -const fs = require(`fs-extra`) - -const traceSVGHelpers = require(`../trace-svg`) - -const notMemoizedtraceSVG = jest.spyOn(traceSVGHelpers, `notMemoizedtraceSVG`) -const notMemoizedPrepareTraceSVGInputFile = jest.spyOn( - traceSVGHelpers, - `notMemoizedPrepareTraceSVGInputFile` -) -// note that we started spying on not memoized functions first -// now we recreate memoized functions that will use function we just started -// spying on -traceSVGHelpers.createMemoizedFunctions() -const memoizedTraceSVG = jest.spyOn(traceSVGHelpers, `memoizedTraceSVG`) -const memoizedPrepareTraceSVGInputFile = jest.spyOn( - traceSVGHelpers, - `memoizedPrepareTraceSVGInputFile` -) - -const { traceSVG } = require(`../`) - -function getFileObject(absolutePath, name = path.parse(absolutePath).name) { - return { - id: `${absolutePath} absPath of file`, - name: name, - absolutePath, - extension: `png`, - internal: { - contentDigest: `2022-01-13T13:27:56.654Z`, - }, - } -} - -describe(`traceSVG memoization`, () => { - const file = getFileObject(path.join(__dirname, `images/test.png`)) - const differentFile = getFileObject( - path.join(__dirname, `images/144-density.png`) - ) - differentFile.internal.contentDigest = `4321` - - beforeAll(async () => { - await fs.ensureDir(path.join(__dirname, `.cache`)) - }) - - afterAll(async () => { - await fs.remove(path.join(__dirname, `.cache`)) - }) - - beforeEach(() => { - traceSVGHelpers.clearMemoizeCaches() - memoizedTraceSVG.mockClear() - notMemoizedtraceSVG.mockClear() - memoizedPrepareTraceSVGInputFile.mockClear() - notMemoizedPrepareTraceSVGInputFile.mockClear() - }) - - it(`Baseline`, async () => { - await traceSVG({ - file, - }) - - expect(memoizedTraceSVG).toBeCalledTimes(1) - expect(notMemoizedtraceSVG).toBeCalledTimes(1) - expect(memoizedPrepareTraceSVGInputFile).toBeCalledTimes(1) - expect(notMemoizedPrepareTraceSVGInputFile).toBeCalledTimes(1) - }) - - it(`should memoizing results for same args`, async () => { - await traceSVG({ - file, - }) - - await traceSVG({ - file, - }) - - expect(memoizedTraceSVG).toBeCalledTimes(2) - expect(notMemoizedtraceSVG).toBeCalledTimes(1) - expect(memoizedPrepareTraceSVGInputFile).toBeCalledTimes(1) - expect(notMemoizedPrepareTraceSVGInputFile).toBeCalledTimes(1) - }) - - it( - `should call functions with same input file when params change`, - async () => { - await traceSVG({ - file, - args: { - color: `red`, - }, - fileArgs: { - width: 400, - }, - }) - await traceSVG({ - file, - args: { - color: `blue`, - }, - fileArgs: { - width: 400, - }, - }) - await traceSVG({ - file, - args: { - color: `red`, - }, - fileArgs: { - width: 200, - }, - }) - await traceSVG({ - file, - args: { - color: `blue`, - }, - fileArgs: { - width: 200, - }, - }) - await traceSVG({ - file: differentFile, - args: { - color: `red`, - }, - fileArgs: { - width: 400, - }, - }) - - expect(memoizedTraceSVG).toBeCalledTimes(5) - expect(notMemoizedtraceSVG).toBeCalledTimes(5) - expect(memoizedPrepareTraceSVGInputFile).toBeCalledTimes(5) - // trace svg should be actually created just 3 times - // because it's affected just by `fileArgs`, and not `args` - // this makes sure we don't try to write to same input file multiple times - expect(notMemoizedPrepareTraceSVGInputFile).toBeCalledTimes(3) - expect(notMemoizedPrepareTraceSVGInputFile).toHaveBeenNthCalledWith( - 1, - expect.objectContaining({ - file, - options: expect.objectContaining({ - width: 400, - }), - }) - ) - expect(notMemoizedPrepareTraceSVGInputFile).toHaveBeenNthCalledWith( - 2, - expect.objectContaining({ - file, - options: expect.objectContaining({ - width: 200, - }), - }) - ) - expect(notMemoizedPrepareTraceSVGInputFile).toHaveBeenNthCalledWith( - 3, - expect.objectContaining({ - file: differentFile, - options: expect.objectContaining({ - width: 400, - }), - }) - ) - - const usedTmpFilePaths = - notMemoizedPrepareTraceSVGInputFile.mock.calls.map( - args => args[0].tmpFilePath - ) - - // tmpFilePath was always unique - expect(usedTmpFilePaths.length).toBe(new Set(usedTmpFilePaths).size) - }, - 10 * 1000 - ) - - it(`Use memoized results for file copies`, async () => { - const copyPath = path.join(__dirname, `images/test-copy.png`) - await fs.copy(path.join(__dirname, `images/test.png`), copyPath) - - try { - const copyOfFile = getFileObject(copyPath) - await traceSVG({ - file, - args: { - color: `red`, - }, - fileArgs: { - width: 400, - }, - }) - await traceSVG({ - file: copyOfFile, - args: { - color: `red`, - }, - fileArgs: { - width: 400, - }, - }) - } finally { - await fs.remove(copyPath) - } - - expect(memoizedTraceSVG).toBeCalledTimes(2) - expect(notMemoizedtraceSVG).toBeCalledTimes(1) - expect(memoizedPrepareTraceSVGInputFile).toBeCalledTimes(1) - expect(notMemoizedPrepareTraceSVGInputFile).toBeCalledTimes(1) - }) - - it(`should work with long filenames`, async () => { - const copyPath = path.join( - __dirname, - `images/${`a`.repeat(10)} (1) ${`a`.repeat(100)}.png` - ) - await fs.copy(path.join(__dirname, `images/test.png`), copyPath) - expect.assertions(1) - - try { - const copyOfFile = getFileObject(copyPath) - await traceSVG({ - file: copyOfFile, - args: { - color: `red`, - }, - fileArgs: { - width: 400, - }, - }) - expect(true).toBe(true) - } finally { - await fs.remove(copyPath) - } - }) - - it(`should work with long filenames that end with a dot`, async () => { - const copyPath = path.join(__dirname, `images/test${`.`.repeat(100)}.png`) - await fs.copy(path.join(__dirname, `images/test.png`), copyPath) - expect.assertions(1) - - try { - const copyOfFile = getFileObject(copyPath) - await traceSVG({ - file: copyOfFile, - args: { - color: `red`, - }, - fileArgs: { - width: 400, - }, - }) - expect(true).toBe(true) - } catch (err) { - await fs.remove(copyPath) - } finally { - await fs.remove(copyPath) - } - }) -}) From ff675eb0f50738c1ec5e534caf341840a150781d Mon Sep 17 00:00:00 2001 From: pieh Date: Thu, 24 Nov 2022 11:28:13 +0100 Subject: [PATCH 09/24] legacy gatsby-image fields --- .../placeholder-handler.ts | 2 +- .../src/customize-schema.js | 61 +++++++++++-------- 2 files changed, 36 insertions(+), 27 deletions(-) diff --git a/packages/gatsby-plugin-utils/src/polyfill-remote-file/placeholder-handler.ts b/packages/gatsby-plugin-utils/src/polyfill-remote-file/placeholder-handler.ts index 42cbeac3cd0b8..8c60df00e5650 100644 --- a/packages/gatsby-plugin-utils/src/polyfill-remote-file/placeholder-handler.ts +++ b/packages/gatsby-plugin-utils/src/polyfill-remote-file/placeholder-handler.ts @@ -71,7 +71,7 @@ const queue = Queue< if (type === PlaceholderType.TRACED_SVG) { if (!didShow) { console.trace( - `[gatsby-plugin-utils placeholder-handler queue handler] traceSVG is no longer supported, falling back to dominantColor` + `[gatsby-plugin-utils placeholder-handler queue handler] traceSVG is no longer supported, falling back to dominantColor. See https://gatsby.dev/tracesvg-removal/` ) didShow = true } diff --git a/packages/gatsby-transformer-sharp/src/customize-schema.js b/packages/gatsby-transformer-sharp/src/customize-schema.js index 29c991ed3f5bc..e228fdae3800a 100644 --- a/packages/gatsby-transformer-sharp/src/customize-schema.js +++ b/packages/gatsby-transformer-sharp/src/customize-schema.js @@ -14,7 +14,6 @@ const { base64, fluid, fixed, - traceSVG, generateImageData, } = require(`gatsby-plugin-sharp`) const { hasFeature } = require(`gatsby-plugin-utils`) @@ -67,14 +66,7 @@ function toArray(buf) { return arr } -const getTracedSVG = async ({ file, image, fieldArgs, cache, reporter }) => - traceSVG({ - file, - args: { ...fieldArgs.traceSVG }, - fileArgs: fieldArgs, - cache, - reporter, - }) +let didShowFixed = false const fixedNodeType = ({ pathPrefix, @@ -90,12 +82,15 @@ const fixedNodeType = ({ base64: { type: GraphQLString }, tracedSVG: { type: GraphQLString, - resolve: parent => - getTracedSVG({ - ...parent, - cache, - reporter, - }), + resolve: parent => { + if (!didShowFixed) { + console.trace( + `[gatsby-transformer-sharp fixed.tracedSVG] traceSVG is no longer supported, falling back to blurred. See https://gatsby.dev/tracesvg-removal/` + ) + didShowFixed = true + } + return parent.base64 + }, }, aspectRatio: { type: GraphQLFloat }, width: { type: new GraphQLNonNull(GraphQLFloat) }, @@ -234,6 +229,8 @@ const fixedNodeType = ({ } } +let didShowFluid = false + const fluidNodeType = ({ pathPrefix, getNodeAndSavePathDependency, @@ -248,12 +245,15 @@ const fluidNodeType = ({ base64: { type: GraphQLString }, tracedSVG: { type: GraphQLString, - resolve: parent => - getTracedSVG({ - ...parent, - cache, - reporter, - }), + resolve: parent => { + if (!didShowFluid) { + console.trace( + `[gatsby-transformer-sharp fluid.tracedSVG] traceSVG is no longer supported, falling back to blurred. See https://gatsby.dev/tracesvg-removal/` + ) + didShowFluid = true + } + return parent.base64 + }, }, aspectRatio: { type: new GraphQLNonNull(GraphQLFloat) }, src: { type: new GraphQLNonNull(GraphQLString) }, @@ -561,6 +561,8 @@ let didShow = false */ const inProgressCopy = new Set() +let didShowResized = false + const createFields = ({ pathPrefix, getNodeAndSavePathDependency, @@ -645,12 +647,19 @@ const createFields = ({ src: { type: GraphQLString }, tracedSVG: { type: GraphQLString, - resolve: parent => - getTracedSVG({ - ...parent, + resolve: async parent => { + if (!didShowResized) { + console.trace( + `[gatsby-transformer-sharp resize resolver] traceSVG is no longer supported, falling back to blurred` + ) + didShowResized = true + } + const { src } = await base64({ + file: parent.file, cache, - reporter, - }), + }) + return src + }, }, width: { type: GraphQLInt }, height: { type: GraphQLInt }, From fc4e02931b8e059352ee67fa1e706c99dcb743ee Mon Sep 17 00:00:00 2001 From: pieh Date: Thu, 24 Nov 2022 11:53:51 +0100 Subject: [PATCH 10/24] update polyfill tests --- .../polyfill-remote-file/__tests__/gatsby-image-resolver.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/gatsby-plugin-utils/src/polyfill-remote-file/__tests__/gatsby-image-resolver.ts b/packages/gatsby-plugin-utils/src/polyfill-remote-file/__tests__/gatsby-image-resolver.ts index 2cbcca6ad0ffb..5b915b29fe9af 100644 --- a/packages/gatsby-plugin-utils/src/polyfill-remote-file/__tests__/gatsby-image-resolver.ts +++ b/packages/gatsby-plugin-utils/src/polyfill-remote-file/__tests__/gatsby-image-resolver.ts @@ -724,7 +724,7 @@ describe(`gatsbyImageData`, () => { `) }) - it(`should generate tracedSVG placeholder`, async () => { + it(`should generate tracedSVG placeholder (fallback to dominant_color)`, async () => { fetchRemoteFile.mockResolvedValueOnce( path.join(__dirname, `__fixtures__`, `dog-portrait.jpg`) ) @@ -747,7 +747,7 @@ describe(`gatsbyImageData`, () => { }) expect(fixedResult?.placeholder).toMatchInlineSnapshot(` Object { - "fallback": "data:image/svg+xml,%3csvg%20xmlns='http://www.w3.org/2000/svg'%20width='20'%20height='33'%20viewBox='0%200%2020%2033'%3e%3cpath%20d='M6%201C4%205%204%205%203%203L2%201C0%201%200%205%200%2014s0%209%203%208c4%200%204%200%204-2v-8H6c0-1%202-3%204-3s2%200%202-2l-1-4c0-3%200-3-3-3L6%201'%20fill='%23d3d3d3'%20fill-rule='evenodd'/%3e%3c/svg%3e", + "fallback": "rgb(56,40,40)", } `) }) From 1763c3abbd896fb2ca8ca7926e4322feeca05481 Mon Sep 17 00:00:00 2001 From: pieh Date: Thu, 24 Nov 2022 11:59:51 +0100 Subject: [PATCH 11/24] update cypress assertion --- .../cypress/integration/gatsby-plugin-image.js | 2 +- .../integration/remote-file/gatsby-plugin-image.js | 2 +- .../cypress/integration/static-image/traced.js | 4 +++- .../cypress/integration/remote-file.js | 2 +- .../cypress/integration/static-image/traced.js | 13 ++++++++++--- 5 files changed, 16 insertions(+), 7 deletions(-) diff --git a/e2e-tests/contentful/cypress/integration/gatsby-plugin-image.js b/e2e-tests/contentful/cypress/integration/gatsby-plugin-image.js index 1894fdc4a6134..db726e5a46c3a 100644 --- a/e2e-tests/contentful/cypress/integration/gatsby-plugin-image.js +++ b/e2e-tests/contentful/cypress/integration/gatsby-plugin-image.js @@ -78,7 +78,7 @@ describe(`gatsby-plugin-image`, () => { testGatsbyPluginImage(`dominant-color`, hasColorPlaceholder) ) it(`traced`, testConfig, () => - testGatsbyPluginImage(`traced`, hasSVGPlaceholder) + testGatsbyPluginImage(`traced`, hasBase64Placeholder) ) it(`blurred`, testConfig, () => testGatsbyPluginImage(`blurred`, hasBase64Placeholder) diff --git a/e2e-tests/development-runtime/cypress/integration/remote-file/gatsby-plugin-image.js b/e2e-tests/development-runtime/cypress/integration/remote-file/gatsby-plugin-image.js index 2fac5405bdca6..65ba35ccb6538 100644 --- a/e2e-tests/development-runtime/cypress/integration/remote-file/gatsby-plugin-image.js +++ b/e2e-tests/development-runtime/cypress/integration/remote-file/gatsby-plugin-image.js @@ -127,7 +127,7 @@ describe(`remote-file`, () => { .first() .should($el => { expect($el.prop("tagName")).to.be.equal("IMG") - expect($el.prop("src")).to.contain("data:image/svg+xml,%3csvg") + expect($el.prop("src")).to.contain("data:image/jpeg;base64") }) cy.get(".full [data-placeholder-image]") .first() diff --git a/e2e-tests/development-runtime/cypress/integration/static-image/traced.js b/e2e-tests/development-runtime/cypress/integration/static-image/traced.js index c038f779a1a2c..cc9698bb6c566 100644 --- a/e2e-tests/development-runtime/cypress/integration/static-image/traced.js +++ b/e2e-tests/development-runtime/cypress/integration/static-image/traced.js @@ -10,7 +10,9 @@ describe(`fixed`, () => { .find(`.gatsby-image-wrapper > img`) .should(`have.attr`, `src`) .and(src => { - ;[`data:image/svg+xml`].forEach(part => expect(src).to.include(part)) + ;[`data:image/jpeg;base64`].forEach(part => + expect(src).to.include(part) + ) }) }) diff --git a/e2e-tests/production-runtime/cypress/integration/remote-file.js b/e2e-tests/production-runtime/cypress/integration/remote-file.js index 4ba72ca178b5a..231644f0f6a71 100644 --- a/e2e-tests/production-runtime/cypress/integration/remote-file.js +++ b/e2e-tests/production-runtime/cypress/integration/remote-file.js @@ -137,7 +137,7 @@ describe( .first() .should($el => { expect($el.prop("tagName")).to.be.equal("IMG") - expect($el.prop("src")).to.contain("data:image/svg+xml,%3csvg") + expect($el.prop("src")).to.contain("data:image/jpeg;base64") }) cy.get(".full [data-placeholder-image]") .first() diff --git a/e2e-tests/production-runtime/cypress/integration/static-image/traced.js b/e2e-tests/production-runtime/cypress/integration/static-image/traced.js index a030714387422..50429659b575d 100644 --- a/e2e-tests/production-runtime/cypress/integration/static-image/traced.js +++ b/e2e-tests/production-runtime/cypress/integration/static-image/traced.js @@ -1,7 +1,12 @@ const tracedTestId = `image-traced` -Cypress.on('uncaught:exception', (err) => { - if ((err.message.includes('Minified React error #418') || err.message.includes('Minified React error #423') || err.message.includes('Minified React error #425')) && Cypress.env(`TEST_PLUGIN_OFFLINE`)) { +Cypress.on("uncaught:exception", err => { + if ( + (err.message.includes("Minified React error #418") || + err.message.includes("Minified React error #423") || + err.message.includes("Minified React error #425")) && + Cypress.env(`TEST_PLUGIN_OFFLINE`) + ) { return false } }) @@ -16,7 +21,9 @@ describe(`fixed`, () => { .find(`.gatsby-image-wrapper > img`) .should(`have.attr`, `src`) .and(src => { - ;[`data:image/svg+xml`].forEach(part => expect(src).to.include(part)) + ;[`data:image/jped;base64`].forEach(part => + expect(src).to.include(part) + ) }) }) From 691a74c787b10a1d002c611ef95f71846ec1fde8 Mon Sep 17 00:00:00 2001 From: pieh Date: Thu, 24 Nov 2022 12:31:45 +0100 Subject: [PATCH 12/24] update contentful cypress assertions --- .../contentful/cypress/integration/gatsby-plugin-image.js | 2 +- e2e-tests/contentful/src/pages/gatsby-plugin-image.js | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/e2e-tests/contentful/cypress/integration/gatsby-plugin-image.js b/e2e-tests/contentful/cypress/integration/gatsby-plugin-image.js index db726e5a46c3a..1678cea7af2d2 100644 --- a/e2e-tests/contentful/cypress/integration/gatsby-plugin-image.js +++ b/e2e-tests/contentful/cypress/integration/gatsby-plugin-image.js @@ -78,7 +78,7 @@ describe(`gatsby-plugin-image`, () => { testGatsbyPluginImage(`dominant-color`, hasColorPlaceholder) ) it(`traced`, testConfig, () => - testGatsbyPluginImage(`traced`, hasBase64Placeholder) + testGatsbyPluginImage(`traced`, hasColorPlaceholder) ) it(`blurred`, testConfig, () => testGatsbyPluginImage(`blurred`, hasBase64Placeholder) diff --git a/e2e-tests/contentful/src/pages/gatsby-plugin-image.js b/e2e-tests/contentful/src/pages/gatsby-plugin-image.js index b444a2080f300..ead0cc27082f0 100644 --- a/e2e-tests/contentful/src/pages/gatsby-plugin-image.js +++ b/e2e-tests/contentful/src/pages/gatsby-plugin-image.js @@ -94,7 +94,9 @@ const GatsbyPluginImagePage = ({ data }) => { ))} -

gatsby-plugin-image: Traced SVG Placeholder

+

+ gatsby-plugin-image: Traced SVG Placeholder (fallback to DOMINANT_COLOR) +

{data.default.nodes.map(node => (
From 8291dc9bebca914d385b8350edfae9dc5069be40 Mon Sep 17 00:00:00 2001 From: pieh Date: Thu, 24 Nov 2022 12:42:50 +0100 Subject: [PATCH 13/24] drop few more packages from gatsby-plugin-sharp as they are no onger used --- packages/gatsby-plugin-sharp/package.json | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/gatsby-plugin-sharp/package.json b/packages/gatsby-plugin-sharp/package.json index cf2e03b8272dd..b7b17d444567e 100644 --- a/packages/gatsby-plugin-sharp/package.json +++ b/packages/gatsby-plugin-sharp/package.json @@ -16,11 +16,9 @@ "gatsby-core-utils": "^4.2.0-next.0", "gatsby-plugin-utils": "^4.2.0-next.0", "lodash": "^4.17.21", - "mini-svg-data-uri": "^1.4.4", "probe-image-size": "^7.2.3", "semver": "^7.3.7", - "sharp": "^0.30.7", - "svgo": "^2.8.0" + "sharp": "^0.30.7" }, "devDependencies": { "@babel/cli": "^7.15.4", From c9b26d3d6152bd73b30142824bae157457bb3f2a Mon Sep 17 00:00:00 2001 From: pieh Date: Thu, 24 Nov 2022 13:06:45 +0100 Subject: [PATCH 14/24] correct IMAGE_CDN fallback for TRACE_SVG --- .../graphql/gatsby-image-resolver.ts | 10 ++++++++++ .../src/polyfill-remote-file/placeholder-handler.ts | 4 ++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/packages/gatsby-plugin-utils/src/polyfill-remote-file/graphql/gatsby-image-resolver.ts b/packages/gatsby-plugin-utils/src/polyfill-remote-file/graphql/gatsby-image-resolver.ts index 128ebc7f4cd1b..e901081b4a102 100644 --- a/packages/gatsby-plugin-utils/src/polyfill-remote-file/graphql/gatsby-image-resolver.ts +++ b/packages/gatsby-plugin-utils/src/polyfill-remote-file/graphql/gatsby-image-resolver.ts @@ -76,6 +76,8 @@ const GATSBY_SHOULD_TRACK_IMAGE_CDN_URLS = [`true`, `1`].includes( process.env.GATSBY_SHOULD_TRACK_IMAGE_CDN_URLS || `` ) +let didShow = false + export async function gatsbyImageResolver( source: IRemoteFileNode, args: IGatsbyImageDataArgs, @@ -121,6 +123,14 @@ export async function gatsbyImageResolver( if (!args.placeholder) { args.placeholder = PlaceholderType.DOMINANT_COLOR + } else if (args.placeholder === PlaceholderType.TRACED_SVG) { + if (!didShow) { + console.trace( + `[gatsby-plugin-utils gatsbyImageResolver] traceSVG is no longer supported, falling back to DOMINANT_COLOR. See https://gatsby.dev/tracesvg-removal/` + ) + didShow = true + } + args.placeholder = PlaceholderType.DOMINANT_COLOR } if (!args.quality) { diff --git a/packages/gatsby-plugin-utils/src/polyfill-remote-file/placeholder-handler.ts b/packages/gatsby-plugin-utils/src/polyfill-remote-file/placeholder-handler.ts index 8c60df00e5650..03aa2f04291c1 100644 --- a/packages/gatsby-plugin-utils/src/polyfill-remote-file/placeholder-handler.ts +++ b/packages/gatsby-plugin-utils/src/polyfill-remote-file/placeholder-handler.ts @@ -71,11 +71,11 @@ const queue = Queue< if (type === PlaceholderType.TRACED_SVG) { if (!didShow) { console.trace( - `[gatsby-plugin-utils placeholder-handler queue handler] traceSVG is no longer supported, falling back to dominantColor. See https://gatsby.dev/tracesvg-removal/` + `[gatsby-plugin-utils placeholder-handler queue handler] traceSVG is no longer supported, falling back to blurred. See https://gatsby.dev/tracesvg-removal/` ) didShow = true } - type = PlaceholderType.DOMINANT_COLOR + type = PlaceholderType.BLURRED } switch (type) { From e4121c22c1ef79f632a3b22f020f1ef65c7a180e Mon Sep 17 00:00:00 2001 From: pieh Date: Thu, 24 Nov 2022 13:47:11 +0100 Subject: [PATCH 15/24] update generateImageData --- packages/gatsby-plugin-sharp/src/image-data.ts | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/packages/gatsby-plugin-sharp/src/image-data.ts b/packages/gatsby-plugin-sharp/src/image-data.ts index a6c34cb11ea02..011c5ec47d789 100644 --- a/packages/gatsby-plugin-sharp/src/image-data.ts +++ b/packages/gatsby-plugin-sharp/src/image-data.ts @@ -119,6 +119,7 @@ function normalizeFormat(format: string): ImageFormat { return format as ImageFormat } +let didShow = false export async function generateImageData({ file, args, @@ -128,7 +129,7 @@ export async function generateImageData({ }: IImageDataArgs): Promise { args = mergeDefaults(args) - const { + let { layout = `constrained`, placeholder = `dominantColor`, tracedSVGOptions = {}, @@ -145,6 +146,16 @@ export async function generateImageData({ : DEFAULT_BREAKPOINTS } + if (placeholder === `tracedSVG`) { + if (!didShow) { + console.trace( + `[gatsby-plugin-sharp image-data generateImageData] traceSVG is no longer supported, falling back to dominant_color. See https://gatsby.dev/tracesvg-removal/` + ) + didShow = true + } + placeholder = `dominantColor` + } + const { fit = `cover`, cropFocus = sharp.strategy.attention, From b5b37f5a523de57199fddd5c1cd9335ede2e190f Mon Sep 17 00:00:00 2001 From: pieh Date: Thu, 24 Nov 2022 14:37:41 +0100 Subject: [PATCH 16/24] update e2e-prod/assertions --- .../cypress/integration/remote-file.js | 255 +++++++++--------- .../integration/static-image/traced.js | 14 +- 2 files changed, 139 insertions(+), 130 deletions(-) diff --git a/e2e-tests/production-runtime/cypress/integration/remote-file.js b/e2e-tests/production-runtime/cypress/integration/remote-file.js index 231644f0f6a71..ad5595a4e3bf2 100644 --- a/e2e-tests/production-runtime/cypress/integration/remote-file.js +++ b/e2e-tests/production-runtime/cypress/integration/remote-file.js @@ -1,149 +1,158 @@ -Cypress.on('uncaught:exception', (err) => { - if ((err.message.includes('Minified React error #418') || err.message.includes('Minified React error #423') || err.message.includes('Minified React error #425')) && Cypress.env(`TEST_PLUGIN_OFFLINE`)) { +Cypress.on("uncaught:exception", err => { + if ( + (err.message.includes("Minified React error #418") || + err.message.includes("Minified React error #423") || + err.message.includes("Minified React error #425")) && + Cypress.env(`TEST_PLUGIN_OFFLINE`) + ) { return false } }) describe( - `remote-file`, + `remote-file`, { retries: { runMode: 4, }, }, () => { - beforeEach(() => { - cy.visit(`/remote-file/`).waitForRouteChange() + beforeEach(() => { + cy.visit(`/remote-file/`).waitForRouteChange() - // trigger intersection observer - cy.scrollTo("top") - cy.wait(100) - cy.scrollTo("bottom", { - duration: 500, - }) - cy.wait(500) - }) - - async function testImages(images, expectations) { - for (let i = 0; i < images.length; i++) { - const expectation = expectations[i] - - const res = await fetch(images[i].currentSrc, { - method: "HEAD", + // trigger intersection observer + cy.scrollTo("top") + cy.wait(100) + cy.scrollTo("bottom", { + duration: 500, }) - expect(res.ok).to.be.true - if (expectation.width) { - expect(Math.ceil(images[i].getBoundingClientRect().width)).to.be.equal( - expectation.width - ) - } - if (expectation.height) { - expect(Math.ceil(images[i].getBoundingClientRect().height)).to.be.equal( - expectation.height - ) - } - } - } + cy.wait(500) + }) - it(`should render correct dimensions`, () => { - cy.get('[data-testid="public"]').then(async $urls => { - const urls = Array.from($urls.map((_, $url) => $url.getAttribute("href"))) + async function testImages(images, expectations) { + for (let i = 0; i < images.length; i++) { + const expectation = expectations[i] - for (const url of urls) { - const res = await fetch(url, { + const res = await fetch(images[i].currentSrc, { method: "HEAD", }) expect(res.ok).to.be.true + if (expectation.width) { + expect( + Math.ceil(images[i].getBoundingClientRect().width) + ).to.be.equal(expectation.width) + } + if (expectation.height) { + expect( + Math.ceil(images[i].getBoundingClientRect().height) + ).to.be.equal(expectation.height) + } } - }) - - cy.get(".resize").then(async $imgs => { - await testImages(Array.from($imgs), [ - { - width: 100, - height: 133, - }, - { - width: 100, - height: 160, - }, - { - width: 100, - height: 67, - }, - ]) - }) + } - cy.get(".fixed").then(async $imgs => { - await testImages(Array.from($imgs), [ - { - width: 100, - height: 133, - }, - { - width: 100, - height: 160, - }, - { - width: 100, - height: 67, - }, - ]) - }) + it(`should render correct dimensions`, () => { + cy.get('[data-testid="public"]').then(async $urls => { + const urls = Array.from( + $urls.map((_, $url) => $url.getAttribute("href")) + ) - cy.get(".constrained").then(async $imgs => { - await testImages(Array.from($imgs), [ - { - width: 300, - height: 400, - }, - { - width: 300, - height: 481, - }, - { - width: 300, - height: 200, - }, - ]) - }) + for (const url of urls) { + const res = await fetch(url, { + method: "HEAD", + }) + expect(res.ok).to.be.true + } + }) - cy.get(".full").then(async $imgs => { - await testImages(Array.from($imgs), [ - { - height: 1229, - }, - { - height: 1478, - }, - { - height: 614, - }, - ]) - }) - }) + cy.get(".resize").then(async $imgs => { + await testImages(Array.from($imgs), [ + { + width: 100, + height: 133, + }, + { + width: 100, + height: 160, + }, + { + width: 100, + height: 67, + }, + ]) + }) - it(`should render a placeholder`, () => { - cy.get(".fixed [data-placeholder-image]") - .first() - .should("have.css", "background-color", "rgb(232, 184, 8)") - cy.get(".constrained [data-placeholder-image]") - .first() - .should($el => { - expect($el.prop("tagName")).to.be.equal("IMG") - expect($el.prop("src")).to.contain("data:image/jpg;base64") + cy.get(".fixed").then(async $imgs => { + await testImages(Array.from($imgs), [ + { + width: 100, + height: 133, + }, + { + width: 100, + height: 160, + }, + { + width: 100, + height: 67, + }, + ]) }) - cy.get(".constrained_traced [data-placeholder-image]") - .first() - .should($el => { - expect($el.prop("tagName")).to.be.equal("IMG") - expect($el.prop("src")).to.contain("data:image/jpeg;base64") + + cy.get(".constrained").then(async $imgs => { + await testImages(Array.from($imgs), [ + { + width: 300, + height: 400, + }, + { + width: 300, + height: 481, + }, + { + width: 300, + height: 200, + }, + ]) }) - cy.get(".full [data-placeholder-image]") - .first() - .should($el => { - expect($el.prop("tagName")).to.be.equal("DIV") - expect($el).to.be.empty + + cy.get(".full").then(async $imgs => { + await testImages(Array.from($imgs), [ + { + height: 1229, + }, + { + height: 1478, + }, + { + height: 614, + }, + ]) }) - }) -}) + }) + + it(`should render a placeholder`, () => { + cy.get(".fixed [data-placeholder-image]") + .first() + .should("have.css", "background-color", "rgb(232, 184, 8)") + cy.get(".constrained [data-placeholder-image]") + .first() + .should($el => { + expect($el.prop("tagName")).to.be.equal("IMG") + expect($el.prop("src")).to.contain("data:image/jpg;base64") + }) + cy.get(".constrained_traced [data-placeholder-image]") + .first() + .should($el => { + // traced falls back to DOMINANT_COLOR + expect($el.prop("tagName")).to.be.equal("DIV") + expect($el).to.be.empty + }) + cy.get(".full [data-placeholder-image]") + .first() + .should($el => { + expect($el.prop("tagName")).to.be.equal("DIV") + expect($el).to.be.empty + }) + }) + } +) diff --git a/e2e-tests/production-runtime/cypress/integration/static-image/traced.js b/e2e-tests/production-runtime/cypress/integration/static-image/traced.js index 50429659b575d..e364290316848 100644 --- a/e2e-tests/production-runtime/cypress/integration/static-image/traced.js +++ b/e2e-tests/production-runtime/cypress/integration/static-image/traced.js @@ -16,14 +16,14 @@ describe(`fixed`, () => { cy.visit(`/static-image/traced`).waitForRouteChange() }) - it(`renders a traced svg`, () => { + it(`traced svg (falls back to DOMINANT_COLOR)`, () => { cy.getTestElement(tracedTestId) - .find(`.gatsby-image-wrapper > img`) - .should(`have.attr`, `src`) - .and(src => { - ;[`data:image/jped;base64`].forEach(part => - expect(src).to.include(part) - ) + .find(`.gatsby-image-wrapper > [data-placeholder-image]`) + .first() + .should($el => { + // traced falls + expect($el.prop("tagName")).to.be.equal("DIV") + expect($el).to.be.empty }) }) From dd1ca2b9271b6b9844d05313e2d3a63e36ee7e6b Mon Sep 17 00:00:00 2001 From: pieh Date: Thu, 24 Nov 2022 15:17:41 +0100 Subject: [PATCH 17/24] update unit test --- .../__tests__/gatsby-image-resolver.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/gatsby-plugin-utils/src/polyfill-remote-file/__tests__/gatsby-image-resolver.ts b/packages/gatsby-plugin-utils/src/polyfill-remote-file/__tests__/gatsby-image-resolver.ts index 5b915b29fe9af..5c065601a1067 100644 --- a/packages/gatsby-plugin-utils/src/polyfill-remote-file/__tests__/gatsby-image-resolver.ts +++ b/packages/gatsby-plugin-utils/src/polyfill-remote-file/__tests__/gatsby-image-resolver.ts @@ -745,11 +745,11 @@ describe(`gatsbyImageData`, () => { cacheKey: `1`, directory: expect.stringContaining(cacheDir), }) - expect(fixedResult?.placeholder).toMatchInlineSnapshot(` - Object { - "fallback": "rgb(56,40,40)", - } - `) + // placeholder doesn't exist, instead backgroundColor is used + expect(fixedResult?.placeholder).not.toBeDefined() + expect(fixedResult?.backgroundColor).toMatchInlineSnapshot( + `"rgb(56,40,40)"` + ) }) it(`should render avif, webp other format in this order`, async () => { From 9de464d38345a2ccf71eae08d6af7c584ba2ebd5 Mon Sep 17 00:00:00 2001 From: pieh Date: Thu, 24 Nov 2022 15:21:11 +0100 Subject: [PATCH 18/24] update snapshot --- .../gatsby-plugin-image.js/english-0.snap.png | Bin 49237 -> 49398 bytes e2e-tests/contentful/snapshots.js | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/e2e-tests/contentful/cypress/snapshots/gatsby-plugin-image.js/english-0.snap.png b/e2e-tests/contentful/cypress/snapshots/gatsby-plugin-image.js/english-0.snap.png index 263e5595d6eb466e92eaffe1a536258111a422d2..2fabcb0cced11e58587e207d2444021831b99ca4 100644 GIT binary patch literal 49398 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 EFTGt<8 literal 49237 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 diff --git a/e2e-tests/contentful/snapshots.js b/e2e-tests/contentful/snapshots.js index 2aeca7312618f..273c556c6caef 100644 --- a/e2e-tests/contentful/snapshots.js +++ b/e2e-tests/contentful/snapshots.js @@ -1,5 +1,5 @@ module.exports = { - "__version": "9.6.1", + "__version": "9.7.0", "content-reference": { "content-reference-many-2nd-level-loop": { "1": "
\n

Content Reference: Many (2nd level loop)

\n

[ContentfulNumber]\n 42

\n

[ContentfulText]\n The quick brown fox jumps over the lazy dog.

\n

[ContentfulReference]\n Content Reference: One (Loop A -> B)\n : [\n Content Reference: One (Loop B -> A)\n ]

\n
" From 2b019270bdd9783481a770a099e52808bd2c825b Mon Sep 17 00:00:00 2001 From: pieh Date: Thu, 24 Nov 2022 15:27:33 +0100 Subject: [PATCH 19/24] update e2e-dev/assertions --- .../integration/remote-file/gatsby-plugin-image.js | 5 +++-- .../cypress/integration/static-image/traced.js | 14 +++++++------- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/e2e-tests/development-runtime/cypress/integration/remote-file/gatsby-plugin-image.js b/e2e-tests/development-runtime/cypress/integration/remote-file/gatsby-plugin-image.js index 65ba35ccb6538..f5e49c77452ef 100644 --- a/e2e-tests/development-runtime/cypress/integration/remote-file/gatsby-plugin-image.js +++ b/e2e-tests/development-runtime/cypress/integration/remote-file/gatsby-plugin-image.js @@ -126,8 +126,9 @@ describe(`remote-file`, () => { cy.get(".constrained_traced [data-placeholder-image]") .first() .should($el => { - expect($el.prop("tagName")).to.be.equal("IMG") - expect($el.prop("src")).to.contain("data:image/jpeg;base64") + // traced falls back to DOMINANT_COLOR + expect($el.prop("tagName")).to.be.equal("DIV") + expect($el).to.be.empty }) cy.get(".full [data-placeholder-image]") .first() diff --git a/e2e-tests/development-runtime/cypress/integration/static-image/traced.js b/e2e-tests/development-runtime/cypress/integration/static-image/traced.js index cc9698bb6c566..0efda1ea4fef8 100644 --- a/e2e-tests/development-runtime/cypress/integration/static-image/traced.js +++ b/e2e-tests/development-runtime/cypress/integration/static-image/traced.js @@ -5,14 +5,14 @@ describe(`fixed`, () => { cy.visit(`/static-image/traced`).waitForRouteChange() }) - it(`renders a traced svg`, () => { + it(`traced svg (falls back to DOMINANT_COLOR)`, () => { cy.getTestElement(tracedTestId) - .find(`.gatsby-image-wrapper > img`) - .should(`have.attr`, `src`) - .and(src => { - ;[`data:image/jpeg;base64`].forEach(part => - expect(src).to.include(part) - ) + .find(`.gatsby-image-wrapper > [data-placeholder-image]`) + .first() + .should($el => { + // traced falls + expect($el.prop("tagName")).to.be.equal("DIV") + expect($el).to.be.empty }) }) From 0f8792091bc850b0616bbf9e4cdef6f5f9432b1b Mon Sep 17 00:00:00 2001 From: pieh Date: Thu, 24 Nov 2022 20:00:52 +0100 Subject: [PATCH 20/24] drop more unused --- packages/gatsby-plugin-utils/package.json | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/gatsby-plugin-utils/package.json b/packages/gatsby-plugin-utils/package.json index 81cd1650b1480..9645d0650ab66 100644 --- a/packages/gatsby-plugin-utils/package.json +++ b/packages/gatsby-plugin-utils/package.json @@ -54,9 +54,7 @@ "graphql-compose": "^9.0.9", "import-from": "^4.0.0", "joi": "^17.4.2", - "mime": "^3.0.0", - "mini-svg-data-uri": "^1.4.4", - "svgo": "^2.8.0" + "mime": "^3.0.0" }, "devDependencies": { "@babel/cli": "^7.15.4", From 9caedd2d4dca02fd9955ae0d21d0e6148bac9b06 Mon Sep 17 00:00:00 2001 From: pieh Date: Thu, 24 Nov 2022 20:02:21 +0100 Subject: [PATCH 21/24] sync yarn.lock --- yarn.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/yarn.lock b/yarn.lock index 934689288d0ee..f7fff14e1aaaf 100644 --- a/yarn.lock +++ b/yarn.lock @@ -22318,7 +22318,7 @@ svgo@^1.0.0: unquote "~1.1.1" util.promisify "~1.0.0" -svgo@^2.3.0, svgo@^2.8.0: +svgo@^2.3.0: version "2.8.0" resolved "https://registry.yarnpkg.com/svgo/-/svgo-2.8.0.tgz#4ff80cce6710dc2795f0c7c74101e6764cfccd24" integrity sha512-+N/Q9kV1+F+UeWYoSiULYo4xYSDQlTgb+ayMobAXPwMnLvop7oxKMo9OzIrX5x3eS4L4f2UHhc9axXwY8DpChg== From eee1d51d5ae7eba69779be2b2894d1710ee27683 Mon Sep 17 00:00:00 2001 From: pieh Date: Fri, 25 Nov 2022 08:45:50 +0100 Subject: [PATCH 22/24] a bit more prod warnings --- .../gatsby-plugin-sharp/src/image-data.ts | 10 ++--- packages/gatsby-plugin-sharp/src/index.js | 32 +++++++------- .../graphql/gatsby-image-resolver.ts | 11 +++-- .../placeholder-handler.ts | 14 +++--- .../src/gatsby-plugin-image.js | 11 +++-- .../src/customize-schema.js | 43 +++++++++---------- 6 files changed, 58 insertions(+), 63 deletions(-) diff --git a/packages/gatsby-plugin-sharp/src/image-data.ts b/packages/gatsby-plugin-sharp/src/image-data.ts index 011c5ec47d789..81364e3cd0e7e 100644 --- a/packages/gatsby-plugin-sharp/src/image-data.ts +++ b/packages/gatsby-plugin-sharp/src/image-data.ts @@ -119,7 +119,7 @@ function normalizeFormat(format: string): ImageFormat { return format as ImageFormat } -let didShow = false +let didShowTraceSVGRemovalWarning = false export async function generateImageData({ file, args, @@ -147,11 +147,11 @@ export async function generateImageData({ } if (placeholder === `tracedSVG`) { - if (!didShow) { - console.trace( - `[gatsby-plugin-sharp image-data generateImageData] traceSVG is no longer supported, falling back to dominant_color. See https://gatsby.dev/tracesvg-removal/` + if (!didShowTraceSVGRemovalWarning) { + console.warn( + `"TRACED_SVG" placeholder argument value is no longer supported (used in gatsbyImageData processing), falling back to "DOMINANT_COLOR". See https://gatsby.dev/tracesvg-removal/` ) - didShow = true + didShowTraceSVGRemovalWarning = true } placeholder = `dominantColor` } diff --git a/packages/gatsby-plugin-sharp/src/index.js b/packages/gatsby-plugin-sharp/src/index.js index 2587821a042d1..c1fef34709bbc 100644 --- a/packages/gatsby-plugin-sharp/src/index.js +++ b/packages/gatsby-plugin-sharp/src/index.js @@ -381,13 +381,13 @@ async function base64(arg) { return await memoizedBase64(arg) } -let didShow = false +let didShowTraceSVGRemovalWarning = false async function traceSVG(args) { - if (!didShow) { - console.trace( - `[gatsby-plugin-sharp traceSVG()] traceSVG is no longer supported, falling back to base64` + if (!didShowTraceSVGRemovalWarning) { + console.warn( + `traceSVG placeholder generation is no longer supported, falling back to blurred. See https://gatsby.dev/tracesvg-removal/` ) - didShow = true + didShowTraceSVGRemovalWarning = true } const { src } = await base64(args) @@ -415,8 +415,7 @@ async function stats({ file, reporter }) { } } -let didShowFluid = false - +let didShowTraceSVGRemovalWarningFluid = false async function fluid({ file, args = {}, reporter, cache }) { const options = healOptions(getPluginOptions(), args, file.extension) @@ -535,11 +534,11 @@ async function fluid({ file, args = {}, reporter, cache }) { }) if (options.generateTracedSVG && options.tracedSVG) { - if (!didShowFluid) { - console.trace( - `[gatsby-plugin-sharp fluid()] traceSVG is no longer supported, falling back to blurred. See https://gatsby.dev/tracesvg-removal/` + if (!didShowTraceSVGRemovalWarningFluid) { + console.warn( + `tracedSVG placeholder generation for fluid images is no longer supported, falling back to blurred. See https://gatsby.dev/tracesvg-removal/` ) - didShowFluid = true + didShowTraceSVGRemovalWarningFluid = true } } @@ -633,8 +632,7 @@ async function fluid({ file, args = {}, reporter, cache }) { } } -let didShowFixed = false - +let didShowTraceSVGRemovalWarningFixed = false async function fixed({ file, args = {}, reporter, cache }) { const options = healOptions(getPluginOptions(), args, file.extension) @@ -688,11 +686,11 @@ async function fixed({ file, args = {}, reporter, cache }) { }) if (options.generateTracedSVG && options.tracedSVG) { - if (!didShowFixed) { - console.trace( - `[gatsby-plugin-sharp fixed()] traceSVG is no longer supported, falling back to blurred. See https://gatsby.dev/tracesvg-removal/` + if (!didShowTraceSVGRemovalWarningFixed) { + console.warn( + `tracedSVG placeholder generation for fixed images is no longer supported, falling back to blurred. See https://gatsby.dev/tracesvg-removal/` ) - didShowFixed = true + didShowTraceSVGRemovalWarningFixed = true } } diff --git a/packages/gatsby-plugin-utils/src/polyfill-remote-file/graphql/gatsby-image-resolver.ts b/packages/gatsby-plugin-utils/src/polyfill-remote-file/graphql/gatsby-image-resolver.ts index e901081b4a102..4befd828b67cf 100644 --- a/packages/gatsby-plugin-utils/src/polyfill-remote-file/graphql/gatsby-image-resolver.ts +++ b/packages/gatsby-plugin-utils/src/polyfill-remote-file/graphql/gatsby-image-resolver.ts @@ -76,8 +76,7 @@ const GATSBY_SHOULD_TRACK_IMAGE_CDN_URLS = [`true`, `1`].includes( process.env.GATSBY_SHOULD_TRACK_IMAGE_CDN_URLS || `` ) -let didShow = false - +let didShowTraceSVGRemovalWarning = false export async function gatsbyImageResolver( source: IRemoteFileNode, args: IGatsbyImageDataArgs, @@ -124,11 +123,11 @@ export async function gatsbyImageResolver( if (!args.placeholder) { args.placeholder = PlaceholderType.DOMINANT_COLOR } else if (args.placeholder === PlaceholderType.TRACED_SVG) { - if (!didShow) { - console.trace( - `[gatsby-plugin-utils gatsbyImageResolver] traceSVG is no longer supported, falling back to DOMINANT_COLOR. See https://gatsby.dev/tracesvg-removal/` + if (!didShowTraceSVGRemovalWarning) { + console.warn( + `"TRACED_SVG" placeholder argument value is no longer supported (used in gatsbyImage processing), falling back to "DOMINANT_COLOR". See https://gatsby.dev/tracesvg-removal/` ) - didShow = true + didShowTraceSVGRemovalWarning = true } args.placeholder = PlaceholderType.DOMINANT_COLOR } diff --git a/packages/gatsby-plugin-utils/src/polyfill-remote-file/placeholder-handler.ts b/packages/gatsby-plugin-utils/src/polyfill-remote-file/placeholder-handler.ts index 03aa2f04291c1..c97cdd2863604 100644 --- a/packages/gatsby-plugin-utils/src/polyfill-remote-file/placeholder-handler.ts +++ b/packages/gatsby-plugin-utils/src/polyfill-remote-file/placeholder-handler.ts @@ -34,8 +34,7 @@ const PLACEHOLDER_TRACED_WIDTH = 200 let tmpDir: string -let didShow = false - +let didShowTraceSVGRemovalWarning = false const queue = Queue< undefined, { @@ -69,11 +68,14 @@ const queue = Queue< }) if (type === PlaceholderType.TRACED_SVG) { - if (!didShow) { - console.trace( - `[gatsby-plugin-utils placeholder-handler queue handler] traceSVG is no longer supported, falling back to blurred. See https://gatsby.dev/tracesvg-removal/` + if (!didShowTraceSVGRemovalWarning) { + // we should not hit this code path, field resolver should fallback earlier, this is just in-case. + // also this falls back to BLURRED because the shape is compatible. DOMINANT_COLOR is not compatible + // and fallback to DOMINANT_COLOR need to happen very early on and not when already generating value + console.warn( + `"TRACED_SVG" placeholder is no longer supported, falling back to "BLURRED". See https://gatsby.dev/tracesvg-removal/` ) - didShow = true + didShowTraceSVGRemovalWarning = true } type = PlaceholderType.BLURRED } diff --git a/packages/gatsby-source-contentful/src/gatsby-plugin-image.js b/packages/gatsby-source-contentful/src/gatsby-plugin-image.js index 6094aa539b640..963df00d8b013 100644 --- a/packages/gatsby-source-contentful/src/gatsby-plugin-image.js +++ b/packages/gatsby-source-contentful/src/gatsby-plugin-image.js @@ -252,8 +252,7 @@ export function generateImageSource( return { width, height, format: toFormat, src } } -let didShow = false - +let didShowTraceSVGRemovalWarning = false export async function resolveGatsbyImageData( image, options, @@ -289,11 +288,11 @@ export async function resolveGatsbyImageData( options = doMergeDefaults(options, defaults) if (options.placeholder === `tracedSVG`) { - if (!didShow) { - console.trace( - `[gatsby-source-contentful resolveGatsbytImageData] traceSVG is no longer supported, falling back to dominantColor` + if (!didShowTraceSVGRemovalWarning) { + console.warn( + `"TRACED_SVG" placeholder argument value is no longer supported (used in ContentfulAsset.gatsbyImageData processing), falling back to "DOMINANT_COLOR". See https://gatsby.dev/tracesvg-removal/` ) - didShow = true + didShowTraceSVGRemovalWarning = true } options.placeholder = `dominantColor` } diff --git a/packages/gatsby-transformer-sharp/src/customize-schema.js b/packages/gatsby-transformer-sharp/src/customize-schema.js index e228fdae3800a..f2b09e6e1e58f 100644 --- a/packages/gatsby-transformer-sharp/src/customize-schema.js +++ b/packages/gatsby-transformer-sharp/src/customize-schema.js @@ -66,8 +66,7 @@ function toArray(buf) { return arr } -let didShowFixed = false - +let didShowTraceSVGRemovalWarningFixed = false const fixedNodeType = ({ pathPrefix, getNodeAndSavePathDependency, @@ -83,11 +82,11 @@ const fixedNodeType = ({ tracedSVG: { type: GraphQLString, resolve: parent => { - if (!didShowFixed) { - console.trace( - `[gatsby-transformer-sharp fixed.tracedSVG] traceSVG is no longer supported, falling back to blurred. See https://gatsby.dev/tracesvg-removal/` + if (!didShowTraceSVGRemovalWarningFixed) { + console.warn( + `"tracedSVG" placeholder field is no longer supported (used in ImageSharp.fixed processing), falling back to "base64". See https://gatsby.dev/tracesvg-removal/` ) - didShowFixed = true + didShowTraceSVGRemovalWarningFixed = true } return parent.base64 }, @@ -229,8 +228,7 @@ const fixedNodeType = ({ } } -let didShowFluid = false - +let didShowTraceSVGRemovalWarningFluid = false const fluidNodeType = ({ pathPrefix, getNodeAndSavePathDependency, @@ -246,11 +244,11 @@ const fluidNodeType = ({ tracedSVG: { type: GraphQLString, resolve: parent => { - if (!didShowFluid) { - console.trace( - `[gatsby-transformer-sharp fluid.tracedSVG] traceSVG is no longer supported, falling back to blurred. See https://gatsby.dev/tracesvg-removal/` + if (!didShowTraceSVGRemovalWarningFluid) { + console.warn( + `"tracedSVG" placeholder field is no longer supported (used in ImageSharp.fluid processing), falling back to "base64". See https://gatsby.dev/tracesvg-removal/` ) - didShowFluid = true + didShowTraceSVGRemovalWarningFluid = true } return parent.base64 }, @@ -401,6 +399,7 @@ const fluidNodeType = ({ } } +let didShowTraceSVGRemovalWarningGatsbyImageData = false const imageNodeType = ({ pathPrefix, getNodeAndSavePathDependency, @@ -531,11 +530,11 @@ const imageNodeType = ({ } if (fieldArgs?.placeholder === `tracedSVG`) { - if (!didShow) { - console.trace( - `[gatsby-transformer-sharp gatsbyImageData resolver] traceSVG is no longer supported, falling back to dominantColor` + if (!didShowTraceSVGRemovalWarningGatsbyImageData) { + console.warn( + `"TRACED_SVG" placeholder argument value is no longer supported (used in ImageSharp.gatsbyImageData processing), falling back to "DOMINANT_COLOR". See https://gatsby.dev/tracesvg-removal/` ) - didShow = true + didShowTraceSVGRemovalWarningGatsbyImageData = true } fieldArgs.placeholder = `dominantColor` } @@ -553,15 +552,13 @@ const imageNodeType = ({ } } -let didShow = false - /** * Keeps track of asynchronous file copy to prevent sequence errors in the * underlying fs-extra module during parallel copies of the same file */ const inProgressCopy = new Set() -let didShowResized = false +let didShowTraceSVGRemovalWarningResize = false const createFields = ({ pathPrefix, @@ -648,11 +645,11 @@ const createFields = ({ tracedSVG: { type: GraphQLString, resolve: async parent => { - if (!didShowResized) { - console.trace( - `[gatsby-transformer-sharp resize resolver] traceSVG is no longer supported, falling back to blurred` + if (!didShowTraceSVGRemovalWarningResize) { + console.warn( + `"tracedSVG" placeholder field is no longer supported (used in ImageSharp.resize processing), falling back to "base64". See https://gatsby.dev/tracesvg-removal/` ) - didShowResized = true + didShowTraceSVGRemovalWarningResize = true } const { src } = await base64({ file: parent.file, From bf4127dd085e3a144669c905b3b289ebb4971aa4 Mon Sep 17 00:00:00 2001 From: pieh Date: Fri, 25 Nov 2022 08:50:24 +0100 Subject: [PATCH 23/24] adjust gatsby-remark-images plugin options warning --- .../src/__tests__/gatsby-node.js | 14 +++++++------- packages/gatsby-remark-images/src/gatsby-node.js | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/gatsby-remark-images/src/__tests__/gatsby-node.js b/packages/gatsby-remark-images/src/__tests__/gatsby-node.js index 75aab51d09e63..bd7060178e776 100644 --- a/packages/gatsby-remark-images/src/__tests__/gatsby-node.js +++ b/packages/gatsby-remark-images/src/__tests__/gatsby-node.js @@ -79,7 +79,7 @@ describe(`pluginOptionsSchema`, () => { expect(isValid).toBe(true) expect(warnSpy).toBeCalledWith( - `[gatsby-remark-images pluginOptions] traceSVG is no longer supported, falling back to blurred` + `"tracedSVG" plugin option for "gatsby-remark-images" is no longer supported. Blurred placeholder will be used.` ) expect(errors).toEqual([]) }) @@ -175,7 +175,7 @@ describe(`pluginOptionsSchema`, () => { if (booleanValue) { expect(warnSpy).toBeCalledWith( - `[gatsby-remark-images pluginOptions] traceSVG is no longer supported, falling back to blurred` + `"tracedSVG" plugin option for "gatsby-remark-images" is no longer supported. Blurred placeholder will be used.` ) } expect(errors).toEqual([]) @@ -204,7 +204,7 @@ describe(`pluginOptionsSchema`, () => { expect(isValid).toBe(true) expect(warnSpy).toBeCalledWith( - `[gatsby-remark-images pluginOptions] traceSVG is no longer supported, falling back to blurred` + `"tracedSVG" plugin option for "gatsby-remark-images" is no longer supported. Blurred placeholder will be used.` ) expect(errors).toEqual([]) }) @@ -230,7 +230,7 @@ describe(`pluginOptionsSchema`, () => { expect(isValid).toBe(true) expect(warnSpy).toBeCalledWith( - `[gatsby-remark-images pluginOptions] traceSVG is no longer supported, falling back to blurred` + `"tracedSVG" plugin option for "gatsby-remark-images" is no longer supported. Blurred placeholder will be used.` ) expect(errors).toEqual([]) }) @@ -270,7 +270,7 @@ describe(`pluginOptionsSchema`, () => { expect(isValid).toBe(true) expect(warnSpy).toBeCalledWith( - `[gatsby-remark-images pluginOptions] traceSVG is no longer supported, falling back to blurred` + `"tracedSVG" plugin option for "gatsby-remark-images" is no longer supported. Blurred placeholder will be used.` ) expect(errors).toEqual([]) }) @@ -293,7 +293,7 @@ describe(`pluginOptionsSchema`, () => { expect(isValid).toBe(true) expect(warnSpy).toBeCalledWith( - `[gatsby-remark-images pluginOptions] traceSVG is no longer supported, falling back to blurred` + `"tracedSVG" plugin option for "gatsby-remark-images" is no longer supported. Blurred placeholder will be used.` ) expect(errors).toEqual([]) }) @@ -359,7 +359,7 @@ describe(`pluginOptionsSchema`, () => { expect(isValid).toBe(true) expect(warnSpy).toBeCalledWith( - `[gatsby-remark-images pluginOptions] traceSVG is no longer supported, falling back to blurred` + `"tracedSVG" plugin option for "gatsby-remark-images" is no longer supported. Blurred placeholder will be used.` ) expect(errors).toEqual([]) }) diff --git a/packages/gatsby-remark-images/src/gatsby-node.js b/packages/gatsby-remark-images/src/gatsby-node.js index 6b122385b73e6..886cdcbf4121f 100644 --- a/packages/gatsby-remark-images/src/gatsby-node.js +++ b/packages/gatsby-remark-images/src/gatsby-node.js @@ -90,7 +90,7 @@ exports.pluginOptionsSchema = function ({ Joi }) { .custom(value => { if (!!value && !process.env.GATSBY_WORKER_ID) { console.warn( - `[gatsby-remark-images pluginOptions] traceSVG is no longer supported, falling back to blurred` + `"tracedSVG" plugin option for "gatsby-remark-images" is no longer supported. Blurred placeholder will be used.` ) } return undefined From f7e83905690081f27fcf8daba134af30db7aa50d Mon Sep 17 00:00:00 2001 From: pieh Date: Fri, 25 Nov 2022 09:24:44 +0100 Subject: [PATCH 24/24] add link to gatsby-remark-images warning --- .../src/__tests__/gatsby-node.js | 14 +++++++------- packages/gatsby-remark-images/src/gatsby-node.js | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/gatsby-remark-images/src/__tests__/gatsby-node.js b/packages/gatsby-remark-images/src/__tests__/gatsby-node.js index bd7060178e776..f01ba77956186 100644 --- a/packages/gatsby-remark-images/src/__tests__/gatsby-node.js +++ b/packages/gatsby-remark-images/src/__tests__/gatsby-node.js @@ -79,7 +79,7 @@ describe(`pluginOptionsSchema`, () => { expect(isValid).toBe(true) expect(warnSpy).toBeCalledWith( - `"tracedSVG" plugin option for "gatsby-remark-images" is no longer supported. Blurred placeholder will be used.` + `"tracedSVG" plugin option for "gatsby-remark-images" is no longer supported. Blurred placeholder will be used. See https://gatsby.dev/tracesvg-removal/` ) expect(errors).toEqual([]) }) @@ -175,7 +175,7 @@ describe(`pluginOptionsSchema`, () => { if (booleanValue) { expect(warnSpy).toBeCalledWith( - `"tracedSVG" plugin option for "gatsby-remark-images" is no longer supported. Blurred placeholder will be used.` + `"tracedSVG" plugin option for "gatsby-remark-images" is no longer supported. Blurred placeholder will be used. See https://gatsby.dev/tracesvg-removal/` ) } expect(errors).toEqual([]) @@ -204,7 +204,7 @@ describe(`pluginOptionsSchema`, () => { expect(isValid).toBe(true) expect(warnSpy).toBeCalledWith( - `"tracedSVG" plugin option for "gatsby-remark-images" is no longer supported. Blurred placeholder will be used.` + `"tracedSVG" plugin option for "gatsby-remark-images" is no longer supported. Blurred placeholder will be used. See https://gatsby.dev/tracesvg-removal/` ) expect(errors).toEqual([]) }) @@ -230,7 +230,7 @@ describe(`pluginOptionsSchema`, () => { expect(isValid).toBe(true) expect(warnSpy).toBeCalledWith( - `"tracedSVG" plugin option for "gatsby-remark-images" is no longer supported. Blurred placeholder will be used.` + `"tracedSVG" plugin option for "gatsby-remark-images" is no longer supported. Blurred placeholder will be used. See https://gatsby.dev/tracesvg-removal/` ) expect(errors).toEqual([]) }) @@ -270,7 +270,7 @@ describe(`pluginOptionsSchema`, () => { expect(isValid).toBe(true) expect(warnSpy).toBeCalledWith( - `"tracedSVG" plugin option for "gatsby-remark-images" is no longer supported. Blurred placeholder will be used.` + `"tracedSVG" plugin option for "gatsby-remark-images" is no longer supported. Blurred placeholder will be used. See https://gatsby.dev/tracesvg-removal/` ) expect(errors).toEqual([]) }) @@ -293,7 +293,7 @@ describe(`pluginOptionsSchema`, () => { expect(isValid).toBe(true) expect(warnSpy).toBeCalledWith( - `"tracedSVG" plugin option for "gatsby-remark-images" is no longer supported. Blurred placeholder will be used.` + `"tracedSVG" plugin option for "gatsby-remark-images" is no longer supported. Blurred placeholder will be used. See https://gatsby.dev/tracesvg-removal/` ) expect(errors).toEqual([]) }) @@ -359,7 +359,7 @@ describe(`pluginOptionsSchema`, () => { expect(isValid).toBe(true) expect(warnSpy).toBeCalledWith( - `"tracedSVG" plugin option for "gatsby-remark-images" is no longer supported. Blurred placeholder will be used.` + `"tracedSVG" plugin option for "gatsby-remark-images" is no longer supported. Blurred placeholder will be used. See https://gatsby.dev/tracesvg-removal/` ) expect(errors).toEqual([]) }) diff --git a/packages/gatsby-remark-images/src/gatsby-node.js b/packages/gatsby-remark-images/src/gatsby-node.js index 886cdcbf4121f..c1157c6e4d574 100644 --- a/packages/gatsby-remark-images/src/gatsby-node.js +++ b/packages/gatsby-remark-images/src/gatsby-node.js @@ -90,7 +90,7 @@ exports.pluginOptionsSchema = function ({ Joi }) { .custom(value => { if (!!value && !process.env.GATSBY_WORKER_ID) { console.warn( - `"tracedSVG" plugin option for "gatsby-remark-images" is no longer supported. Blurred placeholder will be used.` + `"tracedSVG" plugin option for "gatsby-remark-images" is no longer supported. Blurred placeholder will be used. See https://gatsby.dev/tracesvg-removal/` ) } return undefined