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

Ensure static image works correctly with basePath #29307

Merged
merged 7 commits into from
Sep 23, 2021
Merged
Show file tree
Hide file tree
Changes from 5 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
4 changes: 2 additions & 2 deletions packages/next/build/webpack/loaders/next-image-loader.js
Original file line number Diff line number Diff line change
Expand Up @@ -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') {
Expand All @@ -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)
Expand Down
7 changes: 7 additions & 0 deletions packages/next/server/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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`
Expand Down
4 changes: 3 additions & 1 deletion packages/next/server/image-optimizer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
20 changes: 20 additions & 0 deletions test/integration/image-component/base-path/components/TallImage.js
Original file line number Diff line number Diff line change
@@ -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
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
54 changes: 54 additions & 0 deletions test/integration/image-component/base-path/pages/static-img.js
Original file line number Diff line number Diff line change
@@ -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
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file not shown.
Binary file not shown.
105 changes: 105 additions & 0 deletions test/integration/image-component/base-path/test/static.test.js
Original file line number Diff line number Diff line change
@@ -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(&quot;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=&quot;);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(&quot;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAQAAABuBnYAAAAATklEQVR42i2I0QmAMBQD869Q9K+IsxU6RkfoiA6T55VXDpJLJC9uUJIzcx+XFd2dXMbx8n+QpoeYDpgY66RaDA83jCUfVpK2pER1dcEUP+KfSBtXK+BpAAAAAElFTkSuQmCC&quot;);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()
})