From 1c83394dd3e0b439c283bc7be4b1c7771ae8bda6 Mon Sep 17 00:00:00 2001 From: vercel-release-bot Date: Mon, 27 May 2024 23:23:34 +0000 Subject: [PATCH 1/2] v14.3.0-canary.85 --- lerna.json | 2 +- packages/create-next-app/package.json | 2 +- packages/eslint-config-next/package.json | 4 ++-- packages/eslint-plugin-next/package.json | 2 +- packages/font/package.json | 2 +- packages/next-bundle-analyzer/package.json | 2 +- packages/next-codemod/package.json | 2 +- packages/next-env/package.json | 2 +- packages/next-mdx/package.json | 2 +- packages/next-plugin-storybook/package.json | 2 +- packages/next-polyfill-module/package.json | 2 +- packages/next-polyfill-nomodule/package.json | 2 +- packages/next-swc/package.json | 2 +- packages/next/package.json | 12 ++++++------ packages/react-refresh-utils/package.json | 2 +- packages/third-parties/package.json | 4 ++-- pnpm-lock.yaml | 14 +++++++------- 17 files changed, 30 insertions(+), 30 deletions(-) diff --git a/lerna.json b/lerna.json index 57e797a39e837..de2f5794cf3e5 100644 --- a/lerna.json +++ b/lerna.json @@ -16,5 +16,5 @@ "registry": "https://registry.npmjs.org/" } }, - "version": "14.3.0-canary.84" + "version": "14.3.0-canary.85" } diff --git a/packages/create-next-app/package.json b/packages/create-next-app/package.json index 6a391dd8f5a81..aa04c9ea1a9ed 100644 --- a/packages/create-next-app/package.json +++ b/packages/create-next-app/package.json @@ -1,6 +1,6 @@ { "name": "create-next-app", - "version": "14.3.0-canary.84", + "version": "14.3.0-canary.85", "keywords": [ "react", "next", diff --git a/packages/eslint-config-next/package.json b/packages/eslint-config-next/package.json index 2db5969f51e04..7aeda6dd74b2e 100644 --- a/packages/eslint-config-next/package.json +++ b/packages/eslint-config-next/package.json @@ -1,6 +1,6 @@ { "name": "eslint-config-next", - "version": "14.3.0-canary.84", + "version": "14.3.0-canary.85", "description": "ESLint configuration used by Next.js.", "main": "index.js", "license": "MIT", @@ -10,7 +10,7 @@ }, "homepage": "https://nextjs.org/docs/app/building-your-application/configuring/eslint#eslint-config", "dependencies": { - "@next/eslint-plugin-next": "14.3.0-canary.84", + "@next/eslint-plugin-next": "14.3.0-canary.85", "@rushstack/eslint-patch": "^1.3.3", "@typescript-eslint/parser": "^5.4.2 || ^6.0.0 || 7.0.0 - 7.2.0", "eslint-import-resolver-node": "^0.3.6", diff --git a/packages/eslint-plugin-next/package.json b/packages/eslint-plugin-next/package.json index 9945ace0b5353..13758b5c1cb79 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": "14.3.0-canary.84", + "version": "14.3.0-canary.85", "description": "ESLint plugin for Next.js.", "main": "dist/index.js", "license": "MIT", diff --git a/packages/font/package.json b/packages/font/package.json index 5a147c50c7842..e3c8f05b16dfe 100644 --- a/packages/font/package.json +++ b/packages/font/package.json @@ -1,6 +1,6 @@ { "name": "@next/font", - "version": "14.3.0-canary.84", + "version": "14.3.0-canary.85", "repository": { "url": "vercel/next.js", "directory": "packages/font" diff --git a/packages/next-bundle-analyzer/package.json b/packages/next-bundle-analyzer/package.json index 6c767469dc397..45ed042d16a07 100644 --- a/packages/next-bundle-analyzer/package.json +++ b/packages/next-bundle-analyzer/package.json @@ -1,6 +1,6 @@ { "name": "@next/bundle-analyzer", - "version": "14.3.0-canary.84", + "version": "14.3.0-canary.85", "main": "index.js", "types": "index.d.ts", "license": "MIT", diff --git a/packages/next-codemod/package.json b/packages/next-codemod/package.json index c67f7d40f3aab..62a2e067a3cbc 100644 --- a/packages/next-codemod/package.json +++ b/packages/next-codemod/package.json @@ -1,6 +1,6 @@ { "name": "@next/codemod", - "version": "14.3.0-canary.84", + "version": "14.3.0-canary.85", "license": "MIT", "repository": { "type": "git", diff --git a/packages/next-env/package.json b/packages/next-env/package.json index f0b3bc21da2b3..3bab50e46e2df 100644 --- a/packages/next-env/package.json +++ b/packages/next-env/package.json @@ -1,6 +1,6 @@ { "name": "@next/env", - "version": "14.3.0-canary.84", + "version": "14.3.0-canary.85", "keywords": [ "react", "next", diff --git a/packages/next-mdx/package.json b/packages/next-mdx/package.json index 49d680401cb6f..068cd50504c66 100644 --- a/packages/next-mdx/package.json +++ b/packages/next-mdx/package.json @@ -1,6 +1,6 @@ { "name": "@next/mdx", - "version": "14.3.0-canary.84", + "version": "14.3.0-canary.85", "main": "index.js", "license": "MIT", "repository": { diff --git a/packages/next-plugin-storybook/package.json b/packages/next-plugin-storybook/package.json index 7a7ba852f35ee..680b9c5a5ce87 100644 --- a/packages/next-plugin-storybook/package.json +++ b/packages/next-plugin-storybook/package.json @@ -1,6 +1,6 @@ { "name": "@next/plugin-storybook", - "version": "14.3.0-canary.84", + "version": "14.3.0-canary.85", "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 1515a173e4e37..a004e7ba9618a 100644 --- a/packages/next-polyfill-module/package.json +++ b/packages/next-polyfill-module/package.json @@ -1,6 +1,6 @@ { "name": "@next/polyfill-module", - "version": "14.3.0-canary.84", + "version": "14.3.0-canary.85", "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 2b967a77bbf8f..1a4f52f779b60 100644 --- a/packages/next-polyfill-nomodule/package.json +++ b/packages/next-polyfill-nomodule/package.json @@ -1,6 +1,6 @@ { "name": "@next/polyfill-nomodule", - "version": "14.3.0-canary.84", + "version": "14.3.0-canary.85", "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 1f51dc1d892c8..830a1cd9701cb 100644 --- a/packages/next-swc/package.json +++ b/packages/next-swc/package.json @@ -1,6 +1,6 @@ { "name": "@next/swc", - "version": "14.3.0-canary.84", + "version": "14.3.0-canary.85", "private": true, "scripts": { "clean": "node ../../scripts/rm.mjs native", diff --git a/packages/next/package.json b/packages/next/package.json index 4bd3f3b5eb8cc..9cecc8a3a659d 100644 --- a/packages/next/package.json +++ b/packages/next/package.json @@ -1,6 +1,6 @@ { "name": "next", - "version": "14.3.0-canary.84", + "version": "14.3.0-canary.85", "description": "The React Framework", "main": "./dist/server/next.js", "license": "MIT", @@ -93,7 +93,7 @@ ] }, "dependencies": { - "@next/env": "14.3.0-canary.84", + "@next/env": "14.3.0-canary.85", "@swc/helpers": "0.5.11", "busboy": "1.6.0", "caniuse-lite": "^1.0.30001579", @@ -157,10 +157,10 @@ "@jest/types": "29.5.0", "@mswjs/interceptors": "0.23.0", "@napi-rs/triples": "1.2.0", - "@next/polyfill-module": "14.3.0-canary.84", - "@next/polyfill-nomodule": "14.3.0-canary.84", - "@next/react-refresh-utils": "14.3.0-canary.84", - "@next/swc": "14.3.0-canary.84", + "@next/polyfill-module": "14.3.0-canary.85", + "@next/polyfill-nomodule": "14.3.0-canary.85", + "@next/react-refresh-utils": "14.3.0-canary.85", + "@next/swc": "14.3.0-canary.85", "@opentelemetry/api": "1.6.0", "@playwright/test": "1.41.2", "@swc/core": "1.5.7", diff --git a/packages/react-refresh-utils/package.json b/packages/react-refresh-utils/package.json index efe0a38755352..683c8a34a8be1 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": "14.3.0-canary.84", + "version": "14.3.0-canary.85", "description": "An experimental package providing utilities for React Refresh.", "repository": { "url": "vercel/next.js", diff --git a/packages/third-parties/package.json b/packages/third-parties/package.json index 379d2ca8fbf5d..ecd502c502093 100644 --- a/packages/third-parties/package.json +++ b/packages/third-parties/package.json @@ -1,6 +1,6 @@ { "name": "@next/third-parties", - "version": "14.3.0-canary.84", + "version": "14.3.0-canary.85", "repository": { "url": "vercel/next.js", "directory": "packages/third-parties" @@ -26,7 +26,7 @@ "third-party-capital": "1.0.20" }, "devDependencies": { - "next": "14.3.0-canary.84", + "next": "14.3.0-canary.85", "outdent": "0.8.0", "prettier": "2.5.1" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index cbf5cb6a491e5..845c74adfa57a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -753,7 +753,7 @@ importers: packages/eslint-config-next: dependencies: '@next/eslint-plugin-next': - specifier: 14.3.0-canary.84 + specifier: 14.3.0-canary.85 version: link:../eslint-plugin-next '@rushstack/eslint-patch': specifier: ^1.3.3 @@ -815,7 +815,7 @@ importers: packages/next: dependencies: '@next/env': - specifier: 14.3.0-canary.84 + specifier: 14.3.0-canary.85 version: link:../next-env '@swc/helpers': specifier: 0.5.11 @@ -943,16 +943,16 @@ importers: specifier: 1.2.0 version: 1.2.0 '@next/polyfill-module': - specifier: 14.3.0-canary.84 + specifier: 14.3.0-canary.85 version: link:../next-polyfill-module '@next/polyfill-nomodule': - specifier: 14.3.0-canary.84 + specifier: 14.3.0-canary.85 version: link:../next-polyfill-nomodule '@next/react-refresh-utils': - specifier: 14.3.0-canary.84 + specifier: 14.3.0-canary.85 version: link:../react-refresh-utils '@next/swc': - specifier: 14.3.0-canary.84 + specifier: 14.3.0-canary.85 version: link:../next-swc '@opentelemetry/api': specifier: 1.6.0 @@ -1576,7 +1576,7 @@ importers: version: 1.0.20 devDependencies: next: - specifier: 14.3.0-canary.84 + specifier: 14.3.0-canary.85 version: link:../next outdent: specifier: 0.8.0 From 1415609870fcdea2d0c0a1ab210a26f9dcb4859b Mon Sep 17 00:00:00 2001 From: Jiachi Liu Date: Tue, 28 May 2024 13:55:12 +0200 Subject: [PATCH 2/2] Reland bunling webpack middleware changes (#66049) (#66052) Revert the revert in #66049 It was erroring in pages api with importing `react-dom/server` as this is disallowed in app but shouldn't be in pages. It's caused by we're validating middleware layer as server components but edge pages api is still bundled in the same layer, where we shouldn't apply the check. * Separate the api in api layers, and while handling middleware warnings, checking api layer as well * No need to check layers while handling externals in edge compiler * Found a bug that we shouldn't check if `config.transpilePackages` is defined then we enable `externalDir`, removed that condition. It fails the telemetry tests case build with code change from this PR. Add more tests for pages dir and middleware | | `react` condition | `react-dom/server` condition | | ---- | ---- | ---- | | middleware (edge) | react-server | not allowed, failed with dev/build checks | | pages/api edge | default condition | default condition | | pages/api node | default condition | default condition | --- packages/next/src/build/entries.ts | 11 ++-- packages/next/src/build/handle-externals.ts | 4 +- packages/next/src/build/utils.ts | 13 +++- packages/next/src/build/webpack-config.ts | 33 +++++----- .../webpack/plugins/middleware-plugin.ts | 11 +++- packages/next/src/lib/constants.ts | 7 ++- test/e2e/module-layer/middleware.js | 13 +++- test/e2e/module-layer/module-layer.test.ts | 60 ++++++++++++++----- .../module-layer/pages/api/default-edge.js | 11 ++++ test/e2e/module-layer/pages/api/default.js | 9 +++ test/e2e/module-layer/pages/api/hello-edge.js | 7 --- test/e2e/module-layer/pages/api/hello.js | 5 -- .../pages/api/server-only-edge.js | 12 ++++ .../e2e/module-layer/pages/api/server-only.js | 10 ++++ 14 files changed, 151 insertions(+), 55 deletions(-) create mode 100644 test/e2e/module-layer/pages/api/default-edge.js create mode 100644 test/e2e/module-layer/pages/api/default.js delete mode 100644 test/e2e/module-layer/pages/api/hello-edge.js delete mode 100644 test/e2e/module-layer/pages/api/hello.js create mode 100644 test/e2e/module-layer/pages/api/server-only-edge.js create mode 100644 test/e2e/module-layer/pages/api/server-only.js diff --git a/packages/next/src/build/entries.ts b/packages/next/src/build/entries.ts index 784d1aead8fb2..f7310dc3d24cd 100644 --- a/packages/next/src/build/entries.ts +++ b/packages/next/src/build/entries.ts @@ -812,11 +812,14 @@ export function finalizeEntrypoint({ } } case COMPILER_NAMES.edgeServer: { + const layer = isApi + ? WEBPACK_LAYERS.api + : isMiddlewareFilename(name) || isInstrumentation + ? WEBPACK_LAYERS.middleware + : undefined + return { - layer: - isMiddlewareFilename(name) || isApi || isInstrumentation - ? WEBPACK_LAYERS.middleware - : undefined, + layer, library: { name: ['_ENTRIES', `middleware_[name]`], type: 'assign' }, runtime: EDGE_RUNTIME_WEBPACK, asyncChunks: false, diff --git a/packages/next/src/build/handle-externals.ts b/packages/next/src/build/handle-externals.ts index 9ca94214035f3..5b38dd26e0994 100644 --- a/packages/next/src/build/handle-externals.ts +++ b/packages/next/src/build/handle-externals.ts @@ -10,7 +10,7 @@ import { NODE_ESM_RESOLVE_OPTIONS, NODE_RESOLVE_OPTIONS, } from './webpack-config' -import { isWebpackAppLayer, isWebpackServerOnlyLayer } from './utils' +import { isWebpackBundledLayer, isWebpackServerOnlyLayer } from './utils' import { normalizePathSep } from '../shared/lib/page-path/normalize-path-sep' const reactPackagesRegex = /^(react|react-dom|react-server-dom-webpack)($|\/)/ @@ -174,7 +174,7 @@ export function makeExternalHandler({ return `commonjs next/dist/lib/import-next-warning` } - const isAppLayer = isWebpackAppLayer(layer) + const isAppLayer = isWebpackBundledLayer(layer) // Relative requires don't need custom resolution, because they // are relative to requests we've already resolved here. diff --git a/packages/next/src/build/utils.ts b/packages/next/src/build/utils.ts index bcfd2c5643381..4c841865a9b8c 100644 --- a/packages/next/src/build/utils.ts +++ b/packages/next/src/build/utils.ts @@ -2256,6 +2256,15 @@ export function getSupportedBrowsers( return MODERN_BROWSERSLIST_TARGET } +// Use next/dist/compiled/react packages instead of installed react +export function isWebpackBuiltinReactLayer( + layer: WebpackLayerName | null | undefined +): boolean { + return Boolean( + layer && WEBPACK_LAYERS.GROUP.builtinReact.includes(layer as any) + ) +} + export function isWebpackServerOnlyLayer( layer: WebpackLayerName | null | undefined ): boolean { @@ -2278,8 +2287,8 @@ export function isWebpackDefaultLayer( return layer === null || layer === undefined } -export function isWebpackAppLayer( +export function isWebpackBundledLayer( layer: WebpackLayerName | null | undefined ): boolean { - return Boolean(layer && WEBPACK_LAYERS.GROUP.app.includes(layer as any)) + return Boolean(layer && WEBPACK_LAYERS.GROUP.bundled.includes(layer as any)) } diff --git a/packages/next/src/build/webpack-config.ts b/packages/next/src/build/webpack-config.ts index 42ceb0368f877..4b1b92e4992ec 100644 --- a/packages/next/src/build/webpack-config.ts +++ b/packages/next/src/build/webpack-config.ts @@ -9,7 +9,8 @@ import { escapeStringRegexp } from '../shared/lib/escape-regexp' import { WEBPACK_LAYERS, WEBPACK_RESOURCE_QUERIES } from '../lib/constants' import type { WebpackLayerName } from '../lib/constants' import { - isWebpackAppLayer, + isWebpackBuiltinReactLayer, + isWebpackBundledLayer, isWebpackClientOnlyLayer, isWebpackDefaultLayer, isWebpackServerOnlyLayer, @@ -409,8 +410,7 @@ export default async function getBaseWebpackConfig( loggedIgnoredCompilerOptions = true } - const shouldIncludeExternalDirs = - config.experimental.externalDir || !!config.transpilePackages + const shouldIncludeExternalDirs = config.experimental.externalDir const codeCondition = { test: { or: [/\.(tsx|ts|js|cjs|mjs|jsx)$/, /__barrel_optimize__/] }, ...(shouldIncludeExternalDirs @@ -543,7 +543,7 @@ export default async function getBaseWebpackConfig( // This will cause some performance overhead but // acceptable as Babel will not be recommended. getSwcLoader({ - serverComponents: false, + serverComponents: true, bundleLayer: WEBPACK_LAYERS.middleware, }), babelLoader, @@ -592,13 +592,12 @@ export default async function getBaseWebpackConfig( // Loader for API routes needs to be differently configured as it shouldn't // have RSC transpiler enabled, so syntax checks such as invalid imports won't // be performed. - const apiRoutesLayerLoaders = - hasAppDir && useSWCLoader - ? getSwcLoader({ - serverComponents: false, - bundleLayer: WEBPACK_LAYERS.api, - }) - : defaultLoaders.babel + const apiRoutesLayerLoaders = useSWCLoader + ? getSwcLoader({ + serverComponents: false, + bundleLayer: WEBPACK_LAYERS.api, + }) + : defaultLoaders.babel const pageExtensions = config.pageExtensions @@ -1304,7 +1303,7 @@ export default async function getBaseWebpackConfig( test: /next[\\/]dist[\\/](esm[\\/])?server[\\/]future[\\/]route-modules[\\/]app-page[\\/]module/, }, { - issuerLayer: isWebpackAppLayer, + issuerLayer: isWebpackBundledLayer, resolve: { alias: createNextApiEsmAliases(), }, @@ -1326,7 +1325,7 @@ export default async function getBaseWebpackConfig( ...(hasAppDir && !isClient ? [ { - issuerLayer: isWebpackServerOnlyLayer, + issuerLayer: isWebpackBuiltinReactLayer, test: { // Resolve it if it is a source code file, and it has NOT been // opted out of bundling. @@ -1388,7 +1387,7 @@ export default async function getBaseWebpackConfig( // Alias react for switching between default set and share subset. oneOf: [ { - issuerLayer: isWebpackServerOnlyLayer, + issuerLayer: isWebpackBuiltinReactLayer, test: { // Resolve it if it is a source code file, and it has NOT been // opted out of bundling. @@ -1469,11 +1468,17 @@ export default async function getBaseWebpackConfig( test: codeCondition.test, issuerLayer: WEBPACK_LAYERS.middleware, use: middlewareLayerLoaders, + resolve: { + conditionNames: reactServerCondition, + }, }, { test: codeCondition.test, issuerLayer: WEBPACK_LAYERS.instrument, use: instrumentLayerLoaders, + resolve: { + conditionNames: reactServerCondition, + }, }, ...(hasAppDir ? [ diff --git a/packages/next/src/build/webpack/plugins/middleware-plugin.ts b/packages/next/src/build/webpack/plugins/middleware-plugin.ts index 866aff2ae1a32..e6bdccd6da88e 100644 --- a/packages/next/src/build/webpack/plugins/middleware-plugin.ts +++ b/packages/next/src/build/webpack/plugins/middleware-plugin.ts @@ -28,7 +28,10 @@ import type { Telemetry } from '../../../telemetry/storage' import { traceGlobals } from '../../../trace/shared' import { EVENT_BUILD_FEATURE_USAGE } from '../../../telemetry/events' import { normalizeAppPath } from '../../../shared/lib/router/utils/app-paths' -import { INSTRUMENTATION_HOOK_FILENAME } from '../../../lib/constants' +import { + INSTRUMENTATION_HOOK_FILENAME, + WEBPACK_LAYERS, +} from '../../../lib/constants' import type { CustomRoutes } from '../../../lib/load-custom-routes' import { isInterceptionRouteRewrite } from '../../../lib/generate-interception-routes-rewrites' import { getDynamicCodeEvaluationError } from './wellknown-errors-plugin/parse-dynamic-code-evaluation-error' @@ -272,7 +275,8 @@ function buildWebpackError({ } function isInMiddlewareLayer(parser: webpack.javascript.JavascriptParser) { - return parser.state.module?.layer === 'middleware' + const layer = parser.state.module?.layer + return layer === WEBPACK_LAYERS.middleware || layer === WEBPACK_LAYERS.api } function isNodeJsModule(moduleName: string) { @@ -849,7 +853,8 @@ export async function handleWebpackExternalForEdgeRuntime({ getResolve: () => any }) { if ( - contextInfo.issuerLayer === 'middleware' && + (contextInfo.issuerLayer === WEBPACK_LAYERS.middleware || + contextInfo.issuerLayer === WEBPACK_LAYERS.api) && isNodeJsModule(request) && !supportedEdgePolyfills.has(request) ) { diff --git a/packages/next/src/lib/constants.ts b/packages/next/src/lib/constants.ts index c250a65b4afbb..c098c8da62499 100644 --- a/packages/next/src/lib/constants.ts +++ b/packages/next/src/lib/constants.ts @@ -159,6 +159,11 @@ export type WebpackLayerName = const WEBPACK_LAYERS = { ...WEBPACK_LAYERS_NAMES, GROUP: { + builtinReact: [ + WEBPACK_LAYERS_NAMES.reactServerComponents, + WEBPACK_LAYERS_NAMES.actionBrowser, + WEBPACK_LAYERS_NAMES.appMetadataRoute, + ], serverOnly: [ WEBPACK_LAYERS_NAMES.reactServerComponents, WEBPACK_LAYERS_NAMES.actionBrowser, @@ -174,7 +179,7 @@ const WEBPACK_LAYERS = { WEBPACK_LAYERS_NAMES.serverSideRendering, WEBPACK_LAYERS_NAMES.appPagesBrowser, ], - app: [ + bundled: [ WEBPACK_LAYERS_NAMES.reactServerComponents, WEBPACK_LAYERS_NAMES.actionBrowser, WEBPACK_LAYERS_NAMES.appMetadataRoute, diff --git a/test/e2e/module-layer/middleware.js b/test/e2e/module-layer/middleware.js index 8a4d11761dd78..cd3fc531ccbe4 100644 --- a/test/e2e/module-layer/middleware.js +++ b/test/e2e/module-layer/middleware.js @@ -1,11 +1,20 @@ import 'server-only' -import React from 'react' +import * as React from 'react' import { NextResponse } from 'next/server' // import './lib/mixed-lib' export function middleware(request) { - if (React.useState) { + // To avoid webpack ESM exports checking warning + const ReactObject = Object(React) + if (ReactObject.useState) { throw new Error('React.useState should not be defined in server layer') } + + if (request.nextUrl.pathname === '/react-version') { + return Response.json({ + React: Object.keys(ReactObject), + }) + } + return NextResponse.next() } diff --git a/test/e2e/module-layer/module-layer.test.ts b/test/e2e/module-layer/module-layer.test.ts index bf665e1428df8..7d90ea9fef659 100644 --- a/test/e2e/module-layer/module-layer.test.ts +++ b/test/e2e/module-layer/module-layer.test.ts @@ -4,6 +4,10 @@ import { getRedboxSource, hasRedbox, retry } from 'next-test-utils' describe('module layer', () => { const { next, isNextStart, isNextDev, isTurbopack } = nextTestSetup({ files: __dirname, + dependencies: { + react: '19.0.0-rc-915b914b3a-20240515', + 'react-dom': '19.0.0-rc-915b914b3a-20240515', + }, }) function runTests() { @@ -18,8 +22,10 @@ describe('module layer', () => { '/app/route', '/app/route-edge', // pages/api - '/api/hello', - '/api/hello-edge', + '/api/default', + '/api/default-edge', + '/api/server-only', + '/api/server-only-edge', '/api/mixed', ] @@ -30,6 +36,35 @@ describe('module layer', () => { }) } + it('should render installed react-server condition for middleware', async () => { + const json = await next.fetch('/react-version').then((res) => res.json()) + expect(json.React).toContain('version') // basic react-server export + expect(json.React).not.toContain('useEffect') // no client api export + }) + + // This is for backward compatibility, don't change react usage in existing pages/api + it('should contain client react exports for pages api', async () => { + async function verifyReactExports(route, isEdge) { + const json = await next.fetch(route).then((res) => res.json()) + // contain all react-server and default condition exports + expect(json.React).toContain('version') + expect(json.React).toContain('useEffect') + + // contain react-dom-server default condition exports + expect(json.ReactDomServer).toContain('version') + expect(json.ReactDomServer).toContain('renderToString') + expect(json.ReactDomServer).toContain('renderToStaticMarkup') + expect(json.ReactDomServer).toContain( + isEdge ? 'renderToReadableStream' : 'renderToPipeableStream' + ) + } + + await verifyReactExports('/api/default', false) + await verifyReactExports('/api/default-edge', true) + await verifyReactExports('/api/server-only', false) + await verifyReactExports('/api/server-only-edge', true) + }) + if (isNextStart) { it('should log the build info properly', async () => { const cliOutput = next.cliOutput @@ -40,7 +75,8 @@ describe('module layer', () => { ) expect(functionsManifest.functions).toContainKeys([ '/app/route-edge', - '/api/hello-edge', + '/api/default-edge', + '/api/server-only-edge', '/app/client-edge', '/app/server-edge', ]) @@ -52,9 +88,10 @@ describe('module layer', () => { ) expect(middlewareManifest.middleware).toBeTruthy() expect(pagesManifest).toContainKeys([ - '/api/hello-edge', + '/api/default-edge', '/pages-ssr', - '/api/hello', + '/api/default', + '/api/server-only', ]) }) } @@ -81,22 +118,15 @@ describe('module layer', () => { .replace("// import './lib/mixed-lib'", "import './lib/mixed-lib'") ) - const existingCliOutputLength = next.cliOutput.length await retry(async () => { expect(await hasRedbox(browser)).toBe(true) const source = await getRedboxSource(browser) expect(source).toContain( - `'client-only' cannot be imported from a Server Component module. It should only be used from a Client Component.` + isTurbopack + ? `'client-only' cannot be imported from a Server Component module. It should only be used from a Client Component.` + : `You're importing a component that imports client-only. It only works in a Client Component but none of its parents are marked with "use client"` ) }) - - if (!isTurbopack) { - const newCliOutput = next.cliOutput.slice(existingCliOutputLength) - expect(newCliOutput).toContain('./middleware.js') - expect(newCliOutput).toContain( - `'client-only' cannot be imported from a Server Component module. It should only be used from a Client Component` - ) - } }) }) } diff --git a/test/e2e/module-layer/pages/api/default-edge.js b/test/e2e/module-layer/pages/api/default-edge.js new file mode 100644 index 0000000000000..f4621f2500cc5 --- /dev/null +++ b/test/e2e/module-layer/pages/api/default-edge.js @@ -0,0 +1,11 @@ +import * as ReactDomServer from 'react-dom/server' +import * as React from 'react' + +export default async (_req) => { + return Response.json({ + React: Object.keys(Object(React)), + ReactDomServer: Object.keys(Object(ReactDomServer)), + }) +} + +export const runtime = 'edge' diff --git a/test/e2e/module-layer/pages/api/default.js b/test/e2e/module-layer/pages/api/default.js new file mode 100644 index 0000000000000..2b13630aa4ac6 --- /dev/null +++ b/test/e2e/module-layer/pages/api/default.js @@ -0,0 +1,9 @@ +import * as ReactDomServer from 'react-dom/server' +import * as React from 'react' + +export default async (_req, res) => { + return res.json({ + React: Object.keys(Object(React)), + ReactDomServer: Object.keys(Object(ReactDomServer)), + }) +} diff --git a/test/e2e/module-layer/pages/api/hello-edge.js b/test/e2e/module-layer/pages/api/hello-edge.js deleted file mode 100644 index dce0100296d19..0000000000000 --- a/test/e2e/module-layer/pages/api/hello-edge.js +++ /dev/null @@ -1,7 +0,0 @@ -import 'server-only' - -export default function handler() { - return new Response('pages/api/hello-edge.js:') -} - -export const runtime = 'edge' diff --git a/test/e2e/module-layer/pages/api/hello.js b/test/e2e/module-layer/pages/api/hello.js deleted file mode 100644 index d1fe5339d8e98..0000000000000 --- a/test/e2e/module-layer/pages/api/hello.js +++ /dev/null @@ -1,5 +0,0 @@ -import 'server-only' - -export default function handler(req, res) { - return res.send('pages/api/hello.js') -} diff --git a/test/e2e/module-layer/pages/api/server-only-edge.js b/test/e2e/module-layer/pages/api/server-only-edge.js new file mode 100644 index 0000000000000..17e682c015f4f --- /dev/null +++ b/test/e2e/module-layer/pages/api/server-only-edge.js @@ -0,0 +1,12 @@ +import 'server-only' +import * as ReactDomServer from 'react-dom/server' +import * as React from 'react' + +export default async (_req) => { + return Response.json({ + React: Object.keys(Object(React)), + ReactDomServer: Object.keys(Object(ReactDomServer)), + }) +} + +export const runtime = 'edge' diff --git a/test/e2e/module-layer/pages/api/server-only.js b/test/e2e/module-layer/pages/api/server-only.js new file mode 100644 index 0000000000000..7f276bdf9c846 --- /dev/null +++ b/test/e2e/module-layer/pages/api/server-only.js @@ -0,0 +1,10 @@ +import 'server-only' +import * as ReactDomServer from 'react-dom/server' +import * as React from 'react' + +export default async (_req, res) => { + return res.json({ + React: Object.keys(Object(React)), + ReactDomServer: Object.keys(Object(ReactDomServer)), + }) +}