-
Notifications
You must be signed in to change notification settings - Fork 10.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: image and file cdn url generator adapter implementation (#38685)
* alternate image url construction * try using image cdn in e2e site * Update netlify.toml * have separate check for dispatching image and file service * fix tests? * try to use images from deploy (so we can avoid using ones hosted externally) * replicate prod-runtime imagecdn tests in adapters * fix import * adjusting remote-file tests * adjusting remote-file tests 2 * cleanup/test * assert naturalWidth/height in image-cdn tests (both adapters and production-runtime) * remove unused * don't use path prefix for alternate image cdn url * _gatsby/file is prefixed * feat: move custom image cdn url generator implementation to adapter (#38715) * feat: move custom image cdn url generator implementation to adapter * provide public types for custom image cdn url generator function signature and individual arguments * use position/cover * update comment * update docs * chore: types/jsdocs shuffle * apply suggestion from https://github.com/gatsbyjs/gatsby/pull/38685\#discussion_r1414135797 * remove docs from feature branch * feat: provide custom FILE_CDN url generator from adapter (#38735) * feat: provide file cdn from adapters * update test * fix tests * use edge function for non-image File CDN * why edge function was not deployed? * bump netlify-cli * ? * bump node for adapters tests * add execa to dev deps * cleanup * some jsdocs updates * add note that generated urls ideally are relative, but can be absolute as well * feat: allow adding remote file allowed url patterns (#38719) * feat: move custom image cdn url generator implementation to adapter * provide public types for custom image cdn url generator function signature and individual arguments * feat: allow adding image cdn allowed url patterns * Module.createRequireFromPath doesn't exist anymore in Node 18, and because package requires at least that version we remove it * fix contentful source image url * fix wordpress source image url * rename ImageCdnAllowed to RemoteFileAllowed as it's not just for image cdn * compare allowed remote urls in netlify.toml with ones generated by gatsby * url testing in filecdn * jsdocs * print warnings for netlify.toml about missing remote_images patterns * test if any existing pattern in netlify.toml allow needed remote url instead of just string comparison * chore: update adapter README about imageCDN * use correct remote_images for adapters e2e site --------- Co-authored-by: Michal Piechowiak <misiek.piechowiak@gmail.com>
- Loading branch information
Showing
49 changed files
with
2,177 additions
and
80 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,254 @@ | ||
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 | ||
} | ||
}) | ||
|
||
const PATH_PREFIX = Cypress.env(`PATH_PREFIX`) || `` | ||
|
||
// there are multiple scenarios we want to test and ensure that custom image cdn url is used: | ||
// - child build process (SSG, Page Query) | ||
// - main build process (SSG, Page Context) | ||
// - query engine (SSR, Page Query) | ||
const configs = [ | ||
{ | ||
title: `remote-file (SSG, Page Query)`, | ||
pagePath: `/routes/remote-file/`, | ||
placeholders: true, | ||
}, | ||
{ | ||
title: `remote-file (SSG, Page Context)`, | ||
pagePath: `/routes/remote-file-data-from-context/`, | ||
placeholders: true, | ||
}, | ||
{ | ||
title: `remote-file (SSR, Page Query)`, | ||
pagePath: `/routes/ssr/remote-file/`, | ||
placeholders: false, | ||
}, | ||
] | ||
|
||
for (const config of configs) { | ||
describe( | ||
config.title, | ||
{ | ||
retries: { | ||
runMode: 4, | ||
}, | ||
}, | ||
() => { | ||
beforeEach(() => { | ||
cy.visit(config.pagePath).waitForRouteChange() | ||
|
||
// trigger intersection observer | ||
cy.scrollTo("top") | ||
cy.wait(200) | ||
cy.scrollTo("bottom", { | ||
duration: 600, | ||
}) | ||
cy.wait(600) | ||
}) | ||
|
||
describe(`Image CDN`, () => { | ||
async function testImages(images, expectations) { | ||
for (let i = 0; i < images.length; i++) { | ||
const expectation = expectations[i] | ||
|
||
const url = images[i].currentSrc | ||
|
||
const { href, origin } = new URL(url) | ||
const urlWithoutOrigin = href.replace(origin, ``) | ||
|
||
// using Netlify Image CDN | ||
expect(urlWithoutOrigin).to.match(/^\/.netlify\/images/) | ||
|
||
const res = await fetch(url, { | ||
method: "HEAD", | ||
}) | ||
expect(res.ok).to.be.true | ||
|
||
const expectedNaturalWidth = | ||
expectation.naturalWidth ?? expectation.width | ||
const expectedNaturalHeight = | ||
expectation.naturalHeight ?? expectation.height | ||
|
||
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) | ||
} | ||
if (expectedNaturalWidth) { | ||
expect(Math.ceil(images[i].naturalWidth)).to.be.equal( | ||
expectedNaturalWidth | ||
) | ||
} | ||
if (expectedNaturalHeight) { | ||
expect(Math.ceil(images[i].naturalHeight)).to.be.equal( | ||
expectedNaturalHeight | ||
) | ||
} | ||
} | ||
} | ||
|
||
it(`should render correct dimensions`, () => { | ||
cy.get('[data-testid="image-public"]').then(async $urls => { | ||
const urls = Array.from( | ||
$urls.map((_, $url) => $url.getAttribute("href")) | ||
) | ||
|
||
// urls is array of href attribute, not absolute urls, so it already is stripped of origin | ||
for (const urlWithoutOrigin of urls) { | ||
// using Netlify Image CDN | ||
expect(urlWithoutOrigin).to.match(/^\/.netlify\/images/) | ||
const res = await fetch(urlWithoutOrigin, { | ||
method: "HEAD", | ||
}) | ||
expect(res.ok).to.be.true | ||
} | ||
}) | ||
|
||
cy.get(".resize").then({ timeout: 60000 }, async $imgs => { | ||
await testImages(Array.from($imgs), [ | ||
{ | ||
width: 100, | ||
height: 133, | ||
}, | ||
{ | ||
width: 100, | ||
height: 160, | ||
}, | ||
{ | ||
width: 100, | ||
height: 67, | ||
}, | ||
]) | ||
}) | ||
|
||
cy.get(".fixed img:not([aria-hidden=true])").then( | ||
{ timeout: 60000 }, | ||
async $imgs => { | ||
await testImages(Array.from($imgs), [ | ||
{ | ||
width: 100, | ||
height: 133, | ||
}, | ||
{ | ||
width: 100, | ||
height: 160, | ||
}, | ||
{ | ||
width: 100, | ||
height: 67, | ||
}, | ||
]) | ||
} | ||
) | ||
|
||
cy.get(".constrained img:not([aria-hidden=true])").then( | ||
{ timeout: 60000 }, | ||
async $imgs => { | ||
await testImages(Array.from($imgs), [ | ||
{ | ||
width: 300, | ||
height: 400, | ||
}, | ||
{ | ||
width: 300, | ||
height: 481, | ||
}, | ||
{ | ||
width: 300, | ||
height: 200, | ||
}, | ||
]) | ||
} | ||
) | ||
|
||
cy.get(".full img:not([aria-hidden=true])").then( | ||
{ timeout: 60000 }, | ||
async $imgs => { | ||
await testImages(Array.from($imgs), [ | ||
{ | ||
naturalHeight: 1333, | ||
}, | ||
{ | ||
naturalHeight: 1603, | ||
}, | ||
{ | ||
naturalHeight: 666, | ||
}, | ||
]) | ||
} | ||
) | ||
}) | ||
|
||
it(`should render a placeholder`, () => { | ||
if (config.placeholders) { | ||
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 | ||
}) | ||
}) | ||
}) | ||
|
||
it(`File CDN`, () => { | ||
cy.get('[data-testid="file-public"]').then(async $urls => { | ||
const fileCdnFixtures = Array.from( | ||
$urls.map((_, $url) => { | ||
return { | ||
urlWithoutOrigin: $url.getAttribute("href"), | ||
allowed: $url.getAttribute("data-allowed") === "true", | ||
} | ||
}) | ||
) | ||
|
||
// urls is array of href attribute, not absolute urls, so it already is stripped of origin | ||
for (const { urlWithoutOrigin, allowed } of fileCdnFixtures) { | ||
// using Netlify Image CDN | ||
expect(urlWithoutOrigin).to.match( | ||
new RegExp(`^${PATH_PREFIX}/_gatsby/file`) | ||
) | ||
const res = await fetch(urlWithoutOrigin, { | ||
method: "HEAD", | ||
}) | ||
if (allowed) { | ||
expect(res.ok).to.be.true | ||
} else { | ||
expect(res.ok).to.be.false | ||
expect(res.status).to.be.equal(500) | ||
} | ||
} | ||
}) | ||
}) | ||
} | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.