From f737a3d232d9ab39d643a525e9455c74e96b3640 Mon Sep 17 00:00:00 2001 From: Matt Kane Date: Mon, 12 Jul 2021 14:34:27 +0100 Subject: [PATCH] feat: add support for using image config from next.config.js --- docs/image-handling.md | 4 +++- index.js | 6 ++---- src/index.js | 6 +++++- src/lib/steps/setupImageFunction.js | 13 ++++++------- src/lib/templates/imageFunction.js | 12 +++++++++--- src/tests/defaults.test.js | 7 ++++++- src/tests/fixtures/next.config.js | 3 +++ 7 files changed, 34 insertions(+), 17 deletions(-) diff --git a/docs/image-handling.md b/docs/image-handling.md index adda634968..d77cbd5fd5 100644 --- a/docs/image-handling.md +++ b/docs/image-handling.md @@ -4,4 +4,6 @@ The Essential Next.js plugin includes a function to generate images for `next/im By default, images are returned in the same format as the source image if they are in JPEG, PNG, WebP or AVIF format. If you are only targeting modern browsers and want to live life on the edge, you can [set the environment variable](https://docs.netlify.com/configure-builds/environment-variables/) `FORCE_WEBP_OUTPUT` to `"true"`, and it will return all images in WebP format. This will often lead to significant improvements in file size. However you should not use this if you need to support older browsers, as `next/image` does not support picture tag source fallback and images will appear broken. Check [browser support](https://caniuse.com/webp) to see if you are happy to do this. -If you want to use remote images in `next/image`, you will need to add the image domains to an allow list. Setting them in `images.domains` in `next.config.js` is not supported: instead you should set the environment variable `NEXT_IMAGE_ALLOWED_DOMAINS` to a comma-separated list of domains, e.g. `NEXT_IMAGE_ALLOWED_DOMAINS="placekitten.com,unsplash.com"`. +If you want to use remote images in `next/image`, you will need to add the image domains to an allow list. Add the required domains to the `images.domains` array in `next.config.js`. + +In previous versions of the Essential Next.js plugin you needed to set the environment variable `NEXT_IMAGE_ALLOWED_DOMAINS` rather than using the Next config file. This is no longer required, and will be removed in a future version. diff --git a/index.js b/index.js index 9e359dc074..cdba75c69e 100644 --- a/index.js +++ b/index.js @@ -49,11 +49,9 @@ module.exports = { checkNxConfig({ netlifyConfig, packageJson, nextConfig, failBuild, constants }) } - if (nextConfig.images.domains.length !== 0 && !process.env.NEXT_IMAGE_ALLOWED_DOMAINS) { + if (process.env.NEXT_IMAGE_ALLOWED_DOMAINS) { console.log( - `Image domains set in next.config.js are ignored.\nPlease set the env variable NEXT_IMAGE_ALLOWED_DOMAINS to "${nextConfig.images.domains.join( - ',', - )}" instead`, + `The Essential Next.js plugin now supports reading image domains from your Next config, so using NEXT_IMAGE_ALLOWED_DOMAINS is now deprecated. Please set images.domains in next.config.js instead, and remove the NEXT_IMAGE_ALLOWED_DOMAINS variable.`, ) } await restoreCache({ cache: utils.cache, distDir: nextConfig.distDir }) diff --git a/src/index.js b/src/index.js index 9e16ea330f..9f278c13a2 100644 --- a/src/index.js +++ b/src/index.js @@ -4,6 +4,8 @@ const chokidar = require('chokidar') const debounceFn = require('debounce-fn') const execa = require('execa') +const getNextConfig = require('../helpers/getNextConfig') + const { NETLIFY_PUBLISH_PATH, NETLIFY_FUNCTIONS_PATH, SRC_FILES } = require('./lib/config') const { logTitle } = require('./lib/helpers/logger') const copyNextAssets = require('./lib/steps/copyNextAssets') @@ -31,7 +33,9 @@ const build = async (functionsPath, publishPath, nextRoot) => { await setupPages({ functionsPath, publishPath }) - setupImageFunction(functionsPath) + const { images } = await getNextConfig() + + await setupImageFunction(functionsPath, images) await setupRedirects(publishPath) diff --git a/src/lib/steps/setupImageFunction.js b/src/lib/steps/setupImageFunction.js index 5ee44c8fee..7f3b23e9b0 100644 --- a/src/lib/steps/setupImageFunction.js +++ b/src/lib/steps/setupImageFunction.js @@ -1,18 +1,17 @@ const { join } = require('path') -const { copySync } = require('fs-extra') +const { copyFile, writeJSON, ensureDir } = require('fs-extra') const { NEXT_IMAGE_FUNCTION_NAME, TEMPLATES_DIR } = require('../config') // Move our next/image function into the correct functions directory -const setupImageFunction = (functionsPath) => { +const setupImageFunction = async (functionsPath, imageconfig = {}) => { const functionName = `${NEXT_IMAGE_FUNCTION_NAME}.js` - const functionDirectory = join(functionsPath, functionName) + const functionDirectory = join(functionsPath, NEXT_IMAGE_FUNCTION_NAME) - copySync(join(TEMPLATES_DIR, 'imageFunction.js'), functionDirectory, { - overwrite: false, - errorOnExist: true, - }) + await ensureDir(functionDirectory) + await writeJSON(join(functionDirectory, 'imageconfig.json'), imageconfig) + await copyFile(join(TEMPLATES_DIR, 'imageFunction.js'), join(functionDirectory, functionName)) } module.exports = setupImageFunction diff --git a/src/lib/templates/imageFunction.js b/src/lib/templates/imageFunction.js index a2633a22a1..d2cbb50e65 100644 --- a/src/lib/templates/imageFunction.js +++ b/src/lib/templates/imageFunction.js @@ -5,6 +5,7 @@ const imageType = require('image-type') const isSvg = require('is-svg') const etag = require('etag') const imageSize = require('image-size') +const config = require('./imageconfig.json') // 6MB is hard max Lambda response size const MAX_RESPONSE_SIZE = 6291456 @@ -47,9 +48,14 @@ const handler = async (event) => { } else { isRemoteImage = true // Remote images need to be in the allowlist - const allowedDomains = process.env.NEXT_IMAGE_ALLOWED_DOMAINS - ? process.env.NEXT_IMAGE_ALLOWED_DOMAINS.split(',').map((domain) => domain.trim()) - : [] + let allowedDomains = config.images?.domains || [] + + if (process.env.NEXT_IMAGE_ALLOWED_DOMAINS) { + console.log('Combining `NEXT_IMAGE_ALLOWED_DOMAINS` with any domains found in `next.config.js`') + allowedDomains = allowedDomains.concat( + process.env.NEXT_IMAGE_ALLOWED_DOMAINS.split(',').map((domain) => domain.trim()), + ) + } if (!allowedDomains.includes(new URL(parsedUrl).hostname)) { return { diff --git a/src/tests/defaults.test.js b/src/tests/defaults.test.js index a44aa09dfe..392a732ef0 100644 --- a/src/tests/defaults.test.js +++ b/src/tests/defaults.test.js @@ -86,8 +86,13 @@ describe('next/image', () => { const functionsDir = join(PROJECT_PATH, 'out_functions') test('sets up next_image as a function in every project by default', () => { - expect(existsSync(join(functionsDir, 'next_image.js'))).toBe(true) + expect(existsSync(join(functionsDir, 'next_image', 'next_image.js'))).toBe(true) }) + + test('injects the image plugin config into the function', () => { + expect(readJsonSync(join(functionsDir, 'next_image', 'imageconfig.json')).domains).toEqual(["placekitten.com"]) + }) + }) describe('SSG Pages with getStaticProps', () => { diff --git a/src/tests/fixtures/next.config.js b/src/tests/fixtures/next.config.js index f7cd294561..26511ac4c4 100644 --- a/src/tests/fixtures/next.config.js +++ b/src/tests/fixtures/next.config.js @@ -1,3 +1,6 @@ module.exports = { target: "serverless", + images: { + domains: ["placekitten.com"] + } };