diff --git a/packages/open-next/src/build/createServerBundle.ts b/packages/open-next/src/build/createServerBundle.ts index 303c6b651..7f1fedae2 100644 --- a/packages/open-next/src/build/createServerBundle.ts +++ b/packages/open-next/src/build/createServerBundle.ts @@ -206,7 +206,6 @@ async function generateBundle( patches.patchEnvVars, patches.patchBackgroundRevalidation, patches.patchUseCacheForISR, - patches.patchDropBabel, ...additionalCodePatches, ]); diff --git a/packages/open-next/src/build/patch/patches/dropBabel.ts b/packages/open-next/src/build/patch/patches/dropBabel.ts deleted file mode 100644 index 7e2eae519..000000000 --- a/packages/open-next/src/build/patch/patches/dropBabel.ts +++ /dev/null @@ -1,88 +0,0 @@ -/** - * Patches to avoid pulling babel (~4MB). - * - * Details: - * - empty `NextServer#runMiddleware` and `NextServer#runEdgeFunction` that are not used - * - drop `next/dist/server/node-environment-extensions/error-inspect.js` - */ - -import { getCrossPlatformPathRegex } from "utils/regex.js"; -import { patchCode } from "../astCodePatcher.js"; -import type { CodePatcher } from "../codePatcher.js"; - -export const patchDropBabel: CodePatcher = { - name: "patch-drop-babel", - patches: [ - // Empty the body of `NextServer#runMiddleware` - { - field: { - pathFilter: getCrossPlatformPathRegex( - String.raw`/next/dist/server/next-server\.js$`, - { - escape: false, - }, - ), - contentFilter: /runMiddleware\(/, - patchCode: async ({ code }) => - patchCode(code, createEmptyBodyRule("runMiddleware")), - }, - }, - // Empty the body of `NextServer#runEdgeFunction` - { - field: { - pathFilter: getCrossPlatformPathRegex( - String.raw`/next/dist/server/next-server\.js$`, - { - escape: false, - }, - ), - contentFilter: /runEdgeFunction\(/, - patchCode: async ({ code }) => - patchCode(code, createEmptyBodyRule("runEdgeFunction")), - }, - }, - // Drop `error-inspect` that pulls babel - { - field: { - pathFilter: getCrossPlatformPathRegex( - String.raw`next/dist/server/node-environment\.js$`, - { - escape: false, - }, - ), - contentFilter: /error-inspect/, - patchCode: async ({ code }) => patchCode(code, errorInspectRule), - }, - }, - ], -}; - -/** - * Swaps the body for a throwing implementation - * - * @param methodName The name of the method - * @returns A rule to replace the body with a `throw` - */ -export function createEmptyBodyRule(methodName: string) { - return ` -rule: - pattern: - selector: method_definition - context: "class { async ${methodName}($$$PARAMS) { $$$_ } }" -fix: |- - async ${methodName}($$$PARAMS) { - throw new Error("${methodName} should not be called with OpenNext"); - } -`; -} - -/** - * Drops `require("./node-environment-extensions/error-inspect");` - */ -export const errorInspectRule = ` -rule: - pattern: require("./node-environment-extensions/error-inspect"); -fix: |- - // Removed by OpenNext - // require("./node-environment-extensions/error-inspect"); -`; diff --git a/packages/open-next/src/build/patch/patches/index.ts b/packages/open-next/src/build/patch/patches/index.ts index bd7ec945e..bd46d6532 100644 --- a/packages/open-next/src/build/patch/patches/index.ts +++ b/packages/open-next/src/build/patch/patches/index.ts @@ -7,4 +7,3 @@ export { } from "./patchFetchCacheISR.js"; export { patchFetchCacheSetMissingWaitUntil } from "./patchFetchCacheWaitUntil.js"; export { patchBackgroundRevalidation } from "./patchBackgroundRevalidation.js"; -export { patchDropBabel } from "./dropBabel.js"; diff --git a/packages/open-next/src/build/patch/patches/patchNextServer.ts b/packages/open-next/src/build/patch/patches/patchNextServer.ts index 05e06f3a3..a843cb910 100644 --- a/packages/open-next/src/build/patch/patches/patchNextServer.ts +++ b/packages/open-next/src/build/patch/patches/patchNextServer.ts @@ -1,36 +1,8 @@ +import { getCrossPlatformPathRegex } from "utils/regex.js"; import { createPatchCode } from "../astCodePatcher.js"; import type { CodePatcher } from "../codePatcher.js"; -// This rule will replace the `NEXT_MINIMAL` env variable with true in multiple places to avoid executing unwanted path (i.e. next middleware, edge functions and image optimization) -export const minimalRule = ` -rule: - kind: member_expression - pattern: process.env.NEXT_MINIMAL - any: - - inside: - kind: parenthesized_expression - stopBy: end - inside: - kind: if_statement - any: - - inside: - kind: statement_block - inside: - kind: method_definition - any: - - has: {kind: property_identifier, field: name, regex: ^runEdgeFunction$} - - has: {kind: property_identifier, field: name, regex: ^runMiddleware$} - - has: {kind: property_identifier, field: name, regex: ^imageOptimizer$} - - has: - kind: statement_block - has: - kind: expression_statement - pattern: res.statusCode = 400; -fix: - 'true' -`; - -// This rule will disable the background preloading of route done by NextServer by default during the creation of NextServer +// Disable the background preloading of route done by NextServer by default during the creation of NextServer export const disablePreloadingRule = ` rule: kind: statement_block @@ -49,7 +21,7 @@ fix: '{}' `; -// This rule is mostly for splitted edge functions so that we don't try to match them on the other non edge functions +// Mostly for splitted edge functions so that we don't try to match them on the other non edge functions export const removeMiddlewareManifestRule = ` rule: kind: statement_block @@ -62,23 +34,93 @@ fix: '{return null;}' `; +/** + * Swaps the body for a throwing implementation + * + * @param methodName The name of the method + * @returns A rule to replace the body with a `throw` + */ +export function createEmptyBodyRule(methodName: string) { + return ` +rule: + pattern: + selector: method_definition + context: "class { async ${methodName}($$$PARAMS) { $$$_ } }" +fix: |- + async ${methodName}($$$PARAMS) { + throw new Error("${methodName} should not be called with OpenNext"); + } +`; +} + +/** + * Drops `require("./node-environment-extensions/error-inspect");` + */ +export const errorInspectRule = ` +rule: + pattern: require("./node-environment-extensions/error-inspect"); +fix: |- + // Removed by OpenNext + // require("./node-environment-extensions/error-inspect"); +`; + +const pathFilter = getCrossPlatformPathRegex( + String.raw`/next/dist/server/next-server\.js$`, + { + escape: false, + }, +); + +/** + * Patches to avoid pulling babel (~4MB). + * + * Details: + * - empty `NextServer#runMiddleware` and `NextServer#runEdgeFunction` that are not used + * - drop `next/dist/server/node-environment-extensions/error-inspect.js` + */ +const babelPatches = [ + // Empty the body of `NextServer#runMiddleware` + { + field: { + pathFilter, + contentFilter: /runMiddleware\(/, + patchCode: createPatchCode(createEmptyBodyRule("runMiddleware")), + }, + }, + // Empty the body of `NextServer#runEdgeFunction` + { + field: { + pathFilter, + contentFilter: /runEdgeFunction\(/, + patchCode: createPatchCode(createEmptyBodyRule("runEdgeFunction")), + }, + }, + // Drop `error-inspect` that pulls babel + { + field: { + pathFilter, + contentFilter: /error-inspect/, + patchCode: createPatchCode(errorInspectRule), + }, + }, +]; + export const patchNextServer: CodePatcher = { name: "patch-next-server", patches: [ - // Skip executing next middleware, edge functions and image optimization inside NextServer + // Empty the body of `NextServer#imageOptimizer` - unused in OpenNext { - versions: ">=15.0.0", field: { - pathFilter: /next-server\.js$/, - contentFilter: /process\.env\.NEXT_MINIMAL/, - patchCode: createPatchCode(minimalRule), + pathFilter, + contentFilter: /imageOptimizer\(/, + patchCode: createPatchCode(createEmptyBodyRule("imageOptimizer")), }, }, - // Disable Next background preloading done at creation of `NetxServer` + // Disable Next background preloading done at creation of `NextServer` { versions: ">=15.0.0", field: { - pathFilter: /next-server\.js$/, + pathFilter, contentFilter: /this\.nextConfig\.experimental\.preloadEntriesOnStart/, patchCode: createPatchCode(disablePreloadingRule), }, @@ -88,10 +130,11 @@ export const patchNextServer: CodePatcher = { // Next 12 and some version of 13 use the bundled middleware/edge function versions: ">=14.0.0", field: { - pathFilter: /next-server\.js$/, + pathFilter, contentFilter: /getMiddlewareManifest/, patchCode: createPatchCode(removeMiddlewareManifestRule), }, }, + ...babelPatches, ], }; diff --git a/packages/tests-unit/tests/build/patch/patches/dropBabel.test.ts b/packages/tests-unit/tests/build/patch/patches/dropBabel.test.ts deleted file mode 100644 index 63af54789..000000000 --- a/packages/tests-unit/tests/build/patch/patches/dropBabel.test.ts +++ /dev/null @@ -1,194 +0,0 @@ -import { patchCode } from "@opennextjs/aws/build/patch/astCodePatcher.js"; -import { - createEmptyBodyRule, - errorInspectRule, -} from "@opennextjs/aws/build/patch/patches/dropBabel.js"; -import { describe, expect, test } from "vitest"; - -describe("babel-drop", () => { - test("Drop body", () => { - const code = ` -class NextNodeServer extends _baseserver.default { - constructor(options){ - // Initialize super class - super(options); - this.handleNextImageRequest = async (req, res, parsedUrl) => { /* ... */ }; - } - async handleUpgrade() { - // The web server does not support web sockets, it's only used for HMR in - // development. - } - getEnabledDirectories(dev) { - const dir = dev ? this.dir : this.serverDistDir; - return { - app: (0, _findpagesdir.findDir)(dir, "app") ? true : false, - pages: (0, _findpagesdir.findDir)(dir, "pages") ? true : false - }; - } - /** - * This method gets all middleware matchers and execute them when the request - * matches. It will make sure that each middleware exists and is compiled and - * ready to be invoked. The development server will decorate it to add warns - * and errors with rich traces. - */ async runMiddleware(params) { - if (process.env.NEXT_MINIMAL) { - throw new Error('invariant: runMiddleware should not be called in minimal mode'); - } - // Middleware is skipped for on-demand revalidate requests - if ((0, _apiutils.checkIsOnDemandRevalidate)(params.request, this.renderOpts.previewProps).isOnDemandRevalidate) { - return { - response: new Response(null, { - headers: { - 'x-middleware-next': '1' - } - }) - }; - } - // ... - } - async runEdgeFunction(params) { - if (process.env.NEXT_MINIMAL) { - throw new Error('Middleware is not supported in minimal mode.'); - } - let edgeInfo; - const { query, page, match } = params; - if (!match) await this.ensureEdgeFunction({ - page, - appPaths: params.appPaths, - url: params.req.url - }); - // ... - } - // ... -}`; - - expect( - patchCode(code, createEmptyBodyRule("runMiddleware")), - ).toMatchInlineSnapshot(` - "class NextNodeServer extends _baseserver.default { - constructor(options){ - // Initialize super class - super(options); - this.handleNextImageRequest = async (req, res, parsedUrl) => { /* ... */ }; - } - async handleUpgrade() { - // The web server does not support web sockets, it's only used for HMR in - // development. - } - getEnabledDirectories(dev) { - const dir = dev ? this.dir : this.serverDistDir; - return { - app: (0, _findpagesdir.findDir)(dir, "app") ? true : false, - pages: (0, _findpagesdir.findDir)(dir, "pages") ? true : false - }; - } - /** - * This method gets all middleware matchers and execute them when the request - * matches. It will make sure that each middleware exists and is compiled and - * ready to be invoked. The development server will decorate it to add warns - * and errors with rich traces. - */ async runMiddleware(params) { - throw new Error("runMiddleware should not be called with OpenNext"); - } - async runEdgeFunction(params) { - if (process.env.NEXT_MINIMAL) { - throw new Error('Middleware is not supported in minimal mode.'); - } - let edgeInfo; - const { query, page, match } = params; - if (!match) await this.ensureEdgeFunction({ - page, - appPaths: params.appPaths, - url: params.req.url - }); - // ... - } - // ... - }" - `); - - expect( - patchCode(code, createEmptyBodyRule("runEdgeFunction")), - ).toMatchInlineSnapshot(` - "class NextNodeServer extends _baseserver.default { - constructor(options){ - // Initialize super class - super(options); - this.handleNextImageRequest = async (req, res, parsedUrl) => { /* ... */ }; - } - async handleUpgrade() { - // The web server does not support web sockets, it's only used for HMR in - // development. - } - getEnabledDirectories(dev) { - const dir = dev ? this.dir : this.serverDistDir; - return { - app: (0, _findpagesdir.findDir)(dir, "app") ? true : false, - pages: (0, _findpagesdir.findDir)(dir, "pages") ? true : false - }; - } - /** - * This method gets all middleware matchers and execute them when the request - * matches. It will make sure that each middleware exists and is compiled and - * ready to be invoked. The development server will decorate it to add warns - * and errors with rich traces. - */ async runMiddleware(params) { - if (process.env.NEXT_MINIMAL) { - throw new Error('invariant: runMiddleware should not be called in minimal mode'); - } - // Middleware is skipped for on-demand revalidate requests - if ((0, _apiutils.checkIsOnDemandRevalidate)(params.request, this.renderOpts.previewProps).isOnDemandRevalidate) { - return { - response: new Response(null, { - headers: { - 'x-middleware-next': '1' - } - }) - }; - } - // ... - } - async runEdgeFunction(params) { - throw new Error("runEdgeFunction should not be called with OpenNext"); - } - // ... - }" - `); - }); - - test("Error Inspect", () => { - const code = ` -// This file should be imported before any others. It sets up the environment -// for later imports to work properly. -"use strict"; -Object.defineProperty(exports, "__esModule", { - value: true -}); -require("./node-environment-baseline"); -require("./node-environment-extensions/error-inspect"); -require("./node-environment-extensions/random"); -require("./node-environment-extensions/date"); -require("./node-environment-extensions/web-crypto"); -require("./node-environment-extensions/node-crypto"); -//# sourceMappingURL=node-environment.js.map -}`; - - expect(patchCode(code, errorInspectRule)).toMatchInlineSnapshot(` - "// This file should be imported before any others. It sets up the environment - // for later imports to work properly. - "use strict"; - Object.defineProperty(exports, "__esModule", { - value: true - }); - require("./node-environment-baseline"); - // Removed by OpenNext - // require("./node-environment-extensions/error-inspect"); - require("./node-environment-extensions/random"); - require("./node-environment-extensions/date"); - require("./node-environment-extensions/web-crypto"); - require("./node-environment-extensions/node-crypto"); - //# sourceMappingURL=node-environment.js.map - }" - `); - }); -}); diff --git a/packages/tests-unit/tests/build/patch/patches/patchNextServer.test.ts b/packages/tests-unit/tests/build/patch/patches/patchNextServer.test.ts index 877a063e4..f09d9fb31 100644 --- a/packages/tests-unit/tests/build/patch/patches/patchNextServer.test.ts +++ b/packages/tests-unit/tests/build/patch/patches/patchNextServer.test.ts @@ -1,7 +1,8 @@ import { patchCode } from "@opennextjs/aws/build/patch/astCodePatcher.js"; import { + createEmptyBodyRule, disablePreloadingRule, - minimalRule, + errorInspectRule, removeMiddlewareManifestRule, } from "@opennextjs/aws/build/patch/patches/patchNextServer.js"; import { describe, it } from "vitest"; @@ -423,9 +424,9 @@ getMiddlewareManifest() {return null;} `); }); - it("should patch minimalMode", async () => { + it("should disable preloading", async () => { expect( - patchCode(nextServerMinimalCode, minimalRule), + patchCode(nextServerMinimalCode, disablePreloadingRule), ).toMatchInlineSnapshot(` "class NextNodeServer extends _baseserver.default { constructor(options){ @@ -439,7 +440,7 @@ constructor(options){ if ((0, _requestmeta.getRequestMeta)(req, 'middlewareInvoke')) { return false; } - if (this.minimalMode || this.nextConfig.output === 'export' || true) { + if (this.minimalMode || this.nextConfig.output === 'export' || process.env.NEXT_MINIMAL) { res.statusCode = 400; res.body('Bad Request').send(); return true; @@ -703,27 +704,8 @@ constructor(options){ } const { appDocumentPreloading } = this.nextConfig.experimental; const isDefaultEnabled = typeof appDocumentPreloading === 'undefined'; - if (!options.dev && (appDocumentPreloading === true || !(this.minimalMode && isDefaultEnabled))) { - // pre-warm _document and _app as these will be - // needed for most requests - (0, _loadcomponents.loadComponents)({ - distDir: this.distDir, - page: '/_document', - isAppPath: false, - isDev: this.isDev, - sriEnabled: this.sriEnabled - }).catch(()=>{}); - (0, _loadcomponents.loadComponents)({ - distDir: this.distDir, - page: '/_app', - isAppPath: false, - isDev: this.isDev, - sriEnabled: this.sriEnabled - }).catch(()=>{}); - } - if (!options.dev && !this.minimalMode && this.nextConfig.experimental.preloadEntriesOnStart) { - this.unstable_preloadEntries(); - } + if (!options.dev && (appDocumentPreloading === true || !(this.minimalMode && isDefaultEnabled))) {} + if (!options.dev && !this.minimalMode && this.nextConfig.experimental.preloadEntriesOnStart) {} if (!options.dev) { const { dynamicRoutes = [] } = this.getRoutesManifest() ?? {}; this.dynamicRoutes = dynamicRoutes.map((r)=>{ @@ -758,7 +740,7 @@ constructor(options){ console.timeEnd('Next.js server initialization'); } async runMiddleware(params) { - if (true) { + if (process.env.NEXT_MINIMAL) { throw Object.defineProperty(new Error('invariant: runMiddleware should not be called in minimal mode'), "__NEXT_ERROR_CODE", { value: "E276", enumerable: false, @@ -769,7 +751,7 @@ async runMiddleware(params) { // REST OF THE CODE } async runEdgeFunction(params) { - if (true) { + if (process.env.NEXT_MINIMAL) { throw Object.defineProperty(new Error('Middleware is not supported in minimal mode. Please remove the \`NEXT_MINIMAL\` environment variable.'), "__NEXT_ERROR_CODE", { value: "E58", enumerable: false, @@ -778,7 +760,7 @@ async runEdgeFunction(params) { } } async imageOptimizer(req, res, paramsResult, previousCacheEntry) { - if (true) { + if (process.env.NEXT_MINIMAL) { throw Object.defineProperty(new Error('invariant: imageOptimizer should not be called in minimal mode'), "__NEXT_ERROR_CODE", { value: "E506", enumerable: false, @@ -813,381 +795,820 @@ async imageOptimizer(req, res, paramsResult, previousCacheEntry) { } } } -"`); +" + `); }); - it("should disable preloading", async () => { - expect( - patchCode(nextServerMinimalCode, disablePreloadingRule), - ).toMatchInlineSnapshot(` -"class NextNodeServer extends _baseserver.default { -constructor(options){ - var _options_conf_experimental_sri, _options_conf_experimental; - // Initialize super class - super(options), this.registeredInstrumentation = false, this.cleanupListeners = new _asynccallbackset.AsyncCallbackSet(), this.handleNextImageRequest = async (req, res, parsedUrl)=>{ - if (!parsedUrl.pathname || !parsedUrl.pathname.startsWith('/_next/image')) { - return false; - } - // Ignore if its a middleware request - if ((0, _requestmeta.getRequestMeta)(req, 'middlewareInvoke')) { - return false; - } - if (this.minimalMode || this.nextConfig.output === 'export' || process.env.NEXT_MINIMAL) { - res.statusCode = 400; - res.body('Bad Request').send(); - return true; - // the \`else\` branch is needed for tree-shaking - } else { - const { ImageOptimizerCache } = require('./image-optimizer'); - const imageOptimizerCache = new ImageOptimizerCache({ - distDir: this.distDir, - nextConfig: this.nextConfig - }); - const { sendResponse, ImageError } = require('./image-optimizer'); - if (!this.imageResponseCache) { - throw Object.defineProperty(new Error('invariant image optimizer cache was not initialized'), "__NEXT_ERROR_CODE", { - value: "E160", - enumerable: false, - configurable: true - }); - } - const imagesConfig = this.nextConfig.images; - if (imagesConfig.loader !== 'default' || imagesConfig.unoptimized) { - await this.render404(req, res); - return true; - } - const paramsResult = ImageOptimizerCache.validateParams(req.originalRequest, parsedUrl.query, this.nextConfig, !!this.renderOpts.dev); - if ('errorMessage' in paramsResult) { - res.statusCode = 400; - res.body(paramsResult.errorMessage).send(); - return true; - } - const cacheKey = ImageOptimizerCache.getCacheKey(paramsResult); - try { - var _cacheEntry_value, _cacheEntry_cacheControl; - const { getExtension } = require('./serve-static'); - const cacheEntry = await this.imageResponseCache.get(cacheKey, async ({ previousCacheEntry })=>{ - const { buffer, contentType, maxAge, upstreamEtag, etag } = await this.imageOptimizer(req, res, paramsResult, previousCacheEntry); - return { - value: { - kind: _responsecache.CachedRouteKind.IMAGE, - buffer, - etag, - extension: getExtension(contentType), - upstreamEtag - }, - isFallback: false, - cacheControl: { - revalidate: maxAge, - expire: undefined + describe("Drop babel dependency", () => { + test("Drop body", () => { + expect( + patchCode(nextServerMinimalCode, createEmptyBodyRule("runMiddleware")), + ).toMatchInlineSnapshot(` + "class NextNodeServer extends _baseserver.default { + constructor(options){ + var _options_conf_experimental_sri, _options_conf_experimental; + // Initialize super class + super(options), this.registeredInstrumentation = false, this.cleanupListeners = new _asynccallbackset.AsyncCallbackSet(), this.handleNextImageRequest = async (req, res, parsedUrl)=>{ + if (!parsedUrl.pathname || !parsedUrl.pathname.startsWith('/_next/image')) { + return false; + } + // Ignore if its a middleware request + if ((0, _requestmeta.getRequestMeta)(req, 'middlewareInvoke')) { + return false; + } + if (this.minimalMode || this.nextConfig.output === 'export' || process.env.NEXT_MINIMAL) { + res.statusCode = 400; + res.body('Bad Request').send(); + return true; + // the \`else\` branch is needed for tree-shaking + } else { + const { ImageOptimizerCache } = require('./image-optimizer'); + const imageOptimizerCache = new ImageOptimizerCache({ + distDir: this.distDir, + nextConfig: this.nextConfig + }); + const { sendResponse, ImageError } = require('./image-optimizer'); + if (!this.imageResponseCache) { + throw Object.defineProperty(new Error('invariant image optimizer cache was not initialized'), "__NEXT_ERROR_CODE", { + value: "E160", + enumerable: false, + configurable: true + }); + } + const imagesConfig = this.nextConfig.images; + if (imagesConfig.loader !== 'default' || imagesConfig.unoptimized) { + await this.render404(req, res); + return true; + } + const paramsResult = ImageOptimizerCache.validateParams(req.originalRequest, parsedUrl.query, this.nextConfig, !!this.renderOpts.dev); + if ('errorMessage' in paramsResult) { + res.statusCode = 400; + res.body(paramsResult.errorMessage).send(); + return true; + } + const cacheKey = ImageOptimizerCache.getCacheKey(paramsResult); + try { + var _cacheEntry_value, _cacheEntry_cacheControl; + const { getExtension } = require('./serve-static'); + const cacheEntry = await this.imageResponseCache.get(cacheKey, async ({ previousCacheEntry })=>{ + const { buffer, contentType, maxAge, upstreamEtag, etag } = await this.imageOptimizer(req, res, paramsResult, previousCacheEntry); + return { + value: { + kind: _responsecache.CachedRouteKind.IMAGE, + buffer, + etag, + extension: getExtension(contentType), + upstreamEtag + }, + isFallback: false, + cacheControl: { + revalidate: maxAge, + expire: undefined + } + }; + }, { + routeKind: _routekind.RouteKind.IMAGE, + incrementalCache: imageOptimizerCache, + isFallback: false + }); + if ((cacheEntry == null ? void 0 : (_cacheEntry_value = cacheEntry.value) == null ? void 0 : _cacheEntry_value.kind) !== _responsecache.CachedRouteKind.IMAGE) { + throw Object.defineProperty(new Error('invariant did not get entry from image response cache'), "__NEXT_ERROR_CODE", { + value: "E518", + enumerable: false, + configurable: true + }); } - }; - }, { - routeKind: _routekind.RouteKind.IMAGE, - incrementalCache: imageOptimizerCache, - isFallback: false - }); - if ((cacheEntry == null ? void 0 : (_cacheEntry_value = cacheEntry.value) == null ? void 0 : _cacheEntry_value.kind) !== _responsecache.CachedRouteKind.IMAGE) { - throw Object.defineProperty(new Error('invariant did not get entry from image response cache'), "__NEXT_ERROR_CODE", { - value: "E518", + sendResponse(req.originalRequest, res.originalResponse, paramsResult.href, cacheEntry.value.extension, cacheEntry.value.buffer, cacheEntry.value.etag, paramsResult.isStatic, cacheEntry.isMiss ? 'MISS' : cacheEntry.isStale ? 'STALE' : 'HIT', imagesConfig, ((_cacheEntry_cacheControl = cacheEntry.cacheControl) == null ? void 0 : _cacheEntry_cacheControl.revalidate) || 0, Boolean(this.renderOpts.dev)); + return true; + } catch (err) { + if (err instanceof ImageError) { + res.statusCode = err.statusCode; + res.body(err.message).send(); + return true; + } + throw err; + } + } + }, this.handleCatchallRenderRequest = async (req, res, parsedUrl)=>{ + let { pathname, query } = parsedUrl; + if (!pathname) { + throw Object.defineProperty(new Error('Invariant: pathname is undefined'), "__NEXT_ERROR_CODE", { + value: "E409", enumerable: false, configurable: true }); } - sendResponse(req.originalRequest, res.originalResponse, paramsResult.href, cacheEntry.value.extension, cacheEntry.value.buffer, cacheEntry.value.etag, paramsResult.isStatic, cacheEntry.isMiss ? 'MISS' : cacheEntry.isStale ? 'STALE' : 'HIT', imagesConfig, ((_cacheEntry_cacheControl = cacheEntry.cacheControl) == null ? void 0 : _cacheEntry_cacheControl.revalidate) || 0, Boolean(this.renderOpts.dev)); - return true; - } catch (err) { - if (err instanceof ImageError) { - res.statusCode = err.statusCode; - res.body(err.message).send(); + // This is a catch-all route, there should be no fallbacks so mark it as + // such. + (0, _requestmeta.addRequestMeta)(req, 'bubbleNoFallback', true); + try { + var _this_i18nProvider; + // next.js core assumes page path without trailing slash + pathname = (0, _removetrailingslash.removeTrailingSlash)(pathname); + const options = { + i18n: (_this_i18nProvider = this.i18nProvider) == null ? void 0 : _this_i18nProvider.fromRequest(req, pathname) + }; + const match = await this.matchers.match(pathname, options); + // If we don't have a match, try to render it anyways. + if (!match) { + await this.render(req, res, pathname, query, parsedUrl, true); + return true; + } + // Add the match to the request so we don't have to re-run the matcher + // for the same request. + (0, _requestmeta.addRequestMeta)(req, 'match', match); + // TODO-APP: move this to a route handler + const edgeFunctionsPages = this.getEdgeFunctionsPages(); + for (const edgeFunctionsPage of edgeFunctionsPages){ + // If the page doesn't match the edge function page, skip it. + if (edgeFunctionsPage !== match.definition.page) continue; + if (this.nextConfig.output === 'export') { + await this.render404(req, res, parsedUrl); + return true; + } + delete query[_approuterheaders.NEXT_RSC_UNION_QUERY]; + // If we handled the request, we can return early. + // For api routes edge runtime + try { + const handled = await this.runEdgeFunction({ + req, + res, + query, + params: match.params, + page: match.definition.page, + match, + appPaths: null + }); + if (handled) return true; + } catch (apiError) { + await this.instrumentationOnRequestError(apiError, req, { + routePath: match.definition.page, + routerKind: 'Pages Router', + routeType: 'route', + // Edge runtime does not support ISR + revalidateReason: undefined + }); + throw apiError; + } + } + // If the route was detected as being a Pages API route, then handle + // it. + // TODO: move this behavior into a route handler. + if ((0, _pagesapiroutematch.isPagesAPIRouteMatch)(match)) { + if (this.nextConfig.output === 'export') { + await this.render404(req, res, parsedUrl); + return true; + } + const handled = await this.handleApiRequest(req, res, query, match); + if (handled) return true; + } + await this.render(req, res, pathname, query, parsedUrl, true); return true; + } catch (err) { + if (err instanceof _baseserver.NoFallbackError) { + throw err; + } + try { + if (this.renderOpts.dev) { + const { formatServerError } = require('../lib/format-server-error'); + formatServerError(err); + this.logErrorWithOriginalStack(err); + } else { + this.logError(err); + } + res.statusCode = 500; + await this.renderError(err, req, res, pathname, query); + return true; + } catch {} + throw err; } - throw err; - } - } - }, this.handleCatchallRenderRequest = async (req, res, parsedUrl)=>{ - let { pathname, query } = parsedUrl; - if (!pathname) { - throw Object.defineProperty(new Error('Invariant: pathname is undefined'), "__NEXT_ERROR_CODE", { - value: "E409", - enumerable: false, - configurable: true - }); - } - // This is a catch-all route, there should be no fallbacks so mark it as - // such. - (0, _requestmeta.addRequestMeta)(req, 'bubbleNoFallback', true); - try { - var _this_i18nProvider; - // next.js core assumes page path without trailing slash - pathname = (0, _removetrailingslash.removeTrailingSlash)(pathname); - const options = { - i18n: (_this_i18nProvider = this.i18nProvider) == null ? void 0 : _this_i18nProvider.fromRequest(req, pathname) - }; - const match = await this.matchers.match(pathname, options); - // If we don't have a match, try to render it anyways. - if (!match) { - await this.render(req, res, pathname, query, parsedUrl, true); - return true; - } - // Add the match to the request so we don't have to re-run the matcher - // for the same request. - (0, _requestmeta.addRequestMeta)(req, 'match', match); - // TODO-APP: move this to a route handler - const edgeFunctionsPages = this.getEdgeFunctionsPages(); - for (const edgeFunctionsPage of edgeFunctionsPages){ - // If the page doesn't match the edge function page, skip it. - if (edgeFunctionsPage !== match.definition.page) continue; - if (this.nextConfig.output === 'export') { - await this.render404(req, res, parsedUrl); + }, this.handleCatchallMiddlewareRequest = async (req, res, parsed)=>{ + const isMiddlewareInvoke = (0, _requestmeta.getRequestMeta)(req, 'middlewareInvoke'); + if (!isMiddlewareInvoke) { + return false; + } + const handleFinished = ()=>{ + (0, _requestmeta.addRequestMeta)(req, 'middlewareInvoke', true); + res.body('').send(); return true; + }; + const middleware = await this.getMiddleware(); + if (!middleware) { + return handleFinished(); } - delete query[_approuterheaders.NEXT_RSC_UNION_QUERY]; - // If we handled the request, we can return early. - // For api routes edge runtime + const initUrl = (0, _requestmeta.getRequestMeta)(req, 'initURL'); + const parsedUrl = (0, _parseurl.parseUrl)(initUrl); + const pathnameInfo = (0, _getnextpathnameinfo.getNextPathnameInfo)(parsedUrl.pathname, { + nextConfig: this.nextConfig, + i18nProvider: this.i18nProvider + }); + parsedUrl.pathname = pathnameInfo.pathname; + const normalizedPathname = (0, _removetrailingslash.removeTrailingSlash)(parsed.pathname || ''); + if (!middleware.match(normalizedPathname, req, parsedUrl.query)) { + return handleFinished(); + } + let result; + let bubblingResult = false; try { - const handled = await this.runEdgeFunction({ - req, - res, - query, - params: match.params, - page: match.definition.page, - match, - appPaths: null + await this.ensureMiddleware(req.url); + result = await this.runMiddleware({ + request: req, + response: res, + parsedUrl: parsedUrl, + parsed: parsed }); - if (handled) return true; - } catch (apiError) { - await this.instrumentationOnRequestError(apiError, req, { - routePath: match.definition.page, - routerKind: 'Pages Router', - routeType: 'route', - // Edge runtime does not support ISR - revalidateReason: undefined - }); - throw apiError; - } - } - // If the route was detected as being a Pages API route, then handle - // it. - // TODO: move this behavior into a route handler. - if ((0, _pagesapiroutematch.isPagesAPIRouteMatch)(match)) { - if (this.nextConfig.output === 'export') { - await this.render404(req, res, parsedUrl); + if ('response' in result) { + if (isMiddlewareInvoke) { + bubblingResult = true; + throw Object.defineProperty(new _tracer.BubbledError(true, result), "__NEXT_ERROR_CODE", { + value: "E394", + enumerable: false, + configurable: true + }); + } + for (const [key, value] of Object.entries((0, _utils1.toNodeOutgoingHttpHeaders)(result.response.headers))){ + if (key !== 'content-encoding' && value !== undefined) { + res.setHeader(key, value); + } + } + res.statusCode = result.response.status; + const { originalResponse } = res; + if (result.response.body) { + await (0, _pipereadable.pipeToNodeResponse)(result.response.body, originalResponse); + } else { + originalResponse.end(); + } + return true; + } + } catch (err) { + if (bubblingResult) { + throw err; + } + if ((0, _iserror.default)(err) && err.code === 'ENOENT') { + await this.render404(req, res, parsed); + return true; + } + if (err instanceof _utils.DecodeError) { + res.statusCode = 400; + await this.renderError(err, req, res, parsed.pathname || ''); + return true; + } + const error = (0, _iserror.getProperError)(err); + console.error(error); + res.statusCode = 500; + await this.renderError(error, req, res, parsed.pathname || ''); return true; } - const handled = await this.handleApiRequest(req, res, query, match); - if (handled) return true; + return result.finished; + }; + console.time('Next.js server initialization'); + this.isDev = options.dev ?? false; + this.sriEnabled = Boolean((_options_conf_experimental = options.conf.experimental) == null ? void 0 : (_options_conf_experimental_sri = _options_conf_experimental.sri) == null ? void 0 : _options_conf_experimental_sri.algorithm); + /** + * This sets environment variable to be used at the time of SSR by head.tsx. + * Using this from process.env allows targeting SSR by calling + * \`process.env.__NEXT_OPTIMIZE_CSS\`. + */ if (this.renderOpts.optimizeCss) { + process.env.__NEXT_OPTIMIZE_CSS = JSON.stringify(true); } - await this.render(req, res, pathname, query, parsedUrl, true); - return true; - } catch (err) { - if (err instanceof _baseserver.NoFallbackError) { - throw err; + if (this.renderOpts.nextScriptWorkers) { + process.env.__NEXT_SCRIPT_WORKERS = JSON.stringify(true); } - try { - if (this.renderOpts.dev) { - const { formatServerError } = require('../lib/format-server-error'); - formatServerError(err); - this.logErrorWithOriginalStack(err); - } else { - this.logError(err); - } - res.statusCode = 500; - await this.renderError(err, req, res, pathname, query); - return true; - } catch {} - throw err; - } - }, this.handleCatchallMiddlewareRequest = async (req, res, parsed)=>{ - const isMiddlewareInvoke = (0, _requestmeta.getRequestMeta)(req, 'middlewareInvoke'); - if (!isMiddlewareInvoke) { - return false; - } - const handleFinished = ()=>{ - (0, _requestmeta.addRequestMeta)(req, 'middlewareInvoke', true); - res.body('').send(); - return true; - }; - const middleware = await this.getMiddleware(); - if (!middleware) { - return handleFinished(); + process.env.NEXT_DEPLOYMENT_ID = this.nextConfig.deploymentId || ''; + if (!this.minimalMode) { + this.imageResponseCache = new _responsecache.default(this.minimalMode); + } + const { appDocumentPreloading } = this.nextConfig.experimental; + const isDefaultEnabled = typeof appDocumentPreloading === 'undefined'; + if (!options.dev && (appDocumentPreloading === true || !(this.minimalMode && isDefaultEnabled))) { + // pre-warm _document and _app as these will be + // needed for most requests + (0, _loadcomponents.loadComponents)({ + distDir: this.distDir, + page: '/_document', + isAppPath: false, + isDev: this.isDev, + sriEnabled: this.sriEnabled + }).catch(()=>{}); + (0, _loadcomponents.loadComponents)({ + distDir: this.distDir, + page: '/_app', + isAppPath: false, + isDev: this.isDev, + sriEnabled: this.sriEnabled + }).catch(()=>{}); + } + if (!options.dev && !this.minimalMode && this.nextConfig.experimental.preloadEntriesOnStart) { + this.unstable_preloadEntries(); + } + if (!options.dev) { + const { dynamicRoutes = [] } = this.getRoutesManifest() ?? {}; + this.dynamicRoutes = dynamicRoutes.map((r)=>{ + // TODO: can we just re-use the regex from the manifest? + const regex = (0, _routeregex.getRouteRegex)(r.page); + const match = (0, _routematcher.getRouteMatcher)(regex); + return { + match, + page: r.page, + re: regex.re + }; + }); + } + // ensure options are set when loadConfig isn't called + (0, _setuphttpagentenv.setHttpClientAndAgentOptions)(this.nextConfig); + // Intercept fetch and other testmode apis. + if (this.serverOptions.experimentalTestProxy) { + process.env.NEXT_PRIVATE_TEST_PROXY = 'true'; + const { interceptTestApis } = require('next/dist/experimental/testmode/server'); + interceptTestApis(); + } + this.middlewareManifestPath = (0, _path.join)(this.serverDistDir, _constants.MIDDLEWARE_MANIFEST); + // This is just optimization to fire prepare as soon as possible. It will be + // properly awaited later. We add the catch here to ensure that it does not + // cause a unhandled promise rejection. The promise rejection will be + // handled later on via the \`await\` when the request handler is called. + if (!options.dev) { + this.prepare().catch((err)=>{ + console.error('Failed to prepare server', err); + }); + } + console.timeEnd('Next.js server initialization'); } - const initUrl = (0, _requestmeta.getRequestMeta)(req, 'initURL'); - const parsedUrl = (0, _parseurl.parseUrl)(initUrl); - const pathnameInfo = (0, _getnextpathnameinfo.getNextPathnameInfo)(parsedUrl.pathname, { - nextConfig: this.nextConfig, - i18nProvider: this.i18nProvider - }); - parsedUrl.pathname = pathnameInfo.pathname; - const normalizedPathname = (0, _removetrailingslash.removeTrailingSlash)(parsed.pathname || ''); - if (!middleware.match(normalizedPathname, req, parsedUrl.query)) { - return handleFinished(); + async runMiddleware(params) { + throw new Error("runMiddleware should not be called with OpenNext"); + } + async runEdgeFunction(params) { + if (process.env.NEXT_MINIMAL) { + throw Object.defineProperty(new Error('Middleware is not supported in minimal mode. Please remove the \`NEXT_MINIMAL\` environment variable.'), "__NEXT_ERROR_CODE", { + value: "E58", + enumerable: false, + configurable: true + }); + } + } + async imageOptimizer(req, res, paramsResult, previousCacheEntry) { + if (process.env.NEXT_MINIMAL) { + throw Object.defineProperty(new Error('invariant: imageOptimizer should not be called in minimal mode'), "__NEXT_ERROR_CODE", { + value: "E506", + enumerable: false, + configurable: true + }); + } else { + const { imageOptimizer, fetchExternalImage, fetchInternalImage } = require('./image-optimizer'); + const handleInternalReq = async (newReq, newRes)=>{ + if (newReq.url === req.url) { + throw Object.defineProperty(new Error(\`Invariant attempted to optimize _next/image itself\`), "__NEXT_ERROR_CODE", { + value: "E496", + enumerable: false, + configurable: true + }); + } + if (!this.routerServerHandler) { + throw Object.defineProperty(new Error(\`Invariant missing routerServerHandler\`), "__NEXT_ERROR_CODE", { + value: "E317", + enumerable: false, + configurable: true + }); + } + await this.routerServerHandler(newReq, newRes); + return; + }; + const { isAbsolute, href } = paramsResult; + const imageUpstream = isAbsolute ? await fetchExternalImage(href) : await fetchInternalImage(href, req.originalRequest, res.originalResponse, handleInternalReq); + return imageOptimizer(imageUpstream, paramsResult, this.nextConfig, { + isDev: this.renderOpts.dev, + previousCacheEntry + }); + } } - let result; - let bubblingResult = false; - try { - await this.ensureMiddleware(req.url); - result = await this.runMiddleware({ - request: req, - response: res, - parsedUrl: parsedUrl, - parsed: parsed - }); - if ('response' in result) { - if (isMiddlewareInvoke) { - bubblingResult = true; - throw Object.defineProperty(new _tracer.BubbledError(true, result), "__NEXT_ERROR_CODE", { - value: "E394", + } + " + `); + + expect( + patchCode( + nextServerMinimalCode, + createEmptyBodyRule("runEdgeFunction"), + ), + ).toMatchInlineSnapshot(` + "class NextNodeServer extends _baseserver.default { + constructor(options){ + var _options_conf_experimental_sri, _options_conf_experimental; + // Initialize super class + super(options), this.registeredInstrumentation = false, this.cleanupListeners = new _asynccallbackset.AsyncCallbackSet(), this.handleNextImageRequest = async (req, res, parsedUrl)=>{ + if (!parsedUrl.pathname || !parsedUrl.pathname.startsWith('/_next/image')) { + return false; + } + // Ignore if its a middleware request + if ((0, _requestmeta.getRequestMeta)(req, 'middlewareInvoke')) { + return false; + } + if (this.minimalMode || this.nextConfig.output === 'export' || process.env.NEXT_MINIMAL) { + res.statusCode = 400; + res.body('Bad Request').send(); + return true; + // the \`else\` branch is needed for tree-shaking + } else { + const { ImageOptimizerCache } = require('./image-optimizer'); + const imageOptimizerCache = new ImageOptimizerCache({ + distDir: this.distDir, + nextConfig: this.nextConfig + }); + const { sendResponse, ImageError } = require('./image-optimizer'); + if (!this.imageResponseCache) { + throw Object.defineProperty(new Error('invariant image optimizer cache was not initialized'), "__NEXT_ERROR_CODE", { + value: "E160", + enumerable: false, + configurable: true + }); + } + const imagesConfig = this.nextConfig.images; + if (imagesConfig.loader !== 'default' || imagesConfig.unoptimized) { + await this.render404(req, res); + return true; + } + const paramsResult = ImageOptimizerCache.validateParams(req.originalRequest, parsedUrl.query, this.nextConfig, !!this.renderOpts.dev); + if ('errorMessage' in paramsResult) { + res.statusCode = 400; + res.body(paramsResult.errorMessage).send(); + return true; + } + const cacheKey = ImageOptimizerCache.getCacheKey(paramsResult); + try { + var _cacheEntry_value, _cacheEntry_cacheControl; + const { getExtension } = require('./serve-static'); + const cacheEntry = await this.imageResponseCache.get(cacheKey, async ({ previousCacheEntry })=>{ + const { buffer, contentType, maxAge, upstreamEtag, etag } = await this.imageOptimizer(req, res, paramsResult, previousCacheEntry); + return { + value: { + kind: _responsecache.CachedRouteKind.IMAGE, + buffer, + etag, + extension: getExtension(contentType), + upstreamEtag + }, + isFallback: false, + cacheControl: { + revalidate: maxAge, + expire: undefined + } + }; + }, { + routeKind: _routekind.RouteKind.IMAGE, + incrementalCache: imageOptimizerCache, + isFallback: false + }); + if ((cacheEntry == null ? void 0 : (_cacheEntry_value = cacheEntry.value) == null ? void 0 : _cacheEntry_value.kind) !== _responsecache.CachedRouteKind.IMAGE) { + throw Object.defineProperty(new Error('invariant did not get entry from image response cache'), "__NEXT_ERROR_CODE", { + value: "E518", + enumerable: false, + configurable: true + }); + } + sendResponse(req.originalRequest, res.originalResponse, paramsResult.href, cacheEntry.value.extension, cacheEntry.value.buffer, cacheEntry.value.etag, paramsResult.isStatic, cacheEntry.isMiss ? 'MISS' : cacheEntry.isStale ? 'STALE' : 'HIT', imagesConfig, ((_cacheEntry_cacheControl = cacheEntry.cacheControl) == null ? void 0 : _cacheEntry_cacheControl.revalidate) || 0, Boolean(this.renderOpts.dev)); + return true; + } catch (err) { + if (err instanceof ImageError) { + res.statusCode = err.statusCode; + res.body(err.message).send(); + return true; + } + throw err; + } + } + }, this.handleCatchallRenderRequest = async (req, res, parsedUrl)=>{ + let { pathname, query } = parsedUrl; + if (!pathname) { + throw Object.defineProperty(new Error('Invariant: pathname is undefined'), "__NEXT_ERROR_CODE", { + value: "E409", enumerable: false, configurable: true }); } - for (const [key, value] of Object.entries((0, _utils1.toNodeOutgoingHttpHeaders)(result.response.headers))){ - if (key !== 'content-encoding' && value !== undefined) { - res.setHeader(key, value); + // This is a catch-all route, there should be no fallbacks so mark it as + // such. + (0, _requestmeta.addRequestMeta)(req, 'bubbleNoFallback', true); + try { + var _this_i18nProvider; + // next.js core assumes page path without trailing slash + pathname = (0, _removetrailingslash.removeTrailingSlash)(pathname); + const options = { + i18n: (_this_i18nProvider = this.i18nProvider) == null ? void 0 : _this_i18nProvider.fromRequest(req, pathname) + }; + const match = await this.matchers.match(pathname, options); + // If we don't have a match, try to render it anyways. + if (!match) { + await this.render(req, res, pathname, query, parsedUrl, true); + return true; + } + // Add the match to the request so we don't have to re-run the matcher + // for the same request. + (0, _requestmeta.addRequestMeta)(req, 'match', match); + // TODO-APP: move this to a route handler + const edgeFunctionsPages = this.getEdgeFunctionsPages(); + for (const edgeFunctionsPage of edgeFunctionsPages){ + // If the page doesn't match the edge function page, skip it. + if (edgeFunctionsPage !== match.definition.page) continue; + if (this.nextConfig.output === 'export') { + await this.render404(req, res, parsedUrl); + return true; + } + delete query[_approuterheaders.NEXT_RSC_UNION_QUERY]; + // If we handled the request, we can return early. + // For api routes edge runtime + try { + const handled = await this.runEdgeFunction({ + req, + res, + query, + params: match.params, + page: match.definition.page, + match, + appPaths: null + }); + if (handled) return true; + } catch (apiError) { + await this.instrumentationOnRequestError(apiError, req, { + routePath: match.definition.page, + routerKind: 'Pages Router', + routeType: 'route', + // Edge runtime does not support ISR + revalidateReason: undefined + }); + throw apiError; + } } + // If the route was detected as being a Pages API route, then handle + // it. + // TODO: move this behavior into a route handler. + if ((0, _pagesapiroutematch.isPagesAPIRouteMatch)(match)) { + if (this.nextConfig.output === 'export') { + await this.render404(req, res, parsedUrl); + return true; + } + const handled = await this.handleApiRequest(req, res, query, match); + if (handled) return true; + } + await this.render(req, res, pathname, query, parsedUrl, true); + return true; + } catch (err) { + if (err instanceof _baseserver.NoFallbackError) { + throw err; + } + try { + if (this.renderOpts.dev) { + const { formatServerError } = require('../lib/format-server-error'); + formatServerError(err); + this.logErrorWithOriginalStack(err); + } else { + this.logError(err); + } + res.statusCode = 500; + await this.renderError(err, req, res, pathname, query); + return true; + } catch {} + throw err; } - res.statusCode = result.response.status; - const { originalResponse } = res; - if (result.response.body) { - await (0, _pipereadable.pipeToNodeResponse)(result.response.body, originalResponse); - } else { - originalResponse.end(); + }, this.handleCatchallMiddlewareRequest = async (req, res, parsed)=>{ + const isMiddlewareInvoke = (0, _requestmeta.getRequestMeta)(req, 'middlewareInvoke'); + if (!isMiddlewareInvoke) { + return false; } - return true; + const handleFinished = ()=>{ + (0, _requestmeta.addRequestMeta)(req, 'middlewareInvoke', true); + res.body('').send(); + return true; + }; + const middleware = await this.getMiddleware(); + if (!middleware) { + return handleFinished(); + } + const initUrl = (0, _requestmeta.getRequestMeta)(req, 'initURL'); + const parsedUrl = (0, _parseurl.parseUrl)(initUrl); + const pathnameInfo = (0, _getnextpathnameinfo.getNextPathnameInfo)(parsedUrl.pathname, { + nextConfig: this.nextConfig, + i18nProvider: this.i18nProvider + }); + parsedUrl.pathname = pathnameInfo.pathname; + const normalizedPathname = (0, _removetrailingslash.removeTrailingSlash)(parsed.pathname || ''); + if (!middleware.match(normalizedPathname, req, parsedUrl.query)) { + return handleFinished(); + } + let result; + let bubblingResult = false; + try { + await this.ensureMiddleware(req.url); + result = await this.runMiddleware({ + request: req, + response: res, + parsedUrl: parsedUrl, + parsed: parsed + }); + if ('response' in result) { + if (isMiddlewareInvoke) { + bubblingResult = true; + throw Object.defineProperty(new _tracer.BubbledError(true, result), "__NEXT_ERROR_CODE", { + value: "E394", + enumerable: false, + configurable: true + }); + } + for (const [key, value] of Object.entries((0, _utils1.toNodeOutgoingHttpHeaders)(result.response.headers))){ + if (key !== 'content-encoding' && value !== undefined) { + res.setHeader(key, value); + } + } + res.statusCode = result.response.status; + const { originalResponse } = res; + if (result.response.body) { + await (0, _pipereadable.pipeToNodeResponse)(result.response.body, originalResponse); + } else { + originalResponse.end(); + } + return true; + } + } catch (err) { + if (bubblingResult) { + throw err; + } + if ((0, _iserror.default)(err) && err.code === 'ENOENT') { + await this.render404(req, res, parsed); + return true; + } + if (err instanceof _utils.DecodeError) { + res.statusCode = 400; + await this.renderError(err, req, res, parsed.pathname || ''); + return true; + } + const error = (0, _iserror.getProperError)(err); + console.error(error); + res.statusCode = 500; + await this.renderError(error, req, res, parsed.pathname || ''); + return true; + } + return result.finished; + }; + console.time('Next.js server initialization'); + this.isDev = options.dev ?? false; + this.sriEnabled = Boolean((_options_conf_experimental = options.conf.experimental) == null ? void 0 : (_options_conf_experimental_sri = _options_conf_experimental.sri) == null ? void 0 : _options_conf_experimental_sri.algorithm); + /** + * This sets environment variable to be used at the time of SSR by head.tsx. + * Using this from process.env allows targeting SSR by calling + * \`process.env.__NEXT_OPTIMIZE_CSS\`. + */ if (this.renderOpts.optimizeCss) { + process.env.__NEXT_OPTIMIZE_CSS = JSON.stringify(true); } - } catch (err) { - if (bubblingResult) { - throw err; + if (this.renderOpts.nextScriptWorkers) { + process.env.__NEXT_SCRIPT_WORKERS = JSON.stringify(true); } - if ((0, _iserror.default)(err) && err.code === 'ENOENT') { - await this.render404(req, res, parsed); - return true; + process.env.NEXT_DEPLOYMENT_ID = this.nextConfig.deploymentId || ''; + if (!this.minimalMode) { + this.imageResponseCache = new _responsecache.default(this.minimalMode); } - if (err instanceof _utils.DecodeError) { - res.statusCode = 400; - await this.renderError(err, req, res, parsed.pathname || ''); - return true; + const { appDocumentPreloading } = this.nextConfig.experimental; + const isDefaultEnabled = typeof appDocumentPreloading === 'undefined'; + if (!options.dev && (appDocumentPreloading === true || !(this.minimalMode && isDefaultEnabled))) { + // pre-warm _document and _app as these will be + // needed for most requests + (0, _loadcomponents.loadComponents)({ + distDir: this.distDir, + page: '/_document', + isAppPath: false, + isDev: this.isDev, + sriEnabled: this.sriEnabled + }).catch(()=>{}); + (0, _loadcomponents.loadComponents)({ + distDir: this.distDir, + page: '/_app', + isAppPath: false, + isDev: this.isDev, + sriEnabled: this.sriEnabled + }).catch(()=>{}); } - const error = (0, _iserror.getProperError)(err); - console.error(error); - res.statusCode = 500; - await this.renderError(error, req, res, parsed.pathname || ''); - return true; + if (!options.dev && !this.minimalMode && this.nextConfig.experimental.preloadEntriesOnStart) { + this.unstable_preloadEntries(); + } + if (!options.dev) { + const { dynamicRoutes = [] } = this.getRoutesManifest() ?? {}; + this.dynamicRoutes = dynamicRoutes.map((r)=>{ + // TODO: can we just re-use the regex from the manifest? + const regex = (0, _routeregex.getRouteRegex)(r.page); + const match = (0, _routematcher.getRouteMatcher)(regex); + return { + match, + page: r.page, + re: regex.re + }; + }); + } + // ensure options are set when loadConfig isn't called + (0, _setuphttpagentenv.setHttpClientAndAgentOptions)(this.nextConfig); + // Intercept fetch and other testmode apis. + if (this.serverOptions.experimentalTestProxy) { + process.env.NEXT_PRIVATE_TEST_PROXY = 'true'; + const { interceptTestApis } = require('next/dist/experimental/testmode/server'); + interceptTestApis(); + } + this.middlewareManifestPath = (0, _path.join)(this.serverDistDir, _constants.MIDDLEWARE_MANIFEST); + // This is just optimization to fire prepare as soon as possible. It will be + // properly awaited later. We add the catch here to ensure that it does not + // cause a unhandled promise rejection. The promise rejection will be + // handled later on via the \`await\` when the request handler is called. + if (!options.dev) { + this.prepare().catch((err)=>{ + console.error('Failed to prepare server', err); + }); + } + console.timeEnd('Next.js server initialization'); } - return result.finished; - }; - console.time('Next.js server initialization'); - this.isDev = options.dev ?? false; - this.sriEnabled = Boolean((_options_conf_experimental = options.conf.experimental) == null ? void 0 : (_options_conf_experimental_sri = _options_conf_experimental.sri) == null ? void 0 : _options_conf_experimental_sri.algorithm); - /** - * This sets environment variable to be used at the time of SSR by head.tsx. - * Using this from process.env allows targeting SSR by calling - * \`process.env.__NEXT_OPTIMIZE_CSS\`. - */ if (this.renderOpts.optimizeCss) { - process.env.__NEXT_OPTIMIZE_CSS = JSON.stringify(true); - } - if (this.renderOpts.nextScriptWorkers) { - process.env.__NEXT_SCRIPT_WORKERS = JSON.stringify(true); - } - process.env.NEXT_DEPLOYMENT_ID = this.nextConfig.deploymentId || ''; - if (!this.minimalMode) { - this.imageResponseCache = new _responsecache.default(this.minimalMode); - } - const { appDocumentPreloading } = this.nextConfig.experimental; - const isDefaultEnabled = typeof appDocumentPreloading === 'undefined'; - if (!options.dev && (appDocumentPreloading === true || !(this.minimalMode && isDefaultEnabled))) {} - if (!options.dev && !this.minimalMode && this.nextConfig.experimental.preloadEntriesOnStart) {} - if (!options.dev) { - const { dynamicRoutes = [] } = this.getRoutesManifest() ?? {}; - this.dynamicRoutes = dynamicRoutes.map((r)=>{ - // TODO: can we just re-use the regex from the manifest? - const regex = (0, _routeregex.getRouteRegex)(r.page); - const match = (0, _routematcher.getRouteMatcher)(regex); - return { - match, - page: r.page, - re: regex.re - }; - }); - } - // ensure options are set when loadConfig isn't called - (0, _setuphttpagentenv.setHttpClientAndAgentOptions)(this.nextConfig); - // Intercept fetch and other testmode apis. - if (this.serverOptions.experimentalTestProxy) { - process.env.NEXT_PRIVATE_TEST_PROXY = 'true'; - const { interceptTestApis } = require('next/dist/experimental/testmode/server'); - interceptTestApis(); - } - this.middlewareManifestPath = (0, _path.join)(this.serverDistDir, _constants.MIDDLEWARE_MANIFEST); - // This is just optimization to fire prepare as soon as possible. It will be - // properly awaited later. We add the catch here to ensure that it does not - // cause a unhandled promise rejection. The promise rejection will be - // handled later on via the \`await\` when the request handler is called. - if (!options.dev) { - this.prepare().catch((err)=>{ - console.error('Failed to prepare server', err); - }); - } - console.timeEnd('Next.js server initialization'); - } -async runMiddleware(params) { - if (process.env.NEXT_MINIMAL) { - throw Object.defineProperty(new Error('invariant: runMiddleware should not be called in minimal mode'), "__NEXT_ERROR_CODE", { - value: "E276", - enumerable: false, - configurable: true - }); - } - // Middleware is skipped for on-demand revalidate requests - // REST OF THE CODE - } -async runEdgeFunction(params) { - if (process.env.NEXT_MINIMAL) { - throw Object.defineProperty(new Error('Middleware is not supported in minimal mode. Please remove the \`NEXT_MINIMAL\` environment variable.'), "__NEXT_ERROR_CODE", { - value: "E58", - enumerable: false, - configurable: true - }); - } -} -async imageOptimizer(req, res, paramsResult, previousCacheEntry) { - if (process.env.NEXT_MINIMAL) { - throw Object.defineProperty(new Error('invariant: imageOptimizer should not be called in minimal mode'), "__NEXT_ERROR_CODE", { - value: "E506", - enumerable: false, - configurable: true - }); - } else { - const { imageOptimizer, fetchExternalImage, fetchInternalImage } = require('./image-optimizer'); - const handleInternalReq = async (newReq, newRes)=>{ - if (newReq.url === req.url) { - throw Object.defineProperty(new Error(\`Invariant attempted to optimize _next/image itself\`), "__NEXT_ERROR_CODE", { - value: "E496", + async runMiddleware(params) { + if (process.env.NEXT_MINIMAL) { + throw Object.defineProperty(new Error('invariant: runMiddleware should not be called in minimal mode'), "__NEXT_ERROR_CODE", { + value: "E276", enumerable: false, configurable: true }); } - if (!this.routerServerHandler) { - throw Object.defineProperty(new Error(\`Invariant missing routerServerHandler\`), "__NEXT_ERROR_CODE", { - value: "E317", + // Middleware is skipped for on-demand revalidate requests + // REST OF THE CODE + } + async runEdgeFunction(params) { + throw new Error("runEdgeFunction should not be called with OpenNext"); + } + async imageOptimizer(req, res, paramsResult, previousCacheEntry) { + if (process.env.NEXT_MINIMAL) { + throw Object.defineProperty(new Error('invariant: imageOptimizer should not be called in minimal mode'), "__NEXT_ERROR_CODE", { + value: "E506", enumerable: false, configurable: true }); + } else { + const { imageOptimizer, fetchExternalImage, fetchInternalImage } = require('./image-optimizer'); + const handleInternalReq = async (newReq, newRes)=>{ + if (newReq.url === req.url) { + throw Object.defineProperty(new Error(\`Invariant attempted to optimize _next/image itself\`), "__NEXT_ERROR_CODE", { + value: "E496", + enumerable: false, + configurable: true + }); + } + if (!this.routerServerHandler) { + throw Object.defineProperty(new Error(\`Invariant missing routerServerHandler\`), "__NEXT_ERROR_CODE", { + value: "E317", + enumerable: false, + configurable: true + }); + } + await this.routerServerHandler(newReq, newRes); + return; + }; + const { isAbsolute, href } = paramsResult; + const imageUpstream = isAbsolute ? await fetchExternalImage(href) : await fetchInternalImage(href, req.originalRequest, res.originalResponse, handleInternalReq); + return imageOptimizer(imageUpstream, paramsResult, this.nextConfig, { + isDev: this.renderOpts.dev, + previousCacheEntry + }); } - await this.routerServerHandler(newReq, newRes); - return; - }; - const { isAbsolute, href } = paramsResult; - const imageUpstream = isAbsolute ? await fetchExternalImage(href) : await fetchInternalImage(href, req.originalRequest, res.originalResponse, handleInternalReq); - return imageOptimizer(imageUpstream, paramsResult, this.nextConfig, { - isDev: this.renderOpts.dev, - previousCacheEntry - }); + } } - } -} -" + " `); + }); + + test("Error Inspect", () => { + const code = ` +// This file should be imported before any others. It sets up the environment +// for later imports to work properly. +"use strict"; +Object.defineProperty(exports, "__esModule", { + value: true +}); +require("./node-environment-baseline"); +require("./node-environment-extensions/error-inspect"); +require("./node-environment-extensions/random"); +require("./node-environment-extensions/date"); +require("./node-environment-extensions/web-crypto"); +require("./node-environment-extensions/node-crypto"); +//# sourceMappingURL=node-environment.js.map +}`; + + expect(patchCode(code, errorInspectRule)).toMatchInlineSnapshot(` + "// This file should be imported before any others. It sets up the environment + // for later imports to work properly. + "use strict"; + Object.defineProperty(exports, "__esModule", { + value: true + }); + require("./node-environment-baseline"); + // Removed by OpenNext + // require("./node-environment-extensions/error-inspect"); + require("./node-environment-extensions/random"); + require("./node-environment-extensions/date"); + require("./node-environment-extensions/web-crypto"); + require("./node-environment-extensions/node-crypto"); + //# sourceMappingURL=node-environment.js.map + }" + `); + }); }); });