diff --git a/.github/workflows/build_test_deploy.yml b/.github/workflows/build_test_deploy.yml index bf9b28cf3a04fae..7937f7a0ec667f5 100644 --- a/.github/workflows/build_test_deploy.yml +++ b/.github/workflows/build_test_deploy.yml @@ -208,12 +208,16 @@ jobs: env: NEXT_TELEMETRY_DISABLED: 1 NEXT_TEST_JOB: 1 + strategy: + fail-fast: false + matrix: + node: [16, 17] steps: - name: Setup node uses: actions/setup-node@v2 if: ${{needs.build.outputs.docsChange != 'docs only change'}} with: - node-version: 14 + node-version: ${{ matrix.node }} - run: echo ${{needs.build.outputs.docsChange}} @@ -258,12 +262,16 @@ jobs: env: NEXT_TELEMETRY_DISABLED: 1 NEXT_TEST_JOB: 1 + strategy: + fail-fast: false + matrix: + node: [16, 17] steps: - name: Setup node uses: actions/setup-node@v2 if: ${{needs.build.outputs.docsChange != 'docs only change'}} with: - node-version: 14 + node-version: ${{ matrix.node }} - run: echo ${{needs.build.outputs.docsChange}} @@ -308,12 +316,16 @@ jobs: env: NEXT_TELEMETRY_DISABLED: 1 NEXT_TEST_JOB: 1 + strategy: + fail-fast: false + matrix: + node: [16, 17] steps: - name: Setup node uses: actions/setup-node@v2 if: ${{needs.build.outputs.docsChange != 'docs only change'}} with: - node-version: 14 + node-version: ${{ matrix.node }} - run: echo ${{needs.build.outputs.docsChange}} @@ -348,12 +360,16 @@ jobs: env: NEXT_TELEMETRY_DISABLED: 1 NEXT_TEST_JOB: 1 + strategy: + fail-fast: false + matrix: + node: [16, 17] steps: - name: Setup node uses: actions/setup-node@v2 if: ${{needs.build.outputs.docsChange != 'docs only change'}} with: - node-version: 14 + node-version: ${{ matrix.node }} - run: echo ${{needs.build.outputs.docsChange}} diff --git a/docs/api-reference/next/image.md b/docs/api-reference/next/image.md index 4035fa8c65ec71a..2e75c38e14e7249 100644 --- a/docs/api-reference/next/image.md +++ b/docs/api-reference/next/image.md @@ -48,15 +48,23 @@ When using an external URL, you must add it to ### width -The width of the image, in pixels. Must be an integer without a unit. +The `width` property can represent either the _rendered_ width or _original_ width in pixels, depending on the [`layout`](#layout) and [`sizes`](#sizes) properties. -Required, except for statically imported images, or those with [`layout="fill"`](#layout). +When using `layout="intrinsic"`, `layout="fixed"`, or `layout="raw"` without `sizes`, the `width` property represents the _rendered_ width in pixels, so it will affect how large the image appears. + +When using `layout="responsive"`, `layout="fill"`, or `layout="raw"` with `sizes`, the `width` property represents the _original_ width in pixels, so it will only affect the aspect ratio. + +The `width` property is required, except for [statically imported images](#local-images), or those with `layout="fill"`. ### height -The height of the image, in pixels. Must be an integer without a unit. +The `height` property can represent either the _rendered_ height or _original_ height in pixels, depending on the [`layout`](#layout) and [`sizes`](#sizes) properties. + +When using `layout="intrinsic"`, `layout="fixed"`, or `layout="raw"` without `sizes`, the `height` property represents the _rendered_ height in pixels, so it will affect how large the image appears. + +When using `layout="responsive"`, `layout="fill"`, or `layout="raw"` with `sizes`, the `height` property represents the _original_ height in pixels, so it will only affect the aspect ratio. -Required, except for statically imported images, or those with [`layout="fill"`](#layout). +The `height` property is required, except for [statically imported images](#local-images), or those with `layout="fill"`. ## Optional Props diff --git a/docs/basic-features/image-optimization.md b/docs/basic-features/image-optimization.md index 9de528f1910b175..0c55837410ec0fa 100644 --- a/docs/basic-features/image-optimization.md +++ b/docs/basic-features/image-optimization.md @@ -150,8 +150,8 @@ One of the ways that images most commonly hurt performance is through _layout sh Because `next/image` is designed to guarantee good performance results, it cannot be used in a way that will contribute to layout shift, and **must** be sized in one of three ways: 1. Automatically, using a [static import](#local-images) -2. Explicitly, by including a `height` **and** `width` property -3. Implicitly, by using `layout="fill"` which causes the image to expand to fill its parent element. +2. Explicitly, by including a [`width`](/docs/api-reference/next/image.md#width) and [`height`](/docs/api-reference/next/image.md#height) property +3. Implicitly, by using [`layout="fill"`](/docs/api-reference/next/image.md#layout) which causes the image to expand to fill its parent element. > ### What if I don't know the size of my images? > diff --git a/lerna.json b/lerna.json index 4415b75844cbced..5e8cd0b1971db5c 100644 --- a/lerna.json +++ b/lerna.json @@ -16,5 +16,5 @@ "registry": "https://registry.npmjs.org/" } }, - "version": "12.1.1-canary.13" + "version": "12.1.1-canary.14" } diff --git a/packages/create-next-app/package.json b/packages/create-next-app/package.json index 2d57e6a8e2baa0e..d0e4f0d7d2a671f 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.1.1-canary.13", + "version": "12.1.1-canary.14", "keywords": [ "react", "next", diff --git a/packages/eslint-config-next/package.json b/packages/eslint-config-next/package.json index e68969e8aa4275c..5b3017880013fa0 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.1.1-canary.13", + "version": "12.1.1-canary.14", "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.1.1-canary.13", + "@next/eslint-plugin-next": "12.1.1-canary.14", "@rushstack/eslint-patch": "1.0.8", "@typescript-eslint/parser": "5.10.1", "eslint-import-resolver-node": "0.3.4", diff --git a/packages/eslint-plugin-next/package.json b/packages/eslint-plugin-next/package.json index 3fe50fb6b560104..690f94fef4168f6 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.1.1-canary.13", + "version": "12.1.1-canary.14", "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 fb7f83556de056e..a9a59a5c10672cb 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.1.1-canary.13", + "version": "12.1.1-canary.14", "main": "index.js", "license": "MIT", "repository": { diff --git a/packages/next-codemod/package.json b/packages/next-codemod/package.json index e709967e7c402cc..e3565e5c5371c03 100644 --- a/packages/next-codemod/package.json +++ b/packages/next-codemod/package.json @@ -1,6 +1,6 @@ { "name": "@next/codemod", - "version": "12.1.1-canary.13", + "version": "12.1.1-canary.14", "license": "MIT", "dependencies": { "chalk": "4.1.0", diff --git a/packages/next-env/package.json b/packages/next-env/package.json index 2cc25fd36d5d215..3047335bfe99bd4 100644 --- a/packages/next-env/package.json +++ b/packages/next-env/package.json @@ -1,6 +1,6 @@ { "name": "@next/env", - "version": "12.1.1-canary.13", + "version": "12.1.1-canary.14", "keywords": [ "react", "next", diff --git a/packages/next-mdx/package.json b/packages/next-mdx/package.json index 868e2c86ced2bbd..1e157d63d80d576 100644 --- a/packages/next-mdx/package.json +++ b/packages/next-mdx/package.json @@ -1,6 +1,6 @@ { "name": "@next/mdx", - "version": "12.1.1-canary.13", + "version": "12.1.1-canary.14", "main": "index.js", "license": "MIT", "repository": { diff --git a/packages/next-plugin-storybook/package.json b/packages/next-plugin-storybook/package.json index 74a1e3a4e04e40e..66f42c4541a2199 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.1.1-canary.13", + "version": "12.1.1-canary.14", "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 70fc47de314b664..01186936731e9f8 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.1.1-canary.13", + "version": "12.1.1-canary.14", "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 e3a31329d734cc9..07ce96320c474a4 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.1.1-canary.13", + "version": "12.1.1-canary.14", "description": "A polyfill for non-dead, nomodule browsers.", "main": "dist/polyfill-nomodule.js", "license": "MIT", diff --git a/packages/next-swc/package.json b/packages/next-swc/package.json index dbe4fe548158304..4e36d27fbd4c8a8 100644 --- a/packages/next-swc/package.json +++ b/packages/next-swc/package.json @@ -1,6 +1,6 @@ { "name": "@next/swc", - "version": "12.1.1-canary.13", + "version": "12.1.1-canary.14", "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 c6a57df897a06d8..304b7047b1490ad 100644 --- a/packages/next/build/entries.ts +++ b/packages/next/build/entries.ts @@ -187,6 +187,9 @@ export async function getPageRuntime( if (!pageRuntime) { if (isRuntimeRequired) { pageRuntime = globalRuntimeFallback + } else { + // @TODO: Remove this branch to fully implement the RFC. + pageRuntime = globalRuntimeFallback } } diff --git a/packages/next/build/index.ts b/packages/next/build/index.ts index 3e0584d68b43948..e1176656fe08d14 100644 --- a/packages/next/build/index.ts +++ b/packages/next/build/index.ts @@ -76,11 +76,7 @@ import { } from '../telemetry/events' import { Telemetry } from '../telemetry/storage' import { CompilerResult, runCompiler } from './compiler' -import { - createEntrypoints, - createPagesMapping, - getPageRuntime, -} from './entries' +import { createEntrypoints, createPagesMapping } from './entries' import { generateBuildId } from './generate-build-id' import { isWriteable } from './is-writeable' import * as Log from './output/log' @@ -157,10 +153,11 @@ export default async function build( setGlobal('phase', PHASE_PRODUCTION_BUILD) setGlobal('distDir', distDir) - // We enable concurrent features (Fizz-related rendering architecture) when - // using React 18 or experimental. + // Currently, when the runtime option is set (either `nodejs` or `edge`), + // we enable concurrent features (Fizz-related rendering architecture). + const runtime = config.experimental.runtime const hasReactRoot = shouldUseReactRoot() - const hasConcurrentFeatures = hasReactRoot + const hasConcurrentFeatures = !!runtime const hasServerComponents = hasReactRoot && !!config.experimental.serverComponents @@ -625,7 +622,6 @@ export default async function build( entrypoints: entrypoints.client, rewrites, runWebpackSpan, - hasReactRoot, }), getBaseWebpackConfig(dir, { buildId, @@ -637,7 +633,6 @@ export default async function build( entrypoints: entrypoints.server, rewrites, runWebpackSpan, - hasReactRoot, }), hasReactRoot ? getBaseWebpackConfig(dir, { @@ -651,7 +646,6 @@ export default async function build( entrypoints: entrypoints.edgeServer, rewrites, runWebpackSpan, - hasReactRoot, }) : null, ]) @@ -960,22 +954,10 @@ export default async function build( let ssgPageRoutes: string[] | null = null let isMiddlewareRoute = !!page.match(MIDDLEWARE_ROUTE) - const pagePath = pagePaths.find((_path) => - _path.startsWith(actualPage + '.') - ) - const pageRuntime = - hasConcurrentFeatures && pagePath - ? await getPageRuntime( - join(pagesDir, pagePath), - config.experimental.runtime - ) - : null - if ( !isMiddlewareRoute && !isReservedPage(page) && - // We currently don't support staic optimization in the Edge runtime. - pageRuntime !== 'edge' + !hasConcurrentFeatures ) { try { let isPageStaticSpan = @@ -1501,7 +1483,10 @@ export default async function build( const combinedPages = [...staticPages, ...ssgPages] - if (combinedPages.length > 0 || useStatic404 || useDefaultStatic500) { + if ( + !hasConcurrentFeatures && + (combinedPages.length > 0 || useStatic404 || useDefaultStatic500) + ) { const staticGenerationSpan = nextBuildSpan.traceChild('static-generation') await staticGenerationSpan.traceAsyncFn(async () => { detectConflictingPaths( diff --git a/packages/next/build/webpack-config.ts b/packages/next/build/webpack-config.ts index a42abcae003e0a5..b07c42e17557b83 100644 --- a/packages/next/build/webpack-config.ts +++ b/packages/next/build/webpack-config.ts @@ -48,6 +48,7 @@ import type { Span } from '../trace' import { getRawPageExtensions } from './utils' import browserslist from 'next/dist/compiled/browserslist' import loadJsConfig from './load-jsconfig' +import { shouldUseReactRoot } from '../server/config' import { getMiddlewareSourceMapPlugins } from './webpack/plugins/middleware-source-maps-plugin' const watchOptions = Object.freeze({ @@ -309,7 +310,6 @@ export default async function getBaseWebpackConfig( rewrites, isDevFallback = false, runWebpackSpan, - hasReactRoot, }: { buildId: string config: NextConfigComplete @@ -323,7 +323,6 @@ export default async function getBaseWebpackConfig( rewrites: CustomRoutes['rewrites'] isDevFallback?: boolean runWebpackSpan: Span - hasReactRoot: boolean } ): Promise { const { useTypeScript, jsConfig, resolvedBaseUrl } = await loadJsConfig( @@ -336,10 +335,10 @@ export default async function getBaseWebpackConfig( rewrites.afterFiles.length > 0 || rewrites.fallback.length > 0 const hasReactRefresh: boolean = dev && !isServer - + const hasReactRoot = shouldUseReactRoot() const runtime = config.experimental.runtime - // Make sure `reactRoot` is enabled when React 18 or experimental is detected. + // Make sure reactRoot is enabled when react 18 is detected if (hasReactRoot) { config.experimental.reactRoot = true } @@ -354,14 +353,14 @@ export default async function getBaseWebpackConfig( '`experimental.runtime` requires `experimental.reactRoot` to be enabled along with React 18.' ) } - if (config.experimental.serverComponents && !hasReactRoot) { + if (config.experimental.serverComponents && !runtime) { throw new Error( - '`experimental.serverComponents` requires React 18 to be installed.' + '`experimental.runtime` is required to be set along with `experimental.serverComponents`.' ) } const targetWeb = isEdgeRuntime || !isServer - const hasConcurrentFeatures = hasReactRoot + const hasConcurrentFeatures = !!runtime && hasReactRoot const hasServerComponents = hasConcurrentFeatures && !!config.experimental.serverComponents const disableOptimizedLoading = hasConcurrentFeatures diff --git a/packages/next/client/index.tsx b/packages/next/client/index.tsx index c00d17f7276cd09..cd2f3fc315b2e68 100644 --- a/packages/next/client/index.tsx +++ b/packages/next/client/index.tsx @@ -678,9 +678,13 @@ if (process.env.__NEXT_RSC) { } = require('next/dist/compiled/react-server-dom-webpack') const encoder = new TextEncoder() + let initialServerDataBuffer: string[] | undefined = undefined let initialServerDataWriter: WritableStreamDefaultWriter | undefined = undefined + let initialServerDataLoaded = false + let initialServerDataFlushed = false + function nextServerDataCallback(seg: [number, string, string]) { if (seg[0] === 0) { initialServerDataBuffer = [] @@ -695,24 +699,45 @@ if (process.env.__NEXT_RSC) { } } } + + // There might be race conditions between `nextServerDataRegisterWriter` and + // `DOMContentLoaded`. The former will be called when React starts to hydrate + // the root, the latter will be called when the DOM is fully loaded. + // For streaming, the former is called first due to partial hydration. + // For non-streaming, the latter can be called first. + // Hence, we use two variables `initialServerDataLoaded` and + // `initialServerDataFlushed` to make sure the writer will be closed and + // `initialServerDataBuffer` will be cleared in the right time. function nextServerDataRegisterWriter(writer: WritableStreamDefaultWriter) { if (initialServerDataBuffer) { initialServerDataBuffer.forEach((val) => { writer.write(encoder.encode(val)) }) + if (initialServerDataLoaded && !initialServerDataFlushed) { + writer.close() + initialServerDataFlushed = true + initialServerDataBuffer = undefined + } } + initialServerDataWriter = writer } + // When `DOMContentLoaded`, we can close all pending writers to finish hydration. - document.addEventListener( - 'DOMContentLoaded', - function () { - if (initialServerDataWriter && !initialServerDataWriter.closed) { - initialServerDataWriter.close() - } - }, - false - ) + const DOMContentLoaded = function () { + if (initialServerDataWriter && !initialServerDataFlushed) { + initialServerDataWriter.close() + initialServerDataFlushed = true + initialServerDataBuffer = undefined + } + initialServerDataLoaded = true + } + // It's possible that the DOM is already loaded. + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', DOMContentLoaded, false) + } else { + DOMContentLoaded() + } const nextServerDataLoadingGlobal = ((self as any).__next_s = (self as any).__next_s || []) @@ -771,9 +796,6 @@ if (process.env.__NEXT_RSC) { React.useEffect(() => { rscCache.delete(cacheKey) }) - React.useEffect(() => { - initialServerDataBuffer = undefined - }, []) const response = useServerResponse(cacheKey, serialized) const root = response.readRoot() return root diff --git a/packages/next/export/index.ts b/packages/next/export/index.ts index df995262048eeee..937c01ab089cfc3 100644 --- a/packages/next/export/index.ts +++ b/packages/next/export/index.ts @@ -588,7 +588,6 @@ export default async function exportApp( nextConfig.experimental.disableOptimizedLoading, parentSpanId: pageExportSpan.id, httpAgentOptions: nextConfig.httpAgentOptions, - serverComponents: nextConfig.experimental.serverComponents, }) for (const validation of result.ampValidations || []) { diff --git a/packages/next/export/worker.ts b/packages/next/export/worker.ts index 3679b28eebd1d4a..f2e6dbb64f133f0 100644 --- a/packages/next/export/worker.ts +++ b/packages/next/export/worker.ts @@ -59,7 +59,6 @@ interface ExportPageInput { disableOptimizedLoading: any parentSpanId: any httpAgentOptions: NextConfigComplete['httpAgentOptions'] - serverComponents?: boolean } interface ExportPageResults { @@ -107,7 +106,6 @@ export default async function exportPage({ optimizeCss, disableOptimizedLoading, httpAgentOptions, - serverComponents, }: ExportPageInput): Promise { setHttpAgentOptions(httpAgentOptions) const exportPageSpan = trace('export-page-worker', parentSpanId) @@ -262,7 +260,7 @@ export default async function exportPage({ getServerSideProps, getStaticProps, pageConfig, - } = await loadComponents(distDir, page, serverless, serverComponents) + } = await loadComponents(distDir, page, serverless) const ampState = { ampFirst: pageConfig?.amp === true, hasQuery: Boolean(query.amp), @@ -323,12 +321,7 @@ export default async function exportPage({ throw new Error(`Failed to render serverless page`) } } else { - const components = await loadComponents( - distDir, - page, - serverless, - serverComponents - ) + const components = await loadComponents(distDir, page, serverless) const ampState = { ampFirst: components.pageConfig?.amp === true, hasQuery: Boolean(query.amp), diff --git a/packages/next/package.json b/packages/next/package.json index 355a8dc0cd55a0f..91ce61279a02b5a 100644 --- a/packages/next/package.json +++ b/packages/next/package.json @@ -1,6 +1,6 @@ { "name": "next", - "version": "12.1.1-canary.13", + "version": "12.1.1-canary.14", "description": "The React Framework", "main": "./dist/server/next.js", "license": "MIT", @@ -69,7 +69,7 @@ ] }, "dependencies": { - "@next/env": "12.1.1-canary.13", + "@next/env": "12.1.1-canary.14", "caniuse-lite": "^1.0.30001283", "postcss": "8.4.5", "styled-jsx": "5.0.1", @@ -118,11 +118,11 @@ "@hapi/accept": "5.0.2", "@napi-rs/cli": "2.4.4", "@napi-rs/triples": "1.1.0", - "@next/polyfill-module": "12.1.1-canary.13", - "@next/polyfill-nomodule": "12.1.1-canary.13", - "@next/react-dev-overlay": "12.1.1-canary.13", - "@next/react-refresh-utils": "12.1.1-canary.13", - "@next/swc": "12.1.1-canary.13", + "@next/polyfill-module": "12.1.1-canary.14", + "@next/polyfill-nomodule": "12.1.1-canary.14", + "@next/react-dev-overlay": "12.1.1-canary.14", + "@next/react-refresh-utils": "12.1.1-canary.14", + "@next/swc": "12.1.1-canary.14", "@peculiar/webcrypto": "1.3.1", "@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 72868292fa6aeb6..adc58bf357df7cb 100644 --- a/packages/next/pages/_document.tsx +++ b/packages/next/pages/_document.tsx @@ -585,7 +585,6 @@ export class Head extends Component< disableOptimizedLoading, optimizeCss, optimizeFonts, - hasConcurrentFeatures, } = this.context const disableRuntimeJS = unstable_runtimeJS === false @@ -700,7 +699,7 @@ export class Head extends Component< return ( - {!hasConcurrentFeatures && this.context.isDevelopment && ( + {this.context.isDevelopment && ( <>