diff --git a/.github/labeler.json b/.github/labeler.json index da721c919c40852..ead33c713c82bc9 100644 --- a/.github/labeler.json +++ b/.github/labeler.json @@ -10,7 +10,8 @@ "packages/next-codemod/**", "packages/eslint-plugin-next/**", "packages/eslint-config-next/**", - "packages/next-env/**" + "packages/next-env/**", + "packages/next-swc/**" ], "created-by: Chrome Aurora": [ { "type": "user", "pattern": "spanicker" }, @@ -31,6 +32,11 @@ { "type": "user", "pattern": "leerob" }, { "type": "user", "pattern": "kdy1" }, { "type": "user", "pattern": "timneutkens" } + ], + "created-by: Next.js docs team": [ + { "type": "user", "pattern": "MaedahBatool" }, + { "type": "user", "pattern": "molebox" }, + { "type": "user", "pattern": "ismaelrumzan" } ] } } diff --git a/docs/advanced-features/static-html-export.md b/docs/advanced-features/static-html-export.md index 83653c812c1eb84..e37bd2ababcf487 100644 --- a/docs/advanced-features/static-html-export.md +++ b/docs/advanced-features/static-html-export.md @@ -64,9 +64,9 @@ Features that require a Node.js server, or dynamic logic that cannot be computed It's possible to use the [`getInitialProps`](/docs/api-reference/data-fetching/getInitialProps.md) API instead of `getStaticProps`, but it comes with a few caveats: - - `getInitialProps` cannot be used alongside `getStaticProps` or `getStaticPaths` on any given page. If you have dynamic routes, instead of using `getStaticPaths` you'll need to configure the [`exportPathMap`](/docs/api-reference/next.config.js/exportPathMap.md) parameter in your [`next.config.js`](/docs/api-reference/next.config.js/introduction.md) file to let the exporter know which HTML files it should output. - - When `getInitialProps` is called during export, the `req` and `res` fields of its [`context`](/docs/api-reference/data-fetching/getInitialProps.md#context-object) parameter will be empty objects, since during export there is no server running. - - `getInitialProps` **will be called on every client-side navigation**, if you'd like to only fetch data at build-time, switch to `getStaticProps`. - - `getInitialProps` should fetch from an API and cannot use Node.js-specific libraries or the file system like `getStaticProps` can. +- `getInitialProps` cannot be used alongside `getStaticProps` or `getStaticPaths` on any given page. If you have dynamic routes, instead of using `getStaticPaths` you'll need to configure the [`exportPathMap`](/docs/api-reference/next.config.js/exportPathMap.md) parameter in your [`next.config.js`](/docs/api-reference/next.config.js/introduction.md) file to let the exporter know which HTML files it should output. +- When `getInitialProps` is called during export, the `req` and `res` fields of its [`context`](/docs/api-reference/data-fetching/getInitialProps.md#context-object) parameter will be empty objects, since during export there is no server running. +- `getInitialProps` **will be called on every client-side navigation**, if you'd like to only fetch data at build-time, switch to `getStaticProps`. +- `getInitialProps` should fetch from an API and cannot use Node.js-specific libraries or the file system like `getStaticProps` can. We recommend migrating towards `getStaticProps` over `getInitialProps` whenever possible. diff --git a/docs/api-reference/next/server.md b/docs/api-reference/next/server.md index 44a9ed2e38a2f49..096a689cc4358c8 100644 --- a/docs/api-reference/next/server.md +++ b/docs/api-reference/next/server.md @@ -4,19 +4,29 @@ description: Use Middleware to run code before a request is completed. # next/server -Middleware is created by using a `middleware` function that lives inside a `_middleware` file. The Middleware API is based upon the native [`FetchEvent`](https://developer.mozilla.org/en-US/docs/Web/API/FetchEvent), [`Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response), and [`Request`](https://developer.mozilla.org/en-US/docs/Web/API/Request) objects. +The `next/server` module provides several exports for server-only helpers, such as [Middleware](/docs/middleware.md). + +## NextMiddleware + +Middleware is created by using a `middleware` function that lives inside a `_middleware` file. The Middleware API is based upon the native [`Request`](https://developer.mozilla.org/en-US/docs/Web/API/Request), [`FetchEvent`](https://developer.mozilla.org/en-US/docs/Web/API/FetchEvent), and [`Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response) objects. These native Web API objects are extended to give you more control over how you manipulate and configure a response, based on the incoming requests. -The function signature: +The function signature is defined as follows: ```ts -import type { NextRequest, NextFetchEvent } from 'next/server' +type NextMiddlewareResult = NextResponse | Response | null | undefined -export type Middleware = ( +type NextMiddleware = ( request: NextRequest, event: NextFetchEvent -) => Promise | Response | undefined +) => NextMiddlewareResult | Promise +``` + +It can be imported from `next/server` with the following: + +```ts +import type { NextMiddleware } from 'next/server' ``` The function can be a default export and as such, does **not** have to be named `middleware`. Though this is a convention. Also note that you only need to make the function `async` if you are running asynchronous code. @@ -25,7 +35,7 @@ The function can be a default export and as such, does **not** have to be named The `NextRequest` object is an extension of the native [`Request`](https://developer.mozilla.org/en-US/docs/Web/API/Request) interface, with the following added methods and properties: -- `cookie` - Has the cookies from the `Request` +- `cookies` - Has the cookies from the `Request` - `nextUrl` - Includes an extended, parsed, URL object that gives you access to Next.js specific properties such as `pathname`, `basePath`, `trailingSlash` and `i18n` - `geo` - Has the geo location from the `Request` - `geo.country` - The country code diff --git a/docs/testing.md b/docs/testing.md index ad35bb5c7284382..fb001e66ec650e7 100644 --- a/docs/testing.md +++ b/docs/testing.md @@ -1,5 +1,5 @@ --- -description: Learn how to set up Next.js with three commonly used testing tools — Cypress, Jest, and React Testing Library. +description: Learn how to set up Next.js with three commonly used testing tools — Cypress, Playwright, Jest, and React Testing Library. --- # Testing @@ -243,31 +243,69 @@ You can learn more about Playwright and Continuous Integration from these resour ## Jest and React Testing Library -Jest and React Testing Library are frequently used together for **Unit Testing**. +Jest and React Testing Library are frequently used together for **Unit Testing**. There are three ways you can start using Jest within your Next.js application: + +1. Using one of our [quickstart examples](https://nextjs.org/docs/testing#quickstart-2) +2. With the [Next.js Rust Compiler](https://nextjs.org/docs/testing#setting-up-jest-with-the-rust-compiler) +3. With [Babel](https://nextjs.org/docs/testing#setting-up-jest-with-babel) + +The following sections will go through how you can set up Jest with each of these options: ### Quickstart -You can use `create-next-app` with the [with-jest example](https://github.com/vercel/next.js/tree/canary/examples/with-jest) to quickly get started with Jest and React Testing Library: +You can use `create-next-app` with the [with-jest](https://github.com/vercel/next.js/tree/canary/examples/with-jest) example to quickly get started with Jest and React Testing Library: ```bash npx create-next-app@latest --example with-jest with-jest-app ``` -### Manual setup +### Setting up Jest (with the Rust Compiler) + +Since the release of [Next.js 12](https://nextjs.org/blog/next-12), Next.js now has built-in configuration for Jest. -To manually set up Jest and React Testing Library, install `jest` , `@testing-library/react`, `@testing-library/jest-dom` as well as some supporting packages: +To set up Jest, install `jest` , `@testing-library/react`, `@testing-library/jest-dom` and `react-test-renderer`: ```bash -npm install --save-dev jest babel-jest @testing-library/react @testing-library/jest-dom identity-obj-proxy react-test-renderer +npm install --save-dev jest @testing-library/react @testing-library/jest-dom react-test-renderer ``` -**Configuring Jest** - -Create a `jest.config.js` file in your project's root directory and add the following configuration options: +Create a `jest.config.js` file in your project's root directory and add the following: ```jsx // jest.config.js +const nextJest = require('next/jest') + +const createJestConfig = nextJest({ + // Provide the path to your Next.js app to load next.config.js and .env files in your test environment + dir: './', +}) + +// Add any custom config to be passed to Jest +const customJestConfig = { + setupFilesAfterEnv: ['/jest.setup.js'], +} + +// createJestConfig is exported this way to ensure that next/jest can load the Next.js config which is async +module.exports = createJestConfig(customJestConfig) +``` + +Under the hood, `next/jest` is automatically configuring Jest for you, including: + +- Setting up `transform` using [SWC](https://nextjs.org/docs/advanced-features/compiler) +- Auto mocking stylesheets (`.css`, `.module.css`, and their scss variants) and image imports +- Loading `.env` (and all variants) into `process.env` +- Ignoring `node_modules` from test resolving and transforms +- Ignoring `.next` from test resolving +- Loading `next.config.js` for flags that enable SWC transforms + +### Setting up Jest (with Babel) + +If you opt-out of the [Rust Compiler](https://nextjs.org/docs/advanced-features/compiler), you will need to manually configure Jest and install `babel-jest` and `identity-obj-proxy` in addition to the packages above. +Here are the recommended options to configure Jest for Next.js: + +```jsx +// jest.config.js module.exports = { collectCoverageFrom: [ '**/*.{js,jsx,ts,tsx}', @@ -275,23 +313,26 @@ module.exports = { '!**/node_modules/**', ], moduleNameMapper: { - /* Handle CSS imports (with CSS modules) - https://jestjs.io/docs/webpack#mocking-css-modules */ + // Handle CSS imports (with CSS modules) + // https://jestjs.io/docs/webpack#mocking-css-modules '^.+\\.module\\.(css|sass|scss)$': 'identity-obj-proxy', // Handle CSS imports (without CSS modules) '^.+\\.(css|sass|scss)$': '/__mocks__/styleMock.js', - /* Handle image imports - https://jestjs.io/docs/webpack#handling-static-assets */ - '^.+\\.(jpg|jpeg|png|gif|webp|avif|svg)$': - '/__mocks__/fileMock.js', + // Handle image imports + // https://jestjs.io/docs/webpack#handling-static-assets + '^.+\\.(jpg|jpeg|png|gif|webp|avif|svg)$': `/__mocks__/fileMock.js`, + + // Handle module aliases + '^@/components/(.*)$': '/components/$1', }, + setupFilesAfterEnv: ['/jest.setup.js'], testPathIgnorePatterns: ['/node_modules/', '/.next/'], testEnvironment: 'jsdom', transform: { - /* Use babel-jest to transpile tests with the next/babel preset - https://jestjs.io/docs/configuration#transform-objectstring-pathtotransformer--pathtotransformer-object */ + // Use babel-jest to transpile tests with the next/babel preset + // https://jestjs.io/docs/configuration#transform-objectstring-pathtotransformer--pathtotransformer-object '^.+\\.(js|jsx|ts|tsx)$': ['babel-jest', { presets: ['next/babel'] }], }, transformIgnorePatterns: [ @@ -301,41 +342,37 @@ module.exports = { } ``` -You can learn more about each option above in the [Jest docs](https://jestjs.io/docs/configuration). +You can learn more about each configuration option in the [Jest docs](https://jestjs.io/docs/configuration). **Handling stylesheets and image imports** -These files aren't useful in tests but importing them may cause errors, so we will need to mock them. Create the mock files we referenced in the configuration above - `fileMock.js` and `styleMock.js` - inside a `__mocks__` directory: +Styleheets and images aren't used in the tests but importing them may cause errors, so they will need to be mocked. Create the mock files referenced in the configuration above - `fileMock.js` and `styleMock.js` - inside a `__mocks__` directory: -```json +```js // __mocks__/fileMock.js - -(module.exports = "test-file-stub") +module.exports = 'test-file-stub' ``` -```json +```js // __mocks__/styleMock.js - -module.exports = {}; +module.exports = {} ``` If you're running into the issue `"Failed to parse src "test-file-stub" on 'next/image'"`, add a '/' to your fileMock. -```json +```js // __mocks__/fileMock.js - -(module.exports = "/test-file-stub") +module.exports = '/test-file-stub' ``` For more information on handling static assets, please refer to the [Jest Docs](https://jestjs.io/docs/webpack#handling-static-assets). -**Extend Jest with custom matchers** +**Optional: Extend Jest with custom matchers** `@testing-library/jest-dom` includes a set of convenient [custom matchers](https://github.com/testing-library/jest-dom#custom-matchers) such as `.toBeInTheDocument()` making it easier to write tests. You can import the custom matchers for every test by adding the following option to the Jest configuration file: -```json +```js // jest.config.js - setupFilesAfterEnv: ['/jest.setup.js'] ``` @@ -343,13 +380,12 @@ Then, inside `jest.setup.js`, add the following import: ```jsx // jest.setup.js - import '@testing-library/jest-dom/extend-expect' ``` If you need to add more setup options before each test, it's common to add them to the `jest.setup.js` file above. -**Absolute Imports and Module Path Aliases** +**Optional: Absolute Imports and Module Path Aliases** If your project is using [Module Path Aliases](https://nextjs.org/docs/advanced-features/module-path-aliases), you will need to configure Jest to resolve the imports by matching the paths option in the `jsconfig.json` file with the `moduleNameMapper` option in the `jest.config.js` file. For example: @@ -372,6 +408,8 @@ moduleNameMapper: { } ``` +### Creating your tests: + **Add a test script to package.json** Add the Jest executable in watch mode to the `package.json` scripts: diff --git a/examples/with-jest-babel/.eslintrc.json b/examples/with-jest-babel/.eslintrc.json new file mode 100644 index 000000000000000..4f67f8cbc08db18 --- /dev/null +++ b/examples/with-jest-babel/.eslintrc.json @@ -0,0 +1,15 @@ +{ + "root": true, + "extends": ["next/core-web-vitals"], + "plugins": ["testing-library"], + "overrides": [ + // Only uses Testing Library lint rules in test files + { + "files": [ + "**/__tests__/**/*.[jt]s?(x)", + "**/?(*.)+(spec|test).[jt]s?(x)" + ], + "extends": ["plugin:testing-library/react"] + } + ] +} diff --git a/examples/with-jest-babel/.gitignore b/examples/with-jest-babel/.gitignore new file mode 100644 index 000000000000000..1437c53f70bc211 --- /dev/null +++ b/examples/with-jest-babel/.gitignore @@ -0,0 +1,34 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.js + +# testing +/coverage + +# next.js +/.next/ +/out/ + +# production +/build + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# local env files +.env.local +.env.development.local +.env.test.local +.env.production.local + +# vercel +.vercel diff --git a/examples/with-jest-babel/README.md b/examples/with-jest-babel/README.md new file mode 100644 index 000000000000000..b0d4e63fde1b3b7 --- /dev/null +++ b/examples/with-jest-babel/README.md @@ -0,0 +1,23 @@ +# Next.js + Jest + +This example shows how to configure Jest to work with Next.js and Babel. Since the release of Next.js 12, Next.js has in-built configuration for Jest with SWC. See the [with-jest](https://github.com/vercel/next.js/tree/canary/examples/with-jest) example for the latest implementation. + +This includes Next.js' built-in support for Global CSS, CSS Modules, and TypeScript! + +## How to Use + +Quickly get started using [Create Next App](https://github.com/vercel/next.js/tree/canary/packages/create-next-app#readme)! + +In your terminal, run the following command: + +```bash +npx create-next-app --example with-jest with-jest-app +# or +yarn create next-app --example with-jest with-jest-app +``` + +## Run Jest Tests + +```bash +npm test +``` diff --git a/examples/with-jest/__mocks__/fileMock.js b/examples/with-jest-babel/__mocks__/fileMock.js similarity index 100% rename from examples/with-jest/__mocks__/fileMock.js rename to examples/with-jest-babel/__mocks__/fileMock.js diff --git a/examples/with-jest/__mocks__/styleMock.js b/examples/with-jest-babel/__mocks__/styleMock.js similarity index 100% rename from examples/with-jest/__mocks__/styleMock.js rename to examples/with-jest-babel/__mocks__/styleMock.js diff --git a/examples/with-jest-babel/__tests__/__snapshots__/snapshot.js.snap b/examples/with-jest-babel/__tests__/__snapshots__/snapshot.js.snap new file mode 100644 index 000000000000000..0a3b44498100774 --- /dev/null +++ b/examples/with-jest-babel/__tests__/__snapshots__/snapshot.js.snap @@ -0,0 +1,206 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders homepage unchanged 1`] = ` + +`; diff --git a/examples/with-jest-babel/__tests__/index.test.jsx b/examples/with-jest-babel/__tests__/index.test.jsx new file mode 100644 index 000000000000000..b2fc240afb8f690 --- /dev/null +++ b/examples/with-jest-babel/__tests__/index.test.jsx @@ -0,0 +1,19 @@ +/** + * @jest-environment jsdom + */ + +import React from 'react' +import { render, screen } from '@testing-library/react' +import Home from '../pages/index' + +describe('Home', () => { + it('renders a heading', () => { + render() + + const heading = screen.getByRole('heading', { + name: /welcome to next\.js!/i, + }) + + expect(heading).toBeInTheDocument() + }) +}) diff --git a/examples/with-jest-babel/__tests__/snapshot.js b/examples/with-jest-babel/__tests__/snapshot.js new file mode 100644 index 000000000000000..2d822e87189044a --- /dev/null +++ b/examples/with-jest-babel/__tests__/snapshot.js @@ -0,0 +1,8 @@ +import React from 'react' +import renderer from 'react-test-renderer' +import Index from '../pages/index' + +it('renders homepage unchanged', () => { + const tree = renderer.create().toJSON() + expect(tree).toMatchSnapshot() +}) diff --git a/examples/with-jest-babel/jest.config.js b/examples/with-jest-babel/jest.config.js new file mode 100644 index 000000000000000..93f6fd3085b3dee --- /dev/null +++ b/examples/with-jest-babel/jest.config.js @@ -0,0 +1,35 @@ +// You can learn more about each option below in the Jest docs: https://jestjs.io/docs/configuration. + +module.exports = { + collectCoverageFrom: [ + '**/*.{js,jsx,ts,tsx}', + '!**/*.d.ts', + '!**/node_modules/**', + ], + moduleNameMapper: { + // Handle CSS imports (with CSS modules) + // https://jestjs.io/docs/webpack#mocking-css-modules + '^.+\\.module\\.(css|sass|scss)$': 'identity-obj-proxy', + + // Handle CSS imports (without CSS modules) + '^.+\\.(css|sass|scss)$': '/__mocks__/styleMock.js', + + // Handle image imports + // https://jestjs.io/docs/webpack#handling-static-assets + '^.+\\.(jpg|jpeg|png|gif|webp|avif|svg)$': `/__mocks__/fileMock.js`, + + // Handle module aliases + '^@/components/(.*)$': '/components/$1', + }, + setupFilesAfterEnv: ['/jest.setup.js'], + testPathIgnorePatterns: ['/node_modules/', '/.next/'], + transform: { + // Use babel-jest to transpile tests with the next/babel preset + // https://jestjs.io/docs/configuration#transform-objectstring-pathtotransformer--pathtotransformer-object + '^.+\\.(js|jsx|ts|tsx)$': ['babel-jest', { presets: ['next/babel'] }], + }, + transformIgnorePatterns: [ + '/node_modules/', + '^.+\\.module\\.(css|sass|scss)$', + ], +} diff --git a/examples/with-jest-babel/jest.setup.js b/examples/with-jest-babel/jest.setup.js new file mode 100644 index 000000000000000..996e74a2ba7bfd7 --- /dev/null +++ b/examples/with-jest-babel/jest.setup.js @@ -0,0 +1,6 @@ +// Optional: configure or set up a testing framework before each test. +// If you delete this file, remove `setupFilesAfterEnv` from `jest.config.js` + +// Used for __tests__/testing-library.js +// Learn more: https://github.com/testing-library/jest-dom +import '@testing-library/jest-dom/extend-expect' diff --git a/examples/with-jest-babel/jsconfig.json b/examples/with-jest-babel/jsconfig.json new file mode 100644 index 000000000000000..e81aa88c59ff160 --- /dev/null +++ b/examples/with-jest-babel/jsconfig.json @@ -0,0 +1,8 @@ +{ + "compilerOptions": { + "baseUrl": ".", + "paths": { + "@/components/*": ["components/*"] + } + } +} diff --git a/examples/with-jest-babel/package.json b/examples/with-jest-babel/package.json new file mode 100644 index 000000000000000..eb77b739d3e5ea7 --- /dev/null +++ b/examples/with-jest-babel/package.json @@ -0,0 +1,28 @@ +{ + "private": true, + "scripts": { + "dev": "next dev", + "lint": "next lint", + "build": "next build", + "start": "next start", + "test": "jest --watch", + "test:ci": "jest --ci" + }, + "dependencies": { + "next": "latest", + "react": "^17.0.2", + "react-dom": "^17.0.2" + }, + "devDependencies": { + "@testing-library/jest-dom": "5.14.1", + "@testing-library/react": "12.0.0", + "@testing-library/user-event": "13.2.1", + "babel-jest": "27.0.6", + "eslint": "7.32.0", + "eslint-config-next": "latest", + "eslint-plugin-testing-library": "4.11.0", + "identity-obj-proxy": "3.0.0", + "jest": "27.0.6", + "react-test-renderer": "17.0.2" + } +} diff --git a/examples/with-jest-babel/pages/_app.js b/examples/with-jest-babel/pages/_app.js new file mode 100644 index 000000000000000..d5694127266f207 --- /dev/null +++ b/examples/with-jest-babel/pages/_app.js @@ -0,0 +1,6 @@ +import '../styles/global.css' + +// This default export is required in a new `pages/_app.js` file. +export default function MyApp({ Component, pageProps }) { + return +} diff --git a/examples/with-jest-babel/pages/index.js b/examples/with-jest-babel/pages/index.js new file mode 100644 index 000000000000000..eb81b36756a83ae --- /dev/null +++ b/examples/with-jest-babel/pages/index.js @@ -0,0 +1,62 @@ +import Head from 'next/head' +import styles from './index.module.css' +import Image from 'next/image' + +const Home = () => ( + +) + +export default Home diff --git a/examples/with-jest-babel/pages/index.module.css b/examples/with-jest-babel/pages/index.module.css new file mode 100644 index 000000000000000..2003964508492da --- /dev/null +++ b/examples/with-jest-babel/pages/index.module.css @@ -0,0 +1,87 @@ +.container { + min-height: 100vh; + padding: 0 0.5rem; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; +} + +.title a { + color: #0070f3; + text-decoration: none; +} + +.title a:hover, +.title a:focus, +.title a:active { + text-decoration: underline; +} + +.title { + margin: 0; + line-height: 1.15; + font-size: 4rem; +} + +.title, +.description { + text-align: center; +} + +.description { + line-height: 1.5; + font-size: 1.5rem; +} + +.grid { + display: flex; + align-items: center; + justify-content: center; + flex-wrap: wrap; + + max-width: 800px; + margin-top: 3rem; +} + +.card { + margin: 1rem; + flex-basis: 45%; + padding: 1.5rem; + text-align: left; + color: inherit; + text-decoration: none; + border: 1px solid #eaeaea; + border-radius: 10px; + transition: color 0.15s ease, border-color 0.15s ease; +} + +.card:hover, +.card:focus, +.card:active { + color: #0070f3; + border-color: #0070f3; +} + +.card h3 { + margin: 0 0 1rem 0; + font-size: 1.5rem; +} + +.card p { + margin: 0; + font-size: 1.25rem; + line-height: 1.5; +} + +.logo { + height: 1em; + margin-left: 0.5rem; +} + +@media (max-width: 600px) { + .grid { + width: 100%; + flex-direction: column; + } +} diff --git a/examples/with-jest-babel/public/favicon.ico b/examples/with-jest-babel/public/favicon.ico new file mode 100644 index 000000000000000..4965832f2c9b060 Binary files /dev/null and b/examples/with-jest-babel/public/favicon.ico differ diff --git a/examples/with-jest-babel/public/vercel.svg b/examples/with-jest-babel/public/vercel.svg new file mode 100644 index 000000000000000..fbf0e25a651c289 --- /dev/null +++ b/examples/with-jest-babel/public/vercel.svg @@ -0,0 +1,4 @@ + + + \ No newline at end of file diff --git a/examples/with-jest-babel/styles/global.css b/examples/with-jest-babel/styles/global.css new file mode 100644 index 000000000000000..0afcfbe32f1d0e8 --- /dev/null +++ b/examples/with-jest-babel/styles/global.css @@ -0,0 +1,53 @@ +html, +body { + padding: 0; + margin: 0; + font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, + Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif; +} + +* { + box-sizing: border-box; +} + +main { + padding: 5rem 0; + flex: 1; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; +} + +footer { + width: 100%; + height: 100px; + border-top: 1px solid #eaeaea; + display: flex; + justify-content: center; + align-items: center; +} + +footer img { + margin-left: 0.5rem; +} + +footer a { + display: flex; + justify-content: center; + align-items: center; +} + +a { + color: inherit; + text-decoration: none; +} + +code { + background: #fafafa; + border-radius: 5px; + padding: 0.75rem; + font-size: 1.1rem; + font-family: Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono, + Bitstream Vera Sans Mono, Courier New, monospace; +} diff --git a/examples/with-jest/__tests__/__snapshots__/snapshot.js.snap b/examples/with-jest/__tests__/__snapshots__/snapshot.js.snap index 6b1a9c622ce41fd..0a3b44498100774 100644 --- a/examples/with-jest/__tests__/__snapshots__/snapshot.js.snap +++ b/examples/with-jest/__tests__/__snapshots__/snapshot.js.snap @@ -72,5 +72,135 @@ exports[`renders homepage unchanged 1`] = ` + `; diff --git a/examples/with-jest/jest.config.js b/examples/with-jest/jest.config.js index eacc5d367f5636e..79e3eb86d90645c 100644 --- a/examples/with-jest/jest.config.js +++ b/examples/with-jest/jest.config.js @@ -1,33 +1,18 @@ -module.exports = { - collectCoverageFrom: [ - '**/*.{js,jsx,ts,tsx}', - '!**/*.d.ts', - '!**/node_modules/**', - ], - moduleNameMapper: { - // Handle CSS imports (with CSS modules) - // https://jestjs.io/docs/webpack#mocking-css-modules - '^.+\\.module\\.(css|sass|scss)$': 'identity-obj-proxy', - - // Handle CSS imports (without CSS modules) - '^.+\\.(css|sass|scss)$': '/__mocks__/styleMock.js', +const nextJest = require('next/jest') - // Handle image imports - // https://jestjs.io/docs/webpack#handling-static-assets - '^.+\\.(jpg|jpeg|png|gif|webp|avif|svg)$': `/__mocks__/fileMock.js`, +const createJestConfig = nextJest({ + // Provide the path to your Next.js app to load next.config.js and .env files in your test environment + dir: './', +}) - // Handle module aliases - '^@/components/(.*)$': '/components/$1', - }, +// Add any custom config to be passed to Jest +const customJestConfig = { setupFilesAfterEnv: ['/jest.setup.js'], - testPathIgnorePatterns: ['/node_modules/', '/.next/'], - transform: { - // Use babel-jest to transpile tests with the next/babel preset - // https://jestjs.io/docs/configuration#transform-objectstring-pathtotransformer--pathtotransformer-object - '^.+\\.(js|jsx|ts|tsx)$': ['babel-jest', { presets: ['next/babel'] }], + moduleNameMapper: { + // Handle module aliases (this will be automatically configured for you soon) + '^@/components/(.*)$': '/components/$1', }, - transformIgnorePatterns: [ - '/node_modules/', - '^.+\\.module\\.(css|sass|scss)$', - ], } + +// createJestConfig is exported this way to ensure that next/jest can load the Next.js config which is async +module.exports = createJestConfig(customJestConfig) diff --git a/examples/with-jest/package.json b/examples/with-jest/package.json index eb77b739d3e5ea7..451ddb1eeeef0d4 100644 --- a/examples/with-jest/package.json +++ b/examples/with-jest/package.json @@ -21,8 +21,7 @@ "eslint": "7.32.0", "eslint-config-next": "latest", "eslint-plugin-testing-library": "4.11.0", - "identity-obj-proxy": "3.0.0", - "jest": "27.0.6", + "jest": "27.3.1", "react-test-renderer": "17.0.2" } } diff --git a/examples/with-jest/pages/index.js b/examples/with-jest/pages/index.js index 355ad347b4d54e0..f1800fc900ac111 100644 --- a/examples/with-jest/pages/index.js +++ b/examples/with-jest/pages/index.js @@ -1,4 +1,5 @@ import Head from 'next/head' +import Image from 'next/image' import styles from './index.module.css' const Home = () => ( @@ -42,6 +43,19 @@ const Home = () => ( + + ) diff --git a/examples/with-jest/pages/index.module.css b/examples/with-jest/pages/index.module.css index d6754a34ab1df3f..2003964508492da 100644 --- a/examples/with-jest/pages/index.module.css +++ b/examples/with-jest/pages/index.module.css @@ -74,6 +74,11 @@ line-height: 1.5; } +.logo { + height: 1em; + margin-left: 0.5rem; +} + @media (max-width: 600px) { .grid { width: 100%; diff --git a/examples/with-jest/public/vercel.svg b/examples/with-jest/public/vercel.svg new file mode 100644 index 000000000000000..fbf0e25a651c289 --- /dev/null +++ b/examples/with-jest/public/vercel.svg @@ -0,0 +1,4 @@ + + + \ No newline at end of file diff --git a/examples/with-jest/public/zeit.svg b/examples/with-jest/public/zeit.svg deleted file mode 100644 index b4a5a94a412c05f..000000000000000 --- a/examples/with-jest/public/zeit.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - diff --git a/examples/with-mongodb/lib/mongodb.js b/examples/with-mongodb/lib/mongodb.js index 2990cffd2cac469..0eef49bf4993688 100644 --- a/examples/with-mongodb/lib/mongodb.js +++ b/examples/with-mongodb/lib/mongodb.js @@ -1,10 +1,7 @@ import { MongoClient } from 'mongodb' const uri = process.env.MONGODB_URI -const options = { - useUnifiedTopology: true, - useNewUrlParser: true, -} +const options = {} let client let clientPromise diff --git a/examples/with-mongodb/package.json b/examples/with-mongodb/package.json index fd7f9f0ba260395..f03fe2598d01fe7 100644 --- a/examples/with-mongodb/package.json +++ b/examples/with-mongodb/package.json @@ -6,7 +6,7 @@ "start": "next start" }, "dependencies": { - "mongodb": "^3.5.9", + "mongodb": "^4.1.3", "next": "latest", "react": "^17.0.2", "react-dom": "^17.0.2" diff --git a/examples/with-mongodb/pages/index.js b/examples/with-mongodb/pages/index.js index 9628afb6523c74a..a57dfe93bfb970d 100644 --- a/examples/with-mongodb/pages/index.js +++ b/examples/with-mongodb/pages/index.js @@ -223,17 +223,20 @@ export default function Home({ isConnected }) { } export async function getServerSideProps(context) { - const client = await clientPromise - - // client.db() will be the default database passed in the MONGODB_URI - // You can change the database by calling the client.db() function and specifying a database like: - // const db = client.db("myDatabase"); - // Then you can execute queries against your database like so: - // db.find({}) or any of the MongoDB Node Driver commands - - const isConnected = await client.isConnected() - - return { - props: { isConnected }, + try { + // client.db() will be the default database passed in the MONGODB_URI + // You can change the database by calling the client.db() function and specifying a database like: + // const db = client.db("myDatabase"); + // Then you can execute queries against your database like so: + // db.find({}) or any of the MongoDB Node Driver commands + await clientPromise + return { + props: { isConnected: true }, + } + } catch (e) { + console.error(e) + return { + props: { isConnected: false }, + } } } diff --git a/lerna.json b/lerna.json index 07a7a9e42189847..86e6f3e89c1aac8 100644 --- a/lerna.json +++ b/lerna.json @@ -17,5 +17,5 @@ "registry": "https://registry.npmjs.org/" } }, - "version": "12.0.5-canary.11" + "version": "12.0.5-canary.13" } diff --git a/package.json b/package.json index 42f268a020505d3..c3156bb03db4905 100644 --- a/package.json +++ b/package.json @@ -36,6 +36,7 @@ "lint-staged": "lint-staged", "next-with-deps": "./scripts/next-with-deps.sh", "next": "node --trace-deprecation --enable-source-maps packages/next/dist/bin/next", + "next-react-18": "node --trace-deprecation --enable-source-maps -r ./test/integration/react-18/test/require-hook.js packages/next/dist/bin/next", "next-no-sourcemaps": "node --trace-deprecation packages/next/dist/bin/next", "clean-trace-jaeger": "rm -rf test/integration/basic/.next && TRACE_TARGET=JAEGER node --trace-deprecation --enable-source-maps packages/next/dist/bin/next build test/integration/basic", "debug": "node --inspect packages/next/dist/bin/next", @@ -138,9 +139,9 @@ "pretty-ms": "7.0.0", "random-seed": "0.3.0", "react": "17.0.2", - "react-18": "npm:react@18.0.0-alpha-13455d26d-20211104", + "react-18": "npm:react@18.0.0-beta-0cc724c77-20211125", "react-dom": "17.0.2", - "react-dom-18": "npm:react-dom@18.0.0-alpha-13455d26d-20211104", + "react-dom-18": "npm:react-dom@18.0.0-beta-0cc724c77-20211125", "react-ssr-prepass": "1.0.8", "release": "6.3.0", "request-promise-core": "1.1.2", diff --git a/packages/create-next-app/package.json b/packages/create-next-app/package.json index 91d584a20feaa61..7db9bb8b53ef1c9 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.5-canary.11", + "version": "12.0.5-canary.13", "keywords": [ "react", "next", diff --git a/packages/eslint-config-next/package.json b/packages/eslint-config-next/package.json index 059c31e5f965b91..0c02bf86fa02935 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.5-canary.11", + "version": "12.0.5-canary.13", "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.5-canary.11", + "@next/eslint-plugin-next": "12.0.5-canary.13", "@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 54a937d66b9a76b..a8f199d9886129c 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.5-canary.11", + "version": "12.0.5-canary.13", "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 5ee5f157e33a714..845e5910c98988a 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.5-canary.11", + "version": "12.0.5-canary.13", "main": "index.js", "license": "MIT", "repository": { diff --git a/packages/next-codemod/package.json b/packages/next-codemod/package.json index 991590ac3a8b463..9c9d6dea4d5f404 100644 --- a/packages/next-codemod/package.json +++ b/packages/next-codemod/package.json @@ -1,6 +1,6 @@ { "name": "@next/codemod", - "version": "12.0.5-canary.11", + "version": "12.0.5-canary.13", "license": "MIT", "dependencies": { "chalk": "4.1.0", diff --git a/packages/next-env/package.json b/packages/next-env/package.json index 12502b6eb730025..2c6b1ef19811d91 100644 --- a/packages/next-env/package.json +++ b/packages/next-env/package.json @@ -1,6 +1,6 @@ { "name": "@next/env", - "version": "12.0.5-canary.11", + "version": "12.0.5-canary.13", "keywords": [ "react", "next", diff --git a/packages/next-mdx/package.json b/packages/next-mdx/package.json index d627f03f5eea59b..a83803327e09680 100644 --- a/packages/next-mdx/package.json +++ b/packages/next-mdx/package.json @@ -1,6 +1,6 @@ { "name": "@next/mdx", - "version": "12.0.5-canary.11", + "version": "12.0.5-canary.13", "main": "index.js", "license": "MIT", "repository": { diff --git a/packages/next-plugin-storybook/package.json b/packages/next-plugin-storybook/package.json index f15ac8ab0aa9555..264d323f3ca5ce0 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.5-canary.11", + "version": "12.0.5-canary.13", "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 fa3dd93ffd750ba..d85c0771b370f6a 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.5-canary.11", + "version": "12.0.5-canary.13", "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 8b7fee698244f50..4a7951d80f16c6a 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.5-canary.11", + "version": "12.0.5-canary.13", "description": "A polyfill for non-dead, nomodule browsers.", "main": "dist/polyfill-nomodule.js", "license": "MIT", diff --git a/packages/next-swc/.cargo/config.toml b/packages/next-swc/.cargo/config.toml index d253a8cf613358b..1fb099927fed8fb 100644 --- a/packages/next-swc/.cargo/config.toml +++ b/packages/next-swc/.cargo/config.toml @@ -3,14 +3,22 @@ rustdocflags = [] rustflags = [ + "-Z", + "new-llvm-pass-manager=no", ] [target.aarch64-apple-darwin] -rustflags = [] +rustflags = [ + "-Z", + "new-llvm-pass-manager=no", +] [target.aarch64-unknown-linux-gnu] linker = "aarch64-linux-gnu-gcc" -rustflags = [] +rustflags = [ + "-Z", + "new-llvm-pass-manager=no", +] [target.aarch64-unknown-linux-musl] linker = "aarch64-linux-gnu-gcc" @@ -19,17 +27,31 @@ rustflags = [ "target-feature=-crt-static", "-C", "link-arg=-lgcc", + "-Z", + "new-llvm-pass-manager=no", ] [target.armv7-unknown-linux-gnueabihf] linker = "arm-linux-gnueabihf-gcc" -rustflags = [] +rustflags = [ + "-Z", + "new-llvm-pass-manager=no", +] [target.aarch64-linux-android] -rustflags = [] +rustflags = [ + "-Z", + "new-llvm-pass-manager=no", +] [target.aarch64-pc-windows-msvc] -rustflags = [] +rustflags = [ + "-Z", + "new-llvm-pass-manager=no", +] [target.wasm32-unknown-unknown] -rustflags = [] \ No newline at end of file +rustflags = [ + "-Z", + "new-llvm-pass-manager=no", +] \ No newline at end of file diff --git a/packages/next-swc/Cargo.lock b/packages/next-swc/Cargo.lock index fbdfcc4a447cf3a..7219a82ca1b9bf9 100644 --- a/packages/next-swc/Cargo.lock +++ b/packages/next-swc/Cargo.lock @@ -58,9 +58,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.48" +version = "1.0.51" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62e1f47f7dc0422027a4e370dd4548d4d66b26782e513e98dca1e689e058a80e" +checksum = "8b26702f315f53b6071259e15dd9d64528213b44d61de1ec926eca7715d62203" [[package]] name = "arrayvec" @@ -774,9 +774,9 @@ dependencies = [ [[package]] name = "napi" -version = "1.7.11" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fd38e4945570d1fdc3052343410a5963c843e5797767009fabcd97dbe429bab" +checksum = "a5586ff59e18f42d41f68139a8ca72ef1dbcc243ec62c5696e6383169a8a05a4" dependencies = [ "napi-sys", "serde", @@ -831,9 +831,8 @@ dependencies = [ "swc_common", "swc_css", "swc_ecma_loader", - "swc_ecma_preset_env", "swc_ecma_transforms_testing", - "swc_ecmascript 0.88.1", + "swc_ecmascript 0.88.3", "swc_node_base", "swc_stylis", "testing", @@ -860,7 +859,7 @@ dependencies = [ "swc_bundler", "swc_common", "swc_ecma_loader", - "swc_ecmascript 0.88.1", + "swc_ecmascript 0.88.3", "swc_node_base", ] @@ -1494,9 +1493,9 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.5" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" +checksum = "3c9613b5a66ab9ba26415184cfc41156594925a9cf3a2057e57f31ff145f6568" [[package]] name = "same-file" @@ -1750,9 +1749,9 @@ dependencies = [ [[package]] name = "swc" -version = "0.87.1" +version = "0.88.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5c9ae049ceaf2e35296ab947aee725454ca2f80b80049deb74d4d0387a320fe" +checksum = "95e31ae9192b444c5e9564d6645203b8b1af79e4df7df2162311211ca0b42e21" dependencies = [ "ahash", "anyhow", @@ -1782,7 +1781,7 @@ dependencies = [ "swc_ecma_transforms_optimization", "swc_ecma_utils", "swc_ecma_visit", - "swc_ecmascript 0.88.1", + "swc_ecmascript 0.88.3", "swc_node_comments", "swc_visit", "tracing", @@ -1834,9 +1833,9 @@ dependencies = [ [[package]] name = "swc_common" -version = "0.14.6" +version = "0.14.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b4bae4ba78f7e0f82b85e1cb4b331ccc4473f1db38464ef4b9f13c8ac21e2dd" +checksum = "188984898a61b3d0d7aa7c2451ae23d6bda16cb1a94d19dd8d1b7d906c5754bc" dependencies = [ "ahash", "ast_node", @@ -1954,9 +1953,9 @@ dependencies = [ [[package]] name = "swc_ecma_ast" -version = "0.58.0" +version = "0.58.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3aca91ea5f5bc51155c1f86e83fa7483821c13d3719488ab75d99e2638b0ac22" +checksum = "d8678255e15265b4cf564c44035b9901c5bef22c19c2c3f4babccfb8de5ccfbe" dependencies = [ "is-macro", "num-bigint", @@ -2015,9 +2014,9 @@ dependencies = [ [[package]] name = "swc_ecma_loader" -version = "0.24.3" +version = "0.24.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e99e0ca2565e01957f563548202eed09f9db10e4fa85fe049030770e8d3d597d" +checksum = "eda70aee2118769474918d260e947fe08bfd56a5fc3d17c1a5aebeeb8d0c226e" dependencies = [ "ahash", "anyhow", @@ -2064,9 +2063,9 @@ dependencies = [ [[package]] name = "swc_ecma_parser" -version = "0.78.8" +version = "0.78.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "008627eacf910dabf46f3680f086f0493511b2763456a2aec3c1870c00567a09" +checksum = "abe082abd0148a66e6b5c9c97c36a770bac1912bf500c3bc982ac7101c17218d" dependencies = [ "either", "enum_kind", @@ -2085,9 +2084,9 @@ dependencies = [ [[package]] name = "swc_ecma_preset_env" -version = "0.67.1" +version = "0.67.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df02dc414b3b851f5ab5cde057414aa828ce640004900ca649ea9219e3165521" +checksum = "5429c17805bddd7ab965b7dcfda6d2d3077f39d6aa3746b322fbf3d917cb23ac" dependencies = [ "ahash", "anyhow", @@ -2111,9 +2110,9 @@ dependencies = [ [[package]] name = "swc_ecma_transforms" -version = "0.95.1" +version = "0.95.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c011f89eef93ee994e0c7181e0816b6623f860182228a607cab11e3d9c958d51" +checksum = "54c954d14b46f24ea894f37cacad4bdf1a9f37a316aaefe62e3a5033689e71de" dependencies = [ "swc_atoms", "swc_common", @@ -2167,9 +2166,9 @@ dependencies = [ [[package]] name = "swc_ecma_transforms_compat" -version = "0.52.13" +version = "0.52.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7060bd33df21d392d7295a8b4729be3448060449de2ae76b3e2dd9cf6ce02a66" +checksum = "3f8857bd04b2b6fba11168c1a4c66b48c1d9e7867a7bf1c649fe7131d2c2cfb2" dependencies = [ "ahash", "arrayvec", @@ -2204,9 +2203,9 @@ dependencies = [ [[package]] name = "swc_ecma_transforms_module" -version = "0.58.1" +version = "0.58.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47d1658854475530d0e258c527b2226e94563dadf06c6bdf692c69153ffda583" +checksum = "866abb059c40d454d8010de499d9f8e12994a7213779596d1c9a9a611dac9dfa" dependencies = [ "Inflector", "ahash", @@ -2335,9 +2334,9 @@ dependencies = [ [[package]] name = "swc_ecma_utils" -version = "0.52.3" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74224ff2ec4e4cc94e11303e4f6a87e13f5b00d92b273ee5821e116e8df7992b" +checksum = "a574cee3c938142eb2ba55ddc8de9416c03400f8c7e90a7ac9c1b1b8bae1d211" dependencies = [ "once_cell", "rayon", @@ -2350,9 +2349,9 @@ dependencies = [ [[package]] name = "swc_ecma_visit" -version = "0.44.0" +version = "0.44.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4406ee81ef55497c0f9c24cd37080228b5b70d8a847aa7181f9ca3c532e39439" +checksum = "635ac1f529c75c948a3a55d1bc2cef1861004811b733560cf3f62d0183dbdbb8" dependencies = [ "num-bigint", "swc_atoms", @@ -2375,9 +2374,9 @@ dependencies = [ [[package]] name = "swc_ecmascript" -version = "0.88.1" +version = "0.88.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee4ef782d87d479ffdc3283575703a332d2cf9f3ac9b141beba08ad15f717048" +checksum = "fd485710cd6f3e1b7ff55f3249f613cee3e527bb169529b73fc62717b1e91f9f" dependencies = [ "swc_ecma_ast", "swc_ecma_codegen", @@ -2785,7 +2784,7 @@ dependencies = [ "serde_json", "swc", "swc_common", - "swc_ecmascript 0.88.1", + "swc_ecmascript 0.88.3", "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 0281416b1c2d8bb..e775e5b64407c2d 100644 --- a/packages/next-swc/crates/core/Cargo.toml +++ b/packages/next-swc/crates/core/Cargo.toml @@ -15,13 +15,12 @@ pathdiff = "0.2.0" serde = "1" serde_json = "1" styled_components = "0.2.0" -swc = "0.87.1" +swc = "0.88.0" swc_atoms = "0.2.7" swc_common = {version = "0.14.2", features = ["concurrent", "sourcemap"]} swc_css = "0.31.0" -swc_ecma_loader = { version = "0.24.3", features = ["node", "lru"] } -swc_ecma_preset_env = "0.67.1" -swc_ecmascript = { version = "0.88.1", features = ["codegen", "minifier", "optimization", "parser", "react", "transforms", "typescript", "utils", "visit"] } +swc_ecma_loader = { version = "0.24.4", features = ["node", "lru"] } +swc_ecmascript = { version = "0.88.2", features = ["codegen", "minifier", "optimization", "parser", "react", "transforms", "typescript", "utils", "visit"] } swc_node_base = "0.5.1" swc_stylis = "0.28.0" tracing = {version = "0.1.28", features = ["release_max_level_off"]} @@ -29,7 +28,7 @@ regex = "1.5" [dev-dependencies] swc_ecma_transforms_testing = "0.45.1" -testing = "0.15.1" +testing = "0.15.2" walkdir = "2.3.2" diff --git a/packages/next-swc/crates/napi/Cargo.toml b/packages/next-swc/crates/napi/Cargo.toml index 8d5eea957aaa19b..4a68e599c874c6b 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.87.1" +swc = "0.88.0" swc_atoms = "0.2.7" swc_bundler = { version = "0.82.0", features = ["concurrent"] } swc_common = {version = "0.14.2", features = ["concurrent", "sourcemap"]} -swc_ecma_loader = { version = "0.24.3", features = ["node", "lru"] } -swc_ecmascript = { version = "0.88.1", features = ["codegen", "minifier", "optimization", "parser", "react", "transforms", "typescript", "utils", "visit"] } +swc_ecma_loader = { version = "0.24.4", features = ["node", "lru"] } +swc_ecmascript = { version = "0.88.2", 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 769979c210fac95..ee4c4aa39820fa1 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.87.1" +swc = "0.88.0" swc_common = {version = "0.14.2", features = ["concurrent", "sourcemap"]} -swc_ecmascript = { version = "0.88.1", features = ["codegen", "minifier", "optimization", "parser", "react", "transforms", "typescript", "utils", "visit"] } +swc_ecmascript = { version = "0.88.2", 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" diff --git a/packages/next-swc/package.json b/packages/next-swc/package.json index e71313601236dbc..5f178d66f7872cd 100644 --- a/packages/next-swc/package.json +++ b/packages/next-swc/package.json @@ -1,6 +1,6 @@ { "name": "@next/swc", - "version": "12.0.5-canary.11", + "version": "12.0.5-canary.13", "private": true, "scripts": { "build-native": "napi build --platform --cargo-name next_swc_napi native" diff --git a/packages/next/build/entries.ts b/packages/next/build/entries.ts index 4b241a0c81ed4a3..8f9e1461e1934b7 100644 --- a/packages/next/build/entries.ts +++ b/packages/next/build/entries.ts @@ -25,8 +25,15 @@ export type PagesMapping = { export function createPagesMapping( pagePaths: string[], extensions: string[], - isDev: boolean, - hasServerComponents: boolean + { + isDev, + hasServerComponents, + hasConcurrentFeatures, + }: { + isDev: boolean + hasServerComponents: boolean + hasConcurrentFeatures: boolean + } ): PagesMapping { const previousPages: PagesMapping = {} const pages: PagesMapping = pagePaths.reduce( @@ -65,7 +72,7 @@ export function createPagesMapping( // we alias these in development and allow webpack to // allow falling back to the correct source file so // that HMR can work properly when a file is added/removed - const documentPage = `_document${hasServerComponents ? '-web' : ''}` + const documentPage = `_document${hasConcurrentFeatures ? '-web' : ''}` if (isDev) { pages['/_app'] = `${PAGES_DIR_ALIAS}/_app` pages['/_error'] = `${PAGES_DIR_ALIAS}/_error` diff --git a/packages/next/build/index.ts b/packages/next/build/index.ts index aa1de3f08cb4ed6..ea7417caae8241b 100644 --- a/packages/next/build/index.ts +++ b/packages/next/build/index.ts @@ -281,12 +281,11 @@ export default async function build( const mappedPages = nextBuildSpan .traceChild('create-pages-mapping') .traceFn(() => - createPagesMapping( - pagePaths, - config.pageExtensions, - false, - hasServerComponents - ) + createPagesMapping(pagePaths, config.pageExtensions, { + isDev: false, + hasServerComponents, + hasConcurrentFeatures, + }) ) const entrypoints = nextBuildSpan diff --git a/packages/next/build/webpack-config.ts b/packages/next/build/webpack-config.ts index e476fb90c2d4587..888861c89931cce 100644 --- a/packages/next/build/webpack-config.ts +++ b/packages/next/build/webpack-config.ts @@ -570,7 +570,7 @@ export default async function getBaseWebpackConfig( prev.push(path.join(pagesDir, `_document.${ext}`)) return prev }, [] as string[]), - `next/dist/pages/_document${hasServerComponents ? '-web' : ''}.js`, + `next/dist/pages/_document${webServerRuntime ? '-web' : ''}.js`, ] } 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 9e0cfb7cc4f9656..99d1b959cd78a37 100644 --- a/packages/next/build/webpack/loaders/next-flight-server-loader.ts +++ b/packages/next/build/webpack/loaders/next-flight-server-loader.ts @@ -7,6 +7,12 @@ function isClientComponent(importSource: string, pageExtensions: string[]) { ) } +function isServerComponent(importSource: string, pageExtensions: string[]) { + return new RegExp(`\\.server(\\.(${pageExtensions.join('|')}))?`).test( + importSource + ) +} + function isNextComponent(importSource: string) { return ( importSource.includes('next/link') || importSource.includes('next/image') @@ -39,11 +45,8 @@ async function parseImportsInfo( const node = body[i] switch (node.type) { case 'ImportDeclaration': - // When importing from a server component, ignore const importSource = node.source.value - // For the client compilation, we have to always import the component to - // ensure that all dependencies are tracked. if (!isClientCompilation) { if ( !( @@ -59,6 +62,22 @@ async function parseImportsInfo( node.source.start - lastIndex ) transformedSource += JSON.stringify(`${node.source.value}?flight`) + } else { + // For the client compilation, we skip all modules imports but + // always keep client components in the bundle. All client components + // have to be imported from either server or client components. + if ( + !( + isClientComponent(importSource, pageExtensions) || + isServerComponent(importSource, pageExtensions) || + // Special cases for Next.js APIs that are considered as client + // components: + isNextComponent(importSource) || + isImageImport(importSource) + ) + ) { + continue + } } lastIndex = node.source.end diff --git a/packages/next/build/webpack/loaders/next-serverless-loader/page-handler.ts b/packages/next/build/webpack/loaders/next-serverless-loader/page-handler.ts index ec162b4e5a9a1a3..b1b1f9562f79f4a 100644 --- a/packages/next/build/webpack/loaders/next-serverless-loader/page-handler.ts +++ b/packages/next/build/webpack/loaders/next-serverless-loader/page-handler.ts @@ -191,6 +191,10 @@ export function getPageHandler(ctx: ServerlessHandlerCtx) { locale: detectedLocale, defaultLocale, domainLocales: i18n?.domains, + optimizeImages: process.env.__NEXT_OPTIMIZE_IMAGES, + optimizeCss: process.env.__NEXT_OPTIMIZE_CSS, + concurrentFeatures: process.env.__NEXT_CONCURRENT_FEATURES, + crossOrigin: process.env.__NEXT_CROSS_ORIGIN, }, options ) diff --git a/packages/next/client/image.tsx b/packages/next/client/image.tsx index d937979dfdcd003..61ea616e50e80d3 100644 --- a/packages/next/client/image.tsx +++ b/packages/next/client/image.tsx @@ -642,6 +642,18 @@ export default function Image({ } } + let imageSrcSetPropName = 'imagesrcset' + let imageSizesPropName = 'imagesizes' + if (process.env.__NEXT_REACT_ROOT) { + imageSrcSetPropName = 'imageSrcSet' + imageSizesPropName = 'imageSizes' + } + const linkProps = { + // Note: imagesrcset and imagesizes are not in the link element type with react 17. + [imageSrcSetPropName]: imgAttributes.srcSet, + [imageSizesPropName]: imgAttributes.sizes, + } + return ( {hasSizer ? ( @@ -716,11 +728,8 @@ export default function Image({ rel="preload" as="image" href={imgAttributes.srcSet ? undefined : imgAttributes.src} - // @ts-ignore: imagesrcset is not yet in the link element type. - imagesrcset={imgAttributes.srcSet} - // @ts-ignore: imagesizes is not yet in the link element type. - imagesizes={imgAttributes.sizes} - > + {...linkProps} + /> ) : null} diff --git a/packages/next/client/link.tsx b/packages/next/client/link.tsx index 39bb273ace76c2f..13ca9d9844422f1 100644 --- a/packages/next/client/link.tsx +++ b/packages/next/client/link.tsx @@ -291,11 +291,12 @@ function Link(props: React.PropsWithChildren) { } childProps.onMouseEnter = (e: React.MouseEvent) => { - if (!isLocalURL(href)) return if (child.props && typeof child.props.onMouseEnter === 'function') { child.props.onMouseEnter(e) } - prefetch(router, href, as, { priority: true }) + if (isLocalURL(href)) { + prefetch(router, href, as, { priority: true }) + } } // If child is an tag and doesn't have a href attribute, or if the 'passHref' property is diff --git a/packages/next/export/index.ts b/packages/next/export/index.ts index 531662fcc2d5b57..3c259782334601f 100644 --- a/packages/next/export/index.ts +++ b/packages/next/export/index.ts @@ -381,6 +381,10 @@ export default async function exportApp( // Exported pages do not currently support dynamic HTML. supportsDynamicHTML: false, concurrentFeatures: nextConfig.experimental.concurrentFeatures, + crossOrigin: nextConfig.crossOrigin, + optimizeCss: nextConfig.experimental.optimizeCss, + optimizeFonts: nextConfig.optimizeFonts, + optimizeImages: nextConfig.experimental.optimizeImages, } const { serverRuntimeConfig, publicRuntimeConfig } = nextConfig diff --git a/packages/next/package.json b/packages/next/package.json index 2304eef4ca0bf4f..92bc9508e4d0842 100644 --- a/packages/next/package.json +++ b/packages/next/package.json @@ -1,6 +1,6 @@ { "name": "next", - "version": "12.0.5-canary.11", + "version": "12.0.5-canary.13", "description": "The React Framework", "main": "./dist/server/next.js", "license": "MIT", @@ -70,10 +70,10 @@ "@babel/runtime": "7.15.4", "@hapi/accept": "5.0.2", "@napi-rs/triples": "1.0.3", - "@next/env": "12.0.5-canary.11", - "@next/polyfill-module": "12.0.5-canary.11", - "@next/react-dev-overlay": "12.0.5-canary.11", - "@next/react-refresh-utils": "12.0.5-canary.11", + "@next/env": "12.0.5-canary.13", + "@next/polyfill-module": "12.0.5-canary.13", + "@next/react-dev-overlay": "12.0.5-canary.13", + "@next/react-refresh-utils": "12.0.5-canary.13", "acorn": "8.5.0", "assert": "2.0.0", "browserify-zlib": "0.2.0", @@ -120,8 +120,8 @@ "peerDependencies": { "fibers": ">= 3.1.0", "node-sass": "^4.0.0 || ^5.0.0 || ^6.0.0", - "react": "^17.0.2 || ^18.0.0", - "react-dom": "^17.0.2 || ^18.0.0", + "react": "^17.0.2 || ^18.0.0-0", + "react-dom": "^17.0.2 || ^18.0.0-0", "sass": "^1.3.0" }, "peerDependenciesMeta": { @@ -156,7 +156,7 @@ "@babel/traverse": "7.15.0", "@babel/types": "7.15.0", "@napi-rs/cli": "1.2.1", - "@next/polyfill-nomodule": "12.0.5-canary.11", + "@next/polyfill-nomodule": "12.0.5-canary.13", "@peculiar/webcrypto": "1.1.7", "@taskr/clear": "1.1.0", "@taskr/esnext": "1.1.0", diff --git a/packages/next/pages/_document.tsx b/packages/next/pages/_document.tsx index bd6d4bed21e23e9..cac934af2801bcc 100644 --- a/packages/next/pages/_document.tsx +++ b/packages/next/pages/_document.tsx @@ -51,6 +51,7 @@ function getPolyfillScripts(context: HtmlProps, props: OriginProps) { buildManifest, devOnlyCacheBusterQueryString, disableOptimizedLoading, + crossOrigin, } = context return buildManifest.polyfillFiles @@ -62,7 +63,7 @@ function getPolyfillScripts(context: HtmlProps, props: OriginProps) { key={polyfill} defer={!disableOptimizedLoading} nonce={props.nonce} - crossOrigin={props.crossOrigin || process.env.__NEXT_CROSS_ORIGIN} + crossOrigin={props.crossOrigin || crossOrigin} noModule={true} src={`${assetPrefix}/_next/${polyfill}${devOnlyCacheBusterQueryString}`} /> @@ -70,7 +71,7 @@ function getPolyfillScripts(context: HtmlProps, props: OriginProps) { } function getPreNextScripts(context: HtmlProps, props: OriginProps) { - const { scriptLoader, disableOptimizedLoading } = context + const { scriptLoader, disableOptimizedLoading, crossOrigin } = context return (scriptLoader.beforeInteractive || []).map( (file: ScriptProps, index: number) => { @@ -82,7 +83,7 @@ function getPreNextScripts(context: HtmlProps, props: OriginProps) { defer={!disableOptimizedLoading} nonce={props.nonce} data-nscript="beforeInteractive" - crossOrigin={props.crossOrigin || process.env.__NEXT_CROSS_ORIGIN} + crossOrigin={props.crossOrigin || crossOrigin} /> ) } @@ -100,6 +101,7 @@ function getDynamicChunks( isDevelopment, devOnlyCacheBusterQueryString, disableOptimizedLoading, + crossOrigin, } = context return dynamicImports.map((file) => { @@ -114,7 +116,7 @@ function getDynamicChunks( file )}${devOnlyCacheBusterQueryString}`} nonce={props.nonce} - crossOrigin={props.crossOrigin || process.env.__NEXT_CROSS_ORIGIN} + crossOrigin={props.crossOrigin || crossOrigin} /> ) }) @@ -131,6 +133,7 @@ function getScripts( isDevelopment, devOnlyCacheBusterQueryString, disableOptimizedLoading, + crossOrigin, } = context const normalScripts = files.allFiles.filter((file) => file.endsWith('.js')) @@ -148,7 +151,7 @@ function getScripts( nonce={props.nonce} async={!isDevelopment && disableOptimizedLoading} defer={!disableOptimizedLoading} - crossOrigin={props.crossOrigin || process.env.__NEXT_CROSS_ORIGIN} + crossOrigin={props.crossOrigin || crossOrigin} /> ) }) @@ -258,8 +261,14 @@ export class Head extends Component< context!: React.ContextType getCssLinks(files: DocumentFiles): JSX.Element[] | null { - const { assetPrefix, devOnlyCacheBusterQueryString, dynamicImports } = - this.context + const { + assetPrefix, + devOnlyCacheBusterQueryString, + dynamicImports, + crossOrigin, + optimizeCss, + optimizeFonts, + } = this.context const cssFiles = files.allFiles.filter((f) => f.endsWith('.css')) const sharedFiles: Set = new Set(files.sharedFiles) @@ -282,7 +291,7 @@ export class Head extends Component< cssFiles.forEach((file) => { const isSharedFile = sharedFiles.has(file) - if (!process.env.__NEXT_OPTIMIZE_CSS) { + if (!optimizeCss) { cssLinkElements.push( ) } @@ -308,19 +315,14 @@ export class Head extends Component< href={`${assetPrefix}/_next/${encodeURI( file )}${devOnlyCacheBusterQueryString}`} - crossOrigin={ - this.props.crossOrigin || process.env.__NEXT_CROSS_ORIGIN - } + crossOrigin={this.props.crossOrigin || crossOrigin} data-n-g={isUnmanagedFile ? undefined : isSharedFile ? '' : undefined} data-n-p={isUnmanagedFile ? undefined : isSharedFile ? undefined : ''} /> ) }) - if ( - process.env.NODE_ENV !== 'development' && - process.env.__NEXT_OPTIMIZE_FONTS - ) { + if (process.env.NODE_ENV !== 'development' && optimizeFonts) { cssLinkElements = this.makeStylesheetInert( cssLinkElements ) as ReactElement[] @@ -330,8 +332,12 @@ export class Head extends Component< } getPreloadDynamicChunks() { - const { dynamicImports, assetPrefix, devOnlyCacheBusterQueryString } = - this.context + const { + dynamicImports, + assetPrefix, + devOnlyCacheBusterQueryString, + crossOrigin, + } = this.context return ( dynamicImports @@ -349,9 +355,7 @@ export class Head extends Component< )}${devOnlyCacheBusterQueryString}`} as="script" nonce={this.props.nonce} - crossOrigin={ - this.props.crossOrigin || process.env.__NEXT_CROSS_ORIGIN - } + crossOrigin={this.props.crossOrigin || crossOrigin} /> ) }) @@ -361,8 +365,12 @@ export class Head extends Component< } getPreloadMainLinks(files: DocumentFiles): JSX.Element[] | null { - const { assetPrefix, devOnlyCacheBusterQueryString, scriptLoader } = - this.context + const { + assetPrefix, + devOnlyCacheBusterQueryString, + scriptLoader, + crossOrigin, + } = this.context const preloadFiles = files.allFiles.filter((file: string) => { return file.endsWith('.js') }) @@ -375,9 +383,7 @@ export class Head extends Component< rel="preload" href={file.src} as="script" - crossOrigin={ - this.props.crossOrigin || process.env.__NEXT_CROSS_ORIGIN - } + crossOrigin={this.props.crossOrigin || crossOrigin} /> )), ...preloadFiles.map((file: string) => ( @@ -389,9 +395,7 @@ export class Head extends Component< file )}${devOnlyCacheBusterQueryString}`} as="script" - crossOrigin={ - this.props.crossOrigin || process.env.__NEXT_CROSS_ORIGIN - } + crossOrigin={this.props.crossOrigin || crossOrigin} /> )), ] @@ -479,6 +483,10 @@ export class Head extends Component< unstable_JsPreload, disableOptimizedLoading, useMaybeDeferContent, + optimizeCss, + optimizeFonts, + optimizeImages, + concurrentFeatures, } = this.context const disableRuntimeJS = unstable_runtimeJS === false @@ -532,11 +540,7 @@ export class Head extends Component< ) } - if ( - process.env.NODE_ENV !== 'development' && - process.env.__NEXT_OPTIMIZE_FONTS && - !inAmpMode - ) { + if (process.env.NODE_ENV !== 'development' && optimizeFonts && !inAmpMode) { children = this.makeStylesheetInert(children) } @@ -650,32 +654,29 @@ export class Head extends Component< return ( - {!process.env.__NEXT_CONCURRENT_FEATURES && - this.context.isDevelopment && ( - <> + {!concurrentFeatures && this.context.isDevelopment && ( + <> +