Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: move custom image cdn url generator implementation to adapter #38715

Merged
merged 2 commits into from
Nov 30, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
331 changes: 181 additions & 150 deletions e2e-tests/adapters/cypress/e2e/remote-file.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,107 +11,116 @@ Cypress.on("uncaught:exception", err => {

const PATH_PREFIX = Cypress.env(`PATH_PREFIX`) || ``

describe(
`remote-file`,
// 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 = [
{
retries: {
runMode: 4,
},
title: `remote-file (SSG, Page Query)`,
pagePath: `/routes/remote-file/`,
fileCDN: true,
placeholders: true,
},
{
title: `remote-file (SSG, Page Context)`,
pagePath: `/routes/remote-file-data-from-context/`,
fileCDN: true,
placeholders: true,
},
{
title: `remote-file (SSR, Page Query)`,
pagePath: `/routes/ssr/remote-file/`,
fileCDN: false,
placeholders: false,
},
Comment on lines +31 to 36
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some context here why fileCDN and placeholders are not checked - simply they don't exactly work.

fileCDN explanation:
Because fileCDN is not supported - it would try to run FILE_CDN job "locally" in lambda - however there is a problem with running actual IMAGE_CDN and FILE_CDN jobs (this happens when there is no handling at request time) - the "worker" responsible for handling those jobs is failing to load - this happens locally as well in gatsby serve, so this is not adapter or Netlify issue - it's a framework issue. However even if we fix worker loading - it still actually wouldn't exactly "just work" on Netlify as there is no straight forward way to add additional static assets to deployment after initial build and deployment finished - this likely could be supported via proxy lambda or something like that, but that would need additional work to properly work.

placeholders explanation:
linkfs we use in

// Alias the cache dir paths to the temp dir
const lfs = link(fs, rewrites) as typeof import("fs")
// linkfs doesn't pass across the `native` prop, which graceful-fs needs
for (const key in lfs) {
if (Object.hasOwnProperty.call(fs[key], `native`)) {
lfs[key].native = fs[key].native
}
}
const dbPath = path.join(TEMP_CACHE_DIR, `data`, `datastore`)
// 'promises' is not initially linked within the 'linkfs'
// package, and is needed by underlying Gatsby code (the
// @graphql-tools/code-file-loader)
lfs.promises = link(fs.promises, rewrites)
// Gatsby uses this instead of fs if present
// eslint-disable-next-line no-underscore-dangle
global._fsWrapper = lfs

doesn't seem to provide implementation for new lfs.WriteStream() which is used to generate placeholders (it does provide implementation for fs.createWriteStream so it should be possible to fix/patch it up, but didn't want to do it in this PR)

() => {
beforeEach(() => {
cy.visit(`/routes/remote-file/`).waitForRouteChange()

// trigger intersection observer
cy.scrollTo("top")
cy.wait(200)
cy.scrollTo("bottom", {
duration: 600,
]

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)
})
cy.wait(600)
})

async function testImages(images, expectations) {
for (let i = 0; i < images.length; i++) {
const expectation = expectations[i]
async function testImages(images, expectations) {
for (let i = 0; i < images.length; i++) {
const expectation = expectations[i]

const url = images[i].currentSrc
const url = images[i].currentSrc

const { href, origin } = new URL(url)
const urlWithoutOrigin = href.replace(origin, ``)
const { href, origin } = new URL(url)
const urlWithoutOrigin = href.replace(origin, ``)

// using Netlify Image CDN
expect(urlWithoutOrigin).to.match(/^\/.netlify\/images/)
// 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="public"]').then(async $urls => {
const urls = Array.from(
$urls.map((_, $url) => $url.getAttribute("href"))
)

for (const url of urls) {
// using OSS implementation for publicURL for now
expect(url).to.match(new RegExp(`^${PATH_PREFIX}/_gatsby/file`))
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
)
}
}
})
}

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,
},
])
})
it(`should render correct dimensions`, () => {
if (config.fileCDN) {
cy.get('[data-testid="public"]').then(async $urls => {
const urls = Array.from(
$urls.map((_, $url) => $url.getAttribute("href"))
)

for (const url of urls) {
// using OSS implementation for publicURL for now
expect(url).to.match(new RegExp(`^${PATH_PREFIX}/_gatsby/file`))
const res = await fetch(url, {
method: "HEAD",
})
expect(res.ok).to.be.true
}
})
}

cy.get(".fixed img:not([aria-hidden=true])").then(
{ timeout: 60000 },
async $imgs => {
cy.get(".resize").then({ timeout: 60000 }, async $imgs => {
await testImages(Array.from($imgs), [
{
width: 100,
Expand All @@ -126,70 +135,92 @@ describe(
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(".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(".full img:not([aria-hidden=true])").then(
{ timeout: 60000 },
async $imgs => {
await testImages(Array.from($imgs), [
{
naturalHeight: 1333,
},
{
naturalHeight: 1603,
},
{
naturalHeight: 666,
},
])
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
})
}
)
})

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
})
})
}
)
cy.get(".full [data-placeholder-image]")
.first()
.should($el => {
expect($el.prop("tagName")).to.be.equal("DIV")
expect($el).to.be.empty
})
})
}
)
}
Loading