diff --git a/.github/lock.yml b/.github/lock.yml deleted file mode 100644 index c80c7a0a09968d5..000000000000000 --- a/.github/lock.yml +++ /dev/null @@ -1,6 +0,0 @@ -# Configuration for lock-threads - https://github.com/dessant/lock-threads - -# Number of days of inactivity before a closed issue or pull request is locked -daysUntilLock: 30 -# Comment to post before locking. Set to `false` to disable -lockComment: 'This issue has been automatically locked due to no recent activity. If you are running into a similar issue, please open a new issue with a reproduction. Thank you.' diff --git a/.github/workflows/lock.yml b/.github/workflows/lock.yml new file mode 100644 index 000000000000000..c8df2516aa2c9d8 --- /dev/null +++ b/.github/workflows/lock.yml @@ -0,0 +1,25 @@ +name: 'Lock Threads' + +on: + schedule: + # This runs every hour: https://crontab.guru/every-1-hour + - cron: '0 * * * *' + workflow_dispatch: + +permissions: + issues: write + pull-requests: write + +concurrency: + group: lock + +jobs: + action: + runs-on: ubuntu-latest + steps: + - uses: dessant/lock-threads@v3 + with: + github-token: ${{ secrets.LOCK_TOKEN }} + issue-inactive-days: 30 + issue-comment: 'This issue has been automatically locked due to no recent activity. If you are running into a similar issue, please create a new issue with the steps to reproduce. Thank you.' + pr-inactive-days: 30 diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index 4d15b37533f69f9..1cbbfceb7cc5ca7 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -2,7 +2,8 @@ name: 'Stale issue handler' on: workflow_dispatch: schedule: - - cron: '0 0 * * *' + # This runs every day 20 minutes before midnight: https://crontab.guru/#40_23_*_*_* + - cron: '40 23 * * *' jobs: stale: @@ -12,6 +13,7 @@ jobs: id: stale name: 'Close stale issues with no reproduction' with: + repo-token: ${{ secrets.STALE_TOKEN }} only-labels: 'please add a complete reproduction' close-issue-message: 'This issue has been automatically closed after 30 days of inactivity with no reproduction. If you are running into a similar issue, please open a new issue with a reproduction. Thank you.' days-before-issue-close: 1 @@ -19,3 +21,4 @@ jobs: days-before-pr-close: -1 days-before-pr-stale: -1 exempt-issue-labels: 'blocked,must,should,keep' + operation-per-run: 300 # 1 operation per 100 issues, the rest is to label/comment/close diff --git a/docs/advanced-features/custom-app.md b/docs/advanced-features/custom-app.md index 73d65f5666a9721..cf8a8fd1fee3332 100644 --- a/docs/advanced-features/custom-app.md +++ b/docs/advanced-features/custom-app.md @@ -38,14 +38,14 @@ export default MyApp The `Component` prop is the active `page`, so whenever you navigate between routes, `Component` will change to the new `page`. Therefore, any props you send to `Component` will be received by the `page`. -`pageProps` is an object with the initial props that were preloaded for your page by one of our [data fetching methods](/docs/basic-features/data-fetching/index.md), otherwise it's an empty object. +`pageProps` is an object with the initial props that were preloaded for your page by one of our [data fetching methods](/docs/basic-features/data-fetching/overview.md), otherwise it's an empty object. ### Caveats - If your app is running and you added a custom `App`, you'll need to restart the development server. Only required if `pages/_app.js` didn't exist before. - Adding a custom [`getInitialProps`](/docs/api-reference/data-fetching/get-initial-props.md) in your `App` will disable [Automatic Static Optimization](/docs/advanced-features/automatic-static-optimization.md) in pages without [Static Generation](/docs/basic-features/data-fetching/get-static-props.md). - When you add `getInitialProps` in your custom app, you must `import App from "next/app"`, call `App.getInitialProps(appContext)` inside `getInitialProps` and merge the returned object into the return value. -- `App` currently does not support Next.js [Data Fetching methods](/docs/basic-features/data-fetching/index.md) like [`getStaticProps`](/docs/basic-features/data-fetching/get-static-props.md) or [`getServerSideProps`](/docs/basic-features/data-fetching/get-server-side-props.md). +- `App` currently does not support Next.js [Data Fetching methods](/docs/basic-features/data-fetching/overview.md) like [`getStaticProps`](/docs/basic-features/data-fetching/get-static-props.md) or [`getServerSideProps`](/docs/basic-features/data-fetching/get-server-side-props.md). ### TypeScript diff --git a/docs/advanced-features/custom-document.md b/docs/advanced-features/custom-document.md index 3ec0e856bee7b28..b6af8d8b18b1e9b 100644 --- a/docs/advanced-features/custom-document.md +++ b/docs/advanced-features/custom-document.md @@ -54,7 +54,7 @@ The `ctx` object is equivalent to the one received in [`getInitialProps`](/docs/ - `Document` is only rendered in the server, event handlers like `onClick` won't work. - React components outside of `
` will not be initialized by the browser. Do _not_ add application logic here or custom CSS (like `styled-jsx`). If you need shared components in all your pages (like a menu or a toolbar), take a look at the [`App`](/docs/advanced-features/custom-app.md) component instead. - `Document`'s `getInitialProps` function is not called during client-side transitions, nor when a page is [statically optimized](/docs/advanced-features/automatic-static-optimization.md). -- `Document` currently does not support Next.js [Data Fetching methods](/docs/basic-features/data-fetching/index.md) like [`getStaticProps`](/docs/basic-features/data-fetching/get-static-props.md) or [`getServerSideProps`](/docs/basic-features/data-fetching/get-server-side-props.md). +- `Document` currently does not support Next.js [Data Fetching methods](/docs/basic-features/data-fetching/overview.md) like [`getStaticProps`](/docs/basic-features/data-fetching/get-static-props.md) or [`getServerSideProps`](/docs/basic-features/data-fetching/get-server-side-props.md). ## Customizing `renderPage` diff --git a/docs/advanced-features/preview-mode.md b/docs/advanced-features/preview-mode.md index a5b7ac69c3e6594..4d467c9e07401fa 100644 --- a/docs/advanced-features/preview-mode.md +++ b/docs/advanced-features/preview-mode.md @@ -27,7 +27,7 @@ description: Next.js has the preview mode for statically generated pages. You ca -In the [Pages documentation](/docs/basic-features/pages.md) and the [Data Fetching documentation](/docs/basic-features/data-fetching/index.md), we talked about how to pre-render a page at build time (**Static Generation**) using `getStaticProps` and `getStaticPaths`. +In the [Pages documentation](/docs/basic-features/pages.md) and the [Data Fetching documentation](/docs/basic-features/data-fetching/overview.md), we talked about how to pre-render a page at build time (**Static Generation**) using `getStaticProps` and `getStaticPaths`. Static Generation is useful when your pages fetch data from a headless CMS. However, it’s not ideal when you’re writing a draft on your headless CMS and want to **preview** the draft immediately on your page. You’d want Next.js to render these pages at **request time** instead of build time and fetch the draft content instead of the published content. You’d want Next.js to bypass Static Generation only for this specific case. @@ -230,7 +230,7 @@ This ensures that the bypass cookie can’t be guessed. The following pages might also be useful.
- + Data Fetching: Learn more about data fetching in Next.js. diff --git a/docs/advanced-features/security-headers.md b/docs/advanced-features/security-headers.md index 9a3165c0e9ead90..772d085a09b907f 100644 --- a/docs/advanced-features/security-headers.md +++ b/docs/advanced-features/security-headers.md @@ -43,7 +43,7 @@ This header controls DNS prefetching, allowing browsers to proactively perform d This header informs browsers it should only be accessed using HTTPS, instead of using HTTP. Using the configuration below, all present and future subdomains will use HTTPS for a `max-age` of 2 years. This blocks access to pages or subdomains that can only be served over HTTP. -If you're deploying to [Vercel](https://vercel.com/docs/edge-network/headers#strict-transport-security), this header is not necessary as it's automatically added to all deployments. +If you're deploying to [Vercel](https://vercel.com/docs/edge-network/headers#strict-transport-security), this header is not necessary as it's automatically added to all deployments unless you declare [`headers`](/docs/api-reference/next.config.js/headers.md) in your `next.config.js`. ```jsx { diff --git a/docs/api-reference/data-fetching/get-initial-props.md b/docs/api-reference/data-fetching/get-initial-props.md index f8295dbda52c225..004ba283a4ff5f5 100644 --- a/docs/api-reference/data-fetching/get-initial-props.md +++ b/docs/api-reference/data-fetching/get-initial-props.md @@ -119,7 +119,7 @@ export default class Page extends React.Component { For more information on what to do next, we recommend the following sections:
- + Data Fetching: Learn more about data fetching in Next.js. diff --git a/docs/api-reference/data-fetching/get-server-side-props.md b/docs/api-reference/data-fetching/get-server-side-props.md index af38bfb9ef2ec84..d7e7c1ee6ef50ff 100644 --- a/docs/api-reference/data-fetching/get-server-side-props.md +++ b/docs/api-reference/data-fetching/get-server-side-props.md @@ -144,7 +144,7 @@ export default Page For more information on what to do next, we recommend the following sections:
- + Data Fetching: Learn more about data fetching in Next.js. diff --git a/docs/api-reference/data-fetching/get-static-props.md b/docs/api-reference/data-fetching/get-static-props.md index d83263f429b88c4..7e04d1d9252e7d0 100644 --- a/docs/api-reference/data-fetching/get-static-props.md +++ b/docs/api-reference/data-fetching/get-static-props.md @@ -237,7 +237,7 @@ export default Blog For more information on what to do next, we recommend the following sections:
- + Data Fetching: Learn more about data fetching in Next.js. diff --git a/docs/api-reference/next/router.md b/docs/api-reference/next/router.md index d595c3ac54703ca..69cacb1e66974ac 100644 --- a/docs/api-reference/next/router.md +++ b/docs/api-reference/next/router.md @@ -42,7 +42,7 @@ export default ActiveLink The following is the definition of the `router` object returned by both [`useRouter`](#useRouter) and [`withRouter`](#withRouter): - `pathname`: `String` - Current route. That is the path of the page in `/pages`, the configured `basePath` or `locale` is not included. -- `query`: `Object` - The query string parsed to an object. It will be an empty object during prerendering if the page doesn't have [data fetching requirements](/docs/basic-features/data-fetching/index.md). Defaults to `{}` +- `query`: `Object` - The query string parsed to an object. It will be an empty object during prerendering if the page doesn't have [data fetching requirements](/docs/basic-features/data-fetching/overview.md). Defaults to `{}` - `asPath`: `String` - The path (including the query) shown in the browser without the configured `basePath` or `locale`. - `isFallback`: `boolean` - Whether the current page is in [fallback mode](/docs/api-reference/data-fetching/get-static-paths.md#fallback-pages). - `basePath`: `String` - The active [basePath](/docs/api-reference/next.config.js/basepath.md) (if enabled). diff --git a/docs/authentication.md b/docs/authentication.md index e8de80d469ab457..b62f4b2879afd9f 100644 --- a/docs/authentication.md +++ b/docs/authentication.md @@ -8,7 +8,7 @@ Authentication verifies who a user is, while authorization controls what a user ## Authentication Patterns -The first step to identifying which authentication pattern you need is understanding the [data-fetching strategy](/docs/basic-features/data-fetching/index.md) you want. We can then determine which authentication providers support this strategy. There are two main patterns: +The first step to identifying which authentication pattern you need is understanding the [data-fetching strategy](/docs/basic-features/data-fetching/overview.md) you want. We can then determine which authentication providers support this strategy. There are two main patterns: - Use [static generation](/docs/basic-features/pages.md#static-generation-recommended) to server-render a loading state, followed by fetching user data client-side. - Fetch user data [server-side](/docs/basic-features/pages.md#server-side-rendering) to eliminate a flash of unauthenticated content. @@ -156,7 +156,7 @@ For more information on what to do next, we recommend the following sections:
- + Data Fetching: Learn more about data fetching in Next.js. 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 da2f5e39aae89ab..22aa4d77dbf7326 100644 --- a/docs/basic-features/data-fetching/get-server-side-props.md +++ b/docs/basic-features/data-fetching/get-server-side-props.md @@ -33,7 +33,7 @@ The [`getServerSideProps` API reference](/docs/api-reference/data-fetching/get-s ## When should I use getServerSideProps -You should use `getServerSideProps` only if you need to pre-render a page whose data must be fetched at request time. [Time to First Byte (TTFB)](/learn/seo/web-performance) will be higher than [`getStaticProps`](/docs/basic-features/data-fetching/get-static-props.md) because the server must compute the result on every request, and the result can only be cached by a CDN using `cache-control` headers (which could require extra configuration). +You should use `getServerSideProps` only if you need to pre-render a page whose data must be fetched at request time. [Time to First Byte (TTFB)](https://web.dev/ttfb/) will be higher than [`getStaticProps`](/docs/basic-features/data-fetching/get-static-props.md) because the server must compute the result on every request, and the result can only be cached by a CDN using `cache-control` headers (which could require extra configuration). If you do not need to pre-render the data, then you should consider fetching data on the [client side](#fetching-data-on-the-client-side). diff --git a/docs/basic-features/data-fetching/index.md b/docs/basic-features/data-fetching/overview.md similarity index 93% rename from docs/basic-features/data-fetching/index.md rename to docs/basic-features/data-fetching/overview.md index d414515f11ad86c..290e189dbe45f64 100644 --- a/docs/basic-features/data-fetching/index.md +++ b/docs/basic-features/data-fetching/overview.md @@ -1,5 +1,5 @@ --- -description: 'Data fetching in Next.js allows you to render your content in different ways, depending on your applications use case. These include pre-rendering with server-side rendering or static-site generation, and incremental static regeneration. Learn how to manage your application data in Next.js.' +description: 'Next.js allows you to fetch data in multiple ways, with pre-rendering, server-side rendering or static-site generation, and incremental static regeneration. Learn how to manage your application data in Next.js.' --- # Data Fetching Overview diff --git a/docs/basic-features/environment-variables.md b/docs/basic-features/environment-variables.md index 4e9211bc2f9f353..91182cbb2f3f963 100644 --- a/docs/basic-features/environment-variables.md +++ b/docs/basic-features/environment-variables.md @@ -30,7 +30,7 @@ DB_USER=myuser DB_PASS=mypassword ``` -This loads `process.env.DB_HOST`, `process.env.DB_USER`, and `process.env.DB_PASS` into the Node.js environment automatically allowing you to use them in [Next.js data fetching methods](/docs/basic-features/data-fetching/index.md) and [API routes](/docs/api-routes/introduction.md). +This loads `process.env.DB_HOST`, `process.env.DB_USER`, and `process.env.DB_PASS` into the Node.js environment automatically allowing you to use them in [Next.js data fetching methods](/docs/basic-features/data-fetching/overview.md) and [API routes](/docs/api-routes/introduction.md). For example, using [`getStaticProps`](/docs/basic-features/data-fetching/get-static-props.md): diff --git a/docs/basic-features/pages.md b/docs/basic-features/pages.md index e76d5197366742d..76bda662b3f16f7 100644 --- a/docs/basic-features/pages.md +++ b/docs/basic-features/pages.md @@ -263,7 +263,7 @@ We've discussed two forms of pre-rendering for Next.js. We recommend you to read the following sections next:
- + Data Fetching: Learn more about data fetching in Next.js. diff --git a/docs/faq.md b/docs/faq.md index 187f8836762c33a..96025209501f57f 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -39,7 +39,7 @@ description: Get to know more about Next.js with the frequently asked questions.
How do I fetch data? -

It's up to you. You can use the fetch API or SWR inside your React components for remote data fetching; or use our data fetching methods for initial data population.

+

It's up to you. You can use the fetch API or SWR inside your React components for remote data fetching; or use our data fetching methods for initial data population.

diff --git a/docs/getting-started.md b/docs/getting-started.md index a9d0501b1941bef..b01d60fd909276a 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -91,7 +91,7 @@ So far, we get: - Automatic compilation and bundling (with webpack and babel) - [React Fast Refresh](https://nextjs.org/blog/next-9-4#fast-refresh) -- [Static generation and server-side rendering](/docs/basic-features/data-fetching/index.md) of [`./pages/`](/docs/basic-features/pages.md) +- [Static generation and server-side rendering](/docs/basic-features/data-fetching/overview.md) of [`./pages/`](/docs/basic-features/pages.md) - [Static file serving](/docs/basic-features/static-file-serving.md). `./public/` is mapped to `/` In addition, any Next.js application is ready for production from the start, read more in our [Deployment documentation](/docs/deployment.md). diff --git a/docs/manifest.json b/docs/manifest.json index b45154c19f5e15e..bdb700f85474163 100644 --- a/docs/manifest.json +++ b/docs/manifest.json @@ -18,7 +18,15 @@ "routes": [ { "title": "Overview", - "path": "/docs/basic-features/data-fetching/index.md" + "path": "/docs/basic-features/data-fetching/index.md", + "redirect": { + "destination": "/docs/basic-features/data-fetching/overview.md", + "permanent": true + } + }, + { + "title": "Overview", + "path": "/docs/basic-features/data-fetching/overview.md" }, { "title": "getServerSideProps", diff --git a/docs/migrating/from-create-react-app.md b/docs/migrating/from-create-react-app.md index 77697644ec1c496..54bf69da832abe4 100644 --- a/docs/migrating/from-create-react-app.md +++ b/docs/migrating/from-create-react-app.md @@ -6,7 +6,7 @@ description: Learn how to transition an existing Create React App project to Nex This guide will help you understand how to transition from an existing non-ejected Create React App project to Next.js. Migrating to Next.js will allow you to: -- Choose which [data fetching](/docs/basic-features/data-fetching/index.md) strategy you want on a per-page basis. +- Choose which [data fetching](/docs/basic-features/data-fetching/overview.md) strategy you want on a per-page basis. - Use [Incremental Static Regeneration](/docs/basic-features/data-fetching/incremental-static-regeneration.md) to update _existing_ pages by re-rendering them in the background as traffic comes in. - Use [API Routes](/docs/api-routes/introduction.md). diff --git a/docs/migrating/from-gatsby.md b/docs/migrating/from-gatsby.md index fa46c9819c22b71..36d27b38a8b1e95 100644 --- a/docs/migrating/from-gatsby.md +++ b/docs/migrating/from-gatsby.md @@ -6,7 +6,7 @@ description: Learn how to transition an existing Gatsby project to Next.js. This guide will help you understand how to transition from an existing Gatsby project to Next.js. Migrating to Next.js will allow you to: -- Choose which [data fetching](/docs/basic-features/data-fetching/index.md) strategy you want on a per-page basis. +- Choose which [data fetching](/docs/basic-features/data-fetching/overview.md) strategy you want on a per-page basis. - Use [Incremental Static Regeneration](/docs/basic-features/data-fetching/incremental-static-regeneration.md) to update _existing_ pages by re-rendering them in the background as traffic comes in. - Use [API Routes](/docs/api-routes/introduction.md). @@ -92,7 +92,7 @@ Update any import statements, switch `to` to `href`, and add an `` tag as a c The largest difference between Gatsby and Next.js is how data fetching is implemented. Gatsby is opinionated with GraphQL being the default strategy for retrieving data across your application. With Next.js, you get to choose which strategy you want (GraphQL is one supported option). -Gatsby uses the `graphql` tag to query data in the pages of your site. This may include local data, remote data, or information about your site configuration. Gatsby only allows the creation of static pages. With Next.js, you can choose on a [per-page basis](/docs/basic-features/pages.md) which [data fetching strategy](/docs/basic-features/data-fetching/index.md) you want. For example, `getServerSideProps` allows you to do server-side rendering. If you wanted to generate a static page, you'd export `getStaticProps` / `getStaticPaths` inside the page, rather than using `pageQuery`. For example: +Gatsby uses the `graphql` tag to query data in the pages of your site. This may include local data, remote data, or information about your site configuration. Gatsby only allows the creation of static pages. With Next.js, you can choose on a [per-page basis](/docs/basic-features/pages.md) which [data fetching strategy](/docs/basic-features/data-fetching/overview.md) you want. For example, `getServerSideProps` allows you to do server-side rendering. If you wanted to generate a static page, you'd export `getStaticProps` / `getStaticPaths` inside the page, rather than using `pageQuery`. For example: ```js // src/pages/[slug].js diff --git a/docs/testing.md b/docs/testing.md index cd9c5239d1507dc..6e1c240d03b13b8 100644 --- a/docs/testing.md +++ b/docs/testing.md @@ -341,7 +341,6 @@ module.exports = { '/node_modules/', '^.+\\.module\\.(css|sass|scss)$', ], - testEnvironment: 'jest-environment-jsdom', } ``` diff --git a/lerna.json b/lerna.json index 0c1bdc4ed11a7d2..3fbc8625cdba1a0 100644 --- a/lerna.json +++ b/lerna.json @@ -16,5 +16,5 @@ "registry": "https://registry.npmjs.org/" } }, - "version": "12.0.9-canary.11" + "version": "12.0.9" } diff --git a/packages/create-next-app/package.json b/packages/create-next-app/package.json index a2f4ae3fd849f40..96b77e4e77616f7 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.9-canary.11", + "version": "12.0.9", "keywords": [ "react", "next", diff --git a/packages/eslint-config-next/package.json b/packages/eslint-config-next/package.json index a09e3aab9169945..505f4628c3c3381 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.9-canary.11", + "version": "12.0.9", "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.9-canary.11", + "@next/eslint-plugin-next": "12.0.9", "@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 753a616e767174a..d902660f367b632 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.9-canary.11", + "version": "12.0.9", "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 53014fc2f13836b..185a79d13f80eb7 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.9-canary.11", + "version": "12.0.9", "main": "index.js", "license": "MIT", "repository": { diff --git a/packages/next-codemod/package.json b/packages/next-codemod/package.json index bfc20a368af0c1e..878fa830ed5efa3 100644 --- a/packages/next-codemod/package.json +++ b/packages/next-codemod/package.json @@ -1,6 +1,6 @@ { "name": "@next/codemod", - "version": "12.0.9-canary.11", + "version": "12.0.9", "license": "MIT", "dependencies": { "chalk": "4.1.0", diff --git a/packages/next-env/package.json b/packages/next-env/package.json index 42ee11d2716442a..5617323e10e0fb6 100644 --- a/packages/next-env/package.json +++ b/packages/next-env/package.json @@ -1,6 +1,6 @@ { "name": "@next/env", - "version": "12.0.9-canary.11", + "version": "12.0.9", "keywords": [ "react", "next", diff --git a/packages/next-mdx/package.json b/packages/next-mdx/package.json index 1c8f16daae6659f..def2ec94e682461 100644 --- a/packages/next-mdx/package.json +++ b/packages/next-mdx/package.json @@ -1,6 +1,6 @@ { "name": "@next/mdx", - "version": "12.0.9-canary.11", + "version": "12.0.9", "main": "index.js", "license": "MIT", "repository": { diff --git a/packages/next-plugin-storybook/package.json b/packages/next-plugin-storybook/package.json index 69952383b13903e..3b7791aa611f509 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.9-canary.11", + "version": "12.0.9", "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 fe68db7353459cf..b1bf84d56f61e73 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.9-canary.11", + "version": "12.0.9", "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 a4b218b9fcb80e8..1f3736c96b786c7 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.9-canary.11", + "version": "12.0.9", "description": "A polyfill for non-dead, nomodule browsers.", "main": "dist/polyfill-nomodule.js", "license": "MIT", diff --git a/packages/next-swc/crates/napi/src/lib.rs b/packages/next-swc/crates/napi/src/lib.rs index 4d097014d18bed6..dcc6441fbff6a0c 100644 --- a/packages/next-swc/crates/napi/src/lib.rs +++ b/packages/next-swc/crates/napi/src/lib.rs @@ -44,7 +44,7 @@ mod bundle; mod minify; mod transform; mod util; - +mod parse; static COMPILER: Lazy> = Lazy::new(|| { let cm = Arc::new(SourceMap::new(FilePathMapping::empty())); @@ -68,6 +68,8 @@ fn init(mut exports: JsObject) -> napi::Result<()> { exports.create_named_method("minify", minify::minify)?; exports.create_named_method("minifySync", minify::minify_sync)?; + + exports.create_named_method("parse", parse::parse)?; Ok(()) } diff --git a/packages/next-swc/crates/napi/src/parse.rs b/packages/next-swc/crates/napi/src/parse.rs new file mode 100644 index 000000000000000..a6cb68a0291bf47 --- /dev/null +++ b/packages/next-swc/crates/napi/src/parse.rs @@ -0,0 +1,70 @@ +use crate::util::{deserialize_json, CtxtExt, MapErr}; +use anyhow::Context as _; +use napi::{CallContext, Either, Env, JsObject, JsString, JsUndefined, Task}; +use std::sync::Arc; +use swc::{config::ParseOptions, try_with_handler}; +use swc_common::{FileName, FilePathMapping, SourceMap}; + +pub struct ParseTask { + pub filename: FileName, + pub src: String, + pub options: String, +} + +pub fn complete_parse<'a>(env: &Env, ast_json: String) -> napi::Result { + env.create_string_from_std(ast_json) +} + +impl Task for ParseTask { + type Output = String; + type JsValue = JsString; + + fn compute(&mut self) -> napi::Result { + let c = swc::Compiler::new(Arc::new(SourceMap::new(FilePathMapping::empty()))); + + let options: ParseOptions = deserialize_json(&self.options).convert_err()?; + let fm = + c.cm.new_source_file(self.filename.clone(), self.src.clone()); + let program = try_with_handler(c.cm.clone(), false, |handler| { + c.parse_js( + fm, + &handler, + options.target, + options.syntax, + options.is_module, + options.comments, + ) + }) + .convert_err()?; + + let ast_json = serde_json::to_string(&program) + .context("failed to serialize Program") + .convert_err()?; + + Ok(ast_json) + } + + fn resolve(self, env: Env, result: Self::Output) -> napi::Result { + complete_parse(&env, result) + } +} + +#[js_function(3)] +pub fn parse(ctx: CallContext) -> napi::Result { + let src = ctx.get::(0)?.into_utf8()?.as_str()?.to_string(); + let options = ctx.get_buffer_as_string(1)?; + let filename = ctx.get::>(2)?; + let filename = if let Either::A(value) = filename { + FileName::Real(value.into_utf8()?.as_str()?.to_owned().into()) + } else { + FileName::Anon + }; + + ctx.env + .spawn(ParseTask { + filename, + src, + options, + }) + .map(|t| t.promise_object()) +} diff --git a/packages/next-swc/package.json b/packages/next-swc/package.json index 3fe4143e6caf29b..be84cbfa3e27bcf 100644 --- a/packages/next-swc/package.json +++ b/packages/next-swc/package.json @@ -1,6 +1,6 @@ { "name": "@next/swc", - "version": "12.0.9-canary.11", + "version": "12.0.9", "private": true, "scripts": { "build-native": "napi build --platform --cargo-name next_swc_napi native", diff --git a/packages/next/build/index.ts b/packages/next/build/index.ts index 89cc94663989c15..7f308632333f525 100644 --- a/packages/next/build/index.ts +++ b/packages/next/build/index.ts @@ -551,11 +551,7 @@ export default async function build( .traceChild('generate-required-server-files') .traceFn(() => ({ version: 1, - config: { - ...config, - compress: false, - configFile: undefined, - }, + config: { ...config, configFile: undefined }, appDir: dir, files: [ ROUTES_MANIFEST, diff --git a/packages/next/build/swc/index.js b/packages/next/build/swc/index.js index 6907c51e8605139..8998782147c3abf 100644 --- a/packages/next/build/swc/index.js +++ b/packages/next/build/swc/index.js @@ -72,6 +72,9 @@ async function loadWasm() { minify(src, options) { return Promise.resolve(bindings.minifySync(src.toString(), options)) }, + parse(src, options) { + return Promise.resolve(bindings.parse(src.toString(), options)) + }, } return wasmBindings } catch (e) { @@ -179,6 +182,10 @@ function loadNative() { bundle(options) { return bindings.bundle(toBuffer(options)) }, + + parse(src, options) { + return bindings.parse(src, toBuffer(options ?? {})) + }, } return nativeBindings } @@ -219,3 +226,8 @@ export async function bundle(options) { let bindings = loadBindingsSync() return bindings.bundle(toBuffer(options)) } + +export async function parse(src, options) { + let bindings = loadBindingsSync() + return bindings.parse(src, options).then((astStr) => JSON.parse(astStr)) +} diff --git a/packages/next/build/swc/options.js b/packages/next/build/swc/options.js index 27bf7878acce618..d58d461dff1930f 100644 --- a/packages/next/build/swc/options.js +++ b/packages/next/build/swc/options.js @@ -5,7 +5,7 @@ const regeneratorRuntimePath = require.resolve( 'next/dist/compiled/regenerator-runtime' ) -function getBaseSWCOptions({ +export function getBaseSWCOptions({ filename, jest, development, @@ -45,9 +45,9 @@ function getBaseSWCOptions({ pragma: 'React.createElement', pragmaFrag: 'React.Fragment', throwIfNamespace: true, - development: development, + development: !!development, useBuiltins: true, - refresh: hasReactRefresh, + refresh: !!hasReactRefresh, }, optimizer: { simplify: false, diff --git a/packages/next/build/webpack/loaders/next-flight-client-loader.ts b/packages/next/build/webpack/loaders/next-flight-client-loader.ts index 2a78484a48684d7..6a916a364e39f0c 100644 --- a/packages/next/build/webpack/loaders/next-flight-client-loader.ts +++ b/packages/next/build/webpack/loaders/next-flight-client-loader.ts @@ -5,29 +5,16 @@ * LICENSE file in the root directory of this source tree. */ -import * as acorn from 'next/dist/compiled/acorn' - -type ResolveContext = { - conditions: Array - parentURL: string | void -} - -type ResolveFunction = ( - specifier: string, - context: ResolveContext, - resolve: ResolveFunction -) => { url: string } | Promise<{ url: string }> - -type TransformSourceFunction = (url: string, callback: () => void) => void - -type Source = string | ArrayBuffer | Uint8Array - -let stashedResolve: null | ResolveFunction = null +// TODO: add ts support for next-swc api +// @ts-ignore +import { parse } from '../../swc' +// @ts-ignore +import { getBaseSWCOptions } from '../../swc/options' function addExportNames(names: string[], node: any) { switch (node.type) { case 'Identifier': - names.push(node.name) + names.push(node.value) return case 'ObjectPattern': for (let i = 0; i < node.properties.length; i++) @@ -56,50 +43,25 @@ function addExportNames(names: string[], node: any) { } } -function resolveClientImport( - specifier: string, - parentURL: string -): { url: string } | Promise<{ url: string }> { - // Resolve an import specifier as if it was loaded by the client. This doesn't use - // the overrides that this loader does but instead reverts to the default. - // This resolution algorithm will not necessarily have the same configuration - // as the actual client loader. It should mostly work and if it doesn't you can - // always convert to explicit exported names instead. - const conditions = ['node', 'import'] - if (stashedResolve === null) { - throw new Error( - 'Expected resolve to have been called before transformSource' - ) - } - return stashedResolve(specifier, { conditions, parentURL }, stashedResolve) -} - async function parseExportNamesInto( + resourcePath: string, transformedSource: string, - names: Array, - parentURL: string, - loadModule: TransformSourceFunction + names: Array ): Promise { - const { body } = acorn.parse(transformedSource, { - ecmaVersion: 11, - sourceType: 'module', - }) as any + const opts = getBaseSWCOptions({ + filename: resourcePath, + globalWindow: true, + }) + + const { body } = await parse(transformedSource, { + ...opts.jsc.parser, + isModule: true, + }) for (let i = 0; i < body.length; i++) { const node = body[i] switch (node.type) { - case 'ExportAllDeclaration': - if (node.exported) { - addExportNames(names, node.exported) - continue - } else { - const { url } = await resolveClientImport( - node.source.value, - parentURL - ) - const source = '' - parseExportNamesInto(source, names, url, loadModule) - continue - } + // TODO: support export * from module path + // case 'ExportAllDeclaration': case 'ExportDefaultDeclaration': names.push('default') continue @@ -129,8 +91,8 @@ async function parseExportNamesInto( export default async function transformSource( this: any, - source: Source -): Promise { + source: string +): Promise { const { resourcePath, resourceQuery } = this if (resourceQuery !== '?flight') return source @@ -142,12 +104,7 @@ export default async function transformSource( } const names: string[] = [] - await parseExportNamesInto( - transformedSource as string, - names, - url + resourceQuery, - this.loadModule - ) + await parseExportNamesInto(resourcePath, transformedSource, names) // next.js/packages/next/.js if (/[\\/]next[\\/](link|image)\.js$/.test(url)) { diff --git a/packages/next/build/webpack/loaders/next-flight-server-loader.ts b/packages/next/build/webpack/loaders/next-flight-server-loader.ts index 6c7f1bfc1eabb11..8d5b84927e235c8 100644 --- a/packages/next/build/webpack/loaders/next-flight-server-loader.ts +++ b/packages/next/build/webpack/loaders/next-flight-server-loader.ts @@ -1,4 +1,8 @@ -import * as acorn from 'next/dist/compiled/acorn' +// TODO: add ts support for next-swc api +// @ts-ignore +import { parse } from '../../swc' +// @ts-ignore +import { getBaseSWCOptions } from '../../swc/options' import { getRawPageExtensions } from '../../utils' function isClientComponent(importSource: string, pageExtensions: string[]) { @@ -28,6 +32,7 @@ export function isImageImport(importSource: string) { } async function parseImportsInfo( + resourcePath: string, source: string, imports: Array, isClientCompilation: boolean, @@ -36,21 +41,22 @@ async function parseImportsInfo( source: string defaultExportName: string }> { - const { body } = acorn.parse(source, { - ecmaVersion: 11, - sourceType: 'module', - }) as any - + const opts = getBaseSWCOptions({ + filename: resourcePath, + globalWindow: isClientCompilation, + }) + + const ast = await parse(source, { ...opts.jsc.parser, isModule: true }) + const { body } = ast + const beginPos = ast.span.start let transformedSource = '' let lastIndex = 0 - let defaultExportName = 'RSCComponent' - + let defaultExportName for (let i = 0; i < body.length; i++) { const node = body[i] switch (node.type) { case 'ImportDeclaration': { const importSource = node.source.value - if (!isClientCompilation) { if ( !( @@ -61,10 +67,11 @@ async function parseImportsInfo( ) { continue } - transformedSource += source.substring( + const importDeclarations = source.substring( lastIndex, - node.source.start - 1 + node.source.span.start - beginPos ) + transformedSource += importDeclarations transformedSource += JSON.stringify(`${node.source.value}?flight`) } else { // For the client compilation, we skip all modules imports but @@ -84,16 +91,16 @@ async function parseImportsInfo( } } - lastIndex = node.source.end + lastIndex = node.source.span.end - beginPos imports.push(`require(${JSON.stringify(importSource)})`) continue } case 'ExportDefaultDeclaration': { - const def = node.declaration + const def = node.decl if (def.type === 'Identifier') { defaultExportName = def.name - } else if (def.type === 'FunctionDeclaration') { - defaultExportName = def.id.name + } else if (def.type === 'FunctionExpression') { + defaultExportName = def.identifier.value } break } @@ -129,6 +136,7 @@ export default async function transformSource( const imports: string[] = [] const { source: transformedSource, defaultExportName } = await parseImportsInfo( + resourcePath, source, imports, isClientCompilation, @@ -150,7 +158,9 @@ export default async function transformSource( const noop = `export const __rsc_noop__=()=>{${imports.join(';')}}` const defaultExportNoop = isClientCompilation ? `export default function ${defaultExportName}(){}\n${defaultExportName}.__next_rsc__=1;` - : `${defaultExportName}.__next_rsc__=1;` + : defaultExportName + ? `${defaultExportName}.__next_rsc__=1;` + : '' const transformed = transformedSource + '\n' + noop + '\n' + defaultExportNoop diff --git a/packages/next/build/webpack/loaders/next-middleware-ssr-loader/index.ts b/packages/next/build/webpack/loaders/next-middleware-ssr-loader/index.ts index 4f23d19936fcaaa..5341b620aba8b25 100644 --- a/packages/next/build/webpack/loaders/next-middleware-ssr-loader/index.ts +++ b/packages/next/build/webpack/loaders/next-middleware-ssr-loader/index.ts @@ -14,28 +14,26 @@ export default async function middlewareSSRLoader(this: any) { stringifiedConfig, } = this.getOptions() - const stringifiedAbsolutePagePath = stringifyRequest(this, absolutePagePath) - const stringifiedAbsoluteAppPath = stringifyRequest(this, absoluteAppPath) - const stringifiedAbsolute500PagePath = stringifyRequest( - this, - absolute500Path || absoluteErrorPath - ) - const stringifiedAbsoluteDocumentPath = stringifyRequest( - this, - absoluteDocumentPath - ) + const stringifiedPagePath = stringifyRequest(this, absolutePagePath) + const stringifiedAppPath = stringifyRequest(this, absoluteAppPath) + const stringifiedErrorPath = stringifyRequest(this, absoluteErrorPath) + const stringifiedDocumentPath = stringifyRequest(this, absoluteDocumentPath) + const stringified500Path = absolute500Path + ? stringifyRequest(this, absolute500Path) + : 'null' const transformed = ` import { adapter } from 'next/dist/server/web/adapter' import { RouterContext } from 'next/dist/shared/lib/router-context' - import App from ${stringifiedAbsoluteAppPath} - import Document from ${stringifiedAbsoluteDocumentPath} - import { getRender } from 'next/dist/build/webpack/loaders/next-middleware-ssr-loader/render' - const pageMod = require(${stringifiedAbsolutePagePath}) - const errorMod = require(${stringifiedAbsolute500PagePath}) + import App from ${stringifiedAppPath} + import Document from ${stringifiedDocumentPath} + + const pageMod = require(${stringifiedPagePath}) + const errorMod = require(${stringifiedErrorPath}) + const error500Mod = ${stringified500Path} ? require(${stringified500Path}) : null const buildManifest = self.__BUILD_MANIFEST const reactLoadableManifest = self.__REACT_LOADABLE_MANIFEST @@ -62,6 +60,7 @@ export default async function middlewareSSRLoader(this: any) { // components errorMod, + error500Mod, // renderOpts buildId: ${JSON.stringify(buildId)}, diff --git a/packages/next/package.json b/packages/next/package.json index d9e75b45ae5cd7e..acd0260841b02e6 100644 --- a/packages/next/package.json +++ b/packages/next/package.json @@ -1,6 +1,6 @@ { "name": "next", - "version": "12.0.9-canary.11", + "version": "12.0.9", "description": "The React Framework", "main": "./dist/server/next.js", "license": "MIT", @@ -71,7 +71,7 @@ ] }, "dependencies": { - "@next/env": "12.0.9-canary.11", + "@next/env": "12.0.9", "caniuse-lite": "^1.0.30001283", "postcss": "8.4.5", "styled-jsx": "5.0.0", @@ -119,11 +119,11 @@ "@hapi/accept": "5.0.2", "@napi-rs/cli": "1.2.1", "@napi-rs/triples": "1.0.3", - "@next/polyfill-module": "12.0.9-canary.11", - "@next/polyfill-nomodule": "12.0.9-canary.11", - "@next/react-dev-overlay": "12.0.9-canary.11", - "@next/react-refresh-utils": "12.0.9-canary.11", - "@next/swc": "12.0.9-canary.11", + "@next/polyfill-module": "12.0.9", + "@next/polyfill-nomodule": "12.0.9", + "@next/react-dev-overlay": "12.0.9", + "@next/react-refresh-utils": "12.0.9", + "@next/swc": "12.0.9", "@peculiar/webcrypto": "1.1.7", "@taskr/clear": "1.1.0", "@taskr/esnext": "1.1.0", diff --git a/packages/next/server/image-optimizer.ts b/packages/next/server/image-optimizer.ts index de5a6315731309a..7a0f7fe1ec1804d 100644 --- a/packages/next/server/image-optimizer.ts +++ b/packages/next/server/image-optimizer.ts @@ -253,8 +253,17 @@ export async function imageOptimizer( mockRes.write = (chunk: Buffer | string) => { resBuffers.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk)) } - mockRes._write = (chunk: Buffer | string) => { + mockRes._write = ( + chunk: Buffer | string, + _encoding: string, + callback: () => void + ) => { mockRes.write(chunk) + // According to Node.js documentation, the callback MUST be invoked to signal that + // the write completed successfully. If this callback is not invoked, the 'finish' event + // will not be emitted. + // https://nodejs.org/docs/latest-v16.x/api/stream.html#writable_writechunk-encoding-callback + callback() } const mockHeaders: Record = {} @@ -290,7 +299,6 @@ export async function imageOptimizer( await handleRequest(mockReq, mockRes, nodeUrl.parse(href, true)) await isStreamFinished res.statusCode = mockRes.statusCode - upstreamBuffer = Buffer.concat(resBuffers) upstreamType = detectContentType(upstreamBuffer) || mockRes.getHeader('Content-Type') @@ -328,7 +336,6 @@ export async function imageOptimizer( ) return { finished: true } } - if (!upstreamType.startsWith('image/')) { res.statusCode = 400 res.end("The requested resource isn't a valid image.") diff --git a/packages/next/server/web-server.ts b/packages/next/server/web-server.ts index 8d63d380b67d260..501d33f5aaaa01f 100644 --- a/packages/next/server/web-server.ts +++ b/packages/next/server/web-server.ts @@ -169,8 +169,26 @@ export default class NextWebServer extends BaseServer { } } + const { errorMod, error500Mod } = (globalThis as any).__server_context + + // If there is a custom 500 page. + if (pathname === '/500' && error500Mod) { + return { + query: { + ...(query || {}), + ...(params || {}), + }, + components: { + ...(globalThis as any).__server_context, + Component: error500Mod.default, + getStaticProps: error500Mod.getStaticProps, + getServerSideProps: error500Mod.getServerSideProps, + getStaticPaths: error500Mod.getStaticPaths, + } as LoadComponentsReturnType, + } + } + if (pathname === '/_error') { - const errorMod = (globalThis as any).__server_context.errorMod return { query: { ...(query || {}), diff --git a/packages/react-dev-overlay/package.json b/packages/react-dev-overlay/package.json index b24f8b4abde2835..a8a7000224e7fa5 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.9-canary.11", + "version": "12.0.9", "description": "A development-only overlay for developing React applications.", "repository": { "url": "vercel/next.js", diff --git a/packages/react-refresh-utils/internal/ReactRefreshModule.runtime.ts b/packages/react-refresh-utils/internal/ReactRefreshModule.runtime.ts index 3085febfb7fd0c7..d4b5814bc6eb8ee 100644 --- a/packages/react-refresh-utils/internal/ReactRefreshModule.runtime.ts +++ b/packages/react-refresh-utils/internal/ReactRefreshModule.runtime.ts @@ -17,67 +17,70 @@ declare const module: { // This function gets unwrapped into global scope, which is why we don't invert // if-blocks. Also, you cannot use `return`. export default function () { - // Legacy CSS implementations will `eval` browser code in a Node.js context - // to extract CSS. For backwards compatibility, we need to check we're in a - // browser context before continuing. - if ( - typeof self !== 'undefined' && - // AMP / No-JS mode does not inject these helpers: - '$RefreshHelpers$' in self - ) { - var currentExports = module.__proto__.exports - var prevExports = module.hot.data?.prevExports ?? null + // Wrapped in an IIFE to avoid polluting the global scope + ;(function () { + // Legacy CSS implementations will `eval` browser code in a Node.js context + // to extract CSS. For backwards compatibility, we need to check we're in a + // browser context before continuing. + if ( + typeof self !== 'undefined' && + // AMP / No-JS mode does not inject these helpers: + '$RefreshHelpers$' in self + ) { + var currentExports = module.__proto__.exports + var prevExports = module.hot.data?.prevExports ?? null - // This cannot happen in MainTemplate because the exports mismatch between - // templating and execution. - self.$RefreshHelpers$.registerExportsForReactRefresh( - currentExports, - module.id - ) + // This cannot happen in MainTemplate because the exports mismatch between + // templating and execution. + self.$RefreshHelpers$.registerExportsForReactRefresh( + currentExports, + module.id + ) - // A module can be accepted automatically based on its exports, e.g. when - // it is a Refresh Boundary. - if (self.$RefreshHelpers$.isReactRefreshBoundary(currentExports)) { - // Save the previous exports on update so we can compare the boundary - // signatures. - module.hot.dispose(function (data) { - data.prevExports = currentExports - }) - // Unconditionally accept an update to this module, we'll check if it's - // still a Refresh Boundary later. - module.hot.accept() + // A module can be accepted automatically based on its exports, e.g. when + // it is a Refresh Boundary. + if (self.$RefreshHelpers$.isReactRefreshBoundary(currentExports)) { + // Save the previous exports on update so we can compare the boundary + // signatures. + module.hot.dispose(function (data) { + data.prevExports = currentExports + }) + // Unconditionally accept an update to this module, we'll check if it's + // still a Refresh Boundary later. + module.hot.accept() - // This field is set when the previous version of this module was a - // Refresh Boundary, letting us know we need to check for invalidation or - // enqueue an update. - if (prevExports !== null) { - // A boundary can become ineligible if its exports are incompatible - // with the previous exports. - // - // For example, if you add/remove/change exports, we'll want to - // re-execute the importing modules, and force those components to - // re-render. Similarly, if you convert a class component to a - // function, we want to invalidate the boundary. - if ( - self.$RefreshHelpers$.shouldInvalidateReactRefreshBoundary( - prevExports, - currentExports - ) - ) { + // This field is set when the previous version of this module was a + // Refresh Boundary, letting us know we need to check for invalidation or + // enqueue an update. + if (prevExports !== null) { + // A boundary can become ineligible if its exports are incompatible + // with the previous exports. + // + // For example, if you add/remove/change exports, we'll want to + // re-execute the importing modules, and force those components to + // re-render. Similarly, if you convert a class component to a + // function, we want to invalidate the boundary. + if ( + self.$RefreshHelpers$.shouldInvalidateReactRefreshBoundary( + prevExports, + currentExports + ) + ) { + module.hot.invalidate() + } else { + self.$RefreshHelpers$.scheduleUpdate() + } + } + } else { + // Since we just executed the code for the module, it's possible that the + // new exports made it ineligible for being a boundary. + // We only care about the case when we were _previously_ a boundary, + // because we already accepted this update (accidental side effect). + var isNoLongerABoundary = prevExports !== null + if (isNoLongerABoundary) { module.hot.invalidate() - } else { - self.$RefreshHelpers$.scheduleUpdate() } } - } else { - // Since we just executed the code for the module, it's possible that the - // new exports made it ineligible for being a boundary. - // We only care about the case when we were _previously_ a boundary, - // because we already accepted this update (accidental side effect). - var isNoLongerABoundary = prevExports !== null - if (isNoLongerABoundary) { - module.hot.invalidate() - } } - } + })() } diff --git a/packages/react-refresh-utils/package.json b/packages/react-refresh-utils/package.json index 73fab878f19790f..66c4a46dcbd41aa 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.9-canary.11", + "version": "12.0.9", "description": "An experimental package providing utilities for React Refresh.", "repository": { "url": "vercel/next.js", diff --git a/scripts/check-manifests.js b/scripts/check-manifests.js index 68091d7fa6cabd0..3a1e8d929fb6a06 100755 --- a/scripts/check-manifests.js +++ b/scripts/check-manifests.js @@ -8,7 +8,7 @@ const glob = promisify(globOrig) function collectPaths(routes, paths = []) { for (const route of routes) { - if (route.path) { + if (route.path && !route.redirect) { paths.push(route.path) } diff --git a/test/development/acceptance/ReactRefreshModule.test.ts b/test/development/acceptance/ReactRefreshModule.test.ts new file mode 100644 index 000000000000000..6dae853adc77c52 --- /dev/null +++ b/test/development/acceptance/ReactRefreshModule.test.ts @@ -0,0 +1,44 @@ +import { createNext } from 'e2e-utils' +import { NextInstance } from 'test/lib/next-modes/base' +import { sandbox } from './helpers' + +describe('ReactRefreshModule', () => { + let next: NextInstance + + beforeAll(async () => { + next = await createNext({ + files: {}, + skipStart: true, + }) + }) + afterAll(() => next.destroy()) + + it('should allow any variable names', async () => { + const { session, cleanup } = await sandbox(next, new Map([])) + expect(await session.hasRedbox()).toBe(false) + + const variables = [ + '_a', + '_b', + 'currentExports', + 'prevExports', + 'isNoLongerABoundary', + ] + + for await (const variable of variables) { + await session.patch( + 'pages/index.js', + `import { default as ${variable} } from 'next/link' + export default function Page() { + return null + }` + ) + expect(await session.hasRedbox()).toBe(false) + expect(next.cliOutput).not.toContain( + `'${variable}' has already been declared` + ) + } + + await cleanup() + }) +}) diff --git a/test/integration/image-optimizer/test/index.test.js b/test/integration/image-optimizer/test/index.test.js index 89f9cd36d7fff31..26ea2bfc9d3a440 100644 --- a/test/integration/image-optimizer/test/index.test.js +++ b/test/integration/image-optimizer/test/index.test.js @@ -779,6 +779,14 @@ function runTests({ expect(await res.text()).toBe("The requested resource isn't a valid image.") }) + it('should error if the image file does not exist', async () => { + const query = { url: '/does_not_exist.jpg', w, q: 80 } + const opts = { headers: { accept: 'image/webp' } } + const res = await fetchViaHTTP(appPort, '/_next/image', query, opts) + expect(res.status).toBe(400) + expect(await res.text()).toBe("The requested resource isn't a valid image.") + }) + it('should handle concurrent requests', async () => { await fs.remove(imagesDir) const query = { url: '/test.png', w, q: 80 } diff --git a/test/integration/react-streaming-and-server-components/app/components/client-exports-all.client.js b/test/integration/react-streaming-and-server-components/app/components/client-exports-all.client.js new file mode 100644 index 000000000000000..aa9197254b20278 --- /dev/null +++ b/test/integration/react-streaming-and-server-components/app/components/client-exports-all.client.js @@ -0,0 +1 @@ +export * from './client-exports' diff --git a/test/integration/react-streaming-and-server-components/app/components/client-exports-all.js b/test/integration/react-streaming-and-server-components/app/components/client-exports-all.js new file mode 100644 index 000000000000000..d7d3f726f5a1d6a --- /dev/null +++ b/test/integration/react-streaming-and-server-components/app/components/client-exports-all.js @@ -0,0 +1,20 @@ +export * from './client-exports' + +// TODO: add exports all test case in pages +/** + +import * as all from '../components/client-exports-all' +import * as allClient from '../components/client-exports-all.client' + +export default function Page() { + const { a, b, c, d, e } = all + const { a: ac, b: bc, c: cc, d: dc, e: ec } = allClient + return ( +
+
{a}{b}{c}{d}{e[0]}
+
{ac}{bc}{cc}{dc}{ec[0]}
+
+ ) +} + +*/ diff --git a/test/integration/react-streaming-and-server-components/app/components/client-exports.js b/test/integration/react-streaming-and-server-components/app/components/client-exports.js new file mode 100644 index 000000000000000..6fd69b677b4d4c0 --- /dev/null +++ b/test/integration/react-streaming-and-server-components/app/components/client-exports.js @@ -0,0 +1,10 @@ +const a = 'a' +const b = 'b' +const _c = 'c' +const _d = 'd' +const _e = 'e' +const _eArr = [_e] + +export const c = _c +export { a, b } +export { _d as d, _eArr as e } diff --git a/test/integration/react-streaming-and-server-components/app/pages/client-exports-all.server.js b/test/integration/react-streaming-and-server-components/app/pages/client-exports-all.server.js new file mode 100644 index 000000000000000..d4a270114143572 --- /dev/null +++ b/test/integration/react-streaming-and-server-components/app/pages/client-exports-all.server.js @@ -0,0 +1,25 @@ +import * as all from '../components/client-exports-all' +import * as allClient from '../components/client-exports-all.client' + +export default function Page() { + const { a, b, c, d, e } = all + const { a: ac, b: bc, c: cc, d: dc, e: ec } = allClient + return ( +
+
+ {a} + {b} + {c} + {d} + {e[0]} +
+
+ {ac} + {bc} + {cc} + {dc} + {ec[0]} +
+
+ ) +} diff --git a/test/integration/react-streaming-and-server-components/app/pages/client-exports.server.js b/test/integration/react-streaming-and-server-components/app/pages/client-exports.server.js new file mode 100644 index 000000000000000..68bb37e7ee1def5 --- /dev/null +++ b/test/integration/react-streaming-and-server-components/app/pages/client-exports.server.js @@ -0,0 +1,13 @@ +import { a, b, c, d, e } from '../components/client-exports' + +export default function Page() { + return ( +
+ {a} + {b} + {c} + {d} + {e[0]} +
+ ) +} diff --git a/test/integration/react-streaming-and-server-components/test/index.test.js b/test/integration/react-streaming-and-server-components/test/index.test.js index dfb68408db28062..a298254caaff563 100644 --- a/test/integration/react-streaming-and-server-components/test/index.test.js +++ b/test/integration/react-streaming-and-server-components/test/index.test.js @@ -400,6 +400,19 @@ async function runBasicTests(context, env) { expect(imageTag.attr('src')).toContain('data:image') }) + it('should handle multiple named exports correctly', async () => { + const clientExportsHTML = await renderViaHTTP( + context.appPort, + '/client-exports' + ) + const $clientExports = cheerio.load(clientExportsHTML) + expect($clientExports('div[hidden] > div').text()).toBe('abcde') + + const browser = await webdriver(context.appPort, '/client-exports') + const text = await browser.waitForElementByCss('#__next').text() + expect(text).toBe('abcde') + }) + it('should support multi-level server component imports', async () => { const html = await renderViaHTTP(context.appPort, '/multi') expect(html).toContain('bar.server.js:') diff --git a/test/production/required-server-files.test.ts b/test/production/required-server-files.test.ts index 25779a1b79f120e..5beb07895d099de 100644 --- a/test/production/required-server-files.test.ts +++ b/test/production/required-server-files.test.ts @@ -107,6 +107,12 @@ describe('should set-up next', () => { if (server) await killApp(server) }) + it('`compress` should be `true` by default', async () => { + expect( + await fs.readFileSync(join(next.testDir, 'standalone/server.js'), 'utf8') + ).toContain('"compress":true') + }) + it('should output middleware correctly', async () => { expect( await fs.pathExists(