From d88793d973cb402dd877c855e7fea4ae7ff209a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bal=C3=A1zs=20Orb=C3=A1n?= Date: Wed, 16 Feb 2022 18:45:01 +0100 Subject: [PATCH 01/28] feat: improve opening a new issue flow (#34434) Ref: [Slack thread](https://vercel.slack.com/archives/CGU8HUTUH/p1645003443213449) When opening a new issue, it is desired that the user has checked if the `canary` release not already have fixed their issue, since we do not backport fixes to previous Next.js versions. - added a CLI warning when `next info` runs that looks like this: ![image](https://user-images.githubusercontent.com/18369201/154309275-01ccc979-01e5-4ccb-8a22-5deab64765a0.png) This links to a message docs page with more information and some useful links. - refactored our bug report templates to be more clear and removed the fields that are now unnecessary (since running `next info` is expected to run on releases that already have it) - Made browser/deployment optional, as in most cases those fields are irrelevant. We still ask them, but mention that they are only needed if relevant. - Asking for the exact browser version now ## Bug - [ ] Related issues linked using `fixes #number` - [ ] Integration tests added - [ ] Errors have helpful link attached, see `contributing.md` ## Feature - [ ] Implements an existing feature request or RFC. Make sure the feature request has been accepted for implementation before opening a PR. - [ ] Related issues linked using `fixes #number` - [ ] Integration tests added - [ ] Documentation added - [ ] Telemetry added. In case of a feature if it's used or not. - [ ] Errors have helpful link attached, see `contributing.md` ## Documentation / Examples - [ ] Make sure the linting passes by running `yarn lint` Co-authored-by: JJ Kasper <22380829+ijjk@users.noreply.github.com> --- .github/ISSUE_TEMPLATE/1.bug_report.yml | 45 +++++-------------- .../ISSUE_TEMPLATE/2.example_bug_report.yml | 45 ++++++++----------- errors/manifest.json | 4 ++ errors/opening-an-issue.md | 28 ++++++++++++ packages/next/cli/next-info.ts | 34 +++++++++++++- 5 files changed, 94 insertions(+), 62 deletions(-) create mode 100644 errors/opening-an-issue.md diff --git a/.github/ISSUE_TEMPLATE/1.bug_report.yml b/.github/ISSUE_TEMPLATE/1.bug_report.yml index d0aa0b7589ac..623e939c481f 100644 --- a/.github/ISSUE_TEMPLATE/1.bug_report.yml +++ b/.github/ISSUE_TEMPLATE/1.bug_report.yml @@ -7,49 +7,28 @@ body: value: Thanks for taking the time to file a bug report! Please fill out this form as completely as possible. - type: markdown attributes: - value: If you leave out sections there is a high likelihood it will be moved to the GitHub Discussions "Help" section. - - type: markdown - attributes: - value: 'Please first verify if your issue exists in the Next.js canary release line: `npm install next@canary`.' - - type: markdown - attributes: - value: 'next@canary is the beta version of Next.js. It includes all features and fixes that are pending to land on the stable release line.' + value: If you leave out sections there is a high likelihood it will be moved to the GitHub Discussions ["Help" section](https://github.com/vercel/next.js/discussions/categories/help). + - type: checkboxes + attributes: + label: Verify canary release + description: '`next@canary` is the canary version of Next.js that ships daily. It includes all features and fixes that have not been released to the stable version yet. Think of canary as a public beta. Some issues may already be fixed in the canary version, so please verify that your issue reproduces before opening a new issue.' + options: + - label: I verified that the issue exists in Next.js canary release + required: true - type: textarea attributes: - label: Run `next info` (available from version 12.0.8 and up) + label: Provide environment information description: Please run `next info` in the root directory of your project and paste the results. You might need to use `npx --no-install next info` if next is not in the current PATH. validations: required: false - type: input attributes: - label: What version of Next.js are you using? - description: 'For example: 10.0.1' - validations: - required: true - - type: input - attributes: - label: What version of Node.js are you using? - description: 'For example: 12.0.0' - validations: - required: true - - type: input - attributes: - label: What browser are you using? - description: 'For example: Chrome, Safari' - validations: - required: true - - type: input - attributes: - label: What operating system are you using? - description: 'For example: macOS, Windows' - validations: - required: true + label: What browser are you using? (if relevant) + description: 'Please specify the exact version. For example: Chrome 100.0.4878.0' - type: input attributes: - label: How are you deploying your application? + label: How are you deploying your application? (if relevant) description: 'For example: next start, next export, Vercel, Other platform' - validations: - required: true - type: textarea attributes: label: Describe the Bug diff --git a/.github/ISSUE_TEMPLATE/2.example_bug_report.yml b/.github/ISSUE_TEMPLATE/2.example_bug_report.yml index 8535a441bf4a..7fd1c3fe36d3 100644 --- a/.github/ISSUE_TEMPLATE/2.example_bug_report.yml +++ b/.github/ISSUE_TEMPLATE/2.example_bug_report.yml @@ -1,49 +1,40 @@ name: Example Bug Report -description: Create a bug report for the examples +description: Create a bug report for one of the Next.js examples labels: 'type: example,template: bug' body: - type: markdown attributes: - value: Thanks for taking the time to file a examples bug report! Please fill out this form as completely as possible. + value: Thanks for taking the time to file a bug report for [one of the examples](https://github.com/vercel/next.js/tree/canary/examples)! Please fill out this form as completely as possible. - type: markdown attributes: - value: If you leave out sections there is a high likelihood it will be moved to the GitHub Discussions "Help" section. - - type: input - attributes: - label: What example does this report relate to? - description: 'For example: with-styled-components' - validations: - required: true - - type: input + value: If you leave out sections there is a high likelihood it will be moved to the GitHub Discussions ["Help" section](https://github.com/vercel/next.js/discussions/categories/help). + - type: checkboxes attributes: - label: What version of Next.js are you using? - description: 'For example: 10.0.1' - validations: - required: true - - type: input + label: Verify canary release + description: '`next@canary` is the canary version of Next.js that ships daily. It includes all features and fixes that have not been released to the stable version yet. Think of canary as a public beta. Some issues may already be fixed in the canary version, so please verify that your issue reproduces before opening a new issue.' + options: + - label: I verified that the issue exists in Next.js canary release + required: true + - type: textarea attributes: - label: What version of Node.js are you using? - description: 'For example: 12.0.0' + label: Provide environment information + description: Please run `next info` in the root directory of your project and paste the results. You might need to use `npx --no-install next info` if next is not in the current PATH. validations: - required: true + required: false - type: input attributes: - label: What browser are you using? - description: 'For example: Chrome, Safari' + label: "What example does this report relate to? See a complete list in the [examples folder](https://github.com/vercel/next.js/tree/canary/examples). Note: Examples not in the examples folder might be maintained by the example's library author. Check out their projects before opening the issue on Next.js" + description: 'For example: with-styled-components' validations: required: true - type: input attributes: - label: What operating system are you using? - description: 'For example: macOS, Windows' - validations: - required: true + label: What browser are you using? (if relevant) + description: 'Please specify the exact version. For example: Chrome 100.0.4878.0' - type: input attributes: - label: How are you deploying your application? + label: How are you deploying your application? (if relevant) description: 'For example: next start, next export, Vercel, Other platform' - validations: - required: true - type: textarea attributes: label: Describe the Bug diff --git a/errors/manifest.json b/errors/manifest.json index a7e0bc694424..b14e95bfdd42 100644 --- a/errors/manifest.json +++ b/errors/manifest.json @@ -619,6 +619,10 @@ { "title": "ignored-compiler-options", "path": "/errors/ignored-compiler-options.md" + }, + { + "title": "opening-an-issue", + "path": "/errors/opening-an-issue.md" } ] } diff --git a/errors/opening-an-issue.md b/errors/opening-an-issue.md new file mode 100644 index 000000000000..179f895135e4 --- /dev/null +++ b/errors/opening-an-issue.md @@ -0,0 +1,28 @@ +# Opening a new Issue + +#### Why This Message Occurred + +When `next info` was run, Next.js detected that it's was not on the latest canary release. + +`next@canary` is the canary version of Next.js that ships daily. It includes all features and fixes that have not been released to the stable version yet. Think of canary as a public beta. + +Some issues may already be fixed in the canary version, so please verify that your issue reproduces before opening a new issue. + +Run the following in the codebase: + +```sh +npm install next@canary +``` + +or + +```sh +yarn add next@canary +``` + +And go through the prepared reproduction steps once again, and check if the issue still exists. + +### Useful Links + +- [Video: How to Contribute to Open Source (Next.js)](https://www.youtube.com/watch?v=cuoNzXFLitc) +- [Contributing to Next.js](https://github.com/vercel/next.js/blob/canary/contributing.md) diff --git a/packages/next/cli/next-info.ts b/packages/next/cli/next-info.ts index b0e957bc8dcc..4fd15e44dfc5 100755 --- a/packages/next/cli/next-info.ts +++ b/packages/next/cli/next-info.ts @@ -4,6 +4,7 @@ import childProcess from 'child_process' import chalk from 'next/dist/compiled/chalk' import arg from 'next/dist/compiled/arg/index.js' +import fetch from 'next/dist/compiled/node-fetch' import { printAndExit } from '../server/lib/utils' import { cliCommand } from '../bin/next' import isError from '../lib/is-error' @@ -41,6 +42,8 @@ const nextInfo: cliCommand = async (argv) => { return } + const installedRelease = getPackageVersion('next') + console.log(` Operating System: Platform: ${os.platform()} @@ -52,9 +55,36 @@ const nextInfo: cliCommand = async (argv) => { Yarn: ${getBinaryVersion('yarn')} pnpm: ${getBinaryVersion('pnpm')} Relevant packages: - next: ${getPackageVersion('next')} + next: ${installedRelease} react: ${getPackageVersion('react')} - react-dom: ${getPackageVersion('react-dom')}`) + react-dom: ${getPackageVersion('react-dom')} +`) + + try { + const res = await fetch( + 'https://api.github.com/repos/vercel/next.js/releases' + ) + const releases = await res.json() + const newestRelease = releases[0].tag_name.replace(/^v/, '') + + if (installedRelease !== newestRelease) { + console.warn( + `${chalk.yellow( + chalk.bold('warn') + )} - Latest canary version not detected, detected: "${installedRelease}", newest: "${newestRelease}". + Please try the latest canary version (\`npm install next@canary\`) to confirm the issue still exists before creating a new issue. + Read more - https://nextjs.org/docs/messages/opening-an-issue` + ) + } + } catch { + console.warn( + `${chalk.yellow( + chalk.bold('warn') + )} - Failed to fetch latest canary version. Visit https://github.com/vercel/next.js/releases. Detected "${installedRelease}". + Make sure to try the latest canary version (\`npm install next@canary\`) to confirm the issue still exists before creating a new issue. + Read more - https://nextjs.org/docs/messages/opening-an-issue` + ) + } } export { nextInfo } From 7e93a89ba05c70078647c6bb4dfd62372053fead Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bal=C3=A1zs=20Orb=C3=A1n?= Date: Wed, 16 Feb 2022 19:01:33 +0100 Subject: [PATCH 02/28] Update 2.example_bug_report.yml --- .github/ISSUE_TEMPLATE/2.example_bug_report.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/2.example_bug_report.yml b/.github/ISSUE_TEMPLATE/2.example_bug_report.yml index 7fd1c3fe36d3..361d68636214 100644 --- a/.github/ISSUE_TEMPLATE/2.example_bug_report.yml +++ b/.github/ISSUE_TEMPLATE/2.example_bug_report.yml @@ -23,8 +23,8 @@ body: required: false - type: input attributes: - label: "What example does this report relate to? See a complete list in the [examples folder](https://github.com/vercel/next.js/tree/canary/examples). Note: Examples not in the examples folder might be maintained by the example's library author. Check out their projects before opening the issue on Next.js" - description: 'For example: with-styled-components' + label: Which example does this report relate to? + description: "See a complete list in the [examples folder](https://github.com/vercel/next.js/tree/canary/examples). For example: with-styled-components. Note: Examples not in the examples folder might be maintained by the example's library author. Check out their projects before opening the issue on Next.js" validations: required: true - type: input From 9639fe704cf5c4a5a477bdc0c43219514c811601 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bal=C3=A1zs=20Orb=C3=A1n?= Date: Wed, 16 Feb 2022 19:53:48 +0100 Subject: [PATCH 03/28] Ensure we don't poll page in development when notFound: true is returned (#34352) Fixes: #34342 Visiting the following page will call gSSP indefinitely in a loop and logs errors from `on-demand-entries-client`: ```js const Home = () => null export default Home export function getServerSideProps() { console.log("gssp called") return { notFound: true } } ``` We should not keep fetching the page if it returns 404 as it can introduce unnecessary data requests. ## Bug - [x] Related issues linked using `fixes #number` - [x] Integration tests added - [ ] Errors have helpful link attached, see `contributing.md` Co-authored-by: JJ Kasper <22380829+ijjk@users.noreply.github.com> --- .../client/dev/on-demand-entries-client.js | 12 ++++++- packages/next/server/base-server.ts | 3 ++ packages/next/server/render.tsx | 3 ++ packages/next/server/request-meta.ts | 1 + packages/next/shared/lib/utils.ts | 1 + test/development/gssp-notfound/index.test.ts | 34 +++++++++++++++++++ 6 files changed, 53 insertions(+), 1 deletion(-) create mode 100644 test/development/gssp-notfound/index.test.ts diff --git a/packages/next/client/dev/on-demand-entries-client.js b/packages/next/client/dev/on-demand-entries-client.js index 349f95f5c22d..c68a9dd287e4 100644 --- a/packages/next/client/dev/on-demand-entries-client.js +++ b/packages/next/client/dev/on-demand-entries-client.js @@ -11,7 +11,17 @@ export default async (page) => { } else { Router.ready(() => { setInterval(() => { - sendMessage(JSON.stringify({ event: 'ping', page: Router.pathname })) + // when notFound: true is returned we should use the notFoundPage + // as the Router.pathname will point to the 404 page but we want + // to ping the source page that returned notFound: true instead + const notFoundSrcPage = self.__NEXT_DATA__.notFoundSrcPage + const pathname = + (Router.pathname === '/404' || Router.pathname === '/_error') && + notFoundSrcPage + ? notFoundSrcPage + : Router.pathname + + sendMessage(JSON.stringify({ event: 'ping', page: pathname })) }, 2500) }) } diff --git a/packages/next/server/base-server.ts b/packages/next/server/base-server.ts index 6b4a6ace6885..77160619770f 100644 --- a/packages/next/server/base-server.ts +++ b/packages/next/server/base-server.ts @@ -1550,6 +1550,9 @@ export default abstract class Server { res.body('{"notFound":true}').send() return null } else { + if (this.renderOpts.dev) { + query.__nextNotFoundSrcPage = pathname + } await this.render404( req, res, diff --git a/packages/next/server/render.tsx b/packages/next/server/render.tsx index e52b91eeabc2..87b45ab0b44a 100644 --- a/packages/next/server/render.tsx +++ b/packages/next/server/render.tsx @@ -528,9 +528,11 @@ export async function renderToHTML( const headTags = (...args: any) => callMiddleware('headTags', args) const isFallback = !!query.__nextFallback + const notFoundSrcPage = query.__nextNotFoundSrcPage delete query.__nextFallback delete query.__nextLocale delete query.__nextDefaultLocale + delete query.__nextIsNotFound const isSSG = !!getStaticProps const isBuildTimeSSG = isSSG && renderOpts.nextExport @@ -1374,6 +1376,7 @@ export async function renderToHTML( defaultLocale, domainLocales, isPreview: isPreview === true ? true : undefined, + notFoundSrcPage: notFoundSrcPage && dev ? notFoundSrcPage : undefined, }, buildManifest: filteredBuildManifest, docComponentsRendered, diff --git a/packages/next/server/request-meta.ts b/packages/next/server/request-meta.ts index e2fed315088d..f906e5f5f1f0 100644 --- a/packages/next/server/request-meta.ts +++ b/packages/next/server/request-meta.ts @@ -53,6 +53,7 @@ export function addRequestMeta( } type NextQueryMetadata = { + __nextNotFoundSrcPage?: string __nextDefaultLocale?: string __nextFallback?: 'true' __nextLocale?: string diff --git a/packages/next/shared/lib/utils.ts b/packages/next/shared/lib/utils.ts index 6c22ce7ff852..94196af4f597 100644 --- a/packages/next/shared/lib/utils.ts +++ b/packages/next/shared/lib/utils.ts @@ -107,6 +107,7 @@ export type NEXT_DATA = { domainLocales?: DomainLocale[] scriptLoader?: any[] isPreview?: boolean + notFoundSrcPage?: string rsc?: boolean } diff --git a/test/development/gssp-notfound/index.test.ts b/test/development/gssp-notfound/index.test.ts new file mode 100644 index 000000000000..9ad292b97405 --- /dev/null +++ b/test/development/gssp-notfound/index.test.ts @@ -0,0 +1,34 @@ +import { createNext } from 'e2e-utils' +import { NextInstance } from 'test/lib/next-modes/base' +import { waitFor } from 'next-test-utils' +import webdriver from 'next-webdriver' + +describe('getServerSideProps returns notFound: true', () => { + let next: NextInstance + + beforeAll(async () => { + next = await createNext({ + files: { + 'pages/index.js': ` + const Home = () => null + export default Home + + export function getServerSideProps() { + console.log("gssp called") + return { notFound: true } + } + `, + }, + dependencies: {}, + }) + }) + afterAll(() => next.destroy()) + + it('should not poll indefinitely', async () => { + const browser = await webdriver(next.appPort, '/') + await waitFor(3000) + await browser.close() + const logOccurrences = next.cliOutput.split('gssp called').length - 1 + expect(logOccurrences).toBe(1) + }) +}) From 8a55612c0d37d5b3fb6726eaa310aad01e0b42ab Mon Sep 17 00:00:00 2001 From: Steven Date: Wed, 16 Feb 2022 14:28:22 -0500 Subject: [PATCH 04/28] Add image config for `dangerouslyAllowSVG` and `contentSecurityPolicy` (#34431) ## Feature - [x] Integration tests added - [x] Documentation added - [x] Errors have helpful link attached, see `contributing.md` Co-authored-by: Lee Robinson <9113740+leerob@users.noreply.github.com> --- docs/api-reference/next/image.md | 18 ++- errors/invalid-images-config.md | 4 + packages/next/client/image.tsx | 4 + packages/next/server/config.ts | 22 ++++ packages/next/server/image-config.ts | 16 ++- packages/next/server/image-optimizer.ts | 25 +++- packages/next/server/next-server.ts | 3 +- .../default/test/index.test.js | 6 +- .../image-optimizer/test/index.test.js | 53 ++++++++ test/integration/image-optimizer/test/util.js | 120 ++++++++++-------- test/integration/production/next.config.js | 4 + test/integration/production/test/security.js | 2 +- 12 files changed, 208 insertions(+), 69 deletions(-) diff --git a/docs/api-reference/next/image.md b/docs/api-reference/next/image.md index 0e3d2cb1617a..f278c193a348 100644 --- a/docs/api-reference/next/image.md +++ b/docs/api-reference/next/image.md @@ -16,7 +16,8 @@ description: Enable Image Optimization with the built-in Image component. | Version | Changes | | --------- | ------------------------------------------------------------------------------------------------- | -| `v12.0.9` | `lazyRoot` prop added | +| `v12.1.0` | `dangerouslyAllowSVG` and `contentSecurityPolicy` configuration added. | +| `v12.0.9` | `lazyRoot` prop added. | | `v12.0.0` | `formats` configuration added.
AVIF support added.
Wrapper `
` changed to ``. | | `v11.1.0` | `onLoadingComplete` and `lazyBoundary` props added. | | `v11.0.0` | `src` prop support for static import.
`placeholder` prop added.
`blurDataURL` prop added. | @@ -439,6 +440,21 @@ module.exports = { } ``` +### Dangerously Allow SVG + +The default [loader](#loader) does not optimize SVG images for a few reasons. First, SVG is a vector format meaning it can be resized losslessly. Second, SVG has many of the same features as HTML/CSS, which can lead to vulnerabilities without proper [Content Security Policy (CSP) headers](/docs/advanced-features/security-headers.md). + +If you need to serve SVG images with the default Image Optimization API, you can set `dangerouslyAllowSVG` and `contentSecurityPolicy` inside your `next.config.js`: + +```js +module.exports = { + images: { + dangerouslyAllowSVG: true, + contentSecurityPolicy: "default-src 'self'; script-src 'none'; sandbox;", + }, +} +``` + ## Related For an overview of the Image component features and usage guidelines, see: diff --git a/errors/invalid-images-config.md b/errors/invalid-images-config.md index 581724439222..b409f6056830 100644 --- a/errors/invalid-images-config.md +++ b/errors/invalid-images-config.md @@ -27,6 +27,10 @@ module.exports = { minimumCacheTTL: 60, // ordered list of acceptable optimized image formats (mime types) formats: ['image/webp'], + // enable dangerous use of SVG images + dangerouslyAllowSVG: false, + // set the Content-Security-Policy header + contentSecurityPolicy: "default-src 'self'; script-src 'none'; sandbox;", }, } ``` diff --git a/packages/next/client/image.tsx b/packages/next/client/image.tsx index a634db7a22dd..164e75952635 100644 --- a/packages/next/client/image.tsx +++ b/packages/next/client/image.tsx @@ -385,6 +385,10 @@ export default function Image({ isLazy = false } + if (src.endsWith('.svg') && !config.dangerouslyAllowSVG) { + unoptimized = true + } + if (process.env.NODE_ENV !== 'production') { if (!src) { throw new Error( diff --git a/packages/next/server/config.ts b/packages/next/server/config.ts index 556992760980..f51deaa8254a 100644 --- a/packages/next/server/config.ts +++ b/packages/next/server/config.ts @@ -351,6 +351,28 @@ function assignDefaults(userConfig: { [key: string]: any }) { ) } } + + if ( + typeof images.dangerouslyAllowSVG !== 'undefined' && + typeof images.dangerouslyAllowSVG !== 'boolean' + ) { + throw new Error( + `Specified images.dangerouslyAllowSVG should be a boolean + ', ' + )}), received (${images.dangerouslyAllowSVG}).\nSee more info here: https://nextjs.org/docs/messages/invalid-images-config` + ) + } + + if ( + typeof images.contentSecurityPolicy !== 'undefined' && + typeof images.contentSecurityPolicy !== 'string' + ) { + throw new Error( + `Specified images.contentSecurityPolicy should be a string + ', ' + )}), received (${images.contentSecurityPolicy}).\nSee more info here: https://nextjs.org/docs/messages/invalid-images-config` + ) + } } if (result.webpack5 === false) { diff --git a/packages/next/server/image-config.ts b/packages/next/server/image-config.ts index 41dd013b9f2d..001745447c1a 100644 --- a/packages/next/server/image-config.ts +++ b/packages/next/server/image-config.ts @@ -29,16 +29,22 @@ export type ImageConfigComplete = { path: string /** @see [Image domains configuration](https://nextjs.org/docs/basic-features/image-optimization#domains) */ - domains?: string[] + domains: string[] /** @see [Cache behavior](https://nextjs.org/docs/api-reference/next/image#caching-behavior) */ - disableStaticImages?: boolean + disableStaticImages: boolean /** @see [Cache behavior](https://nextjs.org/docs/api-reference/next/image#caching-behavior) */ - minimumCacheTTL?: number + minimumCacheTTL: number /** @see [Acceptable formats](https://nextjs.org/docs/api-reference/next/image#acceptable-formats) */ - formats?: ImageFormat[] + formats: ImageFormat[] + + /** @see [Dangerously Allow SVG](https://nextjs.org/docs/api-reference/next/image#dangerously-allow-svg) */ + dangerouslyAllowSVG: boolean + + /** @see [Dangerously Allow SVG](https://nextjs.org/docs/api-reference/next/image#dangerously-allow-svg) */ + contentSecurityPolicy: string } export type ImageConfig = Partial @@ -52,4 +58,6 @@ export const imageConfigDefault: ImageConfigComplete = { disableStaticImages: false, minimumCacheTTL: 60, formats: ['image/webp'], + dangerouslyAllowSVG: false, + contentSecurityPolicy: `script-src 'none'; frame-src 'none'; sandbox;`, } diff --git a/packages/next/server/image-optimizer.ts b/packages/next/server/image-optimizer.ts index 56feff6010b3..35c10f24ed43 100644 --- a/packages/next/server/image-optimizer.ts +++ b/packages/next/server/image-optimizer.ts @@ -380,6 +380,16 @@ export async function imageOptimizer( } } + if (upstreamType === SVG && !nextConfig.images.dangerouslyAllowSVG) { + console.error( + `The requested resource "${href}" has type "${upstreamType}" but dangerouslyAllowSVG is disabled` + ) + throw new ImageError( + 400, + '"url" parameter is valid but image type is not allowed' + ) + } + if (upstreamType) { const vector = VECTOR_TYPES.includes(upstreamType) const animate = @@ -576,14 +586,15 @@ function getFileNameWithExtension( return `${fileName}.${extension}` } -export function setResponseHeaders( +function setResponseHeaders( req: IncomingMessage, res: ServerResponse, url: string, etag: string, contentType: string | null, isStatic: boolean, - xCache: XCacheHeader + xCache: XCacheHeader, + contentSecurityPolicy: string ) { res.setHeader('Vary', 'Accept') res.setHeader( @@ -608,7 +619,9 @@ export function setResponseHeaders( ) } - res.setHeader('Content-Security-Policy', `script-src 'none'; sandbox;`) + if (contentSecurityPolicy) { + res.setHeader('Content-Security-Policy', contentSecurityPolicy) + } res.setHeader('X-Nextjs-Cache', xCache) return { finished: false } @@ -621,7 +634,8 @@ export function sendResponse( extension: string, buffer: Buffer, isStatic: boolean, - xCache: XCacheHeader + xCache: XCacheHeader, + contentSecurityPolicy: string ) { const contentType = getContentType(extension) const etag = getHash([buffer]) @@ -632,7 +646,8 @@ export function sendResponse( etag, contentType, isStatic, - xCache + xCache, + contentSecurityPolicy ) if (!result.finished) { res.end(buffer) diff --git a/packages/next/server/next-server.ts b/packages/next/server/next-server.ts index c259936ebc2a..4c7779481822 100644 --- a/packages/next/server/next-server.ts +++ b/packages/next/server/next-server.ts @@ -255,7 +255,8 @@ export default class NextNodeServer extends BaseServer { cacheEntry.value.extension, cacheEntry.value.buffer, paramsResult.isStatic, - cacheEntry.isMiss ? 'MISS' : cacheEntry.isStale ? 'STALE' : 'HIT' + cacheEntry.isMiss ? 'MISS' : cacheEntry.isStale ? 'STALE' : 'HIT', + imagesConfig.contentSecurityPolicy ) } catch (err) { if (err instanceof ImageError) { diff --git a/test/integration/image-component/default/test/index.test.js b/test/integration/image-component/default/test/index.test.js index 8267389e1c94..0f390ed540d0 100644 --- a/test/integration/image-component/default/test/index.test.js +++ b/test/integration/image-component/default/test/index.test.js @@ -208,7 +208,7 @@ function runTests(mode) { ) await check( () => browser.eval(`document.getElementById("img3").currentSrc`), - /test(.*)svg/ + /test\.svg/ ) await check( () => browser.eval(`document.getElementById("img4").currentSrc`), @@ -224,7 +224,7 @@ function runTests(mode) { ) await check( () => browser.eval(`document.getElementById("msg3").textContent`), - 'loaded 1 img3 with dimensions 266x266' + 'loaded 1 img3 with dimensions 400x400' ) await check( () => browser.eval(`document.getElementById("msg4").textContent`), @@ -1077,7 +1077,7 @@ function runTests(mode) { expect( await hasImageMatchingUrl( browser, - `http://localhost:${appPort}/_next/image?url=%2Ftest.svg&w=828&q=75` + `http://localhost:${appPort}/test.svg` ) ).toBe(true) expect( diff --git a/test/integration/image-optimizer/test/index.test.js b/test/integration/image-optimizer/test/index.test.js index 52797c710166..44ae3d8b8b2b 100644 --- a/test/integration/image-optimizer/test/index.test.js +++ b/test/integration/image-optimizer/test/index.test.js @@ -224,6 +224,56 @@ describe('Image Optimizer', () => { `Specified images.loader property (imgix) also requires images.path property to be assigned to a URL prefix.` ) }) + + it('should error when images.dangerouslyAllowSVG is not a boolean', async () => { + await nextConfig.replace( + '{ /* replaceme */ }', + JSON.stringify({ + images: { + dangerouslyAllowSVG: 'foo', + }, + }) + ) + let stderr = '' + + app = await launchApp(appDir, await findPort(), { + onStderr(msg) { + stderr += msg || '' + }, + }) + await waitFor(1000) + await killApp(app).catch(() => {}) + await nextConfig.restore() + + expect(stderr).toContain( + `Specified images.dangerouslyAllowSVG should be a boolean` + ) + }) + + it('should error when images.contentSecurityPolicy is not a string', async () => { + await nextConfig.replace( + '{ /* replaceme */ }', + JSON.stringify({ + images: { + contentSecurityPolicy: 1, + }, + }) + ) + let stderr = '' + + app = await launchApp(appDir, await findPort(), { + onStderr(msg) { + stderr += msg || '' + }, + }) + await waitFor(1000) + await killApp(app).catch(() => {}) + await nextConfig.restore() + + expect(stderr).toContain( + `Specified images.contentSecurityPolicy should be a string` + ) + }) }) // domains for testing @@ -240,11 +290,13 @@ describe('Image Optimizer', () => { describe('Server support for minimumCacheTTL in next.config.js', () => { const size = 96 // defaults defined in server/config.ts + const dangerouslyAllowSVG = true const ctx = { w: size, isDev: false, domains, minimumCacheTTL, + dangerouslyAllowSVG, imagesDir, appDir, } @@ -253,6 +305,7 @@ describe('Image Optimizer', () => { images: { domains, minimumCacheTTL, + dangerouslyAllowSVG, }, }) ctx.nextOutput = '' diff --git a/test/integration/image-optimizer/test/util.js b/test/integration/image-optimizer/test/util.js index 47867ab3b254..5c83a4fc74c5 100644 --- a/test/integration/image-optimizer/test/util.js +++ b/test/integration/image-optimizer/test/util.js @@ -186,29 +186,39 @@ export function runTests(ctx) { expect(isAnimated(await res.buffer())).toBe(true) }) - it('should maintain vector svg', async () => { - const query = { w: ctx.w, q: 90, url: '/test.svg' } - const opts = { headers: { accept: 'image/webp' } } - const res = await fetchViaHTTP(ctx.appPort, '/_next/image', query, opts) - expect(res.status).toBe(200) - expect(res.headers.get('Content-Type')).toContain('image/svg+xml') - expect(res.headers.get('Cache-Control')).toBe( - `public, max-age=0, must-revalidate` - ) - // SVG is compressible so will have accept-encoding set from - // compression - expect(res.headers.get('Vary')).toMatch(/^Accept(,|$)/) - expect(res.headers.get('etag')).toBeTruthy() - expect(res.headers.get('Content-Disposition')).toBe( - `inline; filename="test.svg"` - ) - const actual = await res.text() - const expected = await fs.readFile( - join(ctx.appDir, 'public', 'test.svg'), - 'utf8' - ) - expect(actual).toMatch(expected) - }) + if (ctx.dangerouslyAllowSVG) { + it('should maintain vector svg', async () => { + const query = { w: ctx.w, q: 90, url: '/test.svg' } + const opts = { headers: { accept: 'image/webp' } } + const res = await fetchViaHTTP(ctx.appPort, '/_next/image', query, opts) + expect(res.status).toBe(200) + expect(res.headers.get('Content-Type')).toContain('image/svg+xml') + expect(res.headers.get('Cache-Control')).toBe( + `public, max-age=0, must-revalidate` + ) + // SVG is compressible so will have accept-encoding set from + // compression + expect(res.headers.get('Vary')).toMatch(/^Accept(,|$)/) + expect(res.headers.get('etag')).toBeTruthy() + expect(res.headers.get('Content-Disposition')).toBe( + `inline; filename="test.svg"` + ) + const actual = await res.text() + const expected = await fs.readFile( + join(ctx.appDir, 'public', 'test.svg'), + 'utf8' + ) + expect(actual).toMatch(expected) + }) + } else { + it('should not allow vector svg', async () => { + const query = { w: ctx.w, q: 35, url: '/test.svg' } + const opts = { headers: { accept: 'image/webp' } } + const res = await fetchViaHTTP(ctx.appPort, '/_next/image', query, opts) + expect(res.status).toBe(400) + expect(await res.text()).toContain('valid but image type is not allowed') + }) + } it('should maintain ico format', async () => { const query = { w: ctx.w, q: 90, url: `/test.ico` } @@ -778,41 +788,43 @@ export function runTests(ctx) { } }) - it('should use cached image file when parameters are the same for svg', async () => { - await cleanImagesDir(ctx) + if (ctx.dangerouslyAllowSVG) { + it('should use cached image file when parameters are the same for svg', async () => { + await cleanImagesDir(ctx) - const query = { url: '/test.svg', w: ctx.w, q: 80 } - const opts = { headers: { accept: 'image/webp' } } + const query = { url: '/test.svg', w: ctx.w, q: 80 } + const opts = { headers: { accept: 'image/webp' } } - const res1 = await fetchViaHTTP(ctx.appPort, '/_next/image', query, opts) - expect(res1.status).toBe(200) - expect(res1.headers.get('X-Nextjs-Cache')).toBe('MISS') - expect(res1.headers.get('Content-Type')).toBe('image/svg+xml') - expect(res1.headers.get('Content-Disposition')).toBe( - `inline; filename="test.svg"` - ) - const etagOne = res1.headers.get('etag') + const res1 = await fetchViaHTTP(ctx.appPort, '/_next/image', query, opts) + expect(res1.status).toBe(200) + expect(res1.headers.get('X-Nextjs-Cache')).toBe('MISS') + expect(res1.headers.get('Content-Type')).toBe('image/svg+xml') + expect(res1.headers.get('Content-Disposition')).toBe( + `inline; filename="test.svg"` + ) + const etagOne = res1.headers.get('etag') - let json1 - await check(async () => { - json1 = await fsToJson(ctx.imagesDir) - return Object.keys(json1).some((dir) => { - return Object.keys(json1[dir]).some((file) => file.includes(etagOne)) - }) - ? 'success' - : 'fail' - }, 'success') + let json1 + await check(async () => { + json1 = await fsToJson(ctx.imagesDir) + return Object.keys(json1).some((dir) => { + return Object.keys(json1[dir]).some((file) => file.includes(etagOne)) + }) + ? 'success' + : 'fail' + }, 'success') - const res2 = await fetchViaHTTP(ctx.appPort, '/_next/image', query, opts) - expect(res2.status).toBe(200) - expect(res2.headers.get('X-Nextjs-Cache')).toBe('HIT') - expect(res2.headers.get('Content-Type')).toBe('image/svg+xml') - expect(res2.headers.get('Content-Disposition')).toBe( - `inline; filename="test.svg"` - ) - const json2 = await fsToJson(ctx.imagesDir) - expect(json2).toStrictEqual(json1) - }) + const res2 = await fetchViaHTTP(ctx.appPort, '/_next/image', query, opts) + expect(res2.status).toBe(200) + expect(res2.headers.get('X-Nextjs-Cache')).toBe('HIT') + expect(res2.headers.get('Content-Type')).toBe('image/svg+xml') + expect(res2.headers.get('Content-Disposition')).toBe( + `inline; filename="test.svg"` + ) + const json2 = await fsToJson(ctx.imagesDir) + expect(json2).toStrictEqual(json1) + }) + } it('should use cached image file when parameters are the same for animated gif', async () => { await cleanImagesDir(ctx) diff --git a/test/integration/production/next.config.js b/test/integration/production/next.config.js index cc65ddf36517..e661257a8b2d 100644 --- a/test/integration/production/next.config.js +++ b/test/integration/production/next.config.js @@ -39,4 +39,8 @@ module.exports = { }, ] }, + images: { + // Make sure we have sane default CSP, even when SVG is enabled + dangerouslyAllowSVG: true, + }, } diff --git a/test/integration/production/test/security.js b/test/integration/production/test/security.js index 6ce693ef84b7..8a4778411b7a 100644 --- a/test/integration/production/test/security.js +++ b/test/integration/production/test/security.js @@ -319,7 +319,7 @@ module.exports = (context) => { }) if (browserName !== 'internet explorer') { - it('should not execute script embedded inside svg image', async () => { + it('should not execute script embedded inside svg image, even if dangerouslyAllowSVG=true', async () => { let browser try { browser = await webdriver(context.appPort, '/svg-image') From 01524ef20fe102d623bcde01e6b9d04e67e6f291 Mon Sep 17 00:00:00 2001 From: JJ Kasper Date: Wed, 16 Feb 2022 14:05:39 -0600 Subject: [PATCH 05/28] Revert swc css bump temporarily (#34440) This reverts the below two swc crates bumps to unblock testing changes while an issue with `styled-jsx` interpolation is investigated, we can reland after that issue is addressed. Reverts https://github.com/vercel/next.js/pull/34408 Reverts https://github.com/vercel/next.js/pull/34355 x-ref: [slack thread](https://vercel.slack.com/archives/C02HY34AKME/p1645032079994029) --- packages/next-swc/Cargo.lock | 117 ++++++++---------- packages/next-swc/crates/core/Cargo.toml | 8 +- .../core/src/styled_jsx/transform_css.rs | 87 +++++++++---- .../styled-jsx/ts-with-css-resolve/output.js | 4 +- .../css-selector-after-pseudo/output.js | 2 +- .../fixture/styled-jsx/expressions/output.js | 14 ++- .../styled-jsx/external-stylesheet/output.js | 4 +- .../fixture/styled-jsx/fragment/output.js | 2 +- .../tests/fixture/styled-jsx/global/output.js | 2 +- .../fixture/styled-jsx/issue-30480/output.js | 2 +- .../fixture/styled-jsx/issue-30570/output.js | 2 +- .../output.js | 5 +- .../tests/fixture/styled-jsx/styles/output.js | 12 +- .../fixture/styled-jsx/too-many/output.js | 16 ++- .../fixture/styled-jsx/tpl-escape-1/output.js | 2 +- .../fixture/styled-jsx/tpl-escape-2/output.js | 3 +- .../tpl-placeholder-1-as-property/output.js | 2 +- .../output.js | 3 +- .../tpl-placeholder-3-as-value/output.js | 2 +- .../output.js | 4 +- .../output.js | 3 +- .../transform-css-complex-selector/output.js | 2 +- .../styled-jsx/transform-css-global/output.js | 2 +- .../transform-css-media-query/output.js | 2 +- .../styled-jsx/transform-css-normal/output.js | 2 +- .../styled-jsx/transform-css/output.js | 2 +- .../crates/core/tests/full/example/output.js | 18 +-- .../core/tests/loader/css-hygiene-1/output.js | 2 +- .../core/tests/loader/example/output.js | 2 +- .../core/tests/loader/issue-31627/output.js | 4 +- .../loader/styled-components/1/output.js | 2 +- packages/next-swc/crates/napi/Cargo.toml | 6 +- packages/next-swc/crates/wasm/Cargo.toml | 4 +- 33 files changed, 200 insertions(+), 144 deletions(-) diff --git a/packages/next-swc/Cargo.lock b/packages/next-swc/Cargo.lock index af269fdef7c0..603918b6e739 100644 --- a/packages/next-swc/Cargo.lock +++ b/packages/next-swc/Cargo.lock @@ -812,7 +812,7 @@ dependencies = [ "swc_css", "swc_ecma_loader", "swc_ecma_transforms_testing", - "swc_ecmascript 0.114.2", + "swc_ecmascript", "swc_node_base", "swc_stylis", "testing", @@ -839,7 +839,7 @@ dependencies = [ "swc_bundler", "swc_common", "swc_ecma_loader", - "swc_ecmascript 0.114.2", + "swc_ecmascript", "swc_node_base", ] @@ -915,9 +915,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.9.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da32515d9f6e6e489d7bc9d84c71b060db7247dc035bbe44eac88cf87486d8d5" +checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56" [[package]] name = "opaque-debug" @@ -1589,15 +1589,15 @@ dependencies = [ "serde", "swc_atoms", "swc_common", - "swc_ecmascript 0.112.6", + "swc_ecmascript", "tracing", ] [[package]] name = "swc" -version = "0.126.2" +version = "0.121.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c2373a6f6dddd42719a2d8fcd9989849915ae15691fd1a75f6b04d14a6cab91" +checksum = "e6d2753ff42ccbcf55b444c5b5c75bdf8d711b9c022c6117a1945a5b259721da" dependencies = [ "ahash", "anyhow", @@ -1628,7 +1628,7 @@ dependencies = [ "swc_ecma_transforms_optimization", "swc_ecma_utils", "swc_ecma_visit", - "swc_ecmascript 0.114.2", + "swc_ecmascript", "swc_node_comments", "swc_visit", "tracing", @@ -1646,9 +1646,9 @@ dependencies = [ [[package]] name = "swc_bundler" -version = "0.107.0" +version = "0.105.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5b6761f9b57072658a42ff25304a7edc7f8444024726ed27d5861e4644c1ffe" +checksum = "6288db2c327430667c9ab30fb053a76f6dbb4868569ef9833e04d355ef1e1d96" dependencies = [ "ahash", "anyhow", @@ -1711,9 +1711,9 @@ dependencies = [ [[package]] name = "swc_css" -version = "0.85.0" +version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6f657d1876aa951dabce2be53af58e73f3c6fd61d17f177b647966ad9d77168" +checksum = "75a4e8913ddbf92a39e087996c30a80dfcac5192adc3fb63cd3beabe8975b0c8" dependencies = [ "swc_css_ast", "swc_css_codegen", @@ -1724,9 +1724,9 @@ dependencies = [ [[package]] name = "swc_css_ast" -version = "0.77.0" +version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26127ec7150da493706c0a8153cf1ad73fe1f2dd4e010e51ca783a97d4881a44" +checksum = "b0861c74eba5c61ade5c1ef3a14e3e0fd0699f20e5619bbc292c2ac4f9463617" dependencies = [ "is-macro", "serde", @@ -1737,9 +1737,9 @@ dependencies = [ [[package]] name = "swc_css_codegen" -version = "0.82.0" +version = "0.44.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f7181cd4048b62f2e89e5a26b38c7d2d6bb5cd9080850b8d0278f8525062688" +checksum = "de309d82614f8d3613c27f4ccafbacc1ed524ca5bfd7792387f76314d5131df2" dependencies = [ "auto_impl", "bitflags", @@ -1764,9 +1764,9 @@ dependencies = [ [[package]] name = "swc_css_parser" -version = "0.83.0" +version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69ce9cc25996b42c50a0d2312021faf9e2d646a8c5baea6ea17ab7e928ba4bda" +checksum = "ac061b34fb0a3afa4ac5777b705c149f91c2ab29fc5f8463acb2bf8a17e02938" dependencies = [ "bitflags", "lexical", @@ -1777,9 +1777,9 @@ dependencies = [ [[package]] name = "swc_css_utils" -version = "0.74.0" +version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee0b4a58cce4b02567f54c079cd7ce3a18bfb79638f81f2197079e7c35e4f749" +checksum = "94cdc01e5631f63f8d4c52545d9a61ed5a6616a2d8d4d4b6235ea2ff779ae80c" dependencies = [ "swc_atoms", "swc_common", @@ -1789,9 +1789,9 @@ dependencies = [ [[package]] name = "swc_css_visit" -version = "0.76.0" +version = "0.41.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d4bab1a12ffe359eca6413b09865e27c9e05c0e8d765bebd558d14df31f6e44" +checksum = "ce49ce82798c85e1a8f2a46552b6ef9530868302c333f8b135da3b5c9d5b1ca8" dependencies = [ "swc_atoms", "swc_common", @@ -1862,16 +1862,13 @@ dependencies = [ [[package]] name = "swc_ecma_lints" -version = "0.14.8" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9dc7e7d669e28b2df325d9a232f1d74e7b77d2606c04f0f70cc37e38dcf49ad3" +checksum = "a4c9664c8261d32d1dff6df9978c4aa3b0ac686b8bbcab9d4cc8a2f56c9efdfc" dependencies = [ - "ahash", "auto_impl", - "dashmap", "parking_lot", "rayon", - "regex", "serde", "swc_atoms", "swc_common", @@ -1902,9 +1899,9 @@ dependencies = [ [[package]] name = "swc_ecma_minifier" -version = "0.74.1" +version = "0.72.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "184e0e6337f2a4cfff36e3456e7937846dadf0b47833b7f711654e4acd0a13aa" +checksum = "6541c47325e3b6b2c0a1ecdff32049bc3ae83765eb09e49f532ed4fcbe54a6e7" dependencies = [ "ahash", "indexmap", @@ -1931,9 +1928,9 @@ dependencies = [ [[package]] name = "swc_ecma_parser" -version = "0.88.3" +version = "0.88.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfd30c93f08afdf29226b5695e45aadcc6ce452470cc63ea87a7eb53d29bb02b" +checksum = "016e15f5837f1fb954c7a693a0229fc25cba2982ab9c13773db44ce0e00dc275" dependencies = [ "either", "enum_kind", @@ -1951,9 +1948,9 @@ dependencies = [ [[package]] name = "swc_ecma_preset_env" -version = "0.90.0" +version = "0.88.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5335f575d5cd4fcefe8bb3c52fa8dc8d59d461f07da43fb2ac6c31689aa88e0f" +checksum = "7ebd2ffd8d26a5d52d86da8d643038da2952e86993998f120554a80e0a64e2b5" dependencies = [ "ahash", "anyhow", @@ -1977,9 +1974,9 @@ dependencies = [ [[package]] name = "swc_ecma_transforms" -version = "0.117.0" +version = "0.115.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1fa132c1a736c2c61736958f1102249348a4cc911c7f60e3a6255aa49c1c03e" +checksum = "142a3d2bd7a1d78fd2112a98cb295b7b6aa67c2c590d639650882cd658fc43c9" dependencies = [ "swc_atoms", "swc_common", @@ -2020,9 +2017,9 @@ dependencies = [ [[package]] name = "swc_ecma_transforms_classes" -version = "0.46.0" +version = "0.45.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac8983235c6902879b65dcb1003d4084adf094408c96d94d62d3f33f44c3fa8e" +checksum = "d8fec6a1780299ff1006539b71bed431bde5e48c9773235de4ccc414467ff0f2" dependencies = [ "swc_atoms", "swc_common", @@ -2034,9 +2031,9 @@ dependencies = [ [[package]] name = "swc_ecma_transforms_compat" -version = "0.70.0" +version = "0.69.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ff9a64fc99b205b6104a6996832b0006fc059f5aa9fe61d5fb2c3b9f1885b14" +checksum = "c7e1ec45963836c97a27c1c43c316ddfdddeac274aa793223a0a82391ba2304f" dependencies = [ "ahash", "arrayvec 0.7.2", @@ -2072,9 +2069,9 @@ dependencies = [ [[package]] name = "swc_ecma_transforms_module" -version = "0.78.0" +version = "0.76.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "441616e68981062a1650a923d2c63075021a8178045e2112007a90a157cdb1da" +checksum = "151f5f8fb7bf42d4bbc04e61af0b5fd84e31b1a3014e006f60246b1cac68274e" dependencies = [ "Inflector", "ahash", @@ -2094,9 +2091,9 @@ dependencies = [ [[package]] name = "swc_ecma_transforms_optimization" -version = "0.87.0" +version = "0.85.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e074149dd5e969d35a790851b47b0f76700b14fcfda1c05e15751c1458e2dd38" +checksum = "57792864e708f7ba41c4fed8c8b8e2c01dba8da4a44aa43ebb70a60e07e45265" dependencies = [ "ahash", "dashmap", @@ -2117,9 +2114,9 @@ dependencies = [ [[package]] name = "swc_ecma_transforms_proposal" -version = "0.77.0" +version = "0.76.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "048ac8ea82e02fa9a54b9aa448dc5d15a8e994304364fcd8c4e2f650572c9141" +checksum = "4e0c8f8ad3e3960658e4d7a4a87609b893b2a023f570e45b91f4ebe025845b43" dependencies = [ "either", "serde", @@ -2137,9 +2134,9 @@ dependencies = [ [[package]] name = "swc_ecma_transforms_react" -version = "0.80.0" +version = "0.78.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fb5b7c98597bf41d1503ca4039be5445fd02e7aa381ae520d1c78a8a370f7f5" +checksum = "3d93afe0884dbcb5d4f931d6b2673ba65ad17d0de4d1c88e69f2730f13817739" dependencies = [ "ahash", "base64 0.13.0", @@ -2185,9 +2182,9 @@ dependencies = [ [[package]] name = "swc_ecma_transforms_typescript" -version = "0.82.0" +version = "0.80.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7859a18a33f751d488fbc1b7a0073fb08c69d794ad1f8daa2da47bfda2d9242" +checksum = "4b136bcb8a598378542393b2e9b51231e23e34b858baa0582aa72e78a2d5fee5" dependencies = [ "serde", "swc_atoms", @@ -2202,9 +2199,9 @@ dependencies = [ [[package]] name = "swc_ecma_utils" -version = "0.65.3" +version = "0.65.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b462ac7dd5340544e7a12965bb7fbbbf9db8b26c1b32159b43c4b2430fed3fc8" +checksum = "a372b01214518c1dbfb9f84d7e08e8e46e2098e7e1f63eb9e9f6f06025ae2863" dependencies = [ "indexmap", "once_cell", @@ -2235,18 +2232,6 @@ name = "swc_ecmascript" version = "0.112.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ce38cf2c41ed841d82dbfb1c328379a3ec75d4e4e43900ea8c8ef3c51a44e3cb" -dependencies = [ - "swc_ecma_ast", - "swc_ecma_parser", - "swc_ecma_utils", - "swc_ecma_visit", -] - -[[package]] -name = "swc_ecmascript" -version = "0.114.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e752c12b209c65e203a952186ab2fcefa8021a17a8a393b7d5f410e1fe1a0e87" dependencies = [ "swc_ecma_ast", "swc_ecma_codegen", @@ -2328,9 +2313,9 @@ dependencies = [ [[package]] name = "swc_stylis" -version = "0.81.0" +version = "0.43.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cf66bb3ab3e136fb0852c2577824cd19b76f3346d7c5558cfdb782b5f14f6a4" +checksum = "75fd0f4f37579b15bbcd762b0fd1ae3fc6ef29215777b96cbc2bcdfaacd439ab" dependencies = [ "swc_atoms", "swc_common", @@ -2664,7 +2649,7 @@ dependencies = [ "serde_json", "swc", "swc_common", - "swc_ecmascript 0.114.2", + "swc_ecmascript", "tracing", "wasm-bindgen", "wasm-bindgen-futures", diff --git a/packages/next-swc/crates/core/Cargo.toml b/packages/next-swc/crates/core/Cargo.toml index 6e82e60b7a98..1b43fb3ebc4c 100644 --- a/packages/next-swc/crates/core/Cargo.toml +++ b/packages/next-swc/crates/core/Cargo.toml @@ -16,14 +16,14 @@ pathdiff = "0.2.0" serde = "1" serde_json = "1" styled_components = "0.14.0" -swc = "0.126.2" +swc = "0.121.7" swc_atoms = "0.2.7" swc_common = { version = "0.17.0", features = ["concurrent", "sourcemap"] } -swc_css = "0.85.0" +swc_css = "0.46.0" swc_ecma_loader = { version = "0.28.0", features = ["node", "lru"] } -swc_ecmascript = { version = "0.114.2", features = ["codegen", "minifier", "optimization", "parser", "react", "transforms", "typescript", "utils", "visit"] } +swc_ecmascript = { version = "0.112.6", features = ["codegen", "minifier", "optimization", "parser", "react", "transforms", "typescript", "utils", "visit"] } swc_node_base = "0.5.1" -swc_stylis = "0.81.0" +swc_stylis = "0.43.0" tracing = {version = "0.1.28", features = ["release_max_level_off"]} regex = "1.5" diff --git a/packages/next-swc/crates/core/src/styled_jsx/transform_css.rs b/packages/next-swc/crates/core/src/styled_jsx/transform_css.rs index d25cd05e6349..dcf00d73bf56 100644 --- a/packages/next-swc/crates/core/src/styled_jsx/transform_css.rs +++ b/packages/next-swc/crates/core/src/styled_jsx/transform_css.rs @@ -21,7 +21,7 @@ use tracing::{debug, trace}; use super::{hash_string, string_literal_expr, LocalStyle}; pub fn transform_css( - _cm: Arc, + cm: Arc, style_info: &LocalStyle, is_global: bool, class_name: &Option, @@ -33,6 +33,7 @@ pub fn transform_css( style_info.css_span.lo, style_info.css_span.hi, ParserConfig { + parse_values: false, allow_wrong_line_comments: true, }, // We ignore errors because we inject placeholders for expressions which is @@ -61,6 +62,7 @@ pub fn transform_css( }; // ? Do we need to support optionally prefixing? ss.visit_mut_with(&mut prefixer()); + ss.visit_mut_with(&mut CssPlaceholderFixer { cm }); ss.visit_mut_with(&mut Namespacer { class_name: match class_name { Some(s) => s.clone(), @@ -129,6 +131,48 @@ fn read_number(s: &str) -> (usize, usize) { unreachable!("read_number(`{}`) is invalid because it is empty", s) } +/// This fixes invalid css which is created from interpolated expressions. +/// +/// `__styled-jsx-placeholder-` is handled at here. +struct CssPlaceholderFixer { + cm: Arc, +} + +impl VisitMut for CssPlaceholderFixer { + fn visit_mut_media_query(&mut self, q: &mut MediaQuery) { + q.visit_mut_children_with(self); + + match q { + MediaQuery::Ident(q) => { + if !q.raw.starts_with("__styled-jsx-placeholder-") { + return; + } + // We need to support both of @media ($breakPoint) {} and @media $queryString {} + // This is complex because @media (__styled-jsx-placeholder-0__) {} is valid + // while @media __styled-jsx-placeholder-0__ {} is not + // + // So we check original source code to determine if we should inject + // parenthesis. + + // TODO(kdy1): Avoid allocation. + // To remove allocation, we should patch swc_common to provide a way to get + // source code without allocation. + // + // + // We need + // + // fn with_source_code (self: &mut Self, f: impl FnOnce(&str) -> Ret) -> _ {} + if let Ok(source) = self.cm.span_to_snippet(q.span) { + if source.starts_with('(') { + q.raw = format!("({})", &q.value).into(); + } + } + } + _ => {} + } + } +} + struct Namespacer { class_name: String, is_global: bool, @@ -185,7 +229,9 @@ impl VisitMut for Namespacer { } ComplexSelectorChildren::Combinator(v) => match v.value { CombinatorValue::Descendant => {} - _ => { + CombinatorValue::NextSibling + | CombinatorValue::Child + | CombinatorValue::LaterSibling => { combinator = Some(v.clone()); new_selectors.push(sel); @@ -205,22 +251,23 @@ impl Namespacer { ) -> Result, Error> { let mut pseudo_index = None; - let empty_tokens = vec![]; + let empty_tokens = Tokens { + span: node.span, + tokens: vec![], + }; let mut arg_tokens; for (i, selector) in node.subclass_selectors.iter().enumerate() { let (name, args) = match selector { SubclassSelector::PseudoClass(PseudoClassSelector { name, children, .. }) => { - arg_tokens = children - .iter() - .flatten() - .flat_map(|v| match v { - PseudoSelectorChildren::Nth(v) => nth_to_tokens(v).tokens, - PseudoSelectorChildren::PreservedToken(v) => vec![v.clone()], - }) - .collect::>(); - - (name, &arg_tokens) + match children { + Some(PseudoSelectorChildren::Nth(v)) => { + arg_tokens = nth_to_tokens(&v); + (name, &arg_tokens) + } + Some(PseudoSelectorChildren::Tokens(v)) => (name, v), + None => (name, &empty_tokens), + } } SubclassSelector::PseudoElement(PseudoElementSelector { name, children, .. @@ -233,19 +280,9 @@ impl Namespacer { // One off global selector if &name.value == "global" { - let args = args.clone(); - let mut args = { - let lo = args.first().map(|v| v.span.lo).unwrap_or(BytePos(0)); - let hi = args.last().map(|v| v.span.hi).unwrap_or(BytePos(0)); - - Tokens { - span: Span::new(lo, hi, Default::default()), - tokens: args, - } - }; - let block_tokens = get_block_tokens(&args); let mut front_tokens = get_front_selector_tokens(&args); + let mut args = args.clone(); front_tokens.extend(args.tokens); front_tokens.extend(block_tokens); args.tokens = front_tokens; @@ -254,6 +291,7 @@ impl Namespacer { let x: ComplexSelector = parse_tokens( &args, ParserConfig { + parse_values: false, allow_wrong_line_comments: true, }, // TODO(kdy1): We might be able to report syntax errors. @@ -484,6 +522,7 @@ fn nth_to_tokens(nth: &Nth) -> Tokens { let mut lexer = swc_css::parser::lexer::Lexer::new( StringInput::new(&s, nth.span.lo, nth.span.hi), ParserConfig { + parse_values: false, allow_wrong_line_comments: true, ..Default::default() }, diff --git a/packages/next-swc/crates/core/tests/errors/styled-jsx/ts-with-css-resolve/output.js b/packages/next-swc/crates/core/tests/errors/styled-jsx/ts-with-css-resolve/output.js index 6fbd823e7814..135a868443be 100644 --- a/packages/next-swc/crates/core/tests/errors/styled-jsx/ts-with-css-resolve/output.js +++ b/packages/next-swc/crates/core/tests/errors/styled-jsx/ts-with-css-resolve/output.js @@ -1,5 +1,5 @@ import _JSXStyle from "styled-jsx/style"; export default { - styles: <_JSXStyle id={"71f03d42ea0ec6"}>{".container.jsx-71f03d42ea0ec6{background:#000;color:white;font-weight:700;height:100px}"}, + styles: <_JSXStyle id={"71f03d42ea0ec6"}>{".container.jsx-71f03d42ea0ec6{background:#000;\ncolor:white;\nfont-weight:700;\nheight:100px}"}, className: "jsx-71f03d42ea0ec6" -}; +}; \ No newline at end of file diff --git a/packages/next-swc/crates/core/tests/fixture/styled-jsx/css-selector-after-pseudo/output.js b/packages/next-swc/crates/core/tests/fixture/styled-jsx/css-selector-after-pseudo/output.js index adc37567bc9b..6cb37534c641 100644 --- a/packages/next-swc/crates/core/tests/fixture/styled-jsx/css-selector-after-pseudo/output.js +++ b/packages/next-swc/crates/core/tests/fixture/styled-jsx/css-selector-after-pseudo/output.js @@ -4,7 +4,7 @@ function NavigationItem({ active , className }) { active }, className, "navigation-item") || "")}> - <_JSXStyle id={"2342aae4628612c6"}>{".navigation-item.jsx-2342aae4628612c6 a::after{content:attr(data-text);content:attr(data-text)/\"\"}"} + <_JSXStyle id={"2342aae4628612c6"}>{".navigation-item.jsx-2342aae4628612c6 a::after{content:attr(data-text);\ncontent: attr(data-text) / ''}"}
; } diff --git a/packages/next-swc/crates/core/tests/fixture/styled-jsx/expressions/output.js b/packages/next-swc/crates/core/tests/fixture/styled-jsx/expressions/output.js index 5822f4fe70aa..ea1779a5ca0e 100644 --- a/packages/next-swc/crates/core/tests/fixture/styled-jsx/expressions/output.js +++ b/packages/next-swc/crates/core/tests/fixture/styled-jsx/expressions/output.js @@ -51,7 +51,8 @@ export default (({ display })=>
test

- <_JSXStyle id={"6116059e04f3bff7"}>{`p.${color}.jsx-1ada4ad4dab7822f{color:${otherColor};display:${obj.display}}`} + <_JSXStyle id={"6116059e04f3bff7"}>{`p.${color}.jsx-1ada4ad4dab7822f{color:${otherColor}; +display:${obj.display}}`} <_JSXStyle id={"94239b6d6b42c9b5"}>{"p.jsx-1ada4ad4dab7822f{color:red}"} @@ -73,11 +74,16 @@ export default (({ display })=>
{`p.__jsx-style-dynamic-selector{color:${darken(color) + 2}}`} - <_JSXStyle id={"4e4be2da62837c76"}>{`@media(min-width:${mediumScreen}){p.jsx-1ada4ad4dab7822f{color:green}p.jsx-1ada4ad4dab7822f{color:${`red`}}}p.jsx-1ada4ad4dab7822f{color:red}`} + <_JSXStyle id={"4e4be2da62837c76"}>{`@media (min-width:${mediumScreen}) {p.jsx-1ada4ad4dab7822f{color:green} +p.jsx-1ada4ad4dab7822f{color:${`red`}}} +p.jsx-1ada4ad4dab7822f{color:red}`} - <_JSXStyle id={"27040f0829fb73d4"}>{`p.jsx-1ada4ad4dab7822f{-webkit-animation-duration:${animationDuration};animation-duration:${animationDuration}}`} + <_JSXStyle id={"27040f0829fb73d4"}>{`p.jsx-1ada4ad4dab7822f{-webkit-animation-duration:${animationDuration}; +animation-duration:${animationDuration}}`} - <_JSXStyle id={"3e72d735e703a530"}>{`p.jsx-1ada4ad4dab7822f{-webkit-animation:${animationDuration} forwards ${animationName};animation:${animationDuration} forwards ${animationName}}div.jsx-1ada4ad4dab7822f{background:${color}}`} + <_JSXStyle id={"3e72d735e703a530"}>{`p.jsx-1ada4ad4dab7822f{-webkit-animation:${animationDuration} forwards ${animationName}; +animation:${animationDuration} forwards ${animationName}} +div.jsx-1ada4ad4dab7822f{background:${color}}`} diff --git a/packages/next-swc/crates/core/tests/fixture/styled-jsx/external-stylesheet/output.js b/packages/next-swc/crates/core/tests/fixture/styled-jsx/external-stylesheet/output.js index e0304b413fd0..7dc7df9b4c8d 100644 --- a/packages/next-swc/crates/core/tests/fixture/styled-jsx/external-stylesheet/output.js +++ b/packages/next-swc/crates/core/tests/fixture/styled-jsx/external-stylesheet/output.js @@ -14,7 +14,7 @@ export default (()=>
woot
- <_JSXStyle id={"bee92b62eadf8a14"}>{"p.jsx-bee92b62eadf8a14{color:red}div.jsx-bee92b62eadf8a14{color:green}"} + <_JSXStyle id={"bee92b62eadf8a14"}>{"p.jsx-bee92b62eadf8a14{color:red}\ndiv.jsx-bee92b62eadf8a14{color:green}"} <_JSXStyle id={styles.__hash}>{styles} @@ -30,7 +30,7 @@ export const Test = ()=>
woot
- <_JSXStyle id={"bee92b62eadf8a14"}>{"p.jsx-bee92b62eadf8a14{color:red}div.jsx-bee92b62eadf8a14{color:green}"} + <_JSXStyle id={"bee92b62eadf8a14"}>{"p.jsx-bee92b62eadf8a14{color:red}\ndiv.jsx-bee92b62eadf8a14{color:green}"}
; diff --git a/packages/next-swc/crates/core/tests/fixture/styled-jsx/fragment/output.js b/packages/next-swc/crates/core/tests/fixture/styled-jsx/fragment/output.js index aefcddc43fcb..dda9c2b5fc7c 100644 --- a/packages/next-swc/crates/core/tests/fixture/styled-jsx/fragment/output.js +++ b/packages/next-swc/crates/core/tests/fixture/styled-jsx/fragment/output.js @@ -28,7 +28,7 @@ export default (()=><> - <_JSXStyle id={"6dd5f97e085c0297"}>{"p.jsx-6dd5f97e085c0297{color:cyan}.foo.jsx-6dd5f97e085c0297{font-size:18px;color:hotpink}#head.jsx-6dd5f97e085c0297{text-decoration:underline}"} + <_JSXStyle id={"6dd5f97e085c0297"}>{"p.jsx-6dd5f97e085c0297{color:cyan}\n.foo.jsx-6dd5f97e085c0297{font-size:18px;\ncolor:hotpink}\n#head.jsx-6dd5f97e085c0297{text-decoration:underline}"} ); diff --git a/packages/next-swc/crates/core/tests/fixture/styled-jsx/global/output.js b/packages/next-swc/crates/core/tests/fixture/styled-jsx/global/output.js index 2ec88553ec8d..7eab66c441b5 100644 --- a/packages/next-swc/crates/core/tests/fixture/styled-jsx/global/output.js +++ b/packages/next-swc/crates/core/tests/fixture/styled-jsx/global/output.js @@ -1,7 +1,7 @@ import _JSXStyle from "styled-jsx/style"; const Test = ()=>
- <_JSXStyle id={"d47d6adadf14e957"}>{"body{color:red}:hover{color:red;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-animation:foo 1s ease-out;animation:foo 1s ease-out}div a{display:none}[data-test]>div{color:red}"} + <_JSXStyle id={"d47d6adadf14e957"}>{"body{color:red}\n:hover{color:red;\ndisplay:-webkit-box;\ndisplay:-webkit-flex;\ndisplay:-ms-flexbox;\ndisplay:flex;\n-webkit-animation:foo 1s ease-out;\nanimation:foo 1s ease-out}\ndiv a{display:none}\n[data-test]>div{color:red}"}
; diff --git a/packages/next-swc/crates/core/tests/fixture/styled-jsx/issue-30480/output.js b/packages/next-swc/crates/core/tests/fixture/styled-jsx/issue-30480/output.js index f36b6e2e7383..d91b5759a137 100644 --- a/packages/next-swc/crates/core/tests/fixture/styled-jsx/issue-30480/output.js +++ b/packages/next-swc/crates/core/tests/fixture/styled-jsx/issue-30480/output.js @@ -10,7 +10,7 @@ export default (({ breakPoint })=>
{`@media(${breakPoint}){}`} + ]}>{`@media (${breakPoint}) {}`}
); diff --git a/packages/next-swc/crates/core/tests/fixture/styled-jsx/issue-30570/output.js b/packages/next-swc/crates/core/tests/fixture/styled-jsx/issue-30570/output.js index ac9cfafc4245..fec5cf368064 100644 --- a/packages/next-swc/crates/core/tests/fixture/styled-jsx/issue-30570/output.js +++ b/packages/next-swc/crates/core/tests/fixture/styled-jsx/issue-30570/output.js @@ -6,7 +6,7 @@ export default function IndexPage() { - <_JSXStyle id={"bbdada4ef17d18ef"}>{"@supports(display:flex){h1{color:hotpink}}"} + <_JSXStyle id={"bbdada4ef17d18ef"}>{"@supports (display:flex) {h1{color:hotpink}}"}
; }; diff --git a/packages/next-swc/crates/core/tests/fixture/styled-jsx/issue-31562-interpolation-in-mdea/output.js b/packages/next-swc/crates/core/tests/fixture/styled-jsx/issue-31562-interpolation-in-mdea/output.js index ac5394ea192a..fb59ef207106 100644 --- a/packages/next-swc/crates/core/tests/fixture/styled-jsx/issue-31562-interpolation-in-mdea/output.js +++ b/packages/next-swc/crates/core/tests/fixture/styled-jsx/issue-31562-interpolation-in-mdea/output.js @@ -36,7 +36,10 @@ export default class { Typography.base.size.mediumPlus, Target.largePlus, Typography.base.size.largePlus - ]}>{`html{font-size:${Typography.base.size.default};line-height:${Typography.base.lineHeight}}@media ${Target.mediumPlus}{html{font-size:${Typography.base.size.mediumPlus}}}@media ${Target.largePlus}{html{font-size:${Typography.base.size.largePlus}}}`} + ]}>{`html{font-size:${Typography.base.size.default}; +line-height:${Typography.base.lineHeight}} +@media ${Target.mediumPlus} {html{font-size:${Typography.base.size.mediumPlus}}} +@media ${Target.largePlus} {html{font-size:${Typography.base.size.largePlus}}}`}
; } diff --git a/packages/next-swc/crates/core/tests/fixture/styled-jsx/styles/output.js b/packages/next-swc/crates/core/tests/fixture/styled-jsx/styles/output.js index 7b61c0b7ec13..415058e5691e 100644 --- a/packages/next-swc/crates/core/tests/fixture/styled-jsx/styles/output.js +++ b/packages/next-swc/crates/core/tests/fixture/styled-jsx/styles/output.js @@ -11,18 +11,21 @@ export const uh = bar; export const foo = new String(`div.jsx-945eaa83250ed332{color:${color}}`); foo.__hash = "945eaa83250ed332"; ({ - styles: <_JSXStyle id={"c107a919a5b2943d"}>{`div.jsx-c107a919a5b2943d{color:${colors.green.light}}a.jsx-c107a919a5b2943d{color:red}`}, + styles: <_JSXStyle id={"c107a919a5b2943d"}>{`div.jsx-c107a919a5b2943d{color:${colors.green.light}} +a.jsx-c107a919a5b2943d{color:red}`}, className: "jsx-c107a919a5b2943d" }); const b = { - styles: <_JSXStyle id={"c107a919a5b2943d"}>{`div.jsx-c107a919a5b2943d{color:${colors.green.light}}a.jsx-c107a919a5b2943d{color:red}`}, + styles: <_JSXStyle id={"c107a919a5b2943d"}>{`div.jsx-c107a919a5b2943d{color:${colors.green.light}} +a.jsx-c107a919a5b2943d{color:red}`}, className: "jsx-c107a919a5b2943d" }; const dynamic = (colors1)=>{ const b = { styles: <_JSXStyle id={"60132422fc87f1d1"} dynamic={[ colors1.green.light - ]}>{`div.__jsx-style-dynamic-selector{color:${colors1.green.light}}a.__jsx-style-dynamic-selector{color:red}`}, + ]}>{`div.__jsx-style-dynamic-selector{color:${colors1.green.light}} +a.__jsx-style-dynamic-selector{color:red}`}, className: _JSXStyle.dynamic([ [ "60132422fc87f1d1", @@ -34,6 +37,7 @@ const dynamic = (colors1)=>{ }; }; export default { - styles: <_JSXStyle id={"e5da8dd7ff5c7f39"}>{`div.jsx-e5da8dd7ff5c7f39{font-size:3em}p.jsx-e5da8dd7ff5c7f39{color:${color}}`}, + styles: <_JSXStyle id={"e5da8dd7ff5c7f39"}>{`div.jsx-e5da8dd7ff5c7f39{font-size:3em} +p.jsx-e5da8dd7ff5c7f39{color:${color}}`}, className: "jsx-e5da8dd7ff5c7f39" }; diff --git a/packages/next-swc/crates/core/tests/fixture/styled-jsx/too-many/output.js b/packages/next-swc/crates/core/tests/fixture/styled-jsx/too-many/output.js index fd67a8dec05a..00ff7acd83a6 100644 --- a/packages/next-swc/crates/core/tests/fixture/styled-jsx/too-many/output.js +++ b/packages/next-swc/crates/core/tests/fixture/styled-jsx/too-many/output.js @@ -43,7 +43,21 @@ export const Red = ({ Component ="button" })=>{ e13, e14, e15 - ]}>{`.button.__jsx-style-dynamic-selector{--button-1:${e1};--button-2:${e2};--button-3:${e3};--button-4:${e4};--button-5:${e5};--button-6:${e6};--button-7:${e7};--button-8:${e8};--button-9:${e9};--button-10:${e10};--button-11:${e11};--button-12:${e12};--button-13:${e13};--button-14:${e14};--button-15:${e15}}`} + ]}>{`.button.__jsx-style-dynamic-selector{--button-1: ${e1}; +--button-2: ${e2}; +--button-3: ${e3}; +--button-4: ${e4}; +--button-5: ${e5}; +--button-6: ${e6}; +--button-7: ${e7}; +--button-8: ${e8}; +--button-9: ${e9}; +--button-10: ${e10}; +--button-11: ${e11}; +--button-12: ${e12}; +--button-13: ${e13}; +--button-14: ${e14}; +--button-15: ${e15}}`} ; }; diff --git a/packages/next-swc/crates/core/tests/fixture/styled-jsx/tpl-escape-1/output.js b/packages/next-swc/crates/core/tests/fixture/styled-jsx/tpl-escape-1/output.js index b12af6f02d53..7b9b2f69a803 100644 --- a/packages/next-swc/crates/core/tests/fixture/styled-jsx/tpl-escape-1/output.js +++ b/packages/next-swc/crates/core/tests/fixture/styled-jsx/tpl-escape-1/output.js @@ -5,7 +5,7 @@ export default class {

test

- <_JSXStyle id={"1f6cef12199c3a8f"}>{"p.jsx-1f6cef12199c3a8f{content:\"`\"}"} + <_JSXStyle id={"1f6cef12199c3a8f"}>{"p.jsx-1f6cef12199c3a8f{content:'`'}"} ; } diff --git a/packages/next-swc/crates/core/tests/fixture/styled-jsx/tpl-escape-2/output.js b/packages/next-swc/crates/core/tests/fixture/styled-jsx/tpl-escape-2/output.js index c94ca5493d06..3e3ed2489c67 100644 --- a/packages/next-swc/crates/core/tests/fixture/styled-jsx/tpl-escape-2/output.js +++ b/packages/next-swc/crates/core/tests/fixture/styled-jsx/tpl-escape-2/output.js @@ -11,7 +11,8 @@ export default function Home({ fontFamily }) { <_JSXStyle id={"f804e2f486b6ac13"} dynamic={[ fontFamily - ]}>{`body{font-family:${fontFamily}}code:before,code:after{content:"\`"}`} + ]}>{`body{font-family:${fontFamily}} +code:before, code:after{content:'\`'}`} ; }; diff --git a/packages/next-swc/crates/core/tests/fixture/styled-jsx/tpl-placeholder-1-as-property/output.js b/packages/next-swc/crates/core/tests/fixture/styled-jsx/tpl-placeholder-1-as-property/output.js index 28d29b34b196..9d93281ced13 100644 --- a/packages/next-swc/crates/core/tests/fixture/styled-jsx/tpl-placeholder-1-as-property/output.js +++ b/packages/next-swc/crates/core/tests/fixture/styled-jsx/tpl-placeholder-1-as-property/output.js @@ -21,7 +21,7 @@ export default class { <_JSXStyle id={"e359801ddd3b3cb6"} dynamic={[ inputSize ? "height: calc(2 * var(--a)) !important;" : "" - ]}>{`@media only screen{a.__jsx-style-dynamic-selector{${inputSize ? "height: calc(2 * var(--a)) !important;" : ""} + ]}>{`@media only screen {a.__jsx-style-dynamic-selector{${inputSize ? "height: calc(2 * var(--a)) !important;" : ""} }}`} ; diff --git a/packages/next-swc/crates/core/tests/fixture/styled-jsx/tpl-placeholder-2-as-part-of-value/output.js b/packages/next-swc/crates/core/tests/fixture/styled-jsx/tpl-placeholder-2-as-part-of-value/output.js index 7c0c59d4ac65..bc25a6c5235b 100644 --- a/packages/next-swc/crates/core/tests/fixture/styled-jsx/tpl-placeholder-2-as-part-of-value/output.js +++ b/packages/next-swc/crates/core/tests/fixture/styled-jsx/tpl-placeholder-2-as-part-of-value/output.js @@ -27,7 +27,8 @@ export default class { a[b], -1 * (c || 0), d - ]}>{`.a:hover .b.__jsx-style-dynamic-selector{a:${a[b]}px!important;b:translate3d(0,${-1 * (c || 0)}px,-${d}px)scale(1)!important}`} + ]}>{`.a:hover .b.__jsx-style-dynamic-selector{a:${a[b]}px!important; +b:translate3d(0, ${-1 * (c || 0)}px, -${d}px) scale(1)!important}`} ; } diff --git a/packages/next-swc/crates/core/tests/fixture/styled-jsx/tpl-placeholder-3-as-value/output.js b/packages/next-swc/crates/core/tests/fixture/styled-jsx/tpl-placeholder-3-as-value/output.js index 754e53aa0330..f311c3526881 100644 --- a/packages/next-swc/crates/core/tests/fixture/styled-jsx/tpl-placeholder-3-as-value/output.js +++ b/packages/next-swc/crates/core/tests/fixture/styled-jsx/tpl-placeholder-3-as-value/output.js @@ -21,7 +21,7 @@ export default class { <_JSXStyle id={"4ca4ef3595473f53"} dynamic={[ a - ]}>{`@media only screen{a.__jsx-style-dynamic-selector{color:${a}}}`} + ]}>{`@media only screen {a.__jsx-style-dynamic-selector{color:${a}}}`} ; } diff --git a/packages/next-swc/crates/core/tests/fixture/styled-jsx/tpl-placeholder-4-as-part-of-value-in-multiple/output.js b/packages/next-swc/crates/core/tests/fixture/styled-jsx/tpl-placeholder-4-as-part-of-value-in-multiple/output.js index 8f9e67c31304..4683d8c24306 100644 --- a/packages/next-swc/crates/core/tests/fixture/styled-jsx/tpl-placeholder-4-as-part-of-value-in-multiple/output.js +++ b/packages/next-swc/crates/core/tests/fixture/styled-jsx/tpl-placeholder-4-as-part-of-value-in-multiple/output.js @@ -24,7 +24,9 @@ export default class { <_JSXStyle id={"97886c1e9511aafa"} dynamic={[ a || "var(--c)", b || "inherit" - ]}>{`.a:hover .b.__jsx-style-dynamic-selector{display:inline-block;padding:0 ${a || "var(--c)"};color:${b || "inherit"}}`} + ]}>{`.a:hover .b.__jsx-style-dynamic-selector{display:inline-block; +padding:0 ${a || "var(--c)"}; +color:${b || "inherit"}}`} ; } diff --git a/packages/next-swc/crates/core/tests/fixture/styled-jsx/tpl-placeholder-5-values-of-multiple-properties/output.js b/packages/next-swc/crates/core/tests/fixture/styled-jsx/tpl-placeholder-5-values-of-multiple-properties/output.js index 898bc9e3d60a..e009c13a7456 100644 --- a/packages/next-swc/crates/core/tests/fixture/styled-jsx/tpl-placeholder-5-values-of-multiple-properties/output.js +++ b/packages/next-swc/crates/core/tests/fixture/styled-jsx/tpl-placeholder-5-values-of-multiple-properties/output.js @@ -24,7 +24,8 @@ export default class { <_JSXStyle id={"bcc606c168bcd197"} dynamic={[ a ? "100%" : "200px", b ? "0" : "8px 20px" - ]}>{`.item.__jsx-style-dynamic-selector{max-width:${a ? "100%" : "200px"};padding:${b ? "0" : "8px 20px"}}`} + ]}>{`.item.__jsx-style-dynamic-selector{max-width:${a ? "100%" : "200px"}; +padding:${b ? "0" : "8px 20px"}}`} ; } diff --git a/packages/next-swc/crates/core/tests/fixture/styled-jsx/transform-css-complex-selector/output.js b/packages/next-swc/crates/core/tests/fixture/styled-jsx/transform-css-complex-selector/output.js index 5f2002467dea..a6480c2fb787 100644 --- a/packages/next-swc/crates/core/tests/fixture/styled-jsx/transform-css-complex-selector/output.js +++ b/packages/next-swc/crates/core/tests/fixture/styled-jsx/transform-css-complex-selector/output.js @@ -3,7 +3,7 @@ export default (()=>

test

- <_JSXStyle id={"713499aa363d6373"}>{"p.jsx-713499aa363d6373 a.jsx-713499aa363d6373 span.jsx-713499aa363d6373{color:red}p.jsx-713499aa363d6373 span{background:blue}p.jsx-713499aa363d6373 a[title=\"'w ' ' t'\"].jsx-713499aa363d6373{margin:auto}p.jsx-713499aa363d6373 span:not(.test){color:green}p.jsx-713499aa363d6373,h1.jsx-713499aa363d6373{color:blue;-webkit-animation:hahaha 3s ease forwards infinite;animation:hahaha 3s ease forwards infinite;-webkit-animation-name:hahaha;animation-name:hahaha;animation-delay:100ms}p.jsx-713499aa363d6373{-webkit-animation:hahaha 1s,hehehe 2s;animation:hahaha 1s,hehehe 2s}p.jsx-713499aa363d6373:hover{color:red}p.jsx-713499aa363d6373::before{color:red}.jsx-713499aa363d6373:hover{color:red}.jsx-713499aa363d6373::before{color:red}.jsx-713499aa363d6373:hover p.jsx-713499aa363d6373{color:red}p.jsx-713499aa363d6373+a.jsx-713499aa363d6373{color:red}p.jsx-713499aa363d6373~a.jsx-713499aa363d6373{color:red}p.jsx-713499aa363d6373>a.jsx-713499aa363d6373{color:red}@keyframes hahaha{from{top:0}to{top:100}}@keyframes hehehe{from{left:0}to{left:100}}@media(min-width:500px){.test.jsx-713499aa363d6373{color:red}}.test.jsx-713499aa363d6373{display:block}.inline-flex.jsx-713499aa363d6373{display:-webkit-inline-box;display:-webkit-inline-flex;display:-ms-inline-flexbox;display:inline-flex}.flex.jsx-713499aa363d6373{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex}.test.jsx-713499aa363d6373{box-shadow:0 0 10px black,inset 0 0 5px black}.test[title=\",\"].jsx-713499aa363d6373{display:inline-block}.test.is-status.jsx-713499aa363d6373 .test.jsx-713499aa363d6373{color:red}.a-selector.jsx-713499aa363d6373:hover,.a-selector.jsx-713499aa363d6373:focus{outline:none}"} + <_JSXStyle id={"713499aa363d6373"}>{"p.jsx-713499aa363d6373 a.jsx-713499aa363d6373 span.jsx-713499aa363d6373{color:red}\np.jsx-713499aa363d6373 span{background:blue}\np.jsx-713499aa363d6373 a[title=\"'w ' ' t'\"].jsx-713499aa363d6373{margin:auto}\np.jsx-713499aa363d6373 span:not(.test){color:green}\np.jsx-713499aa363d6373, h1.jsx-713499aa363d6373{color:blue;\n-webkit-animation:hahaha 3s ease forwards infinite;\nanimation:hahaha 3s ease forwards infinite;\n-webkit-animation-name:hahaha;\nanimation-name:hahaha;\nanimation-delay:100ms}\np.jsx-713499aa363d6373{-webkit-animation:hahaha 1s, hehehe 2s;\nanimation:hahaha 1s, hehehe 2s}\np.jsx-713499aa363d6373:hover{color:red}\np.jsx-713499aa363d6373::before{color:red}\n.jsx-713499aa363d6373:hover{color:red}\n.jsx-713499aa363d6373::before{color:red}\n.jsx-713499aa363d6373:hover p.jsx-713499aa363d6373{color:red}\np.jsx-713499aa363d6373+a.jsx-713499aa363d6373{color:red}\np.jsx-713499aa363d6373~a.jsx-713499aa363d6373{color:red}\np.jsx-713499aa363d6373>a.jsx-713499aa363d6373{color:red}\n@keyframes hahaha {from {top:0}to {top:100}}\n@keyframes hehehe {from {left:0}to {left:100}}\n@media (min-width:500px) {.test.jsx-713499aa363d6373{color:red}}\n.test.jsx-713499aa363d6373{display:block}\n.inline-flex.jsx-713499aa363d6373{display:-webkit-inline-box;\ndisplay:-webkit-inline-flex;\ndisplay:-ms-inline-flexbox;\ndisplay:inline-flex}\n.flex.jsx-713499aa363d6373{display:-webkit-box;\ndisplay:-webkit-flex;\ndisplay:-ms-flexbox;\ndisplay:flex}\n.test.jsx-713499aa363d6373{box-shadow:0 0 10px black, inset 0 0 5px black}\n.test[title=\",\"].jsx-713499aa363d6373{display:inline-block}\n.test.is-status.jsx-713499aa363d6373 .test.jsx-713499aa363d6373{color:red}\n.a-selector.jsx-713499aa363d6373:hover, .a-selector.jsx-713499aa363d6373:focus{outline:none}"}
); diff --git a/packages/next-swc/crates/core/tests/fixture/styled-jsx/transform-css-global/output.js b/packages/next-swc/crates/core/tests/fixture/styled-jsx/transform-css-global/output.js index b9405bfa1150..e8ab9d155548 100644 --- a/packages/next-swc/crates/core/tests/fixture/styled-jsx/transform-css-global/output.js +++ b/packages/next-swc/crates/core/tests/fixture/styled-jsx/transform-css-global/output.js @@ -3,7 +3,7 @@ export default (()=>

test

- <_JSXStyle id={"53fd644ab080300c"}>{"html.jsx-53fd644ab080300c{background-image:linear-gradient(0deg,rgba(255,255,255,.8),rgba(255,255,255,.8)),url(/static/background.svg)}p{color:blue}p{color:blue}p,a.jsx-53fd644ab080300c{color:blue}.foo+a{color:red}body{font-family:-apple-system,BlinkMacSystemFont,\"Segoe UI\",Helvetica,Arial,sans-serif}"} + <_JSXStyle id={"53fd644ab080300c"}>{"html.jsx-53fd644ab080300c{background-image:linear-gradient(0deg, rgba(255, 255, 255, 0.8), rgba(255, 255, 255, 0.8)), url(/static/background.svg)}\np{color:blue}\np{color:blue}\np, a.jsx-53fd644ab080300c{color:blue}\n.foo+a{color:red}\nbody{font-family:-apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif}"}
); diff --git a/packages/next-swc/crates/core/tests/fixture/styled-jsx/transform-css-media-query/output.js b/packages/next-swc/crates/core/tests/fixture/styled-jsx/transform-css-media-query/output.js index c5a6b455fb26..abc84ac0f354 100644 --- a/packages/next-swc/crates/core/tests/fixture/styled-jsx/transform-css-media-query/output.js +++ b/packages/next-swc/crates/core/tests/fixture/styled-jsx/transform-css-media-query/output.js @@ -3,7 +3,7 @@ export default (()=>

test

- <_JSXStyle id={"1f7963ae04c6466a"}>{"@media(min-width:1px)and (max-width:768px){[class*=\"grid__col--\"].jsx-1f7963ae04c6466a{margin-top:12px;margin-bottom:12px}}@media(max-width:64em){.test.jsx-1f7963ae04c6466a{margin-bottom:1em}@supports(-moz-appearance:none)and (display:contents){.test.jsx-1f7963ae04c6466a{margin-bottom:2rem}}}"} + <_JSXStyle id={"1f7963ae04c6466a"}>{"@media (min-width:1px) and (max-width:768px) {[class*='grid__col--'].jsx-1f7963ae04c6466a{margin-top:12px;\nmargin-bottom:12px}}\n@media (max-width:64em) {.test.jsx-1f7963ae04c6466a{margin-bottom:1em}\n@supports (-moz-appearance:none) and (display:contents) {.test.jsx-1f7963ae04c6466a{margin-bottom:2rem}}}"}
); diff --git a/packages/next-swc/crates/core/tests/fixture/styled-jsx/transform-css-normal/output.js b/packages/next-swc/crates/core/tests/fixture/styled-jsx/transform-css-normal/output.js index 98a03ea47b4c..9c81fa9facdf 100644 --- a/packages/next-swc/crates/core/tests/fixture/styled-jsx/transform-css-normal/output.js +++ b/packages/next-swc/crates/core/tests/fixture/styled-jsx/transform-css-normal/output.js @@ -3,7 +3,7 @@ export default (()=>

test

- <_JSXStyle id={"1a19bb4817c105dd"}>{"p.jsx-1a19bb4817c105dd{color:red}p.jsx-1a19bb4817c105dd{color:red}*.jsx-1a19bb4817c105dd{color:blue}[href=\"woot\"].jsx-1a19bb4817c105dd{color:red}"} + <_JSXStyle id={"1a19bb4817c105dd"}>{"p.jsx-1a19bb4817c105dd{color:red}\np.jsx-1a19bb4817c105dd{color:red}\n*.jsx-1a19bb4817c105dd{color:blue}\n[href=\"woot\"].jsx-1a19bb4817c105dd{color:red}"}
); diff --git a/packages/next-swc/crates/core/tests/fixture/styled-jsx/transform-css/output.js b/packages/next-swc/crates/core/tests/fixture/styled-jsx/transform-css/output.js index 7a279450b965..ddefde312281 100644 --- a/packages/next-swc/crates/core/tests/fixture/styled-jsx/transform-css/output.js +++ b/packages/next-swc/crates/core/tests/fixture/styled-jsx/transform-css/output.js @@ -3,7 +3,7 @@ export default (()=>

test

- <_JSXStyle id={"768337a97aceabd1"}>{"html.jsx-768337a97aceabd1{background-image:linear-gradient(0deg,rgba(255,255,255,.8),rgba(255,255,255,.8)),url(/static/background.svg)}p{color:blue}p{color:blue}p,a.jsx-768337a97aceabd1{color:blue}.foo+a{color:red}body{font-family:-apple-system,BlinkMacSystemFont,\"Segoe UI\",Helvetica,Arial,sans-serif}p.jsx-768337a97aceabd1{color:red}p.jsx-768337a97aceabd1{color:red}*.jsx-768337a97aceabd1{color:blue}[href=\"woot\"].jsx-768337a97aceabd1{color:red}p.jsx-768337a97aceabd1 a.jsx-768337a97aceabd1 span.jsx-768337a97aceabd1{color:red}p.jsx-768337a97aceabd1 span{background:blue}p.jsx-768337a97aceabd1 a[title=\"'w ' ' t'\"].jsx-768337a97aceabd1{margin:auto}p.jsx-768337a97aceabd1 span:not(.test){color:green}p.jsx-768337a97aceabd1,h1.jsx-768337a97aceabd1{color:blue;-webkit-animation:hahaha 3s ease forwards infinite;animation:hahaha 3s ease forwards infinite;-webkit-animation-name:hahaha;animation-name:hahaha;animation-delay:100ms}p.jsx-768337a97aceabd1{-webkit-animation:hahaha 1s,hehehe 2s;animation:hahaha 1s,hehehe 2s}p.jsx-768337a97aceabd1:hover{color:red}p.jsx-768337a97aceabd1::before{color:red}.jsx-768337a97aceabd1:hover{color:red}.jsx-768337a97aceabd1::before{color:red}.jsx-768337a97aceabd1:hover p.jsx-768337a97aceabd1{color:red}p.jsx-768337a97aceabd1+a.jsx-768337a97aceabd1{color:red}p.jsx-768337a97aceabd1~a.jsx-768337a97aceabd1{color:red}p.jsx-768337a97aceabd1>a.jsx-768337a97aceabd1{color:red}@keyframes hahaha{from{top:0}to{top:100}}@keyframes hehehe{from{left:0}to{left:100}}@media(min-width:500px){.test.jsx-768337a97aceabd1{color:red}}.test.jsx-768337a97aceabd1{display:block}.inline-flex.jsx-768337a97aceabd1{display:-webkit-inline-box;display:-webkit-inline-flex;display:-ms-inline-flexbox;display:inline-flex}.flex.jsx-768337a97aceabd1{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex}.test.jsx-768337a97aceabd1{box-shadow:0 0 10px black,inset 0 0 5px black}.test[title=\",\"].jsx-768337a97aceabd1{display:inline-block}.test.is-status.jsx-768337a97aceabd1 .test.jsx-768337a97aceabd1{color:red}.a-selector.jsx-768337a97aceabd1:hover,.a-selector.jsx-768337a97aceabd1:focus{outline:none}@media(min-width:1px)and (max-width:768px){[class*=\"grid__col--\"].jsx-768337a97aceabd1{margin-top:12px;margin-bottom:12px}}@media(max-width:64em){.test.jsx-768337a97aceabd1{margin-bottom:1em}@supports(-moz-appearance:none)and (display:contents){.test.jsx-768337a97aceabd1{margin-bottom:2rem}}}"} + <_JSXStyle id={"768337a97aceabd1"}>{"html.jsx-768337a97aceabd1{background-image:linear-gradient(0deg, rgba(255, 255, 255, 0.8), rgba(255, 255, 255, 0.8)), url(/static/background.svg)}\np{color:blue}\np{color:blue}\np, a.jsx-768337a97aceabd1{color:blue}\n.foo+a{color:red}\nbody{font-family:-apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif}\np.jsx-768337a97aceabd1{color:red}\np.jsx-768337a97aceabd1{color:red}\n*.jsx-768337a97aceabd1{color:blue}\n[href=\"woot\"].jsx-768337a97aceabd1{color:red}\np.jsx-768337a97aceabd1 a.jsx-768337a97aceabd1 span.jsx-768337a97aceabd1{color:red}\np.jsx-768337a97aceabd1 span{background:blue}\np.jsx-768337a97aceabd1 a[title=\"'w ' ' t'\"].jsx-768337a97aceabd1{margin:auto}\np.jsx-768337a97aceabd1 span:not(.test){color:green}\np.jsx-768337a97aceabd1, h1.jsx-768337a97aceabd1{color:blue;\n-webkit-animation:hahaha 3s ease forwards infinite;\nanimation:hahaha 3s ease forwards infinite;\n-webkit-animation-name:hahaha;\nanimation-name:hahaha;\nanimation-delay:100ms}\np.jsx-768337a97aceabd1{-webkit-animation:hahaha 1s, hehehe 2s;\nanimation:hahaha 1s, hehehe 2s}\np.jsx-768337a97aceabd1:hover{color:red}\np.jsx-768337a97aceabd1::before{color:red}\n.jsx-768337a97aceabd1:hover{color:red}\n.jsx-768337a97aceabd1::before{color:red}\n.jsx-768337a97aceabd1:hover p.jsx-768337a97aceabd1{color:red}\np.jsx-768337a97aceabd1+a.jsx-768337a97aceabd1{color:red}\np.jsx-768337a97aceabd1~a.jsx-768337a97aceabd1{color:red}\np.jsx-768337a97aceabd1>a.jsx-768337a97aceabd1{color:red}\n@keyframes hahaha {from {top:0}to {top:100}}\n@keyframes hehehe {from {left:0}to {left:100}}\n@media (min-width:500px) {.test.jsx-768337a97aceabd1{color:red}}\n.test.jsx-768337a97aceabd1{display:block}\n.inline-flex.jsx-768337a97aceabd1{display:-webkit-inline-box;\ndisplay:-webkit-inline-flex;\ndisplay:-ms-inline-flexbox;\ndisplay:inline-flex}\n.flex.jsx-768337a97aceabd1{display:-webkit-box;\ndisplay:-webkit-flex;\ndisplay:-ms-flexbox;\ndisplay:flex}\n.test.jsx-768337a97aceabd1{box-shadow:0 0 10px black, inset 0 0 5px black}\n.test[title=\",\"].jsx-768337a97aceabd1{display:inline-block}\n.test.is-status.jsx-768337a97aceabd1 .test.jsx-768337a97aceabd1{color:red}\n.a-selector.jsx-768337a97aceabd1:hover, .a-selector.jsx-768337a97aceabd1:focus{outline:none}\n@media (min-width:1px) and (max-width:768px) {[class*='grid__col--'].jsx-768337a97aceabd1{margin-top:12px;\nmargin-bottom:12px}}\n@media (max-width:64em) {.test.jsx-768337a97aceabd1{margin-bottom:1em}\n@supports (-moz-appearance:none) and (display:contents) {.test.jsx-768337a97aceabd1{margin-bottom:2rem}}}"}
); diff --git a/packages/next-swc/crates/core/tests/full/example/output.js b/packages/next-swc/crates/core/tests/full/example/output.js index e0a9c88dd80f..ebba00c28d0b 100644 --- a/packages/next-swc/crates/core/tests/full/example/output.js +++ b/packages/next-swc/crates/core/tests/full/example/output.js @@ -1,9 +1,9 @@ -function a(a, b) { +import a from "other"; +function b(a, b) { (null == b || b > a.length) && (b = a.length); for(var c = 0, d = new Array(b); c < b; c++)d[c] = a[c]; return d; } -import b from "other"; (function(a, c) { return (function(a) { if (Array.isArray(a)) return a; @@ -24,17 +24,17 @@ import b from "other"; } return g; } - })(a, c) || (function(b, c) { - if (b) { - if ("string" == typeof b) return a(b, c); - var d = Object.prototype.toString.call(b).slice(8, -1); - if ("Object" === d && b.constructor && (d = b.constructor.name), "Map" === d || "Set" === d) return Array.from(d); - if ("Arguments" === d || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(d)) return a(b, c); + })(a, c) || (function(a, c) { + if (a) { + if ("string" == typeof a) return b(a, c); + var d = Object.prototype.toString.call(a).slice(8, -1); + if ("Object" === d && a.constructor && (d = a.constructor.name), "Map" === d || "Set" === d) return Array.from(d); + if ("Arguments" === d || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(d)) return b(a, c); } })(a, c) || (function() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); })(); -})(b, 1)[0]; +})(a, 1)[0]; var c = function() { "use strict"; !function(a, b) { diff --git a/packages/next-swc/crates/core/tests/loader/css-hygiene-1/output.js b/packages/next-swc/crates/core/tests/loader/css-hygiene-1/output.js index cd1517cec174..ad93e37fb91f 100644 --- a/packages/next-swc/crates/core/tests/loader/css-hygiene-1/output.js +++ b/packages/next-swc/crates/core/tests/loader/css-hygiene-1/output.js @@ -1,3 +1,3 @@ -var _defaultExport = new String("@media(max-width:870px){th.expiration-date-cell,td.expiration-date-cell{display:none}}"); +var _defaultExport = new String("@media (max-width:870px) {th.expiration-date-cell, td.expiration-date-cell{display:none}}"); _defaultExport.__hash = "fd71bf06ba8860bb"; export default _defaultExport; diff --git a/packages/next-swc/crates/core/tests/loader/example/output.js b/packages/next-swc/crates/core/tests/loader/example/output.js index 1bb38a8e44b6..7bbcd606d5af 100644 --- a/packages/next-swc/crates/core/tests/loader/example/output.js +++ b/packages/next-swc/crates/core/tests/loader/example/output.js @@ -1,3 +1,4 @@ +import other from 'other'; function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for(var i = 0, arr2 = new Array(len); i < len; i++)arr2[i] = arr[i]; @@ -49,7 +50,6 @@ function _unsupportedIterableToArray(o, minLen) { if (n === "Map" || n === "Set") return Array.from(n); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); } -import other from 'other'; var _other = _slicedToArray(other, 1), foo = _other[0]; var Foo = function Foo() { "use strict"; diff --git a/packages/next-swc/crates/core/tests/loader/issue-31627/output.js b/packages/next-swc/crates/core/tests/loader/issue-31627/output.js index 1bd450ee9faf..abdbe09ac240 100644 --- a/packages/next-swc/crates/core/tests/loader/issue-31627/output.js +++ b/packages/next-swc/crates/core/tests/loader/issue-31627/output.js @@ -1,10 +1,10 @@ +import { useEffect } from 'react'; +import { select, selectAll } from 'd3-selection'; function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } -import { useEffect } from 'react'; -import { select, selectAll } from 'd3-selection'; export default function Home() { useEffect(function() { new MyClass(); diff --git a/packages/next-swc/crates/core/tests/loader/styled-components/1/output.js b/packages/next-swc/crates/core/tests/loader/styled-components/1/output.js index 8e100ad666c0..70e1bb4afc6b 100644 --- a/packages/next-swc/crates/core/tests/loader/styled-components/1/output.js +++ b/packages/next-swc/crates/core/tests/loader/styled-components/1/output.js @@ -1,3 +1,4 @@ +import styled from 'styled-components'; function _taggedTemplateLiteral(strings, raw) { if (!raw) { raw = strings.slice(0); @@ -17,7 +18,6 @@ function _templateObject() { }; return data; } -import styled from 'styled-components'; export var foo = styled.input.withConfig({ displayName: "input__foo", componentId: "sc-12c52e68-0" diff --git a/packages/next-swc/crates/napi/Cargo.toml b/packages/next-swc/crates/napi/Cargo.toml index 493424eb23bf..f7d20e9a6405 100644 --- a/packages/next-swc/crates/napi/Cargo.toml +++ b/packages/next-swc/crates/napi/Cargo.toml @@ -16,12 +16,12 @@ once_cell = "1.8.0" serde = "1" serde_json = "1" next-swc = { version = "0.0.0", path = "../core" } -swc = "0.126.2" +swc = "0.121.7" swc_atoms = "0.2.7" -swc_bundler = { version = "0.107.0", features = ["concurrent"] } +swc_bundler = { version = "0.105.0", features = ["concurrent"] } swc_common = { version = "0.17.0", features = ["concurrent", "sourcemap"] } swc_ecma_loader = { version = "0.28.0", features = ["node", "lru"] } -swc_ecmascript = { version = "0.114.2", features = ["codegen", "minifier", "optimization", "parser", "react", "transforms", "typescript", "utils", "visit"] } +swc_ecmascript = { version = "0.112.6", features = ["codegen", "minifier", "optimization", "parser", "react", "transforms", "typescript", "utils", "visit"] } swc_node_base = "0.5.1" [build-dependencies] diff --git a/packages/next-swc/crates/wasm/Cargo.toml b/packages/next-swc/crates/wasm/Cargo.toml index b25e21bcbe0e..c84005528842 100644 --- a/packages/next-swc/crates/wasm/Cargo.toml +++ b/packages/next-swc/crates/wasm/Cargo.toml @@ -16,9 +16,9 @@ path-clean = "0.1" serde = {version = "1", features = ["derive"]} serde_json = "1" next-swc = { version = "0.0.0", path = "../core" } -swc = "0.126.2" +swc = "0.121.7" swc_common = { version = "0.17.0", features = ["concurrent", "sourcemap"] } -swc_ecmascript = { version = "0.114.2", features = ["codegen", "minifier", "optimization", "parser", "react", "transforms", "typescript", "utils", "visit"] } +swc_ecmascript = { version = "0.112.6", features = ["codegen", "minifier", "optimization", "parser", "react", "transforms", "typescript", "utils", "visit"] } tracing = {version = "0.1.28", features = ["release_max_level_off"]} wasm-bindgen = {version = "0.2", features = ["serde-serialize"]} wasm-bindgen-futures = "0.4.8" From 732b4052bda5d10b42ceaa87ba0067f74075971a Mon Sep 17 00:00:00 2001 From: JJ Kasper Date: Wed, 16 Feb 2022 14:36:59 -0600 Subject: [PATCH 06/28] v12.0.11-canary.19 --- lerna.json | 2 +- packages/create-next-app/package.json | 2 +- packages/eslint-config-next/package.json | 4 ++-- packages/eslint-plugin-next/package.json | 2 +- packages/next-bundle-analyzer/package.json | 2 +- packages/next-codemod/package.json | 2 +- packages/next-env/package.json | 2 +- packages/next-mdx/package.json | 2 +- packages/next-plugin-storybook/package.json | 2 +- packages/next-polyfill-module/package.json | 2 +- packages/next-polyfill-nomodule/package.json | 2 +- packages/next-swc/package.json | 2 +- packages/next/package.json | 14 +++++++------- packages/react-dev-overlay/package.json | 2 +- packages/react-refresh-utils/package.json | 2 +- 15 files changed, 22 insertions(+), 22 deletions(-) diff --git a/lerna.json b/lerna.json index a517697c141b..264e579f1ceb 100644 --- a/lerna.json +++ b/lerna.json @@ -16,5 +16,5 @@ "registry": "https://registry.npmjs.org/" } }, - "version": "12.0.11-canary.18" + "version": "12.0.11-canary.19" } diff --git a/packages/create-next-app/package.json b/packages/create-next-app/package.json index bfaf28f519db..733a006f64d4 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.0.11-canary.18", + "version": "12.0.11-canary.19", "keywords": [ "react", "next", diff --git a/packages/eslint-config-next/package.json b/packages/eslint-config-next/package.json index 08d215f8ab7f..a205a69d1dbb 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.0.11-canary.18", + "version": "12.0.11-canary.19", "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.0.11-canary.18", + "@next/eslint-plugin-next": "12.0.11-canary.19", "@rushstack/eslint-patch": "^1.0.8", "@typescript-eslint/parser": "^5.0.0", "eslint-import-resolver-node": "^0.3.4", diff --git a/packages/eslint-plugin-next/package.json b/packages/eslint-plugin-next/package.json index f776c2485cef..ffffceed908f 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.0.11-canary.18", + "version": "12.0.11-canary.19", "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 25e64cab7382..a9b47cce2357 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.0.11-canary.18", + "version": "12.0.11-canary.19", "main": "index.js", "license": "MIT", "repository": { diff --git a/packages/next-codemod/package.json b/packages/next-codemod/package.json index 5f95a9835b6e..69f60ffbc735 100644 --- a/packages/next-codemod/package.json +++ b/packages/next-codemod/package.json @@ -1,6 +1,6 @@ { "name": "@next/codemod", - "version": "12.0.11-canary.18", + "version": "12.0.11-canary.19", "license": "MIT", "dependencies": { "chalk": "4.1.0", diff --git a/packages/next-env/package.json b/packages/next-env/package.json index 3a9151ee3418..668b3b55ca10 100644 --- a/packages/next-env/package.json +++ b/packages/next-env/package.json @@ -1,6 +1,6 @@ { "name": "@next/env", - "version": "12.0.11-canary.18", + "version": "12.0.11-canary.19", "keywords": [ "react", "next", diff --git a/packages/next-mdx/package.json b/packages/next-mdx/package.json index 1600d8aedb0f..c212d22d2285 100644 --- a/packages/next-mdx/package.json +++ b/packages/next-mdx/package.json @@ -1,6 +1,6 @@ { "name": "@next/mdx", - "version": "12.0.11-canary.18", + "version": "12.0.11-canary.19", "main": "index.js", "license": "MIT", "repository": { diff --git a/packages/next-plugin-storybook/package.json b/packages/next-plugin-storybook/package.json index 749b71487b03..e453865d80f5 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.0.11-canary.18", + "version": "12.0.11-canary.19", "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 6a204da8d44d..347d0c5c2b9e 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.0.11-canary.18", + "version": "12.0.11-canary.19", "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 549509485d95..560516330119 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.0.11-canary.18", + "version": "12.0.11-canary.19", "description": "A polyfill for non-dead, nomodule browsers.", "main": "dist/polyfill-nomodule.js", "license": "MIT", diff --git a/packages/next-swc/package.json b/packages/next-swc/package.json index 98b823888fac..833ddaa02637 100644 --- a/packages/next-swc/package.json +++ b/packages/next-swc/package.json @@ -1,6 +1,6 @@ { "name": "@next/swc", - "version": "12.0.11-canary.18", + "version": "12.0.11-canary.19", "private": true, "scripts": { "build-native": "napi build --platform --cargo-name next_swc_napi native", diff --git a/packages/next/package.json b/packages/next/package.json index f36aff156375..a1229df5bac0 100644 --- a/packages/next/package.json +++ b/packages/next/package.json @@ -1,6 +1,6 @@ { "name": "next", - "version": "12.0.11-canary.18", + "version": "12.0.11-canary.19", "description": "The React Framework", "main": "./dist/server/next.js", "license": "MIT", @@ -69,7 +69,7 @@ ] }, "dependencies": { - "@next/env": "12.0.11-canary.18", + "@next/env": "12.0.11-canary.19", "caniuse-lite": "^1.0.30001283", "postcss": "8.4.5", "styled-jsx": "5.0.0", @@ -117,11 +117,11 @@ "@hapi/accept": "5.0.2", "@napi-rs/cli": "1.2.1", "@napi-rs/triples": "1.0.3", - "@next/polyfill-module": "12.0.11-canary.18", - "@next/polyfill-nomodule": "12.0.11-canary.18", - "@next/react-dev-overlay": "12.0.11-canary.18", - "@next/react-refresh-utils": "12.0.11-canary.18", - "@next/swc": "12.0.11-canary.18", + "@next/polyfill-module": "12.0.11-canary.19", + "@next/polyfill-nomodule": "12.0.11-canary.19", + "@next/react-dev-overlay": "12.0.11-canary.19", + "@next/react-refresh-utils": "12.0.11-canary.19", + "@next/swc": "12.0.11-canary.19", "@peculiar/webcrypto": "1.1.7", "@taskr/clear": "1.1.0", "@taskr/esnext": "1.1.0", diff --git a/packages/react-dev-overlay/package.json b/packages/react-dev-overlay/package.json index a81dcc722a69..df902443fcd6 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.0.11-canary.18", + "version": "12.0.11-canary.19", "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 12cb97223853..dbeed5ed378c 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.0.11-canary.18", + "version": "12.0.11-canary.19", "description": "An experimental package providing utilities for React Refresh.", "repository": { "url": "vercel/next.js", From 86aac3fa3d06beb8c339656cc7d13987607937ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bal=C3=A1zs=20Orb=C3=A1n?= Date: Wed, 16 Feb 2022 21:49:31 +0100 Subject: [PATCH 07/28] Update 1.bug_report.yml --- .github/ISSUE_TEMPLATE/1.bug_report.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/1.bug_report.yml b/.github/ISSUE_TEMPLATE/1.bug_report.yml index 623e939c481f..dbce6318ee4a 100644 --- a/.github/ISSUE_TEMPLATE/1.bug_report.yml +++ b/.github/ISSUE_TEMPLATE/1.bug_report.yml @@ -20,7 +20,7 @@ body: label: Provide environment information description: Please run `next info` in the root directory of your project and paste the results. You might need to use `npx --no-install next info` if next is not in the current PATH. validations: - required: false + required: true - type: input attributes: label: What browser are you using? (if relevant) From 9b38ffe5d9d88a0c8e8837c022dd7203bed6da7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bal=C3=A1zs=20Orb=C3=A1n?= Date: Wed, 16 Feb 2022 21:49:54 +0100 Subject: [PATCH 08/28] Update 2.example_bug_report.yml --- .github/ISSUE_TEMPLATE/2.example_bug_report.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/2.example_bug_report.yml b/.github/ISSUE_TEMPLATE/2.example_bug_report.yml index 361d68636214..04483f5a4518 100644 --- a/.github/ISSUE_TEMPLATE/2.example_bug_report.yml +++ b/.github/ISSUE_TEMPLATE/2.example_bug_report.yml @@ -20,7 +20,7 @@ body: label: Provide environment information description: Please run `next info` in the root directory of your project and paste the results. You might need to use `npx --no-install next info` if next is not in the current PATH. validations: - required: false + required: true - type: input attributes: label: Which example does this report relate to? From 54dbeb30c158d263c021e206fefc984035f8a208 Mon Sep 17 00:00:00 2001 From: Tobias Koppers Date: Wed, 16 Feb 2022 22:59:13 +0100 Subject: [PATCH 09/28] update webpack (#34444) https://github.com/webpack/webpack/releases/tag/v5.69.0 --- packages/next/compiled/webpack/bundle5.js | 1009 ++++++++++++++------- packages/next/package.json | 2 +- yarn.lock | 38 +- 3 files changed, 712 insertions(+), 337 deletions(-) diff --git a/packages/next/compiled/webpack/bundle5.js b/packages/next/compiled/webpack/bundle5.js index 093c9ba888ef..c8689dde9bdc 100644 --- a/packages/next/compiled/webpack/bundle5.js +++ b/packages/next/compiled/webpack/bundle5.js @@ -19047,6 +19047,7 @@ const compareModuleIterables = compareIterables(compareModulesByIdentifier); /** @typedef {(c: Chunk, chunkGraph: ChunkGraph) => boolean} ChunkFilterPredicate */ /** @typedef {(m: Module) => boolean} ModuleFilterPredicate */ +/** @typedef {[Module, Entrypoint | undefined]} EntryModuleWithChunkGroup */ /** * @typedef {Object} ChunkSizeOptions @@ -20181,8 +20182,6 @@ class ChunkGraph { return cgc.dependentHashModules; } - /** @typedef {[Module, Entrypoint | undefined]} EntryModuleWithChunkGroup */ - /** * @param {Chunk} chunk the chunk * @returns {Iterable} iterable of modules (do not modify) @@ -22479,6 +22478,7 @@ const { isSourceEqual } = __webpack_require__(41245); /** * @typedef {Object} ChunkHashContext + * @property {CodeGenerationResults} codeGenerationResults results of code generation * @property {RuntimeTemplate} runtimeTemplate the runtime template * @property {ModuleGraph} moduleGraph the module graph * @property {ChunkGraph} chunkGraph the chunk graph @@ -26434,6 +26434,7 @@ This prevents using hashes of each other and should be avoided.`); chunk.updateHash(chunkHash, chunkGraph); this.hooks.chunkHash.call(chunk, chunkHash, { chunkGraph, + codeGenerationResults: this.codeGenerationResults, moduleGraph: this.moduleGraph, runtimeTemplate: this.runtimeTemplate }); @@ -27267,7 +27268,7 @@ This prevents using hashes of each other and should be avoided.`); strictModuleErrorHandling, strictModuleExceptionHandling } = this.outputOptions; - const __nested_webpack_require_152290__ = id => { + const __nested_webpack_require_152432__ = id => { const cached = moduleCache[id]; if (cached !== undefined) { if (cached.error) throw cached.error; @@ -27276,20 +27277,20 @@ This prevents using hashes of each other and should be avoided.`); const moduleArgument = moduleArgumentsById.get(id); return __webpack_require_module__(moduleArgument, id); }; - const interceptModuleExecution = (__nested_webpack_require_152290__[ + const interceptModuleExecution = (__nested_webpack_require_152432__[ RuntimeGlobals.interceptModuleExecution.replace( "__webpack_require__.", "" ) ] = []); - const moduleCache = (__nested_webpack_require_152290__[ + const moduleCache = (__nested_webpack_require_152432__[ RuntimeGlobals.moduleCache.replace( "__webpack_require__.", "" ) ] = {}); - context.__webpack_require__ = __nested_webpack_require_152290__; + context.__webpack_require__ = __nested_webpack_require_152432__; /** * @param {ExecuteModuleArgument} moduleArgument the module argument @@ -27305,7 +27306,7 @@ This prevents using hashes of each other and should be avoided.`); loaded: false, error: undefined }, - require: __nested_webpack_require_152290__ + require: __nested_webpack_require_152432__ }; interceptModuleExecution.forEach(handler => handler(execOptions) @@ -27345,7 +27346,7 @@ This prevents using hashes of each other and should be avoided.`); moduleArgumentsMap.get(runtimeModule) ); } - exports = __nested_webpack_require_152290__(module.identifier()); + exports = __nested_webpack_require_152432__(module.identifier()); } catch (e) { const err = new WebpackError( `Execution of module code from module graph (${module.readableIdentifier( @@ -29715,7 +29716,7 @@ const makeSerializable = __webpack_require__(33032); /** * @typedef {Object} ContextModuleOptionsExtras - * @property {string} resource + * @property {string|string[]} resource * @property {string=} resourceQuery * @property {string=} resourceFragment * @property {TODO} resolveOptions @@ -29746,23 +29747,36 @@ class ContextModule extends Module { * @param {ContextModuleOptions} options options object */ constructor(resolveDependencies, options) { - const parsed = parseResource(options ? options.resource : ""); - const resource = parsed.path; - const resourceQuery = (options && options.resourceQuery) || parsed.query; - const resourceFragment = - (options && options.resourceFragment) || parsed.fragment; - - super("javascript/dynamic", resource); + if (!options || typeof options.resource === "string") { + const parsed = parseResource( + options ? /** @type {string} */ (options.resource) : "" + ); + const resource = parsed.path; + const resourceQuery = (options && options.resourceQuery) || parsed.query; + const resourceFragment = + (options && options.resourceFragment) || parsed.fragment; + + super("javascript/dynamic", resource); + /** @type {ContextModuleOptions} */ + this.options = { + ...options, + resource, + resourceQuery, + resourceFragment + }; + } else { + super("javascript/dynamic"); + /** @type {ContextModuleOptions} */ + this.options = { + ...options, + resource: options.resource, + resourceQuery: options.resourceQuery || "", + resourceFragment: options.resourceFragment || "" + }; + } // Info from Factory this.resolveDependencies = resolveDependencies; - /** @type {ContextModuleOptions} */ - this.options = { - ...options, - resource, - resourceQuery, - resourceFragment - }; if (options && options.resolveOptions !== undefined) { this.resolveOptions = options.resolveOptions; } @@ -29809,7 +29823,11 @@ class ContextModule extends Module { } _createIdentifier() { - let identifier = this.context; + let identifier = + this.context || + (typeof this.options.resource === "string" + ? this.options.resource + : this.options.resource.join("|")); if (this.options.resourceQuery) { identifier += `|${this.options.resourceQuery}`; } @@ -29874,7 +29892,16 @@ class ContextModule extends Module { * @returns {string} a user readable identifier of the module */ readableIdentifier(requestShortener) { - let identifier = requestShortener.shorten(this.context) + "/"; + let identifier; + if (this.context) { + identifier = requestShortener.shorten(this.context) + "/"; + } else if (typeof this.options.resource === "string") { + identifier = requestShortener.shorten(this.options.resource) + "/"; + } else { + identifier = this.options.resource + .map(r => requestShortener.shorten(r) + "/") + .join(" "); + } if (this.options.resourceQuery) { identifier += ` ${this.options.resourceQuery}`; } @@ -29924,11 +29951,30 @@ class ContextModule extends Module { * @returns {string | null} an identifier for library inclusion */ libIdent(options) { - let identifier = contextify( - options.context, - this.context, - options.associatedObjectForCache - ); + let identifier; + + if (this.context) { + identifier = contextify( + options.context, + this.context, + options.associatedObjectForCache + ); + } else if (typeof this.options.resource === "string") { + identifier = contextify( + options.context, + this.options.resource, + options.associatedObjectForCache + ); + } else { + const arr = []; + for (const res of this.options.resource) { + arr.push( + contextify(options.context, res, options.associatedObjectForCache) + ); + } + identifier = arr.join(" "); + } + if (this.layer) identifier = `(${this.layer})/${identifier}`; if (this.options.mode) { identifier += ` ${this.options.mode}`; @@ -30096,7 +30142,11 @@ class ContextModule extends Module { compilation.fileSystemInfo.createSnapshot( startTime, null, - [this.context], + this.context + ? [this.context] + : typeof this.options.resource === "string" + ? [this.options.resource] + : this.options.resource, null, SNAPSHOT_OPTIONS, (err, snapshot) => { @@ -30120,7 +30170,13 @@ class ContextModule extends Module { missingDependencies, buildDependencies ) { - contextDependencies.add(this.context); + if (this.context) { + contextDependencies.add(this.context); + } else if (typeof this.options.resource === "string") { + contextDependencies.add(this.options.resource); + } else { + for (const res of this.options.resource) contextDependencies.add(res); + } } /** @@ -30912,6 +30968,9 @@ module.exports = class ContextModuleFactory extends ModuleFactory { asyncLib.parallel( [ callback => { + const results = []; + const yield_ = obj => results.push(obj); + contextResolver.resolve( {}, context, @@ -30919,11 +30978,12 @@ module.exports = class ContextModuleFactory extends ModuleFactory { { fileDependencies, missingDependencies, - contextDependencies + contextDependencies, + yield: yield_ }, - (err, result) => { + err => { if (err) return callback(err); - callback(null, result); + callback(null, results); } ); }, @@ -30958,15 +31018,20 @@ module.exports = class ContextModuleFactory extends ModuleFactory { contextDependencies }); } - + const [contextResult, loaderResult] = result; this.hooks.afterResolve.callAsync( { addon: loadersPrefix + - result[1].join("!") + - (result[1].length > 0 ? "!" : ""), - resource: result[0], + loaderResult.join("!") + + (loaderResult.length > 0 ? "!" : ""), + resource: + contextResult.length > 1 + ? contextResult.map(r => r.path) + : contextResult[0].path, resolveDependencies: this.resolveDependencies.bind(this), + resourceQuery: contextResult[0].query, + resourceFragment: contextResult[0].fragment, ...beforeResolveResult }, (err, result) => { @@ -31023,26 +31088,28 @@ module.exports = class ContextModuleFactory extends ModuleFactory { } = options; if (!regExp || !resource) return callback(null, []); - const addDirectoryChecked = (directory, visited, callback) => { + let severalContexts = false; + const addDirectoryChecked = (ctx, directory, visited, callback) => { fs.realpath(directory, (err, realPath) => { if (err) return callback(err); if (visited.has(realPath)) return callback(null, []); let recursionStack; addDirectory( + ctx, directory, - (dir, callback) => { + (_, dir, callback) => { if (recursionStack === undefined) { recursionStack = new Set(visited); recursionStack.add(realPath); } - addDirectoryChecked(dir, recursionStack, callback); + addDirectoryChecked(ctx, dir, recursionStack, callback); }, callback ); }); }; - const addDirectory = (directory, addSubDirectory, callback) => { + const addDirectory = (ctx, directory, addSubDirectory, callback) => { fs.readdir(directory, (err, files) => { if (err) return callback(err); const processedFiles = cmf.hooks.contextModuleFiles.call( @@ -31069,16 +31136,15 @@ module.exports = class ContextModuleFactory extends ModuleFactory { if (stat.isDirectory()) { if (!recursive) return callback(); - addSubDirectory(subResource, callback); + addSubDirectory(ctx, subResource, callback); } else if ( stat.isFile() && (!include || subResource.match(include)) ) { const obj = { - context: resource, + context: ctx, request: - "." + - subResource.substr(resource.length).replace(/\\/g, "/") + "." + subResource.substr(ctx.length).replace(/\\/g, "/") }; this.hooks.alternativeRequests.callAsync( @@ -31089,8 +31155,11 @@ module.exports = class ContextModuleFactory extends ModuleFactory { alternatives = alternatives .filter(obj => regExp.test(obj.request)) .map(obj => { + const request = severalContexts + ? join(fs, obj.context, obj.request) + : obj.request; const dep = new ContextElementDependency( - obj.request + resourceQuery + resourceFragment, + request + resourceQuery + resourceFragment, obj.request, typePrefix, category, @@ -31127,12 +31196,38 @@ module.exports = class ContextModuleFactory extends ModuleFactory { }); }; - if (typeof fs.realpath === "function") { - addDirectoryChecked(resource, new Set(), callback); + const addSubDirectory = (ctx, dir, callback) => + addDirectory(ctx, dir, addSubDirectory, callback); + + const visitResource = (resource, callback) => { + if (typeof fs.realpath === "function") { + addDirectoryChecked(resource, resource, new Set(), callback); + } else { + addDirectory(resource, resource, addSubDirectory, callback); + } + }; + + if (typeof resource === "string") { + visitResource(resource, callback); } else { - const addSubDirectory = (dir, callback) => - addDirectory(dir, addSubDirectory, callback); - addDirectory(resource, addSubDirectory, callback); + severalContexts = true; + asyncLib.map(resource, visitResource, (err, result) => { + if (err) return callback(err); + + // result dependencies should have unique userRequest + // ordered by resolve result + const temp = new Set(); + const res = []; + for (let i = 0; i < result.length; i++) { + const inner = result[i]; + for (const el of inner) { + if (temp.has(el.userRequest)) continue; + res.push(el); + temp.add(el.userRequest); + } + } + callback(null, res); + }); } } }; @@ -34588,15 +34683,15 @@ class ExportsInfo { } } for (const exportInfo of this._exports.values()) { + if (!canMangle && exportInfo.canMangleProvide !== false) { + exportInfo.canMangleProvide = false; + changed = true; + } if (excludeExports && excludeExports.has(exportInfo.name)) continue; if (exportInfo.provided !== true && exportInfo.provided !== null) { exportInfo.provided = null; changed = true; } - if (!canMangle && exportInfo.canMangleProvide !== false) { - exportInfo.canMangleProvide = false; - changed = true; - } if (targetKey) { exportInfo.setTarget(targetKey, targetModule, [exportInfo.name], -1); } @@ -49902,7 +49997,10 @@ const LazySet = __webpack_require__(38938); const { getScheme } = __webpack_require__(54500); const { cachedCleverMerge, cachedSetProperty } = __webpack_require__(60839); const { join } = __webpack_require__(17139); -const { parseResource } = __webpack_require__(82186); +const { + parseResource, + parseResourceWithoutFragment +} = __webpack_require__(82186); /** @typedef {import("../declarations/WebpackOptions").ModuleOptionsNormalized} ModuleOptions */ /** @typedef {import("./Generator")} Generator */ @@ -49940,6 +50038,11 @@ const { parseResource } = __webpack_require__(82186); /** @typedef {ResourceData & { data: Record }} ResourceDataWithData */ +/** @typedef {Object} ParsedLoaderRequest + * @property {string} loader loader + * @property {string|undefined} options options + */ + const EMPTY_RESOLVE_OPTIONS = {}; const EMPTY_PARSER_OPTIONS = {}; const EMPTY_GENERATOR_OPTIONS = {}; @@ -49971,27 +50074,6 @@ const stringifyLoadersAndResource = (loaders, resource) => { return str + resource; }; -/** - * @param {string} resultString resultString - * @returns {{loader: string, options: string|undefined}} parsed loader request - */ -const identToLoaderRequest = resultString => { - const idx = resultString.indexOf("?"); - if (idx >= 0) { - const loader = resultString.substr(0, idx); - const options = resultString.substr(idx + 1); - return { - loader, - options - }; - } else { - return { - loader: resultString, - options: undefined - }; - } -}; - const needCalls = (times, callback) => { return err => { if (--times === 0) { @@ -50138,6 +50220,9 @@ class NormalModuleFactory extends ModuleFactory { const cacheParseResource = parseResource.bindCache( associatedObjectForCache ); + const cachedParseResourceWithoutFragment = + parseResourceWithoutFragment.bindCache(associatedObjectForCache); + this._parseResourceWithoutFragment = cachedParseResourceWithoutFragment; this.hooks.factorize.tapAsync( { @@ -50225,7 +50310,7 @@ class NormalModuleFactory extends ModuleFactory { let matchResourceData = undefined; /** @type {string} */ let unresolvedResource; - /** @type {{loader: string, options: string|undefined}[]} */ + /** @type {ParsedLoaderRequest[]} */ let elements; let noPreAutoLoaders = false; let noAutoLoaders = false; @@ -50279,7 +50364,13 @@ class NormalModuleFactory extends ModuleFactory { ) .split(/!+/); unresolvedResource = rawElements.pop(); - elements = rawElements.map(identToLoaderRequest); + elements = rawElements.map(el => { + const { path, query } = cachedParseResourceWithoutFragment(el); + return { + loader: path, + options: query ? query.slice(1) : undefined + }; + }); scheme = getScheme(unresolvedResource); } else { unresolvedResource = requestWithoutMatchResource; @@ -50891,12 +50982,14 @@ If changing the source code is not an option there is also a resolve options cal } if (err) return callback(err); - const parsedResult = identToLoaderRequest(result); + const parsedResult = this._parseResourceWithoutFragment(result); const resolved = { - loader: parsedResult.loader, + loader: parsedResult.path, options: item.options === undefined - ? parsedResult.options + ? parsedResult.query + ? parsedResult.query.slice(1) + : undefined : item.options, ident: item.options === undefined ? undefined : item.ident }; @@ -51311,7 +51404,7 @@ const createDefaultHandler = (profile, logger) => { /** * @callback ReportProgress * @param {number} p - * @param {...string[]} [args] + * @param {...string} [args] * @returns {void} */ @@ -55963,6 +56056,7 @@ module.exports.NUMBER_OF_IDENTIFIER_CONTINUATION_CHARS = +const mime = __webpack_require__(78585); const { basename, extname } = __webpack_require__(71017); const util = __webpack_require__(73837); const Chunk = __webpack_require__(39385); @@ -56075,29 +56169,53 @@ const replacePathVariables = (path, data, assetInfo) => { // [name] - file // [ext] - .js if (typeof data.filename === "string") { - const { path: file, query, fragment } = parseResource(data.filename); - - const ext = extname(file); - const base = basename(file); - const name = base.slice(0, base.length - ext.length); - const path = file.slice(0, file.length - base.length); - - replacements.set("file", replacer(file)); - replacements.set("query", replacer(query, true)); - replacements.set("fragment", replacer(fragment, true)); - replacements.set("path", replacer(path, true)); - replacements.set("base", replacer(base)); - replacements.set("name", replacer(name)); - replacements.set("ext", replacer(ext, true)); - // Legacy - replacements.set( - "filebase", - deprecated( - replacer(base), - "[filebase] is now [base]", - "DEP_WEBPACK_TEMPLATE_PATH_PLUGIN_REPLACE_PATH_VARIABLES_FILENAME" - ) - ); + // check that filename is data uri + let match = data.filename.match(/^data:([^;,]+)/); + if (match) { + const ext = mime.extension(match[1]); + const emptyReplacer = replacer("", true); + + replacements.set("file", emptyReplacer); + replacements.set("query", emptyReplacer); + replacements.set("fragment", emptyReplacer); + replacements.set("path", emptyReplacer); + replacements.set("base", emptyReplacer); + replacements.set("name", emptyReplacer); + replacements.set("ext", replacer(ext ? `.${ext}` : "", true)); + // Legacy + replacements.set( + "filebase", + deprecated( + emptyReplacer, + "[filebase] is now [base]", + "DEP_WEBPACK_TEMPLATE_PATH_PLUGIN_REPLACE_PATH_VARIABLES_FILENAME" + ) + ); + } else { + const { path: file, query, fragment } = parseResource(data.filename); + + const ext = extname(file); + const base = basename(file); + const name = base.slice(0, base.length - ext.length); + const path = file.slice(0, file.length - base.length); + + replacements.set("file", replacer(file)); + replacements.set("query", replacer(query, true)); + replacements.set("fragment", replacer(fragment, true)); + replacements.set("path", replacer(path, true)); + replacements.set("base", replacer(base)); + replacements.set("name", replacer(name)); + replacements.set("ext", replacer(ext, true)); + // Legacy + replacements.set( + "filebase", + deprecated( + replacer(base), + "[filebase] is now [base]", + "DEP_WEBPACK_TEMPLATE_PATH_PLUGIN_REPLACE_PATH_VARIABLES_FILENAME" + ) + ); + } } // Compilation context @@ -58118,6 +58236,7 @@ const Generator = __webpack_require__(93401); const RuntimeGlobals = __webpack_require__(16475); const createHash = __webpack_require__(49835); const { makePathsRelative } = __webpack_require__(82186); +const nonNumericOnlyHash = __webpack_require__(55668); /** @typedef {import("webpack-sources").Source} Source */ /** @typedef {import("../../declarations/WebpackOptions").AssetGeneratorOptions} AssetGeneratorOptions */ @@ -58338,8 +58457,8 @@ class AssetGenerator extends Generator { const fullHash = /** @type {string} */ ( hash.digest(runtimeTemplate.outputOptions.hashDigest) ); - const contentHash = fullHash.slice( - 0, + const contentHash = nonNumericOnlyHash( + fullHash, runtimeTemplate.outputOptions.hashDigestLength ); module.buildInfo.fullContentHash = fullHash; @@ -60103,7 +60222,7 @@ const visitModules = ( const module = it.value; if ( availableModules.has(module) || - availableModules.plus.has(m) + availableModules.plus.has(module) ) { cachedMinAvailableModules.add(module); } @@ -62690,6 +62809,11 @@ class ResolverCachePlugin { fileDependencies: new LazySet(), contextDependencies: new LazySet() }; + let yieldResult; + if (typeof newResolveContext.yield === "function") { + yieldResult = []; + newResolveContext.yield = obj => yieldResult.push(obj); + } const propagate = key => { if (resolveContext[key]) { addAllToSet(resolveContext[key], newResolveContext[key]); @@ -62717,15 +62841,19 @@ class ResolverCachePlugin { snapshotOptions, (err, snapshot) => { if (err) return callback(err); + const resolveResult = result || yieldResult; if (!snapshot) { - if (result) return callback(null, result); + if (resolveResult) return callback(null, resolveResult); return callback(); } - itemCache.store(new CacheEntry(result, snapshot), storeErr => { - if (storeErr) return callback(storeErr); - if (result) return callback(null, result); - callback(); - }); + itemCache.store( + new CacheEntry(resolveResult, snapshot), + storeErr => { + if (storeErr) return callback(storeErr); + if (resolveResult) return callback(null, resolveResult); + callback(); + } + ); } ); } @@ -62735,6 +62863,8 @@ class ResolverCachePlugin { factory(type, hook) { /** @type {Map} */ const activeRequests = new Map(); + /** @type {Map} */ + const activeRequestsWithYield = new Map(); hook.tap( "ResolverCachePlugin", /** @@ -62759,29 +62889,63 @@ class ResolverCachePlugin { if (request._ResolverCachePluginCacheMiss || !fileSystemInfo) { return callback(); } - const identifier = `${type}${optionsIdent}${objectToString( - request, - !cacheWithContext - )}`; - const activeRequest = activeRequests.get(identifier); - if (activeRequest) { - activeRequest.push(callback); - return; + const withYield = typeof resolveContext.yield === "function"; + const identifier = `${type}${ + withYield ? "|yield" : "|default" + }${optionsIdent}${objectToString(request, !cacheWithContext)}`; + + if (withYield) { + const activeRequest = activeRequestsWithYield.get(identifier); + if (activeRequest) { + activeRequest[0].push(callback); + activeRequest[1].push(resolveContext.yield); + return; + } + } else { + const activeRequest = activeRequests.get(identifier); + if (activeRequest) { + activeRequest.push(callback); + return; + } } const itemCache = cache.getItemCache(identifier, null); - let callbacks; - const done = (err, result) => { - if (callbacks === undefined) { - callback(err, result); - callbacks = false; - } else { - for (const callback of callbacks) { - callback(err, result); - } - activeRequests.delete(identifier); - callbacks = false; - } - }; + let callbacks, yields; + const done = withYield + ? (err, result) => { + if (callbacks === undefined) { + if (err) { + callback(err); + } else { + if (result) + for (const r of result) resolveContext.yield(r); + callback(null, null); + } + yields = undefined; + callbacks = false; + } else { + for (let i = 0; i < callbacks.length; i++) { + const cb = callbacks[i]; + const yield_ = yields[i]; + if (result) for (const r of result) yield_(r); + cb(null, null); + } + activeRequestsWithYield.delete(identifier); + yields = undefined; + callbacks = false; + } + } + : (err, result) => { + if (callbacks === undefined) { + callback(err, result); + callbacks = false; + } else { + for (const callback of callbacks) { + callback(err, result); + } + activeRequests.delete(identifier); + callbacks = false; + } + }; /** * @param {Error=} err error if any * @param {CacheEntry=} cacheEntry cache entry @@ -62838,7 +63002,14 @@ class ResolverCachePlugin { } }; itemCache.get(processCacheResult); - if (callbacks === undefined) { + if (withYield && callbacks === undefined) { + callbacks = [callback]; + yields = [resolveContext.yield]; + activeRequestsWithYield.set( + identifier, + /** @type {[any, any]} */ ([callbacks, yields]) + ); + } else if (callbacks === undefined) { callbacks = [callback]; activeRequests.set(identifier, callbacks); } @@ -64946,6 +65117,7 @@ const applyOutputDefaults = ( D(output, "strictModuleExceptionHandling", false); const optimistic = v => v || v === undefined; + const conditionallyOptimistic = (v, c) => (v === undefined && c) || v; F( output.environment, "arrowFunction", @@ -64959,8 +65131,12 @@ const applyOutputDefaults = ( ); F(output.environment, "forOf", () => tp && optimistic(tp.forOf)); F(output.environment, "bigIntLiteral", () => tp && tp.bigIntLiteral); - F(output.environment, "dynamicImport", () => tp && tp.dynamicImport); - F(output.environment, "module", () => tp && tp.module); + F(output.environment, "dynamicImport", () => + conditionallyOptimistic(tp && tp.dynamicImport, output.module) + ); + F(output.environment, "module", () => + conditionallyOptimistic(tp && tp.module, output.module) + ); const { trustedTypes } = output; if (trustedTypes) { @@ -68350,78 +68526,71 @@ class CssLoadingRuntimeModule extends RuntimeModule { "", withLoading ? Template.asString([ - `${fn}.css = ${runtimeTemplate.basicFunction( - "chunkId, promises", - hasCssMatcher !== false - ? [ - "// css chunk loading", - `var installedChunkData = ${RuntimeGlobals.hasOwnProperty}(installedChunks, chunkId) ? installedChunks[chunkId] : undefined;`, - 'if(installedChunkData !== 0) { // 0 means "already installed".', - Template.indent([ - "", - '// a Promise means "currently loading".', - "if(installedChunkData) {", - Template.indent([ - "promises.push(installedChunkData[2]);" - ]), - "} else {", - Template.indent([ - hasCssMatcher === true - ? "if(true) { // all chunks have CSS" - : `if(${hasCssMatcher("chunkId")}) {`, + `${fn}.css = ${runtimeTemplate.basicFunction("chunkId, promises", [ + "// css chunk loading", + `var installedChunkData = ${RuntimeGlobals.hasOwnProperty}(installedChunks, chunkId) ? installedChunks[chunkId] : undefined;`, + 'if(installedChunkData !== 0) { // 0 means "already installed".', + Template.indent([ + "", + '// a Promise means "currently loading".', + "if(installedChunkData) {", + Template.indent(["promises.push(installedChunkData[2]);"]), + "} else {", + Template.indent([ + hasCssMatcher === true + ? "if(true) { // all chunks have CSS" + : `if(${hasCssMatcher("chunkId")}) {`, + Template.indent([ + "// setup Promise in chunk cache", + `var promise = new Promise(${runtimeTemplate.expressionFunction( + `installedChunkData = installedChunks[chunkId] = [resolve, reject]`, + "resolve, reject" + )});`, + "promises.push(installedChunkData[2] = promise);", + "", + "// start chunk loading", + `var url = ${RuntimeGlobals.publicPath} + ${RuntimeGlobals.getChunkCssFilename}(chunkId);`, + "// create error before stack unwound to get useful stacktrace later", + "var error = new Error();", + `var loadingEnded = ${runtimeTemplate.basicFunction( + "event", + [ + `if(${RuntimeGlobals.hasOwnProperty}(installedChunks, chunkId)) {`, Template.indent([ - "// setup Promise in chunk cache", - `var promise = new Promise(${runtimeTemplate.expressionFunction( - `installedChunkData = installedChunks[chunkId] = [resolve, reject]`, - "resolve, reject" - )});`, - "promises.push(installedChunkData[2] = promise);", - "", - "// start chunk loading", - `var url = ${RuntimeGlobals.publicPath} + ${RuntimeGlobals.getChunkCssFilename}(chunkId);`, - "// create error before stack unwound to get useful stacktrace later", - "var error = new Error();", - `var loadingEnded = ${runtimeTemplate.basicFunction( - "event", - [ - `if(${RuntimeGlobals.hasOwnProperty}(installedChunks, chunkId)) {`, - Template.indent([ - "installedChunkData = installedChunks[chunkId];", - "if(installedChunkData !== 0) installedChunks[chunkId] = undefined;", - "if(installedChunkData) {", - Template.indent([ - 'if(event.type !== "load") {', - Template.indent([ - "var errorType = event && event.type;", - "var realSrc = event && event.target && event.target.src;", - "error.message = 'Loading css chunk ' + chunkId + ' failed.\\n(' + errorType + ': ' + realSrc + ')';", - "error.name = 'ChunkLoadError';", - "error.type = errorType;", - "error.request = realSrc;", - "installedChunkData[1](error);" - ]), - "} else {", - Template.indent([ - `loadCssChunkData(${RuntimeGlobals.moduleFactories}, link, chunkId);`, - "installedChunkData[0]();" - ]), - "}" - ]), - "}" - ]), - "}" - ] - )};`, - "var link = loadStylesheet(chunkId, url, loadingEnded);" + "installedChunkData = installedChunks[chunkId];", + "if(installedChunkData !== 0) installedChunks[chunkId] = undefined;", + "if(installedChunkData) {", + Template.indent([ + 'if(event.type !== "load") {', + Template.indent([ + "var errorType = event && event.type;", + "var realSrc = event && event.target && event.target.src;", + "error.message = 'Loading css chunk ' + chunkId + ' failed.\\n(' + errorType + ': ' + realSrc + ')';", + "error.name = 'ChunkLoadError';", + "error.type = errorType;", + "error.request = realSrc;", + "installedChunkData[1](error);" + ]), + "} else {", + Template.indent([ + `loadCssChunkData(${RuntimeGlobals.moduleFactories}, link, chunkId);`, + "installedChunkData[0]();" + ]), + "}" + ]), + "}" ]), - "} else installedChunks[chunkId] = 0;" - ]), - "}" - ]), - "}" - ] - : "installedChunks[chunkId] = 0;" - )};` + "}" + ] + )};`, + "var link = loadStylesheet(chunkId, url, loadingEnded);" + ]), + "} else installedChunks[chunkId] = 0;" + ]), + "}" + ]), + "}" + ])};` ]) : "// no chunk loading", "", @@ -68550,6 +68719,7 @@ const { compareModulesByIdentifier } = __webpack_require__(29579); const createSchemaValidation = __webpack_require__(32540); const createHash = __webpack_require__(49835); const memoize = __webpack_require__(78676); +const nonNumericOnlyHash = __webpack_require__(55668); const CssExportsGenerator = __webpack_require__(91254); const CssGenerator = __webpack_require__(46061); const CssParser = __webpack_require__(98305); @@ -68732,7 +68902,7 @@ class CssModulesPlugin { hash.update(chunkGraph.getModuleHash(module, chunk.runtime)); } const digest = /** @type {string} */ (hash.digest(hashDigest)); - chunk.contentHash.css = digest.substr(0, hashDigestLength); + chunk.contentHash.css = nonNumericOnlyHash(digest, hashDigestLength); }); compilation.hooks.renderManifest.tap(plugin, (result, options) => { const { chunkGraph } = compilation; @@ -70636,7 +70806,8 @@ const interceptAllJavascriptModulesPluginHooks = (compilation, tracer) => { }; const makeInterceptorFor = (instance, tracer) => hookName => ({ - register: ({ name, type, context, fn }) => { + register: tapInfo => { + const { name, type, fn } = tapInfo; const newFn = // Don't tap our own hooks to ensure stream can close cleanly name === pluginName @@ -70647,9 +70818,7 @@ const makeInterceptorFor = (instance, tracer) => hookName => ({ fn }); return { - name, - type, - context, + ...tapInfo, fn: newFn }; } @@ -75184,12 +75353,18 @@ class ContextElementDependency extends ModuleDependency { } serialize(context) { - context.write(this.referencedExports); + const { write } = context; + write(this._typePrefix); + write(this._category); + write(this.referencedExports); super.serialize(context); } deserialize(context) { - this.referencedExports = context.read(); + const { read } = context; + this._typePrefix = read(); + this._category = read(); + this.referencedExports = read(); super.deserialize(context); } } @@ -76123,6 +76298,12 @@ const getProperty = (moduleGraph, module, exportName, property, runtime) => { } } switch (property) { + case "canMangle": { + const exportsInfo = moduleGraph.getExportsInfo(module); + const exportInfo = exportsInfo.getExportInfo(exportName); + if (exportInfo) return exportInfo.canMangle; + return exportsInfo.otherExportsInfo.canMangle; + } case "used": return ( moduleGraph.getExportsInfo(module).getUsed(exportName, runtime) !== @@ -87945,6 +88126,7 @@ const memoize = __webpack_require__(78676); /** @typedef {import("../declarations/WebpackOptions").Entry} Entry */ /** @typedef {import("../declarations/WebpackOptions").EntryNormalized} EntryNormalized */ /** @typedef {import("../declarations/WebpackOptions").EntryObject} EntryObject */ +/** @typedef {import("../declarations/WebpackOptions").FileCacheOptions} FileCacheOptions */ /** @typedef {import("../declarations/WebpackOptions").LibraryOptions} LibraryOptions */ /** @typedef {import("../declarations/WebpackOptions").ModuleOptions} ModuleOptions */ /** @typedef {import("../declarations/WebpackOptions").ResolveOptions} ResolveOptions */ @@ -87960,11 +88142,15 @@ const memoize = __webpack_require__(78676); /** @typedef {import("../declarations/WebpackOptions").WebpackPluginInstance} WebpackPluginInstance */ /** @typedef {import("./Compilation").Asset} Asset */ /** @typedef {import("./Compilation").AssetInfo} AssetInfo */ +/** @typedef {import("./Compilation").EntryOptions} EntryOptions */ +/** @typedef {import("./Compiler").AssetEmittedInfo} AssetEmittedInfo */ /** @typedef {import("./MultiStats")} MultiStats */ /** @typedef {import("./Parser").ParserState} ParserState */ /** @typedef {import("./ResolverFactory").ResolvePluginInstance} ResolvePluginInstance */ /** @typedef {import("./ResolverFactory").Resolver} Resolver */ /** @typedef {import("./Watching")} Watching */ +/** @typedef {import("./cli").Argument} Argument */ +/** @typedef {import("./cli").Problem} Problem */ /** @typedef {import("./stats/DefaultStatsFactoryPlugin").StatsAsset} StatsAsset */ /** @typedef {import("./stats/DefaultStatsFactoryPlugin").StatsChunk} StatsChunk */ /** @typedef {import("./stats/DefaultStatsFactoryPlugin").StatsChunkGroup} StatsChunkGroup */ @@ -89770,6 +89956,7 @@ const { last, someInIterable } = __webpack_require__(39104); const StringXor = __webpack_require__(40293); const { compareModulesByIdentifier } = __webpack_require__(29579); const createHash = __webpack_require__(49835); +const nonNumericOnlyHash = __webpack_require__(55668); const { intersectRuntime } = __webpack_require__(17156); const JavascriptGenerator = __webpack_require__(77106); const JavascriptParser = __webpack_require__(29050); @@ -89848,6 +90035,7 @@ const printGeneratedCodeForStack = (module, code) => { /** * @typedef {Object} RenderBootstrapContext * @property {Chunk} chunk the chunk + * @property {CodeGenerationResults} codeGenerationResults results of code generation * @property {RuntimeTemplate} runtimeTemplate the runtime template * @property {ModuleGraph} moduleGraph the module graph * @property {ChunkGraph} chunkGraph the chunk graph @@ -90077,6 +90265,7 @@ class JavascriptModulesPlugin { { hash: "0000", chunk, + codeGenerationResults: context.codeGenerationResults, chunkGraph: context.chunkGraph, moduleGraph: context.moduleGraph, runtimeTemplate: context.runtimeTemplate @@ -90089,6 +90278,7 @@ class JavascriptModulesPlugin { compilation.hooks.contentHash.tap("JavascriptModulesPlugin", chunk => { const { chunkGraph, + codeGenerationResults, moduleGraph, runtimeTemplate, outputOptions: { @@ -90106,6 +90296,7 @@ class JavascriptModulesPlugin { { hash: "0000", chunk, + codeGenerationResults, chunkGraph: compilation.chunkGraph, moduleGraph: compilation.moduleGraph, runtimeTemplate: compilation.runtimeTemplate @@ -90118,6 +90309,7 @@ class JavascriptModulesPlugin { } hooks.chunkHash.call(chunk, hash, { chunkGraph, + codeGenerationResults, moduleGraph, runtimeTemplate }); @@ -90144,7 +90336,10 @@ class JavascriptModulesPlugin { xor.updateHash(hash); } const digest = /** @type {string} */ (hash.digest(hashDigest)); - chunk.contentHash.javascript = digest.substr(0, hashDigestLength); + chunk.contentHash.javascript = nonNumericOnlyHash( + digest, + hashDigestLength + ); }); compilation.hooks.additionalTreeRuntimeRequirements.tap( "JavascriptModulesPlugin", @@ -90720,7 +90915,13 @@ class JavascriptModulesPlugin { * @returns {{ header: string[], beforeStartup: string[], startup: string[], afterStartup: string[], allowInlineStartup: boolean }} the generated source of the bootstrap code */ renderBootstrap(renderContext, hooks) { - const { chunkGraph, moduleGraph, chunk, runtimeTemplate } = renderContext; + const { + chunkGraph, + codeGenerationResults, + moduleGraph, + chunk, + runtimeTemplate + } = renderContext; const runtimeRequirements = chunkGraph.getTreeRuntimeRequirements(chunk); @@ -90844,8 +91045,18 @@ class JavascriptModulesPlugin { ); result.allowInlineStartup = false; } + + let data; + if (codeGenerationResults.has(entryModule, chunk.runtime)) { + const result = codeGenerationResults.get( + entryModule, + chunk.runtime + ); + data = result.data; + } if ( result.allowInlineStartup && + (!data || !data.get("topLevelDeclarations")) && (!entryModule.buildInfo || !entryModule.buildInfo.topLevelDeclarations) ) { @@ -95012,6 +95223,7 @@ const { getAllChunks } = __webpack_require__(91145); /** @typedef {import("../Chunk")} Chunk */ /** @typedef {import("../Compilation")} Compilation */ /** @typedef {import("../ChunkGraph")} ChunkGraph */ +/** @typedef {import("../ChunkGraph").EntryModuleWithChunkGroup} EntryModuleWithChunkGroup */ /** @typedef {import("../ChunkGroup")} ChunkGroup */ /** @typedef {import("../RuntimeTemplate")} RuntimeTemplate */ /** @typedef {(string|number)[]} EntryItem */ @@ -95021,7 +95233,7 @@ const EXPORT_PREFIX = "var __webpack_exports__ = "; /** * @param {ChunkGraph} chunkGraph chunkGraph * @param {RuntimeTemplate} runtimeTemplate runtimeTemplate - * @param {import("../ChunkGraph").EntryModuleWithChunkGroup[]} entries entries + * @param {EntryModuleWithChunkGroup[]} entries entries * @param {Chunk} chunk chunk * @param {boolean} passive true: passive startup with on chunks loaded * @returns {string} runtime code @@ -95099,7 +95311,7 @@ exports.generateEntryStartup = ( /** * @param {Hash} hash the hash to update * @param {ChunkGraph} chunkGraph chunkGraph - * @param {import("../ChunkGraph").EntryModuleWithChunkGroup[]} entries entries + * @param {EntryModuleWithChunkGroup[]} entries entries * @param {Chunk} chunk chunk * @returns {void} */ @@ -96205,9 +96417,15 @@ class AssignLibraryPlugin extends AbstractLibraryPlugin { * @param {LibraryContext} libraryContext context * @returns {string | undefined} bailout reason */ - embedInRuntimeBailout(module, { chunk }, { options, compilation }) { + embedInRuntimeBailout( + module, + { chunk, codeGenerationResults }, + { options, compilation } + ) { + const { data } = codeGenerationResults.get(module, chunk.runtime); const topLevelDeclarations = - module.buildInfo && module.buildInfo.topLevelDeclarations; + (data && data.get("topLevelDeclarations")) || + (module.buildInfo && module.buildInfo.topLevelDeclarations); if (!topLevelDeclarations) return "it doesn't tell about top level declarations."; const fullNameResolved = this._getResolvedFullName( @@ -98277,6 +98495,7 @@ const builtins = [ "tty", "url", "util", + "util/types", "v8", "vm", "wasi", @@ -100695,10 +100914,6 @@ class ConcatenatedModule extends Module { const topLevelDeclarations = this.buildInfo.topLevelDeclarations; if (topLevelDeclarations !== undefined) { for (const decl of m.buildInfo.topLevelDeclarations) { - // reserved names will always be renamed - if (RESERVED_NAMES.has(decl)) continue; - // TODO actually this is incorrect since with renaming there could be more - // We should do the renaming during build topLevelDeclarations.add(decl); } } @@ -100986,6 +101201,8 @@ class ConcatenatedModule extends Module { // List of all used names to avoid conflicts const allUsedNames = new Set(RESERVED_NAMES); + // Updated Top level declarations are created by renaming + const topLevelDeclarations = new Set(); // List of additional names in scope for module references /** @type {Map, alreadyCheckedScopes: Set }>} */ @@ -101130,6 +101347,7 @@ class ConcatenatedModule extends Module { ); allUsedNames.add(newName); info.internalNames.set(name, newName); + topLevelDeclarations.add(newName); const source = info.source; const allIdentifiers = new Set( references.map(r => r.identifier).concat(variable.identifiers) @@ -101156,6 +101374,7 @@ class ConcatenatedModule extends Module { } else { allUsedNames.add(name); info.internalNames.set(name, name); + topLevelDeclarations.add(name); } } let namespaceObjectName; @@ -101173,6 +101392,7 @@ class ConcatenatedModule extends Module { allUsedNames.add(namespaceObjectName); } info.namespaceObjectName = namespaceObjectName; + topLevelDeclarations.add(namespaceObjectName); break; } case "external": { @@ -101184,6 +101404,7 @@ class ConcatenatedModule extends Module { ); allUsedNames.add(externalName); info.name = externalName; + topLevelDeclarations.add(externalName); break; } } @@ -101196,6 +101417,7 @@ class ConcatenatedModule extends Module { ); allUsedNames.add(externalNameInterop); info.interopNamespaceObjectName = externalNameInterop; + topLevelDeclarations.add(externalNameInterop); } if ( info.module.buildMeta.exportsType === "default" && @@ -101209,6 +101431,7 @@ class ConcatenatedModule extends Module { ); allUsedNames.add(externalNameInterop); info.interopNamespaceObject2Name = externalNameInterop; + topLevelDeclarations.add(externalNameInterop); } if ( info.module.buildMeta.exportsType === "dynamic" || @@ -101222,6 +101445,7 @@ class ConcatenatedModule extends Module { ); allUsedNames.add(externalNameInterop); info.interopDefaultAccessName = externalNameInterop; + topLevelDeclarations.add(externalNameInterop); } } @@ -101491,6 +101715,7 @@ ${defineGetters}` const data = new Map(); if (chunkInitFragments.length > 0) data.set("chunkInitFragments", chunkInitFragments); + data.set("topLevelDeclarations", topLevelDeclarations); /** @type {CodeGenerationResult} */ const resultEntry = { @@ -110956,7 +111181,7 @@ class HttpUriPlugin { /** * @param {string} url URL - * @param {FetchResult} cachedResult result from cache + * @param {FetchResult | RedirectFetchResult} cachedResult result from cache * @param {function((Error | null)=, FetchResult=): void} callback callback * @returns {void} */ @@ -111050,9 +111275,30 @@ class HttpUriPlugin { res.statusCode >= 301 && res.statusCode <= 308 ) { - return finishWith({ + const result = { location: new URL(location, url).href - }); + }; + if ( + !cachedResult || + !("location" in cachedResult) || + cachedResult.location !== result.location || + cachedResult.validUntil < validUntil || + cachedResult.storeLock !== storeLock || + cachedResult.storeCache !== storeCache || + cachedResult.etag !== etag + ) { + return finishWith(result); + } else { + logger.debug(`GET ${url} [${res.statusCode}] (unchanged)`); + return callback(null, { + ...result, + fresh: true, + storeLock, + storeCache, + validUntil, + etag + }); + } } const contentType = res.headers["content-type"] || ""; const bufferArr = []; @@ -112598,6 +112844,8 @@ Section -> Buffer // "wpc" + 1 in little-endian const VERSION = 0x01637077; +const WRITE_LIMIT_TOTAL = 0x7fff0000; +const WRITE_LIMIT_CHUNK = 511 * 1024 * 1024; /** * @param {Buffer[]} buffers buffers @@ -112645,7 +112893,7 @@ const readUInt64LE = Buffer.prototype.readBigUInt64LE * @param {FileMiddleware} middleware this * @param {BufferSerializableType[] | Promise} data data to be serialized * @param {string | boolean} name file base name - * @param {function(string | false, Buffer[]): Promise} writeFile writes a file + * @param {function(string | false, Buffer[], number): Promise} writeFile writes a file * @param {string | Hash} hashFunction hash function to use * @returns {Promise} resulting file pointer and promise */ @@ -112770,9 +113018,9 @@ const serialize = async ( if (name === true) { name = hashForName(buf, hashFunction); } - backgroundJobs.push(writeFile(name, buf)); let size = 0; for (const b of buf) size += b.length; + backgroundJobs.push(writeFile(name, buf, size)); return { size, name, @@ -112980,7 +113228,7 @@ class FileMiddleware extends SerializerMiddleware { // It's important that we don't touch existing files during serialization // because serialize may read existing files (when deserializing) const allWrittenFiles = new Set(); - const writeFile = async (name, content) => { + const writeFile = async (name, content, size) => { const file = name ? join(this.fs, filename, `../${name}${extension}`) : filename; @@ -112999,10 +113247,7 @@ class FileMiddleware extends SerializerMiddleware { [zConstants.BROTLI_PARAM_MODE]: zConstants.BROTLI_MODE_TEXT, [zConstants.BROTLI_PARAM_QUALITY]: 2, [zConstants.BROTLI_PARAM_DISABLE_LITERAL_CONTEXT_MODELING]: true, - [zConstants.BROTLI_PARAM_SIZE_HINT]: content.reduce( - (size, b) => size + b.length, - 0 - ) + [zConstants.BROTLI_PARAM_SIZE_HINT]: size } }); } @@ -113014,8 +113259,44 @@ class FileMiddleware extends SerializerMiddleware { stream.on("error", err => reject(err)); stream.on("finish", () => resolve()); } - for (const b of content) stream.write(b); - stream.end(); + // split into chunks for WRITE_LIMIT_CHUNK size + const chunks = []; + for (const b of content) { + if (b.length < WRITE_LIMIT_CHUNK) { + chunks.push(b); + } else { + for (let i = 0; i < b.length; i += WRITE_LIMIT_CHUNK) { + chunks.push(b.slice(i, i + WRITE_LIMIT_CHUNK)); + } + } + } + + const len = chunks.length; + let i = 0; + const batchWrite = err => { + // will be handled in "on" error handler + if (err) return; + + if (i === len) { + stream.end(); + return; + } + + // queue up a batch of chunks up to the write limit + // end is exclusive + let end = i; + let sum = chunks[end++].length; + while (end < len) { + sum += chunks[end].length; + if (sum > WRITE_LIMIT_TOTAL) break; + end++; + } + while (i < end - 1) { + stream.write(chunks[i++]); + } + stream.write(chunks[i++], batchWrite); + }; + batchWrite(); }); if (name) allWrittenFiles.add(file); }; @@ -124682,7 +124963,7 @@ const toSimpleString = str => { /** * @param {Record} map value map - * @returns {true|false|function(string): string} true/false, when unconditionally true/false, or a template function to determine the value at runtime + * @returns {boolean|(function(string): string)} true/false, when unconditionally true/false, or a template function to determine the value at runtime */ const compileBooleanMatcher = map => { const positiveItems = Object.keys(map).filter(i => map[i]); @@ -125863,7 +126144,7 @@ module.exports = ({ maxSize, minSize, items, getSize, getKey }) => { // return the results return result.map(group => { - /** @type {GroupedItems} */ + /** @type {GroupedItems} */ return { key: group.key, items: group.nodes.map(node => node.item), @@ -126873,7 +127154,49 @@ const requestToAbsolute = (context, relativePath) => { return relativePath; }; -const makeCacheable = fn => { +const makeCacheable = realFn => { + /** @type {WeakMap>} */ + const cache = new WeakMap(); + + const getCache = associatedObjectForCache => { + const entry = cache.get(associatedObjectForCache); + if (entry !== undefined) return entry; + /** @type {Map} */ + const map = new Map(); + cache.set(associatedObjectForCache, map); + return map; + }; + + /** + * @param {string} str the path with query and fragment + * @param {Object=} associatedObjectForCache an object to which the cache will be attached + * @returns {ParsedResource} parsed parts + */ + const fn = (str, associatedObjectForCache) => { + if (!associatedObjectForCache) return realFn(str); + const cache = getCache(associatedObjectForCache); + const entry = cache.get(str); + if (entry !== undefined) return entry; + const result = realFn(str); + cache.set(str, result); + return result; + }; + + fn.bindCache = associatedObjectForCache => { + const cache = getCache(associatedObjectForCache); + return str => { + const entry = cache.get(str); + if (entry !== undefined) return entry; + const result = realFn(str); + cache.set(str, result); + return result; + }; + }; + + return fn; +}; + +const makeCacheableWithContext = fn => { /** @type {WeakMap>>} */ const cache = new WeakMap(); @@ -127007,7 +127330,7 @@ const _makePathsRelative = (context, identifier) => { .join(""); }; -exports.makePathsRelative = makeCacheable(_makePathsRelative); +exports.makePathsRelative = makeCacheableWithContext(_makePathsRelative); /** * @@ -127022,7 +127345,7 @@ const _makePathsAbsolute = (context, identifier) => { .join(""); }; -exports.makePathsAbsolute = makeCacheable(_makePathsAbsolute); +exports.makePathsAbsolute = makeCacheableWithContext(_makePathsAbsolute); /** * @param {string} context absolute context path @@ -127036,7 +127359,7 @@ const _contextify = (context, request) => { .join("!"); }; -const contextify = makeCacheable(_contextify); +const contextify = makeCacheableWithContext(_contextify); exports.contextify = contextify; /** @@ -127051,13 +127374,15 @@ const _absolutify = (context, request) => { .join("!"); }; -const absolutify = makeCacheable(_absolutify); +const absolutify = makeCacheableWithContext(_absolutify); exports.absolutify = absolutify; const PATH_QUERY_FRAGMENT_REGEXP = /^((?:\0.|[^?#\0])*)(\?(?:\0.|[^#\0])*)?(#.*)?$/; +const PATH_QUERY_REGEXP = /^((?:\0.|[^?\0])*)(\?.*)?$/; /** @typedef {{ resource: string, path: string, query: string, fragment: string }} ParsedResource */ +/** @typedef {{ resource: string, path: string, query: string }} ParsedResourceWithoutFragment */ /** * @param {string} str the path with query and fragment @@ -127072,47 +127397,24 @@ const _parseResource = str => { fragment: match[3] || "" }; }; -exports.parseResource = (realFn => { - /** @type {WeakMap>} */ - const cache = new WeakMap(); - - const getCache = associatedObjectForCache => { - const entry = cache.get(associatedObjectForCache); - if (entry !== undefined) return entry; - /** @type {Map} */ - const map = new Map(); - cache.set(associatedObjectForCache, map); - return map; - }; +exports.parseResource = makeCacheable(_parseResource); - /** - * @param {string} str the path with query and fragment - * @param {Object=} associatedObjectForCache an object to which the cache will be attached - * @returns {ParsedResource} parsed parts - */ - const fn = (str, associatedObjectForCache) => { - if (!associatedObjectForCache) return realFn(str); - const cache = getCache(associatedObjectForCache); - const entry = cache.get(str); - if (entry !== undefined) return entry; - const result = realFn(str); - cache.set(str, result); - return result; - }; - - fn.bindCache = associatedObjectForCache => { - const cache = getCache(associatedObjectForCache); - return str => { - const entry = cache.get(str); - if (entry !== undefined) return entry; - const result = realFn(str); - cache.set(str, result); - return result; - }; +/** + * Parse resource, skips fragment part + * @param {string} str the path with query and fragment + * @returns {ParsedResourceWithoutFragment} parsed parts + */ +const _parseResourceWithoutFragment = str => { + const match = PATH_QUERY_REGEXP.exec(str); + return { + resource: str, + path: match[1].replace(/\0(.)/g, "$1"), + query: match[2] ? match[2].replace(/\0(.)/g, "$1") : "" }; - - return fn; -})(_parseResource); +}; +exports.parseResourceWithoutFragment = makeCacheable( + _parseResourceWithoutFragment +); /** * @param {string} filename the filename which should be undone @@ -127443,6 +127745,36 @@ const memoize = fn => { module.exports = memoize; +/***/ }), + +/***/ 55668: +/***/ (function(module) { + +"use strict"; +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Ivan Kopeykin @vankop +*/ + + + +const A_CODE = "a".charCodeAt(0); + +/** + * @param {string} hash hash + * @param {number} hashLength hash length + * @returns {string} returns hash that has at least one non numeric char + */ +module.exports = (hash, hashLength) => { + if (hashLength < 1) return ""; + const slice = hash.slice(0, hashLength); + if (slice.match(/[^\d]/)) return slice; + return `${String.fromCharCode( + A_CODE + (parseInt(hash[0], 10) % 6) + )}${slice.slice(1)}`; +}; + + /***/ }), /***/ 70002: @@ -128725,7 +129057,9 @@ exports.versionLt = versionLt; */ exports.parseRange = str => { const splitAndConvert = str => { - return str.split(".").map(item => (`${+item}` === item ? +item : item)); + return str + .split(".") + .map(item => (item !== "NaN" && `${+item}` === item ? +item : item)); }; // see https://docs.npmjs.com/misc/semver#range-grammar for grammar const parsePartial = str => { @@ -128767,13 +129101,15 @@ exports.parseRange = str => { return [-range[0] - 1, ...range.slice(1)]; }; const parseSimple = str => { - // simple ::= primitive | partial | tilde | caret - // primitive ::= ( '<' | '>' | '>=' | '<=' | '=' ) partial - // tilde ::= '~' partial - // caret ::= '^' partial + // simple ::= primitive | partial | tilde | caret + // primitive ::= ( '<' | '>' | '>=' | '<=' | '=' | '!' ) ( ' ' ) * partial + // tilde ::= '~' ( ' ' ) * partial + // caret ::= '^' ( ' ' ) * partial const match = /^(\^|~|<=|<|>=|>|=|v|!)/.exec(str); const start = match ? match[0] : ""; - const remainder = parsePartial(str.slice(start.length)); + const remainder = parsePartial( + start.length ? str.slice(start.length).trim() : str.trim() + ); switch (start) { case "^": if (remainder.length > 1 && remainder[1] === 0) { @@ -128827,11 +129163,14 @@ exports.parseRange = str => { return [, ...arr, ...items.slice(1).map(() => fn)]; }; const parseRange = str => { - // range ::= hyphen | simple ( ' ' simple ) * | '' - // hyphen ::= partial ' - ' partial - const items = str.split(" - "); + // range ::= hyphen | simple ( ' ' ( ' ' ) * simple ) * | '' + // hyphen ::= partial ( ' ' ) * ' - ' ( ' ' ) * partial + const items = str.split(/\s+-\s+/); if (items.length === 1) { - const items = str.trim().split(/\s+/g).map(parseSimple); + const items = str + .trim() + .split(/(?<=[-0-9A-Za-z])\s+/g) + .map(parseSimple); return combine(items, 2); } const a = parsePartial(items[0]); @@ -135437,7 +135776,7 @@ module.exports = class MainFieldPlugin { /***/ }), -/***/ 48506: +/***/ 79024: /***/ (function(module, __unused_webpack_exports, __webpack_require__) { "use strict"; @@ -135454,7 +135793,7 @@ const getPaths = __webpack_require__(82918); /** @typedef {import("./Resolver")} Resolver */ /** @typedef {import("./Resolver").ResolveStepHook} ResolveStepHook */ -module.exports = class ModulesInHierachicDirectoriesPlugin { +module.exports = class ModulesInHierarchicalDirectoriesPlugin { /** * @param {string | ResolveStepHook} source source * @param {string | Array} directories directories @@ -135475,7 +135814,7 @@ module.exports = class ModulesInHierachicDirectoriesPlugin { resolver .getHook(this.source) .tapAsync( - "ModulesInHierachicDirectoriesPlugin", + "ModulesInHierarchicalDirectoriesPlugin", (request, resolveContext, callback) => { const fs = resolver.fileSystem; const addrs = getPaths(request.path) @@ -135924,6 +136263,7 @@ const { * @property {WriteOnlySet=} missingDependencies dependencies that was not found on file system * @property {Set=} stack set of hooks' calls. For instance, `resolve → parsedResolve → describedResolve`, * @property {(function(string): void)=} log log function + * @property {(function (ResolveRequest): void)=} yield yield result, if provided plugins can return several results */ /** @typedef {AsyncSeriesBailHook<[ResolveRequest, ResolveContext], ResolveRequest | null>} ResolveStepHook */ @@ -136092,6 +136432,16 @@ class Resolver { request: request }; + let yield_; + let yieldCalled = false; + if (typeof resolveContext.yield === "function") { + const old = resolveContext.yield; + yield_ = obj => { + yieldCalled = true; + old(obj); + }; + } + const message = `resolve '${request}' in '${path}'`; const finishResolved = result => { @@ -136129,6 +136479,7 @@ class Resolver { parentLog(msg); log.push(msg); }, + yield: yield_, fileDependencies: resolveContext.fileDependencies, contextDependencies: resolveContext.contextDependencies, missingDependencies: resolveContext.missingDependencies, @@ -136138,6 +136489,7 @@ class Resolver { if (err) return callback(err); if (result) return finishResolved(result); + if (yieldCalled) return callback(null); return finishWithoutResolve(log); } @@ -136151,6 +136503,7 @@ class Resolver { message, { log: undefined, + yield: yield_, fileDependencies: resolveContext.fileDependencies, contextDependencies: resolveContext.contextDependencies, missingDependencies: resolveContext.missingDependencies, @@ -136160,6 +136513,7 @@ class Resolver { if (err) return callback(err); if (result) return finishResolved(result); + if (yieldCalled) return callback(null); // log is missing for the error details // so we redo the resolving for the log info @@ -136174,11 +136528,15 @@ class Resolver { message, { log: msg => log.push(msg), + yield: yield_, stack: resolveContext.stack }, - (err, result) => { + err => { if (err) return callback(err); + // In a case that there is a race condition and yield will be called + if (yieldCalled) return callback(null); + return finishWithoutResolve(log); } ); @@ -136217,6 +136575,7 @@ class Resolver { const innerContext = createInnerContext( { log: resolveContext.log, + yield: resolveContext.yield, fileDependencies: resolveContext.fileDependencies, contextDependencies: resolveContext.contextDependencies, missingDependencies: resolveContext.missingDependencies, @@ -136326,7 +136685,7 @@ const ImportsFieldPlugin = __webpack_require__(7317); const JoinRequestPartPlugin = __webpack_require__(35949); const JoinRequestPlugin = __webpack_require__(5190); const MainFieldPlugin = __webpack_require__(47450); -const ModulesInHierachicDirectoriesPlugin = __webpack_require__(48506); +const ModulesInHierarchicalDirectoriesPlugin = __webpack_require__(79024); const ModulesInRootPlugin = __webpack_require__(88138); const NextPlugin = __webpack_require__(40777); const ParsePlugin = __webpack_require__(97849); @@ -136376,7 +136735,7 @@ const UseFilePlugin = __webpack_require__(96972); * @property {boolean=} fullySpecified The request is already fully specified and no extensions or directories are resolved for it * @property {boolean=} resolveToContext Resolve to a context instead of a file * @property {(string|RegExp)[]=} restrictions A list of resolve restrictions - * @property {boolean=} useSyncFileSystemCalls Use only the sync constiants of the file system calls + * @property {boolean=} useSyncFileSystemCalls Use only the sync constraints of the file system calls * @property {boolean=} preferRelative Prefer to resolve module requests as relative requests before falling back to modules * @property {boolean=} preferAbsolute Prefer to resolve server-relative urls as absolute paths before falling back to resolve in roots */ @@ -136583,7 +136942,7 @@ exports.createResolver = function (options) { resolver.ensureHook("resolve"); resolver.ensureHook("internalResolve"); - resolver.ensureHook("newInteralResolve"); + resolver.ensureHook("newInternalResolve"); resolver.ensureHook("parsedResolve"); resolver.ensureHook("describedResolve"); resolver.ensureHook("internal"); @@ -136605,6 +136964,11 @@ exports.createResolver = function (options) { resolver.ensureHook("existingFile"); resolver.ensureHook("resolved"); + // TODO remove in next major + // cspell:word Interal + // Backward-compat + resolver.hooks.newInteralResolve = resolver.hooks.newInternalResolve; + // resolve for (const { source, resolveOptions } of [ { source: "resolve", resolveOptions: { fullySpecified } }, @@ -136709,7 +137073,7 @@ exports.createResolver = function (options) { if (Array.isArray(item)) { if (item.includes("node_modules") && pnpApi) { plugins.push( - new ModulesInHierachicDirectoriesPlugin( + new ModulesInHierarchicalDirectoriesPlugin( "raw-module", item.filter(i => i !== "node_modules"), "module" @@ -136720,7 +137084,11 @@ exports.createResolver = function (options) { ); } else { plugins.push( - new ModulesInHierachicDirectoriesPlugin("raw-module", item, "module") + new ModulesInHierarchicalDirectoriesPlugin( + "raw-module", + item, + "module" + ) ); } } else { @@ -137065,7 +137433,12 @@ module.exports = class ResultPlugin { resolverContext.log("reporting result " + obj.path); resolver.hooks.result.callAsync(obj, resolverContext, err => { if (err) return callback(err); - callback(null, obj); + if (typeof resolverContext.yield === "function") { + resolverContext.yield(obj); + callback(null, null); + } else { + callback(null, obj); + } }); } ); @@ -137273,7 +137646,7 @@ module.exports = class SymlinkPlugin { .tapAsync("SymlinkPlugin", (request, resolveContext, callback) => { if (request.ignoreSymlinks) return callback(); const pathsResult = getPaths(request.path); - const pathSeqments = pathsResult.seqments; + const pathSegments = pathsResult.segments; const paths = pathsResult.paths; let containsSymlink = false; @@ -137286,7 +137659,7 @@ module.exports = class SymlinkPlugin { resolveContext.fileDependencies.add(path); fs.readlink(path, (err, result) => { if (!err && result) { - pathSeqments[idx] = result; + pathSegments[idx] = result; containsSymlink = true; // Shortcut when absolute symlink found const resultType = getType(result.toString()); @@ -137302,11 +137675,11 @@ module.exports = class SymlinkPlugin { }, (err, idx) => { if (!containsSymlink) return callback(); - const resultSeqments = + const resultSegments = typeof idx === "number" - ? pathSeqments.slice(0, idx + 1) - : pathSeqments.slice(); - const result = resultSeqments.reduceRight((a, b) => { + ? pathSegments.slice(0, idx + 1) + : pathSegments.slice(); + const result = resultSegments.reduceRight((a, b) => { return resolver.join(a, b); }); const obj = { @@ -137646,6 +138019,7 @@ module.exports = function createInnerContext( } const childContext = { log: innerLog, + yield: options.yield, fileDependencies: options.fileDependencies, contextDependencies: options.contextDependencies, missingDependencies: options.missingDependencies, @@ -137737,23 +138111,24 @@ module.exports = function getInnerRequest(resolver, request) { module.exports = function getPaths(path) { + if (path === "/") return { paths: ["/"], segments: [""] }; const parts = path.split(/(.*?[\\/]+)/); const paths = [path]; - const seqments = [parts[parts.length - 1]]; + const segments = [parts[parts.length - 1]]; let part = parts[parts.length - 1]; path = path.substr(0, path.length - part.length - 1); for (let i = parts.length - 2; i > 2; i -= 2) { paths.push(path); part = parts[i]; path = path.substr(0, path.length - part.length) || "/"; - seqments.push(part.substr(0, part.length - 1)); + segments.push(part.substr(0, part.length - 1)); } part = parts[1]; - seqments.push(part); + segments.push(part); paths.push(part); return { paths: paths, - seqments: seqments + segments: segments }; }; @@ -141590,7 +141965,7 @@ module.exports = JSON.parse('{"application/1d-interleaved-parityfec":{"source":" /***/ (function(module) { "use strict"; -module.exports = {"i8":"5.68.0"}; +module.exports = {"i8":"5.69.0"}; /***/ }), diff --git a/packages/next/package.json b/packages/next/package.json index a1229df5bac0..76975ad6b00e 100644 --- a/packages/next/package.json +++ b/packages/next/package.json @@ -267,7 +267,7 @@ "webpack-sources1": "npm:webpack-sources@1.4.3", "webpack-sources3": "npm:webpack-sources@3.2.3", "webpack4": "npm:webpack@4.44.1", - "webpack5": "npm:webpack@5.68.0", + "webpack5": "npm:webpack@5.69.0", "ws": "8.2.3" }, "resolutions": { diff --git a/yarn.lock b/yarn.lock index f557f48cfb93..af18b6562100 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4501,10 +4501,10 @@ version "4.1.5" resolved "https://registry.yarnpkg.com/@types/debug/-/debug-4.1.5.tgz#b14efa8852b7768d898906613c23f688713e02cd" -"@types/eslint-scope@^3.7.0": - version "3.7.0" - resolved "https://registry.yarnpkg.com/@types/eslint-scope/-/eslint-scope-3.7.0.tgz#4792816e31119ebd506902a482caec4951fabd86" - integrity sha512-O/ql2+rrCUe2W2rs7wMR+GqPRcgB6UiqN5RhrR5xruFlY7l9YLMn0ZkDzjoHLeiFkR8MCQZVudUuuvQ2BLC9Qw== +"@types/eslint-scope@^3.7.3": + version "3.7.3" + resolved "https://registry.yarnpkg.com/@types/eslint-scope/-/eslint-scope-3.7.3.tgz#125b88504b61e3c8bc6f870882003253005c3224" + integrity sha512-PB3ldyrcnAicT35TWPs5IcwKD8S333HMaa2VVv4+wdvebJkjWuW/xESoB8IwRcog8HYVYamb1g/R31Qv5Bx03g== dependencies: "@types/eslint" "*" "@types/estree" "*" @@ -4526,10 +4526,10 @@ version "0.0.39" resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.39.tgz#e177e699ee1b8c22d23174caaa7422644389509f" -"@types/estree@^0.0.50": - version "0.0.50" - resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.50.tgz#1e0caa9364d3fccd2931c3ed96fdbeaa5d4cca83" - integrity sha512-C6N5s2ZFtuZRj54k2/zyRhNDjJwwcViAM3Nbm8zjBpbqAdZ00mr0CFxvSKeO8Y/e03WVFLpQMdHYVfUd6SB+Hw== +"@types/estree@^0.0.51": + version "0.0.51" + resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.51.tgz#cfd70924a25a3fd32b218e5e420e6897e1ac4f40" + integrity sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ== "@types/etag@1.8.0": version "1.8.0" @@ -8878,10 +8878,10 @@ enhanced-resolve@^4.3.0: memory-fs "^0.5.0" tapable "^1.0.0" -enhanced-resolve@^5.8.3: - version "5.8.3" - resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.8.3.tgz#6d552d465cce0423f5b3d718511ea53826a7b2f0" - integrity sha512-EGAbGvH7j7Xt2nc0E7D99La1OiEs8LnyimkRgwExpUMScN6O+3x9tIWs7PLQZVNx4YD+00skHXPXi1yQHpAmZA== +enhanced-resolve@^5.9.0: + version "5.9.0" + resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.9.0.tgz#49ac24953ac8452ed8fed2ef1340fc8e043667ee" + integrity sha512-weDYmzbBygL7HzGGS26M3hGQx68vehdEg6VUmqSOaFzXExFqlnKuSvsEJCVGQHScS8CQMbrAqftT+AzzHNt/YA== dependencies: graceful-fs "^4.2.4" tapable "^2.2.0" @@ -20847,13 +20847,13 @@ webpack-bundle-analyzer@4.3.0: watchpack "^1.7.4" webpack-sources "^1.4.1" -"webpack5@npm:webpack@5.68.0": - version "5.68.0" - resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.68.0.tgz#a653a58ed44280062e47257f260117e4be90d560" - integrity sha512-zUcqaUO0772UuuW2bzaES2Zjlm/y3kRBQDVFVCge+s2Y8mwuUTdperGaAv65/NtRL/1zanpSJOq/MD8u61vo6g== +"webpack5@npm:webpack@5.69.0": + version "5.69.0" + resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.69.0.tgz#c9eb607d4f6c49f1e5755492323a7b055c3450e3" + integrity sha512-E5Fqu89Gu8fR6vejRqu26h8ld/k6/dCVbeGUcuZjc+goQHDfCPU9rER71JmdtBYGmci7Ec2aFEATQ2IVXKy2wg== dependencies: - "@types/eslint-scope" "^3.7.0" - "@types/estree" "^0.0.50" + "@types/eslint-scope" "^3.7.3" + "@types/estree" "^0.0.51" "@webassemblyjs/ast" "1.11.1" "@webassemblyjs/wasm-edit" "1.11.1" "@webassemblyjs/wasm-parser" "1.11.1" @@ -20861,7 +20861,7 @@ webpack-bundle-analyzer@4.3.0: acorn-import-assertions "^1.7.6" browserslist "^4.14.5" chrome-trace-event "^1.0.2" - enhanced-resolve "^5.8.3" + enhanced-resolve "^5.9.0" es-module-lexer "^0.9.0" eslint-scope "5.1.1" events "^3.2.0" From d288d43f19d9360e9676c638badc2ecd52649713 Mon Sep 17 00:00:00 2001 From: Kitayoshi Date: Thu, 17 Feb 2022 06:06:18 +0800 Subject: [PATCH 10/28] Update MDX Guide config example (#34405) The previous example may cause an unintentional change of the current `pageExtensions` setting, especially for the people who are not familiar with ## Documentation / Examples - [x] Make sure the linting passes by running `yarn lint` --- docs/advanced-features/using-mdx.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/advanced-features/using-mdx.md b/docs/advanced-features/using-mdx.md index bdfb3b3e8f3d..8087473ca22d 100644 --- a/docs/advanced-features/using-mdx.md +++ b/docs/advanced-features/using-mdx.md @@ -51,7 +51,8 @@ The following steps outline how to setup `@next/mdx` in your Next.js project: }, }) module.exports = withMDX({ - pageExtensions: ['js', 'jsx', 'md', 'mdx'], + // Append the default value with md extensions + pageExtensions: ['ts', 'tsx', 'js', 'jsx', 'md', 'mdx'], }) ``` From 59714db16deee949b426af3184f38ee243c89b8d Mon Sep 17 00:00:00 2001 From: JJ Kasper Date: Wed, 16 Feb 2022 16:32:24 -0600 Subject: [PATCH 11/28] Update server-only changes HMR handling (#34298) * Update server-only changes HMR handling * Add failing tests for GS(S)P server only changes * update test * normalize backslashes * Update to xor the chunk hashes * remove test change * remove other test change --- packages/next/server/dev/hot-reloader.ts | 65 +++- packages/next/types/webpack.d.ts | 1 + .../gssp-ssr-change-reloading/lib/data.json | 3 + .../pages/another/index.js | 0 .../pages/gsp-blog/[post].js | 0 .../pages/gssp-blog/[post].js | 0 .../gssp-ssr-change-reloading/pages/index.js | 2 + .../test/index.test.ts | 309 ++++++++++++++++++ .../test/index.test.js | 197 ----------- 9 files changed, 365 insertions(+), 212 deletions(-) create mode 100644 test/development/basic/gssp-ssr-change-reloading/lib/data.json rename test/{integration => development/basic}/gssp-ssr-change-reloading/pages/another/index.js (100%) rename test/{integration => development/basic}/gssp-ssr-change-reloading/pages/gsp-blog/[post].js (100%) rename test/{integration => development/basic}/gssp-ssr-change-reloading/pages/gssp-blog/[post].js (100%) rename test/{integration => development/basic}/gssp-ssr-change-reloading/pages/index.js (89%) create mode 100644 test/development/basic/gssp-ssr-change-reloading/test/index.test.ts delete mode 100644 test/integration/gssp-ssr-change-reloading/test/index.test.js diff --git a/packages/next/server/dev/hot-reloader.ts b/packages/next/server/dev/hot-reloader.ts index a5b9391be164..92847d189629 100644 --- a/packages/next/server/dev/hot-reloader.ts +++ b/packages/next/server/dev/hot-reloader.ts @@ -3,7 +3,7 @@ import { IncomingMessage, ServerResponse } from 'http' import { WebpackHotMiddleware } from './hot-middleware' import { join, relative, isAbsolute } from 'path' import { UrlObject } from 'url' -import { webpack } from 'next/dist/compiled/webpack/webpack' +import { webpack, StringXor } from 'next/dist/compiled/webpack/webpack' import type { webpack5 } from 'next/dist/compiled/webpack/webpack' import { createEntrypoints, @@ -594,21 +594,56 @@ export default class HotReloader { const trackPageChanges = (pageHashMap: Map, changedItems: Set) => (stats: webpack5.Compilation) => { - stats.entrypoints.forEach((entry, key) => { - if (key.startsWith('pages/')) { - // TODO this doesn't handle on demand loaded chunks - entry.chunks.forEach((chunk: any) => { - if (chunk.id === key) { - const prevHash = pageHashMap.get(key) - - if (prevHash && prevHash !== chunk.hash) { - changedItems.add(key) + try { + stats.entrypoints.forEach((entry, key) => { + if (key.startsWith('pages/')) { + // TODO this doesn't handle on demand loaded chunks + entry.chunks.forEach((chunk) => { + if (chunk.id === key) { + const modsIterable: any = + stats.chunkGraph.getChunkModulesIterable(chunk) + + let chunksHash = new StringXor() + + modsIterable.forEach((mod: any) => { + if ( + mod.resource && + mod.resource.replace(/\\/g, '/').includes(key) + ) { + // use original source to calculate hash since mod.hash + // includes the source map in development which changes + // every time for both server and client so we calculate + // the hash without the source map for the page module + const hash = require('crypto') + .createHash('sha256') + .update(mod.originalSource().buffer()) + .digest() + .toString('hex') + + chunksHash.add(hash) + } else { + // for non-pages we can use the module hash directly + const hash = stats.chunkGraph.getModuleHash( + mod, + chunk.runtime + ) + chunksHash.add(hash) + } + }) + const prevHash = pageHashMap.get(key) + const curHash = chunksHash.toString() + + if (prevHash && prevHash !== curHash) { + changedItems.add(key) + } + pageHashMap.set(key, curHash) } - pageHashMap.set(key, chunk.hash) - } - }) - } - }) + }) + } + }) + } catch (err) { + console.error(err) + } } multiCompiler.compilers[0].hooks.emit.tap( diff --git a/packages/next/types/webpack.d.ts b/packages/next/types/webpack.d.ts index cd3f07453c22..5cbda79547b0 100644 --- a/packages/next/types/webpack.d.ts +++ b/packages/next/types/webpack.d.ts @@ -35,6 +35,7 @@ declare module 'next/dist/compiled/webpack/webpack' { export let BasicEvaluatedExpression: any export let GraphHelpers: any export let sources: typeof webpackSources + export let StringXor: any // TODO change this to webpack5 export { webpack4 as webpack, loader, webpack4, webpack5 } } diff --git a/test/development/basic/gssp-ssr-change-reloading/lib/data.json b/test/development/basic/gssp-ssr-change-reloading/lib/data.json new file mode 100644 index 000000000000..f2a886f39de7 --- /dev/null +++ b/test/development/basic/gssp-ssr-change-reloading/lib/data.json @@ -0,0 +1,3 @@ +{ + "hello": "world" +} diff --git a/test/integration/gssp-ssr-change-reloading/pages/another/index.js b/test/development/basic/gssp-ssr-change-reloading/pages/another/index.js similarity index 100% rename from test/integration/gssp-ssr-change-reloading/pages/another/index.js rename to test/development/basic/gssp-ssr-change-reloading/pages/another/index.js diff --git a/test/integration/gssp-ssr-change-reloading/pages/gsp-blog/[post].js b/test/development/basic/gssp-ssr-change-reloading/pages/gsp-blog/[post].js similarity index 100% rename from test/integration/gssp-ssr-change-reloading/pages/gsp-blog/[post].js rename to test/development/basic/gssp-ssr-change-reloading/pages/gsp-blog/[post].js diff --git a/test/integration/gssp-ssr-change-reloading/pages/gssp-blog/[post].js b/test/development/basic/gssp-ssr-change-reloading/pages/gssp-blog/[post].js similarity index 100% rename from test/integration/gssp-ssr-change-reloading/pages/gssp-blog/[post].js rename to test/development/basic/gssp-ssr-change-reloading/pages/gssp-blog/[post].js diff --git a/test/integration/gssp-ssr-change-reloading/pages/index.js b/test/development/basic/gssp-ssr-change-reloading/pages/index.js similarity index 89% rename from test/integration/gssp-ssr-change-reloading/pages/index.js rename to test/development/basic/gssp-ssr-change-reloading/pages/index.js index 679405ddf80a..3f2fe4523faa 100644 --- a/test/integration/gssp-ssr-change-reloading/pages/index.js +++ b/test/development/basic/gssp-ssr-change-reloading/pages/index.js @@ -1,4 +1,5 @@ import { useRouter } from 'next/router' +import data from '../lib/data.json' export default function Gsp(props) { if (useRouter().isFallback) { @@ -19,6 +20,7 @@ export const getStaticProps = async () => { return { props: { count, + data, random: Math.random(), }, } diff --git a/test/development/basic/gssp-ssr-change-reloading/test/index.test.ts b/test/development/basic/gssp-ssr-change-reloading/test/index.test.ts new file mode 100644 index 000000000000..828dc2adb2fe --- /dev/null +++ b/test/development/basic/gssp-ssr-change-reloading/test/index.test.ts @@ -0,0 +1,309 @@ +/* eslint-env jest */ + +import { join } from 'path' +import webdriver from 'next-webdriver' +import { createNext, FileRef } from 'e2e-utils' +import { check, getRedboxHeader, hasRedbox } from 'next-test-utils' +import { NextInstance } from 'test/lib/next-modes/base' + +const installCheckVisible = (browser) => { + return browser.eval(`(function() { + window.checkInterval = setInterval(function() { + let watcherDiv = document.querySelector('#__next-build-watcher') + watcherDiv = watcherDiv.shadowRoot || watcherDiv + window.showedBuilder = window.showedBuilder || ( + watcherDiv.querySelector('div').className.indexOf('visible') > -1 + ) + if (window.showedBuilder) clearInterval(window.checkInterval) + }, 50) + })()`) +} + +describe('GS(S)P Server-Side Change Reloading', () => { + let next: NextInstance + + beforeAll(async () => { + next = await createNext({ + files: { + pages: new FileRef(join(__dirname, '../pages')), + lib: new FileRef(join(__dirname, '../lib')), + }, + }) + }) + afterAll(() => next.destroy()) + + it('should not reload page when client-side is changed too GSP', async () => { + const browser = await webdriver(next.url, '/gsp-blog/first') + await check(() => browser.elementByCss('#change').text(), 'change me') + await browser.eval(`window.beforeChange = 'hi'`) + + const props = JSON.parse(await browser.elementByCss('#props').text()) + + const page = 'pages/gsp-blog/[post].js' + const originalContent = await next.readFile(page) + await next.patchFile(page, originalContent.replace('change me', 'changed')) + + await check(() => browser.elementByCss('#change').text(), 'changed') + expect(await browser.eval(`window.beforeChange`)).toBe('hi') + + const props2 = JSON.parse(await browser.elementByCss('#props').text()) + expect(props).toEqual(props2) + + await next.patchFile(page, originalContent) + await check(() => browser.elementByCss('#change').text(), 'change me') + }) + + it('should update page when getStaticProps is changed only', async () => { + const browser = await webdriver(next.url, '/gsp-blog/first') + await browser.eval(`window.beforeChange = 'hi'`) + + const props = JSON.parse(await browser.elementByCss('#props').text()) + expect(props.count).toBe(1) + + const page = 'pages/gsp-blog/[post].js' + const originalContent = await next.readFile(page) + await next.patchFile( + page, + originalContent.replace('count = 1', 'count = 2') + ) + + await check( + async () => + JSON.parse(await browser.elementByCss('#props').text()).count + '', + '2' + ) + expect(await browser.eval(`window.beforeChange`)).toBe('hi') + await next.patchFile(page, originalContent) + + await check( + async () => + JSON.parse(await browser.elementByCss('#props').text()).count + '', + '1' + ) + }) + + it('should show indicator when re-fetching data', async () => { + const browser = await webdriver(next.url, '/gsp-blog/second') + await installCheckVisible(browser) + await browser.eval(`window.beforeChange = 'hi'`) + + const props = JSON.parse(await browser.elementByCss('#props').text()) + expect(props.count).toBe(1) + + const page = 'pages/gsp-blog/[post].js' + const originalContent = await next.readFile(page) + await next.patchFile( + page, + originalContent.replace('count = 1', 'count = 2') + ) + + await check( + async () => + JSON.parse(await browser.elementByCss('#props').text()).count + '', + '2' + ) + expect(await browser.eval(`window.beforeChange`)).toBe('hi') + expect(await browser.eval(`window.showedBuilder`)).toBe(true) + + await next.patchFile(page, originalContent) + await check( + async () => + JSON.parse(await browser.elementByCss('#props').text()).count + '', + '1' + ) + }) + + it('should update page when getStaticPaths is changed only', async () => { + const browser = await webdriver(next.url, '/gsp-blog/first') + await browser.eval(`window.beforeChange = 'hi'`) + + const props = JSON.parse(await browser.elementByCss('#props').text()) + expect(props.count).toBe(1) + + const page = 'pages/gsp-blog/[post].js' + const originalContent = await next.readFile(page) + await next.patchFile( + page, + originalContent.replace('paths = 1', 'paths = 2') + ) + + expect(await browser.eval('window.beforeChange')).toBe('hi') + await next.patchFile(page, originalContent) + }) + + it('should update page when getStaticProps is changed only for /index', async () => { + const browser = await webdriver(next.url, '/') + await browser.eval(`window.beforeChange = 'hi'`) + + const props = JSON.parse(await browser.elementByCss('#props').text()) + expect(props.count).toBe(1) + + const page = 'pages/index.js' + const originalContent = await next.readFile(page) + await next.patchFile( + page, + originalContent.replace('count = 1', 'count = 2') + ) + + expect(await browser.eval('window.beforeChange')).toBe('hi') + await next.patchFile(page, originalContent) + }) + + it('should update page when getStaticProps is changed only for /another/index', async () => { + const browser = await webdriver(next.url, '/another') + await browser.eval(`window.beforeChange = 'hi'`) + + const props = JSON.parse(await browser.elementByCss('#props').text()) + expect(props.count).toBe(1) + + const page = 'pages/another/index.js' + const originalContent = await next.readFile(page) + await next.patchFile( + page, + originalContent.replace('count = 1', 'count = 2') + ) + + expect(await browser.eval('window.beforeChange')).toBe('hi') + await next.patchFile(page, originalContent) + }) + + it('should not reload page when client-side is changed too GSSP', async () => { + const browser = await webdriver(next.url, '/gssp-blog/first') + await check(() => browser.elementByCss('#change').text(), 'change me') + await browser.eval(`window.beforeChange = 'hi'`) + + const props = JSON.parse(await browser.elementByCss('#props').text()) + + const page = 'pages/gssp-blog/[post].js' + const originalContent = await next.readFile(page) + await next.patchFile(page, originalContent.replace('change me', 'changed')) + + await check(() => browser.elementByCss('#change').text(), 'changed') + expect(await browser.eval(`window.beforeChange`)).toBe('hi') + + const props2 = JSON.parse(await browser.elementByCss('#props').text()) + expect(props).toEqual(props2) + + await next.patchFile(page, originalContent) + await check(() => browser.elementByCss('#change').text(), 'change me') + }) + + it('should update page when getServerSideProps is changed only', async () => { + const browser = await webdriver(next.url, '/gssp-blog/first') + await check( + async () => + JSON.parse(await browser.elementByCss('#props').text()).count + '', + '1' + ) + await browser.eval(`window.beforeChange = 'hi'`) + + const props = JSON.parse(await browser.elementByCss('#props').text()) + expect(props.count).toBe(1) + + const page = 'pages/gssp-blog/[post].js' + const originalContent = await next.readFile(page) + await next.patchFile( + page, + originalContent.replace('count = 1', 'count = 2') + ) + + await check( + async () => + JSON.parse(await browser.elementByCss('#props').text()).count + '', + '2' + ) + expect(await browser.eval(`window.beforeChange`)).toBe('hi') + await next.patchFile(page, originalContent) + + await check( + async () => + JSON.parse(await browser.elementByCss('#props').text()).count + '', + '1' + ) + }) + + it('should update on props error in getStaticProps', async () => { + const browser = await webdriver(next.url, '/') + await browser.eval(`window.beforeChange = 'hi'`) + + const props = JSON.parse(await browser.elementByCss('#props').text()) + expect(props.count).toBe(1) + + const page = 'pages/index.js' + const originalContent = await next.readFile(page) + + try { + await next.patchFile(page, originalContent.replace('props:', 'propss:')) + expect(await hasRedbox(browser, true)).toBe(true) + expect(await getRedboxHeader(browser)).toContain( + 'Additional keys were returned from' + ) + + await next.patchFile(page, originalContent) + expect(await hasRedbox(browser, false)).toBe(false) + } finally { + await next.patchFile(page, originalContent) + } + }) + + it('should update on thrown error in getStaticProps', async () => { + const browser = await webdriver(next.url, '/') + await browser.eval(`window.beforeChange = 'hi'`) + + const props = JSON.parse(await browser.elementByCss('#props').text()) + expect(props.count).toBe(1) + + const page = 'pages/index.js' + const originalContent = await next.readFile(page) + + try { + await next.patchFile( + page, + originalContent.replace( + 'const count', + 'throw new Error("custom oops"); const count' + ) + ) + expect(await hasRedbox(browser, true)).toBe(true) + expect(await getRedboxHeader(browser)).toContain('custom oops') + + await next.patchFile(page, originalContent) + expect(await hasRedbox(browser, false)).toBe(false) + } finally { + await next.patchFile(page, originalContent) + } + }) + + it('should refresh data when server import is updated', async () => { + const browser = await webdriver(next.url, '/') + await browser.eval(`window.beforeChange = 'hi'`) + + const props = JSON.parse(await browser.elementByCss('#props').text()) + expect(props.count).toBe(1) + expect(props.data).toEqual({ hello: 'world' }) + + const page = 'lib/data.json' + const originalContent = await next.readFile(page) + + try { + await next.patchFile(page, JSON.stringify({ hello: 'replaced!!' })) + await check(async () => { + const props = JSON.parse(await browser.elementByCss('#props').text()) + return props.count === 1 && props.data.hello === 'replaced!!' + ? 'success' + : JSON.stringify(props) + }, 'success') + expect(await browser.eval('window.beforeChange')).toBe('hi') + + await next.patchFile(page, originalContent) + await check(async () => { + const props = JSON.parse(await browser.elementByCss('#props').text()) + return props.count === 1 && props.data.hello === 'world' + ? 'success' + : JSON.stringify(props) + }, 'success') + } finally { + await next.patchFile(page, originalContent) + } + }) +}) diff --git a/test/integration/gssp-ssr-change-reloading/test/index.test.js b/test/integration/gssp-ssr-change-reloading/test/index.test.js deleted file mode 100644 index 3fcd32e4a23d..000000000000 --- a/test/integration/gssp-ssr-change-reloading/test/index.test.js +++ /dev/null @@ -1,197 +0,0 @@ -/* eslint-env jest */ - -import { join } from 'path' -import webdriver from 'next-webdriver' -import { killApp, findPort, launchApp, File, check } from 'next-test-utils' - -const appDir = join(__dirname, '..') - -let appPort -let app - -const installCheckVisible = (browser) => { - return browser.eval(`(function() { - window.checkInterval = setInterval(function() { - let watcherDiv = document.querySelector('#__next-build-watcher') - watcherDiv = watcherDiv.shadowRoot || watcherDiv - window.showedBuilder = window.showedBuilder || ( - watcherDiv.querySelector('div').className.indexOf('visible') > -1 - ) - if (window.showedBuilder) clearInterval(window.checkInterval) - }, 50) - })()`) -} - -describe('GS(S)P Server-Side Change Reloading', () => { - beforeAll(async () => { - appPort = await findPort() - app = await launchApp(appDir, appPort) - }) - afterAll(() => killApp(app)) - - it('should not reload page when client-side is changed too GSP', async () => { - const browser = await webdriver(appPort, '/gsp-blog/first') - await check(() => browser.elementByCss('#change').text(), 'change me') - await browser.eval(() => (window.beforeChange = 'hi')) - - const props = JSON.parse(await browser.elementByCss('#props').text()) - - const page = new File(join(appDir, 'pages/gsp-blog/[post].js')) - page.replace('change me', 'changed') - - await check(() => browser.elementByCss('#change').text(), 'changed') - expect(await browser.eval(() => window.beforeChange)).toBe('hi') - - const props2 = JSON.parse(await browser.elementByCss('#props').text()) - expect(props).toEqual(props2) - - page.restore() - - await check(() => browser.elementByCss('#change').text(), 'change me') - }) - - it('should update page when getStaticProps is changed only', async () => { - const browser = await webdriver(appPort, '/gsp-blog/first') - await browser.eval(() => (window.beforeChange = 'hi')) - - const props = JSON.parse(await browser.elementByCss('#props').text()) - expect(props.count).toBe(1) - - const page = new File(join(appDir, 'pages/gsp-blog/[post].js')) - page.replace('count = 1', 'count = 2') - - await check( - async () => - JSON.parse(await browser.elementByCss('#props').text()).count + '', - '2' - ) - expect(await browser.eval(() => window.beforeChange)).toBe('hi') - page.restore() - - await check( - async () => - JSON.parse(await browser.elementByCss('#props').text()).count + '', - '1' - ) - }) - - it('should show indicator when re-fetching data', async () => { - const browser = await webdriver(appPort, '/gsp-blog/second') - await installCheckVisible(browser) - await browser.eval(() => (window.beforeChange = 'hi')) - - const props = JSON.parse(await browser.elementByCss('#props').text()) - expect(props.count).toBe(1) - - const page = new File(join(appDir, 'pages/gsp-blog/[post].js')) - page.replace('count = 1', 'count = 2') - - await check( - async () => - JSON.parse(await browser.elementByCss('#props').text()).count + '', - '2' - ) - expect(await browser.eval(() => window.beforeChange)).toBe('hi') - expect(await browser.eval(() => window.showedBuilder)).toBe(true) - page.restore() - - await check( - async () => - JSON.parse(await browser.elementByCss('#props').text()).count + '', - '1' - ) - }) - - it('should update page when getStaticPaths is changed only', async () => { - const browser = await webdriver(appPort, '/gsp-blog/first') - await browser.eval(() => (window.beforeChange = 'hi')) - - const props = JSON.parse(await browser.elementByCss('#props').text()) - expect(props.count).toBe(1) - - const page = new File(join(appDir, 'pages/gsp-blog/[post].js')) - page.replace('paths = 1', 'paths = 2') - - expect(await browser.eval('window.beforeChange')).toBe('hi') - page.restore() - }) - - it('should update page when getStaticProps is changed only for /index', async () => { - const browser = await webdriver(appPort, '/') - await browser.eval(() => (window.beforeChange = 'hi')) - - const props = JSON.parse(await browser.elementByCss('#props').text()) - expect(props.count).toBe(1) - - const page = new File(join(appDir, 'pages/index.js')) - page.replace('count = 1', 'count = 2') - - expect(await browser.eval('window.beforeChange')).toBe('hi') - page.restore() - }) - - it('should update page when getStaticProps is changed only for /another/index', async () => { - const browser = await webdriver(appPort, '/another') - await browser.eval(() => (window.beforeChange = 'hi')) - - const props = JSON.parse(await browser.elementByCss('#props').text()) - expect(props.count).toBe(1) - - const page = new File(join(appDir, 'pages/another/index.js')) - page.replace('count = 1', 'count = 2') - - expect(await browser.eval('window.beforeChange')).toBe('hi') - page.restore() - }) - - it('should not reload page when client-side is changed too GSSP', async () => { - const browser = await webdriver(appPort, '/gssp-blog/first') - await check(() => browser.elementByCss('#change').text(), 'change me') - await browser.eval(() => (window.beforeChange = 'hi')) - - const props = JSON.parse(await browser.elementByCss('#props').text()) - - const page = new File(join(appDir, 'pages/gssp-blog/[post].js')) - page.replace('change me', 'changed') - - await check(() => browser.elementByCss('#change').text(), 'changed') - expect(await browser.eval(() => window.beforeChange)).toBe('hi') - - const props2 = JSON.parse(await browser.elementByCss('#props').text()) - expect(props).toEqual(props2) - - page.restore() - - await check(() => browser.elementByCss('#change').text(), 'change me') - }) - - it('should update page when getServerSideProps is changed only', async () => { - const browser = await webdriver(appPort, '/gssp-blog/first') - await check( - async () => - JSON.parse(await browser.elementByCss('#props').text()).count + '', - '1' - ) - await browser.eval(() => (window.beforeChange = 'hi')) - - const props = JSON.parse(await browser.elementByCss('#props').text()) - expect(props.count).toBe(1) - - const page = new File(join(appDir, 'pages/gssp-blog/[post].js')) - page.replace('count = 1', 'count = 2') - - await check( - async () => - JSON.parse(await browser.elementByCss('#props').text()).count + '', - '2' - ) - expect(await browser.eval(() => window.beforeChange)).toBe('hi') - page.restore() - - await check( - async () => - JSON.parse(await browser.elementByCss('#props').text()).count + '', - '1' - ) - }) -}) From 2264d35b647461d78d6f64157eec8667a24f76fb Mon Sep 17 00:00:00 2001 From: Steven Date: Wed, 16 Feb 2022 18:48:17 -0500 Subject: [PATCH 12/28] Fix `.svg` image optimization with a `loader` prop (#34452) * Fix .svg image optimization with a `loader` prop * Add test * Revert test back to 266x266 --- packages/next/client/image.tsx | 12 +++++---- .../default/pages/loader-svg.js | 22 ++++++++++++++++ .../default/test/index.test.js | 26 ++++++++++++++++++- 3 files changed, 54 insertions(+), 6 deletions(-) create mode 100644 test/integration/image-component/default/pages/loader-svg.js diff --git a/packages/next/client/image.tsx b/packages/next/client/image.tsx index 164e75952635..2bc5f9ed8cb1 100644 --- a/packages/next/client/image.tsx +++ b/packages/next/client/image.tsx @@ -385,10 +385,6 @@ export default function Image({ isLazy = false } - if (src.endsWith('.svg') && !config.dangerouslyAllowSVG) { - unoptimized = true - } - if (process.env.NODE_ENV !== 'production') { if (!src) { throw new Error( @@ -466,7 +462,7 @@ export default function Image({ ) } - if (!unoptimized) { + if (!unoptimized && loader !== defaultImageLoader) { const urlStr = loader({ config, src, @@ -866,6 +862,12 @@ function defaultLoader({ } } + if (src.endsWith('.svg') && !config.dangerouslyAllowSVG) { + // Special case to make svg serve as-is to avoid proxying + // through the built-in Image Optimization API. + return src + } + return `${config.path}?url=${encodeURIComponent(src)}&w=${width}&q=${ quality || 75 }` diff --git a/test/integration/image-component/default/pages/loader-svg.js b/test/integration/image-component/default/pages/loader-svg.js new file mode 100644 index 000000000000..2390b5936ce0 --- /dev/null +++ b/test/integration/image-component/default/pages/loader-svg.js @@ -0,0 +1,22 @@ +import React from 'react' +import Image from 'next/image' + +const Page = () => { + return ( +
+

Should work with SVG

+ `${src}?size=${width}`} + /> +
+ +
footer
+
+ ) +} + +export default Page diff --git a/test/integration/image-component/default/test/index.test.js b/test/integration/image-component/default/test/index.test.js index 0f390ed540d0..7d406b9e6335 100644 --- a/test/integration/image-component/default/test/index.test.js +++ b/test/integration/image-component/default/test/index.test.js @@ -224,7 +224,7 @@ function runTests(mode) { ) await check( () => browser.eval(`document.getElementById("msg3").textContent`), - 'loaded 1 img3 with dimensions 400x400' + 'loaded 1 img3 with dimensions 266x266' ) await check( () => browser.eval(`document.getElementById("msg4").textContent`), @@ -785,6 +785,30 @@ function runTests(mode) { /Image with src (.*)gif(.*) has "sizes" property but it will be ignored/gm ) }) + + it('should not warn when svg, even if with loader prop or without', async () => { + const browser = await webdriver(appPort, '/loader-svg') + await browser.eval(`document.querySelector("footer").scrollIntoView()`) + const warnings = (await browser.log('browser')) + .map((log) => log.message) + .join('\n') + expect(await hasRedbox(browser)).toBe(false) + expect(warnings).not.toMatch( + /Image with src (.*) has a "loader" property that does not implement width/gm + ) + expect(await browser.elementById('with-loader').getAttribute('src')).toBe( + '/test.svg?size=256' + ) + expect( + await browser.elementById('with-loader').getAttribute('srcset') + ).toBe('/test.svg?size=128 1x, /test.svg?size=256 2x') + expect( + await browser.elementById('without-loader').getAttribute('src') + ).toBe('/test.svg') + expect( + await browser.elementById('without-loader').getAttribute('srcset') + ).toBe('/test.svg 1x, /test.svg 2x') + }) } else { //server-only tests it('should not create an image folder in server/chunks', async () => { From 49da8c016cabd5c5b9703d66294db4be2dbce926 Mon Sep 17 00:00:00 2001 From: JJ Kasper Date: Wed, 16 Feb 2022 17:53:02 -0600 Subject: [PATCH 13/28] v12.0.11-canary.20 --- lerna.json | 2 +- packages/create-next-app/package.json | 2 +- packages/eslint-config-next/package.json | 4 ++-- packages/eslint-plugin-next/package.json | 2 +- packages/next-bundle-analyzer/package.json | 2 +- packages/next-codemod/package.json | 2 +- packages/next-env/package.json | 2 +- packages/next-mdx/package.json | 2 +- packages/next-plugin-storybook/package.json | 2 +- packages/next-polyfill-module/package.json | 2 +- packages/next-polyfill-nomodule/package.json | 2 +- packages/next-swc/package.json | 2 +- packages/next/package.json | 14 +++++++------- packages/react-dev-overlay/package.json | 2 +- packages/react-refresh-utils/package.json | 2 +- 15 files changed, 22 insertions(+), 22 deletions(-) diff --git a/lerna.json b/lerna.json index 264e579f1ceb..cad2e009a268 100644 --- a/lerna.json +++ b/lerna.json @@ -16,5 +16,5 @@ "registry": "https://registry.npmjs.org/" } }, - "version": "12.0.11-canary.19" + "version": "12.0.11-canary.20" } diff --git a/packages/create-next-app/package.json b/packages/create-next-app/package.json index 733a006f64d4..72753329f694 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.0.11-canary.19", + "version": "12.0.11-canary.20", "keywords": [ "react", "next", diff --git a/packages/eslint-config-next/package.json b/packages/eslint-config-next/package.json index a205a69d1dbb..3b749a84a710 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.0.11-canary.19", + "version": "12.0.11-canary.20", "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.0.11-canary.19", + "@next/eslint-plugin-next": "12.0.11-canary.20", "@rushstack/eslint-patch": "^1.0.8", "@typescript-eslint/parser": "^5.0.0", "eslint-import-resolver-node": "^0.3.4", diff --git a/packages/eslint-plugin-next/package.json b/packages/eslint-plugin-next/package.json index ffffceed908f..fe8744534d70 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.0.11-canary.19", + "version": "12.0.11-canary.20", "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 a9b47cce2357..90a36ab1b142 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.0.11-canary.19", + "version": "12.0.11-canary.20", "main": "index.js", "license": "MIT", "repository": { diff --git a/packages/next-codemod/package.json b/packages/next-codemod/package.json index 69f60ffbc735..9bd41a6a22be 100644 --- a/packages/next-codemod/package.json +++ b/packages/next-codemod/package.json @@ -1,6 +1,6 @@ { "name": "@next/codemod", - "version": "12.0.11-canary.19", + "version": "12.0.11-canary.20", "license": "MIT", "dependencies": { "chalk": "4.1.0", diff --git a/packages/next-env/package.json b/packages/next-env/package.json index 668b3b55ca10..af4a155b0907 100644 --- a/packages/next-env/package.json +++ b/packages/next-env/package.json @@ -1,6 +1,6 @@ { "name": "@next/env", - "version": "12.0.11-canary.19", + "version": "12.0.11-canary.20", "keywords": [ "react", "next", diff --git a/packages/next-mdx/package.json b/packages/next-mdx/package.json index c212d22d2285..2d8427c62e7b 100644 --- a/packages/next-mdx/package.json +++ b/packages/next-mdx/package.json @@ -1,6 +1,6 @@ { "name": "@next/mdx", - "version": "12.0.11-canary.19", + "version": "12.0.11-canary.20", "main": "index.js", "license": "MIT", "repository": { diff --git a/packages/next-plugin-storybook/package.json b/packages/next-plugin-storybook/package.json index e453865d80f5..a7cede6b1e3a 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.0.11-canary.19", + "version": "12.0.11-canary.20", "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 347d0c5c2b9e..c68c31db6d64 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.0.11-canary.19", + "version": "12.0.11-canary.20", "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 560516330119..ee82daff0b72 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.0.11-canary.19", + "version": "12.0.11-canary.20", "description": "A polyfill for non-dead, nomodule browsers.", "main": "dist/polyfill-nomodule.js", "license": "MIT", diff --git a/packages/next-swc/package.json b/packages/next-swc/package.json index 833ddaa02637..69368143ee37 100644 --- a/packages/next-swc/package.json +++ b/packages/next-swc/package.json @@ -1,6 +1,6 @@ { "name": "@next/swc", - "version": "12.0.11-canary.19", + "version": "12.0.11-canary.20", "private": true, "scripts": { "build-native": "napi build --platform --cargo-name next_swc_napi native", diff --git a/packages/next/package.json b/packages/next/package.json index 76975ad6b00e..e22442f6fdef 100644 --- a/packages/next/package.json +++ b/packages/next/package.json @@ -1,6 +1,6 @@ { "name": "next", - "version": "12.0.11-canary.19", + "version": "12.0.11-canary.20", "description": "The React Framework", "main": "./dist/server/next.js", "license": "MIT", @@ -69,7 +69,7 @@ ] }, "dependencies": { - "@next/env": "12.0.11-canary.19", + "@next/env": "12.0.11-canary.20", "caniuse-lite": "^1.0.30001283", "postcss": "8.4.5", "styled-jsx": "5.0.0", @@ -117,11 +117,11 @@ "@hapi/accept": "5.0.2", "@napi-rs/cli": "1.2.1", "@napi-rs/triples": "1.0.3", - "@next/polyfill-module": "12.0.11-canary.19", - "@next/polyfill-nomodule": "12.0.11-canary.19", - "@next/react-dev-overlay": "12.0.11-canary.19", - "@next/react-refresh-utils": "12.0.11-canary.19", - "@next/swc": "12.0.11-canary.19", + "@next/polyfill-module": "12.0.11-canary.20", + "@next/polyfill-nomodule": "12.0.11-canary.20", + "@next/react-dev-overlay": "12.0.11-canary.20", + "@next/react-refresh-utils": "12.0.11-canary.20", + "@next/swc": "12.0.11-canary.20", "@peculiar/webcrypto": "1.1.7", "@taskr/clear": "1.1.0", "@taskr/esnext": "1.1.0", diff --git a/packages/react-dev-overlay/package.json b/packages/react-dev-overlay/package.json index df902443fcd6..23c18f4deec4 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.0.11-canary.19", + "version": "12.0.11-canary.20", "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 dbeed5ed378c..b2119e795818 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.0.11-canary.19", + "version": "12.0.11-canary.20", "description": "An experimental package providing utilities for React Refresh.", "repository": { "url": "vercel/next.js", From f3c3810addff3cf19d66f2cbb4b6ddb61d241aa1 Mon Sep 17 00:00:00 2001 From: Lee Robinson Date: Wed, 16 Feb 2022 19:07:44 -0700 Subject: [PATCH 14/28] Remove hello world RSC example. (#34456) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Let's wait until we have a better place for folks to start that doesn't include my hacky code 😄 For now, we can continue to point to the demos we have previously created, which have the proper guidance and caveats. Replaces https://github.com/vercel/next.js/pull/34301. --- docs/advanced-features/react-18/server-components.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/advanced-features/react-18/server-components.md b/docs/advanced-features/react-18/server-components.md index 940c0c4755fb..dea51f2014fd 100644 --- a/docs/advanced-features/react-18/server-components.md +++ b/docs/advanced-features/react-18/server-components.md @@ -59,7 +59,7 @@ The `` and `` components will always be server-side rendered and > Make sure you're using default imports and exports for server components (`.server.js`). The support of named exports are a work in progress! -To see a full example, check out the [hello world example](https://github.com/vercel/next.js/tree/canary/examples/react-server-components) or the larger [vercel/next-rsc-demo demo](https://github.com/vercel/next-rsc-demo). +To see a full example, check out the [vercel/next-react-server-components demo](https://github.com/vercel/next-react-server-components). ## Supported Next.js APIs From ba78437cfff866c02468b6b180f8ea72979ef76e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bal=C3=A1zs=20Orb=C3=A1n?= Date: Thu, 17 Feb 2022 05:04:29 +0100 Subject: [PATCH 15/28] fix: don't wrap `profile` in firebase example (#34457) --- examples/with-firebase/pages/index.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/examples/with-firebase/pages/index.js b/examples/with-firebase/pages/index.js index bfef773741ee..4993df640231 100644 --- a/examples/with-firebase/pages/index.js +++ b/examples/with-firebase/pages/index.js @@ -20,9 +20,7 @@ export default function Home() { const createUser = async () => { const db = getFirestore() - await setDoc(doc(db, 'profile', profile.username), { - profile, - }) + await setDoc(doc(db, 'profile', profile.username), profile) alert('User created!!') } From 1edd8519d6626ac3972244253a14933185c76a33 Mon Sep 17 00:00:00 2001 From: Gal Schlezinger Date: Thu, 17 Feb 2022 13:32:36 +0200 Subject: [PATCH 16/28] Allow reading request bodies in middlewares (#34294) Related: - resolves #30953 --- packages/next/server/base-http/node.ts | 16 +- packages/next/server/body-streams.ts | 87 +++++++++++ packages/next/server/next-server.ts | 16 +- packages/next/server/web/adapter.ts | 1 + packages/next/server/web/types.ts | 1 + .../index.test.ts | 144 ++++++++++++++++++ yarn.lock | 3 +- 7 files changed, 256 insertions(+), 12 deletions(-) create mode 100644 packages/next/server/body-streams.ts create mode 100644 test/production/reading-request-body-in-middleware/index.test.ts diff --git a/packages/next/server/base-http/node.ts b/packages/next/server/base-http/node.ts index 5d5c54ce0064..d1d720d80989 100644 --- a/packages/next/server/base-http/node.ts +++ b/packages/next/server/base-http/node.ts @@ -7,6 +7,11 @@ import { NEXT_REQUEST_META, RequestMeta } from '../request-meta' import { BaseNextRequest, BaseNextResponse } from './index' +type Req = IncomingMessage & { + [NEXT_REQUEST_META]?: RequestMeta + cookies?: NextApiRequestCookies +} + export class NodeNextRequest extends BaseNextRequest { public headers = this._req.headers; @@ -21,12 +26,11 @@ export class NodeNextRequest extends BaseNextRequest { return this._req } - constructor( - private _req: IncomingMessage & { - [NEXT_REQUEST_META]?: RequestMeta - cookies?: NextApiRequestCookies - } - ) { + set originalRequest(value: Req) { + this._req = value + } + + constructor(private _req: Req) { super(_req.method!.toUpperCase(), _req.url!, _req) } diff --git a/packages/next/server/body-streams.ts b/packages/next/server/body-streams.ts new file mode 100644 index 000000000000..5ce9a0b3abde --- /dev/null +++ b/packages/next/server/body-streams.ts @@ -0,0 +1,87 @@ +import type { IncomingMessage } from 'http' +import { Readable } from 'stream' +import { TransformStream } from 'next/dist/compiled/web-streams-polyfill' + +type BodyStream = ReadableStream + +/** + * Creates a ReadableStream from a Node.js HTTP request + */ +function requestToBodyStream(request: IncomingMessage): BodyStream { + const transform = new TransformStream({ + start(controller) { + request.on('data', (chunk) => controller.enqueue(chunk)) + request.on('end', () => controller.terminate()) + request.on('error', (err) => controller.error(err)) + }, + }) + + return transform.readable as unknown as ReadableStream +} + +function bodyStreamToNodeStream(bodyStream: BodyStream): Readable { + const reader = bodyStream.getReader() + return Readable.from( + (async function* () { + while (true) { + const { done, value } = await reader.read() + if (done) { + return + } + yield value + } + })() + ) +} + +function replaceRequestBody( + base: T, + stream: Readable +): T { + for (const key in stream) { + let v = stream[key as keyof Readable] as any + if (typeof v === 'function') { + v = v.bind(stream) + } + base[key as keyof T] = v + } + + return base +} + +/** + * An interface that encapsulates body stream cloning + * of an incoming request. + */ +export function clonableBodyForRequest( + incomingMessage: T +) { + let bufferedBodyStream: BodyStream | null = null + + return { + /** + * Replaces the original request body if necessary. + * This is done because once we read the body from the original request, + * we can't read it again. + */ + finalize(): void { + if (bufferedBodyStream) { + replaceRequestBody( + incomingMessage, + bodyStreamToNodeStream(bufferedBodyStream) + ) + } + }, + /** + * Clones the body stream + * to pass into a middleware + */ + cloneBodyStream(): BodyStream { + const originalStream = + bufferedBodyStream ?? requestToBodyStream(incomingMessage) + const [stream1, stream2] = originalStream.tee() + bufferedBodyStream = stream1 + return stream2 + }, + } +} diff --git a/packages/next/server/next-server.ts b/packages/next/server/next-server.ts index 4c7779481822..b3c1dcb5cb9e 100644 --- a/packages/next/server/next-server.ts +++ b/packages/next/server/next-server.ts @@ -38,7 +38,7 @@ import { PagesManifest } from '../build/webpack/plugins/pages-manifest-plugin' import { recursiveReadDirSync } from './lib/recursive-readdir-sync' import { format as formatUrl, UrlWithParsedQuery } from 'url' import compression from 'next/dist/compiled/compression' -import Proxy from 'next/dist/compiled/http-proxy' +import HttpProxy from 'next/dist/compiled/http-proxy' import { route } from './router' import { run } from './web/sandbox' @@ -73,6 +73,7 @@ import { loadEnvConfig } from '@next/env' import { getCustomRoute } from './server-route-utils' import { urlQueryToSearchParams } from '../shared/lib/router/utils/querystring' import ResponseCache from '../server/response-cache' +import { clonableBodyForRequest } from './body-streams' export * from './base-server' @@ -485,7 +486,7 @@ export default class NextNodeServer extends BaseServer { parsedUrl.search = stringifyQuery(req, query) const target = formatUrl(parsedUrl) - const proxy = new Proxy({ + const proxy = new HttpProxy({ target, changeOrigin: true, ignorePath: true, @@ -1236,6 +1237,11 @@ export default class NextNodeServer extends BaseServer { const allHeaders = new Headers() let result: FetchEventResult | null = null + const method = (params.request.method || 'GET').toUpperCase() + let originalBody = + method !== 'GET' && method !== 'HEAD' + ? clonableBodyForRequest(params.request.body) + : undefined for (const middleware of this.middleware || []) { if (middleware.match(params.parsedUrl.pathname)) { @@ -1245,7 +1251,6 @@ export default class NextNodeServer extends BaseServer { } await this.ensureMiddleware(middleware.page, middleware.ssr) - const middlewareInfo = this.getMiddlewareInfo(middleware.page) result = await run({ @@ -1254,7 +1259,7 @@ export default class NextNodeServer extends BaseServer { env: middlewareInfo.env, request: { headers: params.request.headers, - method: params.request.method || 'GET', + method, nextConfig: { basePath: this.nextConfig.basePath, i18n: this.nextConfig.i18n, @@ -1262,6 +1267,7 @@ export default class NextNodeServer extends BaseServer { }, url: url, page: page, + body: originalBody?.cloneBodyStream(), }, useCache: !this.nextConfig.experimental.runtime, onWarning: (warning: Error) => { @@ -1298,6 +1304,8 @@ export default class NextNodeServer extends BaseServer { } } + originalBody?.finalize() + return result } diff --git a/packages/next/server/web/adapter.ts b/packages/next/server/web/adapter.ts index ff7f3559453c..6252ea738f5c 100644 --- a/packages/next/server/web/adapter.ts +++ b/packages/next/server/web/adapter.ts @@ -16,6 +16,7 @@ export async function adapter(params: { page: params.page, input: params.request.url, init: { + body: params.request.body, geo: params.request.geo, headers: fromNodeHeaders(params.request.headers), ip: params.request.ip, diff --git a/packages/next/server/web/types.ts b/packages/next/server/web/types.ts index 5a6d48fde63b..029a1024d462 100644 --- a/packages/next/server/web/types.ts +++ b/packages/next/server/web/types.ts @@ -39,6 +39,7 @@ export interface RequestData { params?: { [key: string]: string } } url: string + body?: ReadableStream } export interface FetchEventResult { diff --git a/test/production/reading-request-body-in-middleware/index.test.ts b/test/production/reading-request-body-in-middleware/index.test.ts new file mode 100644 index 000000000000..0f1d61ccfa92 --- /dev/null +++ b/test/production/reading-request-body-in-middleware/index.test.ts @@ -0,0 +1,144 @@ +import { createNext } from 'e2e-utils' +import { NextInstance } from 'test/lib/next-modes/base' +import { fetchViaHTTP } from 'next-test-utils' + +describe('reading request body in middleware', () => { + let next: NextInstance + + beforeAll(async () => { + next = await createNext({ + files: { + 'pages/_middleware.js': ` + const { NextResponse } = require('next/server'); + + export default async function middleware(request) { + if (!request.body) { + return new Response('No body', { status: 400 }); + } + + const json = await request.json(); + + if (request.nextUrl.searchParams.has("next")) { + const res = NextResponse.next(); + res.headers.set('x-from-root-middleware', '1'); + return res; + } + + return new Response(JSON.stringify({ + root: true, + ...json, + }), { + status: 200, + headers: { + 'content-type': 'application/json', + }, + }) + } + `, + + 'pages/nested/_middleware.js': ` + const { NextResponse } = require('next/server'); + + export default async function middleware(request) { + if (!request.body) { + return new Response('No body', { status: 400 }); + } + + const json = await request.json(); + + return new Response(JSON.stringify({ + root: false, + ...json, + }), { + status: 200, + headers: { + 'content-type': 'application/json', + }, + }) + } + `, + + 'pages/api/hi.js': ` + export default function hi(req, res) { + res.json({ + ...req.body, + api: true, + }) + } + `, + }, + dependencies: {}, + }) + }) + afterAll(() => next.destroy()) + + it('rejects with 400 for get requests', async () => { + const response = await fetchViaHTTP(next.url, '/') + expect(response.status).toEqual(400) + }) + + it('returns root: true for root calls', async () => { + const response = await fetchViaHTTP( + next.url, + '/', + {}, + { + method: 'POST', + body: JSON.stringify({ + foo: 'bar', + }), + } + ) + expect(response.status).toEqual(200) + expect(await response.json()).toEqual({ + foo: 'bar', + root: true, + }) + }) + + it('reads the same body on both middlewares', async () => { + const response = await fetchViaHTTP( + next.url, + '/nested/hello', + { + next: '1', + }, + { + method: 'POST', + body: JSON.stringify({ + foo: 'bar', + }), + } + ) + expect(response.status).toEqual(200) + expect(await response.json()).toEqual({ + foo: 'bar', + root: false, + }) + }) + + it('passes the body to the api endpoint', async () => { + const response = await fetchViaHTTP( + next.url, + '/api/hi', + { + next: '1', + }, + { + method: 'POST', + headers: { + 'content-type': 'application/json', + }, + body: JSON.stringify({ + foo: 'bar', + }), + } + ) + expect(response.status).toEqual(200) + expect(await response.json()).toEqual({ + foo: 'bar', + api: true, + }) + expect(response.headers.get('x-from-root-middleware')).toEqual('1') + }) +}) diff --git a/yarn.lock b/yarn.lock index af18b6562100..a5d0874165bc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -20812,8 +20812,7 @@ webpack-bundle-analyzer@4.3.0: source-list-map "^2.0.0" source-map "~0.6.1" -"webpack-sources3@npm:webpack-sources@3.2.3", webpack-sources@^3.2.3: - name webpack-sources3 +"webpack-sources3@npm:webpack-sources@3.2.3", webpack-sources@^3.2.2, webpack-sources@^3.2.3: version "3.2.3" resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-3.2.3.tgz#2d4daab8451fd4b240cc27055ff6a0c2ccea0cde" integrity sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w== From 42020114d2ada652ed9651675ad62791743e432f Mon Sep 17 00:00:00 2001 From: JJ Kasper Date: Thu, 17 Feb 2022 08:39:45 -0600 Subject: [PATCH 17/28] Update font-optimization test snapshot (#34478) --- .../fixtures/with-typekit/manifest-snapshot.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/integration/font-optimization/fixtures/with-typekit/manifest-snapshot.json b/test/integration/font-optimization/fixtures/with-typekit/manifest-snapshot.json index cb517f819778..b7d9fb5eb6d5 100644 --- a/test/integration/font-optimization/fixtures/with-typekit/manifest-snapshot.json +++ b/test/integration/font-optimization/fixtures/with-typekit/manifest-snapshot.json @@ -1,14 +1,14 @@ [ { "url": "https://use.typekit.net/plm1izr.css", - "content": "@import url(\"https://p.typekit.net/p.css?s=1&k=plm1izr&ht=tk&f=32266&a=23152309&app=typekit&e=css\");@font-face{font-family:\"birra-2\";src:url(\"https://use.typekit.net/af/23e0ad/00000000000000003b9b410c/27/l?primer=7cdcb44be4a7db8877ffa5c0007b8dd865b3bbc383831fe2ea177f62257a9191&fvd=n7&v=3\") format(\"woff2\"),url(\"https://use.typekit.net/af/23e0ad/00000000000000003b9b410c/27/d?primer=7cdcb44be4a7db8877ffa5c0007b8dd865b3bbc383831fe2ea177f62257a9191&fvd=n7&v=3\") format(\"woff\"),url(\"https://use.typekit.net/af/23e0ad/00000000000000003b9b410c/27/a?primer=7cdcb44be4a7db8877ffa5c0007b8dd865b3bbc383831fe2ea177f62257a9191&fvd=n7&v=3\") format(\"opentype\");font-display:auto;font-style:normal;font-weight:700}.tk-birra-2{font-family:\"birra-2\",serif}" + "content": "@import url(\"https://p.typekit.net/p.css?s=1&k=plm1izr&ht=tk&f=32266&a=23152309&app=typekit&e=css\");@font-face{font-family:\"birra-2\";src:url(\"https://use.typekit.net/af/23e0ad/00000000000000003b9b410c/27/l?primer=7cdcb44be4a7db8877ffa5c0007b8dd865b3bbc383831fe2ea177f62257a9191&fvd=n7&v=3\") format(\"woff2\"),url(\"https://use.typekit.net/af/23e0ad/00000000000000003b9b410c/27/d?primer=7cdcb44be4a7db8877ffa5c0007b8dd865b3bbc383831fe2ea177f62257a9191&fvd=n7&v=3\") format(\"woff\"),url(\"https://use.typekit.net/af/23e0ad/00000000000000003b9b410c/27/a?primer=7cdcb44be4a7db8877ffa5c0007b8dd865b3bbc383831fe2ea177f62257a9191&fvd=n7&v=3\") format(\"opentype\");font-display:auto;font-style:normal;font-weight:700;font-stretch:normal}.tk-birra-2{font-family:\"birra-2\",serif}" }, { "url": "https://use.typekit.net/ucs7mcf.css", - "content": "@import url(\"https://p.typekit.net/p.css?s=1&k=ucs7mcf&ht=tk&f=43886&a=23152309&app=typekit&e=css\");@font-face{font-family:\"flegrei\";src:url(\"https://use.typekit.net/af/74a5d1/00000000000000003b9b3d6e/27/l?primer=7cdcb44be4a7db8877ffa5c0007b8dd865b3bbc383831fe2ea177f62257a9191&fvd=n4&v=3\") format(\"woff2\"),url(\"https://use.typekit.net/af/74a5d1/00000000000000003b9b3d6e/27/d?primer=7cdcb44be4a7db8877ffa5c0007b8dd865b3bbc383831fe2ea177f62257a9191&fvd=n4&v=3\") format(\"woff\"),url(\"https://use.typekit.net/af/74a5d1/00000000000000003b9b3d6e/27/a?primer=7cdcb44be4a7db8877ffa5c0007b8dd865b3bbc383831fe2ea177f62257a9191&fvd=n4&v=3\") format(\"opentype\");font-display:auto;font-style:normal;font-weight:400}.tk-flegrei{font-family:\"flegrei\",sans-serif}" + "content": "@import url(\"https://p.typekit.net/p.css?s=1&k=ucs7mcf&ht=tk&f=43886&a=23152309&app=typekit&e=css\");@font-face{font-family:\"flegrei\";src:url(\"https://use.typekit.net/af/74a5d1/00000000000000003b9b3d6e/27/l?primer=7cdcb44be4a7db8877ffa5c0007b8dd865b3bbc383831fe2ea177f62257a9191&fvd=n4&v=3\") format(\"woff2\"),url(\"https://use.typekit.net/af/74a5d1/00000000000000003b9b3d6e/27/d?primer=7cdcb44be4a7db8877ffa5c0007b8dd865b3bbc383831fe2ea177f62257a9191&fvd=n4&v=3\") format(\"woff\"),url(\"https://use.typekit.net/af/74a5d1/00000000000000003b9b3d6e/27/a?primer=7cdcb44be4a7db8877ffa5c0007b8dd865b3bbc383831fe2ea177f62257a9191&fvd=n4&v=3\") format(\"opentype\");font-display:auto;font-style:normal;font-weight:400;font-stretch:normal}.tk-flegrei{font-family:\"flegrei\",sans-serif}" }, { "url": "https://use.typekit.net/erd0sed.css", - "content": "@import url(\"https://p.typekit.net/p.css?s=1&k=erd0sed&ht=tk&f=43885&a=23152309&app=typekit&e=css\");@font-face{font-family:\"pantelleria\";src:url(\"https://use.typekit.net/af/1f141c/00000000000000003b9b3d6f/27/l?primer=7cdcb44be4a7db8877ffa5c0007b8dd865b3bbc383831fe2ea177f62257a9191&fvd=n4&v=3\") format(\"woff2\"),url(\"https://use.typekit.net/af/1f141c/00000000000000003b9b3d6f/27/d?primer=7cdcb44be4a7db8877ffa5c0007b8dd865b3bbc383831fe2ea177f62257a9191&fvd=n4&v=3\") format(\"woff\"),url(\"https://use.typekit.net/af/1f141c/00000000000000003b9b3d6f/27/a?primer=7cdcb44be4a7db8877ffa5c0007b8dd865b3bbc383831fe2ea177f62257a9191&fvd=n4&v=3\") format(\"opentype\");font-display:auto;font-style:normal;font-weight:400}.tk-pantelleria{font-family:\"pantelleria\",sans-serif}" + "content": "@import url(\"https://p.typekit.net/p.css?s=1&k=erd0sed&ht=tk&f=43885&a=23152309&app=typekit&e=css\");@font-face{font-family:\"pantelleria\";src:url(\"https://use.typekit.net/af/1f141c/00000000000000003b9b3d6f/27/l?primer=7cdcb44be4a7db8877ffa5c0007b8dd865b3bbc383831fe2ea177f62257a9191&fvd=n4&v=3\") format(\"woff2\"),url(\"https://use.typekit.net/af/1f141c/00000000000000003b9b3d6f/27/d?primer=7cdcb44be4a7db8877ffa5c0007b8dd865b3bbc383831fe2ea177f62257a9191&fvd=n4&v=3\") format(\"woff\"),url(\"https://use.typekit.net/af/1f141c/00000000000000003b9b3d6f/27/a?primer=7cdcb44be4a7db8877ffa5c0007b8dd865b3bbc383831fe2ea177f62257a9191&fvd=n4&v=3\") format(\"opentype\");font-display:auto;font-style:normal;font-weight:400;font-stretch:normal}.tk-pantelleria{font-family:\"pantelleria\",sans-serif}" } ] From b70397e770a0badfbafe9e2db8cb8bfeb1b06f9e Mon Sep 17 00:00:00 2001 From: JJ Kasper Date: Thu, 17 Feb 2022 08:45:31 -0600 Subject: [PATCH 18/28] Revert "Allow reading request bodies in middlewares (#34294)" (#34479) This reverts commit 1edd8519d6626ac3972244253a14933185c76a33. --- packages/next/server/base-http/node.ts | 16 +- packages/next/server/body-streams.ts | 87 ----------- packages/next/server/next-server.ts | 16 +- packages/next/server/web/adapter.ts | 1 - packages/next/server/web/types.ts | 1 - .../index.test.ts | 144 ------------------ yarn.lock | 3 +- 7 files changed, 12 insertions(+), 256 deletions(-) delete mode 100644 packages/next/server/body-streams.ts delete mode 100644 test/production/reading-request-body-in-middleware/index.test.ts diff --git a/packages/next/server/base-http/node.ts b/packages/next/server/base-http/node.ts index d1d720d80989..5d5c54ce0064 100644 --- a/packages/next/server/base-http/node.ts +++ b/packages/next/server/base-http/node.ts @@ -7,11 +7,6 @@ import { NEXT_REQUEST_META, RequestMeta } from '../request-meta' import { BaseNextRequest, BaseNextResponse } from './index' -type Req = IncomingMessage & { - [NEXT_REQUEST_META]?: RequestMeta - cookies?: NextApiRequestCookies -} - export class NodeNextRequest extends BaseNextRequest { public headers = this._req.headers; @@ -26,11 +21,12 @@ export class NodeNextRequest extends BaseNextRequest { return this._req } - set originalRequest(value: Req) { - this._req = value - } - - constructor(private _req: Req) { + constructor( + private _req: IncomingMessage & { + [NEXT_REQUEST_META]?: RequestMeta + cookies?: NextApiRequestCookies + } + ) { super(_req.method!.toUpperCase(), _req.url!, _req) } diff --git a/packages/next/server/body-streams.ts b/packages/next/server/body-streams.ts deleted file mode 100644 index 5ce9a0b3abde..000000000000 --- a/packages/next/server/body-streams.ts +++ /dev/null @@ -1,87 +0,0 @@ -import type { IncomingMessage } from 'http' -import { Readable } from 'stream' -import { TransformStream } from 'next/dist/compiled/web-streams-polyfill' - -type BodyStream = ReadableStream - -/** - * Creates a ReadableStream from a Node.js HTTP request - */ -function requestToBodyStream(request: IncomingMessage): BodyStream { - const transform = new TransformStream({ - start(controller) { - request.on('data', (chunk) => controller.enqueue(chunk)) - request.on('end', () => controller.terminate()) - request.on('error', (err) => controller.error(err)) - }, - }) - - return transform.readable as unknown as ReadableStream -} - -function bodyStreamToNodeStream(bodyStream: BodyStream): Readable { - const reader = bodyStream.getReader() - return Readable.from( - (async function* () { - while (true) { - const { done, value } = await reader.read() - if (done) { - return - } - yield value - } - })() - ) -} - -function replaceRequestBody( - base: T, - stream: Readable -): T { - for (const key in stream) { - let v = stream[key as keyof Readable] as any - if (typeof v === 'function') { - v = v.bind(stream) - } - base[key as keyof T] = v - } - - return base -} - -/** - * An interface that encapsulates body stream cloning - * of an incoming request. - */ -export function clonableBodyForRequest( - incomingMessage: T -) { - let bufferedBodyStream: BodyStream | null = null - - return { - /** - * Replaces the original request body if necessary. - * This is done because once we read the body from the original request, - * we can't read it again. - */ - finalize(): void { - if (bufferedBodyStream) { - replaceRequestBody( - incomingMessage, - bodyStreamToNodeStream(bufferedBodyStream) - ) - } - }, - /** - * Clones the body stream - * to pass into a middleware - */ - cloneBodyStream(): BodyStream { - const originalStream = - bufferedBodyStream ?? requestToBodyStream(incomingMessage) - const [stream1, stream2] = originalStream.tee() - bufferedBodyStream = stream1 - return stream2 - }, - } -} diff --git a/packages/next/server/next-server.ts b/packages/next/server/next-server.ts index b3c1dcb5cb9e..4c7779481822 100644 --- a/packages/next/server/next-server.ts +++ b/packages/next/server/next-server.ts @@ -38,7 +38,7 @@ import { PagesManifest } from '../build/webpack/plugins/pages-manifest-plugin' import { recursiveReadDirSync } from './lib/recursive-readdir-sync' import { format as formatUrl, UrlWithParsedQuery } from 'url' import compression from 'next/dist/compiled/compression' -import HttpProxy from 'next/dist/compiled/http-proxy' +import Proxy from 'next/dist/compiled/http-proxy' import { route } from './router' import { run } from './web/sandbox' @@ -73,7 +73,6 @@ import { loadEnvConfig } from '@next/env' import { getCustomRoute } from './server-route-utils' import { urlQueryToSearchParams } from '../shared/lib/router/utils/querystring' import ResponseCache from '../server/response-cache' -import { clonableBodyForRequest } from './body-streams' export * from './base-server' @@ -486,7 +485,7 @@ export default class NextNodeServer extends BaseServer { parsedUrl.search = stringifyQuery(req, query) const target = formatUrl(parsedUrl) - const proxy = new HttpProxy({ + const proxy = new Proxy({ target, changeOrigin: true, ignorePath: true, @@ -1237,11 +1236,6 @@ export default class NextNodeServer extends BaseServer { const allHeaders = new Headers() let result: FetchEventResult | null = null - const method = (params.request.method || 'GET').toUpperCase() - let originalBody = - method !== 'GET' && method !== 'HEAD' - ? clonableBodyForRequest(params.request.body) - : undefined for (const middleware of this.middleware || []) { if (middleware.match(params.parsedUrl.pathname)) { @@ -1251,6 +1245,7 @@ export default class NextNodeServer extends BaseServer { } await this.ensureMiddleware(middleware.page, middleware.ssr) + const middlewareInfo = this.getMiddlewareInfo(middleware.page) result = await run({ @@ -1259,7 +1254,7 @@ export default class NextNodeServer extends BaseServer { env: middlewareInfo.env, request: { headers: params.request.headers, - method, + method: params.request.method || 'GET', nextConfig: { basePath: this.nextConfig.basePath, i18n: this.nextConfig.i18n, @@ -1267,7 +1262,6 @@ export default class NextNodeServer extends BaseServer { }, url: url, page: page, - body: originalBody?.cloneBodyStream(), }, useCache: !this.nextConfig.experimental.runtime, onWarning: (warning: Error) => { @@ -1304,8 +1298,6 @@ export default class NextNodeServer extends BaseServer { } } - originalBody?.finalize() - return result } diff --git a/packages/next/server/web/adapter.ts b/packages/next/server/web/adapter.ts index 6252ea738f5c..ff7f3559453c 100644 --- a/packages/next/server/web/adapter.ts +++ b/packages/next/server/web/adapter.ts @@ -16,7 +16,6 @@ export async function adapter(params: { page: params.page, input: params.request.url, init: { - body: params.request.body, geo: params.request.geo, headers: fromNodeHeaders(params.request.headers), ip: params.request.ip, diff --git a/packages/next/server/web/types.ts b/packages/next/server/web/types.ts index 029a1024d462..5a6d48fde63b 100644 --- a/packages/next/server/web/types.ts +++ b/packages/next/server/web/types.ts @@ -39,7 +39,6 @@ export interface RequestData { params?: { [key: string]: string } } url: string - body?: ReadableStream } export interface FetchEventResult { diff --git a/test/production/reading-request-body-in-middleware/index.test.ts b/test/production/reading-request-body-in-middleware/index.test.ts deleted file mode 100644 index 0f1d61ccfa92..000000000000 --- a/test/production/reading-request-body-in-middleware/index.test.ts +++ /dev/null @@ -1,144 +0,0 @@ -import { createNext } from 'e2e-utils' -import { NextInstance } from 'test/lib/next-modes/base' -import { fetchViaHTTP } from 'next-test-utils' - -describe('reading request body in middleware', () => { - let next: NextInstance - - beforeAll(async () => { - next = await createNext({ - files: { - 'pages/_middleware.js': ` - const { NextResponse } = require('next/server'); - - export default async function middleware(request) { - if (!request.body) { - return new Response('No body', { status: 400 }); - } - - const json = await request.json(); - - if (request.nextUrl.searchParams.has("next")) { - const res = NextResponse.next(); - res.headers.set('x-from-root-middleware', '1'); - return res; - } - - return new Response(JSON.stringify({ - root: true, - ...json, - }), { - status: 200, - headers: { - 'content-type': 'application/json', - }, - }) - } - `, - - 'pages/nested/_middleware.js': ` - const { NextResponse } = require('next/server'); - - export default async function middleware(request) { - if (!request.body) { - return new Response('No body', { status: 400 }); - } - - const json = await request.json(); - - return new Response(JSON.stringify({ - root: false, - ...json, - }), { - status: 200, - headers: { - 'content-type': 'application/json', - }, - }) - } - `, - - 'pages/api/hi.js': ` - export default function hi(req, res) { - res.json({ - ...req.body, - api: true, - }) - } - `, - }, - dependencies: {}, - }) - }) - afterAll(() => next.destroy()) - - it('rejects with 400 for get requests', async () => { - const response = await fetchViaHTTP(next.url, '/') - expect(response.status).toEqual(400) - }) - - it('returns root: true for root calls', async () => { - const response = await fetchViaHTTP( - next.url, - '/', - {}, - { - method: 'POST', - body: JSON.stringify({ - foo: 'bar', - }), - } - ) - expect(response.status).toEqual(200) - expect(await response.json()).toEqual({ - foo: 'bar', - root: true, - }) - }) - - it('reads the same body on both middlewares', async () => { - const response = await fetchViaHTTP( - next.url, - '/nested/hello', - { - next: '1', - }, - { - method: 'POST', - body: JSON.stringify({ - foo: 'bar', - }), - } - ) - expect(response.status).toEqual(200) - expect(await response.json()).toEqual({ - foo: 'bar', - root: false, - }) - }) - - it('passes the body to the api endpoint', async () => { - const response = await fetchViaHTTP( - next.url, - '/api/hi', - { - next: '1', - }, - { - method: 'POST', - headers: { - 'content-type': 'application/json', - }, - body: JSON.stringify({ - foo: 'bar', - }), - } - ) - expect(response.status).toEqual(200) - expect(await response.json()).toEqual({ - foo: 'bar', - api: true, - }) - expect(response.headers.get('x-from-root-middleware')).toEqual('1') - }) -}) diff --git a/yarn.lock b/yarn.lock index a5d0874165bc..af18b6562100 100644 --- a/yarn.lock +++ b/yarn.lock @@ -20812,7 +20812,8 @@ webpack-bundle-analyzer@4.3.0: source-list-map "^2.0.0" source-map "~0.6.1" -"webpack-sources3@npm:webpack-sources@3.2.3", webpack-sources@^3.2.2, webpack-sources@^3.2.3: +"webpack-sources3@npm:webpack-sources@3.2.3", webpack-sources@^3.2.3: + name webpack-sources3 version "3.2.3" resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-3.2.3.tgz#2d4daab8451fd4b240cc27055ff6a0c2ccea0cde" integrity sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w== From 74fa4d4b93673a355d082473318562056571900f Mon Sep 17 00:00:00 2001 From: Tobias Koppers Date: Thu, 17 Feb 2022 15:59:56 +0100 Subject: [PATCH 19/28] update webpack (#34477) Co-authored-by: JJ Kasper --- packages/next/compiled/webpack/bundle5.js | 294 ++++++---------------- packages/next/package.json | 2 +- yarn.lock | 12 +- 3 files changed, 81 insertions(+), 227 deletions(-) diff --git a/packages/next/compiled/webpack/bundle5.js b/packages/next/compiled/webpack/bundle5.js index c8689dde9bdc..fd1d6d65a405 100644 --- a/packages/next/compiled/webpack/bundle5.js +++ b/packages/next/compiled/webpack/bundle5.js @@ -29716,7 +29716,7 @@ const makeSerializable = __webpack_require__(33032); /** * @typedef {Object} ContextModuleOptionsExtras - * @property {string|string[]} resource + * @property {string} resource * @property {string=} resourceQuery * @property {string=} resourceFragment * @property {TODO} resolveOptions @@ -29747,36 +29747,23 @@ class ContextModule extends Module { * @param {ContextModuleOptions} options options object */ constructor(resolveDependencies, options) { - if (!options || typeof options.resource === "string") { - const parsed = parseResource( - options ? /** @type {string} */ (options.resource) : "" - ); - const resource = parsed.path; - const resourceQuery = (options && options.resourceQuery) || parsed.query; - const resourceFragment = - (options && options.resourceFragment) || parsed.fragment; - - super("javascript/dynamic", resource); - /** @type {ContextModuleOptions} */ - this.options = { - ...options, - resource, - resourceQuery, - resourceFragment - }; - } else { - super("javascript/dynamic"); - /** @type {ContextModuleOptions} */ - this.options = { - ...options, - resource: options.resource, - resourceQuery: options.resourceQuery || "", - resourceFragment: options.resourceFragment || "" - }; - } + const parsed = parseResource(options ? options.resource : ""); + const resource = parsed.path; + const resourceQuery = (options && options.resourceQuery) || parsed.query; + const resourceFragment = + (options && options.resourceFragment) || parsed.fragment; + + super("javascript/dynamic", resource); // Info from Factory this.resolveDependencies = resolveDependencies; + /** @type {ContextModuleOptions} */ + this.options = { + ...options, + resource, + resourceQuery, + resourceFragment + }; if (options && options.resolveOptions !== undefined) { this.resolveOptions = options.resolveOptions; } @@ -29823,11 +29810,7 @@ class ContextModule extends Module { } _createIdentifier() { - let identifier = - this.context || - (typeof this.options.resource === "string" - ? this.options.resource - : this.options.resource.join("|")); + let identifier = this.context; if (this.options.resourceQuery) { identifier += `|${this.options.resourceQuery}`; } @@ -29892,16 +29875,7 @@ class ContextModule extends Module { * @returns {string} a user readable identifier of the module */ readableIdentifier(requestShortener) { - let identifier; - if (this.context) { - identifier = requestShortener.shorten(this.context) + "/"; - } else if (typeof this.options.resource === "string") { - identifier = requestShortener.shorten(this.options.resource) + "/"; - } else { - identifier = this.options.resource - .map(r => requestShortener.shorten(r) + "/") - .join(" "); - } + let identifier = requestShortener.shorten(this.context) + "/"; if (this.options.resourceQuery) { identifier += ` ${this.options.resourceQuery}`; } @@ -29951,30 +29925,11 @@ class ContextModule extends Module { * @returns {string | null} an identifier for library inclusion */ libIdent(options) { - let identifier; - - if (this.context) { - identifier = contextify( - options.context, - this.context, - options.associatedObjectForCache - ); - } else if (typeof this.options.resource === "string") { - identifier = contextify( - options.context, - this.options.resource, - options.associatedObjectForCache - ); - } else { - const arr = []; - for (const res of this.options.resource) { - arr.push( - contextify(options.context, res, options.associatedObjectForCache) - ); - } - identifier = arr.join(" "); - } - + let identifier = contextify( + options.context, + this.context, + options.associatedObjectForCache + ); if (this.layer) identifier = `(${this.layer})/${identifier}`; if (this.options.mode) { identifier += ` ${this.options.mode}`; @@ -30142,11 +30097,7 @@ class ContextModule extends Module { compilation.fileSystemInfo.createSnapshot( startTime, null, - this.context - ? [this.context] - : typeof this.options.resource === "string" - ? [this.options.resource] - : this.options.resource, + [this.context], null, SNAPSHOT_OPTIONS, (err, snapshot) => { @@ -30170,13 +30121,7 @@ class ContextModule extends Module { missingDependencies, buildDependencies ) { - if (this.context) { - contextDependencies.add(this.context); - } else if (typeof this.options.resource === "string") { - contextDependencies.add(this.options.resource); - } else { - for (const res of this.options.resource) contextDependencies.add(res); - } + contextDependencies.add(this.context); } /** @@ -30968,9 +30913,6 @@ module.exports = class ContextModuleFactory extends ModuleFactory { asyncLib.parallel( [ callback => { - const results = []; - const yield_ = obj => results.push(obj); - contextResolver.resolve( {}, context, @@ -30978,12 +30920,11 @@ module.exports = class ContextModuleFactory extends ModuleFactory { { fileDependencies, missingDependencies, - contextDependencies, - yield: yield_ + contextDependencies }, - err => { + (err, result) => { if (err) return callback(err); - callback(null, results); + callback(null, result); } ); }, @@ -31018,20 +30959,15 @@ module.exports = class ContextModuleFactory extends ModuleFactory { contextDependencies }); } - const [contextResult, loaderResult] = result; + this.hooks.afterResolve.callAsync( { addon: loadersPrefix + - loaderResult.join("!") + - (loaderResult.length > 0 ? "!" : ""), - resource: - contextResult.length > 1 - ? contextResult.map(r => r.path) - : contextResult[0].path, + result[1].join("!") + + (result[1].length > 0 ? "!" : ""), + resource: result[0], resolveDependencies: this.resolveDependencies.bind(this), - resourceQuery: contextResult[0].query, - resourceFragment: contextResult[0].fragment, ...beforeResolveResult }, (err, result) => { @@ -31088,28 +31024,26 @@ module.exports = class ContextModuleFactory extends ModuleFactory { } = options; if (!regExp || !resource) return callback(null, []); - let severalContexts = false; - const addDirectoryChecked = (ctx, directory, visited, callback) => { + const addDirectoryChecked = (directory, visited, callback) => { fs.realpath(directory, (err, realPath) => { if (err) return callback(err); if (visited.has(realPath)) return callback(null, []); let recursionStack; addDirectory( - ctx, directory, - (_, dir, callback) => { + (dir, callback) => { if (recursionStack === undefined) { recursionStack = new Set(visited); recursionStack.add(realPath); } - addDirectoryChecked(ctx, dir, recursionStack, callback); + addDirectoryChecked(dir, recursionStack, callback); }, callback ); }); }; - const addDirectory = (ctx, directory, addSubDirectory, callback) => { + const addDirectory = (directory, addSubDirectory, callback) => { fs.readdir(directory, (err, files) => { if (err) return callback(err); const processedFiles = cmf.hooks.contextModuleFiles.call( @@ -31136,15 +31070,16 @@ module.exports = class ContextModuleFactory extends ModuleFactory { if (stat.isDirectory()) { if (!recursive) return callback(); - addSubDirectory(ctx, subResource, callback); + addSubDirectory(subResource, callback); } else if ( stat.isFile() && (!include || subResource.match(include)) ) { const obj = { - context: ctx, + context: resource, request: - "." + subResource.substr(ctx.length).replace(/\\/g, "/") + "." + + subResource.substr(resource.length).replace(/\\/g, "/") }; this.hooks.alternativeRequests.callAsync( @@ -31155,11 +31090,8 @@ module.exports = class ContextModuleFactory extends ModuleFactory { alternatives = alternatives .filter(obj => regExp.test(obj.request)) .map(obj => { - const request = severalContexts - ? join(fs, obj.context, obj.request) - : obj.request; const dep = new ContextElementDependency( - request + resourceQuery + resourceFragment, + obj.request + resourceQuery + resourceFragment, obj.request, typePrefix, category, @@ -31196,38 +31128,12 @@ module.exports = class ContextModuleFactory extends ModuleFactory { }); }; - const addSubDirectory = (ctx, dir, callback) => - addDirectory(ctx, dir, addSubDirectory, callback); - - const visitResource = (resource, callback) => { - if (typeof fs.realpath === "function") { - addDirectoryChecked(resource, resource, new Set(), callback); - } else { - addDirectory(resource, resource, addSubDirectory, callback); - } - }; - - if (typeof resource === "string") { - visitResource(resource, callback); + if (typeof fs.realpath === "function") { + addDirectoryChecked(resource, new Set(), callback); } else { - severalContexts = true; - asyncLib.map(resource, visitResource, (err, result) => { - if (err) return callback(err); - - // result dependencies should have unique userRequest - // ordered by resolve result - const temp = new Set(); - const res = []; - for (let i = 0; i < result.length; i++) { - const inner = result[i]; - for (const el of inner) { - if (temp.has(el.userRequest)) continue; - res.push(el); - temp.add(el.userRequest); - } - } - callback(null, res); - }); + const addSubDirectory = (dir, callback) => + addDirectory(dir, addSubDirectory, callback); + addDirectory(resource, addSubDirectory, callback); } } }; @@ -62809,11 +62715,6 @@ class ResolverCachePlugin { fileDependencies: new LazySet(), contextDependencies: new LazySet() }; - let yieldResult; - if (typeof newResolveContext.yield === "function") { - yieldResult = []; - newResolveContext.yield = obj => yieldResult.push(obj); - } const propagate = key => { if (resolveContext[key]) { addAllToSet(resolveContext[key], newResolveContext[key]); @@ -62841,19 +62742,15 @@ class ResolverCachePlugin { snapshotOptions, (err, snapshot) => { if (err) return callback(err); - const resolveResult = result || yieldResult; if (!snapshot) { - if (resolveResult) return callback(null, resolveResult); + if (result) return callback(null, result); return callback(); } - itemCache.store( - new CacheEntry(resolveResult, snapshot), - storeErr => { - if (storeErr) return callback(storeErr); - if (resolveResult) return callback(null, resolveResult); - callback(); - } - ); + itemCache.store(new CacheEntry(result, snapshot), storeErr => { + if (storeErr) return callback(storeErr); + if (result) return callback(null, result); + callback(); + }); } ); } @@ -62863,8 +62760,6 @@ class ResolverCachePlugin { factory(type, hook) { /** @type {Map} */ const activeRequests = new Map(); - /** @type {Map} */ - const activeRequestsWithYield = new Map(); hook.tap( "ResolverCachePlugin", /** @@ -62889,63 +62784,29 @@ class ResolverCachePlugin { if (request._ResolverCachePluginCacheMiss || !fileSystemInfo) { return callback(); } - const withYield = typeof resolveContext.yield === "function"; - const identifier = `${type}${ - withYield ? "|yield" : "|default" - }${optionsIdent}${objectToString(request, !cacheWithContext)}`; - - if (withYield) { - const activeRequest = activeRequestsWithYield.get(identifier); - if (activeRequest) { - activeRequest[0].push(callback); - activeRequest[1].push(resolveContext.yield); - return; - } - } else { - const activeRequest = activeRequests.get(identifier); - if (activeRequest) { - activeRequest.push(callback); - return; - } + const identifier = `${type}${optionsIdent}${objectToString( + request, + !cacheWithContext + )}`; + const activeRequest = activeRequests.get(identifier); + if (activeRequest) { + activeRequest.push(callback); + return; } const itemCache = cache.getItemCache(identifier, null); - let callbacks, yields; - const done = withYield - ? (err, result) => { - if (callbacks === undefined) { - if (err) { - callback(err); - } else { - if (result) - for (const r of result) resolveContext.yield(r); - callback(null, null); - } - yields = undefined; - callbacks = false; - } else { - for (let i = 0; i < callbacks.length; i++) { - const cb = callbacks[i]; - const yield_ = yields[i]; - if (result) for (const r of result) yield_(r); - cb(null, null); - } - activeRequestsWithYield.delete(identifier); - yields = undefined; - callbacks = false; - } - } - : (err, result) => { - if (callbacks === undefined) { - callback(err, result); - callbacks = false; - } else { - for (const callback of callbacks) { - callback(err, result); - } - activeRequests.delete(identifier); - callbacks = false; - } - }; + let callbacks; + const done = (err, result) => { + if (callbacks === undefined) { + callback(err, result); + callbacks = false; + } else { + for (const callback of callbacks) { + callback(err, result); + } + activeRequests.delete(identifier); + callbacks = false; + } + }; /** * @param {Error=} err error if any * @param {CacheEntry=} cacheEntry cache entry @@ -63002,14 +62863,7 @@ class ResolverCachePlugin { } }; itemCache.get(processCacheResult); - if (withYield && callbacks === undefined) { - callbacks = [callback]; - yields = [resolveContext.yield]; - activeRequestsWithYield.set( - identifier, - /** @type {[any, any]} */ ([callbacks, yields]) - ); - } else if (callbacks === undefined) { + if (callbacks === undefined) { callbacks = [callback]; activeRequests.set(identifier, callbacks); } @@ -141965,7 +141819,7 @@ module.exports = JSON.parse('{"application/1d-interleaved-parityfec":{"source":" /***/ (function(module) { "use strict"; -module.exports = {"i8":"5.69.0"}; +module.exports = {"i8":"5.69.1"}; /***/ }), diff --git a/packages/next/package.json b/packages/next/package.json index e22442f6fdef..9c15019bcdcc 100644 --- a/packages/next/package.json +++ b/packages/next/package.json @@ -267,7 +267,7 @@ "webpack-sources1": "npm:webpack-sources@1.4.3", "webpack-sources3": "npm:webpack-sources@3.2.3", "webpack4": "npm:webpack@4.44.1", - "webpack5": "npm:webpack@5.69.0", + "webpack5": "npm:webpack@5.69.1", "ws": "8.2.3" }, "resolutions": { diff --git a/yarn.lock b/yarn.lock index af18b6562100..1ee4e9b20c7e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8878,7 +8878,7 @@ enhanced-resolve@^4.3.0: memory-fs "^0.5.0" tapable "^1.0.0" -enhanced-resolve@^5.9.0: +enhanced-resolve@^5.8.3: version "5.9.0" resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.9.0.tgz#49ac24953ac8452ed8fed2ef1340fc8e043667ee" integrity sha512-weDYmzbBygL7HzGGS26M3hGQx68vehdEg6VUmqSOaFzXExFqlnKuSvsEJCVGQHScS8CQMbrAqftT+AzzHNt/YA== @@ -20847,10 +20847,10 @@ webpack-bundle-analyzer@4.3.0: watchpack "^1.7.4" webpack-sources "^1.4.1" -"webpack5@npm:webpack@5.69.0": - version "5.69.0" - resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.69.0.tgz#c9eb607d4f6c49f1e5755492323a7b055c3450e3" - integrity sha512-E5Fqu89Gu8fR6vejRqu26h8ld/k6/dCVbeGUcuZjc+goQHDfCPU9rER71JmdtBYGmci7Ec2aFEATQ2IVXKy2wg== +"webpack5@npm:webpack@5.69.1": + version "5.69.1" + resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.69.1.tgz#8cfd92c192c6a52c99ab00529b5a0d33aa848dc5" + integrity sha512-+VyvOSJXZMT2V5vLzOnDuMz5GxEqLk7hKWQ56YxPW/PQRUuKimPqmEIJOx8jHYeyo65pKbapbW464mvsKbaj4A== dependencies: "@types/eslint-scope" "^3.7.3" "@types/estree" "^0.0.51" @@ -20861,7 +20861,7 @@ webpack-bundle-analyzer@4.3.0: acorn-import-assertions "^1.7.6" browserslist "^4.14.5" chrome-trace-event "^1.0.2" - enhanced-resolve "^5.9.0" + enhanced-resolve "^5.8.3" es-module-lexer "^0.9.0" eslint-scope "5.1.1" events "^3.2.0" From d4d79b2d9b9c43ed1061a3d3beeb3099368669a8 Mon Sep 17 00:00:00 2001 From: Shu Ding Date: Thu, 17 Feb 2022 16:05:15 +0100 Subject: [PATCH 20/28] Fix chunk buffering for server components (#34474) --- packages/next/server/render.tsx | 31 ++++++++++++++++++++----------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/packages/next/server/render.tsx b/packages/next/server/render.tsx index 87b45ab0b44a..4c00eb0b92fc 100644 --- a/packages/next/server/render.tsx +++ b/packages/next/server/render.tsx @@ -1764,19 +1764,28 @@ function createInlineDataStream( if (!dataStreamFinished && dataStream) { const dataStreamReader = dataStream.getReader() - dataStreamFinished = (async () => { - try { - while (true) { - const { done, value } = await dataStreamReader.read() - if (done) { - return + + // We are buffering here for the inlined data stream because the + // "shell" stream might be chunkenized again by the underlying stream + // implementation, e.g. with a specific high-water mark. To ensure it's + // the safe timing to pipe the data stream, this extra tick is + // necessary. + dataStreamFinished = new Promise((res) => + setTimeout(async () => { + try { + while (true) { + const { done, value } = await dataStreamReader.read() + if (done) { + return res() + } + controller.enqueue(value) } - controller.enqueue(value) + } catch (err) { + controller.error(err) } - } catch (err) { - controller.error(err) - } - })() + res() + }, 0) + ) } }, flush() { From f0f322c0d1655d722d133f963a84e575a61a5708 Mon Sep 17 00:00:00 2001 From: Gal Schlezinger Date: Thu, 17 Feb 2022 17:12:36 +0200 Subject: [PATCH 21/28] Remove deprecation for relative URL usage in middlewares (#34461) * Remove deprecation for relative URL usage in middlewares * fix tests Co-authored-by: JJ Kasper --- errors/middleware-relative-urls.md | 4 +- packages/next/server/web/utils.ts | 12 +++--- .../core/pages/rewrites/_middleware.js | 2 +- .../middleware/core/test/index.test.js | 39 ++++++++++++++----- 4 files changed, 38 insertions(+), 19 deletions(-) diff --git a/errors/middleware-relative-urls.md b/errors/middleware-relative-urls.md index dabdf3306085..856bc94afaf3 100644 --- a/errors/middleware-relative-urls.md +++ b/errors/middleware-relative-urls.md @@ -2,11 +2,11 @@ #### Why This Error Occurred -You are using a Middleware function that uses `Response.redirect(url)`, `NextResponse.redirect(url)` or `NextResponse.rewrite(url)` where `url` is a relative or an invalid URL. Currently this will work, but building a request with `new Request(url)` or running `fetch(url)` when `url` is a relative URL will **not** work. For this reason and to bring consistency to Next.js Middleware, this behavior will be deprecated soon in favor of always using absolute URLs. +You are using a Middleware function that uses `Response.redirect(url)`, `NextResponse.redirect(url)` or `NextResponse.rewrite(url)` where `url` is a relative or an invalid URL. Prior to Next.js 12.1, we allowed passing relative URLs. However, constructing a request with `new Request(url)` or running `fetch(url)` when `url` is a relative URL **does not** work. For this reason and to bring consistency to Next.js Middleware, this behavior has been deprecated and now removed. #### Possible Ways to Fix It -To fix this warning you must always pass absolute URL for redirecting and rewriting. There are several ways to get the absolute URL but the recommended way is to clone `NextURL` and mutate it: +To fix this error you must always pass absolute URL for redirecting and rewriting. There are several ways to get the absolute URL but the recommended way is to clone `NextURL` and mutate it: ```typescript import type { NextRequest } from 'next/server' diff --git a/packages/next/server/web/utils.ts b/packages/next/server/web/utils.ts index 06e257457f09..a795ec35e144 100644 --- a/packages/next/server/web/utils.ts +++ b/packages/next/server/web/utils.ts @@ -149,18 +149,16 @@ export function splitCookiesString(cookiesString: string) { } /** - * We will be soon deprecating the usage of relative URLs in Middleware introducing - * URL validation. This helper puts the future code in place and prints a warning - * for cases where it will break. Meanwhile we preserve the previous behavior. + * Validate the correctness of a user-provided URL. */ export function validateURL(url: string | URL): string { try { return String(new URL(String(url))) } catch (error: any) { - console.log( - `warn -`, - 'using relative URLs for Middleware will be deprecated soon - https://nextjs.org/docs/messages/middleware-relative-urls' + throw new Error( + `URLs is malformed. Please use only absolute URLs - https://nextjs.org/docs/messages/middleware-relative-urls`, + // @ts-expect-error This will work for people who enable the error causes polyfill + { cause: error } ) - return String(url) } } diff --git a/test/integration/middleware/core/pages/rewrites/_middleware.js b/test/integration/middleware/core/pages/rewrites/_middleware.js index 27137642a1c1..d9b83488318c 100644 --- a/test/integration/middleware/core/pages/rewrites/_middleware.js +++ b/test/integration/middleware/core/pages/rewrites/_middleware.js @@ -12,7 +12,7 @@ export async function middleware(request) { ) { const isExternal = url.searchParams.get('override') === 'external' return NextResponse.rewrite( - isExternal ? 'https://vercel.com' : '/rewrites/a' + isExternal ? 'https://vercel.com' : new URL('/rewrites/a', request.url) ) } diff --git a/test/integration/middleware/core/test/index.test.js b/test/integration/middleware/core/test/index.test.js index c6d33990720f..805cf48fd395 100644 --- a/test/integration/middleware/core/test/index.test.js +++ b/test/integration/middleware/core/test/index.test.js @@ -19,7 +19,7 @@ const context = {} context.appDir = join(__dirname, '../') const middlewareWarning = 'using beta Middleware (not covered by semver)' -const urlsWarning = 'using relative URLs for Middleware will be deprecated soon' +const urlsError = 'Please use only absolute URLs' describe('Middleware base tests', () => { describe('dev mode', () => { @@ -110,7 +110,7 @@ describe('Middleware base tests', () => { }) }) -function urlTests(log, locale = '') { +function urlTests(_log, locale = '') { it('rewrites by default to a target location', async () => { const res = await fetchViaHTTP(context.appPort, `${locale}/urls`) const html = await res.text() @@ -146,18 +146,39 @@ function urlTests(log, locale = '') { }) it('warns when using Response.redirect with a relative URL', async () => { - await fetchViaHTTP(context.appPort, `${locale}/urls/relative-redirect`) - expect(log.output).toContain(urlsWarning) + const response = await fetchViaHTTP( + context.appPort, + `${locale}/urls/relative-redirect` + ) + expect(await response.json()).toEqual({ + error: { + message: expect.stringContaining(urlsError), + }, + }) }) it('warns when using NextResponse.redirect with a relative URL', async () => { - await fetchViaHTTP(context.appPort, `${locale}/urls/relative-next-redirect`) - expect(log.output).toContain(urlsWarning) + const response = await fetchViaHTTP( + context.appPort, + `${locale}/urls/relative-next-redirect` + ) + expect(await response.json()).toEqual({ + error: { + message: expect.stringContaining(urlsError), + }, + }) }) - it('warns when using NextResponse.rewrite with a relative URL', async () => { - await fetchViaHTTP(context.appPort, `${locale}/urls/relative-next-rewrite`) - expect(log.output).toContain(urlsWarning) + it('throws when using NextResponse.rewrite with a relative URL', async () => { + const response = await fetchViaHTTP( + context.appPort, + `${locale}/urls/relative-next-rewrite` + ) + expect(await response.json()).toEqual({ + error: { + message: expect.stringContaining(urlsError), + }, + }) }) } From 69aedbd6667753f02b76563598342c8afa646dfa Mon Sep 17 00:00:00 2001 From: Lee Robinson Date: Thu, 17 Feb 2022 08:18:59 -0700 Subject: [PATCH 22/28] Fix typo (#34480) --- .../data-fetching/get-static-paths.md | 8 +- .../data-fetching/get-static-props.md | 14 ++-- docs/api-routes/response-helpers.md | 1 + .../data-fetching/client-side.md | 11 +++ .../data-fetching/get-server-side-props.md | 4 + .../data-fetching/get-static-paths.md | 12 ++- .../data-fetching/get-static-props.md | 16 +++- .../incremental-static-regeneration.md | 75 ++++++++++++++++++- docs/middleware.md | 2 +- 9 files changed, 124 insertions(+), 19 deletions(-) diff --git a/docs/api-reference/data-fetching/get-static-paths.md b/docs/api-reference/data-fetching/get-static-paths.md index 9175bed28a72..5b86474d8890 100644 --- a/docs/api-reference/data-fetching/get-static-paths.md +++ b/docs/api-reference/data-fetching/get-static-paths.md @@ -7,10 +7,12 @@ description: API reference for `getStaticPaths`. Learn how to fetch data and gen
Version History -| Version | Changes | -| -------- | --------------------------------------------------------------------------------------------------------------- | +| Version | Changes | +| ------- | ------- | + +| `v12.1.0` | [On-demand Incremental Static Regeneration](/docs/basic-features/data-fetching/incremental-static-regeneration.md#on-demand-revalidation-beta) added (Beta). | | `v9.5.0` | Stable [Incremental Static Regeneration](/docs/basic-features/data-fetching/incremental-static-regeneration.md) | -| `v9.3.0` | `getStaticPaths` introduced. | +| `v9.3.0` | `getStaticPaths` introduced. |
diff --git a/docs/api-reference/data-fetching/get-static-props.md b/docs/api-reference/data-fetching/get-static-props.md index 7e04d1d9252e..c5f34276ffa8 100644 --- a/docs/api-reference/data-fetching/get-static-props.md +++ b/docs/api-reference/data-fetching/get-static-props.md @@ -7,12 +7,14 @@ description: API reference for `getStaticProps`. Learn how to use `getStaticProp
Version History -| Version | Changes | -| --------- | ----------------------------------------------------------------------------------------------------------------- | -| `v10.0.0` | `locale`, `locales`, `defaultLocale`, and `notFound` options added. | -| `v9.5.0` | Stable [Incremental Static Regeneration](https://nextjs.org/blog/next-9-5#stable-incremental-static-regeneration) | -| `v9.3.0` | `getStaticProps` introduced. | -| `v10.0.0` | `fallback: 'blocking'` return option added. | +| Version | Changes | +| ------- | ------- | + +| `v12.1.0` | [On-demand Incremental Static Regeneration](/docs/basic-features/data-fetching/incremental-static-regeneration.md#on-demand-revalidation-beta) added (Beta). | +| `v10.0.0` | `locale`, `locales`, `defaultLocale`, and `notFound` options added. | +| `v10.0.0` | `fallback: 'blocking'` return option added. | +| `v9.5.0` | Stable [Incremental Static Regeneration](/docs/basic-features/data-fetching/incremental-static-regeneration.md) | +| `v9.3.0` | `getStaticProps` introduced. |
diff --git a/docs/api-routes/response-helpers.md b/docs/api-routes/response-helpers.md index 41673745b259..0e3af609fbc6 100644 --- a/docs/api-routes/response-helpers.md +++ b/docs/api-routes/response-helpers.md @@ -12,6 +12,7 @@ The included helpers are: - `res.json(body)` - Sends a JSON response. `body` must be a [serializable object](https://developer.mozilla.org/en-US/docs/Glossary/Serialization) - `res.send(body)` - Sends the HTTP response. `body` can be a `string`, an `object` or a `Buffer` - `res.redirect([status,] path)` - Redirects to a specified path or URL. `status` must be a valid [HTTP status code](https://en.wikipedia.org/wiki/List_of_HTTP_status_codes). If not specified, `status` defaults to "307" "Temporary redirect". +- `res.unstable_revalidate(urlPath)` - [Revalidate a page on demand](/docs/basic-features/data-fetching/incremental-static-regeneration.md#on-demand-revalidation-beta) using `getStaticProps`. `urlPath` must be a `string`. ## Setting the status code of a response diff --git a/docs/basic-features/data-fetching/client-side.md b/docs/basic-features/data-fetching/client-side.md index 97cad41a1a8a..6b1768a6e901 100644 --- a/docs/basic-features/data-fetching/client-side.md +++ b/docs/basic-features/data-fetching/client-side.md @@ -68,3 +68,14 @@ function Profile() { ) } ``` + +## Related + +For more information on what to do next, we recommend the following sections: + + diff --git a/docs/basic-features/data-fetching/get-server-side-props.md b/docs/basic-features/data-fetching/get-server-side-props.md index 53952a44942e..377a775aa5a2 100644 --- a/docs/basic-features/data-fetching/get-server-side-props.md +++ b/docs/basic-features/data-fetching/get-server-side-props.md @@ -74,6 +74,10 @@ export async function getServerSideProps() { export default Page ``` +## Related + +For more information on what to do next, we recommend the following sections: +
getServerSideProps API Reference diff --git a/docs/basic-features/data-fetching/get-static-paths.md b/docs/basic-features/data-fetching/get-static-paths.md index cb9b5b68b438..ecb2e7f57c8c 100644 --- a/docs/basic-features/data-fetching/get-static-paths.md +++ b/docs/basic-features/data-fetching/get-static-paths.md @@ -19,7 +19,7 @@ export async function getStaticPaths() { } ``` -Note that`getStaticProps` **must** be used with `getStaticPaths`, and that you **cannot** use it with [`getServerSideProps`](/docs/basic-features/data-fetching/get-server-side-props.md). +`getStaticPaths` **must** be used with `getStaticProps`. You **cannot** use it with [`getServerSideProps`](/docs/basic-features/data-fetching/get-server-side-props.md). The [`getStaticPaths` API reference](/docs/api-reference/data-fetching/get-static-paths.md) covers all parameters and props that can be used with `getStaticPaths`. @@ -35,7 +35,11 @@ You should use `getStaticPaths` if you’re statically pre-rendering pages that ## When does getStaticPaths run -`getStaticPaths` only runs at build time on server-side. If you're using [Incremental Static Regeneration](/docs/basic-features/data-fetching/incremental-static-regeneration.md), `getStaticPaths` can also be run on-demand _in the background_, but still only on the server-side. +`getStaticPaths` always runs on the server and never on the client. You can validate code written inside `getStaticPaths` is removed from the client-side bundle [with this tool](https://next-code-elimination.vercel.app/). + +- `getStaticPaths` runs during `next build` for Pages included in `paths` +- `getStaticPaths` runs on-demand in the background when using `fallback: true` +- `getStaticPaths` runs on-demand blocking rendering when using `fallback: blocking` ## Where can I use getStaticPaths @@ -47,6 +51,10 @@ Note that you must use export `getStaticPaths` as a standalone function — it w In development (`next dev`), `getStaticPaths` will be called on every request. +## Related + +For more information on what to do next, we recommend the following sections: +
getStaticPaths API Reference diff --git a/docs/basic-features/data-fetching/get-static-props.md b/docs/basic-features/data-fetching/get-static-props.md index 0b483f5a6b99..a5a5190fda97 100644 --- a/docs/basic-features/data-fetching/get-static-props.md +++ b/docs/basic-features/data-fetching/get-static-props.md @@ -23,9 +23,17 @@ You should use `getStaticProps` if: - The data can be publicly cached (not user-specific) - The page must be pre-rendered (for SEO) and be very fast — `getStaticProps` generates `HTML` and `JSON` files, both of which can be cached by a CDN for performance +## When does getStaticProps run + +`getStaticProps` always runs on the server and never on the client. You can validate code written inside `getStaticProps` is removed from the client-side bundle [with this tool](https://next-code-elimination.vercel.app/). + +- `getStaticProps` always runs during `next build` +- `getStaticProps` runs in the background when using `revalidate` +- `getStaticProps` runs on-demand in the background when using [`unstable_revalidate`](/docs/basic-features/data-fetching/incremental-static-regeneration.md#on-demand-revalidation-beta) + When combined with [Incremental Static Regeneration](/docs/basic-features/data-fetching/incremental-static-regeneration.md), `getStaticProps` will run in the background while the stale page is being revalidated, and the fresh page served to the browser. -Because `getStaticProps` runs at build time, it does **not** have access to the incoming request (such as query parameters or `HTTP` headers) as it generates static `HTML`. If you need access to the request for your page, consider using [Middleware](/docs/middleware.md) in addition to `getStaticProps`. +`getStaticProps` does not have access to the incoming request (such as query parameters or HTTP headers) as it generates static HTML. If you need access to the request for your page, consider using [Middleware](/docs/middleware.md) in addition to `getStaticProps`. ## Using getStaticProps to fetch data from a CMS @@ -128,9 +136,11 @@ In development (`next dev`), `getStaticProps` will be called on every request. ## Preview Mode -In some cases, you might want to temporarily bypass Static Generation and render the page at **request time** instead of build time. For example, you might be using a headless CMS and want to preview drafts before they're published. +You can temporarily bypass static generation and render the page at **request time** instead of build time using [**Preview Mode**](/docs/advanced-features/preview-mode.md). For example, you might be using a headless CMS and want to preview drafts before they're published. + +## Related -This use case is supported in Next.js by the [**Preview Mode**](/docs/advanced-features/preview-mode.md) feature. +For more information on what to do next, we recommend the following sections:
diff --git a/docs/basic-features/data-fetching/incremental-static-regeneration.md b/docs/basic-features/data-fetching/incremental-static-regeneration.md index abe7f13c8154..fa0a1b350e8d 100644 --- a/docs/basic-features/data-fetching/incremental-static-regeneration.md +++ b/docs/basic-features/data-fetching/incremental-static-regeneration.md @@ -16,9 +16,11 @@ description: 'Learn how to create or update static pages at runtime with Increme
Version History -| Version | Changes | -| -------- | ---------------- | -| `v9.5.0` | Base Path added. | +| Version | Changes | +| --------- | --------------------------------------------------------------------------------------- | +| `v12.1.0` | On-demand ISR added (Beta). | +| `v12.0.0` | [Bot-aware ISR fallback](https://nextjs.org/blog/next-12#bot-aware-isr-fallback) added. | +| `v9.5.0` | Base Path added. |
@@ -85,7 +87,61 @@ When a request is made to a page that was pre-rendered at build time, it will in When a request is made to a path that hasn’t been generated, Next.js will server-render the page on the first request. Future requests will serve the static file from the cache. ISR on Vercel [persists the cache globally and handles rollbacks](https://vercel.com/docs/concepts/next.js/incremental-static-regeneration). -## Error Handling and Revalidation +## On-demand Revalidation (Beta) + +If you set a `revalidate` time of `60`, all visitors will see the same generated version of your site for one minute. The only way to invalidate the cache is from someone visiting that page after the minute has passed. + +Starting with `v12.1.0`, Next.js supports on-demand Incremental Static Regeneration to manually purge the Next.js cache for a specific page. This makes it easier to update your site when: + +- Content from your headless CMS is created or updated +- Ecommerce metadata changes (price, description, category, reviews, etc.) + +Inside `getStaticProps`, you do not need to specify `revalidate` to use on-demand revalidation. If `revalidate` is omitted, Next.js will use the default value of `false` (no revalidation) and only revalidate the page on-demand when `unstable_revalidate` is called. + +### Using On-Demand Revalidation + +First, create a secret token only known by your Next.js app. This secret will be used to prevent unauthorized access to the revalidation API Route. You can access the route (either manually or with a webhook) with the following URL structure: + +```bash +https:///api/revalidate?secret= +``` + +Next, add the secret as an [Environment Variable](/docs/basic-features/environment-variables.md) to your application. Finally, create the revalidation API Route: + +```jsx +// pages/api/revalidate.js + +export default async function handler(req, res) { + // Check for secret to confirm this is a valid request + if (req.query.secret !== process.env.MY_SECRET_TOKEN) { + return res.status(401).json({ message: 'Invalid token' }) + } + + try { + await res.unstable_revalidate('/path-to-revalidate') + return res.json({ revalidated: true }) + } catch (err) { + // If there was an error, Next.js will continue + // to show the last successfully generated page + return res.status(500).send('Error revalidating') + } +} +``` + +[View our demo](https://on-demand-isr.vercel.app) to see on-demand revalidation in action and provide feedback. + +### Testing on-demand ISR during development + +When running locally with `next dev`, `getStaticProps` is invoked on every request. To verify your on-demand ISR configuration is correct, you will need to create a [production build](/docs/api-reference/cli.md#build) and start the [production server](/docs/api-reference/cli.md#production): + +```bash +$ next build +$ next start +``` + +Then, you are able to validate static pages are successfully revalidated. + +## Error handling and revalidation If there is an error inside `getStaticProps` when handling background regeneration, or you manually throw an error, the last successfully generated page will continue to show. On the next subsequent request, Next.js will retry calling `getStaticProps`. @@ -114,3 +170,14 @@ export async function getStaticProps() { } } ``` + +## Related + +For more information on what to do next, we recommend the following sections: + +
diff --git a/docs/middleware.md b/docs/middleware.md index c56eb4df4298..d204f99a6c4d 100644 --- a/docs/middleware.md +++ b/docs/middleware.md @@ -10,7 +10,7 @@ description: Learn how to use Middleware in Next.js to run code before a request | Version | Changes | | --------- | ------------------------------------------------------------------------------------------ | | `v12.0.9` | Enforce absolute URLs in Edge Runtime ([PR](https://github.com/vercel/next.js/pull/33410)) | -| `v12.0.0` | Middleware (beta) added. | +| `v12.0.0` | Middleware (Beta) added. | From 1605f3059c7773a346998da5e1de416d106d8f32 Mon Sep 17 00:00:00 2001 From: JJ Kasper Date: Thu, 17 Feb 2022 09:21:51 -0600 Subject: [PATCH 23/28] v12.0.11-canary.21 --- lerna.json | 2 +- packages/create-next-app/package.json | 2 +- packages/eslint-config-next/package.json | 4 ++-- packages/eslint-plugin-next/package.json | 2 +- packages/next-bundle-analyzer/package.json | 2 +- packages/next-codemod/package.json | 2 +- packages/next-env/package.json | 2 +- packages/next-mdx/package.json | 2 +- packages/next-plugin-storybook/package.json | 2 +- packages/next-polyfill-module/package.json | 2 +- packages/next-polyfill-nomodule/package.json | 2 +- packages/next-swc/package.json | 2 +- packages/next/package.json | 14 +++++++------- packages/react-dev-overlay/package.json | 2 +- packages/react-refresh-utils/package.json | 2 +- 15 files changed, 22 insertions(+), 22 deletions(-) diff --git a/lerna.json b/lerna.json index cad2e009a268..805a5b514c1c 100644 --- a/lerna.json +++ b/lerna.json @@ -16,5 +16,5 @@ "registry": "https://registry.npmjs.org/" } }, - "version": "12.0.11-canary.20" + "version": "12.0.11-canary.21" } diff --git a/packages/create-next-app/package.json b/packages/create-next-app/package.json index 72753329f694..dc7e00b0a135 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.0.11-canary.20", + "version": "12.0.11-canary.21", "keywords": [ "react", "next", diff --git a/packages/eslint-config-next/package.json b/packages/eslint-config-next/package.json index 3b749a84a710..abf2b3e1e380 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.0.11-canary.20", + "version": "12.0.11-canary.21", "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.0.11-canary.20", + "@next/eslint-plugin-next": "12.0.11-canary.21", "@rushstack/eslint-patch": "^1.0.8", "@typescript-eslint/parser": "^5.0.0", "eslint-import-resolver-node": "^0.3.4", diff --git a/packages/eslint-plugin-next/package.json b/packages/eslint-plugin-next/package.json index fe8744534d70..a04f62e191fe 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.0.11-canary.20", + "version": "12.0.11-canary.21", "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 90a36ab1b142..54ced3d633c5 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.0.11-canary.20", + "version": "12.0.11-canary.21", "main": "index.js", "license": "MIT", "repository": { diff --git a/packages/next-codemod/package.json b/packages/next-codemod/package.json index 9bd41a6a22be..6b134e6ae1d9 100644 --- a/packages/next-codemod/package.json +++ b/packages/next-codemod/package.json @@ -1,6 +1,6 @@ { "name": "@next/codemod", - "version": "12.0.11-canary.20", + "version": "12.0.11-canary.21", "license": "MIT", "dependencies": { "chalk": "4.1.0", diff --git a/packages/next-env/package.json b/packages/next-env/package.json index af4a155b0907..2530b0d232ec 100644 --- a/packages/next-env/package.json +++ b/packages/next-env/package.json @@ -1,6 +1,6 @@ { "name": "@next/env", - "version": "12.0.11-canary.20", + "version": "12.0.11-canary.21", "keywords": [ "react", "next", diff --git a/packages/next-mdx/package.json b/packages/next-mdx/package.json index 2d8427c62e7b..8173e639bcb8 100644 --- a/packages/next-mdx/package.json +++ b/packages/next-mdx/package.json @@ -1,6 +1,6 @@ { "name": "@next/mdx", - "version": "12.0.11-canary.20", + "version": "12.0.11-canary.21", "main": "index.js", "license": "MIT", "repository": { diff --git a/packages/next-plugin-storybook/package.json b/packages/next-plugin-storybook/package.json index a7cede6b1e3a..8953a5c3f1e8 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.0.11-canary.20", + "version": "12.0.11-canary.21", "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 c68c31db6d64..f4a725c4d370 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.0.11-canary.20", + "version": "12.0.11-canary.21", "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 ee82daff0b72..9a7d51d3096a 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.0.11-canary.20", + "version": "12.0.11-canary.21", "description": "A polyfill for non-dead, nomodule browsers.", "main": "dist/polyfill-nomodule.js", "license": "MIT", diff --git a/packages/next-swc/package.json b/packages/next-swc/package.json index 69368143ee37..b976e3fac516 100644 --- a/packages/next-swc/package.json +++ b/packages/next-swc/package.json @@ -1,6 +1,6 @@ { "name": "@next/swc", - "version": "12.0.11-canary.20", + "version": "12.0.11-canary.21", "private": true, "scripts": { "build-native": "napi build --platform --cargo-name next_swc_napi native", diff --git a/packages/next/package.json b/packages/next/package.json index 9c15019bcdcc..f77f1a7da137 100644 --- a/packages/next/package.json +++ b/packages/next/package.json @@ -1,6 +1,6 @@ { "name": "next", - "version": "12.0.11-canary.20", + "version": "12.0.11-canary.21", "description": "The React Framework", "main": "./dist/server/next.js", "license": "MIT", @@ -69,7 +69,7 @@ ] }, "dependencies": { - "@next/env": "12.0.11-canary.20", + "@next/env": "12.0.11-canary.21", "caniuse-lite": "^1.0.30001283", "postcss": "8.4.5", "styled-jsx": "5.0.0", @@ -117,11 +117,11 @@ "@hapi/accept": "5.0.2", "@napi-rs/cli": "1.2.1", "@napi-rs/triples": "1.0.3", - "@next/polyfill-module": "12.0.11-canary.20", - "@next/polyfill-nomodule": "12.0.11-canary.20", - "@next/react-dev-overlay": "12.0.11-canary.20", - "@next/react-refresh-utils": "12.0.11-canary.20", - "@next/swc": "12.0.11-canary.20", + "@next/polyfill-module": "12.0.11-canary.21", + "@next/polyfill-nomodule": "12.0.11-canary.21", + "@next/react-dev-overlay": "12.0.11-canary.21", + "@next/react-refresh-utils": "12.0.11-canary.21", + "@next/swc": "12.0.11-canary.21", "@peculiar/webcrypto": "1.1.7", "@taskr/clear": "1.1.0", "@taskr/esnext": "1.1.0", diff --git a/packages/react-dev-overlay/package.json b/packages/react-dev-overlay/package.json index 23c18f4deec4..2cd10559dbd5 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.0.11-canary.20", + "version": "12.0.11-canary.21", "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 b2119e795818..b765c2725a3f 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.0.11-canary.20", + "version": "12.0.11-canary.21", "description": "An experimental package providing utilities for React Refresh.", "repository": { "url": "vercel/next.js", From 8545fd1bb02244ced9e8dc9584a764aeae296cd0 Mon Sep 17 00:00:00 2001 From: JJ Kasper Date: Thu, 17 Feb 2022 09:34:51 -0600 Subject: [PATCH 24/28] v12.1.0 --- lerna.json | 2 +- packages/create-next-app/package.json | 2 +- packages/eslint-config-next/package.json | 4 ++-- packages/eslint-plugin-next/package.json | 2 +- packages/next-bundle-analyzer/package.json | 2 +- packages/next-codemod/package.json | 2 +- packages/next-env/package.json | 2 +- packages/next-mdx/package.json | 2 +- packages/next-plugin-storybook/package.json | 2 +- packages/next-polyfill-module/package.json | 2 +- packages/next-polyfill-nomodule/package.json | 2 +- packages/next-swc/package.json | 2 +- packages/next/package.json | 14 +++++++------- packages/react-dev-overlay/package.json | 2 +- packages/react-refresh-utils/package.json | 2 +- 15 files changed, 22 insertions(+), 22 deletions(-) diff --git a/lerna.json b/lerna.json index 805a5b514c1c..3aa8531b8bb4 100644 --- a/lerna.json +++ b/lerna.json @@ -16,5 +16,5 @@ "registry": "https://registry.npmjs.org/" } }, - "version": "12.0.11-canary.21" + "version": "12.1.0" } diff --git a/packages/create-next-app/package.json b/packages/create-next-app/package.json index dc7e00b0a135..4e57156340a9 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.0.11-canary.21", + "version": "12.1.0", "keywords": [ "react", "next", diff --git a/packages/eslint-config-next/package.json b/packages/eslint-config-next/package.json index abf2b3e1e380..c141e9f35c9d 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.0.11-canary.21", + "version": "12.1.0", "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.0.11-canary.21", + "@next/eslint-plugin-next": "12.1.0", "@rushstack/eslint-patch": "^1.0.8", "@typescript-eslint/parser": "^5.0.0", "eslint-import-resolver-node": "^0.3.4", diff --git a/packages/eslint-plugin-next/package.json b/packages/eslint-plugin-next/package.json index a04f62e191fe..16f5abeb55cb 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.0.11-canary.21", + "version": "12.1.0", "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 54ced3d633c5..90bd9a3bccf2 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.0.11-canary.21", + "version": "12.1.0", "main": "index.js", "license": "MIT", "repository": { diff --git a/packages/next-codemod/package.json b/packages/next-codemod/package.json index 6b134e6ae1d9..25b85d8f7df4 100644 --- a/packages/next-codemod/package.json +++ b/packages/next-codemod/package.json @@ -1,6 +1,6 @@ { "name": "@next/codemod", - "version": "12.0.11-canary.21", + "version": "12.1.0", "license": "MIT", "dependencies": { "chalk": "4.1.0", diff --git a/packages/next-env/package.json b/packages/next-env/package.json index 2530b0d232ec..42a1b5c61c38 100644 --- a/packages/next-env/package.json +++ b/packages/next-env/package.json @@ -1,6 +1,6 @@ { "name": "@next/env", - "version": "12.0.11-canary.21", + "version": "12.1.0", "keywords": [ "react", "next", diff --git a/packages/next-mdx/package.json b/packages/next-mdx/package.json index 8173e639bcb8..06c4c6b95eb7 100644 --- a/packages/next-mdx/package.json +++ b/packages/next-mdx/package.json @@ -1,6 +1,6 @@ { "name": "@next/mdx", - "version": "12.0.11-canary.21", + "version": "12.1.0", "main": "index.js", "license": "MIT", "repository": { diff --git a/packages/next-plugin-storybook/package.json b/packages/next-plugin-storybook/package.json index 8953a5c3f1e8..2fedae0f986f 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.0.11-canary.21", + "version": "12.1.0", "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 f4a725c4d370..9f6788d25e1b 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.0.11-canary.21", + "version": "12.1.0", "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 9a7d51d3096a..3882f7dc5d81 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.0.11-canary.21", + "version": "12.1.0", "description": "A polyfill for non-dead, nomodule browsers.", "main": "dist/polyfill-nomodule.js", "license": "MIT", diff --git a/packages/next-swc/package.json b/packages/next-swc/package.json index b976e3fac516..9f16469b8681 100644 --- a/packages/next-swc/package.json +++ b/packages/next-swc/package.json @@ -1,6 +1,6 @@ { "name": "@next/swc", - "version": "12.0.11-canary.21", + "version": "12.1.0", "private": true, "scripts": { "build-native": "napi build --platform --cargo-name next_swc_napi native", diff --git a/packages/next/package.json b/packages/next/package.json index f77f1a7da137..316aee06f97a 100644 --- a/packages/next/package.json +++ b/packages/next/package.json @@ -1,6 +1,6 @@ { "name": "next", - "version": "12.0.11-canary.21", + "version": "12.1.0", "description": "The React Framework", "main": "./dist/server/next.js", "license": "MIT", @@ -69,7 +69,7 @@ ] }, "dependencies": { - "@next/env": "12.0.11-canary.21", + "@next/env": "12.1.0", "caniuse-lite": "^1.0.30001283", "postcss": "8.4.5", "styled-jsx": "5.0.0", @@ -117,11 +117,11 @@ "@hapi/accept": "5.0.2", "@napi-rs/cli": "1.2.1", "@napi-rs/triples": "1.0.3", - "@next/polyfill-module": "12.0.11-canary.21", - "@next/polyfill-nomodule": "12.0.11-canary.21", - "@next/react-dev-overlay": "12.0.11-canary.21", - "@next/react-refresh-utils": "12.0.11-canary.21", - "@next/swc": "12.0.11-canary.21", + "@next/polyfill-module": "12.1.0", + "@next/polyfill-nomodule": "12.1.0", + "@next/react-dev-overlay": "12.1.0", + "@next/react-refresh-utils": "12.1.0", + "@next/swc": "12.1.0", "@peculiar/webcrypto": "1.1.7", "@taskr/clear": "1.1.0", "@taskr/esnext": "1.1.0", diff --git a/packages/react-dev-overlay/package.json b/packages/react-dev-overlay/package.json index 2cd10559dbd5..a1d512c8a6f7 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.0.11-canary.21", + "version": "12.1.0", "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 b765c2725a3f..f797514e1ac9 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.0.11-canary.21", + "version": "12.1.0", "description": "An experimental package providing utilities for React Refresh.", "repository": { "url": "vercel/next.js", From c0abf32cd81a486917fd5ea12691bf2065861c93 Mon Sep 17 00:00:00 2001 From: Steven Date: Thu, 17 Feb 2022 10:51:28 -0500 Subject: [PATCH 25/28] Update docs for image optimization swr (#34483) - Related to #27208 - Related to #33735 --- docs/api-reference/next/image.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api-reference/next/image.md b/docs/api-reference/next/image.md index f278c193a348..4905a0f1125a 100644 --- a/docs/api-reference/next/image.md +++ b/docs/api-reference/next/image.md @@ -402,7 +402,7 @@ module.exports = { The following describes the caching algorithm for the default [loader](#loader). For all other loaders, please refer to your cloud provider's documentation. -Images are optimized dynamically upon request and stored in the `/cache/images` directory. The optimized image file will be served for subsequent requests until the expiration is reached. When a request is made that matches a cached but expired file, the cached file is deleted before generating a new optimized image and caching the new file. +Images are optimized dynamically upon request and stored in the `/cache/images` directory. The optimized image file will be served for subsequent requests until the expiration is reached. When a request is made that matches a cached but expired file, the expired image is served stale immediately. Then the image is optimized again in the background (also called revalidation) and saved to the cache with the new expiration date. The expiration (or rather Max Age) is defined by either the [`minimumCacheTTL`](#minimum-cache-ttl) configuration or the upstream server's `Cache-Control` header, whichever is larger. Specifically, the `max-age` value of the `Cache-Control` header is used. If both `s-maxage` and `max-age` are found, then `s-maxage` is preferred. From ae3e55dca471178ef0ceccfd7a6357b09f9a24cf Mon Sep 17 00:00:00 2001 From: Shu Ding Date: Thu, 17 Feb 2022 17:52:13 +0100 Subject: [PATCH 26/28] Fix 404 links in React 18 docs (#34486) https://nextjs.org/docs/advanced-features/react-18/overview ## Bug - [ ] Related issues linked using `fixes #number` - [ ] Integration tests added - [ ] Errors have helpful link attached, see `contributing.md` ## Feature - [ ] Implements an existing feature request or RFC. Make sure the feature request has been accepted for implementation before opening a PR. - [ ] Related issues linked using `fixes #number` - [ ] Integration tests added - [ ] Documentation added - [ ] Telemetry added. In case of a feature if it's used or not. - [ ] Errors have helpful link attached, see `contributing.md` ## Documentation / Examples - [x] Make sure the linting passes by running `yarn lint` --- docs/advanced-features/react-18/overview.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/advanced-features/react-18/overview.md b/docs/advanced-features/react-18/overview.md index 2df4aa92b3b0..735431c54d17 100644 --- a/docs/advanced-features/react-18/overview.md +++ b/docs/advanced-features/react-18/overview.md @@ -19,10 +19,10 @@ You can now start using React 18's new APIs like `startTransition` and `Suspense Streaming server-rendering (SSR) is an experimental feature in Next.js 12. When enabled, SSR will use the same [Edge Runtime](/docs/api-reference/edge-runtime.md) as [Middleware](/docs/middleware.md). -[Learn how to enable streaming in Next.js.](/docs/react-18/streaming.md) +[Learn how to enable streaming in Next.js.](/docs/advanced-features/react-18/streaming.md) ## React Server Components (Alpha) Server Components are a new feature in React that let you reduce your JavaScript bundle size by separating server and client-side code. Server Components allow developers to build apps that span the server and client, combining the rich interactivity of client-side apps with the improved performance of traditional server rendering. -Server Components are still in research and development. [Learn how to try Server Components](/docs/react-18/server-components.md) as an experimental feature in Next.js. +Server Components are still in research and development. [Learn how to try Server Components](/docs/advanced-features/react-18/server-components.md) as an experimental feature in Next.js. From d4eea7593add7a7ff257ee01aeb1b75433b1334d Mon Sep 17 00:00:00 2001 From: JJ Kasper Date: Thu, 17 Feb 2022 11:05:40 -0600 Subject: [PATCH 27/28] Fix snippet language sh -> bash (#34487) --- errors/opening-an-issue.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/errors/opening-an-issue.md b/errors/opening-an-issue.md index 179f895135e4..34a08b391843 100644 --- a/errors/opening-an-issue.md +++ b/errors/opening-an-issue.md @@ -10,13 +10,13 @@ Some issues may already be fixed in the canary version, so please verify that yo Run the following in the codebase: -```sh +```bash npm install next@canary ``` or -```sh +```bash yarn add next@canary ``` From eddabd98f8e4e5a82f4341bc1d8926959327919a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bal=C3=A1zs=20Orb=C3=A1n?= Date: Thu, 17 Feb 2022 19:21:40 +0100 Subject: [PATCH 28/28] refactor: move `HtmlContext` (#34482) The shared utils file included an import from `react` (because it was using `createContext`) which seems to be unnecessary in the Middleware bundle. With this PR and steps #34425 laid out, the bundle size did decrease without breaking functionality. ![image](https://user-images.githubusercontent.com/18369201/154508389-0a813e3e-1e07-4c45-8b71-444cc54a7f9e.png) Fixes #34425 ## Bug - [ ] Related issues linked using `fixes #number` - [ ] Integration tests added - [ ] Errors have helpful link attached, see `contributing.md` ## Feature - [ ] Implements an existing feature request or RFC. Make sure the feature request has been accepted for implementation before opening a PR. - [ ] Related issues linked using `fixes #number` - [ ] Integration tests added - [ ] Documentation added - [ ] Telemetry added. In case of a feature if it's used or not. - [ ] Errors have helpful link attached, see `contributing.md` ## Documentation / Examples - [ ] Make sure the linting passes by running `yarn lint` --- packages/next/pages/_document.tsx | 7 +- packages/next/server/render.tsx | 6 +- packages/next/shared/lib/html-context.ts | 42 +++++++++++ packages/next/shared/lib/router/router.ts | 2 +- .../shared/lib/router/utils/format-url.ts | 35 ++++++++- packages/next/shared/lib/utils.ts | 74 +------------------ 6 files changed, 85 insertions(+), 81 deletions(-) create mode 100644 packages/next/shared/lib/html-context.ts diff --git a/packages/next/pages/_document.tsx b/packages/next/pages/_document.tsx index 91b0b526fea9..6af1f81fc86a 100644 --- a/packages/next/pages/_document.tsx +++ b/packages/next/pages/_document.tsx @@ -1,11 +1,9 @@ import React, { Component, ReactElement, ReactNode, useContext } from 'react' import { OPTIMIZED_FONT_PROVIDERS } from '../shared/lib/constants' -import { +import type { DocumentContext, DocumentInitialProps, DocumentProps, - HtmlContext, - HtmlProps, } from '../shared/lib/utils' import { BuildManifest, getPageFiles } from '../server/get-page-files' import { cleanAmpPath } from '../server/utils' @@ -13,6 +11,9 @@ import { htmlEscapeJsonString } from '../server/htmlescape' import Script, { ScriptProps } from '../client/script' import isError from '../lib/is-error' +import { HtmlContext } from '../shared/lib/html-context' +import type { HtmlProps } from '../shared/lib/html-context' + export { DocumentContext, DocumentInitialProps, DocumentProps } export type OriginProps = { diff --git a/packages/next/server/render.tsx b/packages/next/server/render.tsx index 4c00eb0b92fc..573fdf5d268f 100644 --- a/packages/next/server/render.tsx +++ b/packages/next/server/render.tsx @@ -37,8 +37,6 @@ import { DocumentInitialProps, DocumentProps, DocumentContext, - HtmlContext, - HtmlProps, getDisplayName, isResSent, loadGetInitialProps, @@ -46,6 +44,10 @@ import { RenderPage, RenderPageResult, } from '../shared/lib/utils' + +import { HtmlContext } from '../shared/lib/html-context' +import type { HtmlProps } from '../shared/lib/html-context' + import type { NextApiRequestCookies, __ApiPreviewProps } from './api-utils' import { denormalizePagePath } from './denormalize-page-path' import type { FontManifest } from './font-utils' diff --git a/packages/next/shared/lib/html-context.ts b/packages/next/shared/lib/html-context.ts new file mode 100644 index 000000000000..25baecbd7bd0 --- /dev/null +++ b/packages/next/shared/lib/html-context.ts @@ -0,0 +1,42 @@ +import type { BuildManifest } from '../../server/get-page-files' +import type { NEXT_DATA, MaybeDeferContentHook } from './utils' + +import { createContext } from 'react' + +export type HtmlProps = { + __NEXT_DATA__: NEXT_DATA + dangerousAsPath: string + docComponentsRendered: { + Html?: boolean + Main?: boolean + Head?: boolean + NextScript?: boolean + } + buildManifest: BuildManifest + ampPath: string + inAmpMode: boolean + hybridAmp: boolean + isDevelopment: boolean + dynamicImports: string[] + assetPrefix?: string + canonicalBase: string + headTags: any[] + unstable_runtimeJS?: false + unstable_JsPreload?: false + devOnlyCacheBusterQueryString: string + scriptLoader: { afterInteractive?: string[]; beforeInteractive?: any[] } + locale?: string + disableOptimizedLoading?: boolean + styles?: React.ReactElement[] | React.ReactFragment + head?: Array + useMaybeDeferContent: MaybeDeferContentHook + crossOrigin?: string + optimizeCss?: boolean + optimizeFonts?: boolean + runtime?: 'edge' | 'nodejs' +} + +export const HtmlContext = createContext(null as any) +if (process.env.NODE_ENV !== 'production') { + HtmlContext.displayName = 'HtmlContext' +} diff --git a/packages/next/shared/lib/router/router.ts b/packages/next/shared/lib/router/router.ts index d6ca2f98fba8..d3e172431880 100644 --- a/packages/next/shared/lib/router/router.ts +++ b/packages/next/shared/lib/router/router.ts @@ -22,7 +22,6 @@ import { normalizeLocalePath } from '../i18n/normalize-locale-path' import mitt from '../mitt' import { AppContextType, - formatWithValidation, getLocationOrigin, getURL, loadGetInitialProps, @@ -38,6 +37,7 @@ import resolveRewrites from './utils/resolve-rewrites' import { getRouteMatcher } from './utils/route-matcher' import { getRouteRegex } from './utils/route-regex' import { getMiddlewareRegex } from './utils/get-middleware-regex' +import { formatWithValidation } from './utils/format-url' declare global { interface Window { diff --git a/packages/next/shared/lib/router/utils/format-url.ts b/packages/next/shared/lib/router/utils/format-url.ts index bef24b4e3e7d..205ab30a7142 100644 --- a/packages/next/shared/lib/router/utils/format-url.ts +++ b/packages/next/shared/lib/router/utils/format-url.ts @@ -20,8 +20,8 @@ // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE // USE OR OTHER DEALINGS IN THE SOFTWARE. -import { UrlObject } from 'url' -import { ParsedUrlQuery } from 'querystring' +import type { UrlObject } from 'url' +import type { ParsedUrlQuery } from 'querystring' import * as querystring from './querystring' const slashedProtocols = /https?|ftp|gopher|file/ @@ -71,3 +71,34 @@ export function formatUrl(urlObj: UrlObject) { return `${protocol}${host}${pathname}${search}${hash}` } + +export const urlObjectKeys = [ + 'auth', + 'hash', + 'host', + 'hostname', + 'href', + 'path', + 'pathname', + 'port', + 'protocol', + 'query', + 'search', + 'slashes', +] + +export function formatWithValidation(url: UrlObject): string { + if (process.env.NODE_ENV === 'development') { + if (url !== null && typeof url === 'object') { + Object.keys(url).forEach((key) => { + if (urlObjectKeys.indexOf(key) === -1) { + console.warn( + `Unknown key passed via urlObject into url.format: ${key}` + ) + } + }) + } + } + + return formatUrl(url) +} diff --git a/packages/next/shared/lib/utils.ts b/packages/next/shared/lib/utils.ts index 94196af4f597..f18e19f6cf45 100644 --- a/packages/next/shared/lib/utils.ts +++ b/packages/next/shared/lib/utils.ts @@ -1,4 +1,4 @@ -import type { BuildManifest } from '../../server/get-page-files' +import type { HtmlProps } from './html-context' import type { ComponentType } from 'react' import type { DomainLocale } from '../../server/config' import type { Env } from '@next/env' @@ -6,9 +6,6 @@ import type { IncomingMessage, ServerResponse } from 'http' import type { NextRouter } from './router/router' import type { ParsedUrlQuery } from 'querystring' import type { PreviewData } from 'next/types' -import type { UrlObject } from 'url' -import { createContext } from 'react' -import { formatUrl } from './router/utils/format-url' export type NextComponentType< C extends BaseContext = NextPageContext, @@ -195,39 +192,6 @@ export type MaybeDeferContentHook = ( contentFn: () => JSX.Element ) => [boolean, JSX.Element] -export type HtmlProps = { - __NEXT_DATA__: NEXT_DATA - dangerousAsPath: string - docComponentsRendered: { - Html?: boolean - Main?: boolean - Head?: boolean - NextScript?: boolean - } - buildManifest: BuildManifest - ampPath: string - inAmpMode: boolean - hybridAmp: boolean - isDevelopment: boolean - dynamicImports: string[] - assetPrefix?: string - canonicalBase: string - headTags: any[] - unstable_runtimeJS?: false - unstable_JsPreload?: false - devOnlyCacheBusterQueryString: string - scriptLoader: { afterInteractive?: string[]; beforeInteractive?: any[] } - locale?: string - disableOptimizedLoading?: boolean - styles?: React.ReactElement[] | React.ReactFragment - head?: Array - useMaybeDeferContent: MaybeDeferContentHook - crossOrigin?: string - optimizeCss?: boolean - optimizeFonts?: boolean - runtime?: 'edge' | 'nodejs' -} - /** * Next `API` route request */ @@ -410,37 +374,6 @@ export async function loadGetInitialProps< return props } -export const urlObjectKeys = [ - 'auth', - 'hash', - 'host', - 'hostname', - 'href', - 'path', - 'pathname', - 'port', - 'protocol', - 'query', - 'search', - 'slashes', -] - -export function formatWithValidation(url: UrlObject): string { - if (process.env.NODE_ENV === 'development') { - if (url !== null && typeof url === 'object') { - Object.keys(url).forEach((key) => { - if (urlObjectKeys.indexOf(key) === -1) { - console.warn( - `Unknown key passed via urlObject into url.format: ${key}` - ) - } - }) - } - } - - return formatUrl(url) -} - export const SP = typeof performance !== 'undefined' export const ST = SP && @@ -449,11 +382,6 @@ export const ST = export class DecodeError extends Error {} -export const HtmlContext = createContext(null as any) -if (process.env.NODE_ENV !== 'production') { - HtmlContext.displayName = 'HtmlContext' -} - export interface CacheFs { readFile(f: string): Promise readFileSync(f: string): string