From ba2923436aa0307cf0140de3a65746073450a929 Mon Sep 17 00:00:00 2001 From: Darsh Patel Date: Thu, 8 Jul 2021 01:50:16 +0530 Subject: [PATCH] Add `dangerously-unoptimized` loader for next/image (#26847) * add support for static loader * update existing test * add test * add prettier-ignore * update docs * apply suggestions from review * remove un-needed normalize * Apply suggestions from code review Co-authored-by: Steven * update test Co-authored-by: JJ Kasper Co-authored-by: Steven --- docs/basic-features/image-optimization.md | 1 + packages/next/client/image.tsx | 5 ++ packages/next/server/image-config.ts | 1 + .../export-image-loader/next.config.js | 8 +-- .../export-image-loader/test/index.test.js | 51 ++++++++++++++++++- .../image-optimizer/test/index.test.js | 2 +- 6 files changed, 60 insertions(+), 8 deletions(-) diff --git a/docs/basic-features/image-optimization.md b/docs/basic-features/image-optimization.md index 075b66d1b5e2..d557ea7e6400 100644 --- a/docs/basic-features/image-optimization.md +++ b/docs/basic-features/image-optimization.md @@ -121,6 +121,7 @@ The following Image Optimization cloud providers are included: - [Imgix](https://www.imgix.com): `loader: 'imgix'` - [Cloudinary](https://cloudinary.com): `loader: 'cloudinary'` - [Akamai](https://www.akamai.com): `loader: 'akamai'` +- dangerously-unoptimized: uses image directly from `src` prop without optimization (only needed with `next export` or when the built-in `_next/image` optimizer can not be leveraged) `loader: 'dangerously-unoptimized'` - Default: Works automatically with `next dev`, `next start`, or a custom server If you need a different provider, you can use the [`loader`](/docs/api-reference/next/image.md#loader) prop with `next/image`. diff --git a/packages/next/client/image.tsx b/packages/next/client/image.tsx index f9a262214584..332477ad969b 100644 --- a/packages/next/client/image.tsx +++ b/packages/next/client/image.tsx @@ -36,6 +36,7 @@ const loaders = new Map< ['cloudinary', cloudinaryLoader], ['akamai', akamaiLoader], ['default', defaultLoader], + ['dangerously-unoptimized', unoptimizedLoader], ]) const VALID_LAYOUT_VALUES = [ @@ -684,6 +685,10 @@ function cloudinaryLoader({ return `${root}${paramsString}${normalizeSrc(src)}` } +function unoptimizedLoader({ src }: DefaultImageLoaderProps): string { + return src +} + function defaultLoader({ root, src, diff --git a/packages/next/server/image-config.ts b/packages/next/server/image-config.ts index 84473e7503ee..c257d558e011 100644 --- a/packages/next/server/image-config.ts +++ b/packages/next/server/image-config.ts @@ -3,6 +3,7 @@ export const VALID_LOADERS = [ 'imgix', 'cloudinary', 'akamai', + 'dangerously-unoptimized', ] as const export type LoaderValue = typeof VALID_LOADERS[number] diff --git a/test/integration/export-image-loader/next.config.js b/test/integration/export-image-loader/next.config.js index 3dd0d864267e..6b05babba937 100644 --- a/test/integration/export-image-loader/next.config.js +++ b/test/integration/export-image-loader/next.config.js @@ -1,6 +1,2 @@ -module.exports = { - images: { - loader: 'cloudinary', - path: 'https://example.com/', - }, -} +// prettier-ignore +module.exports = { /* replaceme */ } diff --git a/test/integration/export-image-loader/test/index.test.js b/test/integration/export-image-loader/test/index.test.js index 440786b86608..b4f144756d99 100644 --- a/test/integration/export-image-loader/test/index.test.js +++ b/test/integration/export-image-loader/test/index.test.js @@ -3,13 +3,25 @@ import fs from 'fs-extra' import { join } from 'path' import cheerio from 'cheerio' -import { nextBuild, nextExport } from 'next-test-utils' +import { nextBuild, nextExport, File } from 'next-test-utils' jest.setTimeout(1000 * 60 * 5) const appDir = join(__dirname, '../') const outdir = join(appDir, 'out') +const nextConfig = new File(join(appDir, 'next.config.js')) describe('Export with cloudinary loader next/image component', () => { + beforeAll(async () => { + await nextConfig.replace( + '{ /* replaceme */ }', + JSON.stringify({ + images: { + loader: 'cloudinary', + path: 'https://example.com/', + }, + }) + ) + }) it('should build successfully', async () => { await fs.remove(join(appDir, '.next')) const { code } = await nextBuild(appDir) @@ -26,4 +38,41 @@ describe('Export with cloudinary loader next/image component', () => { const $ = cheerio.load(html) expect($('img[alt="icon"]').attr('alt')).toBe('icon') }) + + afterAll(async () => { + await nextConfig.restore() + }) +}) + +describe('Export with dangerously-unoptimized loader next/image component', () => { + beforeAll(async () => { + await nextConfig.replace( + '{ /* replaceme */ }', + JSON.stringify({ + images: { + loader: 'dangerously-unoptimized', + }, + }) + ) + }) + it('should build successfully', async () => { + await fs.remove(join(appDir, '.next')) + const { code } = await nextBuild(appDir) + if (code !== 0) throw new Error(`build failed with status ${code}`) + }) + + it('should export successfully', async () => { + const { code } = await nextExport(appDir, { outdir }) + if (code !== 0) throw new Error(`export failed with status ${code}`) + }) + + it('should contain img element with same src in html output', async () => { + const html = await fs.readFile(join(outdir, 'index.html')) + const $ = cheerio.load(html) + expect($('img[alt="icon"]').attr('src')).toBe('/i.png') + }) + + afterAll(async () => { + await nextConfig.restore() + }) }) diff --git a/test/integration/image-optimizer/test/index.test.js b/test/integration/image-optimizer/test/index.test.js index 07eca10b961b..9e9594737a1c 100644 --- a/test/integration/image-optimizer/test/index.test.js +++ b/test/integration/image-optimizer/test/index.test.js @@ -730,7 +730,7 @@ describe('Image Optimizer', () => { await nextConfig.restore() expect(stderr).toContain( - 'Specified images.loader should be one of (default, imgix, cloudinary, akamai), received invalid value (notreal)' + 'Specified images.loader should be one of (default, imgix, cloudinary, akamai, dangerously-unoptimized), received invalid value (notreal)' ) }) })