Ensure static image works correctly with basePath (#29307)
This ensures we prefix the `src` for static images with the `basePath` correctly, this also copies over the static image tests to the basePath image-component suite. ## Bug - [x] Related issues linked using `fixes #number` - [x] Integration tests added - [x] Errors have helpful link attached, see `contributing.md` Fixes: https://github.com/vercel/next.js/issues/29289
This commit is contained in:
parent
9343b67c11
commit
5dbb8704cc
10 changed files with 191 additions and 3 deletions
|
@ -17,7 +17,7 @@ function nextImageLoader(content) {
|
|||
'/static/image/[path][name].[hash].[ext]',
|
||||
opts
|
||||
)
|
||||
const outputPath = '/_next' + interpolatedName
|
||||
const outputPath = assetPrefix + '/_next' + interpolatedName
|
||||
|
||||
let extension = loaderUtils.interpolateName(this, '[ext]', opts)
|
||||
if (extension === 'jpg') {
|
||||
|
@ -32,7 +32,7 @@ function nextImageLoader(content) {
|
|||
if (isDev) {
|
||||
const prefix = 'http://localhost'
|
||||
const url = new URL('/_next/image', prefix)
|
||||
url.searchParams.set('url', assetPrefix + outputPath)
|
||||
url.searchParams.set('url', outputPath)
|
||||
url.searchParams.set('w', BLUR_IMG_SIZE)
|
||||
url.searchParams.set('q', BLUR_QUALITY)
|
||||
blurDataURL = url.href.slice(prefix.length)
|
||||
|
|
|
@ -196,6 +196,13 @@ function assignDefaults(userConfig: { [key: string]: any }) {
|
|||
)
|
||||
}
|
||||
|
||||
// static images are automatically prefixed with assetPrefix
|
||||
// so we need to ensure _next/image allows downloading from
|
||||
// this resource
|
||||
if (config.assetPrefix?.startsWith('http')) {
|
||||
images.domains.push(new URL(config.assetPrefix).hostname)
|
||||
}
|
||||
|
||||
if (images.domains.length > 50) {
|
||||
throw new Error(
|
||||
`Specified images.domains exceeds length of 50, received length (${images.domains.length}), please reduce the length of the array to continue.\nSee more info here: https://nextjs.org/docs/messages/invalid-images-config`
|
||||
|
|
|
@ -135,7 +135,9 @@ export async function imageOptimizer(
|
|||
}
|
||||
|
||||
// Should match output from next-image-loader
|
||||
const isStatic = url.startsWith('/_next/static/image')
|
||||
const isStatic = url.startsWith(
|
||||
`${nextConfig.basePath || ''}/_next/static/image`
|
||||
)
|
||||
|
||||
const width = parseInt(w, 10)
|
||||
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
import React from 'react'
|
||||
import Image from 'next/image'
|
||||
|
||||
import testTall from './tall.png'
|
||||
|
||||
const Page = () => {
|
||||
return (
|
||||
<div>
|
||||
<h1 id="page-header">Static Image</h1>
|
||||
<Image
|
||||
id="basic-static"
|
||||
src={testTall}
|
||||
layout="fixed"
|
||||
placeholder="blur"
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Page
|
BIN
test/integration/image-component/base-path/components/tall.png
Normal file
BIN
test/integration/image-component/base-path/components/tall.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 6.2 KiB |
|
@ -0,0 +1,54 @@
|
|||
import React from 'react'
|
||||
import testImg from '../public/foo/test-rect.jpg'
|
||||
import Image from 'next/image'
|
||||
|
||||
import testJPG from '../public/test.jpg'
|
||||
import testPNG from '../public/test.png'
|
||||
import testWEBP from '../public/test.webp'
|
||||
import testSVG from '../public/test.svg'
|
||||
import testGIF from '../public/test.gif'
|
||||
import testBMP from '../public/test.bmp'
|
||||
import testICO from '../public/test.ico'
|
||||
|
||||
import TallImage from '../components/TallImage'
|
||||
|
||||
const Page = () => {
|
||||
return (
|
||||
<div>
|
||||
<h1 id="page-header">Static Image</h1>
|
||||
<Image
|
||||
id="basic-static"
|
||||
src={testImg}
|
||||
layout="fixed"
|
||||
placeholder="blur"
|
||||
/>
|
||||
<TallImage />
|
||||
<Image
|
||||
id="defined-size-static"
|
||||
src={testPNG}
|
||||
layout="fixed"
|
||||
height="200"
|
||||
width="200"
|
||||
/>
|
||||
<Image id="require-static" src={require('../public/foo/test-rect.jpg')} />
|
||||
<Image
|
||||
id="basic-non-static"
|
||||
src="/test-rect.jpg"
|
||||
width="400"
|
||||
height="300"
|
||||
/>
|
||||
<br />
|
||||
<Image id="blur-png" src={testPNG} placeholder="blur" />
|
||||
<Image id="blur-jpg" src={testJPG} placeholder="blur" />
|
||||
<Image id="blur-webp" src={testWEBP} placeholder="blur" />
|
||||
<Image id="static-svg" src={testSVG} />
|
||||
<Image id="static-gif" src={testGIF} />
|
||||
<Image id="static-bmp" src={testBMP} />
|
||||
<Image id="static-ico" src={testICO} />
|
||||
<br />
|
||||
<Image id="static-unoptimized" src={testJPG} unoptimized />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Page
|
Binary file not shown.
After Width: | Height: | Size: 5.9 KiB |
BIN
test/integration/image-component/base-path/public/test.ico
Normal file
BIN
test/integration/image-component/base-path/public/test.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.2 KiB |
BIN
test/integration/image-component/base-path/public/test.webp
Normal file
BIN
test/integration/image-component/base-path/public/test.webp
Normal file
Binary file not shown.
After Width: | Height: | Size: 1,018 B |
105
test/integration/image-component/base-path/test/static.test.js
Normal file
105
test/integration/image-component/base-path/test/static.test.js
Normal file
|
@ -0,0 +1,105 @@
|
|||
import {
|
||||
findPort,
|
||||
killApp,
|
||||
nextBuild,
|
||||
nextStart,
|
||||
renderViaHTTP,
|
||||
File,
|
||||
waitFor,
|
||||
} from 'next-test-utils'
|
||||
import webdriver from 'next-webdriver'
|
||||
import { join } from 'path'
|
||||
|
||||
const appDir = join(__dirname, '../')
|
||||
let appPort
|
||||
let app
|
||||
let browser
|
||||
let html
|
||||
|
||||
const indexPage = new File(join(appDir, 'pages/static-img.js'))
|
||||
|
||||
const runTests = () => {
|
||||
it('Should allow an image with a static src to omit height and width', async () => {
|
||||
expect(await browser.elementById('basic-static')).toBeTruthy()
|
||||
expect(await browser.elementById('blur-png')).toBeTruthy()
|
||||
expect(await browser.elementById('blur-webp')).toBeTruthy()
|
||||
expect(await browser.elementById('blur-jpg')).toBeTruthy()
|
||||
expect(await browser.elementById('static-svg')).toBeTruthy()
|
||||
expect(await browser.elementById('static-gif')).toBeTruthy()
|
||||
expect(await browser.elementById('static-bmp')).toBeTruthy()
|
||||
expect(await browser.elementById('static-ico')).toBeTruthy()
|
||||
expect(await browser.elementById('static-unoptimized')).toBeTruthy()
|
||||
})
|
||||
it('Should use immutable cache-control header for static import', async () => {
|
||||
await browser.eval(
|
||||
`document.getElementById("basic-static").scrollIntoView()`
|
||||
)
|
||||
await waitFor(1000)
|
||||
const url = await browser.eval(
|
||||
`document.getElementById("basic-static").src`
|
||||
)
|
||||
const res = await fetch(url)
|
||||
expect(res.headers.get('cache-control')).toBe(
|
||||
'public, max-age=315360000, immutable'
|
||||
)
|
||||
})
|
||||
it('Should use immutable cache-control header even when unoptimized', async () => {
|
||||
await browser.eval(
|
||||
`document.getElementById("static-unoptimized").scrollIntoView()`
|
||||
)
|
||||
await waitFor(1000)
|
||||
const url = await browser.eval(
|
||||
`document.getElementById("static-unoptimized").src`
|
||||
)
|
||||
const res = await fetch(url)
|
||||
expect(res.headers.get('cache-control')).toBe(
|
||||
'public, max-age=31536000, immutable'
|
||||
)
|
||||
})
|
||||
it('Should automatically provide an image height and width', async () => {
|
||||
expect(html).toContain('width:400px;height:300px')
|
||||
})
|
||||
it('Should allow provided width and height to override intrinsic', async () => {
|
||||
expect(html).toContain('width:200px;height:200px')
|
||||
expect(html).not.toContain('width:400px;height:400px')
|
||||
})
|
||||
it('Should add a blurry placeholder to statically imported jpg', async () => {
|
||||
expect(html).toContain(
|
||||
`style="position:absolute;top:0;left:0;bottom:0;right:0;box-sizing:border-box;padding:0;border:none;margin:auto;display:block;width:0;height:0;min-width:100%;max-width:100%;min-height:100%;max-height:100%;filter:blur(20px);background-size:cover;background-image:url("data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wCEAAoKCgoKCgsMDAsPEA4QDxYUExMUFiIYGhgaGCIzICUgICUgMy03LCksNy1RQDg4QFFeT0pPXnFlZXGPiI+7u/sBCgoKCgoKCwwMCw8QDhAPFhQTExQWIhgaGBoYIjMgJSAgJSAzLTcsKSw3LVFAODhAUV5PSk9ecWVlcY+Ij7u7+//CABEIAAgACAMBIgACEQEDEQH/xAAUAAEAAAAAAAAAAAAAAAAAAAAH/9oACAEBAAAAADX/xAAUAQEAAAAAAAAAAAAAAAAAAAAA/9oACAECEAAAAH//xAAUAQEAAAAAAAAAAAAAAAAAAAAA/9oACAEDEAAAAH//xAAdEAABAgcAAAAAAAAAAAAAAAATEhUAAwUUIzLS/9oACAEBAAE/AB0ZlUac43GqMYuo/8QAFBEBAAAAAAAAAAAAAAAAAAAAAP/aAAgBAgEBPwB//8QAFBEBAAAAAAAAAAAAAAAAAAAAAP/aAAgBAwEBPwB//9k=");background-position:0% 0%"`
|
||||
)
|
||||
})
|
||||
it('Should add a blurry placeholder to statically imported png', async () => {
|
||||
expect(html).toContain(
|
||||
`style="position:absolute;top:0;left:0;bottom:0;right:0;box-sizing:border-box;padding:0;border:none;margin:auto;display:block;width:0;height:0;min-width:100%;max-width:100%;min-height:100%;max-height:100%;filter:blur(20px);background-size:cover;background-image:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAQAAABuBnYAAAAATklEQVR42i2I0QmAMBQD869Q9K+IsxU6RkfoiA6T55VXDpJLJC9uUJIzcx+XFd2dXMbx8n+QpoeYDpgY66RaDA83jCUfVpK2pER1dcEUP+KfSBtXK+BpAAAAAElFTkSuQmCC");background-position:0% 0%"`
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
describe('Build Error Tests for basePath', () => {
|
||||
it('should throw build error when import statement is used with missing file', async () => {
|
||||
await indexPage.replace(
|
||||
'../public/foo/test-rect.jpg',
|
||||
'../public/foo/test-rect-broken.jpg'
|
||||
)
|
||||
|
||||
const { stderr } = await nextBuild(appDir, undefined, { stderr: true })
|
||||
await indexPage.restore()
|
||||
|
||||
expect(stderr).toContain(
|
||||
"Error: Can't resolve '../public/foo/test-rect-broken.jpg"
|
||||
)
|
||||
})
|
||||
})
|
||||
describe('Static Image Component Tests for basePath', () => {
|
||||
beforeAll(async () => {
|
||||
await nextBuild(appDir)
|
||||
appPort = await findPort()
|
||||
app = await nextStart(appDir, appPort)
|
||||
html = await renderViaHTTP(appPort, '/docs/static-img')
|
||||
browser = await webdriver(appPort, '/docs/static-img')
|
||||
})
|
||||
afterAll(() => {
|
||||
killApp(app)
|
||||
})
|
||||
runTests()
|
||||
})
|
Loading…
Reference in a new issue