Make concurrent features independent from the global runtime option (#35245)

This PR depends on #35242 and #35243. It allows the global runtime to be unset, as well as enables static optimization for Fizz and RSC pages in the Node.js runtime. Currently for the Edge runtime pages are still always SSR'd.

Closes #31317.

## Bug

- [ ] Related issues linked using `fixes #number`
- [ ] Integration tests added
- [ ] Errors have helpful link attached, see `contributing.md`

## Feature

- [x] Implements an existing feature request or RFC. Make sure the feature request has been accepted for implementation before opening a PR.
- [x] Related issues linked using `fixes #number`
- [x] Integration tests added
- [ ] Documentation added
- [ ] Telemetry added. In case of a feature if it's used or not.
- [ ] Errors have helpful link attached, see `contributing.md`

## Documentation / Examples

- [ ] Make sure the linting passes by running `yarn lint`


Co-authored-by: Jiachi Liu <4800338+huozhi@users.noreply.github.com>
This commit is contained in:
Shu Ding 2022-03-16 13:11:57 +01:00 committed by GitHub
parent 86c1bf6d2b
commit 853442dfc3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
40 changed files with 468 additions and 60 deletions

View file

@ -187,9 +187,6 @@ export async function getPageRuntime(
if (!pageRuntime) {
if (isRuntimeRequired) {
pageRuntime = globalRuntimeFallback
} else {
// @TODO: Remove this branch to fully implement the RFC.
pageRuntime = globalRuntimeFallback
}
}

View file

@ -76,7 +76,11 @@ import {
} from '../telemetry/events'
import { Telemetry } from '../telemetry/storage'
import { CompilerResult, runCompiler } from './compiler'
import { createEntrypoints, createPagesMapping } from './entries'
import {
createEntrypoints,
createPagesMapping,
getPageRuntime,
} from './entries'
import { generateBuildId } from './generate-build-id'
import { isWriteable } from './is-writeable'
import * as Log from './output/log'
@ -153,11 +157,10 @@ export default async function build(
setGlobal('phase', PHASE_PRODUCTION_BUILD)
setGlobal('distDir', distDir)
// Currently, when the runtime option is set (either `nodejs` or `edge`),
// we enable concurrent features (Fizz-related rendering architecture).
const runtime = config.experimental.runtime
// We enable concurrent features (Fizz-related rendering architecture) when
// using React 18 or experimental.
const hasReactRoot = shouldUseReactRoot()
const hasConcurrentFeatures = !!runtime
const hasConcurrentFeatures = hasReactRoot
const hasServerComponents =
hasReactRoot && !!config.experimental.serverComponents
@ -622,6 +625,7 @@ export default async function build(
entrypoints: entrypoints.client,
rewrites,
runWebpackSpan,
hasReactRoot,
}),
getBaseWebpackConfig(dir, {
buildId,
@ -633,6 +637,7 @@ export default async function build(
entrypoints: entrypoints.server,
rewrites,
runWebpackSpan,
hasReactRoot,
}),
hasReactRoot
? getBaseWebpackConfig(dir, {
@ -646,6 +651,7 @@ export default async function build(
entrypoints: entrypoints.edgeServer,
rewrites,
runWebpackSpan,
hasReactRoot,
})
: null,
])
@ -954,10 +960,22 @@ 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) &&
!hasConcurrentFeatures
// We currently don't support staic optimization in the Edge runtime.
pageRuntime !== 'edge'
) {
try {
let isPageStaticSpan =
@ -1483,10 +1501,7 @@ export default async function build(
const combinedPages = [...staticPages, ...ssgPages]
if (
!hasConcurrentFeatures &&
(combinedPages.length > 0 || useStatic404 || useDefaultStatic500)
) {
if (combinedPages.length > 0 || useStatic404 || useDefaultStatic500) {
const staticGenerationSpan = nextBuildSpan.traceChild('static-generation')
await staticGenerationSpan.traceAsyncFn(async () => {
detectConflictingPaths(

View file

@ -48,7 +48,6 @@ 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({
@ -310,6 +309,7 @@ export default async function getBaseWebpackConfig(
rewrites,
isDevFallback = false,
runWebpackSpan,
hasReactRoot,
}: {
buildId: string
config: NextConfigComplete
@ -323,6 +323,7 @@ export default async function getBaseWebpackConfig(
rewrites: CustomRoutes['rewrites']
isDevFallback?: boolean
runWebpackSpan: Span
hasReactRoot: boolean
}
): Promise<webpack.Configuration> {
const { useTypeScript, jsConfig, resolvedBaseUrl } = await loadJsConfig(
@ -335,10 +336,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 is detected
// Make sure `reactRoot` is enabled when React 18 or experimental is detected.
if (hasReactRoot) {
config.experimental.reactRoot = true
}
@ -353,14 +354,14 @@ export default async function getBaseWebpackConfig(
'`experimental.runtime` requires `experimental.reactRoot` to be enabled along with React 18.'
)
}
if (config.experimental.serverComponents && !runtime) {
if (config.experimental.serverComponents && !hasReactRoot) {
throw new Error(
'`experimental.runtime` is required to be set along with `experimental.serverComponents`.'
'`experimental.serverComponents` requires React 18 to be installed.'
)
}
const targetWeb = isEdgeRuntime || !isServer
const hasConcurrentFeatures = !!runtime && hasReactRoot
const hasConcurrentFeatures = hasReactRoot
const hasServerComponents =
hasConcurrentFeatures && !!config.experimental.serverComponents
const disableOptimizedLoading = hasConcurrentFeatures

View file

@ -588,6 +588,7 @@ export default async function exportApp(
nextConfig.experimental.disableOptimizedLoading,
parentSpanId: pageExportSpan.id,
httpAgentOptions: nextConfig.httpAgentOptions,
serverComponents: nextConfig.experimental.serverComponents,
})
for (const validation of result.ampValidations || []) {

View file

@ -59,6 +59,7 @@ interface ExportPageInput {
disableOptimizedLoading: any
parentSpanId: any
httpAgentOptions: NextConfigComplete['httpAgentOptions']
serverComponents?: boolean
}
interface ExportPageResults {
@ -106,6 +107,7 @@ export default async function exportPage({
optimizeCss,
disableOptimizedLoading,
httpAgentOptions,
serverComponents,
}: ExportPageInput): Promise<ExportPageResults> {
setHttpAgentOptions(httpAgentOptions)
const exportPageSpan = trace('export-page-worker', parentSpanId)
@ -260,7 +262,7 @@ export default async function exportPage({
getServerSideProps,
getStaticProps,
pageConfig,
} = await loadComponents(distDir, page, serverless)
} = await loadComponents(distDir, page, serverless, serverComponents)
const ampState = {
ampFirst: pageConfig?.amp === true,
hasQuery: Boolean(query.amp),
@ -321,7 +323,12 @@ export default async function exportPage({
throw new Error(`Failed to render serverless page`)
}
} else {
const components = await loadComponents(distDir, page, serverless)
const components = await loadComponents(
distDir,
page,
serverless,
serverComponents
)
const ampState = {
ampFirst: components.pageConfig?.amp === true,
hasQuery: Boolean(query.amp),

View file

@ -585,11 +585,9 @@ export class Head extends Component<
disableOptimizedLoading,
optimizeCss,
optimizeFonts,
runtime,
hasConcurrentFeatures,
} = this.context
const hasConcurrentFeatures = !!runtime
const disableRuntimeJS = unstable_runtimeJS === false
const disableJsPreload =
unstable_JsPreload === false || !disableOptimizedLoading

View file

@ -154,6 +154,7 @@ export default class HotReloader {
private config: NextConfigComplete
private runtime?: 'nodejs' | 'edge'
private hasServerComponents: boolean
private hasReactRoot: boolean
public clientStats: webpack5.Stats | null
public serverStats: webpack5.Stats | null
private clientError: Error | null = null
@ -197,7 +198,9 @@ export default class HotReloader {
this.config = config
this.runtime = config.experimental.runtime
this.hasServerComponents = !!config.experimental.serverComponents
this.hasReactRoot = shouldUseReactRoot()
this.hasServerComponents =
this.hasReactRoot && !!config.experimental.serverComponents
this.previewProps = previewProps
this.rewrites = rewrites
this.hotReloaderSpan = trace('hot-reloader', undefined, {
@ -340,8 +343,6 @@ export default class HotReloader {
)
)
const hasReactRoot = shouldUseReactRoot()
return webpackConfigSpan
.traceChild('generate-webpack-config')
.traceAsyncFn(() =>
@ -356,6 +357,7 @@ export default class HotReloader {
rewrites: this.rewrites,
entrypoints: entrypoints.client,
runWebpackSpan: this.hotReloaderSpan,
hasReactRoot: this.hasReactRoot,
}),
getBaseWebpackConfig(this.dir, {
dev: true,
@ -366,9 +368,10 @@ export default class HotReloader {
rewrites: this.rewrites,
entrypoints: entrypoints.server,
runWebpackSpan: this.hotReloaderSpan,
hasReactRoot: this.hasReactRoot,
}),
// The edge runtime is only supported with React root.
hasReactRoot
this.hasReactRoot
? getBaseWebpackConfig(this.dir, {
dev: true,
isServer: true,
@ -379,6 +382,7 @@ export default class HotReloader {
rewrites: this.rewrites,
entrypoints: entrypoints.edgeServer,
runWebpackSpan: this.hotReloaderSpan,
hasReactRoot: this.hasReactRoot,
})
: null,
].filter(Boolean) as webpack.Configuration[]
@ -417,6 +421,7 @@ export default class HotReloader {
this.pagesDir
)
).client,
hasReactRoot: this.hasReactRoot,
})
const fallbackCompiler = webpack(fallbackConfig)

View file

@ -6,6 +6,7 @@ import type {
import {
BUILD_MANIFEST,
REACT_LOADABLE_MANIFEST,
MIDDLEWARE_FLIGHT_MANIFEST,
} from '../shared/lib/constants'
import { join } from 'path'
import { requirePage } from './require'
@ -30,6 +31,7 @@ export type LoadComponentsReturnType = {
pageConfig: PageConfig
buildManifest: BuildManifest
reactLoadableManifest: ReactLoadableManifest
serverComponentManifest?: any | null
Document: DocumentType
App: AppType
getStaticProps?: GetStaticProps
@ -61,7 +63,8 @@ export async function loadDefaultErrorComponents(distDir: string) {
export async function loadComponents(
distDir: string,
pathname: string,
serverless: boolean
serverless: boolean,
serverComponents?: boolean
): Promise<LoadComponentsReturnType> {
if (serverless) {
const ComponentMod = await requirePage(pathname, distDir, serverless)
@ -102,9 +105,13 @@ export async function loadComponents(
requirePage(pathname, distDir, serverless),
])
const [buildManifest, reactLoadableManifest] = await Promise.all([
const [buildManifest, reactLoadableManifest, serverComponentManifest] =
await Promise.all([
require(join(distDir, BUILD_MANIFEST)),
require(join(distDir, REACT_LOADABLE_MANIFEST)),
serverComponents
? require(join(distDir, 'server', MIDDLEWARE_FLIGHT_MANIFEST + '.json'))
: null,
])
const Component = interopDefault(ComponentMod)
@ -125,5 +132,6 @@ export async function loadComponents(
getServerSideProps,
getStaticProps,
getStaticPaths,
serverComponentManifest,
}
}

View file

@ -693,7 +693,7 @@ export default class NextNodeServer extends BaseServer {
}
protected getServerComponentManifest() {
if (!this.nextConfig.experimental.runtime) return undefined
if (!this.nextConfig.experimental.serverComponents) return undefined
return require(join(
this.distDir,
'server',

View file

@ -450,12 +450,12 @@ export async function renderToHTML(
supportsDynamicHTML,
images,
reactRoot,
runtime,
runtime: globalRuntime,
ComponentMod,
AppMod,
} = renderOpts
const hasConcurrentFeatures = !!runtime
const hasConcurrentFeatures = reactRoot
let Document = renderOpts.Document
const OriginalComponent = renderOpts.Component
@ -464,7 +464,7 @@ export async function renderToHTML(
const isServerComponent =
!!serverComponentManifest &&
hasConcurrentFeatures &&
ComponentMod.__next_rsc__
!!ComponentMod.__next_rsc__
let Component: React.ComponentType<{}> | ((props: any) => JSX.Element) =
renderOpts.Component
@ -1243,7 +1243,7 @@ export async function renderToHTML(
| typeof Document
| undefined
if (runtime === 'edge' && Document.getInitialProps) {
if (process.browser && Document.getInitialProps) {
// In the Edge runtime, `Document.getInitialProps` isn't supported.
// We throw an error here if it's customized.
if (!builtinDocument) {
@ -1329,7 +1329,8 @@ export async function renderToHTML(
) : (
<Body>
<AppContainerWithIsomorphicFiberStructure>
{renderOpts.serverComponents && AppMod.__next_rsc__ ? (
{isServerComponent && AppMod.__next_rsc__ ? (
// _app.server.js is used.
<Component {...props.pageProps} router={router} />
) : (
<App {...props} Component={Component} router={router} />
@ -1361,7 +1362,6 @@ export async function renderToHTML(
),
generateStaticHTML: true,
})
const flushed = await streamToString(flushEffectStream)
return flushed
}
@ -1489,7 +1489,8 @@ export async function renderToHTML(
optimizeCss: renderOpts.optimizeCss,
optimizeFonts: renderOpts.optimizeFonts,
nextScriptWorkers: renderOpts.nextScriptWorkers,
runtime,
runtime: globalRuntime,
hasConcurrentFeatures,
}
const document = (

View file

@ -38,6 +38,7 @@ export type HtmlProps = {
optimizeFonts?: boolean
nextScriptWorkers?: boolean
runtime?: 'edge' | 'nodejs'
hasConcurrentFeatures?: boolean
}
export const HtmlContext = createContext<HtmlProps>(null as any)

View file

@ -25,19 +25,6 @@ describe('Invalid react 18 webpack config', () => {
)
})
it('should require `experimental.runtime` for server components', async () => {
writeNextConfig({
reactRoot: true,
serverComponents: true,
})
const { stderr } = await nextBuild(appDir, [], { stderr: true })
nextConfig.restore()
expect(stderr).toContain(
'`experimental.runtime` is required to be set along with `experimental.serverComponents`.'
)
})
it('should warn user when not using react 18 and `experimental.reactRoot` is enabled', async () => {
const reactDomPackagePah = join(appDir, 'node_modules/react-dom')
await fs.mkdirp(reactDomPackagePah)

View file

@ -2,6 +2,7 @@ import { Suspense } from 'react'
import dynamic from 'next/dynamic'
const Bar = dynamic(() => import('../../components/bar'), {
ssr: false,
suspense: true,
// Explicitly declare loaded modules.
// For suspense cases, they'll be ignored.
@ -14,7 +15,7 @@ const Bar = dynamic(() => import('../../components/bar'), {
export default function NoPreload() {
return (
<Suspense fallback={'rab'}>
<Suspense fallback={'fallback'}>
<Bar />
</Suspense>
)

View file

@ -31,7 +31,7 @@ export default (context) => {
const nextData = JSON.parse($('#__NEXT_DATA__').text())
const content = $('#__next').text()
// <Bar> is suspended
expect(content).toBe('rab')
expect(content).toBe('fallback')
expect(nextData.dynamicIds).toBeUndefined()
})

View file

@ -8,15 +8,16 @@ export default (context, render) => {
return cheerio.load(html)
}
it('should render fallback on server side if suspense without preload', async () => {
it('should render fallback on server side if suspense without ssr', async () => {
const $ = await get$('/suspense/no-preload')
const nextData = JSON.parse($('#__NEXT_DATA__').text())
const content = $('#__next').text()
expect(content).toBe('rab')
expect(content).toBe('fallback')
expect(nextData.dynamicIds).toBeUndefined()
})
it('should render fallback on server side if suspended on server with preload', async () => {
// Testing the same thing as above.
it.skip('should render import fallback on server side if suspended without ssr', async () => {
const $ = await get$('/suspense/thrown')
const html = $('body').html()
expect(html).toContain('loading')

View file

@ -5,3 +5,7 @@ export default function MyError() {
throw new Error('oops')
}
}
export const config = {
runtime: 'edge',
}

View file

@ -18,3 +18,7 @@ export default function page() {
</>
)
}
export const config = {
runtime: 'edge',
}

View file

@ -7,3 +7,7 @@ const Page = () => {
}
export default Page
export const config = {
runtime: 'edge',
}

View file

@ -15,3 +15,7 @@ export default function LinkPage({ router }) {
</>
)
}
export const config = {
runtime: 'edge',
}

View file

@ -40,3 +40,7 @@ export default function () {
</>
)
}
export const config = {
runtime: 'edge',
}

View file

@ -1,3 +1,7 @@
export default function Pid({ router }) {
return <div>{`query: ${router.query.dynamic}`}</div>
}
export const config = {
runtime: 'edge',
}

View file

@ -21,3 +21,7 @@ export default function Page() {
</Suspense>
)
}
export const config = {
runtime: 'edge',
}

View file

@ -21,3 +21,7 @@ export default function Page() {
</Suspense>
)
}
export const config = {
runtime: 'edge',
}

View file

@ -0,0 +1,9 @@
const withReact18 = require('../../react-18/test/with-react-18')
module.exports = withReact18({
reactStrictMode: true,
experimental: {
serverComponents: true,
// runtime: 'edge',
},
})

View file

@ -0,0 +1,9 @@
{
"private": true,
"scripts": {
"lnext": "node -r ../../react-18/test/require-hook.js ../../../../packages/next/dist/bin/next",
"dev": "yarn lnext dev",
"build": "yarn lnext build",
"start": "yarn lnext start"
}
}

View file

@ -0,0 +1,9 @@
{
"/_app": "pages/_app.js",
"/_error": "pages/_error.js",
"/edge-rsc": "pages/edge-rsc.js",
"/static": "pages/static.js",
"/node-rsc": "pages/node-rsc.js",
"/node": "pages/node.js",
"/edge": "pages/edge.js"
}

View file

@ -0,0 +1,18 @@
import getRuntime from '../utils/runtime'
import getTime from '../utils/time'
export default function Page() {
return (
<div>
This is a SSR RSC page.
<br />
{'Runtime: ' + getRuntime()}
<br />
{'Time: ' + getTime()}
</div>
)
}
export const config = {
runtime: 'edge',
}

View file

@ -0,0 +1,18 @@
import getRuntime from '../utils/runtime'
import getTime from '../utils/time'
export default function Page() {
return (
<div>
This is a SSR page.
<br />
{'Runtime: ' + getRuntime()}
<br />
{'Time: ' + getTime()}
</div>
)
}
export const config = {
runtime: 'edge',
}

View file

@ -0,0 +1,26 @@
import getRuntime from '../utils/runtime'
import getTime from '../utils/time'
export default function Page({ type }) {
return (
<div>
This is a {type} RSC page.
<br />
{'Runtime: ' + getRuntime()}
<br />
{'Time: ' + getTime()}
</div>
)
}
export function getStaticProps() {
return {
props: {
type: 'SSG',
},
}
}
export const config = {
runtime: 'nodejs',
}

View file

@ -0,0 +1,26 @@
import getRuntime from '../utils/runtime'
import getTime from '../utils/time'
export default function Page({ type }) {
return (
<div>
This is a {type} RSC page.
<br />
{'Runtime: ' + getRuntime()}
<br />
{'Time: ' + getTime()}
</div>
)
}
export function getServerSideProps() {
return {
props: {
type: 'SSR',
},
}
}
export const config = {
runtime: 'nodejs',
}

View file

@ -0,0 +1,18 @@
import getRuntime from '../utils/runtime'
import getTime from '../utils/time'
export default function Page() {
return (
<div>
This is a static RSC page.
<br />
{'Runtime: ' + getRuntime()}
<br />
{'Time: ' + getTime()}
</div>
)
}
export const config = {
runtime: 'nodejs',
}

View file

@ -0,0 +1,26 @@
import getRuntime from '../utils/runtime'
import getTime from '../utils/time'
export default function Page({ type }) {
return (
<div>
This is a {type} page.
<br />
{'Runtime: ' + getRuntime()}
<br />
{'Time: ' + getTime()}
</div>
)
}
export function getStaticProps() {
return {
props: {
type: 'SSG',
},
}
}
export const config = {
runtime: 'nodejs',
}

View file

@ -0,0 +1,26 @@
import getRuntime from '../utils/runtime'
import getTime from '../utils/time'
export default function Page({ type }) {
return (
<div>
This is a {type} page.
<br />
{'Runtime: ' + getRuntime()}
<br />
{'Time: ' + getTime()}
</div>
)
}
export function getServerSideProps() {
return {
props: {
type: 'SSR',
},
}
}
export const config = {
runtime: 'nodejs',
}

View file

@ -0,0 +1,18 @@
import getRuntime from '../utils/runtime'
import getTime from '../utils/time'
export default function Page() {
return (
<div>
This is a static page.
<br />
{'Runtime: ' + getRuntime()}
<br />
{'Time: ' + getTime()}
</div>
)
}
export const config = {
runtime: 'nodejs',
}

View file

@ -0,0 +1,14 @@
import getRuntime from '../utils/runtime'
import getTime from '../utils/time'
export default function Page() {
return (
<div>
This is a static page.
<br />
{'Runtime: ' + getRuntime()}
<br />
{'Time: ' + getTime()}
</div>
)
}

View file

@ -0,0 +1,3 @@
export default function getRuntime() {
return process.version ? `Node.js ${process.version}` : 'Edge/Browser'
}

View file

@ -0,0 +1,3 @@
export default function getTime() {
return Date.now()
}

View file

@ -0,0 +1,127 @@
/* eslint-env jest */
import { join } from 'path'
import {
// File,
nextBuild as _nextBuild,
nextStart as _nextStart,
} from 'next-test-utils'
import { findPort, killApp, renderViaHTTP } from 'next-test-utils'
const nodeArgs = ['-r', join(__dirname, '../../react-18/test/require-hook.js')]
const appDir = join(__dirname, '../switchable-runtime')
// const nextConfig = new File(join(appDir, 'next.config.js'))
async function nextBuild(dir, options) {
return await _nextBuild(dir, [], {
...options,
stdout: true,
stderr: true,
nodeArgs,
})
}
async function nextStart(dir, port) {
return await _nextStart(dir, port, {
stdout: true,
stderr: true,
nodeArgs,
})
}
async function testRoute(appPort, url, { isStatic, isEdge }) {
const html1 = await renderViaHTTP(appPort, url)
const renderedAt1 = +html1.match(/Time: (\d+)/)[1]
expect(html1).toContain(`Runtime: ${isEdge ? 'Edge' : 'Node.js'}`)
const html2 = await renderViaHTTP(appPort, url)
const renderedAt2 = +html2.match(/Time: (\d+)/)[1]
expect(html2).toContain(`Runtime: ${isEdge ? 'Edge' : 'Node.js'}`)
if (isStatic) {
// Should not be re-rendered, some timestamp should be returned.
expect(renderedAt1).toBe(renderedAt2)
} else {
// Should be re-rendered.
expect(renderedAt1).toBeLessThan(renderedAt2)
}
}
describe('Without global runtime configuration', () => {
const context = { appDir }
beforeAll(async () => {
context.appPort = await findPort()
const { stderr } = await nextBuild(context.appDir)
context.stderr = stderr
context.server = await nextStart(context.appDir, context.appPort)
})
afterAll(async () => {
await killApp(context.server)
})
it('should build /static as a static page with the nodejs runtime', async () => {
await testRoute(context.appPort, '/static', {
isStatic: true,
isEdge: false,
})
})
it('should build /node as a static page with the nodejs runtime', async () => {
await testRoute(context.appPort, '/node', {
isStatic: true,
isEdge: false,
})
})
it('should build /node-ssr as a dynamic page with the nodejs runtime', async () => {
await testRoute(context.appPort, '/node-ssr', {
isStatic: false,
isEdge: false,
})
})
it('should build /node-ssg as a static page with the nodejs runtime', async () => {
await testRoute(context.appPort, '/node-ssg', {
isStatic: true,
isEdge: false,
})
})
it('should build /node-rsc as a static page with the nodejs runtime', async () => {
await testRoute(context.appPort, '/node-rsc', {
isStatic: true,
isEdge: false,
})
})
it('should build /node-rsc-ssr as a dynamic page with the nodejs runtime', async () => {
await testRoute(context.appPort, '/node-rsc-ssr', {
isStatic: false,
isEdge: false,
})
})
it('should build /node-rsc-ssg as a static page with the nodejs runtime', async () => {
await testRoute(context.appPort, '/node-rsc-ssg', {
isStatic: true,
isEdge: false,
})
})
it('should build /edge as a dynamic page with the edge runtime', async () => {
await testRoute(context.appPort, '/edge', {
isStatic: false,
isEdge: true,
})
})
it('should build /edge-rsc as a dynamic page with the edge runtime', async () => {
await testRoute(context.appPort, '/edge-rsc', {
isStatic: false,
isEdge: true,
})
})
})

View file

@ -3,7 +3,6 @@ const withReact18 = require('../../react-18/test/with-react-18')
module.exports = withReact18({
experimental: {
reactRoot: true,
runtime: 'edge',
serverComponents: true,
},
})

View file

@ -8,3 +8,7 @@ export default function Index() {
console.log(EOF)
return 'Access Node.js native module dns'
}
export const config = {
runtime: 'edge',
}