diff --git a/.eslintrc.json b/.eslintrc.json index 4f3c9e2164f1fbc..b2508a20109dade 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -71,6 +71,7 @@ "functions": false, "classes": false, "variables": false, + "enums": false, "typedefs": false } ], @@ -106,6 +107,16 @@ { "files": ["examples/**/*"], "rules": { + "@typescript-eslint/no-use-before-define": [ + "error", + { + "functions": true, + "classes": true, + "variables": true, + "enums": true, + "typedefs": true + } + ], "import/no-anonymous-default-export": [ "error", { diff --git a/.github/workflows/build_test_deploy.yml b/.github/workflows/build_test_deploy.yml index 9293ce558a4efc1..1537037ceab9cf3 100644 --- a/.github/workflows/build_test_deploy.yml +++ b/.github/workflows/build_test_deploy.yml @@ -1463,7 +1463,7 @@ jobs: rm -rf test - name: Build id: build - uses: vmactions/freebsd-vm@v0.2.0 + uses: vmactions/freebsd-vm@v0.2.3 env: DEBUG: napi:* RUSTUP_HOME: /usr/local/rustup @@ -1493,8 +1493,8 @@ jobs: whoami env freebsd-version - npm i -g "@napi-rs/cli@${NAPI_CLI_VERSION}" "turbo@${TURBO_VERSION}" && if [ ! -f $(dirname $(which yarn))/pnpm ]; then ln -s $(which yarn) $(dirname $(which yarn))/pnpm;fi - yarn --cwd=packages/next-swc build-native-no-plugin --release --target x86_64-unknown-freebsd + npm i -g "@napi-rs/cli@${NAPI_CLI_VERSION}" + cd packages/next-swc && napi build --platform -p next-swc-napi --cargo-name next_swc_napi native --release --target x86_64-unknown-freebsd rm -rf node_modules rm -rf packages/next-swc/target - name: Upload artifact diff --git a/contributing.md b/contributing.md index b07d1eb88737150..6238e972f4bf393 100644 --- a/contributing.md +++ b/contributing.md @@ -30,9 +30,9 @@ To develop locally: ``` git checkout -b MY_BRANCH_NAME ``` -3. Install pnpm: +3. Enable pnpm: ``` - npm install -g pnpm + corepack enable pnpm ``` 4. Install the dependencies with: ``` diff --git a/docs/advanced-features/compiler.md b/docs/advanced-features/compiler.md index fb022c383f6cb87..3f2bb69033ef778 100644 --- a/docs/advanced-features/compiler.md +++ b/docs/advanced-features/compiler.md @@ -358,7 +358,7 @@ This transform uses [handlebars](https://docs.rs/handlebars) to template the rep 1. `matches`: Has type `string[]`. All groups matched by the regular expression. `matches.[0]` is the full match. 2. `member`: Has type `string`. The name of the member import. -3. `lowerCase`, `upperCase`, `camelCase`: Helper functions to convert a string to lower, upper or camel cases. +3. `lowerCase`, `upperCase`, `camelCase`, `kebabCase`: Helper functions to convert a string to lower, upper, camel or kebab cases. ### SWC Trace profiling diff --git a/docs/api-reference/data-fetching/get-server-side-props.md b/docs/api-reference/data-fetching/get-server-side-props.md index 8eed5bffa2e3e5c..2a8a0ff48061f6e 100644 --- a/docs/api-reference/data-fetching/get-server-side-props.md +++ b/docs/api-reference/data-fetching/get-server-side-props.md @@ -33,7 +33,7 @@ The `context` parameter is an object containing the following keys: - `params`: If this page uses a [dynamic route](/docs/routing/dynamic-routes.md), `params` contains the route parameters. If the page name is `[id].js` , then `params` will look like `{ id: ... }`. - `req`: [The `HTTP` IncomingMessage object](https://nodejs.org/api/http.html#http_class_http_incomingmessage), with an additional `cookies` prop, which is an object with string keys mapping to string values of cookies. - `res`: [The `HTTP` response object](https://nodejs.org/api/http.html#http_class_http_serverresponse). -- `query`: An object representing the query string. +- `query`: An object representing the query string, including dynamic route parameters. - `preview`: `preview` is `true` if the page is in the [Preview Mode](/docs/advanced-features/preview-mode.md) and `false` otherwise. - `previewData`: The [preview](/docs/advanced-features/preview-mode.md) data set by `setPreviewData`. - `resolvedUrl`: A normalized version of the request `URL` that strips the `_next/data` prefix for client transitions and includes original query values. diff --git a/docs/api-reference/next/future/image.md b/docs/api-reference/next/future/image.md index 437af9d77c3d779..f7909d6bcec6094 100644 --- a/docs/api-reference/next/future/image.md +++ b/docs/api-reference/next/future/image.md @@ -43,19 +43,9 @@ Compared to `next/image`, the new `next/future/image` component has the followin ## Migration -Although `layout` is not available, you can migrate `next/image` to `next/future/image` using a few props. The following is a comparison of the two components: +Although `layout` is not available, you can migrate `next/image` to `next/future/image` using a few props. The following code snippets compare the two components: - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
next/imagenext/future/image
+#### before: `next/image` ```jsx import Image from 'next/image' @@ -66,8 +56,7 @@ function Page() { } ``` - +#### after: `next/future/image` ```jsx import Image from 'next/future/image' @@ -79,11 +68,7 @@ function Page() { } ``` -
+#### before: `next/image` ```jsx import Image from 'next/image' @@ -94,8 +79,7 @@ function Page() { } ``` - +#### after: `next/future/image` ```jsx import Image from 'next/future/image' @@ -107,11 +91,7 @@ function Page() { } ``` -
+#### before: `next/image` ```jsx import Image from 'next/image' @@ -122,8 +102,7 @@ function Page() { } ``` - +#### after: `next/future/image` ```jsx import Image from 'next/future/image' @@ -134,11 +113,7 @@ function Page() { } ``` -
+#### before: `next/image` ```jsx import Image from 'next/image' @@ -149,8 +124,7 @@ function Page() { } ``` - +#### after: `next/future/image` ```jsx import Image from 'next/future/image' @@ -161,12 +135,6 @@ function Page() { } ``` -
- You can also use `className` instead of `style`. ## Required Props @@ -251,11 +219,37 @@ See also: ### sizes -A string that provides information about how wide the image will be at different breakpoints. +A string that provides information about how wide the image will be at different breakpoints. The value of `sizes` will greatly affect performance for images using [`fill`](#fill) or which are styled to have a responsive size. + +The `sizes` property serves two important purposes related to image performance: + +First, the value of `sizes` is used by the browser to determine which size of the image to download, from `next/future/image`'s automatically-generated source set. When the browser chooses, it does not yet know the size of the image on the page, so it selects an image that is the same size or larger than the viewport. The `sizes` property allows you to tell the browser that the image will actually be smaller than full screen. If you don't specify a `sizes` value in an image with the `fill` property, a default value of `100vw` (full screen width) is used. + +Second, the `sizes` property configures how `next/future/image` automatically generates an image source set. If no `sizes` value is present, a small source set is generated, suitable for a fixed-size image. If `sizes` is defined, a large source set is generated, suitable for a responsive image. If the `sizes` property includes sizes such as `50vw`, which represent a percentage of the viewport width, then the source set is trimmed to not include any values which are too small to ever be necessary. + +For example, if you know your styling will cause an image to be full-width on mobile devices, in a 2-column layout on tablets, and a 3-column layout on desktop displays, you should include a sizes property such as the following: + +```js +import Image from 'next/image' +const Example = () => ( +
+ +
+) +``` + +This example `sizes` could have a dramatic effect on performance metrics. Without the `33vw` sizes, the image selected from the server would be 3 times as wide as it needs to be. Because file size is proportional to the square of the width, without `sizes` the user would download an image that's 9 times larger than necessary. -It's important to assign `sizes` for responsive images that takes up less than the full viewport width. For example, when the parent element will constrain the image to always be less than half the viewport width, use `sizes="50vw"`. Without `sizes`, the image will be sent at twice the necessary resolution, decreasing performance. +Learn more about `srcset` and `sizes`: -[Learn more](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/img#attr-sizes). +- [web.dev](https://web.dev/learn/design/responsive-images/#sizes) +- [mdn](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/img#attr-sizes) ### quality diff --git a/docs/api-reference/next/image.md b/docs/api-reference/next/image.md index 82e4830c8f28def..b0b8a0ff37462bd 100644 --- a/docs/api-reference/next/image.md +++ b/docs/api-reference/next/image.md @@ -131,15 +131,37 @@ const MyImage = (props) => { ### sizes -A string that provides information about how wide the image will be at different breakpoints. Defaults to `100vw` (the full width of the screen) when using `layout="responsive"` or `layout="fill"`. +A string that provides information about how wide the image will be at different breakpoints. The value of `sizes` will greatly affect performance for images using `layout="responsive"` or `layout="fill"`. It will be ignored for images using `layout="intrinsic"` or `layout="fixed"`. -If you are using `layout="fill"` or `layout="responsive"` it's important to assign `sizes` for any image that takes up less than the full viewport width. +The `sizes` property serves two important purposes related to image performance: -For example, when the parent element will constrain the image to always be less than half the viewport width, use `sizes="50vw"`. Without `sizes`, the image will be sent at twice the necessary resolution, decreasing performance. +First, the value of `sizes` is used by the browser to determine which size of the image to download, from `next/image`'s automatically-generated source set. When the browser chooses, it does not yet know the size of the image on the page, so it selects an image that is the same size or larger than the viewport. The `sizes` property allows you to tell the browser that the image will actually be smaller than full screen. If you don't specify a `sizes` value, a default value of `100vw` (full screen width) is used. -If you are using `layout="intrinsic"` or `layout="fixed"`, then `sizes` is not needed because the upper bound width is constrained already. +Second, the `sizes` value is parsed and used to trim the values in the automatically-created source set. If the `sizes` property includes sizes such as `50vw`, which represent a percentage of the viewport width, then the source set is trimmed to not include any values which are too small to ever be necessary. -[Learn more](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/img#attr-sizes). +For example, if you know your styling will cause an image to be full-width on mobile devices, in a 2-column layout on tablets, and a 3-column layout on desktop displays, you should include a sizes property such as the following: + +```js +import Image from 'next/image' +const Example = () => ( +
+ +
+) +``` + +This example `sizes` could have a dramatic effect on performance metrics. Without the `33vw` sizes, the image selected from the server would be 3 times as wide as it needs to be. Because file size is proportional to the square of the width, without `sizes` the user would download an image that's 9 times larger than necessary. + +Learn more about `srcset` and `sizes`: + +- [web.dev](https://web.dev/learn/design/responsive-images/#sizes) +- [mdn](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/img#attr-sizes) ### quality diff --git a/docs/api-reference/next/router.md b/docs/api-reference/next/router.md index f9f3ac0e602a648..e068a85e479acc6 100644 --- a/docs/api-reference/next/router.md +++ b/docs/api-reference/next/router.md @@ -248,14 +248,16 @@ export default function Page() { Prefetch pages for faster client-side transitions. This method is only useful for navigations without [`next/link`](/docs/api-reference/next/link.md), as `next/link` takes care of prefetching pages automatically. -> This is a production only feature. Next.js doesn't prefetch pages on development. +> This is a production only feature. Next.js doesn't prefetch pages in development. ```jsx -router.prefetch(url, as) +router.prefetch(url, as, options) ``` - `url` - The URL to prefetch, including explicit routes (e.g. `/dashboard`) and dynamic routes (e.g. `/product/[id]`) - `as` - Optional decorator for `url`. Before Next.js 9.5.3 this was used to prefetch dynamic routes, check our [previous docs](https://nextjs.org/docs/tag/v9.5.2/api-reference/next/link#dynamic-routes) to see how it worked +- `options` - Optional object with the following allowed fields: + - `locale` - allows providing a different locale from the active one. If `false`, `url` has to include the locale as the active locale won't be used. #### Usage diff --git a/errors/invalid-getserversideprops-return-value.md b/errors/invalid-getserversideprops-value.md similarity index 100% rename from errors/invalid-getserversideprops-return-value.md rename to errors/invalid-getserversideprops-value.md diff --git a/errors/manifest.json b/errors/manifest.json index 44fcf7f3df622f6..3f013867de559b8 100644 --- a/errors/manifest.json +++ b/errors/manifest.json @@ -673,8 +673,14 @@ "path": "/errors/import-next.md" }, { - "title": "invalid-getserversideprops-return-value", - "path": "/errors/invalid-getserversideprops-return-value.md" + "path": "/errors/invalid-getserversideprops-return-value.md", + "redirect": { + "destination": "/docs/messages/invalid-getserversideprops-value" + } + }, + { + "title": "invalid-getserversideprops-value", + "path": "/errors/invalid-getserversideprops-value.md" }, { "title": "no-assign-module-variable", diff --git a/examples/amp-first/components/amp/AmpCustomElement.tsx b/examples/amp-first/components/amp/AmpCustomElement.tsx index 1a14221ea49260e..0c02ae55668c41d 100644 --- a/examples/amp-first/components/amp/AmpCustomElement.tsx +++ b/examples/amp-first/components/amp/AmpCustomElement.tsx @@ -319,6 +319,10 @@ export function AmpIncludeAmpLinkRewriter() { return } +export function AmpIncludeAmpMustache() { + return +} + export function AmpIncludeAmpList() { return ( <> @@ -350,10 +354,6 @@ export function AmpIncludeAmpMowplayer() { return } -export function AmpIncludeAmpMustache() { - return -} - export function AmpIncludeAmpNextPage() { return } diff --git a/examples/amp-first/components/amp/AmpScript.tsx b/examples/amp-first/components/amp/AmpScript.tsx index d8b2b4d2e00b913..c884ec5ae83fca7 100644 --- a/examples/amp-first/components/amp/AmpScript.tsx +++ b/examples/amp-first/components/amp/AmpScript.tsx @@ -10,6 +10,13 @@ type AmpScriptProps = { src?: string } +const generateInlineScript = (script: Function | string) => { + if (typeof script === 'function') { + return `${script.toString()}()` + } + return String(script) +} + /** * Embeds an AMP Script by either linking to a TS `src` file or embedding inline * AMP Script via the `script` property. The inline script hash will automatically @@ -42,10 +49,3 @@ const AmpScript: React.FC = ({ } export default AmpScript - -const generateInlineScript = (script: Function | string) => { - if (typeof script === 'function') { - return `${script.toString()}()` - } - return String(script) -} diff --git a/examples/cms-agilitycms/lib/components/rich-text-area.tsx b/examples/cms-agilitycms/lib/components/rich-text-area.tsx index f747cf0076534ea..7ff6f6f456dbfa2 100644 --- a/examples/cms-agilitycms/lib/components/rich-text-area.tsx +++ b/examples/cms-agilitycms/lib/components/rich-text-area.tsx @@ -1,3 +1,7 @@ +const setHTML = (textblob) => { + return { __html: textblob } +} + export default function RichTextArea({ fields }) { const { textblob } = fields return ( @@ -7,7 +11,3 @@ export default function RichTextArea({ fields }) { /> ) } - -const setHTML = (textblob) => { - return { __html: textblob } -} diff --git a/examples/cms-agilitycms/lib/dependancies.ts b/examples/cms-agilitycms/lib/dependancies.ts index 44ccead1baeef74..183fceba8ac3bba 100644 --- a/examples/cms-agilitycms/lib/dependancies.ts +++ b/examples/cms-agilitycms/lib/dependancies.ts @@ -3,6 +3,23 @@ const path = require('path') const userComponentsPath = path.resolve('./components') const libComponentsPath = path.resolve('./lib/components') +const requireComponent = (name) => { + let Component = null + + try { + //check the user path first (must be relative paths) + Component = require(`../components/${name}.tsx`).default + } catch {} + + if (!Component) + try { + //fallback to lib path (must be relative paths) + Component = require(`./components/${name}.tsx`).default + } catch {} + + return Component +} + //Bug: when dynamic imports are used within the module, it doest not get outputted server-side //let AgilityModule = dynamic(() => import ('../components/' + m.moduleName)); @@ -32,20 +49,3 @@ export const requireComponentDependancyByName = (name) => { return Component } - -const requireComponent = (name) => { - let Component = null - - try { - //check the user path first (must be relative paths) - Component = require(`../components/${name}.tsx`).default - } catch {} - - if (!Component) - try { - //fallback to lib path (must be relative paths) - Component = require(`./components/${name}.tsx`).default - } catch {} - - return Component -} diff --git a/examples/cms-agilitycms/lib/preview.ts b/examples/cms-agilitycms/lib/preview.ts index 7189f4ca398d13a..42e456df04b6730 100644 --- a/examples/cms-agilitycms/lib/preview.ts +++ b/examples/cms-agilitycms/lib/preview.ts @@ -2,45 +2,27 @@ import crypto from 'crypto' import { getClient } from './api' import { CMS_LANG, CMS_CHANNEL } from './constants' -//Validates whether the incoming preview request is valid -export async function validatePreview({ agilityPreviewKey, slug, contentID }) { - //Validate the preview key - if (!agilityPreviewKey) { - return { - error: true, - message: `Missing agilitypreviewkey.`, - } - } - - //sanitize incoming key (replace spaces with '+') - if (agilityPreviewKey.indexOf(` `) > -1) { - agilityPreviewKey = agilityPreviewKey.split(` `).join(`+`) - } - - //compare the preview key being used - const correctPreviewKey = generatePreviewKey() +//Generates a preview key to compare against +export function generatePreviewKey() { + //the string we want to encode + const str = `-1_${process.env.AGILITY_CMS_SECURITY_KEY}_Preview` - if (agilityPreviewKey !== correctPreviewKey) { - return { - error: true, - message: `Invalid agilitypreviewkey.`, - //message: `Invalid agilitypreviewkey. Incoming key is=${agilityPreviewKey} compared to=${correctPreviewKey}...` - } + //build our byte array + let data = [] + for (var i = 0; i < str.length; ++i) { + data.push(str.charCodeAt(i)) + data.push(0) } - const validateSlugResponse = await validateSlugForPreview({ slug, contentID }) - - if (validateSlugResponse.error) { - //kickout - return validateSlugResponse - } + //convert byte array to buffer + const strBuffer = Buffer.from(data) + //encode it! + const previewKey = crypto + .createHash('sha512') + .update(strBuffer) + .digest('base64') - //return success - return { - error: false, - message: null, - slug: validateSlugResponse.slug, - } + return previewKey } //Checks that the requested page exists, if not return a 401 @@ -95,25 +77,43 @@ export async function validateSlugForPreview({ slug, contentID }) { } } -//Generates a preview key to compare against -export function generatePreviewKey() { - //the string we want to encode - const str = `-1_${process.env.AGILITY_CMS_SECURITY_KEY}_Preview` +//Validates whether the incoming preview request is valid +export async function validatePreview({ agilityPreviewKey, slug, contentID }) { + //Validate the preview key + if (!agilityPreviewKey) { + return { + error: true, + message: `Missing agilitypreviewkey.`, + } + } - //build our byte array - let data = [] - for (var i = 0; i < str.length; ++i) { - data.push(str.charCodeAt(i)) - data.push(0) + //sanitize incoming key (replace spaces with '+') + if (agilityPreviewKey.indexOf(` `) > -1) { + agilityPreviewKey = agilityPreviewKey.split(` `).join(`+`) } - //convert byte array to buffer - const strBuffer = Buffer.from(data) - //encode it! - const previewKey = crypto - .createHash('sha512') - .update(strBuffer) - .digest('base64') + //compare the preview key being used + const correctPreviewKey = generatePreviewKey() - return previewKey + if (agilityPreviewKey !== correctPreviewKey) { + return { + error: true, + message: `Invalid agilitypreviewkey.`, + //message: `Invalid agilitypreviewkey. Incoming key is=${agilityPreviewKey} compared to=${correctPreviewKey}...` + } + } + + const validateSlugResponse = await validateSlugForPreview({ slug, contentID }) + + if (validateSlugResponse.error) { + //kickout + return validateSlugResponse + } + + //return success + return { + error: false, + message: null, + slug: validateSlugResponse.slug, + } } diff --git a/examples/cms-builder-io/lib/api.js b/examples/cms-builder-io/lib/api.js index 6b80cf062d97b63..14b287ab1fb0b28 100644 --- a/examples/cms-builder-io/lib/api.js +++ b/examples/cms-builder-io/lib/api.js @@ -11,13 +11,6 @@ export function getAllPostsWithSlug() { }) } -export function getAllPostsForHome(preview) { - return searchPosts( - { 'data.slug': { $exists: true }, 'data.author': { $exists: true } }, - preview - ) -} - export function getDraftPost(id) { return fetch( `https://builder.io/api/v2/content/${BUILDER_CONFIG.postsModel}/${id}?apiKey=${BUILDER_CONFIG.apiKey}&preview=true&noCache=true&cachebust=tru&includeRefs=true` @@ -26,25 +19,6 @@ export function getDraftPost(id) { .then((res) => res || null) } -export async function getPost(mongoQuery, preview) { - let post = preview - ? (await searchPosts(mongoQuery, true))?.[0] - : await builder - .get(BUILDER_CONFIG.postsModel, { - includeRefs: true, - staleCacheSeconds: 20, - apiKey: BUILDER_CONFIG.apiKey, - preview: BUILDER_CONFIG.postsModel, - options: { - noTargeting: true, - }, - query: mongoQuery, - }) - .toPromise() - - return post || null -} - export async function searchPosts(query, preview, limit = 20, offset = 0) { let posts = await builder.getAll(BUILDER_CONFIG.postsModel, { limit, @@ -67,6 +41,32 @@ export async function searchPosts(query, preview, limit = 20, offset = 0) { return posts } +export function getAllPostsForHome(preview) { + return searchPosts( + { 'data.slug': { $exists: true }, 'data.author': { $exists: true } }, + preview + ) +} + +export async function getPost(mongoQuery, preview) { + let post = preview + ? (await searchPosts(mongoQuery, true))?.[0] + : await builder + .get(BUILDER_CONFIG.postsModel, { + includeRefs: true, + staleCacheSeconds: 20, + apiKey: BUILDER_CONFIG.apiKey, + preview: BUILDER_CONFIG.postsModel, + options: { + noTargeting: true, + }, + query: mongoQuery, + }) + .toPromise() + + return post || null +} + export async function getPostAndMorePosts(slug, preview, previewData) { const post = preview && previewData diff --git a/examples/cms-buttercms/lib/api.js b/examples/cms-buttercms/lib/api.js index 5913ae1277efd1b..b4a07b946fdf01f 100644 --- a/examples/cms-buttercms/lib/api.js +++ b/examples/cms-buttercms/lib/api.js @@ -16,6 +16,24 @@ try { const defaultPageSize = 100 const defaultPostCount = 10 +async function getLandingPagesData(page, pageSize = defaultPageSize) { + try { + const params = { + page, + page_size: pageSize, + } + const response = await butter.page.list('landing-page', params) + + return { + pages: response?.data?.data, + prevPage: response?.data?.meta.previous_page, + nextPage: response?.data?.meta.next_page, + } + } catch (e) { + throw e.response.data.detail + } +} + export async function getLandingPage(slug) { try { const page = await butter.page.retrieve('landing-page', slug) @@ -38,24 +56,6 @@ export async function getLandingPages() { return paginatedLandingPages } -async function getLandingPagesData(page, pageSize = defaultPageSize) { - try { - const params = { - page, - page_size: pageSize, - } - const response = await butter.page.list('landing-page', params) - - return { - pages: response?.data?.data, - prevPage: response?.data?.meta.previous_page, - nextPage: response?.data?.meta.next_page, - } - } catch (e) { - throw e.response.data.detail - } -} - export async function getPostsData( { page, pageSize, tag, category } = { page: 1, pageSize: defaultPostCount } ) { diff --git a/examples/cms-kontent/components/image.js b/examples/cms-kontent/components/image.js index 7b47e9176ad06aa..bbf5694d9330d2a 100644 --- a/examples/cms-kontent/components/image.js +++ b/examples/cms-kontent/components/image.js @@ -1,10 +1,6 @@ import NextImage from 'next/image' import { transformImageUrl } from '@kentico/kontent-delivery' -const getLoader = (src) => { - return srcIsKontentAsset(src) ? kontentImageLoader : undefined -} - const srcIsKontentAsset = (src) => { try { const { hostname } = new URL(src) @@ -23,6 +19,10 @@ const kontentImageLoader = ({ src, width, quality = 75 }) => { .getUrl() } +const getLoader = (src) => { + return srcIsKontentAsset(src) ? kontentImageLoader : undefined +} + export default function Image(props) { const loader = getLoader(props.src) diff --git a/examples/with-aphrodite/pages/index.js b/examples/with-aphrodite/pages/index.js index 165a20546656c6d..e13f3adddf11a5a 100644 --- a/examples/with-aphrodite/pages/index.js +++ b/examples/with-aphrodite/pages/index.js @@ -7,14 +7,6 @@ if (typeof window !== 'undefined') { StyleSheet.rehydrate(window.__REHYDRATE_IDS) } -export default function Home() { - return ( -
-

My page

-
- ) -} - const styles = StyleSheet.create({ root: { width: 80, @@ -34,3 +26,11 @@ const styles = StyleSheet.create({ }, }, }) + +export default function Home() { + return ( +
+

My page

+
+ ) +} diff --git a/examples/with-apivideo-upload/components/Card/index.tsx b/examples/with-apivideo-upload/components/Card/index.tsx index 287da46ff84b62a..6f9d3a61f9e0272 100644 --- a/examples/with-apivideo-upload/components/Card/index.tsx +++ b/examples/with-apivideo-upload/components/Card/index.tsx @@ -8,19 +8,6 @@ interface ICardProps { method: 'get' | 'post' } -const Card: React.FC = ({ content, url, method }): JSX.Element => ( - - {method.toUpperCase()} - {content} - - Sketch arrow -

Try it out with our API!

-
-
-) - -export default Card - const Container = styled.a` border: 1px solid rgb(215, 219, 236); border-radius: 0.25rem; @@ -59,3 +46,16 @@ const ImageContainer = styled.div` font-size: 0.5rem; } ` + +const Card: React.FC = ({ content, url, method }): JSX.Element => ( + + {method.toUpperCase()} + {content} + + Sketch arrow +

Try it out with our API!

+
+
+) + +export default Card diff --git a/examples/with-apivideo-upload/components/Loader/index.tsx b/examples/with-apivideo-upload/components/Loader/index.tsx index 0b0bd14a8f1f007..9abbffdb189aa29 100644 --- a/examples/with-apivideo-upload/components/Loader/index.tsx +++ b/examples/with-apivideo-upload/components/Loader/index.tsx @@ -5,10 +5,6 @@ import styled, { keyframes } from 'styled-components' interface ILoaderProps { done: boolean } -const Loader: React.FC = ({ done }): JSX.Element => - done ? : - -export default Loader const spin = keyframes` 0% { @@ -18,6 +14,7 @@ const spin = keyframes` transform: rotate(360deg); } ` + const Spinner = styled.div` border: 3px solid #f3f3f3; border-top: 3px solid rgb(235, 137, 82); @@ -26,3 +23,8 @@ const Spinner = styled.div` height: 25px; animation: ${spin} 1s linear infinite; ` + +const Loader: React.FC = ({ done }): JSX.Element => + done ? : + +export default Loader diff --git a/examples/with-apivideo-upload/components/Status/index.tsx b/examples/with-apivideo-upload/components/Status/index.tsx index 90d494863c0fcfe..d83f8fc123545d2 100644 --- a/examples/with-apivideo-upload/components/Status/index.tsx +++ b/examples/with-apivideo-upload/components/Status/index.tsx @@ -6,6 +6,13 @@ interface IStatusProps { done: boolean title: string } + +const Container = styled.div` + display: flex; + flex-direction: column; + align-items: center; + gap: 5px; +` const Status: React.FC = ({ done, title }): JSX.Element => (

{title}

@@ -14,10 +21,3 @@ const Status: React.FC = ({ done, title }): JSX.Element => ( ) export default Status - -const Container = styled.div` - display: flex; - flex-direction: column; - align-items: center; - gap: 5px; -` diff --git a/examples/with-apivideo-upload/pages/index.tsx b/examples/with-apivideo-upload/pages/index.tsx index 24ec5d4edf99d90..63bb34361dff10d 100644 --- a/examples/with-apivideo-upload/pages/index.tsx +++ b/examples/with-apivideo-upload/pages/index.tsx @@ -42,6 +42,30 @@ const Home: NextPage = () => { fetcher ) + const fetchVideoStatus = async (videoId: string): Promise => { + const { status } = await fetcher(`/api/${videoId}`) + const { encoding, ingest } = status + setStatus({ + ingested: ingest.status === 'uploaded', + encoded: encoding.playable, + }) + if (ingest.status === 'uploaded' && encoding.playable) { + setSize({ + width: encoding.metadata.width, + height: encoding.metadata.height, + }) + setReady(true) + } + } + + const clearState = (): void => { + setReady(false) + setStatus({ ingested: false, encoded: false }) + setVideo(undefined) + setUploadProgress(undefined) + setSize(undefined) + } + useEffect(() => { if (video) { const intervalId = window.setInterval(() => { @@ -72,35 +96,11 @@ const Home: NextPage = () => { setVideo(video) } - const fetchVideoStatus = async (videoId: string): Promise => { - const { status } = await fetcher(`/api/${videoId}`) - const { encoding, ingest } = status - setStatus({ - ingested: ingest.status === 'uploaded', - encoded: encoding.playable, - }) - if (ingest.status === 'uploaded' && encoding.playable) { - setSize({ - width: encoding.metadata.width, - height: encoding.metadata.height, - }) - setReady(true) - } - } - const handleNavigate = (): void => { if (!video) return router.push(`/${video.videoId}?w=${size?.width}&h=${size?.height}`) } - const clearState = (): void => { - setReady(false) - setStatus({ ingested: false, encoded: false }) - setVideo(undefined) - setUploadProgress(undefined) - setSize(undefined) - } - return ( diff --git a/examples/with-apollo-and-redux/components/PostList.js b/examples/with-apollo-and-redux/components/PostList.js index d9b01d3ef1e6abd..a0eee23090ab88c 100644 --- a/examples/with-apollo-and-redux/components/PostList.js +++ b/examples/with-apollo-and-redux/components/PostList.js @@ -35,6 +35,8 @@ export default function PostList() { const loadingMorePosts = networkStatus === NetworkStatus.fetchMore + const { allPosts, _allPostsMeta } = data + const loadMorePosts = () => { fetchMore({ variables: { @@ -46,7 +48,6 @@ export default function PostList() { if (error) return if (loading && !loadingMorePosts) return
Loading
- const { allPosts, _allPostsMeta } = data const areMorePosts = allPosts.length < _allPostsMeta.count return ( diff --git a/examples/with-apollo/components/PostList.js b/examples/with-apollo/components/PostList.js index 4748ac8f35a6aa8..381c156e8189b85 100644 --- a/examples/with-apollo/components/PostList.js +++ b/examples/with-apollo/components/PostList.js @@ -35,6 +35,7 @@ export default function PostList() { ) const loadingMorePosts = networkStatus === NetworkStatus.fetchMore + const { allPosts, _allPostsMeta } = data const loadMorePosts = () => { fetchMore({ @@ -47,7 +48,6 @@ export default function PostList() { if (error) return if (loading && !loadingMorePosts) return
Loading
- const { allPosts, _allPostsMeta } = data const areMorePosts = allPosts.length < _allPostsMeta.count return ( diff --git a/examples/with-babel-macros/pages/index.js b/examples/with-babel-macros/pages/index.js index dea28155a3da0d8..0a0b2d5d6de90c7 100644 --- a/examples/with-babel-macros/pages/index.js +++ b/examples/with-babel-macros/pages/index.js @@ -5,9 +5,7 @@ const whoami = preval` module.exports = userInfo.username ` -export default WhoAmI - -function WhoAmI() { +export default function WhoAmI() { return (

diff --git a/examples/with-cerebral/components/Clock.js b/examples/with-cerebral/components/Clock.js index cfbddeae67ab19e..e2f4fcc8a3c31e3 100644 --- a/examples/with-cerebral/components/Clock.js +++ b/examples/with-cerebral/components/Clock.js @@ -1,3 +1,7 @@ +const pad = (n) => (n < 10 ? `0${n}` : n) +const format = (t) => + `${pad(t.getUTCHours())}:${pad(t.getUTCMinutes())}:${pad(t.getUTCSeconds())}` + export default function Clock(props) { return (
@@ -18,8 +22,3 @@ export default function Clock(props) {
) } - -const format = (t) => - `${pad(t.getUTCHours())}:${pad(t.getUTCMinutes())}:${pad(t.getUTCSeconds())}` - -const pad = (n) => (n < 10 ? `0${n}` : n) diff --git a/examples/with-cxs/pages/index.js b/examples/with-cxs/pages/index.js index bc7e660e5b2383e..fe092135537374c 100644 --- a/examples/with-cxs/pages/index.js +++ b/examples/with-cxs/pages/index.js @@ -8,14 +8,6 @@ if (typeof window !== 'undefined') { cxs.rehydrate(serverCss) } -export default function Home() { - return ( -
-

My page

-
- ) -} - const cx = { root: cxs({ width: 80, @@ -35,3 +27,11 @@ const cx = { }, }), } + +export default function Home() { + return ( +
+

My page

+
+ ) +} diff --git a/examples/with-draft-js/pages/index.js b/examples/with-draft-js/pages/index.js index 950f5b4aceaa731..3185ad29e701f55 100644 --- a/examples/with-draft-js/pages/index.js +++ b/examples/with-draft-js/pages/index.js @@ -7,6 +7,81 @@ import { convertFromRaw, } from 'draft-js' +const initialData = { + blocks: [ + { + key: '16d0k', + text: 'You can edit this text.', + type: 'unstyled', + depth: 0, + inlineStyleRanges: [{ offset: 0, length: 23, style: 'BOLD' }], + entityRanges: [], + data: {}, + }, + { + key: '98peq', + text: '', + type: 'unstyled', + depth: 0, + inlineStyleRanges: [], + entityRanges: [], + data: {}, + }, + { + key: 'ecmnc', + text: 'Luke Skywalker has vanished. In his absence, the sinister FIRST ORDER has risen from the ashes of the Empire and will not rest until Skywalker, the last Jedi, has been destroyed.', + type: 'unstyled', + depth: 0, + inlineStyleRanges: [ + { offset: 0, length: 14, style: 'BOLD' }, + { offset: 133, length: 9, style: 'BOLD' }, + ], + entityRanges: [], + data: {}, + }, + { + key: 'fe2gn', + text: '', + type: 'unstyled', + depth: 0, + inlineStyleRanges: [], + entityRanges: [], + data: {}, + }, + { + key: '4481k', + text: 'With the support of the REPUBLIC, General Leia Organa leads a brave RESISTANCE. She is desperate to find her brother Luke and gain his help in restoring peace and justice to the galaxy.', + type: 'unstyled', + depth: 0, + inlineStyleRanges: [ + { offset: 34, length: 19, style: 'BOLD' }, + { offset: 117, length: 4, style: 'BOLD' }, + { offset: 68, length: 10, style: 'ANYCUSTOMSTYLE' }, + ], + entityRanges: [], + data: {}, + }, + ], + entityMap: {}, +} + +// Custom overrides for each style +const styleMap = { + CODE: { + backgroundColor: 'rgba(0, 0, 0, 0.05)', + fontFamily: '"Inconsolata", "Menlo", "Consolas", monospace', + fontSize: 16, + padding: 4, + }, + BOLD: { + color: '#395296', + fontWeight: 'bold', + }, + ANYCUSTOMSTYLE: { + color: '#00e400', + }, +} + export default class App extends Component { constructor(props) { super(props) @@ -223,23 +298,6 @@ export default class App extends Component { } } -// Custom overrides for each style -const styleMap = { - CODE: { - backgroundColor: 'rgba(0, 0, 0, 0.05)', - fontFamily: '"Inconsolata", "Menlo", "Consolas", monospace', - fontSize: 16, - padding: 4, - }, - BOLD: { - color: '#395296', - fontWeight: 'bold', - }, - ANYCUSTOMSTYLE: { - color: '#00e400', - }, -} - class ToolbarButton extends Component { constructor() { super() @@ -285,61 +343,3 @@ const ToolBar = (props) => {

) } - -const initialData = { - blocks: [ - { - key: '16d0k', - text: 'You can edit this text.', - type: 'unstyled', - depth: 0, - inlineStyleRanges: [{ offset: 0, length: 23, style: 'BOLD' }], - entityRanges: [], - data: {}, - }, - { - key: '98peq', - text: '', - type: 'unstyled', - depth: 0, - inlineStyleRanges: [], - entityRanges: [], - data: {}, - }, - { - key: 'ecmnc', - text: 'Luke Skywalker has vanished. In his absence, the sinister FIRST ORDER has risen from the ashes of the Empire and will not rest until Skywalker, the last Jedi, has been destroyed.', - type: 'unstyled', - depth: 0, - inlineStyleRanges: [ - { offset: 0, length: 14, style: 'BOLD' }, - { offset: 133, length: 9, style: 'BOLD' }, - ], - entityRanges: [], - data: {}, - }, - { - key: 'fe2gn', - text: '', - type: 'unstyled', - depth: 0, - inlineStyleRanges: [], - entityRanges: [], - data: {}, - }, - { - key: '4481k', - text: 'With the support of the REPUBLIC, General Leia Organa leads a brave RESISTANCE. She is desperate to find her brother Luke and gain his help in restoring peace and justice to the galaxy.', - type: 'unstyled', - depth: 0, - inlineStyleRanges: [ - { offset: 34, length: 19, style: 'BOLD' }, - { offset: 117, length: 4, style: 'BOLD' }, - { offset: 68, length: 10, style: 'ANYCUSTOMSTYLE' }, - ], - entityRanges: [], - data: {}, - }, - ], - entityMap: {}, -} diff --git a/examples/with-edgedb/pages/api/post/[id].ts b/examples/with-edgedb/pages/api/post/[id].ts index 32aa946615b929f..a1971a52bc0e3ef 100644 --- a/examples/with-edgedb/pages/api/post/[id].ts +++ b/examples/with-edgedb/pages/api/post/[id].ts @@ -1,23 +1,6 @@ import type { NextApiRequest, NextApiResponse } from 'next' import { client, e } from '../../../client' -export default async function handle( - req: NextApiRequest, - res: NextApiResponse -) { - const postId = req.query.id as string - - if (req.method === 'DELETE') { - res.json(await deletePost(postId)) - } else if (req.method === 'PATCH') { - res.json(await updatePost(postId, req.body)) - } else { - throw new Error( - `The HTTP ${req.method} method is not supported at this route.` - ) - } -} - // PATCH /api/post/:id async function updatePost( postId: string, @@ -42,3 +25,20 @@ async function deletePost(postId: string) { })) .run(client) } + +export default async function handle( + req: NextApiRequest, + res: NextApiResponse +) { + const postId = req.query.id as string + + if (req.method === 'DELETE') { + res.json(await deletePost(postId)) + } else if (req.method === 'PATCH') { + res.json(await updatePost(postId, req.body)) + } else { + throw new Error( + `The HTTP ${req.method} method is not supported at this route.` + ) + } +} diff --git a/examples/with-expo-typescript/pages/index.tsx b/examples/with-expo-typescript/pages/index.tsx index a833b7b6d98ae3f..61d3eba5202f577 100644 --- a/examples/with-expo-typescript/pages/index.tsx +++ b/examples/with-expo-typescript/pages/index.tsx @@ -2,14 +2,6 @@ import React from 'react' import { StyleSheet, Text, View } from 'react-native' -export default function App() { - return ( - - Welcome to Expo + Next.js 👋 - - ) -} - const styles = StyleSheet.create({ container: { flex: 1, @@ -20,3 +12,11 @@ const styles = StyleSheet.create({ fontSize: 16, }, }) + +export default function App() { + return ( + + Welcome to Expo + Next.js 👋 + + ) +} diff --git a/examples/with-expo/pages/index.js b/examples/with-expo/pages/index.js index 7bf300c94990fe3..90b9d18c7086f56 100644 --- a/examples/with-expo/pages/index.js +++ b/examples/with-expo/pages/index.js @@ -1,14 +1,6 @@ // @generated: @expo/next-adapter@2.1.5 import { StyleSheet, Text, View } from 'react-native' -export default function App() { - return ( - - Welcome to Expo + Next.js 👋 - - ) -} - const styles = StyleSheet.create({ container: { flex: 1, @@ -19,3 +11,11 @@ const styles = StyleSheet.create({ fontSize: 16, }, }) + +export default function App() { + return ( + + Welcome to Expo + Next.js 👋 + + ) +} diff --git a/examples/with-fingerprintjs-pro/components/Nav.tsx b/examples/with-fingerprintjs-pro/components/Nav.tsx index 3315c84c36a0edc..0e8ff28a3d78e05 100644 --- a/examples/with-fingerprintjs-pro/components/Nav.tsx +++ b/examples/with-fingerprintjs-pro/components/Nav.tsx @@ -3,15 +3,6 @@ import { useRouter } from 'next/router' import Link from 'next/link' import { RouterProps } from './types' -export const Nav: React.FC = () => { - return ( - - ) -} - const CustomLink: React.FC> = ({ to, children, @@ -35,3 +26,12 @@ const CustomLink: React.FC> = ({ ) } + +export const Nav: React.FC = () => { + return ( + + ) +} diff --git a/examples/with-fingerprintjs-pro/pages/home/[cacheStrategy].tsx b/examples/with-fingerprintjs-pro/pages/home/[cacheStrategy].tsx index 5f67e8eef36b4b8..c9a786a6bcef334 100644 --- a/examples/with-fingerprintjs-pro/pages/home/[cacheStrategy].tsx +++ b/examples/with-fingerprintjs-pro/pages/home/[cacheStrategy].tsx @@ -6,6 +6,14 @@ import { useVisitorData, } from '@fingerprintjs/fingerprintjs-pro-react' +function VisitorDataComponent() { + const { data, isLoading, error } = useVisitorData({ extendedResult: true }) + + return ( + + ) +} + function HomePage() { const { clearCache } = useContext(FpjsContext) @@ -27,12 +35,4 @@ function HomePage() { ) } -function VisitorDataComponent() { - const { data, isLoading, error } = useVisitorData({ extendedResult: true }) - - return ( - - ) -} - export default HomePage diff --git a/examples/with-graphql-hooks/components/submit.js b/examples/with-graphql-hooks/components/submit.js index 71e7fb8395a5ec6..ca69203795bdf11 100644 --- a/examples/with-graphql-hooks/components/submit.js +++ b/examples/with-graphql-hooks/components/submit.js @@ -12,6 +12,22 @@ mutation createPost($title: String!, $url: String!) { } }` +async function handleSubmit(event, onSubmission, createPost) { + event.preventDefault() + const form = event.target + const formData = new window.FormData(form) + const title = formData.get('title') + const url = formData.get('url') + form.reset() + const result = await createPost({ + variables: { + title, + url, + }, + }) + onSubmission && onSubmission(result) +} + export default function Submit({ onSubmission }) { const [createPost, state] = useMutation(CREATE_POST) @@ -38,19 +54,3 @@ export default function Submit({ onSubmission }) { ) } - -async function handleSubmit(event, onSubmission, createPost) { - event.preventDefault() - const form = event.target - const formData = new window.FormData(form) - const title = formData.get('title') - const url = formData.get('url') - form.reset() - const result = await createPost({ - variables: { - title, - url, - }, - }) - onSubmission && onSubmission(result) -} diff --git a/examples/with-iron-session/lib/fetchJson.ts b/examples/with-iron-session/lib/fetchJson.ts index d8402eb865742e1..7165e84b92d3dd5 100644 --- a/examples/with-iron-session/lib/fetchJson.ts +++ b/examples/with-iron-session/lib/fetchJson.ts @@ -1,26 +1,3 @@ -export default async function fetchJson( - input: RequestInfo, - init?: RequestInit -): Promise { - const response = await fetch(input, init) - - // if the server replies, there's always some data in json - // if there's a network error, it will throw at the previous line - const data = await response.json() - - // response.ok is true when res.status is 2xx - // https://developer.mozilla.org/en-US/docs/Web/API/Response/ok - if (response.ok) { - return data - } - - throw new FetchError({ - message: response.statusText, - response, - data, - }) -} - export class FetchError extends Error { response: Response data: { @@ -50,3 +27,26 @@ export class FetchError extends Error { this.data = data ?? { message: message } } } + +export default async function fetchJson( + input: RequestInfo, + init?: RequestInit +): Promise { + const response = await fetch(input, init) + + // if the server replies, there's always some data in json + // if there's a network error, it will throw at the previous line + const data = await response.json() + + // response.ok is true when res.status is 2xx + // https://developer.mozilla.org/en-US/docs/Web/API/Response/ok + if (response.ok) { + return data + } + + throw new FetchError({ + message: response.statusText, + response, + data, + }) +} diff --git a/examples/with-iron-session/pages/api/events.ts b/examples/with-iron-session/pages/api/events.ts index d946088e9277af1..91ed59a494713e2 100644 --- a/examples/with-iron-session/pages/api/events.ts +++ b/examples/with-iron-session/pages/api/events.ts @@ -10,8 +10,6 @@ export type Events = const octokit = new Octokit() -export default withIronSessionApiRoute(eventsRoute, sessionOptions) - async function eventsRoute(req: NextApiRequest, res: NextApiResponse) { const user = req.session.user @@ -31,3 +29,5 @@ async function eventsRoute(req: NextApiRequest, res: NextApiResponse) { res.status(200).json([]) } } + +export default withIronSessionApiRoute(eventsRoute, sessionOptions) diff --git a/examples/with-iron-session/pages/api/login.ts b/examples/with-iron-session/pages/api/login.ts index 9fa2e84cf3e1e0d..c9f094ae429d782 100644 --- a/examples/with-iron-session/pages/api/login.ts +++ b/examples/with-iron-session/pages/api/login.ts @@ -6,8 +6,6 @@ import { sessionOptions } from 'lib/session' import { NextApiRequest, NextApiResponse } from 'next' const octokit = new Octokit() -export default withIronSessionApiRoute(loginRoute, sessionOptions) - async function loginRoute(req: NextApiRequest, res: NextApiResponse) { const { username } = await req.body @@ -24,3 +22,5 @@ async function loginRoute(req: NextApiRequest, res: NextApiResponse) { res.status(500).json({ message: (error as Error).message }) } } + +export default withIronSessionApiRoute(loginRoute, sessionOptions) diff --git a/examples/with-iron-session/pages/api/logout.ts b/examples/with-iron-session/pages/api/logout.ts index cbbfb6a9ee210d8..e081136289bca1a 100644 --- a/examples/with-iron-session/pages/api/logout.ts +++ b/examples/with-iron-session/pages/api/logout.ts @@ -3,9 +3,9 @@ import { sessionOptions } from 'lib/session' import { NextApiRequest, NextApiResponse } from 'next' import type { User } from 'pages/api/user' -export default withIronSessionApiRoute(logoutRoute, sessionOptions) - function logoutRoute(req: NextApiRequest, res: NextApiResponse) { req.session.destroy() res.json({ isLoggedIn: false, login: '', avatarUrl: '' }) } + +export default withIronSessionApiRoute(logoutRoute, sessionOptions) diff --git a/examples/with-iron-session/pages/api/user.ts b/examples/with-iron-session/pages/api/user.ts index 7de3d3b2199942b..e7249a427e02282 100644 --- a/examples/with-iron-session/pages/api/user.ts +++ b/examples/with-iron-session/pages/api/user.ts @@ -8,8 +8,6 @@ export type User = { avatarUrl: string } -export default withIronSessionApiRoute(userRoute, sessionOptions) - async function userRoute(req: NextApiRequest, res: NextApiResponse) { if (req.session.user) { // in a real world application you might read the user id from the session and then do a database request @@ -26,3 +24,5 @@ async function userRoute(req: NextApiRequest, res: NextApiResponse) { }) } } + +export default withIronSessionApiRoute(userRoute, sessionOptions) diff --git a/examples/with-mobx-react-lite/components/StoreProvider.js b/examples/with-mobx-react-lite/components/StoreProvider.js index 9f8cdd1be49249f..8484b5a3eb9147d 100644 --- a/examples/with-mobx-react-lite/components/StoreProvider.js +++ b/examples/with-mobx-react-lite/components/StoreProvider.js @@ -13,12 +13,6 @@ export function useStore() { return context } -export function StoreProvider({ children, initialState: initialData }) { - const store = initializeStore(initialData) - - return {children} -} - function initializeStore(initialData = null) { const _store = store ?? new Store() @@ -34,3 +28,9 @@ function initializeStore(initialData = null) { return _store } + +export function StoreProvider({ children, initialState: initialData }) { + const store = initializeStore(initialData) + + return {children} +} diff --git a/examples/with-mobx-state-tree-typescript/components/Clock.tsx b/examples/with-mobx-state-tree-typescript/components/Clock.tsx index ddf2dd6c51878f9..19ea8d84f6681e4 100644 --- a/examples/with-mobx-state-tree-typescript/components/Clock.tsx +++ b/examples/with-mobx-state-tree-typescript/components/Clock.tsx @@ -1,6 +1,6 @@ +const pad = (n) => (n < 10 ? `0${n}` : n) const format = (t) => `${pad(t.getUTCHours())}:${pad(t.getUTCMinutes())}:${pad(t.getUTCSeconds())}` -const pad = (n) => (n < 10 ? `0${n}` : n) const Clock = (props) => { const divStyle = { diff --git a/examples/with-mongodb-mongoose/components/Form.js b/examples/with-mongodb-mongoose/components/Form.js index 968df84a86ba6df..03b754e7934c4c3 100644 --- a/examples/with-mongodb-mongoose/components/Form.js +++ b/examples/with-mongodb-mongoose/components/Form.js @@ -83,16 +83,6 @@ const Form = ({ formId, petForm, forNewPet = true }) => { }) } - const handleSubmit = (e) => { - e.preventDefault() - const errs = formValidate() - if (Object.keys(errs).length === 0) { - forNewPet ? postData(form) : putData(form) - } else { - setErrors({ errs }) - } - } - /* Makes sure pet info is filled for pet name, owner name, species, and image url*/ const formValidate = () => { let err = {} @@ -103,6 +93,16 @@ const Form = ({ formId, petForm, forNewPet = true }) => { return err } + const handleSubmit = (e) => { + e.preventDefault() + const errs = formValidate() + if (Object.keys(errs).length === 0) { + forNewPet ? postData(form) : putData(form) + } else { + setErrors({ errs }) + } + } + return ( <>
diff --git a/examples/with-react-native-web/pages/alternate.js b/examples/with-react-native-web/pages/alternate.js index e8fc16ec8b693da..1184ce94f2e6c92 100644 --- a/examples/with-react-native-web/pages/alternate.js +++ b/examples/with-react-native-web/pages/alternate.js @@ -1,19 +1,5 @@ import { StyleSheet, Text, View } from 'react-native' -export default function Alternate() { - return ( - - - Alternate Page - - - - Go Back - - - ) -} - const styles = StyleSheet.create({ container: { alignItems: 'center', @@ -29,3 +15,17 @@ const styles = StyleSheet.create({ color: 'blue', }, }) + +export default function Alternate() { + return ( + + + Alternate Page + + + + Go Back + + + ) +} diff --git a/examples/with-react-native-web/pages/index.js b/examples/with-react-native-web/pages/index.js index cac53ebcd5e7469..3fa857406993522 100644 --- a/examples/with-react-native-web/pages/index.js +++ b/examples/with-react-native-web/pages/index.js @@ -1,25 +1,5 @@ import { StyleSheet, Text, View } from 'react-native' -export default function App(props) { - return ( - - - React Native for Web & Next.js - - - - A universal link - - - - - Subheader - - - - ) -} - const styles = StyleSheet.create({ container: { alignItems: 'center', @@ -39,3 +19,23 @@ const styles = StyleSheet.create({ marginBottom: 24, }, }) + +export default function App(props) { + return ( + + + React Native for Web & Next.js + + + + A universal link + + + + + Subheader + + + + ) +} diff --git a/examples/with-redis/pages/api/subscribe.ts b/examples/with-redis/pages/api/subscribe.ts index 544558c36df309f..6f3bac267bd78fb 100644 --- a/examples/with-redis/pages/api/subscribe.ts +++ b/examples/with-redis/pages/api/subscribe.ts @@ -2,6 +2,12 @@ import type { NextApiRequest, NextApiResponse } from 'next' import redis from '../../lib/redis' +function validateEmail(email: string) { + const re = + /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/ + return re.test(String(email).toLowerCase()) +} + export default async function subscribe( req: NextApiRequest, res: NextApiResponse @@ -19,9 +25,3 @@ export default async function subscribe( }) } } - -function validateEmail(email: string) { - const re = - /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/ - return re.test(String(email).toLowerCase()) -} diff --git a/examples/with-redux-wrapper/components/Clock.js b/examples/with-redux-wrapper/components/Clock.js index 6b13e9ebb4d65ab..57523ec179271c6 100644 --- a/examples/with-redux-wrapper/components/Clock.js +++ b/examples/with-redux-wrapper/components/Clock.js @@ -1,3 +1,7 @@ +const pad = (n) => (n < 10 ? `0${n}` : n) +const format = (t) => + `${pad(t.getUTCHours())}:${pad(t.getUTCMinutes())}:${pad(t.getUTCSeconds())}` + export default function Clock({ lastUpdate, light }) { return (
@@ -18,8 +22,3 @@ export default function Clock({ lastUpdate, light }) {
) } - -const format = (t) => - `${pad(t.getUTCHours())}:${pad(t.getUTCMinutes())}:${pad(t.getUTCSeconds())}` - -const pad = (n) => (n < 10 ? `0${n}` : n) diff --git a/lerna.json b/lerna.json index 3f4eb458a73791b..f757484610e5da7 100644 --- a/lerna.json +++ b/lerna.json @@ -16,5 +16,5 @@ "registry": "https://registry.npmjs.org/" } }, - "version": "12.2.5-canary.0" + "version": "12.2.5-canary.3" } diff --git a/package.json b/package.json index efb10eda64d7435..68e71f55f5dcfc9 100644 --- a/package.json +++ b/package.json @@ -56,7 +56,7 @@ "@babel/plugin-proposal-object-rest-spread": "7.14.7", "@babel/preset-flow": "7.14.5", "@babel/preset-react": "7.14.5", - "@edge-runtime/jest-environment": "1.1.0-beta.25", + "@edge-runtime/jest-environment": "1.1.0-beta.26", "@fullhuman/postcss-purgecss": "1.3.0", "@mdx-js/loader": "0.18.0", "@next/bundle-analyzer": "workspace:*", diff --git a/packages/create-next-app/package.json b/packages/create-next-app/package.json index 345c23d29fbc75b..0c1d13e7f089e2d 100644 --- a/packages/create-next-app/package.json +++ b/packages/create-next-app/package.json @@ -1,6 +1,6 @@ { "name": "create-next-app", - "version": "12.2.5-canary.0", + "version": "12.2.5-canary.3", "keywords": [ "react", "next", diff --git a/packages/eslint-config-next/package.json b/packages/eslint-config-next/package.json index 1b5c9c445c0c69c..f7883b455381d76 100644 --- a/packages/eslint-config-next/package.json +++ b/packages/eslint-config-next/package.json @@ -1,6 +1,6 @@ { "name": "eslint-config-next", - "version": "12.2.5-canary.0", + "version": "12.2.5-canary.3", "description": "ESLint configuration used by NextJS.", "main": "index.js", "license": "MIT", @@ -9,7 +9,7 @@ "directory": "packages/eslint-config-next" }, "dependencies": { - "@next/eslint-plugin-next": "12.2.5-canary.0", + "@next/eslint-plugin-next": "12.2.5-canary.3", "@rushstack/eslint-patch": "^1.1.3", "@typescript-eslint/parser": "^5.21.0", "eslint-import-resolver-node": "^0.3.6", diff --git a/packages/eslint-plugin-next/package.json b/packages/eslint-plugin-next/package.json index 07e46332e89a4b4..e9fbe0177c7f57d 100644 --- a/packages/eslint-plugin-next/package.json +++ b/packages/eslint-plugin-next/package.json @@ -1,6 +1,6 @@ { "name": "@next/eslint-plugin-next", - "version": "12.2.5-canary.0", + "version": "12.2.5-canary.3", "description": "ESLint plugin for NextJS.", "main": "lib/index.js", "license": "MIT", diff --git a/packages/next-bundle-analyzer/package.json b/packages/next-bundle-analyzer/package.json index 4104cce6aab46be..3bad9304c7856c9 100644 --- a/packages/next-bundle-analyzer/package.json +++ b/packages/next-bundle-analyzer/package.json @@ -1,6 +1,6 @@ { "name": "@next/bundle-analyzer", - "version": "12.2.5-canary.0", + "version": "12.2.5-canary.3", "main": "index.js", "types": "index.d.ts", "license": "MIT", diff --git a/packages/next-codemod/package.json b/packages/next-codemod/package.json index 74650e06108e610..a7f4123da235620 100644 --- a/packages/next-codemod/package.json +++ b/packages/next-codemod/package.json @@ -1,6 +1,6 @@ { "name": "@next/codemod", - "version": "12.2.5-canary.0", + "version": "12.2.5-canary.3", "license": "MIT", "dependencies": { "chalk": "4.1.0", diff --git a/packages/next-env/index.ts b/packages/next-env/index.ts index 97df9677629daee..f8de0069cbce766 100644 --- a/packages/next-env/index.ts +++ b/packages/next-env/index.ts @@ -4,12 +4,13 @@ import * as path from 'path' import * as dotenv from 'dotenv' import { expand as dotenvExpand } from 'dotenv-expand' -export type Env = { [key: string]: string } +export type Env = { [key: string]: string | undefined } export type LoadedEnvFiles = Array<{ path: string contents: string }> +let initialEnv: Env | undefined = undefined let combinedEnv: Env | undefined = undefined let cachedLoadedEnvFiles: LoadedEnvFiles = [] @@ -21,18 +22,24 @@ type Log = { export function processEnv( loadedEnvFiles: LoadedEnvFiles, dir?: string, - log: Log = console + log: Log = console, + forceReload = false ) { - // don't reload env if we already have since this breaks escaped - // environment values e.g. \$ENV_FILE_KEY - if (process.env.__NEXT_PROCESSED_ENV || loadedEnvFiles.length === 0) { + if (!initialEnv) { + initialEnv = Object.assign({}, process.env) + } + // only reload env when forceReload is specified + if ( + !forceReload && + (process.env.__NEXT_PROCESSED_ENV || loadedEnvFiles.length === 0) + ) { return process.env as Env } // flag that we processed the environment values in case a serverless // function is re-used or we are running in `next start` mode process.env.__NEXT_PROCESSED_ENV = 'true' - const origEnv = Object.assign({}, process.env) + const origEnv = Object.assign({}, initialEnv) const parsed: dotenv.DotenvParseOutput = {} for (const envFile of loadedEnvFiles) { @@ -61,21 +68,27 @@ export function processEnv( ) } } - return Object.assign(process.env, parsed) } export function loadEnvConfig( dir: string, dev?: boolean, - log: Log = console + log: Log = console, + forceReload = false ): { combinedEnv: Env loadedEnvFiles: LoadedEnvFiles } { - // don't reload env if we already have since this breaks escaped - // environment values e.g. \$ENV_FILE_KEY - if (combinedEnv) return { combinedEnv, loadedEnvFiles: cachedLoadedEnvFiles } + if (!initialEnv) { + initialEnv = Object.assign({}, process.env) + } + // only reload env when forceReload is specified + if (combinedEnv && !forceReload) { + return { combinedEnv, loadedEnvFiles: cachedLoadedEnvFiles } + } + process.env = Object.assign({}, initialEnv) + cachedLoadedEnvFiles = [] const isTest = process.env.NODE_ENV === 'test' const mode = isTest ? 'test' : dev ? 'development' : 'production' @@ -112,6 +125,6 @@ export function loadEnvConfig( } } } - combinedEnv = processEnv(cachedLoadedEnvFiles, dir, log) + combinedEnv = processEnv(cachedLoadedEnvFiles, dir, log, forceReload) return { combinedEnv, loadedEnvFiles: cachedLoadedEnvFiles } } diff --git a/packages/next-env/package.json b/packages/next-env/package.json index 67df87ca4735254..020b1cdc17b6a57 100644 --- a/packages/next-env/package.json +++ b/packages/next-env/package.json @@ -1,6 +1,6 @@ { "name": "@next/env", - "version": "12.2.5-canary.0", + "version": "12.2.5-canary.3", "keywords": [ "react", "next", diff --git a/packages/next-mdx/package.json b/packages/next-mdx/package.json index e7d78add28948c4..a7cbf46afaf8645 100644 --- a/packages/next-mdx/package.json +++ b/packages/next-mdx/package.json @@ -1,6 +1,6 @@ { "name": "@next/mdx", - "version": "12.2.5-canary.0", + "version": "12.2.5-canary.3", "main": "index.js", "license": "MIT", "repository": { diff --git a/packages/next-plugin-storybook/package.json b/packages/next-plugin-storybook/package.json index b8c5a238ace18b4..a8a75db929fcfe1 100644 --- a/packages/next-plugin-storybook/package.json +++ b/packages/next-plugin-storybook/package.json @@ -1,6 +1,6 @@ { "name": "@next/plugin-storybook", - "version": "12.2.5-canary.0", + "version": "12.2.5-canary.3", "repository": { "url": "vercel/next.js", "directory": "packages/next-plugin-storybook" diff --git a/packages/next-polyfill-module/package.json b/packages/next-polyfill-module/package.json index 4aeeca2c75143c9..30bfcc9ece1cf93 100644 --- a/packages/next-polyfill-module/package.json +++ b/packages/next-polyfill-module/package.json @@ -1,6 +1,6 @@ { "name": "@next/polyfill-module", - "version": "12.2.5-canary.0", + "version": "12.2.5-canary.3", "description": "A standard library polyfill for ES Modules supporting browsers (Edge 16+, Firefox 60+, Chrome 61+, Safari 10.1+)", "main": "dist/polyfill-module.js", "license": "MIT", diff --git a/packages/next-polyfill-nomodule/package.json b/packages/next-polyfill-nomodule/package.json index 921d8a19d6daa46..7273c3b56bb4d1a 100644 --- a/packages/next-polyfill-nomodule/package.json +++ b/packages/next-polyfill-nomodule/package.json @@ -1,6 +1,6 @@ { "name": "@next/polyfill-nomodule", - "version": "12.2.5-canary.0", + "version": "12.2.5-canary.3", "description": "A polyfill for non-dead, nomodule browsers.", "main": "dist/polyfill-nomodule.js", "license": "MIT", diff --git a/packages/next-swc/Cargo.lock b/packages/next-swc/Cargo.lock index 73033e3bdd844b8..18b43ccfe5c1f31 100644 --- a/packages/next-swc/Cargo.lock +++ b/packages/next-swc/Cargo.lock @@ -343,6 +343,12 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" +[[package]] +name = "convert_case" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb4a24b1aaf0fd0ce8b45161144d6f42cd91677fd5940fd431183eb023b3a2b8" + [[package]] name = "core-foundation" version = "0.9.3" @@ -1477,6 +1483,7 @@ dependencies = [ name = "modularize_imports" version = "0.14.0" dependencies = [ + "convert_case", "handlebars", "once_cell", "regex", diff --git a/packages/next-swc/crates/modularize_imports/Cargo.toml b/packages/next-swc/crates/modularize_imports/Cargo.toml index 5a4617984f7b92f..209795773e17ed3 100644 --- a/packages/next-swc/crates/modularize_imports/Cargo.toml +++ b/packages/next-swc/crates/modularize_imports/Cargo.toml @@ -10,6 +10,7 @@ version = "0.14.0" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +convert_case = "0.5.0" handlebars = "4.2.1" once_cell = "1.13.0" regex = "1.5" diff --git a/packages/next-swc/crates/modularize_imports/src/lib.rs b/packages/next-swc/crates/modularize_imports/src/lib.rs index 5322f9d872d2cc7..f76a78e2df6279f 100644 --- a/packages/next-swc/crates/modularize_imports/src/lib.rs +++ b/packages/next-swc/crates/modularize_imports/src/lib.rs @@ -1,6 +1,6 @@ -use std::borrow::Cow; use std::collections::HashMap; +use convert_case::{Case, Casing}; use handlebars::{Context, Handlebars, Helper, HelperResult, Output, RenderContext}; use once_cell::sync::Lazy; use regex::{Captures, Regex}; @@ -175,6 +175,9 @@ pub fn modularize_imports(config: Config) -> impl Fold { folder .renderer .register_helper("camelCase", Box::new(helper_camel_case)); + folder + .renderer + .register_helper("kebabCase", Box::new(helper_kebab_case)); for (mut k, v) in config.packages { // XXX: Should we keep this hack? if !k.starts_with('^') && !k.ends_with('$') { @@ -223,13 +226,21 @@ fn helper_camel_case( ) -> HelperResult { // get parameter from helper or throw an error let param = h.param(0).and_then(|v| v.value().as_str()).unwrap_or(""); - let value = if param.is_empty() || param.chars().next().unwrap().is_lowercase() { - Cow::Borrowed(param) - } else { - let mut it = param.chars(); - let fst = it.next().unwrap(); - Cow::Owned(fst.to_lowercase().chain(it).collect::()) - }; - out.write(value.as_ref())?; + + out.write(param.to_case(Case::Camel).as_ref())?; + Ok(()) +} + +fn helper_kebab_case( + h: &Helper<'_, '_>, + _: &Handlebars<'_>, + _: &Context, + _: &mut RenderContext<'_, '_>, + out: &mut dyn Output, +) -> HelperResult { + // get parameter from helper or throw an error + let param = h.param(0).and_then(|v| v.value().as_str()).unwrap_or(""); + + out.write(param.to_case(Case::Kebab).as_ref())?; Ok(()) } diff --git a/packages/next-swc/crates/modularize_imports/tests/fixture.rs b/packages/next-swc/crates/modularize_imports/tests/fixture.rs index 4d1c06aa5161a7f..7aeb9e20fef4559 100644 --- a/packages/next-swc/crates/modularize_imports/tests/fixture.rs +++ b/packages/next-swc/crates/modularize_imports/tests/fixture.rs @@ -44,6 +44,14 @@ fn modularize_imports_fixture(input: PathBuf) { skip_default_conversion: true, }, ), + ( + "my-library-3".to_string(), + PackageConfig { + transform: "my-library-3/{{ kebabCase member }}".into(), + prevent_full_import: false, + skip_default_conversion: true, + }, + ), ] .into_iter() .collect(), diff --git a/packages/next-swc/crates/modularize_imports/tests/fixture/simple/input.js b/packages/next-swc/crates/modularize_imports/tests/fixture/simple/input.js index f152bdc8f19ddb0..5b6c7a587c69be0 100644 --- a/packages/next-swc/crates/modularize_imports/tests/fixture/simple/input.js +++ b/packages/next-swc/crates/modularize_imports/tests/fixture/simple/input.js @@ -1,2 +1,3 @@ import { Grid, Row, Col as Col1 } from 'react-bootstrap'; import { MyModule, Widget } from 'my-library-2'; +import { MyModule } from 'my-library-3'; diff --git a/packages/next-swc/crates/modularize_imports/tests/fixture/simple/output.js b/packages/next-swc/crates/modularize_imports/tests/fixture/simple/output.js index 899d45bc9ec017e..ba5cdf371405a7b 100644 --- a/packages/next-swc/crates/modularize_imports/tests/fixture/simple/output.js +++ b/packages/next-swc/crates/modularize_imports/tests/fixture/simple/output.js @@ -3,3 +3,4 @@ import Row from "react-bootstrap/lib/Row"; import Col1 from "react-bootstrap/lib/Col"; import { MyModule } from "my-library-2/myModule"; import { Widget } from "my-library-2/widget"; +import { MyModule } from "my-library-3/my-module"; diff --git a/packages/next-swc/package.json b/packages/next-swc/package.json index 112d219bb68dbf0..764760abb19e63f 100644 --- a/packages/next-swc/package.json +++ b/packages/next-swc/package.json @@ -1,6 +1,6 @@ { "name": "@next/swc", - "version": "12.2.5-canary.0", + "version": "12.2.5-canary.3", "private": true, "scripts": { "build-native": "napi build --platform -p next-swc-napi --cargo-name next_swc_napi native --features plugin", diff --git a/packages/next/build/analysis/get-page-static-info.ts b/packages/next/build/analysis/get-page-static-info.ts index d27ef9057b1b33d..af30649b7edc991 100644 --- a/packages/next/build/analysis/get-page-static-info.ts +++ b/packages/next/build/analysis/get-page-static-info.ts @@ -4,7 +4,6 @@ import { extractExportedConstValue, UnsupportedValueError, } from './extract-const-value' -import { escapeStringRegexp } from '../../shared/lib/escape-regexp' import { parseModule } from './parse-module' import { promises as fs } from 'fs' import { tryToParsePath } from '../../lib/try-to-parse-path' @@ -185,6 +184,7 @@ function getMiddlewareRegExpStrings( getMiddlewareRegExpStrings(matcher, nextConfig) ) } + const { i18n } = nextConfig if (typeof matcherOrMatchers !== 'string') { throw new Error( @@ -197,20 +197,23 @@ function getMiddlewareRegExpStrings( if (!matcher.startsWith('/')) { throw new Error('`matcher`: path matcher must start with /') } + const isRoot = matcher === '/' - if (nextConfig.i18n?.locales) { - matcher = `/:nextInternalLocale(${nextConfig.i18n.locales - .map((locale) => escapeStringRegexp(locale)) - .join('|')})${ - matcher === '/' && !nextConfig.trailingSlash ? '' : matcher - }` + if (i18n?.locales) { + matcher = `/:nextInternalLocale([^/.]{1,})${isRoot ? '' : matcher}` } + matcher = `/:nextData(_next/data/[^/]{1,})?${matcher}${ + isRoot + ? `(${nextConfig.i18n ? '|\\.json|' : ''}/?index|/?index\\.json)?` + : '(.json)?' + }` + if (nextConfig.basePath) { - matcher = `${nextConfig.basePath}${matcher === '/' ? '' : matcher}` + matcher = `${nextConfig.basePath}${matcher}` } - const parsedPage = tryToParsePath(matcher) + if (parsedPage.error) { throw new Error(`Invalid path matcher: ${matcher}`) } diff --git a/packages/next/build/entries.ts b/packages/next/build/entries.ts index 5d757be91e85ddf..78d94e230ded6a4 100644 --- a/packages/next/build/entries.ts +++ b/packages/next/build/entries.ts @@ -19,7 +19,7 @@ import { import { CLIENT_STATIC_FILES_RUNTIME_AMP, CLIENT_STATIC_FILES_RUNTIME_MAIN, - CLIENT_STATIC_FILES_RUNTIME_MAIN_ROOT, + CLIENT_STATIC_FILES_RUNTIME_MAIN_APP, CLIENT_STATIC_FILES_RUNTIME_REACT_REFRESH, EDGE_RUNTIME_WEBPACK, } from '../shared/lib/constants' @@ -506,14 +506,14 @@ export function finalizeEntrypoint({ // Client special cases name !== 'polyfills' && name !== CLIENT_STATIC_FILES_RUNTIME_MAIN && - name !== CLIENT_STATIC_FILES_RUNTIME_MAIN_ROOT && + name !== CLIENT_STATIC_FILES_RUNTIME_MAIN_APP && name !== CLIENT_STATIC_FILES_RUNTIME_AMP && name !== CLIENT_STATIC_FILES_RUNTIME_REACT_REFRESH ) { // TODO-APP: this is a temporary fix. @shuding is going to change the handling of server components if (appDir && entry.import.includes('flight')) { return { - dependOn: CLIENT_STATIC_FILES_RUNTIME_MAIN_ROOT, + dependOn: CLIENT_STATIC_FILES_RUNTIME_MAIN_APP, ...entry, } } diff --git a/packages/next/build/index.ts b/packages/next/build/index.ts index 0db6f54f88d348c..2d942a40070f1d0 100644 --- a/packages/next/build/index.ts +++ b/packages/next/build/index.ts @@ -24,13 +24,12 @@ import { fileExists } from '../lib/file-exists' import { findPagesDir } from '../lib/find-pages-dir' import loadCustomRoutes, { CustomRoutes, - getRedirectStatus, - modifyRouteRegex, normalizeRouteRegex, Redirect, Rewrite, RouteType, } from '../lib/load-custom-routes' +import { getRedirectStatus, modifyRouteRegex } from '../lib/redirect-status' import { nonNullable } from '../lib/non-nullable' import { recursiveDelete } from '../lib/recursive-delete' import { verifyAndLint } from '../lib/verifyAndLint' @@ -56,6 +55,7 @@ import { MIDDLEWARE_MANIFEST, APP_PATHS_MANIFEST, APP_PATH_ROUTES_MANIFEST, + APP_BUILD_MANIFEST, } from '../shared/lib/constants' import { getSortedRoutes, isDynamicRoute } from '../shared/lib/router/utils' import { __ApiPreviewProps } from '../server/api-utils' @@ -117,6 +117,7 @@ import { flatReaddir } from '../lib/flat-readdir' import { RemotePattern } from '../shared/lib/image-config' import { eventSwcPlugins } from '../telemetry/events/swc-plugins' import { normalizeAppPath } from '../shared/lib/router/utils/app-paths' +import { AppBuildManifest } from './webpack/plugins/app-build-manifest-plugin' export type SsgRoute = { initialRevalidateSeconds: number | false @@ -334,7 +335,7 @@ export default async function build( const isLikeServerless = isTargetLikeServerless(target) - const pagePaths = await nextBuildSpan + const pagesPaths = await nextBuildSpan .traceChild('collect-pages') .traceAsyncFn(() => recursiveReadDir( @@ -384,14 +385,14 @@ export default async function build( isDev: false, pageExtensions: config.pageExtensions, pagesType: 'pages', - pagePaths: pagePaths, + pagePaths: pagesPaths, }) ) - let mappedAppPaths: { [page: string]: string } | undefined + let mappedAppPages: { [page: string]: string } | undefined if (appPaths && appDir) { - mappedAppPaths = nextBuildSpan + mappedAppPages = nextBuildSpan .traceChild('create-app-mapping') .traceFn(() => createPagesMapping({ @@ -430,12 +431,18 @@ export default async function build( rootDir: dir, rootPaths: mappedRootPaths, appDir, - appPaths: mappedAppPaths, + appPaths: mappedAppPages, pageExtensions: config.pageExtensions, }) ) - const pageKeys = Object.keys(mappedPages) + const pageKeys = { + pages: Object.keys(mappedPages), + app: mappedAppPages + ? Object.keys(mappedAppPages).map((key) => normalizeAppPath(key)) + : undefined, + } + const conflictingPublicFiles: string[] = [] const hasPages404 = mappedPages['/404']?.startsWith(PAGES_DIR_ALIAS) const hasCustomErrorPage = @@ -478,7 +485,7 @@ export default async function build( } }) - const nestedReservedPages = pageKeys.filter((page) => { + const nestedReservedPages = pageKeys.pages.filter((page) => { return ( page.match(/\/(_app|_document|_error)$/) && path.dirname(page) !== '/' ) @@ -579,10 +586,8 @@ export default async function build( } } = nextBuildSpan.traceChild('generate-routes-manifest').traceFn(() => { const sortedRoutes = getSortedRoutes([ - ...pageKeys, - ...Object.keys(mappedAppPaths || {}).map((key) => - normalizeAppPath(key) - ), + ...pageKeys.pages, + ...(pageKeys.app ?? []), ]) const dynamicRoutes: Array> = [] const staticRoutes: typeof dynamicRoutes = [] @@ -912,7 +917,7 @@ export default async function build( throw err } else { telemetry.record( - eventBuildCompleted(pagePaths, { + eventBuildCompleted(pagesPaths, { durationInSeconds: webpackBuildEnd[0], }) ) @@ -931,6 +936,7 @@ export default async function build( }) const buildManifestPath = path.join(distDir, BUILD_MANIFEST) + const appBuildManifestPath = path.join(distDir, APP_BUILD_MANIFEST) const ssgPages = new Set() const ssgStaticFallbackPages = new Set() @@ -950,6 +956,11 @@ export default async function build( const buildManifest = JSON.parse( await promises.readFile(buildManifestPath, 'utf8') ) as BuildManifest + const appBuildManifest = appDir + ? (JSON.parse( + await promises.readFile(appBuildManifestPath, 'utf8') + ) as AppBuildManifest) + : undefined const timeout = config.staticPageGenerationTimeout || 0 const sharedPool = config.experimental.sharedPool || false @@ -1080,188 +1091,222 @@ export default async function build( let hasSsrAmpPages = false const computedManifestData = await computeFromManifest( - buildManifest, + { build: buildManifest, app: appBuildManifest }, distDir, config.experimental.gzipSize ) await Promise.all( - pageKeys.map((page) => { - const checkPageSpan = staticCheckSpan.traceChild('check-page', { - page, - }) - return checkPageSpan.traceAsyncFn(async () => { - const actualPage = normalizePagePath(page) - const [selfSize, allSize] = await getJsPageSizeInKb( - actualPage, - distDir, - buildManifest, - config.experimental.gzipSize, - computedManifestData - ) + Object.entries(pageKeys) + .reduce>( + (acc, [key, files]) => { + if (!files) { + return acc + } - let isSsg = false - let isStatic = false - let isServerComponent = false - let isHybridAmp = false - let ssgPageRoutes: string[] | null = null + const pageType = key as keyof typeof pageKeys - const pagePath = pagePaths.find( - (p) => - p.startsWith(actualPage + '.') || - p.startsWith(actualPage + '/index.') - ) + for (const page of files) { + acc.push({ pageType, page }) + } - const pageRuntime = pagePath - ? ( - await getPageStaticInfo({ - pageFilePath: join(pagesDir, pagePath), - nextConfig: config, - }) - ).runtime - : undefined + return acc + }, + [] + ) + .map(({ pageType, page }) => { + const checkPageSpan = staticCheckSpan.traceChild('check-page', { + page, + }) + return checkPageSpan.traceAsyncFn(async () => { + const actualPage = normalizePagePath(page) + const [selfSize, allSize] = await getJsPageSizeInKb( + pageType, + actualPage, + distDir, + buildManifest, + appBuildManifest, + config.experimental.gzipSize, + computedManifestData + ) - if (hasServerComponents && pagePath) { - if (isServerComponentPage(config, pagePath)) { - isServerComponent = true + let isSsg = false + let isStatic = false + let isServerComponent = false + let isHybridAmp = false + let ssgPageRoutes: string[] | null = null + + const pagePath = + pageType === 'pages' + ? pagesPaths.find( + (p) => + p.startsWith(actualPage + '.') || + p.startsWith(actualPage + '/index.') + ) + : appPaths?.find((p) => p.startsWith(actualPage + '/page.')) + + const pageRuntime = + pageType === 'pages' && pagePath + ? ( + await getPageStaticInfo({ + pageFilePath: join(pagesDir, pagePath), + nextConfig: config, + }) + ).runtime + : undefined + + if (hasServerComponents && pagePath) { + if (isServerComponentPage(config, pagePath)) { + isServerComponent = true + } } - } - if ( - !isReservedPage(page) && - // We currently don't support static optimization in the Edge runtime. - pageRuntime !== SERVER_RUNTIME.edge - ) { - try { - let isPageStaticSpan = - checkPageSpan.traceChild('is-page-static') - let workerResult = await isPageStaticSpan.traceAsyncFn(() => { - return staticWorkers.isPageStatic( - page, - distDir, - isLikeServerless, - configFileName, - runtimeEnvConfig, - config.httpAgentOptions, - config.i18n?.locales, - config.i18n?.defaultLocale, - isPageStaticSpan.id + if ( + // Only calculate page static information if the page is not an + // app page. + pageType !== 'app' && + !isReservedPage(page) && + // We currently don't support static optimization in the Edge runtime. + pageRuntime !== SERVER_RUNTIME.edge + ) { + try { + let isPageStaticSpan = + checkPageSpan.traceChild('is-page-static') + let workerResult = await isPageStaticSpan.traceAsyncFn( + () => { + return staticWorkers.isPageStatic( + page, + distDir, + isLikeServerless, + configFileName, + runtimeEnvConfig, + config.httpAgentOptions, + config.i18n?.locales, + config.i18n?.defaultLocale, + isPageStaticSpan.id + ) + } ) - }) - if (config.outputFileTracing) { - pageTraceIncludes.set( - page, - workerResult.traceIncludes || [] - ) - pageTraceExcludes.set( - page, - workerResult.traceExcludes || [] - ) - } + if (config.outputFileTracing) { + pageTraceIncludes.set( + page, + workerResult.traceIncludes || [] + ) + pageTraceExcludes.set( + page, + workerResult.traceExcludes || [] + ) + } - if ( - workerResult.isStatic === false && - (workerResult.isHybridAmp || workerResult.isAmpOnly) - ) { - hasSsrAmpPages = true - } + if ( + workerResult.isStatic === false && + (workerResult.isHybridAmp || workerResult.isAmpOnly) + ) { + hasSsrAmpPages = true + } - if (workerResult.isHybridAmp) { - isHybridAmp = true - hybridAmpPages.add(page) - } + if (workerResult.isHybridAmp) { + isHybridAmp = true + hybridAmpPages.add(page) + } - if (workerResult.isNextImageImported) { - isNextImageImported = true - } + if (workerResult.isNextImageImported) { + isNextImageImported = true + } - if (workerResult.hasStaticProps) { - ssgPages.add(page) - isSsg = true + if (workerResult.hasStaticProps) { + ssgPages.add(page) + isSsg = true - if ( - workerResult.prerenderRoutes && - workerResult.encodedPrerenderRoutes - ) { - additionalSsgPaths.set(page, workerResult.prerenderRoutes) - additionalSsgPathsEncoded.set( - page, + if ( + workerResult.prerenderRoutes && workerResult.encodedPrerenderRoutes - ) - ssgPageRoutes = workerResult.prerenderRoutes + ) { + additionalSsgPaths.set( + page, + workerResult.prerenderRoutes + ) + additionalSsgPathsEncoded.set( + page, + workerResult.encodedPrerenderRoutes + ) + ssgPageRoutes = workerResult.prerenderRoutes + } + + if (workerResult.prerenderFallback === 'blocking') { + ssgBlockingFallbackPages.add(page) + } else if (workerResult.prerenderFallback === true) { + ssgStaticFallbackPages.add(page) + } + } else if (workerResult.hasServerProps) { + serverPropsPages.add(page) + } else if ( + workerResult.isStatic && + !isServerComponent && + (await customAppGetInitialPropsPromise) === false + ) { + staticPages.add(page) + isStatic = true + } else if (isServerComponent) { + // This is a static server component page that doesn't have + // gSP or gSSP. We still treat it as a SSG page. + ssgPages.add(page) + isSsg = true } - if (workerResult.prerenderFallback === 'blocking') { - ssgBlockingFallbackPages.add(page) - } else if (workerResult.prerenderFallback === true) { - ssgStaticFallbackPages.add(page) + if (hasPages404 && page === '/404') { + if ( + !workerResult.isStatic && + !workerResult.hasStaticProps + ) { + throw new Error( + `\`pages/404\` ${STATIC_STATUS_PAGE_GET_INITIAL_PROPS_ERROR}` + ) + } + // we need to ensure the 404 lambda is present since we use + // it when _app has getInitialProps + if ( + (await customAppGetInitialPropsPromise) && + !workerResult.hasStaticProps + ) { + staticPages.delete(page) + } } - } else if (workerResult.hasServerProps) { - serverPropsPages.add(page) - } else if ( - workerResult.isStatic && - !isServerComponent && - (await customAppGetInitialPropsPromise) === false - ) { - staticPages.add(page) - isStatic = true - } else if (isServerComponent) { - // This is a static server component page that doesn't have - // gSP or gSSP. We still treat it as a SSG page. - ssgPages.add(page) - isSsg = true - } - if (hasPages404 && page === '/404') { if ( + STATIC_STATUS_PAGES.includes(page) && !workerResult.isStatic && !workerResult.hasStaticProps ) { throw new Error( - `\`pages/404\` ${STATIC_STATUS_PAGE_GET_INITIAL_PROPS_ERROR}` + `\`pages${page}\` ${STATIC_STATUS_PAGE_GET_INITIAL_PROPS_ERROR}` ) } - // we need to ensure the 404 lambda is present since we use - // it when _app has getInitialProps + } catch (err) { if ( - (await customAppGetInitialPropsPromise) && - !workerResult.hasStaticProps - ) { - staticPages.delete(page) - } - } - - if ( - STATIC_STATUS_PAGES.includes(page) && - !workerResult.isStatic && - !workerResult.hasStaticProps - ) { - throw new Error( - `\`pages${page}\` ${STATIC_STATUS_PAGE_GET_INITIAL_PROPS_ERROR}` + !isError(err) || + err.message !== 'INVALID_DEFAULT_EXPORT' ) + throw err + invalidPages.add(page) } - } catch (err) { - if (!isError(err) || err.message !== 'INVALID_DEFAULT_EXPORT') - throw err - invalidPages.add(page) } - } - pageInfos.set(page, { - size: selfSize, - totalSize: allSize, - static: isStatic, - isSsg, - isHybridAmp, - ssgPageRoutes, - initialRevalidateSeconds: false, - runtime: pageRuntime, - pageDuration: undefined, - ssgPageDurations: undefined, + pageInfos.set(page, { + size: selfSize, + totalSize: allSize, + static: isStatic, + isSsg, + isHybridAmp, + ssgPageRoutes, + initialRevalidateSeconds: false, + runtime: pageRuntime, + pageDuration: undefined, + ssgPageDurations: undefined, + }) }) }) - }) ) const errorPageResult = await errorPageStaticResult @@ -1331,7 +1376,7 @@ export default async function build( }) } - for (let page of pageKeys) { + for (let page of pageKeys.pages) { await includeExcludeSpan .traceChild('include-exclude', { page }) .traceAsyncFn(async () => { @@ -1672,7 +1717,7 @@ export default async function build( await copyTracedFiles( dir, distDir, - pageKeys, + pageKeys.pages, outputFileTracingRoot, requiredServerFiles.config, middlewareManifest @@ -1712,7 +1757,7 @@ export default async function build( detectConflictingPaths( [ ...combinedPages, - ...pageKeys.filter((page) => !combinedPages.includes(page)), + ...pageKeys.pages.filter((page) => !combinedPages.includes(page)), ], ssgPages, additionalSsgPaths @@ -2134,13 +2179,13 @@ export default async function build( const analysisEnd = process.hrtime(analysisBegin) telemetry.record( - eventBuildOptimize(pagePaths, { + eventBuildOptimize(pagesPaths, { durationInSeconds: analysisEnd[0], staticPageCount: staticPages.size, staticPropsPageCount: ssgPages.size, serverPropsPageCount: serverPropsPages.size, ssrPageCount: - pagePaths.length - + pagesPaths.length - (staticPages.size + ssgPages.size + serverPropsPages.size), hasStatic404: useStatic404, hasReportWebVitals: @@ -2301,21 +2346,17 @@ export default async function build( }) await nextBuildSpan.traceChild('print-tree-view').traceAsyncFn(() => - printTreeView( - Object.keys(mappedPages), - allPageInfos, - isLikeServerless, - { - distPath: distDir, - buildId: buildId, - pagesDir, - useStatic404, - pageExtensions: config.pageExtensions, - buildManifest, - middlewareManifest, - gzipSize: config.experimental.gzipSize, - } - ) + printTreeView(pageKeys, allPageInfos, isLikeServerless, { + distPath: distDir, + buildId: buildId, + pagesDir, + useStatic404, + pageExtensions: config.pageExtensions, + appBuildManifest, + buildManifest, + middlewareManifest, + gzipSize: config.experimental.gzipSize, + }) ) if (debugOutput) { diff --git a/packages/next/build/utils.ts b/packages/next/build/utils.ts index 32ab83145dd4459..5cc24805b4e271c 100644 --- a/packages/next/build/utils.ts +++ b/packages/next/build/utils.ts @@ -42,6 +42,9 @@ import { Sema } from 'next/dist/compiled/async-sema' import { MiddlewareManifest } from './webpack/plugins/middleware-plugin' import { denormalizePagePath } from '../shared/lib/page-path/denormalize-page-path' import { normalizePagePath } from '../shared/lib/page-path/normalize-page-path' +import { AppBuildManifest } from './webpack/plugins/app-build-manifest-plugin' + +export type ROUTER_TYPE = 'pages' | 'app' const RESERVED_PAGE = /^\/(_app|_error|_document|api(\/|$))/ const fileGzipStats: { [k: string]: Promise | undefined } = {} @@ -76,7 +79,10 @@ export interface PageInfo { } export async function printTreeView( - list: readonly string[], + lists: { + pages: ReadonlyArray + app?: ReadonlyArray + }, pageInfos: Map, serverless: boolean, { @@ -85,6 +91,7 @@ export async function printTreeView( pagesDir, pageExtensions, buildManifest, + appBuildManifest, middlewareManifest, useStatic404, gzipSize = true, @@ -94,6 +101,7 @@ export async function printTreeView( pagesDir: string pageExtensions: string[] buildManifest: BuildManifest + appBuildManifest?: AppBuildManifest middlewareManifest: MiddlewareManifest useStatic404: boolean gzipSize?: boolean @@ -129,227 +137,266 @@ export async function printTreeView( // Remove file hash .replace(/(?:^|[.-])([0-9a-z]{6})[0-9a-z]{14}(?=\.)/, '.$1') - const messages: [string, string, string][] = [ - ['Page', 'Size', 'First Load JS'].map((entry) => - chalk.underline(entry) - ) as [string, string, string], - ] - + // Check if we have a custom app. const hasCustomApp = await findPageFile(pagesDir, '/_app', pageExtensions) - pageInfos.set('/404', { - ...(pageInfos.get('/404') || pageInfos.get('/_error')), - static: useStatic404, - } as any) - if (!list.includes('/404')) { - list = [...list, '/404'] - } + const filterAndSortList = (list: ReadonlyArray) => + list + .slice() + .filter( + (e) => + !( + e === '/_document' || + e === '/_error' || + (!hasCustomApp && e === '/_app') + ) + ) + .sort((a, b) => a.localeCompare(b)) - const sizeData = await computeFromManifest( - buildManifest, + // Collect all the symbols we use so we can print the icons out. + const usedSymbols = new Set() + + const messages: [string, string, string][] = [] + + const stats = await computeFromManifest( + { build: buildManifest, app: appBuildManifest }, distPath, gzipSize, pageInfos ) - const usedSymbols = new Set() - - const pageList = list - .slice() - .filter( - (e) => - !( - e === '/_document' || - e === '/_error' || - (!hasCustomApp && e === '/_app') - ) + const printFileTree = async ({ + list, + routerType, + }: { + list: ReadonlyArray + routerType: ROUTER_TYPE + }) => { + messages.push( + [ + routerType === 'app' ? 'Route (app)' : 'Route (pages)', + 'Size', + 'First Load JS', + ].map((entry) => chalk.underline(entry)) as [string, string, string] ) - .sort((a, b) => a.localeCompare(b)) - - pageList.forEach((item, i, arr) => { - const border = - i === 0 - ? arr.length === 1 - ? '─' - : '┌' - : i === arr.length - 1 - ? '└' - : '├' - - const pageInfo = pageInfos.get(item) - const ampFirst = buildManifest.ampFirstPages.includes(item) - const totalDuration = - (pageInfo?.pageDuration || 0) + - (pageInfo?.ssgPageDurations?.reduce((a, b) => a + (b || 0), 0) || 0) - - const symbol = - item === '/_app' || item === '/_app.server' - ? ' ' - : pageInfo?.static - ? '○' - : pageInfo?.isSsg - ? '●' - : pageInfo?.runtime === SERVER_RUNTIME.edge - ? 'ℇ' - : 'λ' - - usedSymbols.add(symbol) - - if (pageInfo?.initialRevalidateSeconds) usedSymbols.add('ISR') - - messages.push([ - `${border} ${symbol} ${ - pageInfo?.initialRevalidateSeconds - ? `${item} (ISR: ${pageInfo?.initialRevalidateSeconds} Seconds)` - : item - }${ - totalDuration > MIN_DURATION - ? ` (${getPrettyDuration(totalDuration)})` - : '' - }`, - pageInfo - ? ampFirst - ? chalk.cyan('AMP') - : pageInfo.size >= 0 - ? prettyBytes(pageInfo.size) - : '' - : '', - pageInfo - ? ampFirst - ? chalk.cyan('AMP') - : pageInfo.size >= 0 - ? getPrettySize(pageInfo.totalSize) - : '' - : '', - ]) - - const uniqueCssFiles = - buildManifest.pages[item]?.filter( - (file) => file.endsWith('.css') && sizeData.uniqueFiles.includes(file) - ) || [] - - if (uniqueCssFiles.length > 0) { - const contSymbol = i === arr.length - 1 ? ' ' : '├' - uniqueCssFiles.forEach((file, index, { length }) => { - const innerSymbol = index === length - 1 ? '└' : '├' - messages.push([ - `${contSymbol} ${innerSymbol} ${getCleanName(file)}`, - prettyBytes(sizeData.sizeUniqueFiles[file]), - '', - ]) - }) - } - - if (pageInfo?.ssgPageRoutes?.length) { - const totalRoutes = pageInfo.ssgPageRoutes.length - const contSymbol = i === arr.length - 1 ? ' ' : '├' - - let routes: { route: string; duration: number; avgDuration?: number }[] - if ( - pageInfo.ssgPageDurations && - pageInfo.ssgPageDurations.some((d) => d > MIN_DURATION) - ) { - const previewPages = totalRoutes === 8 ? 8 : Math.min(totalRoutes, 7) - const routesWithDuration = pageInfo.ssgPageRoutes - .map((route, idx) => ({ - route, - duration: pageInfo.ssgPageDurations![idx] || 0, - })) - .sort(({ duration: a }, { duration: b }) => - // Sort by duration - // keep too small durations in original order at the end - a <= MIN_DURATION && b <= MIN_DURATION ? 0 : b - a - ) - routes = routesWithDuration.slice(0, previewPages) - const remainingRoutes = routesWithDuration.slice(previewPages) - if (remainingRoutes.length) { - const remaining = remainingRoutes.length - const avgDuration = Math.round( - remainingRoutes.reduce( - (total, { duration }) => total + duration, - 0 - ) / remainingRoutes.length - ) - routes.push({ - route: `[+${remaining} more paths]`, - duration: 0, - avgDuration, - }) - } - } else { - const previewPages = totalRoutes === 4 ? 4 : Math.min(totalRoutes, 3) - routes = pageInfo.ssgPageRoutes - .slice(0, previewPages) - .map((route) => ({ route, duration: 0 })) - if (totalRoutes > previewPages) { - const remaining = totalRoutes - previewPages - routes.push({ route: `[+${remaining} more paths]`, duration: 0 }) - } + filterAndSortList(list).forEach((item, i, arr) => { + const border = + i === 0 + ? arr.length === 1 + ? '─' + : '┌' + : i === arr.length - 1 + ? '└' + : '├' + + const pageInfo = pageInfos.get(item) + const ampFirst = buildManifest.ampFirstPages.includes(item) + const totalDuration = + (pageInfo?.pageDuration || 0) + + (pageInfo?.ssgPageDurations?.reduce((a, b) => a + (b || 0), 0) || 0) + + const symbol = + routerType === 'app' || item === '/_app' || item === '/_app.server' + ? ' ' + : pageInfo?.static + ? '○' + : pageInfo?.isSsg + ? '●' + : pageInfo?.runtime === SERVER_RUNTIME.edge + ? 'ℇ' + : 'λ' + + usedSymbols.add(symbol) + + if (pageInfo?.initialRevalidateSeconds) usedSymbols.add('ISR') + + messages.push([ + `${border} ${routerType === 'pages' ? `${symbol} ` : ''}${ + pageInfo?.initialRevalidateSeconds + ? `${item} (ISR: ${pageInfo?.initialRevalidateSeconds} Seconds)` + : item + }${ + totalDuration > MIN_DURATION + ? ` (${getPrettyDuration(totalDuration)})` + : '' + }`, + pageInfo + ? ampFirst + ? chalk.cyan('AMP') + : pageInfo.size >= 0 + ? prettyBytes(pageInfo.size) + : '' + : '', + pageInfo + ? ampFirst + ? chalk.cyan('AMP') + : pageInfo.size >= 0 + ? getPrettySize(pageInfo.totalSize) + : '' + : '', + ]) + + const uniqueCssFiles = + buildManifest.pages[item]?.filter( + (file) => + file.endsWith('.css') && + stats.router[routerType]?.unique.files.includes(file) + ) || [] + + if (uniqueCssFiles.length > 0) { + const contSymbol = i === arr.length - 1 ? ' ' : '├' + + uniqueCssFiles.forEach((file, index, { length }) => { + const innerSymbol = index === length - 1 ? '└' : '├' + const size = stats.sizes.get(file) + messages.push([ + `${contSymbol} ${innerSymbol} ${getCleanName(file)}`, + typeof size === 'number' ? prettyBytes(size) : '', + '', + ]) + }) } - routes.forEach(({ route, duration, avgDuration }, index, { length }) => { - const innerSymbol = index === length - 1 ? '└' : '├' - messages.push([ - `${contSymbol} ${innerSymbol} ${route}${ - duration > MIN_DURATION ? ` (${getPrettyDuration(duration)})` : '' - }${ - avgDuration && avgDuration > MIN_DURATION - ? ` (avg ${getPrettyDuration(avgDuration)})` - : '' - }`, - '', - '', - ]) - }) - } - }) + if (pageInfo?.ssgPageRoutes?.length) { + const totalRoutes = pageInfo.ssgPageRoutes.length + const contSymbol = i === arr.length - 1 ? ' ' : '├' - const sharedFilesSize = sizeData.sizeCommonFiles - const sharedFiles = sizeData.sizeCommonFile - - messages.push([ - '+ First Load JS shared by all', - getPrettySize(sharedFilesSize), - '', - ]) - const sharedFileKeys = Object.keys(sharedFiles) - const sharedCssFiles: string[] = [] - ;[ - ...sharedFileKeys - .filter((file) => { - if (file.endsWith('.css')) { - sharedCssFiles.push(file) - return false + let routes: { route: string; duration: number; avgDuration?: number }[] + if ( + pageInfo.ssgPageDurations && + pageInfo.ssgPageDurations.some((d) => d > MIN_DURATION) + ) { + const previewPages = totalRoutes === 8 ? 8 : Math.min(totalRoutes, 7) + const routesWithDuration = pageInfo.ssgPageRoutes + .map((route, idx) => ({ + route, + duration: pageInfo.ssgPageDurations![idx] || 0, + })) + .sort(({ duration: a }, { duration: b }) => + // Sort by duration + // keep too small durations in original order at the end + a <= MIN_DURATION && b <= MIN_DURATION ? 0 : b - a + ) + routes = routesWithDuration.slice(0, previewPages) + const remainingRoutes = routesWithDuration.slice(previewPages) + if (remainingRoutes.length) { + const remaining = remainingRoutes.length + const avgDuration = Math.round( + remainingRoutes.reduce( + (total, { duration }) => total + duration, + 0 + ) / remainingRoutes.length + ) + routes.push({ + route: `[+${remaining} more paths]`, + duration: 0, + avgDuration, + }) + } + } else { + const previewPages = totalRoutes === 4 ? 4 : Math.min(totalRoutes, 3) + routes = pageInfo.ssgPageRoutes + .slice(0, previewPages) + .map((route) => ({ route, duration: 0 })) + if (totalRoutes > previewPages) { + const remaining = totalRoutes - previewPages + routes.push({ route: `[+${remaining} more paths]`, duration: 0 }) + } } - return true - }) - .map((e) => e.replace(buildId, '')) - .sort(), - ...sharedCssFiles.map((e) => e.replace(buildId, '')).sort(), - ].forEach((fileName, index, { length }) => { - const innerSymbol = index === length - 1 ? '└' : '├' - const originalName = fileName.replace('', buildId) - const cleanName = getCleanName(fileName) + routes.forEach( + ({ route, duration, avgDuration }, index, { length }) => { + const innerSymbol = index === length - 1 ? '└' : '├' + messages.push([ + `${contSymbol} ${innerSymbol} ${route}${ + duration > MIN_DURATION + ? ` (${getPrettyDuration(duration)})` + : '' + }${ + avgDuration && avgDuration > MIN_DURATION + ? ` (avg ${getPrettyDuration(avgDuration)})` + : '' + }`, + '', + '', + ]) + } + ) + } + }) + + const sharedFilesSize = stats.router[routerType]?.common.size.total + const sharedFiles = stats.router[routerType]?.common.files ?? [] messages.push([ - ` ${innerSymbol} ${cleanName}`, - prettyBytes(sharedFiles[originalName]), + '+ First Load JS shared by all', + typeof sharedFilesSize === 'number' ? getPrettySize(sharedFilesSize) : '', '', ]) + const sharedCssFiles: string[] = [] + ;[ + ...sharedFiles + .filter((file) => { + if (file.endsWith('.css')) { + sharedCssFiles.push(file) + return false + } + return true + }) + .map((e) => e.replace(buildId, '')) + .sort(), + ...sharedCssFiles.map((e) => e.replace(buildId, '')).sort(), + ].forEach((fileName, index, { length }) => { + const innerSymbol = index === length - 1 ? '└' : '├' + + const originalName = fileName.replace('', buildId) + const cleanName = getCleanName(fileName) + const size = stats.sizes.get(originalName) + + messages.push([ + ` ${innerSymbol} ${cleanName}`, + typeof size === 'number' ? prettyBytes(size) : '', + '', + ]) + }) + } + + // If enabled, then print the tree for the app directory. + if (lists.app && stats.router.app) { + await printFileTree({ + routerType: 'app', + list: lists.app, + }) + + messages.push(['', '', '']) + } + + pageInfos.set('/404', { + ...(pageInfos.get('/404') || pageInfos.get('/_error')), + static: useStatic404, + } as any) + + if (!lists.pages.includes('/404')) { + lists.pages = [...lists.pages, '/404'] + } + + // Print the tree view for the pages directory. + await printFileTree({ + routerType: 'pages', + list: lists.pages, }) const middlewareInfo = middlewareManifest.middleware?.['/'] if (middlewareInfo?.files.length > 0) { - const sizes = await Promise.all( + const middlewareSizes = await Promise.all( middlewareInfo.files .map((dep) => `${distPath}/${dep}`) .map(gzipSize ? fsStatGzip : fsStat) ) messages.push(['', '', '']) - messages.push(['ƒ Middleware', getPrettySize(sum(sizes)), '']) + messages.push(['ƒ Middleware', getPrettySize(sum(middlewareSizes)), '']) } console.log( @@ -479,165 +526,281 @@ export function printCustomRoutes({ } } -type ComputeManifestShape = { - commonFiles: string[] - uniqueFiles: string[] - sizeUniqueFiles: { [file: string]: number } - sizeCommonFile: { [file: string]: number } - sizeCommonFiles: number +type ComputeFilesGroup = { + files: ReadonlyArray + size: { + total: number + } +} + +type ComputeFilesManifest = { + unique: ComputeFilesGroup + common: ComputeFilesGroup +} + +type ComputeFilesManifestResult = { + router: { + pages: ComputeFilesManifest + app?: ComputeFilesManifest + } + sizes: Map } let cachedBuildManifest: BuildManifest | undefined +let cachedAppBuildManifest: AppBuildManifest | undefined -let lastCompute: ComputeManifestShape | undefined +let lastCompute: ComputeFilesManifestResult | undefined let lastComputePageInfo: boolean | undefined export async function computeFromManifest( - manifest: BuildManifest, + manifests: { + build: BuildManifest + app?: AppBuildManifest + }, distPath: string, gzipSize: boolean = true, pageInfos?: Map -): Promise { +): Promise { if ( - Object.is(cachedBuildManifest, manifest) && - lastComputePageInfo === !!pageInfos + Object.is(cachedBuildManifest, manifests.build) && + lastComputePageInfo === !!pageInfos && + Object.is(cachedAppBuildManifest, manifests.app) ) { return lastCompute! } - let expected = 0 - const files = new Map() - Object.keys(manifest.pages).forEach((key) => { + // Determine the files that are in pages and app and count them, this will + // tell us if they are unique or common. + + const countBuildFiles = ( + map: Map, + key: string, + manifest: Record> + ) => { + for (const file of manifest[key]) { + if (key === '/_app') { + map.set(file, Infinity) + } else if (map.has(file)) { + map.set(file, map.get(file)! + 1) + } else { + map.set(file, 1) + } + } + } + + const files: { + pages: { + each: Map + expected: number + } + app?: { + each: Map + expected: number + } + } = { + pages: { each: new Map(), expected: 0 }, + } + + for (const key in manifests.build.pages) { if (pageInfos) { const pageInfo = pageInfos.get(key) // don't include AMP pages since they don't rely on shared bundles // AMP First pages are not under the pageInfos key if (pageInfo?.isHybridAmp) { - return + continue } } - ++expected - manifest.pages[key].forEach((file) => { - if (key === '/_app') { - files.set(file, Infinity) - } else if (files.has(file)) { - files.set(file, files.get(file)! + 1) - } else { - files.set(file, 1) - } - }) - }) + files.pages.expected++ + countBuildFiles(files.pages.each, key, manifests.build.pages) + } + + // Collect the build files form the app manifest. + if (manifests.app?.pages) { + files.app = { each: new Map(), expected: 0 } + + for (const key in manifests.app.pages) { + files.app.expected++ + countBuildFiles(files.app.each, key, manifests.app.pages) + } + } const getSize = gzipSize ? fsStatGzip : fsStat + const stats = new Map() + + // For all of the files in the pages and app manifests, compute the file size + // at once. + + await Promise.all( + [ + ...new Set([ + ...files.pages.each.keys(), + ...(files.app?.each.keys() ?? []), + ]), + ].map(async (f) => { + try { + // Add the file size to the stats. + stats.set(f, await getSize(path.join(distPath, f))) + } catch {} + }) + ) - const commonFiles = [...files.entries()] - .filter(([, len]) => len === expected || len === Infinity) - .map(([f]) => f) - const uniqueFiles = [...files.entries()] - .filter(([, len]) => len === 1) - .map(([f]) => f) + const groupFiles = async (listing: { + each: Map + expected: number + }): Promise => { + const entries = [...listing.each.entries()] - let stats: [string, number][] - try { - stats = await Promise.all( - commonFiles.map( - async (f) => - [f, await getSize(path.join(distPath, f))] as [string, number] - ) - ) - } catch (_) { - stats = [] - } + const shapeGroup = (group: [string, number][]): ComputeFilesGroup => + group.reduce( + (acc, [f]) => { + acc.files.push(f) - let uniqueStats: [string, number][] - try { - uniqueStats = await Promise.all( - uniqueFiles.map( - async (f) => - [f, await getSize(path.join(distPath, f))] as [string, number] + const size = stats.get(f) + if (typeof size === 'number') { + acc.size.total += size + } + + return acc + }, + { + files: [] as string[], + size: { + total: 0, + }, + } ) - ) - } catch (_) { - uniqueStats = [] + + return { + unique: shapeGroup(entries.filter(([, len]) => len === 1)), + common: shapeGroup( + entries.filter( + ([, len]) => len === listing.expected || len === Infinity + ) + ), + } } lastCompute = { - commonFiles, - uniqueFiles, - sizeUniqueFiles: uniqueStats.reduce( - (obj, n) => Object.assign(obj, { [n[0]]: n[1] }), - {} - ), - sizeCommonFile: stats.reduce( - (obj, n) => Object.assign(obj, { [n[0]]: n[1] }), - {} - ), - sizeCommonFiles: stats.reduce((size, [f, stat]) => { - if (f.endsWith('.css')) return size - return size + stat - }, 0), + router: { + pages: await groupFiles(files.pages), + app: files.app ? await groupFiles(files.app) : undefined, + }, + sizes: stats, } - cachedBuildManifest = manifest + cachedBuildManifest = manifests.build + cachedAppBuildManifest = manifests.app lastComputePageInfo = !!pageInfos return lastCompute! } -export function difference(main: T[] | Set, sub: T[] | Set): T[] { +export function unique(main: ReadonlyArray, sub: ReadonlyArray): T[] { + return [...new Set([...main, ...sub])] +} + +export function difference( + main: ReadonlyArray | ReadonlySet, + sub: ReadonlyArray | ReadonlySet +): T[] { const a = new Set(main) const b = new Set(sub) return [...a].filter((x) => !b.has(x)) } -function intersect(main: T[], sub: T[]): T[] { +/** + * Return an array of the items shared by both arrays. + */ +function intersect(main: ReadonlyArray, sub: ReadonlyArray): T[] { const a = new Set(main) const b = new Set(sub) return [...new Set([...a].filter((x) => b.has(x)))] } -function sum(a: number[]): number { +function sum(a: ReadonlyArray): number { return a.reduce((size, stat) => size + stat, 0) } +function denormalizeAppPagePath(page: string): string { + return page + '/page' +} + export async function getJsPageSizeInKb( + routerType: ROUTER_TYPE, page: string, distPath: string, buildManifest: BuildManifest, + appBuildManifest?: AppBuildManifest, gzipSize: boolean = true, - computedManifestData?: ComputeManifestShape + cachedStats?: ComputeFilesManifestResult ): Promise<[number, number]> { - const data = - computedManifestData || - (await computeFromManifest(buildManifest, distPath, gzipSize)) + const pageManifest = routerType === 'pages' ? buildManifest : appBuildManifest + if (!pageManifest) { + throw new Error('expected appBuildManifest with an "app" pageType') + } + + // If stats was not provided, then compute it again. + const stats = + cachedStats ?? + (await computeFromManifest( + { build: buildManifest, app: appBuildManifest }, + distPath, + gzipSize + )) + + const pageData = stats.router[routerType] + if (!pageData) { + // This error shouldn't happen and represents an error in Next.js. + throw new Error('expected "app" manifest data with an "app" pageType') + } + + const pagePath = + routerType === 'pages' + ? denormalizePagePath(page) + : denormalizeAppPagePath(page) const fnFilterJs = (entry: string) => entry.endsWith('.js') - const pageFiles = ( - buildManifest.pages[denormalizePagePath(page)] || [] - ).filter(fnFilterJs) - const appFiles = (buildManifest.pages['/_app'] || []).filter(fnFilterJs) + const pageFiles = (pageManifest.pages[pagePath] ?? []).filter(fnFilterJs) + const appFiles = (pageManifest.pages['/_app'] ?? []).filter(fnFilterJs) const fnMapRealPath = (dep: string) => `${distPath}/${dep}` - const allFilesReal = [...new Set([...pageFiles, ...appFiles])].map( - fnMapRealPath - ) + const allFilesReal = unique(pageFiles, appFiles).map(fnMapRealPath) const selfFilesReal = difference( - intersect(pageFiles, data.uniqueFiles), - data.commonFiles + // Find the files shared by the pages files and the unique files... + intersect(pageFiles, pageData.unique.files), + // but without the common files. + pageData.common.files ).map(fnMapRealPath) const getSize = gzipSize ? fsStatGzip : fsStat + // Try to get the file size from the page data if available, otherwise do a + // raw compute. + const getCachedSize = async (file: string) => { + const key = file.slice(distPath.length + 1) + const size: number | undefined = stats.sizes.get(key) + + // If the size wasn't in the stats bundle, then get it from the file + // directly. + if (typeof size !== 'number') { + return getSize(file) + } + + return size + } + try { // Doesn't use `Promise.all`, as we'd double compute duplicate files. This // function is memoized, so the second one will instantly resolve. - const allFilesSize = sum(await Promise.all(allFilesReal.map(getSize))) - const selfFilesSize = sum(await Promise.all(selfFilesReal.map(getSize))) + const allFilesSize = sum(await Promise.all(allFilesReal.map(getCachedSize))) + const selfFilesSize = sum( + await Promise.all(selfFilesReal.map(getCachedSize)) + ) return [selfFilesSize, allFilesSize] - } catch (_) {} + } catch {} return [-1, -1] } @@ -1126,7 +1289,7 @@ export function isServerComponentPage( export async function copyTracedFiles( dir: string, distDir: string, - pageKeys: string[], + pageKeys: ReadonlyArray, tracingRoot: string, serverConfig: { [key: string]: any }, middlewareManifest: MiddlewareManifest diff --git a/packages/next/build/webpack-config.ts b/packages/next/build/webpack-config.ts index 9de8fd598f2f928..964b61f3587ad3a 100644 --- a/packages/next/build/webpack-config.ts +++ b/packages/next/build/webpack-config.ts @@ -19,7 +19,7 @@ import { CustomRoutes } from '../lib/load-custom-routes.js' import { CLIENT_STATIC_FILES_RUNTIME_AMP, CLIENT_STATIC_FILES_RUNTIME_MAIN, - CLIENT_STATIC_FILES_RUNTIME_MAIN_ROOT, + CLIENT_STATIC_FILES_RUNTIME_MAIN_APP, CLIENT_STATIC_FILES_RUNTIME_POLYFILLS_SYMBOL, CLIENT_STATIC_FILES_RUNTIME_REACT_REFRESH, CLIENT_STATIC_FILES_RUNTIME_WEBPACK, @@ -59,6 +59,7 @@ import browserslist from 'next/dist/compiled/browserslist' import loadJsConfig from './load-jsconfig' import { loadBindings } from './swc' import { clientComponentRegex } from './webpack/loaders/utils' +import { AppBuildManifestPlugin } from './webpack/plugins/app-build-manifest-plugin' const watchOptions = Object.freeze({ aggregateTimeout: 5, @@ -572,7 +573,7 @@ export default async function getBaseWebpackConfig( .replace(/\\/g, '/'), ...(config.experimental.appDir ? { - [CLIENT_STATIC_FILES_RUNTIME_MAIN_ROOT]: dev + [CLIENT_STATIC_FILES_RUNTIME_MAIN_APP]: dev ? [ require.resolve( `next/dist/compiled/@next/react-refresh-utils/dist/runtime` @@ -1718,6 +1719,10 @@ export default async function getBaseWebpackConfig( minimized: true, }, }), + !!config.experimental.appDir && + hasServerComponents && + isClient && + new AppBuildManifestPlugin({ dev }), hasServerComponents && (isClient ? new FlightManifestPlugin({ diff --git a/packages/next/build/webpack/loaders/next-serverless-loader/page-handler.ts b/packages/next/build/webpack/loaders/next-serverless-loader/page-handler.ts index 0dd70dbc25c4c33..90eaa23c39b0226 100644 --- a/packages/next/build/webpack/loaders/next-serverless-loader/page-handler.ts +++ b/packages/next/build/webpack/loaders/next-serverless-loader/page-handler.ts @@ -8,7 +8,7 @@ import { renderToHTML } from '../../../../server/render' import { tryGetPreviewData } from '../../../../server/api-utils/node' import { denormalizePagePath } from '../../../../shared/lib/page-path/denormalize-page-path' import { setLazyProp, getCookieParser } from '../../../../server/api-utils' -import { getRedirectStatus } from '../../../../lib/load-custom-routes' +import { getRedirectStatus } from '../../../../lib/redirect-status' import getRouteNoAssetPath from '../../../../shared/lib/router/utils/get-route-from-asset-path' import { PERMANENT_REDIRECT_STATUS } from '../../../../shared/lib/constants' import RenderResult from '../../../../server/render-result' diff --git a/packages/next/build/webpack/plugins/app-build-manifest-plugin.ts b/packages/next/build/webpack/plugins/app-build-manifest-plugin.ts new file mode 100644 index 000000000000000..f13b8901f4bca92 --- /dev/null +++ b/packages/next/build/webpack/plugins/app-build-manifest-plugin.ts @@ -0,0 +1,98 @@ +import { webpack, sources } from 'next/dist/compiled/webpack/webpack' +import { + APP_BUILD_MANIFEST, + CLIENT_STATIC_FILES_RUNTIME_AMP, + CLIENT_STATIC_FILES_RUNTIME_MAIN, + CLIENT_STATIC_FILES_RUNTIME_MAIN_APP, + CLIENT_STATIC_FILES_RUNTIME_REACT_REFRESH, +} from '../../../shared/lib/constants' +import type { webpack5 } from 'next/dist/compiled/webpack/webpack' +import { getEntrypointFiles } from './build-manifest-plugin' +import getAppRouteFromEntrypoint from '../../../server/get-app-route-from-entrypoint' + +type Options = { + dev: boolean +} + +export type AppBuildManifest = { + pages: Record +} + +const PLUGIN_NAME = 'AppBuildManifestPlugin' + +export class AppBuildManifestPlugin { + private readonly dev: boolean + + constructor(options: Options) { + this.dev = options.dev + } + + public apply(compiler: any) { + compiler.hooks.compilation.tap( + PLUGIN_NAME, + (compilation: any, { normalModuleFactory }: any) => { + compilation.dependencyFactories.set( + (webpack as any).dependencies.ModuleDependency, + normalModuleFactory + ) + compilation.dependencyTemplates.set( + (webpack as any).dependencies.ModuleDependency, + new (webpack as any).dependencies.NullDependency.Template() + ) + } + ) + + compiler.hooks.make.tap(PLUGIN_NAME, (compilation: any) => { + compilation.hooks.processAssets.tap( + { + name: PLUGIN_NAME, + // @ts-ignore TODO: Remove ignore when webpack 5 is stable + stage: webpack.Compilation.PROCESS_ASSETS_STAGE_ADDITIONS, + }, + (assets: any) => this.createAsset(assets, compilation) + ) + }) + } + + private createAsset(assets: any, compilation: webpack5.Compilation) { + const manifest: AppBuildManifest = { + pages: {}, + } + + const systemEntrypoints = new Set([ + CLIENT_STATIC_FILES_RUNTIME_MAIN, + CLIENT_STATIC_FILES_RUNTIME_REACT_REFRESH, + CLIENT_STATIC_FILES_RUNTIME_AMP, + CLIENT_STATIC_FILES_RUNTIME_MAIN_APP, + ]) + + const mainFiles = new Set( + getEntrypointFiles( + compilation.entrypoints.get(CLIENT_STATIC_FILES_RUNTIME_MAIN_APP) + ) + ) + + for (const entrypoint of compilation.entrypoints.values()) { + if (!entrypoint.name) { + continue + } + + if (systemEntrypoints.has(entrypoint.name)) { + continue + } + + const pagePath = getAppRouteFromEntrypoint(entrypoint.name) + if (!pagePath) { + continue + } + + const filesForPage = getEntrypointFiles(entrypoint) + + manifest.pages[pagePath] = [...new Set([...mainFiles, ...filesForPage])] + } + + const json = JSON.stringify(manifest, null, 2) + + assets[APP_BUILD_MANIFEST] = new sources.RawSource(json) + } +} diff --git a/packages/next/build/webpack/plugins/build-manifest-plugin.ts b/packages/next/build/webpack/plugins/build-manifest-plugin.ts index 55cae92b3917c5f..6f3d2ca342693ef 100644 --- a/packages/next/build/webpack/plugins/build-manifest-plugin.ts +++ b/packages/next/build/webpack/plugins/build-manifest-plugin.ts @@ -1,3 +1,4 @@ +import type { Rewrite, CustomRoutes } from '../../../lib/load-custom-routes' import devalue from 'next/dist/compiled/devalue' import { webpack, sources } from 'next/dist/compiled/webpack/webpack' import { @@ -5,7 +6,7 @@ import { MIDDLEWARE_BUILD_MANIFEST, CLIENT_STATIC_FILES_PATH, CLIENT_STATIC_FILES_RUNTIME_MAIN, - CLIENT_STATIC_FILES_RUNTIME_MAIN_ROOT, + CLIENT_STATIC_FILES_RUNTIME_MAIN_APP, CLIENT_STATIC_FILES_RUNTIME_POLYFILLS_SYMBOL, CLIENT_STATIC_FILES_RUNTIME_REACT_REFRESH, CLIENT_STATIC_FILES_RUNTIME_AMP, @@ -13,10 +14,8 @@ import { import { BuildManifest } from '../../../server/get-page-files' import getRouteFromEntrypoint from '../../../server/get-route-from-entrypoint' import { ampFirstEntryNamesMap } from './next-drop-client-page-plugin' -import { Rewrite } from '../../../lib/load-custom-routes' import { getSortedRoutes } from '../../../shared/lib/router/utils' import { spans } from './profiling-plugin' -import { CustomRoutes } from '../../../lib/load-custom-routes' type DeepMutable = { -readonly [P in keyof T]: DeepMutable } @@ -156,7 +155,7 @@ export default class BuildManifestPlugin { assetMap.rootMainFiles = [ ...new Set( getEntrypointFiles( - entrypoints.get(CLIENT_STATIC_FILES_RUNTIME_MAIN_ROOT) + entrypoints.get(CLIENT_STATIC_FILES_RUNTIME_MAIN_APP) ) ), ] @@ -193,7 +192,7 @@ export default class BuildManifestPlugin { CLIENT_STATIC_FILES_RUNTIME_MAIN, CLIENT_STATIC_FILES_RUNTIME_REACT_REFRESH, CLIENT_STATIC_FILES_RUNTIME_AMP, - ...(this.appDirEnabled ? [CLIENT_STATIC_FILES_RUNTIME_MAIN_ROOT] : []), + ...(this.appDirEnabled ? [CLIENT_STATIC_FILES_RUNTIME_MAIN_APP] : []), ]) for (const entrypoint of compilation.entrypoints.values()) { diff --git a/packages/next/client/future/image.tsx b/packages/next/client/future/image.tsx index d4a11ae146054a0..57e07805dd5fbae 100644 --- a/packages/next/client/future/image.tsx +++ b/packages/next/client/future/image.tsx @@ -581,7 +581,7 @@ export default function Image({ showAltText || placeholder === 'blur' ? {} : { color: 'transparent' }, style ) - const svgBlurPlaceholder = `url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http%3A//www.w3.org/2000/svg' viewBox='0 0 ${widthInt} ${heightInt}'%3E%3Cfilter id='b' color-interpolation-filters='sRGB'%3E%3CfeGaussianBlur stdDeviation='50'/%3E%3CfeComponentTransfer%3E%3CfeFuncA type='discrete' tableValues='1 1'/%3E%3C/feComponentTransfer%3E%3C/filter%3E%3Cimage filter='url(%23b)' x='0' y='0' height='100%25' width='100%25' href='${blurDataURL}'/%3E%3C/svg%3E")` + const svgBlurPlaceholder = `url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http%3A//www.w3.org/2000/svg' viewBox='0 0 ${widthInt} ${heightInt}'%3E%3Cfilter id='b' color-interpolation-filters='sRGB'%3E%3CfeGaussianBlur stdDeviation='50'/%3E%3C/filter%3E%3Cimage filter='url(%23b)' x='0' y='0' height='100%25' width='100%25' href='${blurDataURL}'/%3E%3C/svg%3E")` const blurStyle = placeholder === 'blur' && !blurComplete ? { diff --git a/packages/next/client/index.tsx b/packages/next/client/index.tsx index 5c4f482da59bbd8..535584636276cf2 100644 --- a/packages/next/client/index.tsx +++ b/packages/next/client/index.tsx @@ -468,7 +468,7 @@ async function render(renderingProps: RenderRouteInfo): Promise { // 404 and 500 errors are special kind of errors // and they are still handle via the main render method. function renderError(renderErrorProps: RenderErrorProps): Promise { - const { App, err } = renderErrorProps + let { App, err } = renderErrorProps // In development runtime errors are caught by our overlay // In production we catch runtime errors using componentDidCatch which will trigger renderError @@ -497,10 +497,18 @@ function renderError(renderErrorProps: RenderErrorProps): Promise { .loadPage('/_error') .then(({ page: ErrorComponent, styleSheets }) => { return lastAppProps?.Component === ErrorComponent - ? import('../pages/_error').then((m) => ({ - ErrorComponent: m.default as React.ComponentType<{}>, - styleSheets: [], - })) + ? import('../pages/_error') + .then((errorModule) => { + return import('../pages/_app').then((appModule) => { + App = appModule.default as any as AppComponent + renderErrorProps.App = App + return errorModule + }) + }) + .then((m) => ({ + ErrorComponent: m.default as React.ComponentType<{}>, + styleSheets: [], + })) : { ErrorComponent, styleSheets } }) .then(({ ErrorComponent, styleSheets }) => { diff --git a/packages/next/compiled/@edge-runtime/primitives/package.json b/packages/next/compiled/@edge-runtime/primitives/package.json index 835ca8cc3c16df6..f313ba0b2bba632 100644 --- a/packages/next/compiled/@edge-runtime/primitives/package.json +++ b/packages/next/compiled/@edge-runtime/primitives/package.json @@ -1 +1 @@ -{"name":"@edge-runtime/primitives","version":"1.1.0-beta.25","main":"./index.js","license":"MPLv2"} +{"name":"@edge-runtime/primitives","version":"1.1.0-beta.26","main":"./index.js","license":"MPLv2"} diff --git a/packages/next/compiled/edge-runtime/index.js b/packages/next/compiled/edge-runtime/index.js index a042b3d70eb883f..dd1b86049f7b953 100644 --- a/packages/next/compiled/edge-runtime/index.js +++ b/packages/next/compiled/edge-runtime/index.js @@ -1 +1 @@ -(()=>{var __webpack_modules__={605:(__unused_webpack_module,exports,__nccwpck_require__)=>{"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.EdgeVM=void 0;const buffer_1=__nccwpck_require__(300);const require_1=__nccwpck_require__(274);const vm_1=__nccwpck_require__(386);class EdgeVM extends vm_1.VM{constructor(e={}){super({...e,extend:t=>e.extend?e.extend(addPrimitives(t)):addPrimitives(t)})}}exports.EdgeVM=EdgeVM;function addPrimitives(context){defineProperty(context,"self",{enumerable:true,value:context});defineProperty(context,"globalThis",{value:context});defineProperty(context,"Symbol",{value:Symbol});defineProperty(context,"clearInterval",{value:clearInterval});defineProperty(context,"clearTimeout",{value:clearTimeout});defineProperty(context,"setInterval",{value:setInterval});defineProperty(context,"setTimeout",{value:setTimeout});defineProperties(context,{exports:(0,require_1.requireWithCache)({context:context,path:require.resolve("next/dist/compiled/@edge-runtime/primitives/console"),scopedContext:{console:console}}),nonenumerable:["console"]});const encodings=(0,require_1.requireWithCache)({context:context,path:require.resolve("next/dist/compiled/@edge-runtime/primitives/encoding"),scopedContext:{Buffer:buffer_1.Buffer,global:{ArrayBuffer:ArrayBuffer}}});defineProperties(context,{exports:encodings,nonenumerable:["atob","btoa","TextEncoder","TextDecoder"]});const streams=(0,require_1.requireWithCache)({path:require.resolve("next/dist/compiled/@edge-runtime/primitives/streams"),context:context});defineProperties(context,{exports:streams,nonenumerable:["ReadableStream","ReadableStreamBYOBReader","ReadableStreamDefaultReader","TransformStream","WritableStream","WritableStreamDefaultWriter"]});const abort=(0,require_1.requireWithCache)({context:context,path:require.resolve("next/dist/compiled/@edge-runtime/primitives/abort-controller")});defineProperties(context,{exports:abort,nonenumerable:["AbortController","AbortSignal"]});defineProperties(context,{exports:(0,require_1.requireWithCache)({cache:new Map([["punycode",{exports:__nccwpck_require__(477)}]]),context:context,path:require.resolve("next/dist/compiled/@edge-runtime/primitives/url"),scopedContext:{TextEncoder:encodings.TextEncoder,TextDecoder:encodings.TextDecoder}}),nonenumerable:["URL","URLSearchParams","URLPattern"]});const blob=(0,require_1.requireWithCache)({context:context,path:require.resolve("next/dist/compiled/@edge-runtime/primitives/blob")});defineProperties(context,{exports:blob,nonenumerable:["Blob"]});const webFetch=(0,require_1.requireWithCache)({context:context,cache:new Map([["abort-controller",{exports:abort}],["assert",{exports:__nccwpck_require__(491)}],["buffer",{exports:__nccwpck_require__(300)}],["events",{exports:__nccwpck_require__(361)}],["http",{exports:__nccwpck_require__(685)}],["net",{exports:__nccwpck_require__(808)}],["perf_hooks",{exports:__nccwpck_require__(74)}],["stream",{exports:__nccwpck_require__(781)}],["tls",{exports:__nccwpck_require__(404)}],["util",{exports:__nccwpck_require__(837)}],["zlib",{exports:__nccwpck_require__(796)}],[require.resolve("next/dist/compiled/@edge-runtime/primitives/streams"),{exports:streams}],[require.resolve("next/dist/compiled/@edge-runtime/primitives/blob"),{exports:blob}]]),path:require.resolve("next/dist/compiled/@edge-runtime/primitives/fetch"),scopedContext:{Buffer:buffer_1.Buffer,FinalizationRegistry:function(){return{register:function(){}}},global:{},queueMicrotask:queueMicrotask,setImmediate:setImmediate}});defineProperties(context,{exports:webFetch,nonenumerable:["fetch","File","FormData","Headers","Request","Response"]});defineProperties(context,{exports:(0,require_1.requireWithCache)({cache:new Map([[require.resolve("next/dist/compiled/@edge-runtime/primitives/fetch"),{exports:webFetch}]]),context:context,path:require.resolve("next/dist/compiled/@edge-runtime/primitives/cache"),scopedContext:{global:{}}}),enumerable:["caches"],nonenumerable:["Cache","CacheStorage"]});defineProperties(context,{exports:(0,require_1.requireWithCache)({context:context,cache:new Map([["crypto",{exports:__nccwpck_require__(113)}],["process",{exports:__nccwpck_require__(282)}]]),path:require.resolve("next/dist/compiled/@edge-runtime/primitives/crypto"),scopedContext:{Buffer:buffer_1.Buffer}}),enumerable:["crypto"],nonenumerable:["Crypto","CryptoKey","SubtleCrypto"]});defineProperties(context,{exports:(0,require_1.requireWithCache)({context:context,path:require.resolve("next/dist/compiled/@edge-runtime/primitives/events")}),nonenumerable:["Event","EventTarget","FetchEvent","PromiseRejectionEvent"]});defineProperties(context,{exports:(0,require_1.requireWithCache)({context:context,path:require.resolve("next/dist/compiled/@edge-runtime/primitives/structured-clone")}),nonenumerable:["structuredClone"]});return context}function defineProperty(e,t,r){var n,o,s;Object.defineProperty(e,t,{configurable:(n=r.configurable)!==null&&n!==void 0?n:false,enumerable:(o=r.enumerable)!==null&&o!==void 0?o:false,value:r.value,writable:(s=r.writable)!==null&&s!==void 0?s:true})}function defineProperties(e,t){var r,n;for(const n of(r=t.enumerable)!==null&&r!==void 0?r:[]){if(!t.exports[n]){throw new Error(`Attempt to export a nullable value for "${n}"`)}defineProperty(e,n,{enumerable:true,value:t.exports[n]})}for(const r of(n=t.nonenumerable)!==null&&n!==void 0?n:[]){if(!t.exports[r]){throw new Error(`Attempt to export a nullable value for "${r}"`)}defineProperty(e,r,{value:t.exports[r]})}}},844:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:true});t.VM=t.EdgeVM=void 0;var n=r(605);Object.defineProperty(t,"EdgeVM",{enumerable:true,get:function(){return n.EdgeVM}});var o=r(386);Object.defineProperty(t,"VM",{enumerable:true,get:function(){return o.VM}})},274:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:true});t.requireWithCache=t.createRequire=t.requireDependencies=void 0;const n=r(147);const o=r(144);const s=r(17);function requireDependencies(e){const{context:t,requireCache:r,dependencies:n}=e;const o=createRequire(t,r);for(const{path:e,mapExports:r}of n){const n=o(e,e);for(const e of Object.keys(r)){t[r[e]]=n[e]}}}t.requireDependencies=requireDependencies;function createRequire(e,t,r,i={}){return function requireFn(a,E){const c=require.resolve(E,{paths:[(0,s.dirname)(a)]});const u=t.get(E)||t.get(c);if(u!==undefined&&u!==null){return u.exports}const _={exports:{},loaded:false,id:c};t.set(c,_);r===null||r===void 0?void 0:r.add(c);const d=(0,o.runInContext)(`(function(module,exports,require,__dirname,__filename,${Object.keys(i).join(",")}) {${(0,n.readFileSync)(c,"utf-8")}\n})`,e);try{d(_,_.exports,requireFn.bind(null,c),(0,s.dirname)(c),c,...Object.values(i))}catch(e){t.delete(c);throw e}_.loaded=true;return _.exports}}t.createRequire=createRequire;function requireWithCache(e){var t;return createRequire(e.context,(t=e.cache)!==null&&t!==void 0?t:new Map,e.references,e.scopedContext).call(null,e.path,e.path)}t.requireWithCache=requireWithCache},370:function(e,t,r){"use strict";var n=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(t,"__esModule",{value:true});t.tempFile=void 0;const o=r(277);const s=n(r(17));const i=n(r(147));const a=n(r(37));function tempFile(e){const t=s.default.join(a.default.tmpdir(),o.crypto.randomUUID());i.default.writeFileSync(t,e);return{path:t,remove:()=>i.default.unlinkSync(t)}}t.tempFile=tempFile},386:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:true});t.VM=void 0;const n=r(144);const o=r(274);const s=r(370);class VM{constructor(e={}){var t,r,s,i;const a=(0,n.createContext)({},{name:"Edge Runtime",codeGeneration:(t=e.codeGeneration)!==null&&t!==void 0?t:{strings:false,wasm:true}});this.requireCache=(r=e.requireCache)!==null&&r!==void 0?r:new Map;this.context=(i=(s=e.extend)===null||s===void 0?void 0:s.call(e,a))!==null&&i!==void 0?i:a;this.requireFn=(0,o.createRequire)(this.context,this.requireCache)}evaluate(e){return(0,n.runInContext)(e,this.context)}require(e){return this.requireFn(e,e)}requireInContext(e){const t=this.require(e);for(const[e,r]of Object.entries(t)){this.context[e]=r}}requireInlineInContext(e){const t=(0,s.tempFile)(e);this.requireInContext(t.path);t.remove()}}t.VM=VM},734:e=>{"use strict";e.exports=e=>{const t=e[0]*1e9+e[1];const r=t/1e6;const n=t/1e9;return{seconds:n,milliseconds:r,nanoseconds:t}}},663:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:true});t.EdgeRuntime=void 0;const n=r(844);let o;let s;class EdgeRuntime extends n.EdgeVM{constructor(e){super({...e,extend:t=>{var r,n;return(n=(r=e===null||e===void 0?void 0:e.extend)===null||r===void 0?void 0:r.call(e,t))!==null&&n!==void 0?n:t}});defineHandlerProps({object:this,setterName:"__onUnhandledRejectionHandler",setter:e=>o=e,getterName:"__rejectionHandlers",getter:()=>o});defineHandlerProps({object:this,setterName:"__onErrorHandler",setter:e=>s=e,getterName:"__errorHandlers",getter:()=>s});Object.defineProperty(this.context,"EdgeRuntime",{configurable:false,enumerable:false,writable:false,value:"edge-runtime"});this.evaluate(getDefineEventListenersCode());this.dispatchFetch=this.evaluate(getDispatchFetchCode());if(e===null||e===void 0?void 0:e.initialCode){this.evaluate(e.initialCode)}}}t.EdgeRuntime=EdgeRuntime;process.on("unhandledRejection",(function invokeRejectionHandlers(e,t){o===null||o===void 0?void 0:o.forEach((r=>r(e,t)))}));process.on("uncaughtException",(function invokeErrorHandlers(e){s===null||s===void 0?void 0:s.forEach((t=>t(e)))}));function getDefineEventListenersCode(){return`\n Object.defineProperty(self, '__listeners', {\n configurable: false,\n enumerable: false,\n value: {},\n writable: true,\n })\n\n function __conditionallyUpdatesHandlerList(eventType) {\n if (eventType === 'unhandledrejection') {\n self.__onUnhandledRejectionHandler = self.__listeners[eventType];\n } else if (eventType === 'error') {\n self.__onErrorHandler = self.__listeners[eventType];\n }\n }\n\n function addEventListener(type, handler) {\n const eventType = type.toLowerCase();\n if (eventType === 'fetch' && self.__listeners.fetch) {\n throw new TypeError('You can register just one "fetch" event listener');\n }\n\n self.__listeners[eventType] = self.__listeners[eventType] || [];\n self.__listeners[eventType].push(handler);\n __conditionallyUpdatesHandlerList(eventType);\n }\n\n function removeEventListener(type, handler) {\n const eventType = type.toLowerCase();\n if (self.__listeners[eventType]) {\n self.__listeners[eventType] = self.__listeners[eventType].filter(item => {\n return item !== handler;\n });\n\n if (self.__listeners[eventType].length === 0) {\n delete self.__listeners[eventType];\n }\n }\n __conditionallyUpdatesHandlerList(eventType);\n }\n `}function getDispatchFetchCode(){return`(async function dispatchFetch(input, init) {\n const request = new Request(input, init);\n const event = new FetchEvent(request);\n if (!self.__listeners.fetch) {\n throw new Error("No fetch event listeners found");\n }\n\n const getResponse = ({ response, error }) => {\n if (error || !response || !(response instanceof Response)) {\n console.error(error ? error : 'The event listener did not respond')\n response = new Response(null, {\n statusText: 'Internal Server Error',\n status: 500\n })\n }\n\n response.waitUntil = () => Promise.all(event.awaiting);\n return response;\n }\n\n try {\n await self.__listeners.fetch[0].call(event, event)\n } catch (error) {\n return getResponse({ error })\n }\n\n return Promise.resolve(event.response)\n .then(response => getResponse({ response }))\n .catch(error => getResponse({ error }))\n })`}function defineHandlerProps({object:e,setterName:t,setter:r,getterName:n,getter:o}){Object.defineProperty(e.context,t,{set:r,configurable:false,enumerable:false});Object.defineProperty(e,n,{get:o,configurable:false,enumerable:false})}},353:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:true});t.getClonableBodyStream=void 0;const n=r(781);function getClonableBodyStream(e,t){let r=null;return{finalize(){if(r){replaceRequestBody(e,bodyStreamToNodeStream(r))}},cloneBodyStream(){const n=r!==null&&r!==void 0?r:requestToBodyStream(e,t);const[o,s]=n.tee();r=o;return s}}}t.getClonableBodyStream=getClonableBodyStream;function requestToBodyStream(e,t){const r=new t({start(t){e.on("data",(e=>t.enqueue(e)));e.on("end",(()=>t.terminate()));e.on("error",(e=>t.error(e)))}});return r.readable}function bodyStreamToNodeStream(e){const t=e.getReader();return n.Readable.from(async function*(){while(true){const{done:e,value:r}=await t.read();if(e){return}yield r}}())}function replaceRequestBody(e,t){for(const r in t){let n=t[r];if(typeof n==="function"){n=n.bind(t)}e[r]=n}return e}},564:function(e,t,r){"use strict";var n=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(t,"__esModule",{value:true});t.createHandler=void 0;const o=r(353);const s=n(r(720));const i=n(r(242));const a=n(r(504));function createHandler(e){const t=new Set;return{handler:async(r,n)=>{var E,c;const u=(0,a.default)();const _=r.method!=="GET"&&r.method!=="HEAD"?(0,o.getClonableBodyStream)(r,e.runtime.context.TransformStream):undefined;const d=await e.runtime.dispatchFetch(String(getURL(r)),{headers:toRequestInitHeaders(r),method:r.method,body:_===null||_===void 0?void 0:_.cloneBodyStream()});const l=d.waitUntil();t.add(l);l.finally((()=>t.delete(l)));n.statusCode=d.status;n.statusMessage=d.statusText;for(const[e,t]of Object.entries(toNodeHeaders(d.headers))){if(e!=="content-encoding"&&t!==undefined){n.setHeader(e,t)}}if(d.body){for await(const e of d.body){n.write(e)}}const S=`${r.socket.remoteAddress} ${r.method} ${r.url}`;const R=`${(E=(0,s.default)(u()).match(/[a-zA-Z]+|[0-9]+/g))===null||E===void 0?void 0:E.join(" ")}`;const h=`${n.statusCode} ${i.default[n.statusCode]}`;(c=e.logger)===null||c===void 0?void 0:c.debug(`${S} → ${h} in ${R}`);n.end()},waitUntil:()=>Promise.all(t)}}t.createHandler=createHandler;function getURL(e){var t;const r=((t=e.socket)===null||t===void 0?void 0:t.encrypted)?"https":"http";return new URL(String(e.url),`${r}://${String(e.headers.host)}`)}function toRequestInitHeaders(e){return Object.keys(e.headers).map((t=>{const r=e.headers[t];return[t,Array.isArray(r)?r.join(", "):r!==null&&r!==void 0?r:""]}))}function toNodeHeaders(e){const t={};if(e){for(const[r,n]of e.entries()){t[r]=r.toLowerCase()==="set-cookie"?e.getAll("set-cookie"):n}}return t}},179:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:true});t.runServer=t.createHandler=void 0;var n=r(564);Object.defineProperty(t,"createHandler",{enumerable:true,get:function(){return n.createHandler}});var o=r(82);Object.defineProperty(t,"runServer",{enumerable:true,get:function(){return o.runServer}})},82:function(e,t,r){"use strict";var n=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(t,"__esModule",{value:true});t.runServer=void 0;const o=r(564);const s=r(361);const i=n(r(685));async function runServer(e){const{handler:t,waitUntil:r}=(0,o.createHandler)(e);const n=i.default.createServer(t);n.listen(e.port);try{await(0,s.once)(n,"listening")}catch(t){if((t===null||t===void 0?void 0:t.code)==="EADDRINUSE"){return runServer({...e,port:undefined})}throw t}const a=n.address();const E=typeof a==="string"||a==null?String(a):`http://localhost:${a.port}`;return{url:E,close:async()=>{await r();await new Promise(((e,t)=>n.close((r=>{if(r)t(r);e()}))))},waitUntil:r}}t.runServer=runServer},242:e=>{var t;t={"1xx":"Informational","1xx_NAME":"INFORMATIONAL","1xx_MESSAGE":"Indicates an interim response for communicating connection status or request progress prior to completing the requested action and sending a final response.",INFORMATIONAL:"1xx","2xx":"Successful","2xx_NAME":"SUCCESSFUL","2xx_MESSAGE":"Indicates that the client's request was successfully received, understood, and accepted.",SUCCESSFUL:"2xx","3xx":"Redirection","3xx_NAME":"REDIRECTION","3xx_MESSAGE":"Indicates that further action needs to be taken by the user agent in order to fulfill the request.",REDIRECTION:"3xx","4xx":"Client Error","4xx_NAME":"CLIENT_ERROR","4xx_MESSAGE":"Indicates that the client seems to have erred.",CLIENT_ERROR:"4xx","5xx":"Server Error","5xx_NAME":"SERVER_ERROR","5xx_MESSAGE":"Indicates that the server is aware that it has erred or is incapable of performing the requested method.",SERVER_ERROR:"5xx"};e.exports={classes:t,100:"Continue","100_NAME":"CONTINUE","100_MESSAGE":"The server has received the request headers and the client should proceed to send the request body.","100_CLASS":t.INFORMATIONAL,CONTINUE:100,101:"Switching Protocols","101_NAME":"SWITCHING_PROTOCOLS","101_MESSAGE":"The requester has asked the server to switch protocols and the server has agreed to do so.","101_CLASS":t.INFORMATIONAL,SWITCHING_PROTOCOLS:101,102:"Processing","102_NAME":"PROCESSING","102_MESSAGE":"A WebDAV request may contain many sub-requests involving file operations, requiring a long time to complete the request. This code indicates that the server has received and is processing the request, but no response is available yet.[7] This prevents the client from timing out and assuming the request was lost.","102_CLASS":t.INFORMATIONAL,PROCESSING:102,103:"Early Hints","103_NAME":"EARLY_HINTS","103_MESSAGE":"Used to return some response headers before final HTTP message.","103_CLASS":t.INFORMATIONAL,EARLY_HINTS:103,200:"OK","200_NAME":"OK","200_MESSAGE":"Standard response for successful HTTP requests.","200_CLASS":t.SUCCESSFUL,OK:200,201:"Created","201_NAME":"CREATED","201_MESSAGE":"The request has been fulfilled, resulting in the creation of a new resource.","201_CLASS":t.SUCCESSFUL,CREATED:201,202:"Accepted","202_NAME":"ACCEPTED","202_MESSAGE":"The request has been accepted for processing, but the processing has not been completed.","202_CLASS":t.SUCCESSFUL,ACCEPTED:202,203:"Non-Authoritative Information","203_NAME":"NON_AUTHORITATIVE_INFORMATION","203_MESSAGE":"The server is a transforming proxy (e.g. a Web accelerator) that received a 200 OK from its origin, but is returning a modified version of the origin's response.","203_CLASS":t.SUCCESSFUL,NON_AUTHORITATIVE_INFORMATION:203,204:"No Content","204_NAME":"NO_CONTENT","204_MESSAGE":"The server successfully processed the request and is not returning any content.","204_CLASS":t.SUCCESSFUL,NO_CONTENT:204,205:"Reset Content","205_NAME":"RESET_CONTENT","205_MESSAGE":"The server successfully processed the request, but is not returning any content. Unlike a 204 response, this response requires that the requester reset the document view.","205_CLASS":t.SUCCESSFUL,RESET_CONTENT:205,206:"Partial Content","206_NAME":"PARTIAL_CONTENT","206_MESSAGE":"The server is delivering only part of the resource (byte serving) due to a range header sent by the client.","206_CLASS":t.SUCCESSFUL,PARTIAL_CONTENT:206,207:"Multi Status","207_NAME":"MULTI_STATUS","207_MESSAGE":"The message body that follows is by default an XML message and can contain a number of separate response codes, depending on how many sub-requests were made.","207_CLASS":t.SUCCESSFUL,MULTI_STATUS:207,208:"Already Reported","208_NAME":"ALREADY_REPORTED","208_MESSAGE":"The members of a DAV binding have already been enumerated in a preceding part of the (multistatus) response, and are not being included again.","208_CLASS":t.SUCCESSFUL,ALREADY_REPORTED:208,226:"IM Used","226_NAME":"IM_USED","226_MESSAGE":"The server has fulfilled a request for the resource, and the response is a representation of the result of one or more instance-manipulations applied to the current instance.","226_CLASS":t.SUCCESSFUL,IM_USED:226,300:"Multiple Choices","300_NAME":"MULTIPLE_CHOICES","300_MESSAGE":"Indicates multiple options for the resource from which the client may choose.","300_CLASS":t.REDIRECTION,MULTIPLE_CHOICES:300,301:"Moved Permanently","301_NAME":"MOVED_PERMANENTLY","301_MESSAGE":"This and all future requests should be directed to the given URI.","301_CLASS":t.REDIRECTION,MOVED_PERMANENTLY:301,302:"Found","302_NAME":"FOUND","302_MESSAGE":'This is an example of industry practice contradicting the standard. The HTTP/1.0 specification (RFC 1945) required the client to perform a temporary redirect (the original describing phrase was "Moved Temporarily"), but popular browsers implemented 302 with the functionality of a 303 See Other. Therefore, HTTP/1.1 added status codes 303 and 307 to distinguish between the two behaviours.',"302_CLASS":t.REDIRECTION,FOUND:302,303:"See Other","303_NAME":"SEE_OTHER","303_MESSAGE":"The response to the request can be found under another URI using the GET method.","303_CLASS":t.REDIRECTION,SEE_OTHER:303,304:"Not Modified","304_NAME":"NOT_MODIFIED","304_MESSAGE":"Indicates that the resource has not been modified since the version specified by the request headers If-Modified-Since or If-None-Match.","304_CLASS":t.REDIRECTION,NOT_MODIFIED:304,305:"Use Proxy","305_NAME":"USE_PROXY","305_MESSAGE":"The requested resource is available only through a proxy, the address for which is provided in the response.","305_CLASS":t.REDIRECTION,USE_PROXY:305,306:"Switch Proxy","306_NAME":"SWITCH_PROXY","306_MESSAGE":'No longer used. Originally meant "Subsequent requests should use the specified proxy.',"306_CLASS":t.REDIRECTION,SWITCH_PROXY:306,307:"Temporary Redirect","307_NAME":"TEMPORARY_REDIRECT","307_MESSAGE":"In this case, the request should be repeated with another URI; however, future requests should still use the original URI.","307_CLASS":t.REDIRECTION,TEMPORARY_REDIRECT:307,308:"Permanent Redirect","308_NAME":"PERMANENT_REDIRECT","308_MESSAGE":"The request and all future requests should be repeated using another URI.","308_CLASS":t.REDIRECTION,PERMANENT_REDIRECT:308,400:"Bad Request","400_NAME":"BAD_REQUEST","400_MESSAGE":"The server cannot or will not process the request due to an apparent client error.","400_CLASS":t.CLIENT_ERROR,BAD_REQUEST:400,401:"Unauthorized","401_NAME":"UNAUTHORIZED","401_MESSAGE":"Similar to 403 Forbidden, but specifically for use when authentication is required and has failed or has not yet been provided.","401_CLASS":t.CLIENT_ERROR,UNAUTHORIZED:401,402:"Payment Required","402_NAME":"PAYMENT_REQUIRED","402_MESSAGE":"Reserved for future use. The original intention was that this code might be used as part of some form of digital cash or micropayment scheme, as proposed for example by GNU Taler, but that has not yet happened, and this code is not usually used.","402_CLASS":t.CLIENT_ERROR,PAYMENT_REQUIRED:402,403:"Forbidden","403_NAME":"FORBIDDEN","403_MESSAGE":"The request was valid, but the server is refusing action.","403_CLASS":t.CLIENT_ERROR,FORBIDDEN:403,404:"Not Found","404_NAME":"NOT_FOUND","404_MESSAGE":"The requested resource could not be found but may be available in the future. Subsequent requests by the client are permissible.","404_CLASS":t.CLIENT_ERROR,NOT_FOUND:404,405:"Method Not Allowed","405_NAME":"METHOD_NOT_ALLOWED","405_MESSAGE":"A request method is not supported for the requested resource.","405_CLASS":t.CLIENT_ERROR,METHOD_NOT_ALLOWED:405,406:"Not Acceptable","406_NAME":"NOT_ACCEPTABLE","406_MESSAGE":"The requested resource is capable of generating only content not acceptable according to the Accept headers sent in the request.","406_CLASS":t.CLIENT_ERROR,NOT_ACCEPTABLE:406,407:"Proxy Authentication Required","407_NAME":"PROXY_AUTHENTICATION_REQUIRED","407_MESSAGE":"The client must first authenticate itself with the proxy.","407_CLASS":t.CLIENT_ERROR,PROXY_AUTHENTICATION_REQUIRED:407,408:"Request Time-out","408_NAME":"REQUEST_TIMEOUT","408_MESSAGE":"The server timed out waiting for the request.","408_CLASS":t.CLIENT_ERROR,REQUEST_TIMEOUT:408,409:"Conflict","409_NAME":"CONFLICT","409_MESSAGE":"Indicates that the request could not be processed because of conflict in the request, such as an edit conflict between multiple simultaneous updates.","409_CLASS":t.CLIENT_ERROR,CONFLICT:409,410:"Gone","410_NAME":"GONE","410_MESSAGE":"Indicates that the resource requested is no longer available and will not be available again.","410_CLASS":t.CLIENT_ERROR,GONE:410,411:"Length Required","411_NAME":"LENGTH_REQUIRED","411_MESSAGE":"The request did not specify the length of its content, which is required by the requested resource.","411_CLASS":t.CLIENT_ERROR,LENGTH_REQUIRED:411,412:"Precondition Failed","412_NAME":"PRECONDITION_FAILED","412_MESSAGE":"The server does not meet one of the preconditions that the requester put on the request.","412_CLASS":t.CLIENT_ERROR,PRECONDITION_FAILED:412,413:"Request Entity Too Large","413_NAME":"REQUEST_ENTITY_TOO_LARGE","413_MESSAGE":'The request is larger than the server is willing or able to process. Previously called "Request Entity Too Large".',"413_CLASS":t.CLIENT_ERROR,REQUEST_ENTITY_TOO_LARGE:413,414:"Request-URI Too Large","414_NAME":"REQUEST_URI_TOO_LONG","414_MESSAGE":"The URI provided was too long for the server to process.","414_CLASS":t.CLIENT_ERROR,REQUEST_URI_TOO_LONG:414,415:"Unsupported Media Type","415_NAME":"UNSUPPORTED_MEDIA_TYPE","415_MESSAGE":"The request entity has a media type which the server or resource does not support.","415_CLASS":t.CLIENT_ERROR,UNSUPPORTED_MEDIA_TYPE:415,416:"Requested Range not Satisfiable","416_NAME":"REQUESTED_RANGE_NOT_SATISFIABLE","416_MESSAGE":"The client has asked for a portion of the file (byte serving), but the server cannot supply that portion.","416_CLASS":t.CLIENT_ERROR,REQUESTED_RANGE_NOT_SATISFIABLE:416,417:"Expectation Failed","417_NAME":"EXPECTATION_FAILED","417_MESSAGE":"The server cannot meet the requirements of the Expect request-header field.","417_CLASS":t.CLIENT_ERROR,EXPECTATION_FAILED:417,418:"I'm a teapot","418_NAME":"IM_A_TEAPOT","418_MESSAGE":'Any attempt to brew coffee with a teapot should result in the error code "418 I\'m a teapot". The resulting entity body MAY be short and stout.',"418_CLASS":t.CLIENT_ERROR,IM_A_TEAPOT:418,421:"Misdirected Request","421_NAME":"MISDIRECTED_REQUEST","421_MESSAGE":"The request was directed at a server that is not able to produce a response.","421_CLASS":t.CLIENT_ERROR,MISDIRECTED_REQUEST:421,422:"Unprocessable Entity","422_NAME":"UNPROCESSABLE_ENTITY","422_MESSAGE":"The request was well-formed but was unable to be followed due to semantic errors.","422_CLASS":t.CLIENT_ERROR,UNPROCESSABLE_ENTITY:422,423:"Locked","423_NAME":"LOCKED","423_MESSAGE":"The resource that is being accessed is locked.","423_CLASS":t.CLIENT_ERROR,LOCKED:423,424:"Failed Dependency","424_NAME":"FAILED_DEPENDENCY","424_MESSAGE":"The request failed because it depended on another request and that request failed.","424_CLASS":t.CLIENT_ERROR,FAILED_DEPENDENCY:424,426:"Upgrade Required","426_NAME":"UPGRADE_REQUIRED","426_MESSAGE":"The client should switch to a different protocol such as TLS/1.0, given in the Upgrade header field.","426_CLASS":t.CLIENT_ERROR,UPGRADE_REQUIRED:426,428:"Precondition Required","428_NAME":"PRECONDITION_REQUIRED","428_MESSAGE":"The origin server requires the request to be conditional.","428_CLASS":t.CLIENT_ERROR,PRECONDITION_REQUIRED:428,429:"Too Many Requests","429_NAME":"TOO_MANY_REQUESTS","429_MESSAGE":"The user has sent too many requests in a given amount of time.","429_CLASS":t.CLIENT_ERROR,TOO_MANY_REQUESTS:429,431:"Request Header Fields Too Large","431_NAME":"REQUEST_HEADER_FIELDS_TOO_LARGE","431_MESSAGE":"The server is unwilling to process the request because either an individual header field, or all the header fields collectively, are too large.","431_CLASS":t.CLIENT_ERROR,REQUEST_HEADER_FIELDS_TOO_LARGE:431,451:"Unavailable For Legal Reasons","451_NAME":"UNAVAILABLE_FOR_LEGAL_REASONS","451_MESSAGE":"A server operator has received a legal demand to deny access to a resource or to a set of resources that includes the requested resource.","451_CLASS":t.CLIENT_ERROR,UNAVAILABLE_FOR_LEGAL_REASONS:451,500:"Internal Server Error","500_NAME":"INTERNAL_SERVER_ERROR","500_MESSAGE":"A generic error message, given when an unexpected condition was encountered and no more specific message is suitable.","500_CLASS":t.SERVER_ERROR,INTERNAL_SERVER_ERROR:500,501:"Not Implemented","501_NAME":"NOT_IMPLEMENTED","501_MESSAGE":"The server either does not recognize the request method, or it lacks the ability to fulfil the request. Usually this implies future availability.","501_CLASS":t.SERVER_ERROR,NOT_IMPLEMENTED:501,502:"Bad Gateway","502_NAME":"BAD_GATEWAY","502_MESSAGE":"The server was acting as a gateway or proxy and received an invalid response from the upstream server.","502_CLASS":t.SERVER_ERROR,BAD_GATEWAY:502,503:"Service Unavailable","503_NAME":"SERVICE_UNAVAILABLE","503_MESSAGE":"The server is currently unavailable (because it is overloaded or down for maintenance). Generally, this is a temporary state.","503_CLASS":t.SERVER_ERROR,SERVICE_UNAVAILABLE:503,504:"Gateway Time-out","504_NAME":"GATEWAY_TIMEOUT","504_MESSAGE":"The server was acting as a gateway or proxy and did not receive a timely response from the upstream server.","504_CLASS":t.SERVER_ERROR,GATEWAY_TIMEOUT:504,505:"HTTP Version not Supported","505_NAME":"HTTP_VERSION_NOT_SUPPORTED","505_MESSAGE":"The server does not support the HTTP protocol version used in the request.","505_CLASS":t.SERVER_ERROR,HTTP_VERSION_NOT_SUPPORTED:505,506:"Variant Also Negotiates","506_NAME":"VARIANT_ALSO_NEGOTIATES","506_MESSAGE":"Transparent content negotiation for the request results in a circular reference.","506_CLASS":t.SERVER_ERROR,VARIANT_ALSO_NEGOTIATES:506,507:"Insufficient Storage","507_NAME":"INSUFFICIENT_STORAGE","507_MESSAGE":"The server is unable to store the representation needed to complete the request.","507_CLASS":t.SERVER_ERROR,INSUFFICIENT_STORAGE:507,508:"Loop Detected","508_NAME":"LOOP_DETECTED","508_MESSAGE":"The server detected an infinite loop while processing the request.","508_CLASS":t.SERVER_ERROR,LOOP_DETECTED:508,510:"Not Extended","510_NAME":"NOT_EXTENDED","510_MESSAGE":"Further extensions to the request are required for the server to fulfil it.","510_CLASS":t.SERVER_ERROR,NOT_EXTENDED:510,511:"Network Authentication Required","511_NAME":"NETWORK_AUTHENTICATION_REQUIRED","511_MESSAGE":"The client needs to authenticate to gain network access. Intended for use by intercepting proxies used to control access to the network.","511_CLASS":t.SERVER_ERROR,NETWORK_AUTHENTICATION_REQUIRED:511,extra:{unofficial:{103:"Checkpoint","103_NAME":"CHECKPOINT","103_MESSAGE":"Used in the resumable requests proposal to resume aborted PUT or POST requests.","103_CLASS":t.INFORMATIONAL,CHECKPOINT:103,419:"Page Expired","419_NAME":"PAGE_EXPIRED","419_MESSAGE":"Used by the Laravel Framework when a CSRF Token is missing or expired.","419_CLASS":t.CLIENT_ERROR,PAGE_EXPIRED:419,218:"This is fine","218_NAME":"THIS_IS_FINE","218_MESSAGE":"Used as a catch-all error condition for allowing response bodies to flow through Apache when ProxyErrorOverride is enabled. When ProxyErrorOverride is enabled in Apache, response bodies that contain a status code of 4xx or 5xx are automatically discarded by Apache in favor of a generic response or a custom response specified by the ErrorDocument directive.","218_CLASS":t.SUCCESSFUL,THIS_IS_FINE:218,420:"Enhance Your Calm","420_NAME":"ENHANCE_YOUR_CALM","420_MESSAGE":"Returned by version 1 of the Twitter Search and Trends API when the client is being rate limited; versions 1.1 and later use the 429 Too Many Requests response code instead.","420_CLASS":t.CLIENT_ERROR,ENHANCE_YOUR_CALM:420,450:"Blocked by Windows Parental Controls","450_NAME":"BLOCKED_BY_WINDOWS_PARENTAL_CONTROLS","450_MESSAGE":"The Microsoft extension code indicated when Windows Parental Controls are turned on and are blocking access to the requested webpage.","450_CLASS":t.CLIENT_ERROR,BLOCKED_BY_WINDOWS_PARENTAL_CONTROLS:450,498:"Invalid Token","498_NAME":"INVALID_TOKEN","498_MESSAGE":"Returned by ArcGIS for Server. Code 498 indicates an expired or otherwise invalid token.","498_CLASS":t.CLIENT_ERROR,INVALID_TOKEN:498,499:"Token Required","499_NAME":"TOKEN_REQUIRED","499_MESSAGE":"Returned by ArcGIS for Server. Code 499 indicates that a token is required but was not submitted.","499_CLASS":t.CLIENT_ERROR,TOKEN_REQUIRED:499,509:"Bandwidth Limit Exceeded","509_NAME":"BANDWIDTH_LIMIT_EXCEEDED","509_MESSAGE":"The server has exceeded the bandwidth specified by the server administrator.","509_CLASS":t.SERVER_ERROR,BANDWIDTH_LIMIT_EXCEEDED:509,530:"Site is frozen","530_NAME":"SITE_IS_FROZEN","530_MESSAGE":"Used by the Pantheon web platform to indicate a site that has been frozen due to inactivity.","530_CLASS":t.SERVER_ERROR,SITE_IS_FROZEN:530,598:"Network read timeout error","598_NAME":"NETWORK_READ_TIMEOUT_ERROR","598_MESSAGE":"Used by some HTTP proxies to signal a network read timeout behind the proxy to a client in front of the proxy.","598_CLASS":t.SERVER_ERROR,NETWORK_READ_TIMEOUT_ERROR:598},iis:{440:"Login Time-out","440_NAME":"LOGIN_TIME_OUT","440_MESSAGE":"The client's session has expired and must log in again.","440_CLASS":t.CLIENT_ERROR,LOGIN_TIME_OUT:440,449:"Retry With","449_NAME":"RETRY_WITH","449_MESSAGE":"The server cannot honour the request because the user has not provided the required information.","449_CLASS":t.CLIENT_ERROR,RETRY_WITH:449,451:"Redirect","451_NAME":"REDIRECT","451_MESSAGE":"Used in Exchange ActiveSync when either a more efficient server is available or the server cannot access the users' mailbox.","451_CLASS":t.CLIENT_ERROR,REDIRECT:451},nginx:{444:"No Response","444_NAME":"NO_RESPONSE","444_MESSAGE":"Used internally to instruct the server to return no information to the client and close the connection immediately.","444_CLASS":t.CLIENT_ERROR,NO_RESPONSE:444,494:"Request header too large","494_NAME":"REQUEST_HEADER_TOO_LARGE","494_MESSAGE":"Client sent too large request or too long header line.","494_CLASS":t.CLIENT_ERROR,REQUEST_HEADER_TOO_LARGE:494,495:"SSL Certificate Error","495_NAME":"SSL_CERTIFICATE_ERROR","495_MESSAGE":"An expansion of the 400 Bad Request response code, used when the client has provided an invalid client certificate.","495_CLASS":t.CLIENT_ERROR,SSL_CERTIFICATE_ERROR:495,496:"SSL Certificate Required","496_NAME":"SSL_CERTIFICATE_REQUIRED","496_MESSAGE":"An expansion of the 400 Bad Request response code, used when a client certificate is required but not provided.","496_CLASS":t.CLIENT_ERROR,SSL_CERTIFICATE_REQUIRED:496,497:"HTTP Request Sent to HTTPS Port","497_NAME":"HTTP_REQUEST_SENT_TO_HTTPS_PORT","497_MESSAGE":"An expansion of the 400 Bad Request response code, used when the client has made a HTTP request to a port listening for HTTPS requests.","497_CLASS":t.CLIENT_ERROR,HTTP_REQUEST_SENT_TO_HTTPS_PORT:497,499:"Client Closed Request","499_NAME":"CLIENT_CLOSED_REQUEST","499_MESSAGE":"Used when the client has closed the request before the server could send a response.","499_CLASS":t.CLIENT_ERROR,CLIENT_CLOSED_REQUEST:499},cloudflare:{520:"Unknown Error","520_NAME":"UNKNOWN_ERROR","520_MESSAGE":'The 520 error is used as a "catch-all response for when the origin server returns something unexpected", listing connection resets, large headers, and empty or invalid responses as common triggers.',"520_CLASS":t.SERVER_ERROR,UNKNOWN_ERROR:520,521:"Web Server Is Down","521_NAME":"WEB_SERVER_IS_DOWN","521_MESSAGE":"The origin server has refused the connection from Cloudflare.","521_CLASS":t.SERVER_ERROR,WEB_SERVER_IS_DOWN:521,522:"Connection Timed Out","522_NAME":"CONNECTION_TIMED_OUT","522_MESSAGE":"Cloudflare could not negotiate a TCP handshake with the origin server.","522_CLASS":t.SERVER_ERROR,CONNECTION_TIMED_OUT:522,523:"Origin Is Unreachable","523_NAME":"ORIGIN_IS_UNREACHABLE","523_MESSAGE":"Cloudflare could not reach the origin server.","523_CLASS":t.SERVER_ERROR,ORIGIN_IS_UNREACHABLE:523,524:"A Timeout Occurred","524_NAME":"A_TIMEOUT_OCCURRED","524_MESSAGE":"Cloudflare was able to complete a TCP connection to the origin server, but did not receive a timely HTTP response.","524_CLASS":t.SERVER_ERROR,A_TIMEOUT_OCCURRED:524,525:"SSL Handshake Failed","525_NAME":"SSL_HANDSHAKE_FAILED","525_MESSAGE":"Cloudflare could not negotiate a SSL/TLS handshake with the origin server.","525_CLASS":t.SERVER_ERROR,SSL_HANDSHAKE_FAILED:525,526:"Invalid SSL Certificate","526_NAME":"INVALID_SSL_CERTIFICATE","526_MESSAGE":"Cloudflare could not validate the SSL/TLS certificate that the origin server presented.","526_CLASS":t.SERVER_ERROR,INVALID_SSL_CERTIFICATE:526,527:"Railgun Error","527_NAME":"RAILGUN_ERROR","527_MESSAGE":"Error 527 indicates that the request timed out or failed after the WAN connection had been established.","527_CLASS":t.SERVER_ERROR,RAILGUN_ERROR:527}}}},672:e=>{"use strict";e.exports=e=>{if(typeof e!=="number"){throw new TypeError("Expected a number")}const t=e>0?Math.floor:Math.ceil;return{days:t(e/864e5),hours:t(e/36e5)%24,minutes:t(e/6e4)%60,seconds:t(e/1e3)%60,milliseconds:t(e)%1e3,microseconds:t(e*1e3)%1e3,nanoseconds:t(e*1e6)%1e3}}},720:(e,t,r)=>{"use strict";const n=r(672);const pluralize=(e,t)=>t===1?e:`${e}s`;const o=1e-7;e.exports=(e,t={})=>{if(!Number.isFinite(e)){throw new TypeError("Expected a finite number")}if(t.colonNotation){t.compact=false;t.formatSubMilliseconds=false;t.separateMilliseconds=false;t.verbose=false}if(t.compact){t.secondsDecimalDigits=0;t.millisecondsDecimalDigits=0}const r=[];const floorDecimals=(e,t)=>{const r=Math.floor(e*10**t+o);const n=Math.round(r)/10**t;return n.toFixed(t)};const add=(e,n,o,s)=>{if((r.length===0||!t.colonNotation)&&e===0&&!(t.colonNotation&&o==="m")){return}s=(s||e||"0").toString();let i;let a;if(t.colonNotation){i=r.length>0?":":"";a="";const e=s.includes(".")?s.split(".")[0].length:s.length;const t=r.length>0?2:1;s="0".repeat(Math.max(0,t-e))+s}else{i="";a=t.verbose?" "+pluralize(n,e):o}r.push(i+s+a)};const s=n(e);add(Math.trunc(s.days/365),"year","y");add(s.days%365,"day","d");add(s.hours,"hour","h");add(s.minutes,"minute","m");if(t.separateMilliseconds||t.formatSubMilliseconds||!t.colonNotation&&e<1e3){add(s.seconds,"second","s");if(t.formatSubMilliseconds){add(s.milliseconds,"millisecond","ms");add(s.microseconds,"microsecond","µs");add(s.nanoseconds,"nanosecond","ns")}else{const e=s.milliseconds+s.microseconds/1e3+s.nanoseconds/1e6;const r=typeof t.millisecondsDecimalDigits==="number"?t.millisecondsDecimalDigits:0;const n=e>=1?Math.round(e):Math.ceil(e);const o=r?e.toFixed(r):n;add(Number.parseFloat(o,10),"millisecond","ms",o)}}else{const r=e/1e3%60;const n=typeof t.secondsDecimalDigits==="number"?t.secondsDecimalDigits:1;const o=floorDecimals(r,n);const s=t.keepDecimalsOnWholeSeconds?o:o.replace(/\.0+$/,"");add(Number.parseFloat(s,10),"second","s",s)}if(r.length===0){return"0"+(t.verbose?" milliseconds":"ms")}if(t.compact){return r[0]}if(typeof t.unitCount==="number"){const e=t.colonNotation?"":" ";return r.slice(0,Math.max(t.unitCount,1)).join(e)}return t.colonNotation?r.join(""):r.join(" ")}},504:(e,t,r)=>{"use strict";const n=r(734);e.exports=()=>{const e=process.hrtime();const end=t=>n(process.hrtime(e))[t];const returnValue=()=>end("milliseconds");returnValue.rounded=()=>Math.round(end("milliseconds"));returnValue.seconds=()=>end("seconds");returnValue.nanoseconds=()=>end("nanoseconds");return returnValue}},491:e=>{"use strict";e.exports=require("assert")},300:e=>{"use strict";e.exports=require("buffer")},113:e=>{"use strict";e.exports=require("crypto")},361:e=>{"use strict";e.exports=require("events")},147:e=>{"use strict";e.exports=require("fs")},685:e=>{"use strict";e.exports=require("http")},808:e=>{"use strict";e.exports=require("net")},277:e=>{"use strict";e.exports=require("next/dist/compiled/@edge-runtime/primitives/crypto")},37:e=>{"use strict";e.exports=require("os")},17:e=>{"use strict";e.exports=require("path")},74:e=>{"use strict";e.exports=require("perf_hooks")},282:e=>{"use strict";e.exports=require("process")},477:e=>{"use strict";e.exports=require("punycode")},781:e=>{"use strict";e.exports=require("stream")},404:e=>{"use strict";e.exports=require("tls")},837:e=>{"use strict";e.exports=require("util")},144:e=>{"use strict";e.exports=require("vm")},796:e=>{"use strict";e.exports=require("zlib")}};var __webpack_module_cache__={};function __nccwpck_require__(e){var t=__webpack_module_cache__[e];if(t!==undefined){return t.exports}var r=__webpack_module_cache__[e]={exports:{}};var n=true;try{__webpack_modules__[e].call(r.exports,r,r.exports,__nccwpck_require__);n=false}finally{if(n)delete __webpack_module_cache__[e]}return r.exports}if(typeof __nccwpck_require__!=="undefined")__nccwpck_require__.ab=__dirname+"/";var __webpack_exports__={};(()=>{"use strict";var e=__webpack_exports__;Object.defineProperty(e,"__esModule",{value:true});e.EdgeRuntime=e.runServer=e.createHandler=void 0;var t=__nccwpck_require__(179);Object.defineProperty(e,"createHandler",{enumerable:true,get:function(){return t.createHandler}});Object.defineProperty(e,"runServer",{enumerable:true,get:function(){return t.runServer}});var r=__nccwpck_require__(663);Object.defineProperty(e,"EdgeRuntime",{enumerable:true,get:function(){return r.EdgeRuntime}})})();module.exports=__webpack_exports__})(); \ No newline at end of file +(()=>{var __webpack_modules__={162:(__unused_webpack_module,exports,__nccwpck_require__)=>{"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.EdgeVM=void 0;const buffer_1=__nccwpck_require__(300);const require_1=__nccwpck_require__(986);const vm_1=__nccwpck_require__(218);const vm_2=__nccwpck_require__(144);class EdgeVM extends vm_1.VM{constructor(e={}){super({...e,extend:t=>e.extend?e.extend(addPrimitives(t)):addPrimitives(t)})}}exports.EdgeVM=EdgeVM;function addPrimitives(context){defineProperty(context,"self",{enumerable:true,value:context});defineProperty(context,"globalThis",{value:context});defineProperty(context,"Symbol",{value:Symbol});defineProperty(context,"clearInterval",{value:clearInterval});defineProperty(context,"clearTimeout",{value:clearTimeout});defineProperty(context,"setInterval",{value:setInterval});defineProperty(context,"setTimeout",{value:setTimeout});defineProperty(context,"EdgeRuntime",{value:"edge-runtime"});defineProperties(context,{exports:(0,require_1.requireWithCache)({context:context,path:require.resolve("next/dist/compiled/@edge-runtime/primitives/console"),scopedContext:{console:console}}),nonenumerable:["console"]});const encodings=(0,require_1.requireWithCache)({context:context,path:require.resolve("next/dist/compiled/@edge-runtime/primitives/encoding"),scopedContext:{Buffer:buffer_1.Buffer,global:{ArrayBuffer:ArrayBuffer}}});defineProperties(context,{exports:encodings,nonenumerable:["atob","btoa","TextEncoder","TextDecoder"]});const streams=(0,require_1.requireWithCache)({path:require.resolve("next/dist/compiled/@edge-runtime/primitives/streams"),context:context});defineProperties(context,{exports:streams,nonenumerable:["ReadableStream","ReadableStreamBYOBReader","ReadableStreamDefaultReader","TransformStream","WritableStream","WritableStreamDefaultWriter"]});const abort=(0,require_1.requireWithCache)({context:context,path:require.resolve("next/dist/compiled/@edge-runtime/primitives/abort-controller")});defineProperties(context,{exports:abort,nonenumerable:["AbortController","AbortSignal"]});defineProperties(context,{exports:(0,require_1.requireWithCache)({cache:new Map([["punycode",{exports:__nccwpck_require__(477)}]]),context:context,path:require.resolve("next/dist/compiled/@edge-runtime/primitives/url"),scopedContext:{TextEncoder:encodings.TextEncoder,TextDecoder:encodings.TextDecoder}}),nonenumerable:["URL","URLSearchParams","URLPattern"]});const blob=(0,require_1.requireWithCache)({context:context,path:require.resolve("next/dist/compiled/@edge-runtime/primitives/blob")});defineProperties(context,{exports:blob,nonenumerable:["Blob"]});const webFetch=(0,require_1.requireWithCache)({context:context,cache:new Map([["abort-controller",{exports:abort}],["assert",{exports:__nccwpck_require__(491)}],["buffer",{exports:__nccwpck_require__(300)}],["events",{exports:__nccwpck_require__(361)}],["http",{exports:__nccwpck_require__(685)}],["net",{exports:__nccwpck_require__(808)}],["perf_hooks",{exports:__nccwpck_require__(74)}],["stream",{exports:__nccwpck_require__(781)}],["tls",{exports:__nccwpck_require__(404)}],["util",{exports:__nccwpck_require__(837)}],["zlib",{exports:__nccwpck_require__(796)}],[require.resolve("next/dist/compiled/@edge-runtime/primitives/streams"),{exports:streams}],[require.resolve("next/dist/compiled/@edge-runtime/primitives/blob"),{exports:blob}]]),path:require.resolve("next/dist/compiled/@edge-runtime/primitives/fetch"),scopedContext:{Buffer:buffer_1.Buffer,FinalizationRegistry:function(){return{register:function(){}}},global:{},queueMicrotask:queueMicrotask,setImmediate:setImmediate}});defineProperties(context,{exports:webFetch,nonenumerable:["fetch","File","FormData","Headers","Request","Response"]});defineProperties(context,{exports:(0,require_1.requireWithCache)({cache:new Map([[require.resolve("next/dist/compiled/@edge-runtime/primitives/fetch"),{exports:webFetch}]]),context:context,path:require.resolve("next/dist/compiled/@edge-runtime/primitives/cache"),scopedContext:{global:{}}}),enumerable:["caches"],nonenumerable:["Cache","CacheStorage"]});defineProperties(context,{exports:(0,require_1.requireWithCache)({context:context,cache:new Map([["crypto",{exports:__nccwpck_require__(113)}],["process",{exports:__nccwpck_require__(282)}]]),path:require.resolve("next/dist/compiled/@edge-runtime/primitives/crypto"),scopedContext:{Buffer:buffer_1.Buffer,Uint8Array:new Proxy((0,vm_2.runInContext)("Uint8Array",context),{construct(e,t){const r=new e(...t);if(!(t[0]instanceof buffer_1.Buffer)){return r}return new e([...r])}})}}),enumerable:["crypto"],nonenumerable:["Crypto","CryptoKey","SubtleCrypto"]});defineProperties(context,{exports:(0,require_1.requireWithCache)({context:context,path:require.resolve("next/dist/compiled/@edge-runtime/primitives/events")}),nonenumerable:["Event","EventTarget","FetchEvent","PromiseRejectionEvent"]});defineProperties(context,{exports:(0,require_1.requireWithCache)({context:context,path:require.resolve("next/dist/compiled/@edge-runtime/primitives/structured-clone")}),nonenumerable:["structuredClone"]});return context}function defineProperty(e,t,r){var n,o,s;Object.defineProperty(e,t,{configurable:(n=r.configurable)!==null&&n!==void 0?n:false,enumerable:(o=r.enumerable)!==null&&o!==void 0?o:false,value:r.value,writable:(s=r.writable)!==null&&s!==void 0?s:true})}function defineProperties(e,t){var r,n;for(const n of(r=t.enumerable)!==null&&r!==void 0?r:[]){if(!t.exports[n]){throw new Error(`Attempt to export a nullable value for "${n}"`)}defineProperty(e,n,{enumerable:true,value:t.exports[n]})}for(const r of(n=t.nonenumerable)!==null&&n!==void 0?n:[]){if(!t.exports[r]){throw new Error(`Attempt to export a nullable value for "${r}"`)}defineProperty(e,r,{value:t.exports[r]})}}},135:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:true});t.VM=t.EdgeVM=void 0;var n=r(162);Object.defineProperty(t,"EdgeVM",{enumerable:true,get:function(){return n.EdgeVM}});var o=r(218);Object.defineProperty(t,"VM",{enumerable:true,get:function(){return o.VM}})},986:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:true});t.requireWithCache=t.createRequire=t.requireDependencies=void 0;const n=r(147);const o=r(144);const s=r(17);function requireDependencies(e){const{context:t,requireCache:r,dependencies:n}=e;const o=createRequire(t,r);for(const{path:e,mapExports:r}of n){const n=o(e,e);for(const e of Object.keys(r)){t[r[e]]=n[e]}}}t.requireDependencies=requireDependencies;function createRequire(e,t,r,i={}){return function requireFn(a,E){const c=require.resolve(E,{paths:[(0,s.dirname)(a)]});const u=t.get(E)||t.get(c);if(u!==undefined&&u!==null){return u.exports}const _={exports:{},loaded:false,id:c};t.set(c,_);r===null||r===void 0?void 0:r.add(c);const d=(0,o.runInContext)(`(function(module,exports,require,__dirname,__filename,${Object.keys(i).join(",")}) {${(0,n.readFileSync)(c,"utf-8")}\n})`,e);try{d(_,_.exports,requireFn.bind(null,c),(0,s.dirname)(c),c,...Object.values(i))}catch(e){t.delete(c);throw e}_.loaded=true;return _.exports}}t.createRequire=createRequire;function requireWithCache(e){var t;return createRequire(e.context,(t=e.cache)!==null&&t!==void 0?t:new Map,e.references,e.scopedContext).call(null,e.path,e.path)}t.requireWithCache=requireWithCache},266:function(e,t,r){"use strict";var n=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(t,"__esModule",{value:true});t.tempFile=void 0;const o=r(277);const s=n(r(17));const i=n(r(147));const a=n(r(37));function tempFile(e){const t=s.default.join(a.default.tmpdir(),o.crypto.randomUUID());i.default.writeFileSync(t,e);return{path:t,remove:()=>i.default.unlinkSync(t)}}t.tempFile=tempFile},218:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:true});t.VM=void 0;const n=r(144);const o=r(986);const s=r(266);class VM{constructor(e={}){var t,r,s,i;const a=(0,n.createContext)({},{name:"Edge Runtime",codeGeneration:(t=e.codeGeneration)!==null&&t!==void 0?t:{strings:false,wasm:true}});this.requireCache=(r=e.requireCache)!==null&&r!==void 0?r:new Map;this.context=(i=(s=e.extend)===null||s===void 0?void 0:s.call(e,a))!==null&&i!==void 0?i:a;this.requireFn=(0,o.createRequire)(this.context,this.requireCache)}evaluate(e){return(0,n.runInContext)(e,this.context)}require(e){return this.requireFn(e,e)}requireInContext(e){const t=this.require(e);for(const[e,r]of Object.entries(t)){this.context[e]=r}}requireInlineInContext(e){const t=(0,s.tempFile)(e);this.requireInContext(t.path);t.remove()}}t.VM=VM},734:e=>{"use strict";e.exports=e=>{const t=e[0]*1e9+e[1];const r=t/1e6;const n=t/1e9;return{seconds:n,milliseconds:r,nanoseconds:t}}},593:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:true});t.EdgeRuntime=void 0;const n=r(135);let o;let s;class EdgeRuntime extends n.EdgeVM{constructor(e){super({...e,extend:t=>{var r,n;return(n=(r=e===null||e===void 0?void 0:e.extend)===null||r===void 0?void 0:r.call(e,t))!==null&&n!==void 0?n:t}});defineHandlerProps({object:this,setterName:"__onUnhandledRejectionHandler",setter:e=>o=e,getterName:"__rejectionHandlers",getter:()=>o});defineHandlerProps({object:this,setterName:"__onErrorHandler",setter:e=>s=e,getterName:"__errorHandlers",getter:()=>s});this.evaluate(getDefineEventListenersCode());this.dispatchFetch=this.evaluate(getDispatchFetchCode());if(e===null||e===void 0?void 0:e.initialCode){this.evaluate(e.initialCode)}}}t.EdgeRuntime=EdgeRuntime;process.on("unhandledRejection",(function invokeRejectionHandlers(e,t){o===null||o===void 0?void 0:o.forEach((r=>r(e,t)))}));process.on("uncaughtException",(function invokeErrorHandlers(e){s===null||s===void 0?void 0:s.forEach((t=>t(e)))}));function getDefineEventListenersCode(){return`\n Object.defineProperty(self, '__listeners', {\n configurable: false,\n enumerable: false,\n value: {},\n writable: true,\n })\n\n function __conditionallyUpdatesHandlerList(eventType) {\n if (eventType === 'unhandledrejection') {\n self.__onUnhandledRejectionHandler = self.__listeners[eventType];\n } else if (eventType === 'error') {\n self.__onErrorHandler = self.__listeners[eventType];\n }\n }\n\n function addEventListener(type, handler) {\n const eventType = type.toLowerCase();\n if (eventType === 'fetch' && self.__listeners.fetch) {\n throw new TypeError('You can register just one "fetch" event listener');\n }\n\n self.__listeners[eventType] = self.__listeners[eventType] || [];\n self.__listeners[eventType].push(handler);\n __conditionallyUpdatesHandlerList(eventType);\n }\n\n function removeEventListener(type, handler) {\n const eventType = type.toLowerCase();\n if (self.__listeners[eventType]) {\n self.__listeners[eventType] = self.__listeners[eventType].filter(item => {\n return item !== handler;\n });\n\n if (self.__listeners[eventType].length === 0) {\n delete self.__listeners[eventType];\n }\n }\n __conditionallyUpdatesHandlerList(eventType);\n }\n `}function getDispatchFetchCode(){return`(async function dispatchFetch(input, init) {\n const request = new Request(input, init);\n const event = new FetchEvent(request);\n if (!self.__listeners.fetch) {\n throw new Error("No fetch event listeners found");\n }\n\n const getResponse = ({ response, error }) => {\n if (error || !response || !(response instanceof Response)) {\n console.error(error ? error : 'The event listener did not respond')\n response = new Response(null, {\n statusText: 'Internal Server Error',\n status: 500\n })\n }\n\n response.waitUntil = () => Promise.all(event.awaiting);\n return response;\n }\n\n try {\n await self.__listeners.fetch[0].call(event, event)\n } catch (error) {\n return getResponse({ error })\n }\n\n return Promise.resolve(event.response)\n .then(response => getResponse({ response }))\n .catch(error => getResponse({ error }))\n })`}function defineHandlerProps({object:e,setterName:t,setter:r,getterName:n,getter:o}){Object.defineProperty(e.context,t,{set:r,configurable:false,enumerable:false});Object.defineProperty(e,n,{get:o,configurable:false,enumerable:false})}},882:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:true});t.getClonableBodyStream=void 0;const n=r(781);function getClonableBodyStream(e,t){let r=null;return{finalize(){if(r){replaceRequestBody(e,bodyStreamToNodeStream(r))}},cloneBodyStream(){const n=r!==null&&r!==void 0?r:requestToBodyStream(e,t);const[o,s]=n.tee();r=o;return s}}}t.getClonableBodyStream=getClonableBodyStream;function requestToBodyStream(e,t){const r=new t({start(t){e.on("data",(e=>t.enqueue(e)));e.on("end",(()=>t.terminate()));e.on("error",(e=>t.error(e)))}});return r.readable}function bodyStreamToNodeStream(e){const t=e.getReader();return n.Readable.from(async function*(){while(true){const{done:e,value:r}=await t.read();if(e){return}yield r}}())}function replaceRequestBody(e,t){for(const r in t){let n=t[r];if(typeof n==="function"){n=n.bind(t)}e[r]=n}return e}},12:function(e,t,r){"use strict";var n=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(t,"__esModule",{value:true});t.createHandler=void 0;const o=r(882);const s=n(r(720));const i=n(r(242));const a=n(r(504));function createHandler(e){const t=new Set;return{handler:async(r,n)=>{var E,c;const u=(0,a.default)();const _=r.method!=="GET"&&r.method!=="HEAD"?(0,o.getClonableBodyStream)(r,e.runtime.context.TransformStream):undefined;const d=await e.runtime.dispatchFetch(String(getURL(r)),{headers:toRequestInitHeaders(r),method:r.method,body:_===null||_===void 0?void 0:_.cloneBodyStream()});const l=d.waitUntil();t.add(l);l.finally((()=>t.delete(l)));n.statusCode=d.status;n.statusMessage=d.statusText;for(const[e,t]of Object.entries(toNodeHeaders(d.headers))){if(e!=="content-encoding"&&t!==undefined){n.setHeader(e,t)}}if(d.body){for await(const e of d.body){n.write(e)}}const S=`${r.socket.remoteAddress} ${r.method} ${r.url}`;const R=`${(E=(0,s.default)(u()).match(/[a-zA-Z]+|[0-9]+/g))===null||E===void 0?void 0:E.join(" ")}`;const h=`${n.statusCode} ${i.default[n.statusCode]}`;(c=e.logger)===null||c===void 0?void 0:c.debug(`${S} → ${h} in ${R}`);n.end()},waitUntil:()=>Promise.all(t)}}t.createHandler=createHandler;function getURL(e){var t;const r=((t=e.socket)===null||t===void 0?void 0:t.encrypted)?"https":"http";return new URL(String(e.url),`${r}://${String(e.headers.host)}`)}function toRequestInitHeaders(e){return Object.keys(e.headers).map((t=>{const r=e.headers[t];return[t,Array.isArray(r)?r.join(", "):r!==null&&r!==void 0?r:""]}))}function toNodeHeaders(e){const t={};if(e){for(const[r,n]of e.entries()){t[r]=r.toLowerCase()==="set-cookie"?e.getAll("set-cookie"):n}}return t}},742:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:true});t.runServer=t.createHandler=void 0;var n=r(12);Object.defineProperty(t,"createHandler",{enumerable:true,get:function(){return n.createHandler}});var o=r(653);Object.defineProperty(t,"runServer",{enumerable:true,get:function(){return o.runServer}})},653:function(e,t,r){"use strict";var n=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(t,"__esModule",{value:true});t.runServer=void 0;const o=r(12);const s=r(361);const i=n(r(685));async function runServer(e){const{handler:t,waitUntil:r}=(0,o.createHandler)(e);const n=i.default.createServer(t);n.listen(e.port);try{await(0,s.once)(n,"listening")}catch(t){if((t===null||t===void 0?void 0:t.code)==="EADDRINUSE"){return runServer({...e,port:undefined})}throw t}const a=n.address();const E=typeof a==="string"||a==null?String(a):`http://localhost:${a.port}`;return{url:E,close:async()=>{await r();await new Promise(((e,t)=>n.close((r=>{if(r)t(r);e()}))))},waitUntil:r}}t.runServer=runServer},242:e=>{var t;t={"1xx":"Informational","1xx_NAME":"INFORMATIONAL","1xx_MESSAGE":"Indicates an interim response for communicating connection status or request progress prior to completing the requested action and sending a final response.",INFORMATIONAL:"1xx","2xx":"Successful","2xx_NAME":"SUCCESSFUL","2xx_MESSAGE":"Indicates that the client's request was successfully received, understood, and accepted.",SUCCESSFUL:"2xx","3xx":"Redirection","3xx_NAME":"REDIRECTION","3xx_MESSAGE":"Indicates that further action needs to be taken by the user agent in order to fulfill the request.",REDIRECTION:"3xx","4xx":"Client Error","4xx_NAME":"CLIENT_ERROR","4xx_MESSAGE":"Indicates that the client seems to have erred.",CLIENT_ERROR:"4xx","5xx":"Server Error","5xx_NAME":"SERVER_ERROR","5xx_MESSAGE":"Indicates that the server is aware that it has erred or is incapable of performing the requested method.",SERVER_ERROR:"5xx"};e.exports={classes:t,100:"Continue","100_NAME":"CONTINUE","100_MESSAGE":"The server has received the request headers and the client should proceed to send the request body.","100_CLASS":t.INFORMATIONAL,CONTINUE:100,101:"Switching Protocols","101_NAME":"SWITCHING_PROTOCOLS","101_MESSAGE":"The requester has asked the server to switch protocols and the server has agreed to do so.","101_CLASS":t.INFORMATIONAL,SWITCHING_PROTOCOLS:101,102:"Processing","102_NAME":"PROCESSING","102_MESSAGE":"A WebDAV request may contain many sub-requests involving file operations, requiring a long time to complete the request. This code indicates that the server has received and is processing the request, but no response is available yet.[7] This prevents the client from timing out and assuming the request was lost.","102_CLASS":t.INFORMATIONAL,PROCESSING:102,103:"Early Hints","103_NAME":"EARLY_HINTS","103_MESSAGE":"Used to return some response headers before final HTTP message.","103_CLASS":t.INFORMATIONAL,EARLY_HINTS:103,200:"OK","200_NAME":"OK","200_MESSAGE":"Standard response for successful HTTP requests.","200_CLASS":t.SUCCESSFUL,OK:200,201:"Created","201_NAME":"CREATED","201_MESSAGE":"The request has been fulfilled, resulting in the creation of a new resource.","201_CLASS":t.SUCCESSFUL,CREATED:201,202:"Accepted","202_NAME":"ACCEPTED","202_MESSAGE":"The request has been accepted for processing, but the processing has not been completed.","202_CLASS":t.SUCCESSFUL,ACCEPTED:202,203:"Non-Authoritative Information","203_NAME":"NON_AUTHORITATIVE_INFORMATION","203_MESSAGE":"The server is a transforming proxy (e.g. a Web accelerator) that received a 200 OK from its origin, but is returning a modified version of the origin's response.","203_CLASS":t.SUCCESSFUL,NON_AUTHORITATIVE_INFORMATION:203,204:"No Content","204_NAME":"NO_CONTENT","204_MESSAGE":"The server successfully processed the request and is not returning any content.","204_CLASS":t.SUCCESSFUL,NO_CONTENT:204,205:"Reset Content","205_NAME":"RESET_CONTENT","205_MESSAGE":"The server successfully processed the request, but is not returning any content. Unlike a 204 response, this response requires that the requester reset the document view.","205_CLASS":t.SUCCESSFUL,RESET_CONTENT:205,206:"Partial Content","206_NAME":"PARTIAL_CONTENT","206_MESSAGE":"The server is delivering only part of the resource (byte serving) due to a range header sent by the client.","206_CLASS":t.SUCCESSFUL,PARTIAL_CONTENT:206,207:"Multi Status","207_NAME":"MULTI_STATUS","207_MESSAGE":"The message body that follows is by default an XML message and can contain a number of separate response codes, depending on how many sub-requests were made.","207_CLASS":t.SUCCESSFUL,MULTI_STATUS:207,208:"Already Reported","208_NAME":"ALREADY_REPORTED","208_MESSAGE":"The members of a DAV binding have already been enumerated in a preceding part of the (multistatus) response, and are not being included again.","208_CLASS":t.SUCCESSFUL,ALREADY_REPORTED:208,226:"IM Used","226_NAME":"IM_USED","226_MESSAGE":"The server has fulfilled a request for the resource, and the response is a representation of the result of one or more instance-manipulations applied to the current instance.","226_CLASS":t.SUCCESSFUL,IM_USED:226,300:"Multiple Choices","300_NAME":"MULTIPLE_CHOICES","300_MESSAGE":"Indicates multiple options for the resource from which the client may choose.","300_CLASS":t.REDIRECTION,MULTIPLE_CHOICES:300,301:"Moved Permanently","301_NAME":"MOVED_PERMANENTLY","301_MESSAGE":"This and all future requests should be directed to the given URI.","301_CLASS":t.REDIRECTION,MOVED_PERMANENTLY:301,302:"Found","302_NAME":"FOUND","302_MESSAGE":'This is an example of industry practice contradicting the standard. The HTTP/1.0 specification (RFC 1945) required the client to perform a temporary redirect (the original describing phrase was "Moved Temporarily"), but popular browsers implemented 302 with the functionality of a 303 See Other. Therefore, HTTP/1.1 added status codes 303 and 307 to distinguish between the two behaviours.',"302_CLASS":t.REDIRECTION,FOUND:302,303:"See Other","303_NAME":"SEE_OTHER","303_MESSAGE":"The response to the request can be found under another URI using the GET method.","303_CLASS":t.REDIRECTION,SEE_OTHER:303,304:"Not Modified","304_NAME":"NOT_MODIFIED","304_MESSAGE":"Indicates that the resource has not been modified since the version specified by the request headers If-Modified-Since or If-None-Match.","304_CLASS":t.REDIRECTION,NOT_MODIFIED:304,305:"Use Proxy","305_NAME":"USE_PROXY","305_MESSAGE":"The requested resource is available only through a proxy, the address for which is provided in the response.","305_CLASS":t.REDIRECTION,USE_PROXY:305,306:"Switch Proxy","306_NAME":"SWITCH_PROXY","306_MESSAGE":'No longer used. Originally meant "Subsequent requests should use the specified proxy.',"306_CLASS":t.REDIRECTION,SWITCH_PROXY:306,307:"Temporary Redirect","307_NAME":"TEMPORARY_REDIRECT","307_MESSAGE":"In this case, the request should be repeated with another URI; however, future requests should still use the original URI.","307_CLASS":t.REDIRECTION,TEMPORARY_REDIRECT:307,308:"Permanent Redirect","308_NAME":"PERMANENT_REDIRECT","308_MESSAGE":"The request and all future requests should be repeated using another URI.","308_CLASS":t.REDIRECTION,PERMANENT_REDIRECT:308,400:"Bad Request","400_NAME":"BAD_REQUEST","400_MESSAGE":"The server cannot or will not process the request due to an apparent client error.","400_CLASS":t.CLIENT_ERROR,BAD_REQUEST:400,401:"Unauthorized","401_NAME":"UNAUTHORIZED","401_MESSAGE":"Similar to 403 Forbidden, but specifically for use when authentication is required and has failed or has not yet been provided.","401_CLASS":t.CLIENT_ERROR,UNAUTHORIZED:401,402:"Payment Required","402_NAME":"PAYMENT_REQUIRED","402_MESSAGE":"Reserved for future use. The original intention was that this code might be used as part of some form of digital cash or micropayment scheme, as proposed for example by GNU Taler, but that has not yet happened, and this code is not usually used.","402_CLASS":t.CLIENT_ERROR,PAYMENT_REQUIRED:402,403:"Forbidden","403_NAME":"FORBIDDEN","403_MESSAGE":"The request was valid, but the server is refusing action.","403_CLASS":t.CLIENT_ERROR,FORBIDDEN:403,404:"Not Found","404_NAME":"NOT_FOUND","404_MESSAGE":"The requested resource could not be found but may be available in the future. Subsequent requests by the client are permissible.","404_CLASS":t.CLIENT_ERROR,NOT_FOUND:404,405:"Method Not Allowed","405_NAME":"METHOD_NOT_ALLOWED","405_MESSAGE":"A request method is not supported for the requested resource.","405_CLASS":t.CLIENT_ERROR,METHOD_NOT_ALLOWED:405,406:"Not Acceptable","406_NAME":"NOT_ACCEPTABLE","406_MESSAGE":"The requested resource is capable of generating only content not acceptable according to the Accept headers sent in the request.","406_CLASS":t.CLIENT_ERROR,NOT_ACCEPTABLE:406,407:"Proxy Authentication Required","407_NAME":"PROXY_AUTHENTICATION_REQUIRED","407_MESSAGE":"The client must first authenticate itself with the proxy.","407_CLASS":t.CLIENT_ERROR,PROXY_AUTHENTICATION_REQUIRED:407,408:"Request Time-out","408_NAME":"REQUEST_TIMEOUT","408_MESSAGE":"The server timed out waiting for the request.","408_CLASS":t.CLIENT_ERROR,REQUEST_TIMEOUT:408,409:"Conflict","409_NAME":"CONFLICT","409_MESSAGE":"Indicates that the request could not be processed because of conflict in the request, such as an edit conflict between multiple simultaneous updates.","409_CLASS":t.CLIENT_ERROR,CONFLICT:409,410:"Gone","410_NAME":"GONE","410_MESSAGE":"Indicates that the resource requested is no longer available and will not be available again.","410_CLASS":t.CLIENT_ERROR,GONE:410,411:"Length Required","411_NAME":"LENGTH_REQUIRED","411_MESSAGE":"The request did not specify the length of its content, which is required by the requested resource.","411_CLASS":t.CLIENT_ERROR,LENGTH_REQUIRED:411,412:"Precondition Failed","412_NAME":"PRECONDITION_FAILED","412_MESSAGE":"The server does not meet one of the preconditions that the requester put on the request.","412_CLASS":t.CLIENT_ERROR,PRECONDITION_FAILED:412,413:"Request Entity Too Large","413_NAME":"REQUEST_ENTITY_TOO_LARGE","413_MESSAGE":'The request is larger than the server is willing or able to process. Previously called "Request Entity Too Large".',"413_CLASS":t.CLIENT_ERROR,REQUEST_ENTITY_TOO_LARGE:413,414:"Request-URI Too Large","414_NAME":"REQUEST_URI_TOO_LONG","414_MESSAGE":"The URI provided was too long for the server to process.","414_CLASS":t.CLIENT_ERROR,REQUEST_URI_TOO_LONG:414,415:"Unsupported Media Type","415_NAME":"UNSUPPORTED_MEDIA_TYPE","415_MESSAGE":"The request entity has a media type which the server or resource does not support.","415_CLASS":t.CLIENT_ERROR,UNSUPPORTED_MEDIA_TYPE:415,416:"Requested Range not Satisfiable","416_NAME":"REQUESTED_RANGE_NOT_SATISFIABLE","416_MESSAGE":"The client has asked for a portion of the file (byte serving), but the server cannot supply that portion.","416_CLASS":t.CLIENT_ERROR,REQUESTED_RANGE_NOT_SATISFIABLE:416,417:"Expectation Failed","417_NAME":"EXPECTATION_FAILED","417_MESSAGE":"The server cannot meet the requirements of the Expect request-header field.","417_CLASS":t.CLIENT_ERROR,EXPECTATION_FAILED:417,418:"I'm a teapot","418_NAME":"IM_A_TEAPOT","418_MESSAGE":'Any attempt to brew coffee with a teapot should result in the error code "418 I\'m a teapot". The resulting entity body MAY be short and stout.',"418_CLASS":t.CLIENT_ERROR,IM_A_TEAPOT:418,421:"Misdirected Request","421_NAME":"MISDIRECTED_REQUEST","421_MESSAGE":"The request was directed at a server that is not able to produce a response.","421_CLASS":t.CLIENT_ERROR,MISDIRECTED_REQUEST:421,422:"Unprocessable Entity","422_NAME":"UNPROCESSABLE_ENTITY","422_MESSAGE":"The request was well-formed but was unable to be followed due to semantic errors.","422_CLASS":t.CLIENT_ERROR,UNPROCESSABLE_ENTITY:422,423:"Locked","423_NAME":"LOCKED","423_MESSAGE":"The resource that is being accessed is locked.","423_CLASS":t.CLIENT_ERROR,LOCKED:423,424:"Failed Dependency","424_NAME":"FAILED_DEPENDENCY","424_MESSAGE":"The request failed because it depended on another request and that request failed.","424_CLASS":t.CLIENT_ERROR,FAILED_DEPENDENCY:424,426:"Upgrade Required","426_NAME":"UPGRADE_REQUIRED","426_MESSAGE":"The client should switch to a different protocol such as TLS/1.0, given in the Upgrade header field.","426_CLASS":t.CLIENT_ERROR,UPGRADE_REQUIRED:426,428:"Precondition Required","428_NAME":"PRECONDITION_REQUIRED","428_MESSAGE":"The origin server requires the request to be conditional.","428_CLASS":t.CLIENT_ERROR,PRECONDITION_REQUIRED:428,429:"Too Many Requests","429_NAME":"TOO_MANY_REQUESTS","429_MESSAGE":"The user has sent too many requests in a given amount of time.","429_CLASS":t.CLIENT_ERROR,TOO_MANY_REQUESTS:429,431:"Request Header Fields Too Large","431_NAME":"REQUEST_HEADER_FIELDS_TOO_LARGE","431_MESSAGE":"The server is unwilling to process the request because either an individual header field, or all the header fields collectively, are too large.","431_CLASS":t.CLIENT_ERROR,REQUEST_HEADER_FIELDS_TOO_LARGE:431,451:"Unavailable For Legal Reasons","451_NAME":"UNAVAILABLE_FOR_LEGAL_REASONS","451_MESSAGE":"A server operator has received a legal demand to deny access to a resource or to a set of resources that includes the requested resource.","451_CLASS":t.CLIENT_ERROR,UNAVAILABLE_FOR_LEGAL_REASONS:451,500:"Internal Server Error","500_NAME":"INTERNAL_SERVER_ERROR","500_MESSAGE":"A generic error message, given when an unexpected condition was encountered and no more specific message is suitable.","500_CLASS":t.SERVER_ERROR,INTERNAL_SERVER_ERROR:500,501:"Not Implemented","501_NAME":"NOT_IMPLEMENTED","501_MESSAGE":"The server either does not recognize the request method, or it lacks the ability to fulfil the request. Usually this implies future availability.","501_CLASS":t.SERVER_ERROR,NOT_IMPLEMENTED:501,502:"Bad Gateway","502_NAME":"BAD_GATEWAY","502_MESSAGE":"The server was acting as a gateway or proxy and received an invalid response from the upstream server.","502_CLASS":t.SERVER_ERROR,BAD_GATEWAY:502,503:"Service Unavailable","503_NAME":"SERVICE_UNAVAILABLE","503_MESSAGE":"The server is currently unavailable (because it is overloaded or down for maintenance). Generally, this is a temporary state.","503_CLASS":t.SERVER_ERROR,SERVICE_UNAVAILABLE:503,504:"Gateway Time-out","504_NAME":"GATEWAY_TIMEOUT","504_MESSAGE":"The server was acting as a gateway or proxy and did not receive a timely response from the upstream server.","504_CLASS":t.SERVER_ERROR,GATEWAY_TIMEOUT:504,505:"HTTP Version not Supported","505_NAME":"HTTP_VERSION_NOT_SUPPORTED","505_MESSAGE":"The server does not support the HTTP protocol version used in the request.","505_CLASS":t.SERVER_ERROR,HTTP_VERSION_NOT_SUPPORTED:505,506:"Variant Also Negotiates","506_NAME":"VARIANT_ALSO_NEGOTIATES","506_MESSAGE":"Transparent content negotiation for the request results in a circular reference.","506_CLASS":t.SERVER_ERROR,VARIANT_ALSO_NEGOTIATES:506,507:"Insufficient Storage","507_NAME":"INSUFFICIENT_STORAGE","507_MESSAGE":"The server is unable to store the representation needed to complete the request.","507_CLASS":t.SERVER_ERROR,INSUFFICIENT_STORAGE:507,508:"Loop Detected","508_NAME":"LOOP_DETECTED","508_MESSAGE":"The server detected an infinite loop while processing the request.","508_CLASS":t.SERVER_ERROR,LOOP_DETECTED:508,510:"Not Extended","510_NAME":"NOT_EXTENDED","510_MESSAGE":"Further extensions to the request are required for the server to fulfil it.","510_CLASS":t.SERVER_ERROR,NOT_EXTENDED:510,511:"Network Authentication Required","511_NAME":"NETWORK_AUTHENTICATION_REQUIRED","511_MESSAGE":"The client needs to authenticate to gain network access. Intended for use by intercepting proxies used to control access to the network.","511_CLASS":t.SERVER_ERROR,NETWORK_AUTHENTICATION_REQUIRED:511,extra:{unofficial:{103:"Checkpoint","103_NAME":"CHECKPOINT","103_MESSAGE":"Used in the resumable requests proposal to resume aborted PUT or POST requests.","103_CLASS":t.INFORMATIONAL,CHECKPOINT:103,419:"Page Expired","419_NAME":"PAGE_EXPIRED","419_MESSAGE":"Used by the Laravel Framework when a CSRF Token is missing or expired.","419_CLASS":t.CLIENT_ERROR,PAGE_EXPIRED:419,218:"This is fine","218_NAME":"THIS_IS_FINE","218_MESSAGE":"Used as a catch-all error condition for allowing response bodies to flow through Apache when ProxyErrorOverride is enabled. When ProxyErrorOverride is enabled in Apache, response bodies that contain a status code of 4xx or 5xx are automatically discarded by Apache in favor of a generic response or a custom response specified by the ErrorDocument directive.","218_CLASS":t.SUCCESSFUL,THIS_IS_FINE:218,420:"Enhance Your Calm","420_NAME":"ENHANCE_YOUR_CALM","420_MESSAGE":"Returned by version 1 of the Twitter Search and Trends API when the client is being rate limited; versions 1.1 and later use the 429 Too Many Requests response code instead.","420_CLASS":t.CLIENT_ERROR,ENHANCE_YOUR_CALM:420,450:"Blocked by Windows Parental Controls","450_NAME":"BLOCKED_BY_WINDOWS_PARENTAL_CONTROLS","450_MESSAGE":"The Microsoft extension code indicated when Windows Parental Controls are turned on and are blocking access to the requested webpage.","450_CLASS":t.CLIENT_ERROR,BLOCKED_BY_WINDOWS_PARENTAL_CONTROLS:450,498:"Invalid Token","498_NAME":"INVALID_TOKEN","498_MESSAGE":"Returned by ArcGIS for Server. Code 498 indicates an expired or otherwise invalid token.","498_CLASS":t.CLIENT_ERROR,INVALID_TOKEN:498,499:"Token Required","499_NAME":"TOKEN_REQUIRED","499_MESSAGE":"Returned by ArcGIS for Server. Code 499 indicates that a token is required but was not submitted.","499_CLASS":t.CLIENT_ERROR,TOKEN_REQUIRED:499,509:"Bandwidth Limit Exceeded","509_NAME":"BANDWIDTH_LIMIT_EXCEEDED","509_MESSAGE":"The server has exceeded the bandwidth specified by the server administrator.","509_CLASS":t.SERVER_ERROR,BANDWIDTH_LIMIT_EXCEEDED:509,530:"Site is frozen","530_NAME":"SITE_IS_FROZEN","530_MESSAGE":"Used by the Pantheon web platform to indicate a site that has been frozen due to inactivity.","530_CLASS":t.SERVER_ERROR,SITE_IS_FROZEN:530,598:"Network read timeout error","598_NAME":"NETWORK_READ_TIMEOUT_ERROR","598_MESSAGE":"Used by some HTTP proxies to signal a network read timeout behind the proxy to a client in front of the proxy.","598_CLASS":t.SERVER_ERROR,NETWORK_READ_TIMEOUT_ERROR:598},iis:{440:"Login Time-out","440_NAME":"LOGIN_TIME_OUT","440_MESSAGE":"The client's session has expired and must log in again.","440_CLASS":t.CLIENT_ERROR,LOGIN_TIME_OUT:440,449:"Retry With","449_NAME":"RETRY_WITH","449_MESSAGE":"The server cannot honour the request because the user has not provided the required information.","449_CLASS":t.CLIENT_ERROR,RETRY_WITH:449,451:"Redirect","451_NAME":"REDIRECT","451_MESSAGE":"Used in Exchange ActiveSync when either a more efficient server is available or the server cannot access the users' mailbox.","451_CLASS":t.CLIENT_ERROR,REDIRECT:451},nginx:{444:"No Response","444_NAME":"NO_RESPONSE","444_MESSAGE":"Used internally to instruct the server to return no information to the client and close the connection immediately.","444_CLASS":t.CLIENT_ERROR,NO_RESPONSE:444,494:"Request header too large","494_NAME":"REQUEST_HEADER_TOO_LARGE","494_MESSAGE":"Client sent too large request or too long header line.","494_CLASS":t.CLIENT_ERROR,REQUEST_HEADER_TOO_LARGE:494,495:"SSL Certificate Error","495_NAME":"SSL_CERTIFICATE_ERROR","495_MESSAGE":"An expansion of the 400 Bad Request response code, used when the client has provided an invalid client certificate.","495_CLASS":t.CLIENT_ERROR,SSL_CERTIFICATE_ERROR:495,496:"SSL Certificate Required","496_NAME":"SSL_CERTIFICATE_REQUIRED","496_MESSAGE":"An expansion of the 400 Bad Request response code, used when a client certificate is required but not provided.","496_CLASS":t.CLIENT_ERROR,SSL_CERTIFICATE_REQUIRED:496,497:"HTTP Request Sent to HTTPS Port","497_NAME":"HTTP_REQUEST_SENT_TO_HTTPS_PORT","497_MESSAGE":"An expansion of the 400 Bad Request response code, used when the client has made a HTTP request to a port listening for HTTPS requests.","497_CLASS":t.CLIENT_ERROR,HTTP_REQUEST_SENT_TO_HTTPS_PORT:497,499:"Client Closed Request","499_NAME":"CLIENT_CLOSED_REQUEST","499_MESSAGE":"Used when the client has closed the request before the server could send a response.","499_CLASS":t.CLIENT_ERROR,CLIENT_CLOSED_REQUEST:499},cloudflare:{520:"Unknown Error","520_NAME":"UNKNOWN_ERROR","520_MESSAGE":'The 520 error is used as a "catch-all response for when the origin server returns something unexpected", listing connection resets, large headers, and empty or invalid responses as common triggers.',"520_CLASS":t.SERVER_ERROR,UNKNOWN_ERROR:520,521:"Web Server Is Down","521_NAME":"WEB_SERVER_IS_DOWN","521_MESSAGE":"The origin server has refused the connection from Cloudflare.","521_CLASS":t.SERVER_ERROR,WEB_SERVER_IS_DOWN:521,522:"Connection Timed Out","522_NAME":"CONNECTION_TIMED_OUT","522_MESSAGE":"Cloudflare could not negotiate a TCP handshake with the origin server.","522_CLASS":t.SERVER_ERROR,CONNECTION_TIMED_OUT:522,523:"Origin Is Unreachable","523_NAME":"ORIGIN_IS_UNREACHABLE","523_MESSAGE":"Cloudflare could not reach the origin server.","523_CLASS":t.SERVER_ERROR,ORIGIN_IS_UNREACHABLE:523,524:"A Timeout Occurred","524_NAME":"A_TIMEOUT_OCCURRED","524_MESSAGE":"Cloudflare was able to complete a TCP connection to the origin server, but did not receive a timely HTTP response.","524_CLASS":t.SERVER_ERROR,A_TIMEOUT_OCCURRED:524,525:"SSL Handshake Failed","525_NAME":"SSL_HANDSHAKE_FAILED","525_MESSAGE":"Cloudflare could not negotiate a SSL/TLS handshake with the origin server.","525_CLASS":t.SERVER_ERROR,SSL_HANDSHAKE_FAILED:525,526:"Invalid SSL Certificate","526_NAME":"INVALID_SSL_CERTIFICATE","526_MESSAGE":"Cloudflare could not validate the SSL/TLS certificate that the origin server presented.","526_CLASS":t.SERVER_ERROR,INVALID_SSL_CERTIFICATE:526,527:"Railgun Error","527_NAME":"RAILGUN_ERROR","527_MESSAGE":"Error 527 indicates that the request timed out or failed after the WAN connection had been established.","527_CLASS":t.SERVER_ERROR,RAILGUN_ERROR:527}}}},672:e=>{"use strict";e.exports=e=>{if(typeof e!=="number"){throw new TypeError("Expected a number")}const t=e>0?Math.floor:Math.ceil;return{days:t(e/864e5),hours:t(e/36e5)%24,minutes:t(e/6e4)%60,seconds:t(e/1e3)%60,milliseconds:t(e)%1e3,microseconds:t(e*1e3)%1e3,nanoseconds:t(e*1e6)%1e3}}},720:(e,t,r)=>{"use strict";const n=r(672);const pluralize=(e,t)=>t===1?e:`${e}s`;const o=1e-7;e.exports=(e,t={})=>{if(!Number.isFinite(e)){throw new TypeError("Expected a finite number")}if(t.colonNotation){t.compact=false;t.formatSubMilliseconds=false;t.separateMilliseconds=false;t.verbose=false}if(t.compact){t.secondsDecimalDigits=0;t.millisecondsDecimalDigits=0}const r=[];const floorDecimals=(e,t)=>{const r=Math.floor(e*10**t+o);const n=Math.round(r)/10**t;return n.toFixed(t)};const add=(e,n,o,s)=>{if((r.length===0||!t.colonNotation)&&e===0&&!(t.colonNotation&&o==="m")){return}s=(s||e||"0").toString();let i;let a;if(t.colonNotation){i=r.length>0?":":"";a="";const e=s.includes(".")?s.split(".")[0].length:s.length;const t=r.length>0?2:1;s="0".repeat(Math.max(0,t-e))+s}else{i="";a=t.verbose?" "+pluralize(n,e):o}r.push(i+s+a)};const s=n(e);add(Math.trunc(s.days/365),"year","y");add(s.days%365,"day","d");add(s.hours,"hour","h");add(s.minutes,"minute","m");if(t.separateMilliseconds||t.formatSubMilliseconds||!t.colonNotation&&e<1e3){add(s.seconds,"second","s");if(t.formatSubMilliseconds){add(s.milliseconds,"millisecond","ms");add(s.microseconds,"microsecond","µs");add(s.nanoseconds,"nanosecond","ns")}else{const e=s.milliseconds+s.microseconds/1e3+s.nanoseconds/1e6;const r=typeof t.millisecondsDecimalDigits==="number"?t.millisecondsDecimalDigits:0;const n=e>=1?Math.round(e):Math.ceil(e);const o=r?e.toFixed(r):n;add(Number.parseFloat(o,10),"millisecond","ms",o)}}else{const r=e/1e3%60;const n=typeof t.secondsDecimalDigits==="number"?t.secondsDecimalDigits:1;const o=floorDecimals(r,n);const s=t.keepDecimalsOnWholeSeconds?o:o.replace(/\.0+$/,"");add(Number.parseFloat(s,10),"second","s",s)}if(r.length===0){return"0"+(t.verbose?" milliseconds":"ms")}if(t.compact){return r[0]}if(typeof t.unitCount==="number"){const e=t.colonNotation?"":" ";return r.slice(0,Math.max(t.unitCount,1)).join(e)}return t.colonNotation?r.join(""):r.join(" ")}},504:(e,t,r)=>{"use strict";const n=r(734);e.exports=()=>{const e=process.hrtime();const end=t=>n(process.hrtime(e))[t];const returnValue=()=>end("milliseconds");returnValue.rounded=()=>Math.round(end("milliseconds"));returnValue.seconds=()=>end("seconds");returnValue.nanoseconds=()=>end("nanoseconds");return returnValue}},491:e=>{"use strict";e.exports=require("assert")},300:e=>{"use strict";e.exports=require("buffer")},113:e=>{"use strict";e.exports=require("crypto")},361:e=>{"use strict";e.exports=require("events")},147:e=>{"use strict";e.exports=require("fs")},685:e=>{"use strict";e.exports=require("http")},808:e=>{"use strict";e.exports=require("net")},277:e=>{"use strict";e.exports=require("next/dist/compiled/@edge-runtime/primitives/crypto")},37:e=>{"use strict";e.exports=require("os")},17:e=>{"use strict";e.exports=require("path")},74:e=>{"use strict";e.exports=require("perf_hooks")},282:e=>{"use strict";e.exports=require("process")},477:e=>{"use strict";e.exports=require("punycode")},781:e=>{"use strict";e.exports=require("stream")},404:e=>{"use strict";e.exports=require("tls")},837:e=>{"use strict";e.exports=require("util")},144:e=>{"use strict";e.exports=require("vm")},796:e=>{"use strict";e.exports=require("zlib")}};var __webpack_module_cache__={};function __nccwpck_require__(e){var t=__webpack_module_cache__[e];if(t!==undefined){return t.exports}var r=__webpack_module_cache__[e]={exports:{}};var n=true;try{__webpack_modules__[e].call(r.exports,r,r.exports,__nccwpck_require__);n=false}finally{if(n)delete __webpack_module_cache__[e]}return r.exports}if(typeof __nccwpck_require__!=="undefined")__nccwpck_require__.ab=__dirname+"/";var __webpack_exports__={};(()=>{"use strict";var e=__webpack_exports__;Object.defineProperty(e,"__esModule",{value:true});e.EdgeRuntime=e.runServer=e.createHandler=void 0;var t=__nccwpck_require__(742);Object.defineProperty(e,"createHandler",{enumerable:true,get:function(){return t.createHandler}});Object.defineProperty(e,"runServer",{enumerable:true,get:function(){return t.runServer}});var r=__nccwpck_require__(593);Object.defineProperty(e,"EdgeRuntime",{enumerable:true,get:function(){return r.EdgeRuntime}})})();module.exports=__webpack_exports__})(); \ No newline at end of file diff --git a/packages/next/index.d.ts b/packages/next/index.d.ts index 5d7a7b9cd205958..cba2b1dd4e05620 100644 --- a/packages/next/index.d.ts +++ b/packages/next/index.d.ts @@ -1,5 +1,5 @@ /// -/// +/// /// /// /// diff --git a/packages/next/lib/download-wasm-swc.ts b/packages/next/lib/download-wasm-swc.ts index 1cacf10d5f97e39..f1d3ac2eaa5bf40 100644 --- a/packages/next/lib/download-wasm-swc.ts +++ b/packages/next/lib/download-wasm-swc.ts @@ -83,7 +83,7 @@ export async function downloadWasmSwc( try { const output = execSync('npm config get registry').toString().trim() if (output.startsWith('http')) { - registry = output + registry = output.endsWith('/') ? output : `${output}/` } } catch (_) {} diff --git a/packages/next/lib/load-custom-routes.ts b/packages/next/lib/load-custom-routes.ts index caa0e2f8307cd30..f9b6ee393c557bb 100644 --- a/packages/next/lib/load-custom-routes.ts +++ b/packages/next/lib/load-custom-routes.ts @@ -3,9 +3,8 @@ import type { Token } from 'next/dist/compiled/path-to-regexp' import chalk from './chalk' import { escapeStringRegexp } from '../shared/lib/escape-regexp' -import { PERMANENT_REDIRECT_STATUS } from '../shared/lib/constants' -import { TEMPORARY_REDIRECT_STATUS } from '../shared/lib/constants' import { tryToParsePath } from './try-to-parse-path' +import { allowedStatusCodes } from './redirect-status' export type RouteHas = | { @@ -53,41 +52,14 @@ export type Redirect = { } ) -export const allowedStatusCodes = new Set([301, 302, 303, 307, 308]) const allowedHasTypes = new Set(['header', 'cookie', 'query', 'host']) const namedGroupsRegex = /\(\?<([a-zA-Z][a-zA-Z0-9]*)>/g -export function getRedirectStatus(route: { - statusCode?: number - permanent?: boolean -}): number { - return ( - route.statusCode || - (route.permanent ? PERMANENT_REDIRECT_STATUS : TEMPORARY_REDIRECT_STATUS) - ) -} - export function normalizeRouteRegex(regex: string) { // clean up un-necessary escaping from regex.source which turns / into \\/ return regex.replace(/\\\//g, '/') } -// for redirects we restrict matching /_next and for all routes -// we add an optional trailing slash at the end for easier -// configuring between trailingSlash: true/false -export function modifyRouteRegex(regex: string, restrictedPaths?: string[]) { - if (restrictedPaths) { - regex = regex.replace( - /\^/, - `^(?!${restrictedPaths - .map((path) => path.replace(/\//g, '\\/')) - .join('|')})` - ) - } - regex = regex.replace(/\$$/, '(?:\\/)?$') - return regex -} - function checkRedirect(route: Redirect): { invalidParts: string[] hadInvalidStatus: boolean diff --git a/packages/next/lib/redirect-status.ts b/packages/next/lib/redirect-status.ts new file mode 100644 index 000000000000000..54ecf4816491cde --- /dev/null +++ b/packages/next/lib/redirect-status.ts @@ -0,0 +1,30 @@ +import { PERMANENT_REDIRECT_STATUS } from '../shared/lib/constants' +import { TEMPORARY_REDIRECT_STATUS } from '../shared/lib/constants' + +export const allowedStatusCodes = new Set([301, 302, 303, 307, 308]) + +export function getRedirectStatus(route: { + statusCode?: number + permanent?: boolean +}): number { + return ( + route.statusCode || + (route.permanent ? PERMANENT_REDIRECT_STATUS : TEMPORARY_REDIRECT_STATUS) + ) +} + +// for redirects we restrict matching /_next and for all routes +// we add an optional trailing slash at the end for easier +// configuring between trailingSlash: true/false +export function modifyRouteRegex(regex: string, restrictedPaths?: string[]) { + if (restrictedPaths) { + regex = regex.replace( + /\^/, + `^(?!${restrictedPaths + .map((path) => path.replace(/\//g, '\\/')) + .join('|')})` + ) + } + regex = regex.replace(/\$$/, '(?:\\/)?$') + return regex +} diff --git a/packages/next/package.json b/packages/next/package.json index 0acc58eedc28509..8b5d5e2454c5a4d 100644 --- a/packages/next/package.json +++ b/packages/next/package.json @@ -1,6 +1,6 @@ { "name": "next", - "version": "12.2.5-canary.0", + "version": "12.2.5-canary.3", "description": "The React Framework", "main": "./dist/server/next.js", "license": "MIT", @@ -70,7 +70,7 @@ ] }, "dependencies": { - "@next/env": "12.2.5-canary.0", + "@next/env": "12.2.5-canary.3", "@swc/helpers": "0.4.3", "caniuse-lite": "^1.0.30001332", "postcss": "8.4.14", @@ -116,15 +116,15 @@ "@babel/runtime": "7.15.4", "@babel/traverse": "7.18.0", "@babel/types": "7.18.0", - "@edge-runtime/primitives": "1.1.0-beta.25", + "@edge-runtime/primitives": "1.1.0-beta.26", "@hapi/accept": "5.0.2", "@napi-rs/cli": "2.7.0", "@napi-rs/triples": "1.1.0", - "@next/polyfill-module": "12.2.5-canary.0", - "@next/polyfill-nomodule": "12.2.5-canary.0", - "@next/react-dev-overlay": "12.2.5-canary.0", - "@next/react-refresh-utils": "12.2.5-canary.0", - "@next/swc": "12.2.5-canary.0", + "@next/polyfill-module": "12.2.5-canary.3", + "@next/polyfill-nomodule": "12.2.5-canary.3", + "@next/react-dev-overlay": "12.2.5-canary.3", + "@next/react-refresh-utils": "12.2.5-canary.3", + "@next/swc": "12.2.5-canary.3", "@segment/ajv-human-errors": "2.1.2", "@taskr/clear": "1.1.0", "@taskr/esnext": "1.1.0", @@ -194,7 +194,7 @@ "debug": "4.1.1", "devalue": "2.0.1", "domain-browser": "4.19.0", - "edge-runtime": "1.1.0-beta.25", + "edge-runtime": "1.1.0-beta.26", "events": "3.3.0", "find-cache-dir": "3.3.1", "find-up": "4.1.0", @@ -257,7 +257,7 @@ "string-hash": "1.1.3", "string_decoder": "1.3.0", "strip-ansi": "6.0.0", - "styled-jsx": "5.0.2", + "styled-jsx": "5.0.3", "tar": "6.1.11", "taskr": "1.1.0", "terser": "5.14.1", diff --git a/packages/next/server/base-server.ts b/packages/next/server/base-server.ts index 072d714c316174f..31fbb55c94afc16 100644 --- a/packages/next/server/base-server.ts +++ b/packages/next/server/base-server.ts @@ -1,4 +1,4 @@ -import { __ApiPreviewProps } from './api-utils' +import type { __ApiPreviewProps } from './api-utils' import type { CustomRoutes } from '../lib/load-custom-routes' import type { DomainLocale } from './config' import type { DynamicRoutes, PageChecker, Route } from './router' @@ -27,7 +27,7 @@ import type { PayloadOptions } from './send-payload' import { join, resolve } from '../shared/lib/isomorphic/path' import { parse as parseQs } from 'querystring' import { format as formatUrl, parse as parseUrl } from 'url' -import { getRedirectStatus } from '../lib/load-custom-routes' +import { getRedirectStatus } from '../lib/redirect-status' import { NEXT_BUILTIN_DOCUMENT, NEXT_CLIENT_SSR_ENTRY_SUFFIX, @@ -119,6 +119,10 @@ export interface Options { * The port the server is running behind */ port?: number + /** + * The HTTP Server that Next.js is running behind + */ + httpServer?: import('http').Server } export interface BaseRequestHandler { @@ -261,7 +265,10 @@ export default abstract class Server { res: BaseNextResponse ): void - protected abstract loadEnvConfig(params: { dev: boolean }): void + protected abstract loadEnvConfig(params: { + dev: boolean + forceReload?: boolean + }): void public constructor(options: ServerOptions) { const { @@ -617,7 +624,7 @@ export default abstract class Server { } } - if (defaultLocale) { + if (!this.minimalMode && defaultLocale) { const redirect = getLocaleRedirect({ defaultLocale, domainLocale, @@ -665,6 +672,12 @@ export default abstract class Server { return this.handleRequest.bind(this) } + protected async handleUpgrade( + _req: BaseNextRequest, + _socket: any, + _head?: any + ): Promise {} + public setAssetPrefix(prefix?: string): void { this.renderOpts.assetPrefix = prefix ? prefix.replace(/\/$/, '') : '' } diff --git a/packages/next/server/config-shared.ts b/packages/next/server/config-shared.ts index 416fc7cb01da643..a41f84c65a40f29 100644 --- a/packages/next/server/config-shared.ts +++ b/packages/next/server/config-shared.ts @@ -1,6 +1,6 @@ import os from 'os' import type { webpack5 } from 'next/dist/compiled/webpack/webpack' -import { Header, Redirect, Rewrite } from '../lib/load-custom-routes' +import type { Header, Redirect, Rewrite } from '../lib/load-custom-routes' import { ImageConfig, ImageConfigComplete, diff --git a/packages/next/server/dev/hot-reloader.ts b/packages/next/server/dev/hot-reloader.ts index 996eca68a0e3ee4..73d879c302249fe 100644 --- a/packages/next/server/dev/hot-reloader.ts +++ b/packages/next/server/dev/hot-reloader.ts @@ -1,10 +1,12 @@ +import type { webpack5 } from 'next/dist/compiled/webpack/webpack' +import type { NextConfigComplete } from '../config-shared' +import type { CustomRoutes } from '../../lib/load-custom-routes' import { getOverlayMiddleware } from 'next/dist/compiled/@next/react-dev-overlay/dist/middleware' import { IncomingMessage, ServerResponse } from 'http' import { WebpackHotMiddleware } from './hot-middleware' import { join, relative, isAbsolute } from 'path' import { UrlObject } from 'url' import { webpack, StringXor } from 'next/dist/compiled/webpack/webpack' -import type { webpack5 } from 'next/dist/compiled/webpack/webpack' import { createEntrypoints, createPagesMapping, @@ -29,6 +31,7 @@ import { findPageFile } from '../lib/find-page-file' import { BUILDING, entries, + getInvalidator, onDemandEntryHandler, } from './on-demand-entry-handler' import { denormalizePagePath } from '../../shared/lib/page-path/denormalize-page-path' @@ -40,8 +43,6 @@ import { withoutRSCExtensions, isMiddlewareFilename, } from '../../build/utils' -import { NextConfigComplete } from '../config-shared' -import { CustomRoutes } from '../../lib/load-custom-routes' import { DecodeError } from '../../shared/lib/utils' import { Span, trace } from '../../trace' import { getProperError } from '../../lib/is-error' @@ -941,6 +942,10 @@ export default class HotReloader { edgeServerStats: () => this.edgeServerStats, }), ] + + // trigger invalidation to ensure any previous callbacks + // are handled in the on-demand-entry-handler + getInvalidator()?.invalidate() } public async stop(): Promise { diff --git a/packages/next/server/dev/next-dev-server.ts b/packages/next/server/dev/next-dev-server.ts index 20048ba4f86f1dd..3f73466ec4588ce 100644 --- a/packages/next/server/dev/next-dev-server.ts +++ b/packages/next/server/dev/next-dev-server.ts @@ -83,10 +83,6 @@ const ReactDevOverlay = (props: any) => { } export interface Options extends ServerOptions { - /** - * The HTTP Server that Next.js is running behind - */ - httpServer?: HTTPServer /** * Tells of Next.js is running from the `next dev` command */ @@ -256,7 +252,7 @@ export default class DevServer extends Server { ) let resolved = false - return new Promise((resolve, reject) => { + return new Promise(async (resolve, reject) => { // Watchpack doesn't emit an event for an empty directory fs.readdir(this.pagesDir, (_, files) => { if (files?.length) { @@ -269,7 +265,9 @@ export default class DevServer extends Server { } }) - const wp = (this.webpackWatcher = new Watchpack()) + const wp = (this.webpackWatcher = new Watchpack({ + ignored: /(node_modules|\.next)/, + })) const pages = [this.pagesDir] const app = this.appDir ? [this.appDir] : [] const directories = [...pages, ...app] @@ -279,7 +277,16 @@ export default class DevServer extends Server { ) let nestedMiddleware: string[] = [] - wp.watch(files, directories, 0) + const envFiles = [ + '.env.development.local', + '.env.local', + '.env.development', + '.env', + ].map((file) => pathJoin(this.dir, file)) + + files.push(...envFiles) + wp.watch({ directories: [this.dir], startTime: 0 }) + const envFileTimes = new Map() wp.on('aggregated', async () => { let middlewareMatcher: RegExp | undefined @@ -287,8 +294,27 @@ export default class DevServer extends Server { const knownFiles = wp.getTimeInfoEntries() const appPaths: Record = {} const edgeRoutesSet = new Set() + let envChange = false for (const [fileName, meta] of knownFiles) { + if ( + !files.includes(fileName) && + !directories.some((dir) => fileName.startsWith(dir)) + ) { + continue + } + + if (envFiles.includes(fileName)) { + if ( + envFileTimes.get(fileName) && + envFileTimes.get(fileName) !== meta.timestamp + ) { + envChange = true + } + envFileTimes.set(fileName, meta.timestamp) + continue + } + if ( meta?.accuracy === undefined || !regexPageExtension.test(fileName) @@ -362,6 +388,12 @@ export default class DevServer extends Server { routedPages.push(pageName) } + if (envChange) { + this.loadEnvConfig({ dev: true, forceReload: true }) + await this.hotReloader?.stop() + await this.hotReloader?.start() + } + if (nestedMiddleware.length > 0) { Log.error( new NestedMiddlewareError(nestedMiddleware, this.dir, this.pagesDir) @@ -629,6 +661,8 @@ export default class DevServer extends Server { ) ) { this.hotReloader?.onHMR(req, socket, head) + } else { + this.handleUpgrade(req, socket, head) } }) } diff --git a/packages/next/server/dev/on-demand-entry-handler.ts b/packages/next/server/dev/on-demand-entry-handler.ts index 9d34f9a3df761af..0fd01e7c2fb5fc6 100644 --- a/packages/next/server/dev/on-demand-entry-handler.ts +++ b/packages/next/server/dev/on-demand-entry-handler.ts @@ -127,6 +127,10 @@ export const entries: { let invalidator: Invalidator export const getInvalidator = () => invalidator +const doneCallbacks: EventEmitter | null = new EventEmitter() +const lastClientAccessPages = [''] +const lastServerAccessPagesForAppDir = [''] + export function onDemandEntryHandler({ maxInactiveAge, multiCompiler, @@ -145,9 +149,6 @@ export function onDemandEntryHandler({ appDir?: string }) { invalidator = new Invalidator(multiCompiler) - const doneCallbacks: EventEmitter | null = new EventEmitter() - const lastClientAccessPages = [''] - const lastServerAccessPagesForAppDir = [''] const startBuilding = (_compilation: webpack.Compilation) => { invalidator.startBuilding() @@ -223,11 +224,7 @@ export function onDemandEntryHandler({ const pingIntervalTime = Math.max(1000, Math.min(5000, maxInactiveAge)) setInterval(function () { - disposeInactiveEntries( - lastClientAccessPages, - lastServerAccessPagesForAppDir, - maxInactiveAge - ) + disposeInactiveEntries(maxInactiveAge) }, pingIntervalTime + 1000).unref() function handleAppDirPing( @@ -397,11 +394,7 @@ export function onDemandEntryHandler({ } } -function disposeInactiveEntries( - lastClientAccessPages: string[], - lastServerAccessPagesForAppDir: string[], - maxInactiveAge: number -) { +function disposeInactiveEntries(maxInactiveAge: number) { Object.keys(entries).forEach((page) => { const { lastActiveTime, status, dispose, bundlePath } = entries[page] @@ -467,19 +460,19 @@ class Invalidator { this.building = true if (!keys || keys.length === 0) { - this.multiCompiler.compilers[0].watching.invalidate() - this.multiCompiler.compilers[1].watching.invalidate() - this.multiCompiler.compilers[2].watching.invalidate() + this.multiCompiler.compilers[0].watching?.invalidate() + this.multiCompiler.compilers[1].watching?.invalidate() + this.multiCompiler.compilers[2].watching?.invalidate() return } for (const key of keys) { if (key === 'client') { - this.multiCompiler.compilers[0].watching.invalidate() + this.multiCompiler.compilers[0].watching?.invalidate() } else if (key === 'server') { - this.multiCompiler.compilers[1].watching.invalidate() + this.multiCompiler.compilers[1].watching?.invalidate() } else if (key === 'edgeServer') { - this.multiCompiler.compilers[2].watching.invalidate() + this.multiCompiler.compilers[2].watching?.invalidate() } } } diff --git a/packages/next/server/get-app-route-from-entrypoint.ts b/packages/next/server/get-app-route-from-entrypoint.ts new file mode 100644 index 000000000000000..28e4870e709a1f3 --- /dev/null +++ b/packages/next/server/get-app-route-from-entrypoint.ts @@ -0,0 +1,17 @@ +import matchBundle from './match-bundle' + +// matches app/:path*.js +const APP_ROUTE_NAME_REGEX = /^app[/\\](.*)$/ + +export default function getAppRouteFromEntrypoint(entryFile: string) { + const pagePath = matchBundle(APP_ROUTE_NAME_REGEX, entryFile) + if (typeof pagePath === 'string' && !pagePath) { + return '/' + } + + if (!pagePath) { + return null + } + + return pagePath +} diff --git a/packages/next/server/get-route-from-entrypoint.ts b/packages/next/server/get-route-from-entrypoint.ts index 4a95b4c58b6725e..298c93d9cecaa7a 100644 --- a/packages/next/server/get-route-from-entrypoint.ts +++ b/packages/next/server/get-route-from-entrypoint.ts @@ -1,22 +1,12 @@ -import getRouteFromAssetPath from '../shared/lib/router/utils/get-route-from-asset-path' +import getAppRouteFromEntrypoint from './get-app-route-from-entrypoint' +import matchBundle from './match-bundle' // matches pages/:page*.js const SERVER_ROUTE_NAME_REGEX = /^pages[/\\](.*)$/ -// matches app/:path*.js -const APP_ROUTE_NAME_REGEX = /^app[/\\](.*)$/ + // matches static/pages/:page*.js const BROWSER_ROUTE_NAME_REGEX = /^static[/\\]pages[/\\](.*)$/ -function matchBundle(regex: RegExp, input: string): string | null { - const result = regex.exec(input) - - if (!result) { - return null - } - - return getRouteFromAssetPath(`/${result[1]}`) -} - export default function getRouteFromEntrypoint( entryFile: string, app?: boolean @@ -28,8 +18,7 @@ export default function getRouteFromEntrypoint( } if (app) { - pagePath = matchBundle(APP_ROUTE_NAME_REGEX, entryFile) - if (typeof pagePath === 'string' && !pagePath) pagePath = '/' + pagePath = getAppRouteFromEntrypoint(entryFile) if (pagePath) return pagePath } diff --git a/packages/next/server/lib/start-server.ts b/packages/next/server/lib/start-server.ts index f01432504f06a3c..4308a93db34fdb2 100644 --- a/packages/next/server/lib/start-server.ts +++ b/packages/next/server/lib/start-server.ts @@ -39,6 +39,14 @@ export function startServer(opts: StartServerOptions) { } }) + let upgradeHandler: any + + if (!opts.dev) { + server.on('upgrade', (req, socket, upgrade) => { + upgradeHandler(req, socket, upgrade) + }) + } + server.on('listening', () => { const addr = server.address() const hostname = @@ -55,6 +63,7 @@ export function startServer(opts: StartServerOptions) { }) requestHandler = app.getRequestHandler() + upgradeHandler = app.getUpgradeHandler() resolve(app) }) diff --git a/packages/next/server/match-bundle.ts b/packages/next/server/match-bundle.ts new file mode 100644 index 000000000000000..7ee4deba7e02d68 --- /dev/null +++ b/packages/next/server/match-bundle.ts @@ -0,0 +1,14 @@ +import getRouteFromAssetPath from '../shared/lib/router/utils/get-route-from-asset-path' + +export default function matchBundle( + regex: RegExp, + input: string +): string | null { + const result = regex.exec(input) + + if (!result) { + return null + } + + return getRouteFromAssetPath(`/${result[1]}`) +} diff --git a/packages/next/server/next-server.ts b/packages/next/server/next-server.ts index 209e93d3589028f..69bee1ffbfdc6fa 100644 --- a/packages/next/server/next-server.ts +++ b/packages/next/server/next-server.ts @@ -54,6 +54,7 @@ import { apiResolver } from './api-utils/node' import { RenderOpts, renderToHTML } from './render' import { renderToHTMLOrFlight as appRenderToHTMLOrFlight } from './app-render' import { ParsedUrl, parseUrl } from '../shared/lib/router/utils/parse-url' +import { parse as nodeParseUrl } from 'url' import * as Log from '../build/output/log' import loadRequireHook from '../build/webpack/require-hook' @@ -159,8 +160,14 @@ export default class NextNodeServer extends BaseServer { ? (compression() as ExpressMiddleware) : undefined - protected loadEnvConfig({ dev }: { dev: boolean }) { - loadEnvConfig(this.dir, dev, Log) + protected loadEnvConfig({ + dev, + forceReload, + }: { + dev: boolean + forceReload?: boolean + }) { + loadEnvConfig(this.dir, dev, Log, forceReload) } protected getPublicDir(): string { @@ -501,10 +508,15 @@ export default class NextNodeServer extends BaseServer { } } + protected async handleUpgrade(req: NodeNextRequest, socket: any, head: any) { + await this.router.execute(req, socket, nodeParseUrl(req.url, true), head) + } + protected async proxyRequest( req: NodeNextRequest, res: NodeNextResponse, - parsedUrl: ParsedUrl + parsedUrl: ParsedUrl, + upgradeHead?: any ) { const { query } = parsedUrl delete (parsedUrl as any).query @@ -516,27 +528,46 @@ export default class NextNodeServer extends BaseServer { changeOrigin: true, ignorePath: true, xfwd: true, - proxyTimeout: 30_000, // limit proxying to 30 seconds + ws: true, + // we limit proxy requests to 30s by default, in development + // we don't time out WebSocket requests to allow proxying + proxyTimeout: upgradeHead && this.renderOpts.dev ? undefined : 30_000, }) await new Promise((proxyResolve, proxyReject) => { let finished = false - proxy.on('proxyReq', (proxyReq) => { - proxyReq.on('close', () => { - if (!finished) { - finished = true - proxyResolve(true) - } - }) - }) proxy.on('error', (err) => { + console.error(`Failed to proxy ${target}`, err) if (!finished) { finished = true proxyReject(err) } }) - proxy.web(req.originalRequest, res.originalResponse) + + // if upgrade head is present treat as WebSocket request + if (upgradeHead) { + proxy.on('proxyReqWs', (proxyReq) => { + proxyReq.on('close', () => { + if (!finished) { + finished = true + proxyResolve(true) + } + }) + }) + proxy.ws(req as any as IncomingMessage, res, upgradeHead) + proxyResolve(true) + } else { + proxy.on('proxyReq', (proxyReq) => { + proxyReq.on('close', () => { + if (!finished) { + finished = true + proxyResolve(true) + } + }) + }) + proxy.web(req.originalRequest, res.originalResponse) + } }) return { @@ -989,7 +1020,7 @@ export default class NextNodeServer extends BaseServer { matchesLocale: true, matchesLocaleAPIRoutes: true, matchesTrailingSlash: true, - fn: async (req, res, params, parsedUrl) => { + fn: async (req, res, params, parsedUrl, upgradeHead) => { const { newUrl, parsedDestination } = prepareDestination({ appendParamsToQuery: true, destination: rewriteRoute.destination, @@ -1002,7 +1033,8 @@ export default class NextNodeServer extends BaseServer { return this.proxyRequest( req as NodeNextRequest, res as NodeNextResponse, - parsedDestination + parsedDestination, + upgradeHead ) } diff --git a/packages/next/server/next.ts b/packages/next/server/next.ts index a6f3bbc0581bf6c..643af922ab70752 100644 --- a/packages/next/server/next.ts +++ b/packages/next/server/next.ts @@ -62,6 +62,15 @@ export class NextServer { } } + getUpgradeHandler() { + return async (req: IncomingMessage, socket: any, head: any) => { + const server = await this.getServer() + // @ts-expect-error we mark this as protected so it + // causes an error here + return server.handleUpgrade.apply(server, [req, socket, head]) + } + } + setAssetPrefix(assetPrefix: string) { if (this.server) { this.server.setAssetPrefix(assetPrefix) diff --git a/packages/next/server/render.tsx b/packages/next/server/render.tsx index 42bf8bf58490e6f..1bc29fbc3361dbf 100644 --- a/packages/next/server/render.tsx +++ b/packages/next/server/render.tsx @@ -64,10 +64,7 @@ import { HtmlContext } from '../shared/lib/html-context' import { normalizePagePath } from '../shared/lib/page-path/normalize-page-path' import { denormalizePagePath } from '../shared/lib/page-path/denormalize-page-path' import { getRequestMeta, NextParsedUrlQuery } from './request-meta' -import { - allowedStatusCodes, - getRedirectStatus, -} from '../lib/load-custom-routes' +import { allowedStatusCodes, getRedirectStatus } from '../lib/redirect-status' import RenderResult from './render-result' import isError from '../lib/is-error' import { diff --git a/packages/next/server/router.ts b/packages/next/server/router.ts index 1159d08e21d6729..f06b4443249a86b 100644 --- a/packages/next/server/router.ts +++ b/packages/next/server/router.ts @@ -39,7 +39,8 @@ export type Route = { req: BaseNextRequest, res: BaseNextResponse, params: Params, - parsedUrl: NextUrlWithParsedQuery + parsedUrl: NextUrlWithParsedQuery, + upgradeHead?: any ) => Promise | RouteResult } @@ -130,7 +131,8 @@ export default class Router { async execute( req: BaseNextRequest, res: BaseNextResponse, - parsedUrl: NextUrlWithParsedQuery + parsedUrl: NextUrlWithParsedQuery, + upgradeHead?: any ): Promise { if (this.seenRequests.has(req)) { throw new Error( @@ -306,6 +308,11 @@ export default class Router { ] for (const testRoute of allRoutes) { + // only process rewrites for upgrade request + if (upgradeHead && testRoute.type !== 'rewrite') { + continue + } + const originalPathname = parsedUrlUpdated.pathname as string const pathnameInfo = getNextPathnameInfo(originalPathname, { nextConfig: this.nextConfig, @@ -388,7 +395,8 @@ export default class Router { req, res, newParams, - parsedUrlUpdated + parsedUrlUpdated, + upgradeHead ) if (result.finished) { diff --git a/packages/next/server/server-route-utils.ts b/packages/next/server/server-route-utils.ts index eecfcb14fff7326..c21f07a98bcf920 100644 --- a/packages/next/server/server-route-utils.ts +++ b/packages/next/server/server-route-utils.ts @@ -9,7 +9,7 @@ import type { Route } from './router' import type { BaseNextRequest } from './base-http' import type { ParsedUrlQuery } from 'querystring' -import { getRedirectStatus, modifyRouteRegex } from '../lib/load-custom-routes' +import { getRedirectStatus, modifyRouteRegex } from '../lib/redirect-status' import { getPathMatch } from '../shared/lib/router/utils/path-match' import { compileNonPath, diff --git a/packages/next/shared/lib/constants.ts b/packages/next/shared/lib/constants.ts index 6c381c89e7d39d6..9e97927eeed1d73 100644 --- a/packages/next/shared/lib/constants.ts +++ b/packages/next/shared/lib/constants.ts @@ -7,6 +7,7 @@ export const PAGES_MANIFEST = 'pages-manifest.json' export const APP_PATHS_MANIFEST = 'app-paths-manifest.json' export const APP_PATH_ROUTES_MANIFEST = 'app-path-routes-manifest.json' export const BUILD_MANIFEST = 'build-manifest.json' +export const APP_BUILD_MANIFEST = 'app-build-manifest.json' export const EXPORT_MARKER = 'export-marker.json' export const EXPORT_DETAIL = 'export-detail.json' export const PRERENDER_MANIFEST = 'prerender-manifest.json' @@ -47,7 +48,7 @@ export const MIDDLEWARE_REACT_LOADABLE_MANIFEST = // static/runtime/main.js export const CLIENT_STATIC_FILES_RUNTIME_MAIN = `main` -export const CLIENT_STATIC_FILES_RUNTIME_MAIN_ROOT = `${CLIENT_STATIC_FILES_RUNTIME_MAIN}-app` +export const CLIENT_STATIC_FILES_RUNTIME_MAIN_APP = `${CLIENT_STATIC_FILES_RUNTIME_MAIN}-app` // static/runtime/react-refresh.js export const CLIENT_STATIC_FILES_RUNTIME_REACT_REFRESH = `react-refresh` // static/runtime/amp.js diff --git a/packages/next/shared/lib/router/utils/app-paths.ts b/packages/next/shared/lib/router/utils/app-paths.ts index 56edad8f58d7bdd..bf69f05e090286c 100644 --- a/packages/next/shared/lib/router/utils/app-paths.ts +++ b/packages/next/shared/lib/router/utils/app-paths.ts @@ -1,17 +1,19 @@ // remove (name) from pathname as it's not considered for routing export function normalizeAppPath(pathname: string) { - let normalized = '' - const segments = pathname.split('/') + return pathname.split('/').reduce((acc, segment, index, segments) => { + // Empty segments are ignored. + if (!segment) { + return acc + } - segments.forEach((segment, index) => { - if (!segment) return if (segment.startsWith('(') && segment.endsWith(')')) { - return + return acc } + if (segment === 'page' && index === segments.length - 1) { - return + return acc } - normalized += `/${segment}` - }) - return normalized + + return acc + `/${segment}` + }, '') } diff --git a/packages/next/shared/lib/router/utils/resolve-rewrites.ts b/packages/next/shared/lib/router/utils/resolve-rewrites.ts index 6d612aaaa30a61d..66bd11d09e4301d 100644 --- a/packages/next/shared/lib/router/utils/resolve-rewrites.ts +++ b/packages/next/shared/lib/router/utils/resolve-rewrites.ts @@ -1,7 +1,7 @@ import type { ParsedUrlQuery } from 'querystring' +import type { Rewrite } from '../../../../lib/load-custom-routes' import { getPathMatch } from './path-match' import { matchHas, prepareDestination } from './prepare-destination' -import { Rewrite } from '../../../../lib/load-custom-routes' import { removeTrailingSlash } from './remove-trailing-slash' import { normalizeLocalePath } from '../../i18n/normalize-locale-path' import { removeBasePath } from '../../../../client/remove-base-path' diff --git a/packages/next/shared/lib/router/utils/sorted-routes.ts b/packages/next/shared/lib/router/utils/sorted-routes.ts index d3f0699bd1cf55a..973126c8e0e584b 100644 --- a/packages/next/shared/lib/router/utils/sorted-routes.ts +++ b/packages/next/shared/lib/router/utils/sorted-routes.ts @@ -195,7 +195,9 @@ class UrlNode { } } -export function getSortedRoutes(normalizedPages: string[]): string[] { +export function getSortedRoutes( + normalizedPages: ReadonlyArray +): string[] { // First the UrlNode is created, and every UrlNode can have only 1 dynamic segment // Eg you can't have pages/[post]/abc.js and pages/[hello]/something-else.js // Only 1 dynamic segment per nesting level diff --git a/packages/next/taskfile.js b/packages/next/taskfile.js index 198100f9a0cb59a..b38d7f37756aa2f 100644 --- a/packages/next/taskfile.js +++ b/packages/next/taskfile.js @@ -44,7 +44,7 @@ export async function copy_regenerator_runtime(task, opts) { // eslint-disable-next-line camelcase export async function copy_styled_jsx_assets(task, opts) { - // we copy the styled-jsx types so that we can reference them + // we copy the styled-jsx assets and types so that we can reference them // in the next-env.d.ts file so it doesn't matter if the styled-jsx // package is hoisted out of Next.js' node_modules or not const styledJsxPath = dirname(require.resolve('styled-jsx/package.json')) @@ -53,33 +53,35 @@ export async function copy_styled_jsx_assets(task, opts) { cwd: styledJsxPath, }) const outputDir = join(__dirname, 'dist/styled-jsx') - let typeReferences = `` + // Separate type files into different folders to avoid conflicts between + // dev dep `styled-jsx` and `next/dist/styled-jsx` for duplicated declare modules + const typesDir = join(outputDir, 'types') + let typeReferences = '' + let globalTypesContent = '' await fs.ensureDir(outputDir) + await fs.ensureDir(typesDir) for (const file of typeFiles) { const fileNoExt = file.replace(/\.d\.ts/, '') - const content = await fs.readFile(join(styledJsxPath, file), 'utf8') - const exportsIndex = content.indexOf('export') - - let replacedContent = - `${content.substring(0, exportsIndex)}\n` + - `declare module 'styled-jsx${ - file === 'index.d.ts' ? '' : '/' + fileNoExt - }' { - ${content.substring(exportsIndex)} - }` + let content = await fs.readFile(join(styledJsxPath, file), 'utf8') + if (file === 'index.d.ts') { - replacedContent = replacedContent - .replace(/export function StyleRegistry/g, 'export function IRegistry') - .replace(/StyleRegistry/g, 'IStyleRegistry') - .replace(/IRegistry/g, 'Registry') + const styledJsxIdx = content.indexOf(`declare module 'styled-jsx' {`) + globalTypesContent = content.substring(0, styledJsxIdx) + content = content + .substring(styledJsxIdx) + .replace('React.', `import('react').`) } - await fs.writeFile(join(outputDir, file), replacedContent) + + await fs.writeFile(join(typesDir, file), content) typeReferences += `/// \n` } - await fs.writeFile(join(outputDir, 'global.d.ts'), typeReferences) + await fs.writeFile( + join(typesDir, 'global.d.ts'), + `${typeReferences}\n${globalTypesContent}` + ) for (const file of jsFiles) { const content = await fs.readFile(join(styledJsxPath, file), 'utf8') @@ -1806,7 +1808,12 @@ export async function path_to_regexp(task, opts) { export async function precompile(task, opts) { await task.parallel( - ['browser_polyfills', 'path_to_regexp', 'copy_ncced'], + [ + 'browser_polyfills', + 'path_to_regexp', + 'copy_ncced', + 'copy_styled_jsx_assets', + ], opts ) } @@ -1965,7 +1972,6 @@ export async function compile(task, opts) { // we compile this each time so that fresh runtime data is pulled // before each publish 'ncc_amp_optimizer', - 'copy_styled_jsx_assets', ], opts ) diff --git a/packages/next/types/misc.d.ts b/packages/next/types/misc.d.ts index 02a3d342988ce52..7571c94bfe57377 100644 --- a/packages/next/types/misc.d.ts +++ b/packages/next/types/misc.d.ts @@ -18,11 +18,6 @@ declare module 'next/dist/compiled/@next/react-refresh-utils/dist/ReactRefreshWe export = m } -declare module 'next/dist/styled-jsx' { - import m from 'styled-jsx' - export = m -} - declare module 'next/dist/compiled/node-html-parser' { export * from 'node-html-parser' } @@ -289,6 +284,11 @@ declare module 'next/dist/compiled/postcss-scss' { import m from 'postcss-scss' export = m } +declare module 'next/dist/styled-jsx' { + import m from 'styled-jsx' + export = m +} + declare module 'next/dist/compiled/text-table' { function textTable( rows: Array>, @@ -361,6 +361,12 @@ declare module 'next/dist/compiled/watchpack' { class Watchpack extends EventEmitter { constructor(options?: any) + watch(params: { + files?: string[] + directories?: string[] + startTime?: number + missing?: string[] + }): void watch(files: string[], directories: string[], startTime?: number): void close(): void diff --git a/packages/react-dev-overlay/package.json b/packages/react-dev-overlay/package.json index 22ae6b6fb596af1..610e0473acbfe8c 100644 --- a/packages/react-dev-overlay/package.json +++ b/packages/react-dev-overlay/package.json @@ -1,6 +1,6 @@ { "name": "@next/react-dev-overlay", - "version": "12.2.5-canary.0", + "version": "12.2.5-canary.3", "description": "A development-only overlay for developing React applications.", "repository": { "url": "vercel/next.js", diff --git a/packages/react-refresh-utils/package.json b/packages/react-refresh-utils/package.json index 1ab4b07f0894052..c3ebc2624378869 100644 --- a/packages/react-refresh-utils/package.json +++ b/packages/react-refresh-utils/package.json @@ -1,6 +1,6 @@ { "name": "@next/react-refresh-utils", - "version": "12.2.5-canary.0", + "version": "12.2.5-canary.3", "description": "An experimental package providing utilities for React Refresh.", "repository": { "url": "vercel/next.js", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b1b29baf8d4b923..399388e5654c20b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -19,7 +19,7 @@ importers: '@babel/plugin-proposal-object-rest-spread': 7.14.7 '@babel/preset-flow': 7.14.5 '@babel/preset-react': 7.14.5 - '@edge-runtime/jest-environment': 1.1.0-beta.25 + '@edge-runtime/jest-environment': 1.1.0-beta.26 '@fullhuman/postcss-purgecss': 1.3.0 '@mdx-js/loader': 0.18.0 '@next/bundle-analyzer': workspace:* @@ -173,7 +173,7 @@ importers: '@babel/plugin-proposal-object-rest-spread': 7.14.7_@babel+core@7.18.0 '@babel/preset-flow': 7.14.5_@babel+core@7.18.0 '@babel/preset-react': 7.14.5_@babel+core@7.18.0 - '@edge-runtime/jest-environment': 1.1.0-beta.25 + '@edge-runtime/jest-environment': 1.1.0-beta.26 '@fullhuman/postcss-purgecss': 1.3.0 '@mdx-js/loader': 0.18.0_uuaxwgga6hqycsez5ok7v2wg4i '@next/bundle-analyzer': link:packages/next-bundle-analyzer @@ -364,7 +364,7 @@ importers: packages/eslint-config-next: specifiers: - '@next/eslint-plugin-next': 12.2.5-canary.0 + '@next/eslint-plugin-next': 12.2.5-canary.3 '@rushstack/eslint-patch': ^1.1.3 '@typescript-eslint/parser': ^5.21.0 eslint-import-resolver-node: ^0.3.6 @@ -416,16 +416,16 @@ importers: '@babel/runtime': 7.15.4 '@babel/traverse': 7.18.0 '@babel/types': 7.18.0 - '@edge-runtime/primitives': 1.1.0-beta.25 + '@edge-runtime/primitives': 1.1.0-beta.26 '@hapi/accept': 5.0.2 '@napi-rs/cli': 2.7.0 '@napi-rs/triples': 1.1.0 - '@next/env': 12.2.5-canary.0 - '@next/polyfill-module': 12.2.5-canary.0 - '@next/polyfill-nomodule': 12.2.5-canary.0 - '@next/react-dev-overlay': 12.2.5-canary.0 - '@next/react-refresh-utils': 12.2.5-canary.0 - '@next/swc': 12.2.5-canary.0 + '@next/env': 12.2.5-canary.3 + '@next/polyfill-module': 12.2.5-canary.3 + '@next/polyfill-nomodule': 12.2.5-canary.3 + '@next/react-dev-overlay': 12.2.5-canary.3 + '@next/react-refresh-utils': 12.2.5-canary.3 + '@next/swc': 12.2.5-canary.3 '@segment/ajv-human-errors': 2.1.2 '@swc/helpers': 0.4.3 '@taskr/clear': 1.1.0 @@ -497,7 +497,7 @@ importers: debug: 4.1.1 devalue: 2.0.1 domain-browser: 4.19.0 - edge-runtime: 1.1.0-beta.25 + edge-runtime: 1.1.0-beta.26 events: 3.3.0 find-cache-dir: 3.3.1 find-up: 4.1.0 @@ -561,7 +561,7 @@ importers: string_decoder: 1.3.0 string-hash: 1.1.3 strip-ansi: 6.0.0 - styled-jsx: 5.0.2 + styled-jsx: 5.0.3 tar: 6.1.11 taskr: 1.1.0 terser: 5.14.1 @@ -609,7 +609,7 @@ importers: '@babel/runtime': 7.15.4 '@babel/traverse': 7.18.0 '@babel/types': 7.18.0 - '@edge-runtime/primitives': 1.1.0-beta.25 + '@edge-runtime/primitives': 1.1.0-beta.26 '@hapi/accept': 5.0.2 '@napi-rs/cli': 2.7.0 '@napi-rs/triples': 1.1.0 @@ -687,7 +687,7 @@ importers: debug: 4.1.1 devalue: 2.0.1 domain-browser: 4.19.0 - edge-runtime: 1.1.0-beta.25 + edge-runtime: 1.1.0-beta.26 events: 3.3.0 find-cache-dir: 3.3.1 find-up: 4.1.0 @@ -750,7 +750,7 @@ importers: string_decoder: 1.3.0 string-hash: 1.1.3 strip-ansi: 6.0.0 - styled-jsx: 5.0.2_@babel+core@7.18.0 + styled-jsx: 5.0.3_@babel+core@7.18.0 tar: 6.1.11 taskr: 1.1.0 terser: 5.14.1 @@ -3218,14 +3218,14 @@ packages: protobufjs: 6.11.2 dev: true - /@edge-runtime/format/1.1.0-beta.25: - resolution: {integrity: sha512-7fzTXgfVzcWs6T8ePVyogbVU67TbXpDHhgop9yP9stsRlejJjD2bDm/jDwX9XAfQdET5gVaKDiYc0wkp9E4gig==} + /@edge-runtime/format/1.1.0-beta.26: + resolution: {integrity: sha512-05wywSsBZVE1iMezvYXW1ZMak6y2riJPVmEDK9RW9BECazykJtYOOQ+GG+oVaicNfxh2YtQTQD/WaI/4K3yKWA==} dev: true - /@edge-runtime/jest-environment/1.1.0-beta.25: - resolution: {integrity: sha512-tOjxI0/8m0beqEiOXmDzPCqpycUiMLvIV1PoQ9zb5qcxJqF8AcmZMRqICfdqnWo+P3qJIr8YeZSG31lz5EaJfw==} + /@edge-runtime/jest-environment/1.1.0-beta.26: + resolution: {integrity: sha512-NnnYjF3Kaw83Vrkoy0lpuZCk49VjR5Ruo5XDjZ9dKqslA6lNVBkirTDvQrQBYSTaZM8lu3Ed39o50zH2Ua4lTw==} dependencies: - '@edge-runtime/vm': 1.1.0-beta.25 + '@edge-runtime/vm': 1.1.0-beta.26 '@jest/environment': 28.1.3 '@jest/fake-timers': 28.1.3 '@jest/types': 28.1.3 @@ -3233,14 +3233,14 @@ packages: jest-util: 28.1.3 dev: true - /@edge-runtime/primitives/1.1.0-beta.25: - resolution: {integrity: sha512-+lKore2sAuGD2AMa2GZviLMHKfFmw5k2BzhvyatKPuJ/frIFpb1OdluxGHfqmVBtNIJwnn7IJoJd9jm8r/6Flg==} + /@edge-runtime/primitives/1.1.0-beta.26: + resolution: {integrity: sha512-SsbXbw2+bAHbcjAmT+e5WoADmU/yDO5glNFkYd/971Zo26cJoW/1W3n4IMm5gOtyvLHbezSRKt8t5GteX0wYdA==} dev: true - /@edge-runtime/vm/1.1.0-beta.25: - resolution: {integrity: sha512-xDre6a3L0KXsxQxuZaXw5ukyGh5v7k1E7XL5ooxBbaHz+5GtvCidSZ3sPvFDzetlKq7eBT4ztg4RTkHbCUclDA==} + /@edge-runtime/vm/1.1.0-beta.26: + resolution: {integrity: sha512-hxWtmuO13zgNkM3zHvRENfMeavM+PAKSoHhvzt+sHjSothxGlA06XXN38t/NT6LD4ND8p8FmPJ70+fTptL4a/A==} dependencies: - '@edge-runtime/primitives': 1.1.0-beta.25 + '@edge-runtime/primitives': 1.1.0-beta.26 dev: true /@emotion/is-prop-valid/0.8.8: @@ -9980,12 +9980,12 @@ packages: safe-buffer: 5.2.0 dev: true - /edge-runtime/1.1.0-beta.25: - resolution: {integrity: sha512-2yC0hqnMis0t2zIFCWsV7uu8hEhUZDgVszZyXS/C4719r1km2FkL5kWk+6ktVD4A6fUTnzTrYPq+fzTxIt7X2A==} + /edge-runtime/1.1.0-beta.26: + resolution: {integrity: sha512-2JZlxpISnRv3p0yh6Oo9QZF06lWcgJH0RMxVbJ5Az0Grde7B1PLw34oCIcOu23Zu7Icn4Lh2wojA6riq3jyDqg==} hasBin: true dependencies: - '@edge-runtime/format': 1.1.0-beta.25 - '@edge-runtime/vm': 1.1.0-beta.25 + '@edge-runtime/format': 1.1.0-beta.26 + '@edge-runtime/vm': 1.1.0-beta.26 exit-hook: 2.2.1 http-status: 1.5.2 mri: 1.2.0 @@ -10454,7 +10454,6 @@ packages: eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 dependencies: eslint: 7.32.0 - dev: false /eslint-plugin-react/7.23.2_eslint@7.24.0: resolution: {integrity: sha512-AfjgFQB+nYszudkxRkTFu0UR1zEQig0ArVMPloKhxwlwkzaw/fBiH0QWcBBhZONlXqQC51+nfqFrkn4EzHcGBw==} @@ -20560,8 +20559,8 @@ packages: postcss-load-plugins: 2.3.0 dev: true - /styled-jsx/5.0.2_@babel+core@7.18.0: - resolution: {integrity: sha512-LqPQrbBh3egD57NBcHET4qcgshPks+yblyhPlH2GY8oaDgKs8SK4C3dBh3oSJjgzJ3G5t1SYEZGHkP+QEpX9EQ==} + /styled-jsx/5.0.3_@babel+core@7.18.0: + resolution: {integrity: sha512-v82oihjxFj2WJtQiodZIDjJpnmVcE71HTCVylxdcQHU0ocnI0rGhJ0+5A3311NMQUx0KFJ+18RSHNlfIgcSU8g==} engines: {node: '>= 12.0.0'} peerDependencies: '@babel/core': '*' diff --git a/test/e2e/middleware-matcher/index.test.ts b/test/e2e/middleware-matcher/index.test.ts index d76a1b5d8ed09de..812e19c6947519a 100644 --- a/test/e2e/middleware-matcher/index.test.ts +++ b/test/e2e/middleware-matcher/index.test.ts @@ -1,3 +1,4 @@ +/* eslint-disable jest/no-identical-title */ import { createNext, FileRef } from 'e2e-utils' import { NextInstance } from 'test/lib/next-modes/base' import { check, fetchViaHTTP } from 'next-test-utils' @@ -27,7 +28,7 @@ describe('Middleware can set the matcher in its config', () => { expect(await response.text()).toContain('This should run the middleware') }) - it('adds the header for a matched data path', async () => { + it('adds the header for a matched data path (with header)', async () => { const response = await fetchViaHTTP( next.url, `/_next/data/${next.buildId}/with-middleware.json`, @@ -42,6 +43,19 @@ describe('Middleware can set the matcher in its config', () => { expect(response.headers.get('X-From-Middleware')).toBe('true') }) + it('adds the header for a matched data path (without header)', async () => { + const response = await fetchViaHTTP( + next.url, + `/_next/data/${next.buildId}/with-middleware.json` + ) + expect(await response.json()).toMatchObject({ + pageProps: { + message: 'Hello, cruel world.', + }, + }) + expect(response.headers.get('X-From-Middleware')).toBe('true') + }) + it('adds the header for another matched path', async () => { const response = await fetchViaHTTP(next.url, '/another-middleware') expect(response.headers.get('X-From-Middleware')).toBe('true') @@ -195,7 +209,7 @@ describe('using a single matcher', () => { expect(response.headers.get('X-From-Middleware')).toBe('true') }) - it('adds the headers for a matched data path', async () => { + it('adds the headers for a matched data path (with header)', async () => { const response = await fetchViaHTTP( next.url, `/_next/data/${next.buildId}/middleware/works.json`, @@ -210,6 +224,19 @@ describe('using a single matcher', () => { expect(response.headers.get('X-From-Middleware')).toBe('true') }) + it('adds the header for a matched data path (without header)', async () => { + const response = await fetchViaHTTP( + next.url, + `/_next/data/${next.buildId}/middleware/works.json` + ) + expect(await response.json()).toMatchObject({ + pageProps: { + message: 'Hello from /middleware/works', + }, + }) + expect(response.headers.get('X-From-Middleware')).toBe('true') + }) + it('does not add the header for an unmatched path', async () => { const response = await fetchViaHTTP(next.url, '/about/me') expect(await response.text()).toContain('Hello from /about/me') @@ -217,7 +244,88 @@ describe('using a single matcher', () => { }) }) -describe('using a single matcher with i18n', () => { +describe('using root matcher', () => { + let next: NextInstance + beforeAll(async () => { + next = await createNext({ + files: { + 'pages/index.js': ` + export function getStaticProps() { + return { + props: { + message: 'hello world' + } + } + } + + export default function Home({ message }) { + return
Hi there!
+ } + `, + 'middleware.js': ` + import { NextResponse } from 'next/server' + export default (req) => { + const res = NextResponse.next(); + res.headers.set('X-From-Middleware', 'true'); + return res; + } + + export const config = { matcher: '/' }; + `, + }, + dependencies: {}, + }) + }) + afterAll(() => next.destroy()) + + it('adds the header to the /', async () => { + const response = await fetchViaHTTP(next.url, '/') + expect(response.status).toBe(200) + expect(Object.fromEntries(response.headers)).toMatchObject({ + 'x-from-middleware': 'true', + }) + }) + + it('adds the header to the /index', async () => { + const response = await fetchViaHTTP(next.url, '/index') + expect(Object.fromEntries(response.headers)).toMatchObject({ + 'x-from-middleware': 'true', + }) + }) + + it('adds the header for a matched data path (with header)', async () => { + const response = await fetchViaHTTP( + next.url, + `/_next/data/${next.buildId}/index.json`, + undefined, + { headers: { 'x-nextjs-data': '1' } } + ) + expect(await response.json()).toMatchObject({ + pageProps: { + message: 'hello world', + }, + }) + expect(response.headers.get('X-From-Middleware')).toBe('true') + }) + + it('adds the header for a matched data path (without header)', async () => { + const response = await fetchViaHTTP( + next.url, + `/_next/data/${next.buildId}/index.json` + ) + expect(await response.json()).toMatchObject({ + pageProps: { + message: 'hello world', + }, + }) + expect(response.headers.get('X-From-Middleware')).toBe('true') + }) +}) + +describe.each([ + { title: '' }, + { title: ' and trailingSlash', trailingSlash: true }, +])('using a single matcher with i18n$title', ({ trailingSlash }) => { let next: NextInstance beforeAll(async () => { next = await createNext({ @@ -254,6 +362,7 @@ describe('using a single matcher with i18n', () => { `, 'next.config.js': ` module.exports = { + ${trailingSlash ? 'trailingSlash: true,' : ''} i18n: { localeDetection: false, locales: ['es', 'en'], @@ -276,6 +385,15 @@ describe('using a single matcher with i18n', () => { expect(res2.headers.get('X-From-Middleware')).toBe('true') }) + it('adds the header for a mathed root path with /index', async () => { + const res1 = await fetchViaHTTP(next.url, `/index`) + expect(await res1.text()).toContain(`(en) Hello from /`) + expect(res1.headers.get('X-From-Middleware')).toBe('true') + const res2 = await fetchViaHTTP(next.url, `/es/index`) + expect(await res2.text()).toContain(`(es) Hello from /`) + expect(res2.headers.get('X-From-Middleware')).toBe('true') + }) + it(`adds the headers for a matched data path`, async () => { const res1 = await fetchViaHTTP( next.url, @@ -290,8 +408,7 @@ describe('using a single matcher with i18n', () => { const res2 = await fetchViaHTTP( next.url, `/_next/data/${next.buildId}/es.json`, - undefined, - { headers: { 'x-nextjs-data': '1' } } + undefined ) expect(await res2.json()).toMatchObject({ pageProps: { message: `(es) Hello from /` }, @@ -306,12 +423,17 @@ describe('using a single matcher with i18n', () => { }) }) -describe('using a single matcher with i18n and basePath', () => { - let next: NextInstance - beforeAll(async () => { - next = await createNext({ - files: { - 'pages/index.js': ` +describe.each([ + { title: '' }, + { title: ' and trailingSlash', trailingSlash: true }, +])( + 'using a single matcher with i18n and basePath$title', + ({ trailingSlash }) => { + let next: NextInstance + beforeAll(async () => { + next = await createNext({ + files: { + 'pages/index.js': ` export default function Page({ message }) { return

root page

@@ -322,7 +444,7 @@ describe('using a single matcher with i18n and basePath', () => { props: { message: \`(\${locale}) Hello from /\` } }) `, - 'pages/[...route].js': ` + 'pages/[...route].js': ` export default function Page({ message }) { return

catchall page

@@ -333,7 +455,7 @@ describe('using a single matcher with i18n and basePath', () => { props: { message: \`(\${locale}) Hello from /\` + params.route.join("/") } }) `, - 'middleware.js': ` + 'middleware.js': ` import { NextResponse } from 'next/server' export const config = { matcher: '/' }; export default (req) => { @@ -342,8 +464,9 @@ describe('using a single matcher with i18n and basePath', () => { return res; } `, - 'next.config.js': ` + 'next.config.js': ` module.exports = { + ${trailingSlash ? 'trailingSlash: true,' : ''} basePath: '/root', i18n: { localeDetection: false, @@ -352,47 +475,57 @@ describe('using a single matcher with i18n and basePath', () => { } } `, - }, - dependencies: {}, + }, + dependencies: {}, + }) + }) + afterAll(() => next.destroy()) + + it(`adds the header for a matched path`, async () => { + const res1 = await fetchViaHTTP(next.url, `/root`) + expect(await res1.text()).toContain(`(en) Hello from /`) + expect(res1.headers.get('X-From-Middleware')).toBe('true') + const res2 = await fetchViaHTTP(next.url, `/root/es`) + expect(await res2.text()).toContain(`(es) Hello from /`) + expect(res2.headers.get('X-From-Middleware')).toBe('true') }) - }) - afterAll(() => next.destroy()) - - it(`adds the header for a matched path`, async () => { - const res1 = await fetchViaHTTP(next.url, `/root`) - expect(await res1.text()).toContain(`(en) Hello from /`) - expect(res1.headers.get('X-From-Middleware')).toBe('true') - const res2 = await fetchViaHTTP(next.url, `/root/es`) - expect(await res2.text()).toContain(`(es) Hello from /`) - expect(res2.headers.get('X-From-Middleware')).toBe('true') - }) - it(`adds the headers for a matched data path`, async () => { - const res1 = await fetchViaHTTP( - next.url, - `/root/_next/data/${next.buildId}/en.json`, - undefined, - { headers: { 'x-nextjs-data': '1' } } - ) - expect(await res1.json()).toMatchObject({ - pageProps: { message: `(en) Hello from /` }, + it('adds the header for a mathed root path with /index', async () => { + const res1 = await fetchViaHTTP(next.url, `/root/index`) + expect(await res1.text()).toContain(`(en) Hello from /`) + expect(res1.headers.get('X-From-Middleware')).toBe('true') + const res2 = await fetchViaHTTP(next.url, `/root/es/index`) + expect(await res2.text()).toContain(`(es) Hello from /`) + expect(res2.headers.get('X-From-Middleware')).toBe('true') }) - expect(res1.headers.get('X-From-Middleware')).toBe('true') - const res2 = await fetchViaHTTP( - next.url, - `/root/_next/data/${next.buildId}/es.json`, - undefined, - { headers: { 'x-nextjs-data': '1' } } - ) - expect(await res2.json()).toMatchObject({ - pageProps: { message: `(es) Hello from /` }, + + it(`adds the headers for a matched data path`, async () => { + const res1 = await fetchViaHTTP( + next.url, + `/root/_next/data/${next.buildId}/en.json`, + undefined, + { headers: { 'x-nextjs-data': '1' } } + ) + expect(await res1.json()).toMatchObject({ + pageProps: { message: `(en) Hello from /` }, + }) + expect(res1.headers.get('X-From-Middleware')).toBe('true') + const res2 = await fetchViaHTTP( + next.url, + `/root/_next/data/${next.buildId}/es.json`, + undefined, + { headers: { 'x-nextjs-data': '1' } } + ) + expect(await res2.json()).toMatchObject({ + pageProps: { message: `(es) Hello from /` }, + }) + expect(res2.headers.get('X-From-Middleware')).toBe('true') }) - expect(res2.headers.get('X-From-Middleware')).toBe('true') - }) - it(`does not add the header for an unmatched path`, async () => { - const response = await fetchViaHTTP(next.url, `/root/about/me`) - expect(await response.text()).toContain('Hello from /about/me') - expect(response.headers.get('X-From-Middleware')).toBeNull() - }) -}) + it(`does not add the header for an unmatched path`, async () => { + const response = await fetchViaHTTP(next.url, `/root/about/me`) + expect(await response.text()).toContain('Hello from /about/me') + expect(response.headers.get('X-From-Middleware')).toBeNull() + }) + } +) diff --git a/test/integration/custom-routes/next.config.js b/test/integration/custom-routes/next.config.js index 3f9fc9bfcd23436..1a06c9778588799 100644 --- a/test/integration/custom-routes/next.config.js +++ b/test/integration/custom-routes/next.config.js @@ -12,6 +12,11 @@ module.exports = { }, ] : []), + { + source: '/to-websocket', + destination: + 'http://localhost:__EXTERNAL_PORT__/_next/webpack-hmr?page=/about', + }, { source: '/to-nowhere', destination: 'http://localhost:12233', diff --git a/test/integration/custom-routes/test/index.test.js b/test/integration/custom-routes/test/index.test.js index 55b60e85546a986..cd5fc57f5ddb15f 100644 --- a/test/integration/custom-routes/test/index.test.js +++ b/test/integration/custom-routes/test/index.test.js @@ -5,6 +5,7 @@ import url from 'url' import stripAnsi from 'strip-ansi' import fs from 'fs-extra' import { join } from 'path' +import WebSocket from 'ws' import cheerio from 'cheerio' import webdriver from 'next-webdriver' import escapeRegex from 'escape-string-regexp' @@ -39,6 +40,29 @@ let appPort let app const runTests = (isDev = false) => { + it('should successfully rewrite a WebSocket request', async () => { + const messages = [] + const ws = await new Promise((resolve, reject) => { + let socket = new WebSocket(`ws://localhost:${appPort}/to-websocket`) + socket.on('message', (data) => { + messages.push(data.toString()) + }) + socket.on('open', () => resolve(socket)) + socket.on('error', (err) => { + console.error(err) + socket.close() + reject() + }) + }) + + await check( + () => (messages.length > 0 ? 'success' : JSON.stringify(messages)), + 'success' + ) + ws.close() + expect([...externalServerHits]).toEqual(['/_next/webpack-hmr?page=/about']) + }) + it('should not rewrite for _next/data route when a match is found', async () => { const initial = await fetchViaHTTP(appPort, '/overridden/first') expect(initial.status).toBe(200) @@ -1809,6 +1833,11 @@ const runTests = (isDev = false) => { }, ], afterFiles: [ + { + destination: `http://localhost:${externalServerPort}/_next/webpack-hmr?page=/about`, + regex: normalizeRegEx('^\\/to-websocket(?:\\/)?$'), + source: '/to-websocket', + }, { destination: 'http://localhost:12233', regex: normalizeRegEx('^\\/to-nowhere(?:\\/)?$'), @@ -2235,6 +2264,14 @@ describe('Custom routes', () => { const externalHost = req.headers['host'] res.end(`hi ${nextHost} from ${externalHost}`) }) + const wsServer = new WebSocket.Server({ noServer: true }) + + externalServer.on('upgrade', (req, socket, head) => { + externalServerHits.add(req.url) + wsServer.handleUpgrade(req, socket, head, (client) => { + client.send('hello world') + }) + }) await new Promise((resolve, reject) => { externalServer.listen(externalServerPort, (error) => { if (error) return reject(error) @@ -2244,7 +2281,7 @@ describe('Custom routes', () => { nextConfigRestoreContent = await fs.readFile(nextConfigPath, 'utf8') await fs.writeFile( nextConfigPath, - nextConfigRestoreContent.replace(/__EXTERNAL_PORT__/, externalServerPort) + nextConfigRestoreContent.replace(/__EXTERNAL_PORT__/g, externalServerPort) ) }) afterAll(async () => { diff --git a/test/integration/env-config/app/pages/another-global.js b/test/integration/env-config/app/pages/another-global.js new file mode 100644 index 000000000000000..7db38a7029f9e09 --- /dev/null +++ b/test/integration/env-config/app/pages/another-global.js @@ -0,0 +1 @@ +export default () =>

{process.env.NEXT_PUBLIC_HELLO_WORLD}

diff --git a/test/integration/env-config/app/pages/api/all.js b/test/integration/env-config/app/pages/api/all.js index 882343cf27b4a71..52f181fe5fb8304 100644 --- a/test/integration/env-config/app/pages/api/all.js +++ b/test/integration/env-config/app/pages/api/all.js @@ -21,18 +21,22 @@ const variables = [ 'ENV_FILE_EXPANDED_CONCAT', 'ENV_FILE_EXPANDED_ESCAPED', 'ENV_FILE_KEY_EXCLAMATION', + 'NEW_ENV_KEY', + 'NEW_ENV_LOCAL_KEY', + 'NEW_ENV_DEV_KEY', + 'NEXT_PUBLIC_HELLO_WORLD', ] -const items = { - nextConfigEnv: process.env.nextConfigEnv, - nextConfigPublicEnv: process.env.nextConfigPublicEnv, -} +export default async (req, res) => { + const items = { + nextConfigEnv: process.env.nextConfigEnv, + nextConfigPublicEnv: process.env.nextConfigPublicEnv, + } -variables.forEach((variable) => { - items[variable] = process.env[variable] -}) + variables.forEach((variable) => { + items[variable] = process.env[variable] + }) -export default async (req, res) => { // Only for testing, don't do this... res.json(items) } diff --git a/test/integration/env-config/app/pages/index.js b/test/integration/env-config/app/pages/index.js index 8e0d187a75f8334..c8bc8ce3a77e03d 100644 --- a/test/integration/env-config/app/pages/index.js +++ b/test/integration/env-config/app/pages/index.js @@ -21,6 +21,10 @@ const variables = [ 'ENV_FILE_EXPANDED_CONCAT', 'ENV_FILE_EXPANDED_ESCAPED', 'ENV_FILE_KEY_EXCLAMATION', + 'NEW_ENV_KEY', + 'NEW_ENV_LOCAL_KEY', + 'NEW_ENV_DEV_KEY', + 'NEXT_PUBLIC_HELLO_WORLD', ] export async function getStaticProps() { diff --git a/test/integration/env-config/app/pages/some-ssg.js b/test/integration/env-config/app/pages/some-ssg.js index 5486f5de7b405dc..d46d371b59065a4 100644 --- a/test/integration/env-config/app/pages/some-ssg.js +++ b/test/integration/env-config/app/pages/some-ssg.js @@ -21,6 +21,10 @@ const variables = [ 'ENV_FILE_EXPANDED_CONCAT', 'ENV_FILE_EXPANDED_ESCAPED', 'ENV_FILE_KEY_EXCLAMATION', + 'NEW_ENV_KEY', + 'NEW_ENV_LOCAL_KEY', + 'NEW_ENV_DEV_KEY', + 'NEXT_PUBLIC_HELLO_WORLD', ] export async function getStaticProps() { diff --git a/test/integration/env-config/app/pages/some-ssp.js b/test/integration/env-config/app/pages/some-ssp.js index 0be53f7d03786e2..ee985c926e2281c 100644 --- a/test/integration/env-config/app/pages/some-ssp.js +++ b/test/integration/env-config/app/pages/some-ssp.js @@ -21,6 +21,10 @@ const variables = [ 'ENV_FILE_EXPANDED_CONCAT', 'ENV_FILE_EXPANDED_ESCAPED', 'ENV_FILE_KEY_EXCLAMATION', + 'NEW_ENV_KEY', + 'NEW_ENV_LOCAL_KEY', + 'NEW_ENV_DEV_KEY', + 'NEXT_PUBLIC_HELLO_WORLD', ] export async function getServerSideProps() { diff --git a/test/integration/env-config/test/index.test.js b/test/integration/env-config/test/index.test.js index c87ce54f6f3b209..23baf525c59f084 100644 --- a/test/integration/env-config/test/index.test.js +++ b/test/integration/env-config/test/index.test.js @@ -12,10 +12,12 @@ import { launchApp, killApp, fetchViaHTTP, + check, } from 'next-test-utils' let app let appPort +let output = '' const appDir = join(__dirname, '../app') const getEnvFromHtml = async (path) => { @@ -27,7 +29,7 @@ const getEnvFromHtml = async (path) => { return env } -const runTests = (mode = 'dev') => { +const runTests = (mode = 'dev', didReload = false) => { const isDevOnly = mode === 'dev' const isTestEnv = mode === 'test' const isDev = isDevOnly || isTestEnv @@ -56,6 +58,11 @@ const runTests = (mode = 'dev') => { expect(data.ENV_FILE_EMPTY_FIRST).toBe(isTestEnv ? '' : '$escaped') expect(data.ENV_FILE_PROCESS_ENV).toBe('env-cli') + if (didReload) { + expect(data.NEW_ENV_KEY).toBe('true') + expect(data.NEW_ENV_LOCAL_KEY).toBe('hello') + expect(data.NEW_ENV_DEV_KEY).toBe('from-dev') + } expect(data.nextConfigEnv).toBe('hello from next.config.js') expect(data.nextConfigPublicEnv).toBe('hello again from next.config.js') } @@ -138,17 +145,84 @@ const runTests = (mode = 'dev') => { describe('Env Config', () => { describe('dev mode', () => { beforeAll(async () => { + output = '' appPort = await findPort() app = await launchApp(appDir, appPort, { env: { PROCESS_ENV_KEY: 'processenvironment', ENV_FILE_PROCESS_ENV: 'env-cli', }, + onStdout(msg) { + output += msg || '' + }, + onStderr(msg) { + output += msg || '' + }, }) + + await renderViaHTTP(appPort, '/another-global') }) afterAll(() => killApp(app)) runTests('dev') + + describe('with hot reload', () => { + const originalContents = [] + beforeAll(async () => { + const outputIndex = output.length + const envToUpdate = [ + { + toAdd: 'NEW_ENV_KEY=true', + file: '.env', + }, + { + toAdd: 'NEW_ENV_LOCAL_KEY=hello', + file: '.env.local', + }, + { + toAdd: 'NEW_ENV_DEV_KEY=from-dev\nNEXT_PUBLIC_HELLO_WORLD=again', + file: '.env.development', + }, + ] + + for (const { file, toAdd } of envToUpdate) { + const content = await fs.readFile(join(appDir, file), 'utf8') + originalContents.push({ file, content }) + await fs.writeFile(join(appDir, file), content + '\n' + toAdd) + } + + await check(() => { + return output.substring(outputIndex) + }, /Loaded env from/) + }) + afterAll(async () => { + for (const { file, content } of originalContents) { + await fs.writeFile(join(appDir, file), content) + } + }) + + runTests('dev', true) + + it('should update inlined values correctly', async () => { + await renderViaHTTP(appPort, '/another-global') + + const buildManifest = await fs.readJson( + join(__dirname, '../app/.next/build-manifest.json') + ) + + const pageFile = buildManifest.pages['/another-global'].find( + (filename) => filename.includes('pages/another-global') + ) + + // read client bundle contents since a server side render can + // have the value available during render but it not be injected + const bundleContent = await fs.readFile( + join(appDir, '.next', pageFile), + 'utf8' + ) + expect(bundleContent).toContain('again') + }) + }) }) describe('test environment', () => { diff --git a/test/integration/image-future/base-path/test/static.test.js b/test/integration/image-future/base-path/test/static.test.js index 077fab76c9e9f29..0a55ccc0d9a50fc 100644 --- a/test/integration/image-future/base-path/test/static.test.js +++ b/test/integration/image-future/base-path/test/static.test.js @@ -65,7 +65,7 @@ const runTests = () => { }) it('Should add a blur to a statically imported image', async () => { expect(html).toContain( - `style="background-size:cover;background-position:0% 0%;background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http%3A//www.w3.org/2000/svg' viewBox='0 0 400 300'%3E%3Cfilter id='b' color-interpolation-filters='sRGB'%3E%3CfeGaussianBlur stdDeviation='50'/%3E%3CfeComponentTransfer%3E%3CfeFuncA type='discrete' tableValues='1 1'/%3E%3C/feComponentTransfer%3E%3C/filter%3E%3Cimage filter='url(%23b)' x='0' y='0' height='100%25' width='100%25' href='data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wCEAAoKCgoKCgsMDAsPEA4QDxYUExMUFiIYGhgaGCIzICUgICUgMy03LCksNy1RQDg4QFFeT0pPXnFlZXGPiI+7u/sBCgoKCgoKCwwMCw8QDhAPFhQTExQWIhgaGBoYIjMgJSAgJSAzLTcsKSw3LVFAODhAUV5PSk9ecWVlcY+Ij7u7+//CABEIAAYACAMBIgACEQEDEQH/xAAnAAEBAAAAAAAAAAAAAAAAAAAABwEBAAAAAAAAAAAAAAAAAAAAAP/aAAwDAQACEAMQAAAAmgP/xAAcEAACAQUBAAAAAAAAAAAAAAASFBMAAQMFERX/2gAIAQEAAT8AZ1HjrKZX55JysIc4Ff/EABQRAQAAAAAAAAAAAAAAAAAAAAD/2gAIAQIBAT8Af//EABQRAQAAAAAAAAAAAAAAAAAAAAD/2gAIAQMBAT8Af//Z'/%3E%3C/svg%3E")` + `style="background-size:cover;background-position:0% 0%;background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http%3A//www.w3.org/2000/svg' viewBox='0 0 400 300'%3E%3Cfilter id='b' color-interpolation-filters='sRGB'%3E%3CfeGaussianBlur stdDeviation='50'/%3E%3C/filter%3E%3Cimage filter='url(%23b)' x='0' y='0' height='100%25' width='100%25' href='data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wCEAAoKCgoKCgsMDAsPEA4QDxYUExMUFiIYGhgaGCIzICUgICUgMy03LCksNy1RQDg4QFFeT0pPXnFlZXGPiI+7u/sBCgoKCgoKCwwMCw8QDhAPFhQTExQWIhgaGBoYIjMgJSAgJSAzLTcsKSw3LVFAODhAUV5PSk9ecWVlcY+Ij7u7+//CABEIAAYACAMBIgACEQEDEQH/xAAnAAEBAAAAAAAAAAAAAAAAAAAABwEBAAAAAAAAAAAAAAAAAAAAAP/aAAwDAQACEAMQAAAAmgP/xAAcEAACAQUBAAAAAAAAAAAAAAASFBMAAQMFERX/2gAIAQEAAT8AZ1HjrKZX55JysIc4Ff/EABQRAQAAAAAAAAAAAAAAAAAAAAD/2gAIAQIBAT8Af//EABQRAQAAAAAAAAAAAAAAAAAAAAD/2gAIAQMBAT8Af//Z'/%3E%3C/svg%3E")` ) }) } diff --git a/test/integration/image-future/default/test/index.test.js b/test/integration/image-future/default/test/index.test.js index fb800d9a36d82e9..1a9715f4f40cee4 100644 --- a/test/integration/image-future/default/test/index.test.js +++ b/test/integration/image-future/default/test/index.test.js @@ -1053,11 +1053,11 @@ function runTests(mode) { $html('noscript > img').attr('id', 'unused') expect($html('#blurry-placeholder-raw')[0].attribs.style).toContain( - `background-size:cover;background-position:0% 0%;background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http%3A//www.w3.org/2000/svg' viewBox='0 0 400 400'%3E%3Cfilter id='b' color-interpolation-filters='sRGB'%3E%3CfeGaussianBlur stdDeviation='50'/%3E%3CfeComponentTransfer%3E%3CfeFuncA type='discrete' tableValues='1 1'/%3E%3C/feComponentTransfer%3E%3C/filter%3E%3Cimage filter='url(%23b)' x='0' y='0' height='100%25' width='100%25' href='data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8P4nhDwAGuAKPn6cicwAAAABJRU5ErkJggg=='/%3E%3C/svg%3E")` + `background-size:cover;background-position:0% 0%;background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http%3A//www.w3.org/2000/svg' viewBox='0 0 400 400'%3E%3Cfilter id='b' color-interpolation-filters='sRGB'%3E%3CfeGaussianBlur stdDeviation='50'/%3E%3C/filter%3E%3Cimage filter='url(%23b)' x='0' y='0' height='100%25' width='100%25' href='data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8P4nhDwAGuAKPn6cicwAAAABJRU5ErkJggg=='/%3E%3C/svg%3E")` ) expect($html('#blurry-placeholder-with-lazy')[0].attribs.style).toContain( - `background-size:cover;background-position:0% 0%;background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http%3A//www.w3.org/2000/svg' viewBox='0 0 400 400'%3E%3Cfilter id='b' color-interpolation-filters='sRGB'%3E%3CfeGaussianBlur stdDeviation='50'/%3E%3CfeComponentTransfer%3E%3CfeFuncA type='discrete' tableValues='1 1'/%3E%3C/feComponentTransfer%3E%3C/filter%3E%3Cimage filter='url(%23b)' x='0' y='0' height='100%25' width='100%25' href='data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mO0/8/wBwAE/wI85bEJ6gAAAABJRU5ErkJggg=='/%3E%3C/svg%3E")` + `background-size:cover;background-position:0% 0%;background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http%3A//www.w3.org/2000/svg' viewBox='0 0 400 400'%3E%3Cfilter id='b' color-interpolation-filters='sRGB'%3E%3CfeGaussianBlur stdDeviation='50'/%3E%3C/filter%3E%3Cimage filter='url(%23b)' x='0' y='0' height='100%25' width='100%25' href='data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mO0/8/wBwAE/wI85bEJ6gAAAABJRU5ErkJggg=='/%3E%3C/svg%3E")` ) }) @@ -1088,7 +1088,7 @@ function runTests(mode) { 'background-image' ) ).toBe( - `url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http%3A//www.w3.org/2000/svg' viewBox='0 0 400 400'%3E%3Cfilter id='b' color-interpolation-filters='sRGB'%3E%3CfeGaussianBlur stdDeviation='50'/%3E%3CfeComponentTransfer%3E%3CfeFuncA type='discrete' tableValues='1 1'/%3E%3C/feComponentTransfer%3E%3C/filter%3E%3Cimage filter='url(%23b)' x='0' y='0' height='100%25' width='100%25' href='data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mO0/8/wBwAE/wI85bEJ6gAAAABJRU5ErkJggg=='/%3E%3C/svg%3E")` + `url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http%3A//www.w3.org/2000/svg' viewBox='0 0 400 400'%3E%3Cfilter id='b' color-interpolation-filters='sRGB'%3E%3CfeGaussianBlur stdDeviation='50'/%3E%3C/filter%3E%3Cimage filter='url(%23b)' x='0' y='0' height='100%25' width='100%25' href='data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mO0/8/wBwAE/wI85bEJ6gAAAABJRU5ErkJggg=='/%3E%3C/svg%3E")` ) await browser.eval('document.getElementById("spacer").remove()') diff --git a/test/integration/image-future/default/test/static.test.js b/test/integration/image-future/default/test/static.test.js index 11b8df8e0dbce41..7e02bf407b2b8d0 100644 --- a/test/integration/image-future/default/test/static.test.js +++ b/test/integration/image-future/default/test/static.test.js @@ -65,7 +65,7 @@ const runTests = () => { }) it('Should add a blur to a statically imported image in "raw" mode', async () => { expect(html).toContain( - `style="background-size:cover;background-position:0% 0%;background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http%3A//www.w3.org/2000/svg' viewBox='0 0 400 300'%3E%3Cfilter id='b' color-interpolation-filters='sRGB'%3E%3CfeGaussianBlur stdDeviation='50'/%3E%3CfeComponentTransfer%3E%3CfeFuncA type='discrete' tableValues='1 1'/%3E%3C/feComponentTransfer%3E%3C/filter%3E%3Cimage filter='url(%23b)' x='0' y='0' height='100%25' width='100%25' href='data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wCEAAoKCgoKCgsMDAsPEA4QDxYUExMUFiIYGhgaGCIzICUgICUgMy03LCksNy1RQDg4QFFeT0pPXnFlZXGPiI+7u/sBCgoKCgoKCwwMCw8QDhAPFhQTExQWIhgaGBoYIjMgJSAgJSAzLTcsKSw3LVFAODhAUV5PSk9ecWVlcY+Ij7u7+//CABEIAAYACAMBIgACEQEDEQH/xAAnAAEBAAAAAAAAAAAAAAAAAAAABwEBAAAAAAAAAAAAAAAAAAAAAP/aAAwDAQACEAMQAAAAmgP/xAAcEAACAQUBAAAAAAAAAAAAAAASFBMAAQMFERX/2gAIAQEAAT8AZ1HjrKZX55JysIc4Ff/EABQRAQAAAAAAAAAAAAAAAAAAAAD/2gAIAQIBAT8Af//EABQRAQAAAAAAAAAAAAAAAAAAAAD/2gAIAQMBAT8Af//Z'/%3E%3C/svg%3E")` + `style="background-size:cover;background-position:0% 0%;background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http%3A//www.w3.org/2000/svg' viewBox='0 0 400 300'%3E%3Cfilter id='b' color-interpolation-filters='sRGB'%3E%3CfeGaussianBlur stdDeviation='50'/%3E%3C/filter%3E%3Cimage filter='url(%23b)' x='0' y='0' height='100%25' width='100%25' href='data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wCEAAoKCgoKCgsMDAsPEA4QDxYUExMUFiIYGhgaGCIzICUgICUgMy03LCksNy1RQDg4QFFeT0pPXnFlZXGPiI+7u/sBCgoKCgoKCwwMCw8QDhAPFhQTExQWIhgaGBoYIjMgJSAgJSAzLTcsKSw3LVFAODhAUV5PSk9ecWVlcY+Ij7u7+//CABEIAAYACAMBIgACEQEDEQH/xAAnAAEBAAAAAAAAAAAAAAAAAAAABwEBAAAAAAAAAAAAAAAAAAAAAP/aAAwDAQACEAMQAAAAmgP/xAAcEAACAQUBAAAAAAAAAAAAAAASFBMAAQMFERX/2gAIAQEAAT8AZ1HjrKZX55JysIc4Ff/EABQRAQAAAAAAAAAAAAAAAAAAAAD/2gAIAQIBAT8Af//EABQRAQAAAAAAAAAAAAAAAAAAAAD/2gAIAQMBAT8Af//Z'/%3E%3C/svg%3E")` ) }) } diff --git a/test/lib/next-modes/base.ts b/test/lib/next-modes/base.ts index f6d2d9302260f47..2e9b7432f9cc418 100644 --- a/test/lib/next-modes/base.ts +++ b/test/lib/next-modes/base.ts @@ -178,10 +178,14 @@ export class NextInstance { } } - const nextConfigFile = Object.keys(this.files).find((file) => + let nextConfigFile = Object.keys(this.files).find((file) => file.startsWith('next.config.') ) + if (await fs.pathExists(path.join(this.testDir, 'next.config.js'))) { + nextConfigFile = 'next.config.js' + } + if (nextConfigFile && this.nextConfig) { throw new Error( `nextConfig provided on "createNext()" and as a file "${nextConfigFile}", use one or the other to continue` diff --git a/test/production/fatal-render-errror/app/pages/_app.js b/test/production/fatal-render-errror/app/pages/_app.js new file mode 100644 index 000000000000000..904b7838ceebfcb --- /dev/null +++ b/test/production/fatal-render-errror/app/pages/_app.js @@ -0,0 +1,15 @@ +export default function App({ Component, pageProps }) { + if (process.env.NODE_ENV === 'production' && typeof window !== 'undefined') { + if (!window.renderAttempts) { + window.renderAttempts = 0 + } + window.renderAttempts++ + throw new Error('error in custom _app') + } + return ( + <> +

from _app

+ + + ) +} diff --git a/test/production/fatal-render-errror/app/pages/_error.js b/test/production/fatal-render-errror/app/pages/_error.js new file mode 100644 index 000000000000000..9b8ff8ee05875eb --- /dev/null +++ b/test/production/fatal-render-errror/app/pages/_error.js @@ -0,0 +1,6 @@ +export default function Error() { + if (process.env.NODE_ENV === 'production' && typeof window !== 'undefined') { + throw new Error('error in custom _app') + } + return
Error encountered!
+} diff --git a/test/production/fatal-render-errror/app/pages/index.js b/test/production/fatal-render-errror/app/pages/index.js new file mode 100644 index 000000000000000..08263e34c35fd22 --- /dev/null +++ b/test/production/fatal-render-errror/app/pages/index.js @@ -0,0 +1,3 @@ +export default function Page() { + return

index page

+} diff --git a/test/production/fatal-render-errror/app/pages/with-error.js b/test/production/fatal-render-errror/app/pages/with-error.js new file mode 100644 index 000000000000000..2a240431ba18880 --- /dev/null +++ b/test/production/fatal-render-errror/app/pages/with-error.js @@ -0,0 +1,6 @@ +export default function Error() { + if (process.env.NODE_ENV === 'production' && typeof window !== 'undefined') { + throw new Error('error in pages/with-error') + } + return
with-error
+} diff --git a/test/production/fatal-render-errror/index.test.ts b/test/production/fatal-render-errror/index.test.ts new file mode 100644 index 000000000000000..1037cda593f20ef --- /dev/null +++ b/test/production/fatal-render-errror/index.test.ts @@ -0,0 +1,55 @@ +import { createNext, FileRef } from 'e2e-utils' +import { NextInstance } from 'test/lib/next-modes/base' +import { check, renderViaHTTP, waitFor } from 'next-test-utils' +import webdriver from 'next-webdriver' +import { join } from 'path' + +describe('fatal-render-errror', () => { + let next: NextInstance + + beforeAll(async () => { + next = await createNext({ + files: new FileRef(join(__dirname, 'app')), + dependencies: {}, + }) + }) + afterAll(() => next.destroy()) + + it('should render page without error correctly', async () => { + const html = await renderViaHTTP(next.url, '/') + expect(html).toContain('index page') + expect(html).toContain('from _app') + }) + + it('should handle fatal error in _app and _error without loop on direct visit', async () => { + const browser = await webdriver(next.url, '/with-error') + + // wait a bit to see if we are rendering multiple times unexpectedly + await waitFor(500) + expect(await browser.eval('window.renderAttempts')).toBeLessThan(10) + + const html = await browser.eval('document.documentElement.innerHTML') + expect(html).not.toContain('from _app') + expect(html).toContain( + 'Application error: a client-side exception has occurred' + ) + }) + + it('should handle fatal error in _app and _error without loop on client-transition', async () => { + const browser = await webdriver(next.url, '/') + await browser.eval('window.renderAttempts = 0') + + await browser.eval('window.next.router.push("/with-error")') + await check(() => browser.eval('location.pathname'), '/with-error') + + // wait a bit to see if we are rendering multiple times unexpectedly + await waitFor(500) + expect(await browser.eval('window.renderAttempts')).toBeLessThan(10) + + const html = await browser.eval('document.documentElement.innerHTML') + expect(html).not.toContain('from _app') + expect(html).toContain( + 'Application error: a client-side exception has occurred' + ) + }) +}) diff --git a/test/production/required-server-files-i18n.test.ts b/test/production/required-server-files-i18n.test.ts index a8316fc530da0ba..17c5df1c80dff2a 100644 --- a/test/production/required-server-files-i18n.test.ts +++ b/test/production/required-server-files-i18n.test.ts @@ -135,6 +135,27 @@ describe('should set-up next', () => { if (server) await killApp(server) }) + it('should not apply locale redirect in minimal mode', async () => { + const res = await fetchViaHTTP(appPort, '/', undefined, { + redirect: 'manual', + headers: { + 'accept-language': 'fr', + }, + }) + expect(res.status).toBe(200) + expect(await res.text()).toContain('index page') + + const resCookie = await fetchViaHTTP(appPort, '/', undefined, { + redirect: 'manual', + headers: { + 'accept-language': 'en', + cookie: 'NEXT_LOCALE=fr', + }, + }) + expect(resCookie.status).toBe(200) + expect(await resCookie.text()).toContain('index page') + }) + it('should output required-server-files manifest correctly', async () => { expect(requiredFilesManifest.version).toBe(1) expect(Array.isArray(requiredFilesManifest.files)).toBe(true) diff --git a/test/production/typescript-basic/app/pages/index.tsx b/test/production/typescript-basic/app/pages/index.tsx index 85950f2e3452c56..e974da66bbafd91 100644 --- a/test/production/typescript-basic/app/pages/index.tsx +++ b/test/production/typescript-basic/app/pages/index.tsx @@ -1,6 +1,8 @@ import { useRouter } from 'next/router' import Link from 'next/link' import { type PageConfig } from 'next' +// eslint-disable-next-line @typescript-eslint/no-unused-vars +import { StyleRegistry, createStyleRegistry } from 'styled-jsx' export const config: PageConfig = {}