From f422853a57e89367833dbe2da72766a67ce500ea Mon Sep 17 00:00:00 2001 From: Erica Pisani Date: Wed, 28 Sep 2022 15:55:40 -0400 Subject: [PATCH 01/15] feat: add env var to disable IPX function includes some domain path and test updates --- demos/canary/pages/index.js | 2 +- demos/default/next.config.js | 16 +++++++--------- demos/default/pages/image.js | 2 +- package-lock.json | 4 ++-- packages/runtime/src/helpers/edge.ts | 3 ++- packages/runtime/src/helpers/utils.ts | 3 +++ packages/runtime/src/index.ts | 27 ++++++++++++++++++--------- test/helpers/utils.spec.ts | 3 ++- test/index.js | 2 +- 9 files changed, 37 insertions(+), 25 deletions(-) diff --git a/demos/canary/pages/index.js b/demos/canary/pages/index.js index 33b7ca0774..ba410683af 100755 --- a/demos/canary/pages/index.js +++ b/demos/canary/pages/index.js @@ -17,7 +17,7 @@ export default function Home() { Picture of the author (

netlify logomark Picture of the author { const nextConfig = nextConfigFile.config await writeJSON(join(edgeFunctionRoot, 'edge-shared', 'nextConfig.json'), nextConfig) - if (!process.env.NEXT_DISABLE_EDGE_IMAGES) { + if (!process.env.NEXT_DISABLE_EDGE_IMAGES || !isEnvSet('DISABLE_IPX')) { console.log( 'Using Netlify Edge Functions for image format detection. Set env var "NEXT_DISABLE_EDGE_IMAGES=true" to disable.', ) diff --git a/packages/runtime/src/helpers/utils.ts b/packages/runtime/src/helpers/utils.ts index 89416d2f4b..5bb3b6efea 100644 --- a/packages/runtime/src/helpers/utils.ts +++ b/packages/runtime/src/helpers/utils.ts @@ -228,4 +228,7 @@ export const getRemotePatterns = (experimental: ExperimentalConfigWithLegacy, im } return [] } + +export const isEnvSet = (envVar: string) => process.env[envVar] === 'true' || process.env[envVar] === '1' + /* eslint-enable max-lines */ diff --git a/packages/runtime/src/index.ts b/packages/runtime/src/index.ts index abf9cc1759..afc1a2d50b 100644 --- a/packages/runtime/src/index.ts +++ b/packages/runtime/src/index.ts @@ -25,7 +25,13 @@ import { import { moveStaticPages, movePublicFiles, patchNextFiles } from './helpers/files' import { generateFunctions, setupImageFunction, generatePagesResolver } from './helpers/functions' import { generateRedirects, generateStaticRedirects } from './helpers/redirects' -import { shouldSkip, isNextAuthInstalled, getCustomImageResponseHeaders, getRemotePatterns } from './helpers/utils' +import { + shouldSkip, + isNextAuthInstalled, + getCustomImageResponseHeaders, + getRemotePatterns, + isEnvSet, +} from './helpers/utils' import { verifyNetlifyBuildVersion, checkNextSiteHasBuilt, @@ -64,6 +70,7 @@ const plugin: NetlifyPlugin = { netlifyConfig.build.environment.NEXT_PRIVATE_TARGET = 'server' }, + // eslint-disable-next-line max-lines-per-function async onBuild({ constants, netlifyConfig, @@ -163,14 +170,16 @@ const plugin: NetlifyPlugin = { nextConfig: { basePath, i18n }, }) - await setupImageFunction({ - constants, - imageconfig: images, - netlifyConfig, - basePath, - remotePatterns: getRemotePatterns(experimental, images), - responseHeaders: getCustomImageResponseHeaders(netlifyConfig.headers), - }) + if (!isEnvSet('DISABLE_IPX')) { + await setupImageFunction({ + constants, + imageconfig: images, + netlifyConfig, + basePath, + remotePatterns: getRemotePatterns(experimental, images), + responseHeaders: getCustomImageResponseHeaders(netlifyConfig.headers), + }) + } await generateRedirects({ netlifyConfig, diff --git a/test/helpers/utils.spec.ts b/test/helpers/utils.spec.ts index c19622507a..ae56d3d933 100644 --- a/test/helpers/utils.spec.ts +++ b/test/helpers/utils.spec.ts @@ -59,7 +59,8 @@ describe('getRemotePatterns', () => { formats: [ 'image/avif', 'image/webp' ], dangerouslyAllowSVG: false, contentSecurityPolicy: "script-src 'none'; frame-src 'none'; sandbox;", - unoptimized: false + unoptimized: false, + remotePatterns: [] } as ImagesConfig }) diff --git a/test/index.js b/test/index.js index 37ebeed60b..edcbf70782 100644 --- a/test/index.js +++ b/test/index.js @@ -567,7 +567,7 @@ describe('onBuild()', () => { const imageConfigPath = path.join(constants.INTERNAL_FUNCTIONS_SRC, IMAGE_FUNCTION_NAME, 'imageconfig.json') const imageConfigJson = await readJson(imageConfigPath) - expect(imageConfigJson.domains.length).toBe(1) + expect(imageConfigJson.domains.length).toBe(2) expect(imageConfigJson.remotePatterns.length).toBe(1) expect(imageConfigJson.responseHeaders).toStrictEqual({ 'X-Foo': mockHeaderValue, From 7fcef4586aa5daffc09a1ff7da33a6d87aa6eb56 Mon Sep 17 00:00:00 2001 From: Erica Pisani Date: Wed, 28 Sep 2022 16:06:12 -0400 Subject: [PATCH 02/15] docs: update README --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index f0ed64bc7f..2a7163c22a 100644 --- a/README.md +++ b/README.md @@ -42,6 +42,10 @@ by targeting the `/_next/image/*` route: X-Test = 'foobar' ``` +## Disabling `ipx` + +If you wish to disable the use of the `ipx` package, set the `DISABLE_IPX` environment variable to `true`. + ## Next.js Middleware on Netlify Next.js Middleware works out of the box on Netlify. By default, middleware runs using Netlify Edge Functions. For legacy From f426c98b2b0cb696c44f41a1ee66f66003bb25f6 Mon Sep 17 00:00:00 2001 From: Erica Pisani Date: Thu, 29 Sep 2022 11:00:04 -0400 Subject: [PATCH 03/15] feat: add sharp package to address prod error when ipx is removed, need to use sharp for image optimization --- demos/default/netlify.toml | 3 -- demos/default/package.json | 5 +-- package-lock.json | 63 +++++++++++++++++++++++++++++++++++++- 3 files changed, 65 insertions(+), 6 deletions(-) diff --git a/demos/default/netlify.toml b/demos/default/netlify.toml index 606fb255f8..3baf78fa0f 100644 --- a/demos/default/netlify.toml +++ b/demos/default/netlify.toml @@ -18,9 +18,6 @@ NODE_VERSION = "16.15.1" Strict-Transport-Security = "max-age=31536000" X-Test = 'foobar' -[dev] -framework = "#static" - [[plugins]] package = "../plugin-wrapper/" diff --git a/demos/default/package.json b/demos/default/package.json index 5043dc7c48..9025612f74 100644 --- a/demos/default/package.json +++ b/demos/default/package.json @@ -22,7 +22,8 @@ "@reach/visually-hidden": "^0.16.0", "next": "^12.3.0", "react": "^18.0.0", - "react-dom": "^18.0.0" + "react-dom": "^18.0.0", + "sharp": "^0.31.1" }, "devDependencies": { "@netlify/plugin-nextjs": "*", @@ -35,4 +36,4 @@ "npm-run-all": "^4.1.5", "typescript": "^4.6.3" } -} \ No newline at end of file +} diff --git a/package-lock.json b/package-lock.json index bb362bf804..522a902057 100644 --- a/package-lock.json +++ b/package-lock.json @@ -117,7 +117,8 @@ "@reach/visually-hidden": "^0.16.0", "next": "^12.3.0", "react": "^18.0.0", - "react-dom": "^18.0.0" + "react-dom": "^18.0.0", + "sharp": "^0.31.1" }, "devDependencies": { "@netlify/plugin-nextjs": "*", @@ -188,6 +189,42 @@ "react-dom": "^16.8.0 || 17.x" } }, + "demos/default/node_modules/semver": { + "version": "7.3.7", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", + "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "demos/default/node_modules/sharp": { + "version": "0.31.1", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.31.1.tgz", + "integrity": "sha512-GR8M1wBwOiFKLkm9JPun27OQnNRZdHfSf9VwcdZX6UrRmM1/XnOrLFTF0GAil+y/YK4E6qcM/ugxs80QirsHxg==", + "hasInstallScript": true, + "dependencies": { + "color": "^4.2.3", + "detect-libc": "^2.0.1", + "node-addon-api": "^5.0.0", + "prebuild-install": "^7.1.1", + "semver": "^7.3.7", + "simple-get": "^4.0.1", + "tar-fs": "^2.1.1", + "tunnel-agent": "^0.6.0" + }, + "engines": { + "node": ">=14.15.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, "demos/middleware": { "version": "0.1.0", "dependencies": { @@ -30150,6 +30187,7 @@ "npm-run-all": "^4.1.5", "react": "^18.0.0", "react-dom": "^18.0.0", + "sharp": "^0.31.1", "typescript": "^4.6.3" }, "dependencies": { @@ -30193,6 +30231,29 @@ "prop-types": "^15.7.2", "tslib": "^2.3.0" } + }, + "semver": { + "version": "7.3.7", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", + "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", + "requires": { + "lru-cache": "^6.0.0" + } + }, + "sharp": { + "version": "0.31.1", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.31.1.tgz", + "integrity": "sha512-GR8M1wBwOiFKLkm9JPun27OQnNRZdHfSf9VwcdZX6UrRmM1/XnOrLFTF0GAil+y/YK4E6qcM/ugxs80QirsHxg==", + "requires": { + "color": "^4.2.3", + "detect-libc": "^2.0.1", + "node-addon-api": "^5.0.0", + "prebuild-install": "^7.1.1", + "semver": "^7.3.7", + "simple-get": "^4.0.1", + "tar-fs": "^2.1.1", + "tunnel-agent": "^0.6.0" + } } } }, From e40b75bb79051862e079cd141a1ecc9ecc0ef2d8 Mon Sep 17 00:00:00 2001 From: Erica Pisani Date: Thu, 29 Sep 2022 14:04:48 -0400 Subject: [PATCH 04/15] refactor: move check for env var into setupImageFunction ensure basePath redirect is still added --- packages/runtime/src/helpers/functions.ts | 57 ++++++++++++----------- packages/runtime/src/index.ts | 19 ++++---- 2 files changed, 39 insertions(+), 37 deletions(-) diff --git a/packages/runtime/src/helpers/functions.ts b/packages/runtime/src/helpers/functions.ts index f0d407510f..b656084468 100644 --- a/packages/runtime/src/helpers/functions.ts +++ b/packages/runtime/src/helpers/functions.ts @@ -7,6 +7,7 @@ import { join, relative, resolve } from 'pathe' import { HANDLER_FUNCTION_NAME, ODB_FUNCTION_NAME, IMAGE_FUNCTION_NAME, DEFAULT_FUNCTIONS_SRC } from '../constants' import { getHandler } from '../templates/getHandler' import { getPageResolver } from '../templates/getPageResolver' +import { isEnvSet } from './utils' export const generateFunctions = async ( { FUNCTIONS_SRC = DEFAULT_FUNCTIONS_SRC, INTERNAL_FUNCTIONS_SRC, PUBLISH_DIR }: NetlifyPluginConstants, @@ -69,35 +70,39 @@ export const setupImageFunction = async ({ remotePatterns: RemotePattern[] responseHeaders?: Record }): Promise => { - const functionsPath = INTERNAL_FUNCTIONS_SRC || FUNCTIONS_SRC - const functionName = `${IMAGE_FUNCTION_NAME}.js` - const functionDirectory = join(functionsPath, IMAGE_FUNCTION_NAME) - - await ensureDir(functionDirectory) - await writeJSON(join(functionDirectory, 'imageconfig.json'), { - ...imageconfig, - basePath: [basePath, IMAGE_FUNCTION_NAME].join('/'), - remotePatterns, - responseHeaders, - }) - await copyFile(join(__dirname, '..', '..', 'lib', 'templates', 'ipx.js'), join(functionDirectory, functionName)) + + if(!isEnvSet('DISABLE_IPX')) { + const functionsPath = INTERNAL_FUNCTIONS_SRC || FUNCTIONS_SRC + const functionName = `${IMAGE_FUNCTION_NAME}.js` + const functionDirectory = join(functionsPath, IMAGE_FUNCTION_NAME) - const imagePath = imageconfig.path || '/_next/image' + await ensureDir(functionDirectory) + await writeJSON(join(functionDirectory, 'imageconfig.json'), { + ...imageconfig, + basePath: [basePath, IMAGE_FUNCTION_NAME].join('/'), + remotePatterns, + responseHeaders, + }) + + await copyFile(join(__dirname, '..', '..', 'lib', 'templates', 'ipx.js'), join(functionDirectory, functionName)) - // If we have edge functions then the request will have already been rewritten - // so this won't match. This is matched if edge is disabled or unavailable. - netlifyConfig.redirects.push({ - from: `${imagePath}*`, - query: { url: ':url', w: ':width', q: ':quality' }, - to: `${basePath}/${IMAGE_FUNCTION_NAME}/w_:width,q_:quality/:url`, - status: 301, - }) + const imagePath = imageconfig.path || '/_next/image' - netlifyConfig.redirects.push({ - from: `${basePath}/${IMAGE_FUNCTION_NAME}/*`, - to: `/.netlify/builders/${IMAGE_FUNCTION_NAME}`, - status: 200, - }) + // If we have edge functions then the request will have already been rewritten + // so this won't match. This is matched if edge is disabled or unavailable. + netlifyConfig.redirects.push({ + from: `${imagePath}*`, + query: { url: ':url', w: ':width', q: ':quality' }, + to: `${basePath}/${IMAGE_FUNCTION_NAME}/w_:width,q_:quality/:url`, + status: 301, + }) + + netlifyConfig.redirects.push({ + from: `${basePath}/${IMAGE_FUNCTION_NAME}/*`, + to: `/.netlify/builders/${IMAGE_FUNCTION_NAME}`, + status: 200, + }) + } if (basePath) { // next/image generates image static URLs that still point at the site root diff --git a/packages/runtime/src/index.ts b/packages/runtime/src/index.ts index afc1a2d50b..667a0e0dc6 100644 --- a/packages/runtime/src/index.ts +++ b/packages/runtime/src/index.ts @@ -30,7 +30,6 @@ import { isNextAuthInstalled, getCustomImageResponseHeaders, getRemotePatterns, - isEnvSet, } from './helpers/utils' import { verifyNetlifyBuildVersion, @@ -170,16 +169,14 @@ const plugin: NetlifyPlugin = { nextConfig: { basePath, i18n }, }) - if (!isEnvSet('DISABLE_IPX')) { - await setupImageFunction({ - constants, - imageconfig: images, - netlifyConfig, - basePath, - remotePatterns: getRemotePatterns(experimental, images), - responseHeaders: getCustomImageResponseHeaders(netlifyConfig.headers), - }) - } + await setupImageFunction({ + constants, + imageconfig: images, + netlifyConfig, + basePath, + remotePatterns: getRemotePatterns(experimental, images), + responseHeaders: getCustomImageResponseHeaders(netlifyConfig.headers), + }) await generateRedirects({ netlifyConfig, From 7b52c85369473b6295c4f8e538da09e1f62f3ffb Mon Sep 17 00:00:00 2001 From: Erica Pisani Date: Thu, 29 Sep 2022 14:37:49 -0400 Subject: [PATCH 05/15] refactor: include sharp module when ipx is disabled don't add ipx function to netlifyConfig if ipx is disabled --- packages/runtime/src/helpers/config.ts | 11 ++++++++--- packages/runtime/src/helpers/functions.ts | 10 +++++----- packages/runtime/src/index.ts | 8 +------- 3 files changed, 14 insertions(+), 15 deletions(-) diff --git a/packages/runtime/src/helpers/config.ts b/packages/runtime/src/helpers/config.ts index 09902a0615..81836cf68c 100644 --- a/packages/runtime/src/helpers/config.ts +++ b/packages/runtime/src/helpers/config.ts @@ -8,6 +8,7 @@ import slash from 'slash' import { HANDLER_FUNCTION_NAME, ODB_FUNCTION_NAME } from '../constants' import type { RoutesManifest } from './types' +import { isEnvSet } from './utils' const ROUTES_MANIFEST_FILE = 'routes-manifest.json' @@ -77,7 +78,9 @@ const resolveModuleRoot = (moduleName) => { } } -const DEFAULT_EXCLUDED_MODULES = ['sharp', 'electron'] +const configureDefaultExcludedModules = () => (isEnvSet('DISABLE_IPX') ? ['electron'] : ['sharp', 'electron']) + +const DEFAULT_EXCLUDED_MODULES = configureDefaultExcludedModules() export const configureHandlerFunctions = async ({ netlifyConfig, publish, ignore = [] }) => { const config = await getRequiredServerFiles(publish) @@ -85,8 +88,10 @@ export const configureHandlerFunctions = async ({ netlifyConfig, publish, ignore const cssFilesToInclude = files.filter((f) => f.startsWith(`${publish}/static/css/`)) /* eslint-disable no-underscore-dangle */ - netlifyConfig.functions._ipx ||= {} - netlifyConfig.functions._ipx.node_bundler = 'nft' + if (!isEnvSet('DISABLE_IPX')) { + netlifyConfig.functions._ipx ||= {} + netlifyConfig.functions._ipx.node_bundler = 'nft' + } /* eslint-enable no-underscore-dangle */ ;[HANDLER_FUNCTION_NAME, ODB_FUNCTION_NAME].forEach((functionName) => { diff --git a/packages/runtime/src/helpers/functions.ts b/packages/runtime/src/helpers/functions.ts index b656084468..d1cf9cf950 100644 --- a/packages/runtime/src/helpers/functions.ts +++ b/packages/runtime/src/helpers/functions.ts @@ -7,6 +7,7 @@ import { join, relative, resolve } from 'pathe' import { HANDLER_FUNCTION_NAME, ODB_FUNCTION_NAME, IMAGE_FUNCTION_NAME, DEFAULT_FUNCTIONS_SRC } from '../constants' import { getHandler } from '../templates/getHandler' import { getPageResolver } from '../templates/getPageResolver' + import { isEnvSet } from './utils' export const generateFunctions = async ( @@ -70,8 +71,7 @@ export const setupImageFunction = async ({ remotePatterns: RemotePattern[] responseHeaders?: Record }): Promise => { - - if(!isEnvSet('DISABLE_IPX')) { + if (!isEnvSet('DISABLE_IPX')) { const functionsPath = INTERNAL_FUNCTIONS_SRC || FUNCTIONS_SRC const functionName = `${IMAGE_FUNCTION_NAME}.js` const functionDirectory = join(functionsPath, IMAGE_FUNCTION_NAME) @@ -83,7 +83,7 @@ export const setupImageFunction = async ({ remotePatterns, responseHeaders, }) - + await copyFile(join(__dirname, '..', '..', 'lib', 'templates', 'ipx.js'), join(functionDirectory, functionName)) const imagePath = imageconfig.path || '/_next/image' @@ -96,12 +96,12 @@ export const setupImageFunction = async ({ to: `${basePath}/${IMAGE_FUNCTION_NAME}/w_:width,q_:quality/:url`, status: 301, }) - + netlifyConfig.redirects.push({ from: `${basePath}/${IMAGE_FUNCTION_NAME}/*`, to: `/.netlify/builders/${IMAGE_FUNCTION_NAME}`, status: 200, - }) + }) } if (basePath) { diff --git a/packages/runtime/src/index.ts b/packages/runtime/src/index.ts index 667a0e0dc6..abf9cc1759 100644 --- a/packages/runtime/src/index.ts +++ b/packages/runtime/src/index.ts @@ -25,12 +25,7 @@ import { import { moveStaticPages, movePublicFiles, patchNextFiles } from './helpers/files' import { generateFunctions, setupImageFunction, generatePagesResolver } from './helpers/functions' import { generateRedirects, generateStaticRedirects } from './helpers/redirects' -import { - shouldSkip, - isNextAuthInstalled, - getCustomImageResponseHeaders, - getRemotePatterns, -} from './helpers/utils' +import { shouldSkip, isNextAuthInstalled, getCustomImageResponseHeaders, getRemotePatterns } from './helpers/utils' import { verifyNetlifyBuildVersion, checkNextSiteHasBuilt, @@ -69,7 +64,6 @@ const plugin: NetlifyPlugin = { netlifyConfig.build.environment.NEXT_PRIVATE_TARGET = 'server' }, - // eslint-disable-next-line max-lines-per-function async onBuild({ constants, netlifyConfig, From 5ebd2bd59c7fb144ffe1b13d952262608915ec58 Mon Sep 17 00:00:00 2001 From: Erica Pisani Date: Thu, 29 Sep 2022 14:38:09 -0400 Subject: [PATCH 06/15] style: lint --- packages/runtime/src/helpers/config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/runtime/src/helpers/config.ts b/packages/runtime/src/helpers/config.ts index 81836cf68c..e34e8b5f44 100644 --- a/packages/runtime/src/helpers/config.ts +++ b/packages/runtime/src/helpers/config.ts @@ -94,7 +94,7 @@ export const configureHandlerFunctions = async ({ netlifyConfig, publish, ignore } /* eslint-enable no-underscore-dangle */ - ;[HANDLER_FUNCTION_NAME, ODB_FUNCTION_NAME].forEach((functionName) => { + [HANDLER_FUNCTION_NAME, ODB_FUNCTION_NAME].forEach((functionName) => { netlifyConfig.functions[functionName] ||= { included_files: [], external_node_modules: [] } netlifyConfig.functions[functionName].node_bundler = 'nft' netlifyConfig.functions[functionName].included_files ||= [] From f062b536322dfd434eb2bf1e95ca8d3021cdb567 Mon Sep 17 00:00:00 2001 From: Erica Pisani Date: Fri, 30 Sep 2022 10:14:02 -0400 Subject: [PATCH 07/15] refactor: serve 404 when ipx is disabled and no custom loader is specified --- README.md | 4 +- demos/default/package.json | 3 +- package-lock.json | 63 +---------------------- packages/runtime/src/helpers/config.ts | 4 +- packages/runtime/src/helpers/functions.ts | 18 +++++-- 5 files changed, 21 insertions(+), 71 deletions(-) diff --git a/README.md b/README.md index 2a7163c22a..6da8782bbe 100644 --- a/README.md +++ b/README.md @@ -44,7 +44,9 @@ by targeting the `/_next/image/*` route: ## Disabling `ipx` -If you wish to disable the use of the `ipx` package, set the `DISABLE_IPX` environment variable to `true`. +If you wish to disable the use of the `ipx` package, set the `DISABLE_IPX` environment variable to `true`. Please note that some requests to `/_next/image/*` may fail unless an image loader, such as Cloudinary or Imgix, is specified as a replacement for `ipx`. + +See the [Next.js documentation](https://nextjs.org/docs/api-reference/next/image#built-in-loaders) for options. ## Next.js Middleware on Netlify diff --git a/demos/default/package.json b/demos/default/package.json index 9025612f74..3c93b24290 100644 --- a/demos/default/package.json +++ b/demos/default/package.json @@ -22,8 +22,7 @@ "@reach/visually-hidden": "^0.16.0", "next": "^12.3.0", "react": "^18.0.0", - "react-dom": "^18.0.0", - "sharp": "^0.31.1" + "react-dom": "^18.0.0" }, "devDependencies": { "@netlify/plugin-nextjs": "*", diff --git a/package-lock.json b/package-lock.json index 522a902057..bb362bf804 100644 --- a/package-lock.json +++ b/package-lock.json @@ -117,8 +117,7 @@ "@reach/visually-hidden": "^0.16.0", "next": "^12.3.0", "react": "^18.0.0", - "react-dom": "^18.0.0", - "sharp": "^0.31.1" + "react-dom": "^18.0.0" }, "devDependencies": { "@netlify/plugin-nextjs": "*", @@ -189,42 +188,6 @@ "react-dom": "^16.8.0 || 17.x" } }, - "demos/default/node_modules/semver": { - "version": "7.3.7", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", - "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "demos/default/node_modules/sharp": { - "version": "0.31.1", - "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.31.1.tgz", - "integrity": "sha512-GR8M1wBwOiFKLkm9JPun27OQnNRZdHfSf9VwcdZX6UrRmM1/XnOrLFTF0GAil+y/YK4E6qcM/ugxs80QirsHxg==", - "hasInstallScript": true, - "dependencies": { - "color": "^4.2.3", - "detect-libc": "^2.0.1", - "node-addon-api": "^5.0.0", - "prebuild-install": "^7.1.1", - "semver": "^7.3.7", - "simple-get": "^4.0.1", - "tar-fs": "^2.1.1", - "tunnel-agent": "^0.6.0" - }, - "engines": { - "node": ">=14.15.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - } - }, "demos/middleware": { "version": "0.1.0", "dependencies": { @@ -30187,7 +30150,6 @@ "npm-run-all": "^4.1.5", "react": "^18.0.0", "react-dom": "^18.0.0", - "sharp": "^0.31.1", "typescript": "^4.6.3" }, "dependencies": { @@ -30231,29 +30193,6 @@ "prop-types": "^15.7.2", "tslib": "^2.3.0" } - }, - "semver": { - "version": "7.3.7", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", - "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", - "requires": { - "lru-cache": "^6.0.0" - } - }, - "sharp": { - "version": "0.31.1", - "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.31.1.tgz", - "integrity": "sha512-GR8M1wBwOiFKLkm9JPun27OQnNRZdHfSf9VwcdZX6UrRmM1/XnOrLFTF0GAil+y/YK4E6qcM/ugxs80QirsHxg==", - "requires": { - "color": "^4.2.3", - "detect-libc": "^2.0.1", - "node-addon-api": "^5.0.0", - "prebuild-install": "^7.1.1", - "semver": "^7.3.7", - "simple-get": "^4.0.1", - "tar-fs": "^2.1.1", - "tunnel-agent": "^0.6.0" - } } } }, diff --git a/packages/runtime/src/helpers/config.ts b/packages/runtime/src/helpers/config.ts index e34e8b5f44..3ff321d38e 100644 --- a/packages/runtime/src/helpers/config.ts +++ b/packages/runtime/src/helpers/config.ts @@ -78,9 +78,7 @@ const resolveModuleRoot = (moduleName) => { } } -const configureDefaultExcludedModules = () => (isEnvSet('DISABLE_IPX') ? ['electron'] : ['sharp', 'electron']) - -const DEFAULT_EXCLUDED_MODULES = configureDefaultExcludedModules() +const DEFAULT_EXCLUDED_MODULES = ['sharp', 'electron'] export const configureHandlerFunctions = async ({ netlifyConfig, publish, ignore = [] }) => { const config = await getRequiredServerFiles(publish) diff --git a/packages/runtime/src/helpers/functions.ts b/packages/runtime/src/helpers/functions.ts index d1cf9cf950..f06f53000c 100644 --- a/packages/runtime/src/helpers/functions.ts +++ b/packages/runtime/src/helpers/functions.ts @@ -71,7 +71,21 @@ export const setupImageFunction = async ({ remotePatterns: RemotePattern[] responseHeaders?: Record }): Promise => { - if (!isEnvSet('DISABLE_IPX')) { + const imagePath = imageconfig.path || '/_next/image' + + if (isEnvSet('DISABLE_IPX')) { + // If no image loader is specified, need to redirect to a 404 page since there's no + // backing loader to serve local site images + if (!imageconfig.loader) { + netlifyConfig.redirects.push({ + from: `${imagePath}*`, + query: { url: ':url', w: ':width', q: ':quality' }, + to: '/404.html', + status: 404, + force: true + }) + } + } else { const functionsPath = INTERNAL_FUNCTIONS_SRC || FUNCTIONS_SRC const functionName = `${IMAGE_FUNCTION_NAME}.js` const functionDirectory = join(functionsPath, IMAGE_FUNCTION_NAME) @@ -86,8 +100,6 @@ export const setupImageFunction = async ({ await copyFile(join(__dirname, '..', '..', 'lib', 'templates', 'ipx.js'), join(functionDirectory, functionName)) - const imagePath = imageconfig.path || '/_next/image' - // If we have edge functions then the request will have already been rewritten // so this won't match. This is matched if edge is disabled or unavailable. netlifyConfig.redirects.push({ From 346d63557c777f71170552cc6c7a3534054c9390 Mon Sep 17 00:00:00 2001 From: Erica Pisani Date: Fri, 30 Sep 2022 10:25:33 -0400 Subject: [PATCH 08/15] refactor: update image loader logic --- packages/runtime/src/helpers/config.ts | 2 +- packages/runtime/src/helpers/functions.ts | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/runtime/src/helpers/config.ts b/packages/runtime/src/helpers/config.ts index 3ff321d38e..bba2af2309 100644 --- a/packages/runtime/src/helpers/config.ts +++ b/packages/runtime/src/helpers/config.ts @@ -92,7 +92,7 @@ export const configureHandlerFunctions = async ({ netlifyConfig, publish, ignore } /* eslint-enable no-underscore-dangle */ - [HANDLER_FUNCTION_NAME, ODB_FUNCTION_NAME].forEach((functionName) => { + ;[HANDLER_FUNCTION_NAME, ODB_FUNCTION_NAME].forEach((functionName) => { netlifyConfig.functions[functionName] ||= { included_files: [], external_node_modules: [] } netlifyConfig.functions[functionName].node_bundler = 'nft' netlifyConfig.functions[functionName].included_files ||= [] diff --git a/packages/runtime/src/helpers/functions.ts b/packages/runtime/src/helpers/functions.ts index f06f53000c..1410998627 100644 --- a/packages/runtime/src/helpers/functions.ts +++ b/packages/runtime/src/helpers/functions.ts @@ -72,18 +72,18 @@ export const setupImageFunction = async ({ responseHeaders?: Record }): Promise => { const imagePath = imageconfig.path || '/_next/image' - + if (isEnvSet('DISABLE_IPX')) { // If no image loader is specified, need to redirect to a 404 page since there's no // backing loader to serve local site images - if (!imageconfig.loader) { + if (imageconfig.loader && imageconfig.loader === 'default') { netlifyConfig.redirects.push({ from: `${imagePath}*`, query: { url: ':url', w: ':width', q: ':quality' }, to: '/404.html', status: 404, - force: true - }) + force: true, + }) } } else { const functionsPath = INTERNAL_FUNCTIONS_SRC || FUNCTIONS_SRC From 6f94bf1053b5a0410844ce22f099dedc85555bb7 Mon Sep 17 00:00:00 2001 From: Erica Pisani Date: Fri, 30 Sep 2022 10:53:23 -0400 Subject: [PATCH 09/15] test: confirming behaviour --- demos/default/netlify.toml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/demos/default/netlify.toml b/demos/default/netlify.toml index 3baf78fa0f..fa854a3a4b 100644 --- a/demos/default/netlify.toml +++ b/demos/default/netlify.toml @@ -24,3 +24,8 @@ package = "../plugin-wrapper/" # This is a fake plugin, that makes it run npm install [[plugins]] package = "@netlify/plugin-local-install-core" + +[[redirects]] +from = "/en/image" +to = "/.netlify/functions/___netlify-handler" +status = 200 From 729a51794cdfb33397cd0ae236621f44315808b1 Mon Sep 17 00:00:00 2001 From: Erica Pisani Date: Fri, 30 Sep 2022 11:10:05 -0400 Subject: [PATCH 10/15] refactor: remove redirect --- demos/default/netlify.toml | 5 ----- packages/runtime/src/helpers/config.ts | 2 +- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/demos/default/netlify.toml b/demos/default/netlify.toml index fa854a3a4b..3baf78fa0f 100644 --- a/demos/default/netlify.toml +++ b/demos/default/netlify.toml @@ -24,8 +24,3 @@ package = "../plugin-wrapper/" # This is a fake plugin, that makes it run npm install [[plugins]] package = "@netlify/plugin-local-install-core" - -[[redirects]] -from = "/en/image" -to = "/.netlify/functions/___netlify-handler" -status = 200 diff --git a/packages/runtime/src/helpers/config.ts b/packages/runtime/src/helpers/config.ts index bba2af2309..3ff321d38e 100644 --- a/packages/runtime/src/helpers/config.ts +++ b/packages/runtime/src/helpers/config.ts @@ -92,7 +92,7 @@ export const configureHandlerFunctions = async ({ netlifyConfig, publish, ignore } /* eslint-enable no-underscore-dangle */ - ;[HANDLER_FUNCTION_NAME, ODB_FUNCTION_NAME].forEach((functionName) => { + [HANDLER_FUNCTION_NAME, ODB_FUNCTION_NAME].forEach((functionName) => { netlifyConfig.functions[functionName] ||= { included_files: [], external_node_modules: [] } netlifyConfig.functions[functionName].node_bundler = 'nft' netlifyConfig.functions[functionName].included_files ||= [] From 276b948c288e3b592e4db6a96c206dd61c3d9b51 Mon Sep 17 00:00:00 2001 From: Erica Pisani Date: Mon, 3 Oct 2022 08:29:35 -0400 Subject: [PATCH 11/15] refactor: remove build command temporarily --- demos/default/netlify.toml | 1 - packages/runtime/src/helpers/functions.ts | 9 +++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/demos/default/netlify.toml b/demos/default/netlify.toml index 3baf78fa0f..ac273c439e 100644 --- a/demos/default/netlify.toml +++ b/demos/default/netlify.toml @@ -1,7 +1,6 @@ [build] command = "next build" publish = ".next" -ignore = "if [ $CACHED_COMMIT_REF == $COMMIT_REF ]; then (exit 1); else git diff --quiet $CACHED_COMMIT_REF $COMMIT_REF ../..; fi;" [build.environment] # cache Cypress binary in local "node_modules" folder diff --git a/packages/runtime/src/helpers/functions.ts b/packages/runtime/src/helpers/functions.ts index 1410998627..acb9c5f7db 100644 --- a/packages/runtime/src/helpers/functions.ts +++ b/packages/runtime/src/helpers/functions.ts @@ -57,7 +57,7 @@ export const generatePagesResolver = async ({ // Move our next/image function into the correct functions directory export const setupImageFunction = async ({ - constants: { INTERNAL_FUNCTIONS_SRC, FUNCTIONS_SRC = DEFAULT_FUNCTIONS_SRC }, + constants: { INTERNAL_FUNCTIONS_SRC, FUNCTIONS_SRC = DEFAULT_FUNCTIONS_SRC, IS_LOCAL }, imageconfig = {}, netlifyConfig, basePath, @@ -72,11 +72,12 @@ export const setupImageFunction = async ({ responseHeaders?: Record }): Promise => { const imagePath = imageconfig.path || '/_next/image' - + console.log('DISABLE?', process.env.DISABLE_IPX) + if (isEnvSet('DISABLE_IPX')) { // If no image loader is specified, need to redirect to a 404 page since there's no - // backing loader to serve local site images - if (imageconfig.loader && imageconfig.loader === 'default') { + // backing loader to serve local site images once deployed to Netlify + if (!IS_LOCAL && imageconfig.loader && imageconfig.loader === 'default') { netlifyConfig.redirects.push({ from: `${imagePath}*`, query: { url: ':url', w: ':width', q: ':quality' }, From ab17bfa7cbd09cb0565e738fb979d993b61d17df Mon Sep 17 00:00:00 2001 From: Erica Pisani Date: Mon, 3 Oct 2022 09:26:54 -0400 Subject: [PATCH 12/15] test: add tests also do general cleanup --- demos/default/netlify.toml | 1 + packages/runtime/src/helpers/functions.ts | 3 +-- test/index.js | 28 +++++++++++++++++++++++ 3 files changed, 30 insertions(+), 2 deletions(-) diff --git a/demos/default/netlify.toml b/demos/default/netlify.toml index ac273c439e..3baf78fa0f 100644 --- a/demos/default/netlify.toml +++ b/demos/default/netlify.toml @@ -1,6 +1,7 @@ [build] command = "next build" publish = ".next" +ignore = "if [ $CACHED_COMMIT_REF == $COMMIT_REF ]; then (exit 1); else git diff --quiet $CACHED_COMMIT_REF $COMMIT_REF ../..; fi;" [build.environment] # cache Cypress binary in local "node_modules" folder diff --git a/packages/runtime/src/helpers/functions.ts b/packages/runtime/src/helpers/functions.ts index acb9c5f7db..024d8df262 100644 --- a/packages/runtime/src/helpers/functions.ts +++ b/packages/runtime/src/helpers/functions.ts @@ -72,8 +72,7 @@ export const setupImageFunction = async ({ responseHeaders?: Record }): Promise => { const imagePath = imageconfig.path || '/_next/image' - console.log('DISABLE?', process.env.DISABLE_IPX) - + if (isEnvSet('DISABLE_IPX')) { // If no image loader is specified, need to redirect to a 404 page since there's no // backing loader to serve local site images once deployed to Netlify diff --git a/test/index.js b/test/index.js index 590e529cca..42a89a9faa 100644 --- a/test/index.js +++ b/test/index.js @@ -571,6 +571,34 @@ describe('onBuild()', () => { 'X-Foo': mockHeaderValue, }) }) + + test('generates an ipx function by default', async () => { + await moveNextDist() + await nextRuntime.onBuild(defaultArgs) + expect(existsSync(path.join('.netlify', 'functions-internal', '_ipx', '_ipx.js'))).toBeTruthy() + }) + + test('does not generate an ipx function when DISABLE_IPX is set', async () => { + process.env.DISABLE_IPX = '1' + await moveNextDist() + await nextRuntime.onBuild(defaultArgs) + expect(existsSync(path.join('.netlify', 'functions-internal', '_ipx', '_ipx.js'))).toBeFalsy() + delete process.env.DISABLE_IPX + }) + + test('creates 404 redirect when DISABLE_IPX is set', async () => { + process.env.DISABLE_IPX = '1' + await moveNextDist() + await nextRuntime.onBuild(defaultArgs) + const nextImageRedirect = netlifyConfig.redirects.find(redirect => redirect.from.includes('/_next/image')) + + expect(nextImageRedirect).toBeDefined() + expect(nextImageRedirect.to).toEqual("/404.html") + expect(nextImageRedirect.status).toEqual(404) + expect(nextImageRedirect.force).toEqual(true) + + delete process.env.DISABLE_IPX + }) }) describe('onPostBuild', () => { From 32c70461b5231e4e450e40569df478c5d8e30213 Mon Sep 17 00:00:00 2001 From: Erica Pisani Date: Mon, 3 Oct 2022 09:27:26 -0400 Subject: [PATCH 13/15] style: lint --- packages/runtime/src/helpers/config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/runtime/src/helpers/config.ts b/packages/runtime/src/helpers/config.ts index bba2af2309..3ff321d38e 100644 --- a/packages/runtime/src/helpers/config.ts +++ b/packages/runtime/src/helpers/config.ts @@ -92,7 +92,7 @@ export const configureHandlerFunctions = async ({ netlifyConfig, publish, ignore } /* eslint-enable no-underscore-dangle */ - ;[HANDLER_FUNCTION_NAME, ODB_FUNCTION_NAME].forEach((functionName) => { + [HANDLER_FUNCTION_NAME, ODB_FUNCTION_NAME].forEach((functionName) => { netlifyConfig.functions[functionName] ||= { included_files: [], external_node_modules: [] } netlifyConfig.functions[functionName].node_bundler = 'nft' netlifyConfig.functions[functionName].included_files ||= [] From 8ccc3b10d13c9984acdce7d4748f72eb14b96af4 Mon Sep 17 00:00:00 2001 From: Erica Pisani Date: Mon, 3 Oct 2022 11:13:35 -0400 Subject: [PATCH 14/15] docs: update README based on CR feedback --- README.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 6da8782bbe..a36da4a0fb 100644 --- a/README.md +++ b/README.md @@ -42,11 +42,13 @@ by targeting the `/_next/image/*` route: X-Test = 'foobar' ``` -## Disabling `ipx` +## Disabling included image loader -If you wish to disable the use of the `ipx` package, set the `DISABLE_IPX` environment variable to `true`. Please note that some requests to `/_next/image/*` may fail unless an image loader, such as Cloudinary or Imgix, is specified as a replacement for `ipx`. +If you wish to disable the use of the image loader which is bundled into the runtime by default, set the `DISABLE_IPX` environment variable to `true`. -See the [Next.js documentation](https://nextjs.org/docs/api-reference/next/image#built-in-loaders) for options. +This should only be done if the site is not using `next/image` or is using a different loader (such as Cloudinary or Imgix). + +See the [Next.js documentation](https://nextjs.org/docs/api-reference/next/image#built-in-loaders) for image loader options. ## Next.js Middleware on Netlify From 9c135b2335ac06fdd572a5398dacdeb2901f6ede Mon Sep 17 00:00:00 2001 From: Erica Pisani Date: Mon, 3 Oct 2022 14:47:17 -0400 Subject: [PATCH 15/15] refactor: remove unnnecessary logic --- packages/runtime/src/helpers/functions.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/runtime/src/helpers/functions.ts b/packages/runtime/src/helpers/functions.ts index fe16a7969e..e0bbfa5d47 100644 --- a/packages/runtime/src/helpers/functions.ts +++ b/packages/runtime/src/helpers/functions.ts @@ -75,7 +75,7 @@ export const setupImageFunction = async ({ if (destr(process.env.DISABLE_IPX)) { // If no image loader is specified, need to redirect to a 404 page since there's no // backing loader to serve local site images once deployed to Netlify - if (!IS_LOCAL && imageconfig.loader && imageconfig.loader === 'default') { + if (!IS_LOCAL && imageconfig.loader === 'default') { netlifyConfig.redirects.push({ from: `${imagePath}*`, query: { url: ':url', w: ':width', q: ':quality' },