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..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/svg+xml,%3csvg")
+ // 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 c038f779a1a2c..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,12 +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/svg+xml`].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
})
})
diff --git a/e2e-tests/production-runtime/cypress/integration/remote-file.js b/e2e-tests/production-runtime/cypress/integration/remote-file.js
index 9247acf4e7948..1c22b8d1f47cf 100644
--- a/e2e-tests/production-runtime/cypress/integration/remote-file.js
+++ b/e2e-tests/production-runtime/cypress/integration/remote-file.js
@@ -1,143 +1,147 @@
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/svg+xml,%3csvg")
+
+ 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 c038f779a1a2c..0efda1ea4fef8 100644
--- a/e2e-tests/production-runtime/cypress/integration/static-image/traced.js
+++ b/e2e-tests/production-runtime/cypress/integration/static-image/traced.js
@@ -5,12 +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/svg+xml`].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
})
})
diff --git a/examples/using-contentful/package.json b/examples/using-contentful/package.json
index bee4a0c26c41c..838a1bf31d704 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"
}
}
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 a35d1cb9eba38..3e5f5a49e2a84 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",
@@ -17,11 +16,9 @@
"gatsby-core-utils": "^3.24.0",
"gatsby-plugin-utils": "^3.18.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",
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/__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)
- }
- })
-})
diff --git a/packages/gatsby-plugin-sharp/src/image-data.ts b/packages/gatsby-plugin-sharp/src/image-data.ts
index a6c34cb11ea02..81364e3cd0e7e 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 didShowTraceSVGRemovalWarning = 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 (!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/`
+ )
+ didShowTraceSVGRemovalWarning = true
+ }
+ placeholder = `dominantColor`
+ }
+
const {
fit = `cover`,
cropFocus = sharp.strategy.attention,
diff --git a/packages/gatsby-plugin-sharp/src/index.js b/packages/gatsby-plugin-sharp/src/index.js
index f770efd3f5912..c1fef34709bbc 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,26 +381,17 @@ async function base64(arg) {
return await memoizedBase64(arg)
}
+let didShowTraceSVGRemovalWarning = false
async function traceSVG(args) {
- if (args.cache) {
- // Not all transformer plugins are going to provide cache
- return await cachifiedProcess(args, generateCacheKey, notMemoizedtraceSVG)
+ if (!didShowTraceSVGRemovalWarning) {
+ console.warn(
+ `traceSVG placeholder generation is no longer supported, falling back to blurred. See https://gatsby.dev/tracesvg-removal/`
+ )
+ didShowTraceSVGRemovalWarning = true
}
- return await memoizedTraceSVG(args)
-}
-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
+ const { src } = await base64(args)
+ return src
}
async function stats({ file, reporter }) {
@@ -425,6 +415,7 @@ async function stats({ file, reporter }) {
}
}
+let didShowTraceSVGRemovalWarningFluid = false
async function fluid({ file, args = {}, reporter, cache }) {
const options = healOptions(getPluginOptions(), args, file.extension)
@@ -542,8 +533,17 @@ async function fluid({ file, args = {}, reporter, cache }) {
reporter,
})
+ if (options.generateTracedSVG && options.tracedSVG) {
+ if (!didShowTraceSVGRemovalWarningFluid) {
+ console.warn(
+ `tracedSVG placeholder generation for fluid images is no longer supported, falling back to blurred. See https://gatsby.dev/tracesvg-removal/`
+ )
+ didShowTraceSVGRemovalWarningFluid = true
+ }
+ }
+
let base64Image
- if (options.base64) {
+ if (options.base64 || (options.generateTracedSVG && options.tracedSVG)) {
const base64Width = options.base64Width
const base64Height = Math.max(
1,
@@ -566,8 +566,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 =>
@@ -614,7 +612,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,
@@ -625,10 +623,16 @@ async function fluid({ file, args = {}, reporter, cache }) {
density,
presentationWidth,
presentationHeight,
- tracedSVG,
+ tracedSVG:
+ (options.generateTracedSVG &&
+ options.tracedSVG &&
+ base64Image &&
+ base64Image.src) ||
+ undefined,
}
}
+let didShowTraceSVGRemovalWarningFixed = false
async function fixed({ file, args = {}, reporter, cache }) {
const options = healOptions(getPluginOptions(), args, file.extension)
@@ -681,8 +685,17 @@ async function fixed({ file, args = {}, reporter, cache }) {
reporter,
})
+ if (options.generateTracedSVG && options.tracedSVG) {
+ if (!didShowTraceSVGRemovalWarningFixed) {
+ console.warn(
+ `tracedSVG placeholder generation for fixed images is no longer supported, falling back to blurred. See https://gatsby.dev/tracesvg-removal/`
+ )
+ didShowTraceSVGRemovalWarningFixed = true
+ }
+ }
+
let base64Image
- if (options.base64) {
+ if (options.base64 || (options.generateTracedSVG && options.tracedSVG)) {
const base64Width = options.base64Width
const base64Height = Math.max(
1,
@@ -710,8 +723,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) => {
@@ -735,14 +746,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,
}
}
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 ef4b6fb60c578..e65dd198d308a 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": "^3.24.0",
@@ -55,9 +54,7 @@
"graphql-compose": "^9.0.7",
"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",
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..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
@@ -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`)
)
@@ -745,11 +745,11 @@ describe(`gatsbyImageData`, () => {
cacheKey: `1`,
directory: expect.stringContaining(cacheDir),
})
- 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",
- }
- `)
+ // 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 () => {
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..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,6 +76,7 @@ const GATSBY_SHOULD_TRACK_IMAGE_CDN_URLS = [`true`, `1`].includes(
process.env.GATSBY_SHOULD_TRACK_IMAGE_CDN_URLS || ``
)
+let didShowTraceSVGRemovalWarning = false
export async function gatsbyImageResolver(
source: IRemoteFileNode,
args: IGatsbyImageDataArgs,
@@ -121,6 +122,14 @@ export async function gatsbyImageResolver(
if (!args.placeholder) {
args.placeholder = PlaceholderType.DOMINANT_COLOR
+ } else if (args.placeholder === PlaceholderType.TRACED_SVG) {
+ 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/`
+ )
+ didShowTraceSVGRemovalWarning = true
+ }
+ args.placeholder = PlaceholderType.DOMINANT_COLOR
}
if (!args.quality) {
@@ -305,9 +314,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..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,6 +34,7 @@ const PLACEHOLDER_TRACED_WIDTH = 200
let tmpDir: string
+let didShowTraceSVGRemovalWarning = false
const queue = Queue<
undefined,
{
@@ -66,6 +67,19 @@ const queue = Queue<
httpHeaders,
})
+ if (type === PlaceholderType.TRACED_SVG) {
+ 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/`
+ )
+ didShowTraceSVGRemovalWarning = true
+ }
+ type = PlaceholderType.BLURRED
+ }
+
switch (type) {
case PlaceholderType.BLURRED: {
let buffer: Buffer
@@ -99,76 +113,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 09d8a448091b1..5ca660a02e7e4 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": "^3.24.0",
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`] = `
+"
+
+
+
+
+ "
+`;
+
exports[`it handles goofy nesting properly 1`] = `
""
`;
-exports[`it uses tracedSVG placeholder when enabled 1`] = `
-"
-
-
-
-
- "
-`;
-
exports[`markdownCaptions display title in markdown as caption when showCaptions === true && markdownCaptions === true 1`] = `
"