Skip to content

Commit

Permalink
fix(gatsby-plugin-image): fix image flickers (#35226)
Browse files Browse the repository at this point in the history
  • Loading branch information
wardpeet committed May 5, 2022
1 parent f358dc3 commit 9a91295
Show file tree
Hide file tree
Showing 29 changed files with 808 additions and 754 deletions.
1 change: 1 addition & 0 deletions e2e-tests/contentful/cypress/integration/rich-text.js
Expand Up @@ -14,6 +14,7 @@ function testWithGatsbyPluginImage(elem) {
const cleanHtml = html
.replace(base64ImageExp, `data:image/redacted;base64,redacted`)
.replace(styleAttrExp, ``)
.replace(/data-gatsby-image-ssr=\"\"/gm, "")

// Create a DOM element with the redacted base64 data
cy.document().then(document => {
Expand Down
39 changes: 19 additions & 20 deletions e2e-tests/contentful/package.json
Expand Up @@ -5,22 +5,30 @@
"author": "Kyle Mathews <mathews.kyle@gmail.com>",
"dependencies": {
"@contentful/rich-text-types": "^14.1.2",
"cypress": "^6.8.0",
"cypress-image-snapshot": "^4.0.1",
"gatsby": "^3.1.1",
"gatsby-image": "^3.3.0",
"gatsby-plugin-image": "^1.3.1",
"gatsby-plugin-sharp": "^3.1.2",
"gatsby-source-contentful": "^5.1.1",
"gatsby-transformer-remark": "^4.0.0",
"gatsby-transformer-sharp": "^3.3.0",
"gatsby-transformer-sqip": "3.3.1",
"gatsby": "next",
"gatsby-image": "next",
"gatsby-plugin-image": "next",
"gatsby-plugin-sharp": "next",
"gatsby-source-contentful": "next",
"gatsby-transformer-remark": "next",
"gatsby-transformer-sharp": "next",
"gatsby-transformer-sqip": "next",
"modern-normalize": "^1.0.0",
"prop-types": "^15.7.2",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"slugify": "^1.5.0"
},
"devDependencies": {
"@cypress/snapshot": "^2.1.7",
"cross-env": "^7.0.3",
"cypress": "^9.5.4",
"cypress-image-snapshot": "^4.0.1",
"gatsby-cypress": "latest",
"prettier": "^2.6.2",
"srcset": "^5.0.0",
"start-server-and-test": "^1.7.1"
},
"keywords": [
"gatsby"
],
Expand All @@ -35,17 +43,8 @@
"cy:open": "cypress open",
"cy:run": "node ../../scripts/cypress-run-with-conditional-record-flag.js --browser chrome"
},
"devDependencies": {
"@cypress/snapshot": "^2.1.7",
"cross-env": "^7.0.3",
"gatsby-cypress": "^1.3.0",
"is-ci": "^3.0.0",
"prettier": "2.2.1",
"srcset": "^5.0.0",
"start-server-and-test": "^1.7.1"
},
"repository": {
"type": "git",
"url": "https://github.com/gatsbyjs/gatsby-starter-default"
}
}
}
6 changes: 3 additions & 3 deletions e2e-tests/contentful/snapshots.js

Large diffs are not rendered by default.

@@ -0,0 +1,190 @@
const MutationObserver =
window.MutationObserver || window.WebKitMutationObserver

// helper function to observe DOM changes
function observeDOM(obj, options, callback) {
if (!obj || obj.nodeType !== window.Node.ELEMENT_NODE) {
throw new Error("can not observe this element")
}

const obs = new MutationObserver(callback)

obs.observe(obj, { childList: true, subtree: true, ...options })

return () => {
obs.disconnect()
}
}

describe(`gatsby-plugin-image`, () => {
it(`doesn't recycle image nodes when not necessary`, () => {
const mutationStub = cy.stub()
let cleanup

cy.visit(`/gatsby-plugin-image-page-1/`)

// wait for the image to load
cy.get("[data-main-image]").should("be.visible")

// start watching mutations in the image-wrapper
cy.get("body").then($element => {
cleanup = observeDOM($element[0], {}, mutations => {
const normalizedMutations = []
mutations.forEach(mutation => {
if (
mutation.type === "childList" &&
mutation.target.classList.contains("gatsby-image-wrapper")
) {
normalizedMutations.push({
type: mutation.type,
addedNodes: !!mutation.addedNodes.length,
removedNodes: !!mutation.removedNodes.length,
})
}
})

if (normalizedMutations.length) {
mutationStub(normalizedMutations)
}
})
})

cy.window()
.then(win => win.___navigate(`/gatsby-plugin-image-page-2/`))
.waitForRouteChange()

// wait for the image to load
cy.get("[data-main-image]").should("be.visible")
cy.wait(500)

cy.then(() => {
cleanup()
expect(mutationStub).to.be.calledWith([
{
type: "childList",
addedNodes: true,
removedNodes: false,
},
])
})
})

it(`rerenders when image src changed`, () => {
const mutationStub = cy.stub()
let cleanup

cy.visit(`/gatsby-plugin-image-page-1/`)

// wait for the image to load
cy.get("[data-main-image]").should("be.visible")

// start watching mutations in the image-wrapper
cy.get("#image-wrapper").then($element => {
cleanup = observeDOM($element[0], {}, mutations => {
const normalizedMutations = []
mutationStub(
mutations.map(mutation => {
normalizedMutations.push({
addedNodes: mutation.addedNodes,
removedNodes: mutation.removedNodes,
})

return {
type: mutation.type,
addedNodes: !!mutation.addedNodes.length,
removedNodes: !!mutation.removedNodes.length,
}
})
)

Cypress.log({
name: "MutationObserver",
message: `${normalizedMutations.length} mutations`,
consoleProps: () => {
return {
mutations: normalizedMutations,
}
},
})
})
})

cy.get("#click").click()

cy.wait(500)

cy.get("[data-main-image]", {
timeout: 5000,
}).should("be.visible")

cy.then(() => {
cleanup()
expect(mutationStub).to.be.calledOnce
expect(mutationStub).to.be.calledWith([
{
type: "childList",
addedNodes: true,
removedNodes: true,
},
])
})
})

it(`rerenders when background color changes`, () => {
const mutationStub = cy.stub()
let cleanup

cy.visit(`/gatsby-plugin-image-page-2/`)

// wait for the image to load
cy.get("[data-main-image]").should("be.visible")

// start watching mutations in the image-wrapper
cy.get("#image-wrapper").then($element => {
cleanup = observeDOM(
$element[0],
{
attributes: true,
attributeFilter: ["style"],
},
mutations => {
mutationStub(
mutations.map(mutation => ({
type: mutation.type,
attributeName: mutation.attributeName,
}))
)
}
)
})

cy.get("[data-gatsby-image-wrapper]").should(
"have.css",
"background-color",
"rgb(102, 51, 153)"
)

cy.get("#click").click()

cy.wait(500)

// wait for the image to load
cy.get("[data-main-image]").should("be.visible")

cy.get("[data-gatsby-image-wrapper]").should(
"have.css",
"background-color",
"rgb(255, 0, 0)"
)
cy.then(() => {
cleanup()
expect(mutationStub).to.be.calledOnce
expect(mutationStub).to.be.calledWith([
{
type: "attributes",
attributeName: "style",
},
])
})
})
})
@@ -1,6 +1,4 @@
// TODO: In https://github.com/gatsbyjs/gatsby/pull/35226 the skip needs to be removed

describe.skip(`Production build tests`, () => {
describe(`Production build tests`, () => {
it(`should remount when navigating to different template`, () => {
cy.visit(`/`).waitForRouteChange()

Expand Down
@@ -0,0 +1,45 @@
import { graphql } from "gatsby"
import React from "react"

import { GatsbyImage } from "gatsby-plugin-image"

const PluginImage = ({ data }) => {
const images = data.allMyRemoteFile.nodes
const [nodeIndex, updateNode] = React.useState(0)

return (
<div>
<h1>Image Testing</h1>

<div id="image-wrapper">
<GatsbyImage
image={data.allMyRemoteFile.nodes[nodeIndex].fixed}
alt="fixed"
backgroundColor="rebeccapurple"
/>
</div>

<button
id="click"
onClick={() =>
updateNode(nodeIndex === images.length - 1 ? 0 : nodeIndex + 1)
}
>
Change Image
</button>
</div>
)
}

export const pageQuery = graphql`
{
allMyRemoteFile {
nodes {
id
fixed: gatsbyImage(layout: FIXED, width: 100, placeholder: NONE)
}
}
}
`

export default PluginImage
@@ -0,0 +1,44 @@
import { graphql } from "gatsby"
import React from "react"

import { GatsbyImage } from "gatsby-plugin-image"

const PluginImage = ({ data }) => {
const [bg, updateBg] = React.useState("rebeccapurple")

return (
<div>
<h1>Image Testing</h1>

<div id="image-wrapper">
<GatsbyImage
image={data.allMyRemoteFile.nodes[0].fixed}
alt="fixed"
backgroundColor={bg}
/>
</div>

<button
id="click"
onClick={() =>
updateBg(bg === "rebeccapurple" ? "red" : "rebeccapurple")
}
>
Change Image
</button>
</div>
)
}

export const pageQuery = graphql`
{
allMyRemoteFile {
nodes {
id
fixed: gatsbyImage(layout: FIXED, width: 100, placeholder: NONE)
}
}
}
`

export default PluginImage
6 changes: 0 additions & 6 deletions packages/gatsby-plugin-image/gatsby-browser.js

This file was deleted.

7 changes: 3 additions & 4 deletions packages/gatsby-plugin-image/package.json
Expand Up @@ -6,9 +6,9 @@
"build:gatsby-node": "tsc --jsx react --downlevelIteration true --skipLibCheck true --esModuleInterop true --outDir dist/ src/gatsby-node.ts src/babel-plugin-parse-static-images.ts src/resolver-utils.ts src/types.d.ts -d --declarationDir dist/src",
"build:gatsby-ssr": "microbundle -i src/gatsby-ssr.tsx -f cjs -o ./[name].js --no-pkg-main --jsx React.createElement --no-compress --external=common-tags,react --no-sourcemap",
"build:server": "microbundle -f cjs,es --jsx React.createElement --define SERVER=true",
"build:browser": "microbundle -i src/index.browser.ts -f cjs,modern,es --jsx React.createElement -o dist/gatsby-image.browser --define SERVER=false",
"build:browser": "microbundle -i src/index.browser.ts -f cjs,modern --jsx React.createElement -o dist/gatsby-image.browser --define SERVER=false",
"prepare": "yarn build",
"watch": "run-p watch:*",
"watch": "npm-run-all -s clean -p watch:*",
"watch:gatsby-node": "yarn build:gatsby-node --watch",
"watch:gatsby-ssr": "yarn build:gatsby-ssr watch",
"watch:server": "yarn build:server --no-compress watch",
Expand All @@ -27,8 +27,7 @@
"esmodule": "dist/gatsby-image.modern.js",
"browser": {
"./dist/gatsby-image.js": "./dist/gatsby-image.browser.js",
"./dist/gatsby-image.module.js": "./dist/gatsby-image.browser.module.js",
"./dist/gatsby-image.modern.js": "./dist/gatsby-image.browser.modern.js"
"./dist/gatsby-image.module.js": "./dist/gatsby-image.browser.modern.js"
},
"files": [
"dist/*",
Expand Down

0 comments on commit 9a91295

Please sign in to comment.