From e8a8220d49434021f2fb9975a6ec7081043b97b4 Mon Sep 17 00:00:00 2001 From: JJ Kasper Date: Thu, 5 May 2022 06:15:32 -0500 Subject: [PATCH 01/15] Tweak routing tests (#36667) Co-authored-by: Tim Neutkens --- packages/next/build/entries.ts | 58 +++------ packages/next/build/index.ts | 42 +++---- packages/next/build/webpack-config.ts | 29 ++--- .../build/webpack/loaders/next-view-loader.ts | 17 +++ .../webpack/plugins/pages-manifest-plugin.ts | 6 +- packages/next/export/index.ts | 2 +- packages/next/export/worker.ts | 8 +- packages/next/lib/constants.ts | 3 +- packages/next/lib/find-pages-dir.ts | 14 +-- .../{pages/root.tsx => lib/views-layout.tsx} | 7 +- packages/next/server/base-server.ts | 119 +++++++----------- packages/next/server/config-shared.ts | 4 +- packages/next/server/dev/hot-reloader.ts | 12 +- packages/next/server/dev/next-dev-server.ts | 46 +++---- .../server/dev/on-demand-entry-handler.ts | 41 +++--- .../next/server/get-route-from-entrypoint.ts | 2 +- packages/next/server/lib/find-page-file.ts | 7 +- packages/next/server/load-components.ts | 12 +- packages/next/server/next-server.ts | 22 ++-- packages/next/server/require.ts | 4 +- .../{root-render.tsx => view-render.tsx} | 8 +- packages/next/server/web-server.ts | 2 +- packages/next/shared/lib/constants.ts | 2 +- .../shared/lib/page-path/get-page-paths.ts | 16 +-- .../shared/lib/router/utils/root-paths.ts | 14 --- .../shared/lib/router/utils/view-paths.ts | 17 +++ packages/next/taskfile.js | 14 +-- .../app/root/conditional/[slug].server.js | 27 ---- .../app/root/conditional/[slug]@team/index.js | 7 -- .../root/conditional/[slug]@team/members.js | 7 -- .../app/root/conditional/[slug]@user/index.js | 7 -- .../app/root/conditional/[slug]@user/teams.js | 7 -- .../app/next.config.js | 2 +- .../app/pages/blog/[slug].js | 0 .../app/pages/index.js | 0 .../app/public/hello.txt | 0 .../dashboard/changelog/page.server.js} | 0 .../dashboard/hello/page.server.js} | 0 .../client-component-route/page.client.js} | 0 .../views/client-nested/index/page.server.js} | 0 .../app/views/client-nested/layout.client.js} | 0 .../deployments/[id]/page.server.js} | 0 .../deployments/info/page.server.js} | 0 .../dashboard/deployments/layout.server.js} | 0 .../app/views/dashboard/index/page.server.js} | 0 .../integrations/index/page.server.js} | 0 .../app/views/dashboard/layout.server.js} | 0 .../app/views/layout.server.js} | 0 .../views/partial-match-[id]/page.server.js} | 0 .../app/views/shared-component-route/page.js} | 0 .../should-not-serve-client/page.client.js} | 0 .../should-not-serve-server/page.server.js} | 0 .../e2e/{root-dir => views-dir}/index.test.ts | 62 ++------- 53 files changed, 228 insertions(+), 419 deletions(-) create mode 100644 packages/next/build/webpack/loaders/next-view-loader.ts rename packages/next/{pages/root.tsx => lib/views-layout.tsx} (65%) rename packages/next/server/{root-render.tsx => view-render.tsx} (98%) delete mode 100644 packages/next/shared/lib/router/utils/root-paths.ts create mode 100644 packages/next/shared/lib/router/utils/view-paths.ts delete mode 100644 test/e2e/root-dir/app/root/conditional/[slug].server.js delete mode 100644 test/e2e/root-dir/app/root/conditional/[slug]@team/index.js delete mode 100644 test/e2e/root-dir/app/root/conditional/[slug]@team/members.js delete mode 100644 test/e2e/root-dir/app/root/conditional/[slug]@user/index.js delete mode 100644 test/e2e/root-dir/app/root/conditional/[slug]@user/teams.js rename test/e2e/{root-dir => views-dir}/app/next.config.js (85%) rename test/e2e/{root-dir => views-dir}/app/pages/blog/[slug].js (100%) rename test/e2e/{root-dir => views-dir}/app/pages/index.js (100%) rename test/e2e/{root-dir => views-dir}/app/public/hello.txt (100%) rename test/e2e/{root-dir/app/root/dashboard+changelog.server.js => views-dir/app/views/(rootonly)/dashboard/changelog/page.server.js} (100%) rename test/e2e/{root-dir/app/root/dashboard+rootonly/hello.server.js => views-dir/app/views/(rootonly)/dashboard/hello/page.server.js} (100%) rename test/e2e/{root-dir/app/root/client-component-route.client.js => views-dir/app/views/client-component-route/page.client.js} (100%) rename test/e2e/{root-dir/app/root/client-nested/index.server.js => views-dir/app/views/client-nested/index/page.server.js} (100%) rename test/e2e/{root-dir/app/root/client-nested.client.js => views-dir/app/views/client-nested/layout.client.js} (100%) rename test/e2e/{root-dir/app/root/dashboard/deployments/[id].server.js => views-dir/app/views/dashboard/deployments/[id]/page.server.js} (100%) rename test/e2e/{root-dir/app/root/dashboard/deployments/info.server.js => views-dir/app/views/dashboard/deployments/info/page.server.js} (100%) rename test/e2e/{root-dir/app/root/dashboard/deployments.server.js => views-dir/app/views/dashboard/deployments/layout.server.js} (100%) rename test/e2e/{root-dir/app/root/dashboard/index.server.js => views-dir/app/views/dashboard/index/page.server.js} (100%) rename test/e2e/{root-dir/app/root/dashboard/integrations/index.server.js => views-dir/app/views/dashboard/integrations/index/page.server.js} (100%) rename test/e2e/{root-dir/app/root/dashboard.server.js => views-dir/app/views/dashboard/layout.server.js} (100%) rename test/e2e/{root-dir/app/root.server.js => views-dir/app/views/layout.server.js} (100%) rename test/e2e/{root-dir/app/root/partial-match-[id].server.js => views-dir/app/views/partial-match-[id]/page.server.js} (100%) rename test/e2e/{root-dir/app/root/shared-component-route.js => views-dir/app/views/shared-component-route/page.js} (100%) rename test/e2e/{root-dir/app/root/should-not-serve-client.client.js => views-dir/app/views/should-not-serve-client/page.client.js} (100%) rename test/e2e/{root-dir/app/root/should-not-serve-server.server.js => views-dir/app/views/should-not-serve-server/page.server.js} (100%) rename test/e2e/{root-dir => views-dir}/index.test.ts (79%) diff --git a/packages/next/build/entries.ts b/packages/next/build/entries.ts index 440cd60c3dfdc..1e0eacd258905 100644 --- a/packages/next/build/entries.ts +++ b/packages/next/build/entries.ts @@ -14,8 +14,7 @@ import { API_ROUTE, DOT_NEXT_ALIAS, PAGES_DIR_ALIAS, - ROOT_ALIAS, - ROOT_DIR_ALIAS, + VIEWS_DIR_ALIAS, } from '../lib/constants' import { CLIENT_STATIC_FILES_RUNTIME_AMP, @@ -40,11 +39,7 @@ type ObjectValue = T extends { [key: string]: infer V } ? V : never * special case because it is the only page where we want to preserve the RSC * server extension. */ -export function getPageFromPath( - pagePath: string, - pageExtensions: string[], - isRoot?: boolean -) { +export function getPageFromPath(pagePath: string, pageExtensions: string[]) { const extensions = pagePath.includes('/_app.server.') ? withoutRSCExtensions(pageExtensions) : pageExtensions @@ -53,9 +48,7 @@ export function getPageFromPath( pagePath.replace(new RegExp(`\\.+(${extensions.join('|')})$`), '') ) - if (!isRoot) { - page = page.replace(/\/index$/, '') - } + page = page.replace(/\/index$/, '') return page === '' ? '/' : page } @@ -63,18 +56,18 @@ export function getPageFromPath( export function createPagesMapping({ hasServerComponents, isDev, - isRoot, + isViews, pageExtensions, pagePaths, }: { hasServerComponents: boolean isDev: boolean - isRoot?: boolean + isViews?: boolean pageExtensions: string[] pagePaths: string[] }): { [page: string]: string } { const previousPages: { [key: string]: string } = {} - const pathAlias = isRoot ? ROOT_DIR_ALIAS : PAGES_DIR_ALIAS + const pathAlias = isViews ? VIEWS_DIR_ALIAS : PAGES_DIR_ALIAS const pages = pagePaths.reduce<{ [key: string]: string }>( (result, pagePath) => { // Do not process .d.ts files inside the `pages` folder @@ -82,7 +75,7 @@ export function createPagesMapping({ return result } - const pageKey = getPageFromPath(pagePath, pageExtensions, isRoot) + const pageKey = getPageFromPath(pagePath, pageExtensions) // Assume that if there's a Client Component, that there is // a matching Server Component that will map to the page. @@ -103,11 +96,7 @@ export function createPagesMapping({ previousPages[pageKey] = pagePath } - if (pageKey === 'root') { - result['root'] = normalizePathSep(join(ROOT_ALIAS, pagePath)) - } else { - result[pageKey] = normalizePathSep(join(pathAlias, pagePath)) - } + result[pageKey] = normalizePathSep(join(pathAlias, pagePath)) return result }, {} @@ -117,12 +106,7 @@ export function createPagesMapping({ // the correct source file so that HMR can work properly when a file is // added or removed. - if (isRoot) { - if (isDev) { - pages['root'] = `${ROOT_ALIAS}/root` - } else { - pages['root'] = pages['root'] || 'next/dist/pages/root' - } + if (isViews) { return pages } @@ -259,8 +243,8 @@ interface CreateEntrypointsParams { pagesDir: string previewMode: __ApiPreviewProps target: 'server' | 'serverless' | 'experimental-serverless-trace' - rootDir?: string - rootPaths?: Record + viewsDir?: string + viewPaths?: Record } export function getEdgeServerEntry(opts: { @@ -365,18 +349,18 @@ export function getClientEntry(opts: { } export async function createEntrypoints(params: CreateEntrypointsParams) { - const { config, pages, pagesDir, isDev, target, rootDir, rootPaths } = params + const { config, pages, pagesDir, isDev, target, viewsDir, viewPaths } = params const edgeServer: webpack5.EntryObject = {} const server: webpack5.EntryObject = {} const client: webpack5.EntryObject = {} const getEntryHandler = - (mappings: Record, isRoot: boolean) => + (mappings: Record, isViews: boolean) => async (page: string) => { const bundleFile = normalizePagePath(page) const clientBundlePath = posix.join('pages', bundleFile) const serverBundlePath = posix.join( - isRoot ? (bundleFile === '/root' ? './' : 'root') : 'pages', + isViews ? 'views' : 'pages', bundleFile ) @@ -387,12 +371,8 @@ export async function createEntrypoints(params: CreateEntrypointsParams) { return absolutePagePath.replace(PAGES_DIR_ALIAS, pagesDir) } - if (absolutePagePath.startsWith(ROOT_DIR_ALIAS) && rootDir) { - return absolutePagePath.replace(ROOT_DIR_ALIAS, rootDir) - } - - if (absolutePagePath.startsWith(ROOT_ALIAS) && rootDir) { - return absolutePagePath.replace(ROOT_ALIAS, join(rootDir, '..')) + if (absolutePagePath.startsWith(VIEWS_DIR_ALIAS) && viewsDir) { + return absolutePagePath.replace(VIEWS_DIR_ALIAS, viewsDir) } return require.resolve(absolutePagePath) @@ -432,9 +412,9 @@ export async function createEntrypoints(params: CreateEntrypointsParams) { }) } - if (rootDir && rootPaths) { - const entryHandler = getEntryHandler(rootPaths, true) - await Promise.all(Object.keys(rootPaths).map(entryHandler)) + if (viewsDir && viewPaths) { + const entryHandler = getEntryHandler(viewPaths, true) + await Promise.all(Object.keys(viewPaths).map(entryHandler)) } await Promise.all(Object.keys(pages).map(getEntryHandler(pages, false))) diff --git a/packages/next/build/index.ts b/packages/next/build/index.ts index e48f42b2f77d3..119a686c0be41 100644 --- a/packages/next/build/index.ts +++ b/packages/next/build/index.ts @@ -114,7 +114,6 @@ import { MiddlewareManifest } from './webpack/plugins/middleware-plugin' import { recursiveCopy } from '../lib/recursive-copy' import { recursiveReadDir } from '../lib/recursive-readdir' import { lockfilePatchPromise, teardownTraceSubscriber } from './swc' -import { findPageFile } from '../server/lib/find-page-file' export type SsgRoute = { initialRevalidateSeconds: number | false @@ -207,9 +206,9 @@ export default async function build( setGlobal('telemetry', telemetry) const publicDir = path.join(dir, 'public') - const { pages: pagesDir, root: rootDir } = findPagesDir( + const { pages: pagesDir, views: viewsDir } = findPagesDir( dir, - config.experimental.rootDir + config.experimental.viewsDir ) const hasPublicDir = await fileExists(publicDir) @@ -245,7 +244,7 @@ export default async function build( .traceAsyncFn(() => verifyTypeScriptSetup( dir, - [pagesDir, rootDir].filter(Boolean) as string[], + [pagesDir, viewsDir].filter(Boolean) as string[], !ignoreTypeScriptErrors, config, cacheDir @@ -311,24 +310,17 @@ export default async function build( ) ) - let rootPaths: string[] | undefined + let viewPaths: string[] | undefined - if (rootDir) { - rootPaths = await nextBuildSpan - .traceChild('collect-root-paths') + if (viewsDir) { + viewPaths = await nextBuildSpan + .traceChild('collect-view-paths') .traceAsyncFn(() => recursiveReadDir( - rootDir, + viewsDir, new RegExp(`\\.(?:${config.pageExtensions.join('|')})$`) ) ) - - const rootFile = await findPageFile( - path.join(rootDir, '..'), - 'root', - config.pageExtensions - ) - if (rootFile) rootPaths.push(rootFile) } // needed for static exporting since we want to replace with HTML // files @@ -353,17 +345,17 @@ export default async function build( }) ) - let mappedRootPaths: ReturnType | undefined + let mappedViewPaths: ReturnType | undefined - if (rootPaths && rootDir) { - mappedRootPaths = nextBuildSpan - .traceChild('create-root-mapping') + if (viewPaths && viewsDir) { + mappedViewPaths = nextBuildSpan + .traceChild('create-views-mapping') .traceFn(() => createPagesMapping({ - pagePaths: rootPaths!, + pagePaths: viewPaths!, hasServerComponents, isDev: false, - isRoot: true, + isViews: true, pageExtensions: config.pageExtensions, }) ) @@ -381,8 +373,8 @@ export default async function build( pagesDir, previewMode: previewProps, target, - rootDir, - rootPaths: mappedRootPaths, + viewsDir, + viewPaths: mappedViewPaths, }) ) @@ -688,7 +680,7 @@ export default async function build( rewrites, runWebpackSpan, target, - rootDir, + viewsDir, } const configs = await runWebpackSpan diff --git a/packages/next/build/webpack-config.ts b/packages/next/build/webpack-config.ts index 346ec8fb31a00..724f96aa0ffab 100644 --- a/packages/next/build/webpack-config.ts +++ b/packages/next/build/webpack-config.ts @@ -10,8 +10,7 @@ import { NEXT_PROJECT_ROOT, NEXT_PROJECT_ROOT_DIST_CLIENT, PAGES_DIR_ALIAS, - ROOT_ALIAS, - ROOT_DIR_ALIAS, + VIEWS_DIR_ALIAS, } from '../lib/constants' import { fileExists } from '../lib/file-exists' import { CustomRoutes } from '../lib/load-custom-routes.js' @@ -315,7 +314,7 @@ export default async function getBaseWebpackConfig( rewrites, runWebpackSpan, target = 'server', - rootDir, + viewsDir, }: { buildId: string config: NextConfigComplete @@ -329,7 +328,7 @@ export default async function getBaseWebpackConfig( rewrites: CustomRoutes['rewrites'] runWebpackSpan: Span target?: string - rootDir?: string + viewsDir?: string } ): Promise { const isClient = compilerType === 'client' @@ -542,7 +541,7 @@ export default async function getBaseWebpackConfig( ) ) .replace(/\\/g, '/'), - ...(config.experimental.rootDir + ...(config.experimental.viewsDir ? { [CLIENT_STATIC_FILES_RUNTIME_MAIN_ROOT]: `./` + @@ -607,16 +606,6 @@ export default async function getBaseWebpackConfig( }, [] as string[]), `next/dist/pages/_document.js`, ] - - if (config.experimental.rootDir && rootDir) { - customRootAliases[`${ROOT_ALIAS}/root`] = [ - ...config.pageExtensions.reduce((prev, ext) => { - prev.push(path.join(rootDir, `root.${ext}`)) - return prev - }, [] as string[]), - 'next/dist/pages/root.js', - ] - } } const resolveConfig = { @@ -651,10 +640,9 @@ export default async function getBaseWebpackConfig( ...customRootAliases, [PAGES_DIR_ALIAS]: pagesDir, - ...(rootDir + ...(viewsDir ? { - [ROOT_DIR_ALIAS]: rootDir, - [ROOT_ALIAS]: path.join(rootDir, '..'), + [VIEWS_DIR_ALIAS]: viewsDir, } : {}), [DOT_NEXT_ALIAS]: distDir, @@ -1190,6 +1178,7 @@ export default async function getBaseWebpackConfig( 'next-middleware-loader', 'next-middleware-ssr-loader', 'next-middleware-wasm-loader', + 'next-view-loader', ].reduce((alias, loader) => { // using multiple aliases to replace `resolveLoader.modules` alias[loader] = path.join(__dirname, 'webpack', 'loaders', loader) @@ -1560,7 +1549,7 @@ export default async function getBaseWebpackConfig( serverless: isLikeServerless, dev, isEdgeRuntime: isEdgeServer, - rootEnabled: !!config.experimental.rootDir, + rootEnabled: !!config.experimental.viewsDir, }), // MiddlewarePlugin should be after DefinePlugin so NEXT_PUBLIC_* // replacement is done before its process.env.* handling @@ -1571,7 +1560,7 @@ export default async function getBaseWebpackConfig( rewrites, isDevFallback, exportRuntime: hasConcurrentFeatures, - rootEnabled: !!config.experimental.rootDir, + rootEnabled: !!config.experimental.viewsDir, }), new ProfilingPlugin({ runWebpackSpan }), config.optimizeFonts && diff --git a/packages/next/build/webpack/loaders/next-view-loader.ts b/packages/next/build/webpack/loaders/next-view-loader.ts new file mode 100644 index 0000000000000..af12ebee00709 --- /dev/null +++ b/packages/next/build/webpack/loaders/next-view-loader.ts @@ -0,0 +1,17 @@ +import type webpack from 'webpack5' + +const nextViewLoader: webpack.LoaderDefinitionFunction<{ + components: string[] +}> = function nextViewLoader() { + const loaderOptions = this.getOptions() || {} + + return ` + export const components = { + ${loaderOptions.components + .map((component) => `'${component}': () => import('${component}')`) + .join(',\n')} + } + ` +} + +export default nextViewLoader diff --git a/packages/next/build/webpack/plugins/pages-manifest-plugin.ts b/packages/next/build/webpack/plugins/pages-manifest-plugin.ts index c68f7e944ad43..4cb3e3640511c 100644 --- a/packages/next/build/webpack/plugins/pages-manifest-plugin.ts +++ b/packages/next/build/webpack/plugins/pages-manifest-plugin.ts @@ -1,7 +1,7 @@ import { webpack, sources } from 'next/dist/compiled/webpack/webpack' import { PAGES_MANIFEST, - ROOT_PATHS_MANIFEST, + VIEW_PATHS_MANIFEST, } from '../../../shared/lib/constants' import getRouteFromEntrypoint from '../../../server/get-route-from-entrypoint' import { normalizePathSep } from '../../../shared/lib/page-path/normalize-path-sep' @@ -74,7 +74,7 @@ export default class PagesManifestPlugin implements webpack.Plugin { } file = normalizePathSep(file) - if (entrypoint.name.startsWith('root/')) { + if (entrypoint.name.startsWith('views/')) { rootPaths[pagePath] = file } else { pages[pagePath] = file @@ -106,7 +106,7 @@ export default class PagesManifestPlugin implements webpack.Plugin { if (this.rootEnabled) { assets[ - `${!this.dev && !this.isEdgeRuntime ? '../' : ''}` + ROOT_PATHS_MANIFEST + `${!this.dev && !this.isEdgeRuntime ? '../' : ''}` + VIEW_PATHS_MANIFEST ] = new sources.RawSource( JSON.stringify( { diff --git a/packages/next/export/index.ts b/packages/next/export/index.ts index c6c8e2fef7b30..366a00e4e1ee5 100644 --- a/packages/next/export/index.ts +++ b/packages/next/export/index.ts @@ -581,7 +581,7 @@ export default async function exportApp( outDir, pagesDataDir, renderOpts, - rootDir: nextConfig.experimental.rootDir, + viewsDir: nextConfig.experimental.viewsDir, serverRuntimeConfig, subFolders, buildExport: options.buildExport, diff --git a/packages/next/export/worker.ts b/packages/next/export/worker.ts index 8934c0d441b42..e8db5c26e4671 100644 --- a/packages/next/export/worker.ts +++ b/packages/next/export/worker.ts @@ -60,7 +60,7 @@ interface ExportPageInput { parentSpanId: any httpAgentOptions: NextConfigComplete['httpAgentOptions'] serverComponents?: boolean - rootDir?: boolean + viewsDir?: boolean } interface ExportPageResults { @@ -85,7 +85,7 @@ interface RenderOpts { locale?: string defaultLocale?: string trailingSlash?: boolean - rootDir?: boolean + viewsDir?: boolean } type ComponentModule = ComponentType<{}> & { @@ -99,7 +99,7 @@ export default async function exportPage({ pathMap, distDir, outDir, - rootDir, + viewsDir, pagesDataDir, renderOpts, buildExport, @@ -270,7 +270,7 @@ export default async function exportPage({ page, serverless, serverComponents, - rootDir + viewsDir ) const ampState = { ampFirst: pageConfig?.amp === true, diff --git a/packages/next/lib/constants.ts b/packages/next/lib/constants.ts index ea8e0690e1177..354b625817043 100644 --- a/packages/next/lib/constants.ts +++ b/packages/next/lib/constants.ts @@ -25,8 +25,7 @@ export const MIDDLEWARE_ROUTE = /_middleware$/ // we have to use a private alias export const PAGES_DIR_ALIAS = 'private-next-pages' export const DOT_NEXT_ALIAS = 'private-dot-next' -export const ROOT_DIR_ALIAS = 'private-next-root-dir' -export const ROOT_ALIAS = 'private-next-root' +export const VIEWS_DIR_ALIAS = 'private-next-views-dir' export const PUBLIC_DIR_MIDDLEWARE_CONFLICT = `You can not have a '_next' folder inside of your public folder. This conflicts with the internal '/_next' route. https://nextjs.org/docs/messages/public-next-folder-conflict` diff --git a/packages/next/lib/find-pages-dir.ts b/packages/next/lib/find-pages-dir.ts index 862849fd00dec..26a6799687ea9 100644 --- a/packages/next/lib/find-pages-dir.ts +++ b/packages/next/lib/find-pages-dir.ts @@ -10,7 +10,7 @@ export const existsSync = (f: string): boolean => { } } -function findDir(dir: string, name: 'pages' | 'root'): string | null { +function findDir(dir: string, name: 'pages' | 'views'): string | null { // prioritize ./${name} over ./src/${name} let curDir = path.join(dir, name) if (existsSync(curDir)) return curDir @@ -23,13 +23,13 @@ function findDir(dir: string, name: 'pages' | 'root'): string | null { export function findPagesDir( dir: string, - root?: boolean -): { pages: string; root?: string } { + views?: boolean +): { pages: string; views?: string } { const pagesDir = findDir(dir, 'pages') - let rootDir: undefined | string + let viewsDir: undefined | string - if (root) { - rootDir = findDir(dir, 'root') || undefined + if (views) { + viewsDir = findDir(dir, 'views') || undefined } // TODO: allow "root" dir without pages dir @@ -41,6 +41,6 @@ export function findPagesDir( return { pages: pagesDir, - root: rootDir, + views: viewsDir, } } diff --git a/packages/next/pages/root.tsx b/packages/next/lib/views-layout.tsx similarity index 65% rename from packages/next/pages/root.tsx rename to packages/next/lib/views-layout.tsx index 5f972e688ce63..bbbbecd42693d 100644 --- a/packages/next/pages/root.tsx +++ b/packages/next/lib/views-layout.tsx @@ -1,11 +1,14 @@ import React from 'react' -export type RootProps = { +export type LayoutProps = { headChildren: any bodyChildren: any } -export default function Root({ headChildren, bodyChildren }: RootProps) { +export default function ViewsLayout({ + headChildren, + bodyChildren, +}: LayoutProps) { return ( diff --git a/packages/next/server/base-server.ts b/packages/next/server/base-server.ts index 73a8413f94102..d80fd6fde3861 100644 --- a/packages/next/server/base-server.ts +++ b/packages/next/server/base-server.ts @@ -70,7 +70,7 @@ import { createHeaderRoute, createRedirectRoute } from './server-route-utils' import { PrerenderManifest } from '../build' import { ImageConfigComplete } from '../shared/lib/image-config' import { replaceBasePath } from './router-utils' -import { normalizeRootPath } from '../shared/lib/router/utils/root-paths' +import { normalizeViewPath } from '../shared/lib/router/utils/view-paths' export type FindComponentsResult = { components: LoadComponentsReturnType @@ -142,7 +142,7 @@ export default abstract class Server { protected publicDir: string protected hasStaticDir: boolean protected pagesManifest?: PagesManifest - protected rootPathsManifest?: PagesManifest + protected viewPathsManifest?: PagesManifest protected buildId: string protected minimalMode: boolean protected renderOpts: { @@ -182,7 +182,7 @@ export default abstract class Server { private responseCache: ResponseCache protected router: Router protected dynamicRoutes?: DynamicRoutes - protected rootPathRoutes?: Record + protected viewPathRoutes?: Record protected customRoutes: CustomRoutes protected middlewareManifest?: MiddlewareManifest protected middleware?: RoutingItem[] @@ -193,7 +193,7 @@ export default abstract class Server { protected abstract getPublicDir(): string protected abstract getHasStaticDir(): boolean protected abstract getPagesManifest(): PagesManifest | undefined - protected abstract getRootPathsManifest(): PagesManifest | undefined + protected abstract getViewPathsManifest(): PagesManifest | undefined protected abstract getBuildId(): string protected abstract generatePublicRoutes(): Route[] protected abstract generateImageRoutes(): Route[] @@ -356,7 +356,7 @@ export default abstract class Server { }) this.pagesManifest = this.getPagesManifest() - this.rootPathsManifest = this.getRootPathsManifest() + this.viewPathsManifest = this.getViewPathsManifest() this.middlewareManifest = this.getMiddlewareManifest() this.customRoutes = this.getCustomRoutes() @@ -876,7 +876,7 @@ export default abstract class Server { const { useFileSystemPublicRoutes } = this.nextConfig if (useFileSystemPublicRoutes) { - this.rootPathRoutes = this.getRootPathRoutes() + this.viewPathRoutes = this.getViewPathRoutes() this.dynamicRoutes = this.getDynamicRoutes() if (!this.minimalMode) { this.middleware = this.getMiddleware() @@ -898,30 +898,6 @@ export default abstract class Server { } } - protected isRoutableRootPath(pathname: string): boolean { - if (this.rootPathRoutes) { - const paths = Object.keys(this.rootPathRoutes) - - /** - * a root path is only routable if - * 1. has root/hello.js and no root/hello/ folder - * 2. has root/hello.js and a root/hello/index.js - */ - const hasFolderIndex = this.rootPathRoutes[`${pathname}/index`] - const hasFolder = paths.some((path) => { - return path.startsWith(`${pathname}/`) - }) - - if (hasFolder && hasFolderIndex) { - return true - } - if (!hasFolder && this.rootPathRoutes[pathname]) { - return true - } - } - return false - } - protected async hasPage(pathname: string): Promise { let found = false try { @@ -995,7 +971,7 @@ export default abstract class Server { return getSortedRoutes( [ - ...Object.keys(this.rootPathRoutes || {}), + ...Object.keys(this.viewPathRoutes || {}), ...Object.keys(this.pagesManifest!), ].map( (page) => @@ -1013,31 +989,33 @@ export default abstract class Server { .filter((item): item is RoutingItem => Boolean(item)) } - protected getRootPathRoutes(): Record { - const rootPathRoutes: Record = {} + protected getViewPathRoutes(): Record { + const viewPathRoutes: Record = {} - Object.keys(this.rootPathsManifest || {}).forEach((entry) => { - rootPathRoutes[normalizeRootPath(entry)] = entry + Object.keys(this.viewPathsManifest || {}).forEach((entry) => { + viewPathRoutes[normalizeViewPath(entry)] = entry }) - return rootPathRoutes + return viewPathRoutes } - protected getRootPathLayouts(pathname: string): string[] { + protected getViewPathLayouts(pathname: string): string[] { const layoutPaths: string[] = [] - if (this.rootPathRoutes) { - const paths = Object.values(this.rootPathRoutes) + if (this.viewPathRoutes) { + const paths = Object.values(this.viewPathRoutes) const parts = pathname.split('/').filter(Boolean) for (let i = 1; i < parts.length; i++) { - const parentPath = `/${parts.slice(0, i).join('/')}` + const layoutPath = `/${parts.slice(0, i).join('/')}/layout` - if (paths.includes(parentPath)) { - layoutPaths.push(parentPath) + if (paths.includes(layoutPath)) { + layoutPaths.push(layoutPath) } } - // TODO: when should we bail on adding the root.js wrapper - layoutPaths.unshift('/_root') + + if (this.viewPathRoutes['/layout']) { + layoutPaths.unshift('/layout') + } } return layoutPaths } @@ -1755,38 +1733,31 @@ export default abstract class Server { delete query._nextBubbleNoFallback // map the route to the actual bundle name e.g. // `/dashboard/rootonly/hello` -> `/dashboard+rootonly/hello` - const getOriginalRootPath = (rootPath: string) => { - if (this.nextConfig.experimental.rootDir) { - const originalRootPath = - this.rootPathRoutes?.[`${pathname}/index`] || - this.rootPathRoutes?.[pathname] + const getOriginalViewPath = (viewPath: string) => { + if (this.nextConfig.experimental.viewsDir) { + const originalViewPath = + this.viewPathRoutes?.[`${viewPath}/index`] || + this.viewPathRoutes?.[`${viewPath}`] - if (!originalRootPath) { + if (!originalViewPath) { return null } - const isRoutable = this.isRoutableRootPath(rootPath) - // 404 when layout is hit and this isn't a routable path - // e.g. root/hello.js with root/hello/another.js but - // no root/hello/index.js - if (!isRoutable) { - return '' - } - return originalRootPath + return originalViewPath } return null } - const gatherRootLayouts = async ( - rootPath: string, + const gatherViewLayouts = async ( + viewPath: string, result: FindComponentsResult ): Promise => { - const layoutPaths = this.getRootPathLayouts(rootPath) - result.components.rootLayouts = await Promise.all( + const layoutPaths = this.getViewPathLayouts(viewPath) + result.components.viewLayouts = await Promise.all( layoutPaths.map(async (path) => { const layoutRes = await this.findPageComponents(path) return { - isRoot: path === '/_root', + isRootLayout: path === '/layout', Component: layoutRes?.components.Component!, getStaticProps: layoutRes?.components.getStaticProps, getServerSideProps: layoutRes?.components.getServerSideProps, @@ -1799,16 +1770,16 @@ export default abstract class Server { // Ensure a request to the URL /accounts/[id] will be treated as a dynamic // route correctly and not loaded immediately without parsing params. if (!isDynamicRoute(pathname)) { - const rootPath = getOriginalRootPath(pathname) + const viewPath = getOriginalViewPath(pathname) - if (typeof rootPath === 'string') { - page = rootPath + if (typeof viewPath === 'string') { + page = viewPath } const result = await this.findPageComponents(page, query) if (result) { try { - if (result.components.isRootPath) { - await gatherRootLayouts(page, result) + if (result.components.isViewPath) { + await gatherViewLayouts(page, result) } return await this.renderToResponseWithComponents(ctx, result) } catch (err) { @@ -1828,21 +1799,21 @@ export default abstract class Server { continue } page = dynamicRoute.page - const rootPath = getOriginalRootPath(page) + const viewPath = getOriginalViewPath(page) - if (typeof rootPath === 'string') { - page = rootPath + if (typeof viewPath === 'string') { + page = viewPath } const dynamicRouteResult = await this.findPageComponents( - dynamicRoute.page, + page, query, params ) if (dynamicRouteResult) { try { - if (dynamicRouteResult.components.isRootPath) { - await gatherRootLayouts(page, dynamicRouteResult) + if (dynamicRouteResult.components.isViewPath) { + await gatherViewLayouts(page, dynamicRouteResult) } return await this.renderToResponseWithComponents( { diff --git a/packages/next/server/config-shared.ts b/packages/next/server/config-shared.ts index 5d3b4b9e15285..8b1c10c8581d4 100644 --- a/packages/next/server/config-shared.ts +++ b/packages/next/server/config-shared.ts @@ -96,7 +96,7 @@ export interface ExperimentalConfig { scrollRestoration?: boolean externalDir?: boolean conformance?: boolean - rootDir?: boolean + viewsDir?: boolean amp?: { optimizer?: any validator?: string @@ -494,7 +494,7 @@ export const defaultConfig: NextConfig = { swcFileReading: true, craCompat: false, esmExternals: true, - rootDir: false, + viewsDir: false, // default to 50MB limit isrMemoryCacheSize: 50 * 1024 * 1024, serverComponents: false, diff --git a/packages/next/server/dev/hot-reloader.ts b/packages/next/server/dev/hot-reloader.ts index 4a71f8bfb5cd2..b0035377844b5 100644 --- a/packages/next/server/dev/hot-reloader.ts +++ b/packages/next/server/dev/hot-reloader.ts @@ -168,7 +168,7 @@ export default class HotReloader { private fallbackWatcher: any private hotReloaderSpan: Span private pagesMapping: { [key: string]: string } = {} - private rootDir?: string + private viewsDir?: string constructor( dir: string, @@ -179,7 +179,7 @@ export default class HotReloader { buildId, previewProps, rewrites, - rootDir, + viewsDir, }: { config: NextConfigComplete pagesDir: string @@ -187,14 +187,14 @@ export default class HotReloader { buildId: string previewProps: __ApiPreviewProps rewrites: CustomRoutes['rewrites'] - rootDir?: string + viewsDir?: string } ) { this.buildId = buildId this.dir = dir this.middlewares = [] this.pagesDir = pagesDir - this.rootDir = rootDir + this.viewsDir = viewsDir this.distDir = distDir this.clientStats = null this.serverStats = null @@ -427,7 +427,7 @@ export default class HotReloader { pagesDir: this.pagesDir, rewrites: this.rewrites, runWebpackSpan: this.hotReloaderSpan, - rootDir: this.rootDir, + viewsDir: this.viewsDir, } return webpackConfigSpan @@ -839,7 +839,7 @@ export default class HotReloader { multiCompiler, watcher: this.watcher, pagesDir: this.pagesDir, - rootDir: this.rootDir, + viewsDir: this.viewsDir, nextConfig: this.config, ...(this.config.onDemandEntries as { maxInactiveAge: number diff --git a/packages/next/server/dev/next-dev-server.ts b/packages/next/server/dev/next-dev-server.ts index 3a855aa3c6246..7a5c28597ddd6 100644 --- a/packages/next/server/dev/next-dev-server.ts +++ b/packages/next/server/dev/next-dev-server.ts @@ -66,7 +66,7 @@ import { isCustomErrorPage, isReservedPage } from '../../build/utils' import { NodeNextResponse, NodeNextRequest } from '../base-http/node' import { getPageRuntime, invalidatePageRuntimeCache } from '../../build/entries' import { normalizePathSep } from '../../shared/lib/page-path/normalize-path-sep' -import { normalizeRootPath } from '../../shared/lib/router/utils/root-paths' +import { normalizeViewPath } from '../../shared/lib/router/utils/view-paths' // Load ReactDevOverlay only when needed let ReactDevOverlayImpl: React.FunctionComponent @@ -98,7 +98,7 @@ export default class DevServer extends Server { protected sortedRoutes?: string[] private addedUpgradeListener = false private pagesDir: string - private rootDir?: string + private viewsDir?: string protected staticPathsWorker?: { [key: string]: any } & { loadStaticPaths: typeof import('./static-paths-worker').loadStaticPaths @@ -179,12 +179,12 @@ export default class DevServer extends Server { this.isCustomServer = !options.isNextDevCommand // TODO: hot-reload root/pages dirs? - const { pages: pagesDir, root: rootDir } = findPagesDir( + const { pages: pagesDir, views: viewsDir } = findPagesDir( this.dir, - this.nextConfig.experimental.rootDir + this.nextConfig.experimental.viewsDir ) this.pagesDir = pagesDir - this.rootDir = rootDir + this.viewsDir = viewsDir } protected getBuildId(): string { @@ -267,8 +267,8 @@ export default class DevServer extends Server { let wp = (this.webpackWatcher = new Watchpack()) const toWatch = [this.pagesDir!] - if (this.rootDir) { - toWatch.push(this.rootDir) + if (this.viewsDir) { + toWatch.push(this.viewsDir) } wp.watch([], toWatch, 0) @@ -276,7 +276,7 @@ export default class DevServer extends Server { const routedMiddleware = [] const routedPages: string[] = [] const knownFiles = wp.getTimeInfoEntries() - const rootPaths: Record = {} + const viewPaths: Record = {} const ssrMiddleware = new Set() for (const [fileName, { accuracy, safeTime }] of knownFiles) { @@ -284,17 +284,17 @@ export default class DevServer extends Server { continue } let pageName: string = '' - let isRootPath = false + let isViewPath = false if ( - this.rootDir && + this.viewsDir && normalizePathSep(fileName).startsWith( - normalizePathSep(this.rootDir) + normalizePathSep(this.viewsDir) ) ) { - isRootPath = true + isViewPath = true pageName = absolutePathToPage( - this.rootDir, + this.viewsDir, fileName, this.nextConfig.pageExtensions, false @@ -307,11 +307,11 @@ export default class DevServer extends Server { ) } - if (isRootPath) { + if (isViewPath) { // TODO: should only routes ending in /index.js be route-able? const originalPageName = pageName - pageName = normalizeRootPath(pageName) - rootPaths[pageName] = originalPageName + pageName = normalizeViewPath(pageName) + viewPaths[pageName] = originalPageName if (routedPages.includes(pageName)) { continue @@ -348,7 +348,7 @@ export default class DevServer extends Server { routedPages.push(pageName) } - this.rootPathRoutes = rootPaths + this.viewPathRoutes = viewPaths this.middleware = getSortedRoutes(routedMiddleware).map((page) => ({ match: getRouteMatcher( getMiddlewareRegex(page, !ssrMiddleware.has(page)) @@ -410,7 +410,7 @@ export default class DevServer extends Server { setGlobal('phase', PHASE_DEVELOPMENT_SERVER) await verifyTypeScriptSetup( this.dir, - [this.pagesDir!, this.rootDir].filter(Boolean) as string[], + [this.pagesDir!, this.viewsDir].filter(Boolean) as string[], false, this.nextConfig ) @@ -437,7 +437,7 @@ export default class DevServer extends Server { previewProps: this.getPreviewProps(), buildId: this.buildId, rewrites, - rootDir: this.rootDir, + viewsDir: this.viewsDir, }) await super.prepare() await this.addExportPathMapRoutes() @@ -495,10 +495,10 @@ export default class DevServer extends Server { return false } - // check rootDir first if enabled - if (this.rootDir) { + // check viewsDir first if enabled + if (this.viewsDir) { const pageFile = await findPageFile( - this.rootDir, + this.viewsDir, normalizedPath, this.nextConfig.pageExtensions ) @@ -786,7 +786,7 @@ export default class DevServer extends Server { return undefined } - protected getRootPathsManifest(): undefined { + protected getViewPathsManifest(): undefined { return undefined } diff --git a/packages/next/server/dev/on-demand-entry-handler.ts b/packages/next/server/dev/on-demand-entry-handler.ts index 6ca20c9b068cf..116136adb030e 100644 --- a/packages/next/server/dev/on-demand-entry-handler.ts +++ b/packages/next/server/dev/on-demand-entry-handler.ts @@ -54,7 +54,7 @@ export function onDemandEntryHandler({ nextConfig, pagesBufferLength, pagesDir, - rootDir, + viewsDir, watcher, }: { maxInactiveAge: number @@ -62,7 +62,7 @@ export function onDemandEntryHandler({ nextConfig: NextConfigComplete pagesBufferLength: number pagesDir: string - rootDir?: string + viewsDir?: string watcher: any }) { const invalidator = new Invalidator(watcher) @@ -101,7 +101,7 @@ export function onDemandEntryHandler({ return invalidator.doneBuilding() } const [clientStats, serverStats, edgeServerStats] = multiStats.stats - const root = !!rootDir + const root = !!viewsDir const pagePaths = [ ...getPagePathsFromEntrypoints( 'client', @@ -182,7 +182,7 @@ export function onDemandEntryHandler({ pagesDir, page, nextConfig.pageExtensions, - rootDir + viewsDir ) let entryAdded = false @@ -340,23 +340,18 @@ async function findPagePathData( pagesDir: string, page: string, extensions: string[], - rootDir?: string + viewsDir?: string ) { const normalizedPagePath = tryToNormalizePagePath(page) let pagePath: string | null = null - let isRoot = false - const isRootFile = rootDir && normalizedPagePath === '/_root' - - // check rootDir first - if (rootDir) { - pagePath = await findPageFile( - join(rootDir, isRootFile ? '..' : ''), - isRootFile ? 'root' : normalizedPagePath, - extensions - ) + let isView = false + + // check viewsDir first + if (viewsDir) { + pagePath = await findPageFile(viewsDir, normalizedPagePath, extensions) if (pagePath) { - isRoot = true + isView = true } } @@ -366,19 +361,11 @@ async function findPagePathData( if (pagePath !== null) { const pageUrl = ensureLeadingSlash( - removePagePathTail(normalizePathSep(pagePath), extensions, !isRoot) + removePagePathTail(normalizePathSep(pagePath), extensions, !isView) ) const bundleFile = normalizePagePath(pageUrl) - let bundlePath - let absolutePagePath - - if (isRootFile) { - bundlePath = 'root' - absolutePagePath = join(rootDir!, '..', pagePath) - } else { - bundlePath = posix.join(isRoot ? 'root' : 'pages', bundleFile) - absolutePagePath = join(isRoot ? rootDir! : pagesDir, pagePath) - } + const bundlePath = posix.join(isView ? 'views' : 'pages', bundleFile) + const absolutePagePath = join(isView ? viewsDir! : pagesDir, pagePath) return { absolutePagePath, diff --git a/packages/next/server/get-route-from-entrypoint.ts b/packages/next/server/get-route-from-entrypoint.ts index ddeafbfff5380..30e40e9858a35 100644 --- a/packages/next/server/get-route-from-entrypoint.ts +++ b/packages/next/server/get-route-from-entrypoint.ts @@ -3,7 +3,7 @@ import getRouteFromAssetPath from '../shared/lib/router/utils/get-route-from-ass // matches pages/:page*.js const SERVER_ROUTE_NAME_REGEX = /^pages[/\\](.*)$/ // matches root/:path*.js -const ROOT_ROUTE_NAME_REGEX = /^root[/\\](.*)$/ +const ROOT_ROUTE_NAME_REGEX = /^views[/\\](.*)$/ // matches static/pages/:page*.js const BROWSER_ROUTE_NAME_REGEX = /^static[/\\]pages[/\\](.*)$/ diff --git a/packages/next/server/lib/find-page-file.ts b/packages/next/server/lib/find-page-file.ts index 7c3e8bf838d35..b7e60c2b7dd56 100644 --- a/packages/next/server/lib/find-page-file.ts +++ b/packages/next/server/lib/find-page-file.ts @@ -20,12 +20,7 @@ export async function findPageFile( normalizedPagePath: string, pageExtensions: string[] ): Promise { - const isRootPaths = pagesDir.replace(/\\/g, '/').endsWith('/root') - const pagePaths = getPagePaths( - normalizedPagePath, - pageExtensions, - isRootPaths - ) + const pagePaths = getPagePaths(normalizedPagePath, pageExtensions) const [existingPath, ...others] = ( await Promise.all( pagePaths.map(async (path) => diff --git a/packages/next/server/load-components.ts b/packages/next/server/load-components.ts index 862834e523f2e..3b03250be45d5 100644 --- a/packages/next/server/load-components.ts +++ b/packages/next/server/load-components.ts @@ -40,9 +40,9 @@ export type LoadComponentsReturnType = { ComponentMod: any AppMod: any AppServerMod: any - isRootPath?: boolean - rootLayouts?: Array<{ - isRoot?: boolean + isViewPath?: boolean + viewLayouts?: Array<{ + isRootLayout?: boolean Component: NextComponentType getStaticProps?: GetStaticProps getServerSideProps?: GetServerSideProps @@ -134,7 +134,7 @@ export async function loadComponents( const { getServerSideProps, getStaticProps, getStaticPaths } = ComponentMod - let isRootPath = false + let isViewPath = false if (rootEnabled) { const pagePath = getPagePath( @@ -145,7 +145,7 @@ export async function loadComponents( undefined, rootEnabled ) - isRootPath = !!pagePath?.match(/server[/\\]root[/\\]/) + isViewPath = !!pagePath?.match(/server[/\\]views[/\\]/) } return { @@ -162,6 +162,6 @@ export async function loadComponents( getStaticProps, getStaticPaths, serverComponentManifest, - isRootPath, + isViewPath, } } diff --git a/packages/next/server/next-server.ts b/packages/next/server/next-server.ts index d0b527aea940f..47764d6a06cbb 100644 --- a/packages/next/server/next-server.ts +++ b/packages/next/server/next-server.ts @@ -31,7 +31,7 @@ import { ROUTES_MANIFEST, MIDDLEWARE_FLIGHT_MANIFEST, CLIENT_PUBLIC_FILES_PATH, - ROOT_PATHS_MANIFEST, + VIEW_PATHS_MANIFEST, } from '../shared/lib/constants' import { recursiveReadDirSync } from './lib/recursive-readdir-sync' import { format as formatUrl, UrlWithParsedQuery } from 'url' @@ -46,7 +46,7 @@ import { getExtension, serveStatic } from './serve-static' import { ParsedUrlQuery } from 'querystring' import { apiResolver } from './api-utils/node' import { RenderOpts, renderToHTML } from './render' -import { renderToHTML as rootRenderToHTML } from './root-render' +import { renderToHTML as viewRenderToHTML } from './view-render' import { ParsedUrl, parseUrl } from '../shared/lib/router/utils/parse-url' import * as Log from '../build/output/log' @@ -159,13 +159,13 @@ export default class NextNodeServer extends BaseServer { return require(join(this.serverDistDir, PAGES_MANIFEST)) } - protected getRootPathsManifest(): PagesManifest | undefined { - if (this.nextConfig.experimental.rootDir) { - const rootPathsManifestPath = join( + protected getViewPathsManifest(): PagesManifest | undefined { + if (this.nextConfig.experimental.viewsDir) { + const viewPathsManifestPath = join( this.serverDistDir, - ROOT_PATHS_MANIFEST + VIEW_PATHS_MANIFEST ) - return require(rootPathsManifestPath) + return require(viewPathsManifestPath) } } @@ -584,8 +584,8 @@ export default class NextNodeServer extends BaseServer { // https://github.com/vercel/next.js/blob/df7cbd904c3bd85f399d1ce90680c0ecf92d2752/packages/next/server/render.tsx#L947-L952 renderOpts.serverComponentManifest = this.serverComponentManifest - if (renderOpts.isRootPath) { - return rootRenderToHTML( + if (renderOpts.isViewPath) { + return viewRenderToHTML( req.originalRequest, res.originalResponse, pathname, @@ -642,7 +642,7 @@ export default class NextNodeServer extends BaseServer { this._isLikeServerless, this.renderOpts.dev, locales, - this.nextConfig.experimental.rootDir + this.nextConfig.experimental.viewsDir ) } @@ -673,7 +673,7 @@ export default class NextNodeServer extends BaseServer { pagePath!, !this.renderOpts.dev && this._isLikeServerless, this.renderOpts.serverComponents, - this.nextConfig.experimental.rootDir + this.nextConfig.experimental.viewsDir ) if ( diff --git a/packages/next/server/require.ts b/packages/next/server/require.ts index 3205c04d624d6..c5bebb5aca485 100644 --- a/packages/next/server/require.ts +++ b/packages/next/server/require.ts @@ -6,7 +6,7 @@ import { PAGES_MANIFEST, SERVER_DIRECTORY, SERVERLESS_DIRECTORY, - ROOT_PATHS_MANIFEST, + VIEW_PATHS_MANIFEST, } from '../shared/lib/constants' import { normalizeLocalePath } from '../shared/lib/i18n/normalize-locale-path' import { normalizePagePath } from '../shared/lib/page-path/normalize-page-path' @@ -39,7 +39,7 @@ export function getPagePath( if (page === '/_root') { return join(serverBuildPath, 'root.js') } - rootPathsManifest = require(join(serverBuildPath, ROOT_PATHS_MANIFEST)) + rootPathsManifest = require(join(serverBuildPath, VIEW_PATHS_MANIFEST)) } const pagesManifest = require(join( serverBuildPath, diff --git a/packages/next/server/root-render.tsx b/packages/next/server/view-render.tsx similarity index 98% rename from packages/next/server/root-render.tsx rename to packages/next/server/view-render.tsx index 6e9af6d9f7cde..d8bedf081b0fa 100644 --- a/packages/next/server/root-render.tsx +++ b/packages/next/server/view-render.tsx @@ -21,6 +21,7 @@ import { FlushEffectsContext } from '../shared/lib/flush-effects' import ReactDOMServer from 'react-dom/server.browser' import { isDynamicRoute } from '../shared/lib/router/utils' import { tryGetPreviewData } from './api-utils/node' +import DefaultRootLayout from '../lib/views-layout' export type RenderOptsPartial = { err?: Error | null @@ -215,7 +216,7 @@ export async function renderToHTML( const hasConcurrentFeatures = !!runtime const pageIsDynamic = isDynamicRoute(pathname) - const layouts = renderOpts.rootLayouts || [] + const layouts = renderOpts.viewLayouts || [] layouts.push({ Component: renderOpts.Component, @@ -242,7 +243,7 @@ export async function renderToHTML( const dataCacheKey = i.toString() const layout = layouts[i] - if (layout.isRoot) { + if (layout.isRootLayout) { RootLayout = layout.Component continue } @@ -330,7 +331,8 @@ export async function renderToHTML( if (!RootLayout) { // TODO: fallback to our own root layout? - throw new Error('invariant RootLayout not loaded') + // throw new Error('invariant RootLayout not loaded') + RootLayout = DefaultRootLayout } const headChildren = buildManifest.rootMainFiles.map((src) => ( diff --git a/packages/next/server/web-server.ts b/packages/next/server/web-server.ts index ca37bbebfbb12..4747f91ca122c 100644 --- a/packages/next/server/web-server.ts +++ b/packages/next/server/web-server.ts @@ -103,7 +103,7 @@ export default class NextWebServer extends BaseServer { [this.serverOptions.webServerConfig.page]: '', } } - protected getRootPathsManifest() { + protected getViewPathsManifest() { return { [this.serverOptions.webServerConfig.page]: '', } diff --git a/packages/next/shared/lib/constants.ts b/packages/next/shared/lib/constants.ts index ab3193f05b3d7..8c17e406a3e76 100644 --- a/packages/next/shared/lib/constants.ts +++ b/packages/next/shared/lib/constants.ts @@ -4,7 +4,7 @@ export const PHASE_PRODUCTION_SERVER = 'phase-production-server' export const PHASE_DEVELOPMENT_SERVER = 'phase-development-server' export const PHASE_TEST = 'phase-test' export const PAGES_MANIFEST = 'pages-manifest.json' -export const ROOT_PATHS_MANIFEST = 'root-paths-manifest.json' +export const VIEW_PATHS_MANIFEST = 'view-paths-manifest.json' export const BUILD_MANIFEST = 'build-manifest.json' export const EXPORT_MARKER = 'export-marker.json' export const EXPORT_DETAIL = 'export-detail.json' diff --git a/packages/next/shared/lib/page-path/get-page-paths.ts b/packages/next/shared/lib/page-path/get-page-paths.ts index 9be9bce3549d7..4d4906e3d8680 100644 --- a/packages/next/shared/lib/page-path/get-page-paths.ts +++ b/packages/next/shared/lib/page-path/get-page-paths.ts @@ -10,23 +10,9 @@ import { join } from '../isomorphic/path' * @param normalizedPagePath Normalized page path (it will denormalize). * @param extensions Allowed extensions. */ -export function getPagePaths( - normalizedPagePath: string, - extensions: string[], - isRoot?: boolean -) { +export function getPagePaths(normalizedPagePath: string, extensions: string[]) { const page = denormalizePagePath(normalizedPagePath) - if (isRoot) { - return flatten( - extensions.map((extension) => { - return normalizedPagePath.endsWith('/index') - ? [`${page}.${extension}`, join(page, `index.${extension}`)] - : [`${page}.${extension}`] - }) - ) - } - return flatten( extensions.map((extension) => { return !normalizedPagePath.endsWith('/index') diff --git a/packages/next/shared/lib/router/utils/root-paths.ts b/packages/next/shared/lib/router/utils/root-paths.ts deleted file mode 100644 index 02b01d911d59c..0000000000000 --- a/packages/next/shared/lib/router/utils/root-paths.ts +++ /dev/null @@ -1,14 +0,0 @@ -// strip `@propName` from pathname and normalize `+` to `/` -export function normalizeRootPath(pathname: string) { - let normalized = '' - - pathname.split('/').forEach((segment) => { - segment.split('+').forEach((subSegment) => { - const value = subSegment.split('@').shift() - if (value) { - normalized += `/${value}` - } - }) - }) - return normalized -} diff --git a/packages/next/shared/lib/router/utils/view-paths.ts b/packages/next/shared/lib/router/utils/view-paths.ts new file mode 100644 index 0000000000000..f34eb96c3bf1d --- /dev/null +++ b/packages/next/shared/lib/router/utils/view-paths.ts @@ -0,0 +1,17 @@ +// remove (name) from pathname as it's not considered for routing +export function normalizeViewPath(pathname: string) { + let normalized = '' + const segments = pathname.split('/') + + segments.forEach((segment, index) => { + if (!segment) return + if (segment.startsWith('(') && segment.endsWith(')')) { + return + } + if (segment === 'page' && index === segments.length - 1) { + return + } + normalized += `/${segment}` + }) + return normalized +} diff --git a/packages/next/taskfile.js b/packages/next/taskfile.js index 445c3959d4316..3edcde3e5a6a6 100644 --- a/packages/next/taskfile.js +++ b/packages/next/taskfile.js @@ -1887,21 +1887,9 @@ export async function pages_document(task, opts) { .target('dist/pages') } -export async function pages_root(task, opts) { - await task - .source('pages/root.tsx') - .swc('server', { dev: opts.dev, keepImportAssertions: true }) -} - export async function pages(task, opts) { await task.parallel( - [ - 'pages_app', - 'pages_app_server', - 'pages_error', - 'pages_document', - 'pages_root', - ], + ['pages_app', 'pages_app_server', 'pages_error', 'pages_document'], opts ) } diff --git a/test/e2e/root-dir/app/root/conditional/[slug].server.js b/test/e2e/root-dir/app/root/conditional/[slug].server.js deleted file mode 100644 index 14dae08e2713a..0000000000000 --- a/test/e2e/root-dir/app/root/conditional/[slug].server.js +++ /dev/null @@ -1,27 +0,0 @@ -export async function getServerSideProps({ params }) { - if (params.slug === 'nonexistent') { - return { - notFound: true, - } - } - return { - props: { - isUser: params.slug === 'tim', - isBoth: params.slug === 'both', - }, - } -} - -export default function UserOrTeam({ isUser, isBoth, user, team }) { - return ( - <> - {isUser && !isBoth ? user : team} - {isBoth ? ( - <> - {user} - {team} - - ) : null} - - ) -} diff --git a/test/e2e/root-dir/app/root/conditional/[slug]@team/index.js b/test/e2e/root-dir/app/root/conditional/[slug]@team/index.js deleted file mode 100644 index 02119380f3382..0000000000000 --- a/test/e2e/root-dir/app/root/conditional/[slug]@team/index.js +++ /dev/null @@ -1,7 +0,0 @@ -export default function TeamHomePage(props) { - return ( - <> -

hello from team homepage

- - ) -} diff --git a/test/e2e/root-dir/app/root/conditional/[slug]@team/members.js b/test/e2e/root-dir/app/root/conditional/[slug]@team/members.js deleted file mode 100644 index 2c3ba112beade..0000000000000 --- a/test/e2e/root-dir/app/root/conditional/[slug]@team/members.js +++ /dev/null @@ -1,7 +0,0 @@ -export default function TeamMembersPage(props) { - return ( - <> -

hello from team/members

- - ) -} diff --git a/test/e2e/root-dir/app/root/conditional/[slug]@user/index.js b/test/e2e/root-dir/app/root/conditional/[slug]@user/index.js deleted file mode 100644 index 81100777dae29..0000000000000 --- a/test/e2e/root-dir/app/root/conditional/[slug]@user/index.js +++ /dev/null @@ -1,7 +0,0 @@ -export default function UserHomePage(props) { - return ( - <> -

hello from user homepage

- - ) -} diff --git a/test/e2e/root-dir/app/root/conditional/[slug]@user/teams.js b/test/e2e/root-dir/app/root/conditional/[slug]@user/teams.js deleted file mode 100644 index 294c3bf316dad..0000000000000 --- a/test/e2e/root-dir/app/root/conditional/[slug]@user/teams.js +++ /dev/null @@ -1,7 +0,0 @@ -export default function UserTeamsPage(props) { - return ( - <> -

hello from user/teams

- - ) -} diff --git a/test/e2e/root-dir/app/next.config.js b/test/e2e/views-dir/app/next.config.js similarity index 85% rename from test/e2e/root-dir/app/next.config.js rename to test/e2e/views-dir/app/next.config.js index 9abd7b65b73ae..4131d5b608757 100644 --- a/test/e2e/root-dir/app/next.config.js +++ b/test/e2e/views-dir/app/next.config.js @@ -1,6 +1,6 @@ module.exports = { experimental: { - rootDir: true, + viewsDir: true, runtime: 'nodejs', reactRoot: true, serverComponents: true, diff --git a/test/e2e/root-dir/app/pages/blog/[slug].js b/test/e2e/views-dir/app/pages/blog/[slug].js similarity index 100% rename from test/e2e/root-dir/app/pages/blog/[slug].js rename to test/e2e/views-dir/app/pages/blog/[slug].js diff --git a/test/e2e/root-dir/app/pages/index.js b/test/e2e/views-dir/app/pages/index.js similarity index 100% rename from test/e2e/root-dir/app/pages/index.js rename to test/e2e/views-dir/app/pages/index.js diff --git a/test/e2e/root-dir/app/public/hello.txt b/test/e2e/views-dir/app/public/hello.txt similarity index 100% rename from test/e2e/root-dir/app/public/hello.txt rename to test/e2e/views-dir/app/public/hello.txt diff --git a/test/e2e/root-dir/app/root/dashboard+changelog.server.js b/test/e2e/views-dir/app/views/(rootonly)/dashboard/changelog/page.server.js similarity index 100% rename from test/e2e/root-dir/app/root/dashboard+changelog.server.js rename to test/e2e/views-dir/app/views/(rootonly)/dashboard/changelog/page.server.js diff --git a/test/e2e/root-dir/app/root/dashboard+rootonly/hello.server.js b/test/e2e/views-dir/app/views/(rootonly)/dashboard/hello/page.server.js similarity index 100% rename from test/e2e/root-dir/app/root/dashboard+rootonly/hello.server.js rename to test/e2e/views-dir/app/views/(rootonly)/dashboard/hello/page.server.js diff --git a/test/e2e/root-dir/app/root/client-component-route.client.js b/test/e2e/views-dir/app/views/client-component-route/page.client.js similarity index 100% rename from test/e2e/root-dir/app/root/client-component-route.client.js rename to test/e2e/views-dir/app/views/client-component-route/page.client.js diff --git a/test/e2e/root-dir/app/root/client-nested/index.server.js b/test/e2e/views-dir/app/views/client-nested/index/page.server.js similarity index 100% rename from test/e2e/root-dir/app/root/client-nested/index.server.js rename to test/e2e/views-dir/app/views/client-nested/index/page.server.js diff --git a/test/e2e/root-dir/app/root/client-nested.client.js b/test/e2e/views-dir/app/views/client-nested/layout.client.js similarity index 100% rename from test/e2e/root-dir/app/root/client-nested.client.js rename to test/e2e/views-dir/app/views/client-nested/layout.client.js diff --git a/test/e2e/root-dir/app/root/dashboard/deployments/[id].server.js b/test/e2e/views-dir/app/views/dashboard/deployments/[id]/page.server.js similarity index 100% rename from test/e2e/root-dir/app/root/dashboard/deployments/[id].server.js rename to test/e2e/views-dir/app/views/dashboard/deployments/[id]/page.server.js diff --git a/test/e2e/root-dir/app/root/dashboard/deployments/info.server.js b/test/e2e/views-dir/app/views/dashboard/deployments/info/page.server.js similarity index 100% rename from test/e2e/root-dir/app/root/dashboard/deployments/info.server.js rename to test/e2e/views-dir/app/views/dashboard/deployments/info/page.server.js diff --git a/test/e2e/root-dir/app/root/dashboard/deployments.server.js b/test/e2e/views-dir/app/views/dashboard/deployments/layout.server.js similarity index 100% rename from test/e2e/root-dir/app/root/dashboard/deployments.server.js rename to test/e2e/views-dir/app/views/dashboard/deployments/layout.server.js diff --git a/test/e2e/root-dir/app/root/dashboard/index.server.js b/test/e2e/views-dir/app/views/dashboard/index/page.server.js similarity index 100% rename from test/e2e/root-dir/app/root/dashboard/index.server.js rename to test/e2e/views-dir/app/views/dashboard/index/page.server.js diff --git a/test/e2e/root-dir/app/root/dashboard/integrations/index.server.js b/test/e2e/views-dir/app/views/dashboard/integrations/index/page.server.js similarity index 100% rename from test/e2e/root-dir/app/root/dashboard/integrations/index.server.js rename to test/e2e/views-dir/app/views/dashboard/integrations/index/page.server.js diff --git a/test/e2e/root-dir/app/root/dashboard.server.js b/test/e2e/views-dir/app/views/dashboard/layout.server.js similarity index 100% rename from test/e2e/root-dir/app/root/dashboard.server.js rename to test/e2e/views-dir/app/views/dashboard/layout.server.js diff --git a/test/e2e/root-dir/app/root.server.js b/test/e2e/views-dir/app/views/layout.server.js similarity index 100% rename from test/e2e/root-dir/app/root.server.js rename to test/e2e/views-dir/app/views/layout.server.js diff --git a/test/e2e/root-dir/app/root/partial-match-[id].server.js b/test/e2e/views-dir/app/views/partial-match-[id]/page.server.js similarity index 100% rename from test/e2e/root-dir/app/root/partial-match-[id].server.js rename to test/e2e/views-dir/app/views/partial-match-[id]/page.server.js diff --git a/test/e2e/root-dir/app/root/shared-component-route.js b/test/e2e/views-dir/app/views/shared-component-route/page.js similarity index 100% rename from test/e2e/root-dir/app/root/shared-component-route.js rename to test/e2e/views-dir/app/views/shared-component-route/page.js diff --git a/test/e2e/root-dir/app/root/should-not-serve-client.client.js b/test/e2e/views-dir/app/views/should-not-serve-client/page.client.js similarity index 100% rename from test/e2e/root-dir/app/root/should-not-serve-client.client.js rename to test/e2e/views-dir/app/views/should-not-serve-client/page.client.js diff --git a/test/e2e/root-dir/app/root/should-not-serve-server.server.js b/test/e2e/views-dir/app/views/should-not-serve-server/page.server.js similarity index 100% rename from test/e2e/root-dir/app/root/should-not-serve-server.server.js rename to test/e2e/views-dir/app/views/should-not-serve-server/page.server.js diff --git a/test/e2e/root-dir/index.test.ts b/test/e2e/views-dir/index.test.ts similarity index 79% rename from test/e2e/root-dir/index.test.ts rename to test/e2e/views-dir/index.test.ts index 991f46ea10e6b..361613c99dfc6 100644 --- a/test/e2e/root-dir/index.test.ts +++ b/test/e2e/views-dir/index.test.ts @@ -5,7 +5,11 @@ import path from 'path' import cheerio from 'cheerio' import webdriver from 'next-webdriver' -describe('root dir', () => { +describe('views dir', () => { + if (process.env.NEXT_TEST_REACT_VERSION === '^17') { + it('should skip for react v17', () => {}) + return + } let next: NextInstance beforeAll(async () => { @@ -13,18 +17,11 @@ describe('root dir', () => { files: { public: new FileRef(path.join(__dirname, 'app/public')), pages: new FileRef(path.join(__dirname, 'app/pages')), - root: new FileRef(path.join(__dirname, 'app/root')), - 'root.server.js': new FileRef( - path.join(__dirname, 'app/root.server.js') - ), + views: new FileRef(path.join(__dirname, 'app/views')), 'next.config.js': new FileRef( path.join(__dirname, 'app/next.config.js') ), }, - dependencies: { - react: '18.0.0-rc.2', - 'react-dom': '18.0.0-rc.2', - }, }) }) afterAll(() => next.destroy()) @@ -60,7 +57,7 @@ describe('root dir', () => { // TODO: why is this routable but /should-not-serve-server.server.js it('should not include parent when not in parent directory with route in directory', async () => { - const html = await renderViaHTTP(next.url, '/dashboard/rootonly/hello') + const html = await renderViaHTTP(next.url, '/dashboard/hello') const $ = cheerio.load(html) // Should be nested in /root.js @@ -131,51 +128,6 @@ describe('root dir', () => { expect(html).toContain('hello from root/partial-match-[id]. ID is: 123') }) - // TODO: Implement - describe.skip('parallel routes', () => { - describe('conditional routes', () => { - it('should serve user page', async () => { - const html = await renderViaHTTP(next.url, '/conditional/tim') - expect(html).toContain('hello from user homepage') - }) - - it('should serve user teams page', async () => { - const html = await renderViaHTTP(next.url, '/conditional/tim/teams') - expect(html).toContain('hello from user/teams') - }) - - it('should not serve teams page to user', async () => { - const html = await renderViaHTTP(next.url, '/conditional/tim/members') - expect(html).not.toContain('hello from team/members') - }) - - it('should serve team page', async () => { - const html = await renderViaHTTP(next.url, '/conditional/vercel') - expect(html).toContain('hello from team homepage') - }) - - it('should serve team members page', async () => { - const html = await renderViaHTTP( - next.url, - '/conditional/vercel/members' - ) - expect(html).toContain('hello from team/members') - }) - - it('should provide both matches if both paths match', async () => { - const html = await renderViaHTTP(next.url, '/conditional/both') - expect(html).toContain('hello from team homepage') - expect(html).toContain('hello from user homepage') - }) - - it('should 404 based on getServerSideProps', async () => { - const res = await fetchViaHTTP(next.url, '/conditional/nonexistent') - expect(res.status).toBe(404) - expect(await res.text()).toContain('This page could not be found') - }) - }) - }) - describe('server components', () => { // TODO: why is this not servable but /dashboard+rootonly/hello.server.js // should be? Seems like they both either should be servable or not From cefb944ee5c04396707fca54a731d5458cf4e70b Mon Sep 17 00:00:00 2001 From: Steven Date: Thu, 5 May 2022 08:08:52 -0400 Subject: [PATCH 02/15] v12.1.7-canary.2 --- 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/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 | 14 +++++++------- packages/react-dev-overlay/package.json | 2 +- packages/react-refresh-utils/package.json | 2 +- 15 files changed, 22 insertions(+), 22 deletions(-) diff --git a/lerna.json b/lerna.json index 01c52b0c10f6f..def43d985a14c 100644 --- a/lerna.json +++ b/lerna.json @@ -16,5 +16,5 @@ "registry": "https://registry.npmjs.org/" } }, - "version": "12.1.7-canary.1" + "version": "12.1.7-canary.2" } diff --git a/packages/create-next-app/package.json b/packages/create-next-app/package.json index a6b0f83e7e3f0..fc6c94ccb8158 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.7-canary.1", + "version": "12.1.7-canary.2", "keywords": [ "react", "next", diff --git a/packages/eslint-config-next/package.json b/packages/eslint-config-next/package.json index 49cebd2a633e0..3386e07675a7c 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.7-canary.1", + "version": "12.1.7-canary.2", "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.7-canary.1", + "@next/eslint-plugin-next": "12.1.7-canary.2", "@rushstack/eslint-patch": "^1.1.3", "@typescript-eslint/parser": "^5.21.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 59a741768d1d1..a04257730c790 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.7-canary.1", + "version": "12.1.7-canary.2", "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 122f59f6be863..fb16c23a6a085 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.7-canary.1", + "version": "12.1.7-canary.2", "main": "index.js", "license": "MIT", "repository": { diff --git a/packages/next-codemod/package.json b/packages/next-codemod/package.json index da72b5d4972fb..75ff9fc1b5415 100644 --- a/packages/next-codemod/package.json +++ b/packages/next-codemod/package.json @@ -1,6 +1,6 @@ { "name": "@next/codemod", - "version": "12.1.7-canary.1", + "version": "12.1.7-canary.2", "license": "MIT", "dependencies": { "chalk": "4.1.0", diff --git a/packages/next-env/package.json b/packages/next-env/package.json index db23ec0d09dda..5cc6906f67d6f 100644 --- a/packages/next-env/package.json +++ b/packages/next-env/package.json @@ -1,6 +1,6 @@ { "name": "@next/env", - "version": "12.1.7-canary.1", + "version": "12.1.7-canary.2", "keywords": [ "react", "next", diff --git a/packages/next-mdx/package.json b/packages/next-mdx/package.json index 43944d7596fa9..5f226722c15dd 100644 --- a/packages/next-mdx/package.json +++ b/packages/next-mdx/package.json @@ -1,6 +1,6 @@ { "name": "@next/mdx", - "version": "12.1.7-canary.1", + "version": "12.1.7-canary.2", "main": "index.js", "license": "MIT", "repository": { diff --git a/packages/next-plugin-storybook/package.json b/packages/next-plugin-storybook/package.json index f76831a716b56..5207b41c7e87b 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.7-canary.1", + "version": "12.1.7-canary.2", "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 1551856883847..ae673d685fe46 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.7-canary.1", + "version": "12.1.7-canary.2", "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 f54c227a23c9e..6d85b8b8b3f81 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.7-canary.1", + "version": "12.1.7-canary.2", "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 2bbadf2ae275d..c2bd27437e9e3 100644 --- a/packages/next-swc/package.json +++ b/packages/next-swc/package.json @@ -1,6 +1,6 @@ { "name": "@next/swc", - "version": "12.1.7-canary.1", + "version": "12.1.7-canary.2", "private": true, "scripts": { "build-native": "napi build --platform --cargo-name next_swc_napi native", diff --git a/packages/next/package.json b/packages/next/package.json index d50386bfdc09c..5b1dc3be2409f 100644 --- a/packages/next/package.json +++ b/packages/next/package.json @@ -1,6 +1,6 @@ { "name": "next", - "version": "12.1.7-canary.1", + "version": "12.1.7-canary.2", "description": "The React Framework", "main": "./dist/server/next.js", "license": "MIT", @@ -69,7 +69,7 @@ ] }, "dependencies": { - "@next/env": "12.1.7-canary.1", + "@next/env": "12.1.7-canary.2", "caniuse-lite": "^1.0.30001332", "postcss": "8.4.5", "styled-jsx": "5.0.2" @@ -117,11 +117,11 @@ "@hapi/accept": "5.0.2", "@napi-rs/cli": "2.4.4", "@napi-rs/triples": "1.1.0", - "@next/polyfill-module": "12.1.7-canary.1", - "@next/polyfill-nomodule": "12.1.7-canary.1", - "@next/react-dev-overlay": "12.1.7-canary.1", - "@next/react-refresh-utils": "12.1.7-canary.1", - "@next/swc": "12.1.7-canary.1", + "@next/polyfill-module": "12.1.7-canary.2", + "@next/polyfill-nomodule": "12.1.7-canary.2", + "@next/react-dev-overlay": "12.1.7-canary.2", + "@next/react-refresh-utils": "12.1.7-canary.2", + "@next/swc": "12.1.7-canary.2", "@peculiar/webcrypto": "1.3.1", "@taskr/clear": "1.1.0", "@taskr/esnext": "1.1.0", diff --git a/packages/react-dev-overlay/package.json b/packages/react-dev-overlay/package.json index bb0d94236ab11..8bb0d4486bcdd 100644 --- a/packages/react-dev-overlay/package.json +++ b/packages/react-dev-overlay/package.json @@ -1,6 +1,6 @@ { "name": "@next/react-dev-overlay", - "version": "12.1.7-canary.1", + "version": "12.1.7-canary.2", "description": "A development-only overlay for developing React applications.", "repository": { "url": "vercel/next.js", diff --git a/packages/react-refresh-utils/package.json b/packages/react-refresh-utils/package.json index f8c3393b8a2c0..67c3dc9268362 100644 --- a/packages/react-refresh-utils/package.json +++ b/packages/react-refresh-utils/package.json @@ -1,6 +1,6 @@ { "name": "@next/react-refresh-utils", - "version": "12.1.7-canary.1", + "version": "12.1.7-canary.2", "description": "An experimental package providing utilities for React Refresh.", "repository": { "url": "vercel/next.js", From 08ffbc9e09ab9557946631cdfdbb5128e53a5326 Mon Sep 17 00:00:00 2001 From: Luis Alvarez D Date: Thu, 5 May 2022 10:08:28 -0500 Subject: [PATCH 03/15] Fix playwright tests (#36705) --- examples/with-playwright/styles/Home.module.css | 1 - 1 file changed, 1 deletion(-) diff --git a/examples/with-playwright/styles/Home.module.css b/examples/with-playwright/styles/Home.module.css index 35454bb748190..ec63ce5c9b457 100644 --- a/examples/with-playwright/styles/Home.module.css +++ b/examples/with-playwright/styles/Home.module.css @@ -5,7 +5,6 @@ flex-direction: column; justify-content: center; align-items: center; - height: 100vh; } .main { From 41c0a66902899f7a085d4ec87c884ca4e5fff5c8 Mon Sep 17 00:00:00 2001 From: JJ Kasper Date: Thu, 5 May 2022 11:11:17 -0500 Subject: [PATCH 04/15] Update renderOpts.dev handling and fix check (#36666) * Update renderOpts.dev handling and fix check * Update awaiting in load-components * extra arg * fix streaming case --- .../loaders/next-middleware-ssr-loader/render.ts | 1 + packages/next/server/load-components.ts | 16 ++++++++++++---- packages/next/server/next-server.ts | 2 +- 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/packages/next/build/webpack/loaders/next-middleware-ssr-loader/render.ts b/packages/next/build/webpack/loaders/next-middleware-ssr-loader/render.ts index 341334e3d4d5d..e186b66087d20 100644 --- a/packages/next/build/webpack/loaders/next-middleware-ssr-loader/render.ts +++ b/packages/next/build/webpack/loaders/next-middleware-ssr-loader/render.ts @@ -54,6 +54,7 @@ export function getRender({ } const server = new WebServer({ + dev, conf: config, minimalMode: true, webServerConfig: { diff --git a/packages/next/server/load-components.ts b/packages/next/server/load-components.ts index 3b03250be45d5..7f44ccac93f1e 100644 --- a/packages/next/server/load-components.ts +++ b/packages/next/server/load-components.ts @@ -111,11 +111,19 @@ export async function loadComponents( } const [DocumentMod, AppMod, ComponentMod, AppServerMod] = await Promise.all([ - requirePage('/_document', distDir, serverless, rootEnabled), - requirePage('/_app', distDir, serverless, rootEnabled), - requirePage(pathname, distDir, serverless, rootEnabled), + Promise.resolve().then(() => + requirePage('/_document', distDir, serverless, rootEnabled) + ), + Promise.resolve().then(() => + requirePage('/_app', distDir, serverless, rootEnabled) + ), + Promise.resolve().then(() => + requirePage(pathname, distDir, serverless, rootEnabled) + ), serverComponents - ? requirePage('/_app.server', distDir, serverless, rootEnabled) + ? Promise.resolve().then(() => + requirePage('/_app.server', distDir, serverless, rootEnabled) + ) : null, ]) diff --git a/packages/next/server/next-server.ts b/packages/next/server/next-server.ts index 47764d6a06cbb..3ce99c042e62b 100644 --- a/packages/next/server/next-server.ts +++ b/packages/next/server/next-server.ts @@ -126,7 +126,7 @@ export default class NextNodeServer extends BaseServer { ) } - if (!this.renderOpts.dev) { + if (!options.dev) { // pre-warm _document and _app as these will be // needed for most requests loadComponents(this.distDir, '/_document', this._isLikeServerless).catch( From 4fb0beb1ee372d56128db16ec02ff88e333d4b2a Mon Sep 17 00:00:00 2001 From: Jiachi Liu Date: Thu, 5 May 2022 20:53:51 +0200 Subject: [PATCH 05/15] fix: duplicate app server (#36710) Fixes #36659 `App` is alreay included in `ServerComponentWrapper` ## Bug - [x] Related issues linked using `fixes #number` - [x] Integration tests added - [ ] Errors have helpful link attached, see `contributing.md` --- packages/next/server/render.tsx | 22 +++++++++---------- .../switchable-runtime/pages/_app.js | 7 ++++++ .../test/switchable-runtime.test.js | 16 +++++++++----- 3 files changed, 28 insertions(+), 17 deletions(-) create mode 100644 test/integration/react-streaming-and-server-components/switchable-runtime/pages/_app.js diff --git a/packages/next/server/render.tsx b/packages/next/server/render.tsx index f38083b00d1ba..ca0b4c5111b36 100644 --- a/packages/next/server/render.tsx +++ b/packages/next/server/render.tsx @@ -208,7 +208,6 @@ function renderPageTree( isServerComponent: boolean ) { const { router: _, ...rest } = props - if (isServerComponent) { return ( @@ -400,7 +399,7 @@ const useFlightResponse = createFlightHook() // Create the wrapper component for a Flight stream. function createServerComponentRenderer( App: any, - ComponentMod: any, + Component: any, { cachePrefix, inlinedTransformStream, @@ -413,12 +412,6 @@ function createServerComponentRenderer( serverComponentManifest: NonNullable } ) { - // We need to expose the `__webpack_require__` API globally for - // react-server-dom-webpack. This is a hack until we find a better way. - // @ts-ignore - globalThis.__webpack_require__ = ComponentMod.__next_rsc__.__webpack_require__ - const Component = interopDefault(ComponentMod) - function ServerComponentWrapper({ router, ...props }: any) { const id = (React as any).useId() @@ -527,7 +520,13 @@ export async function renderToHTML( if (isServerComponent) { serverComponentsInlinedTransformStream = new TransformStream() const search = urlQueryToSearchParams(query).toString() - Component = createServerComponentRenderer(App, ComponentMod, { + // We need to expose the `__webpack_require__` API globally for + // react-server-dom-webpack. This is a hack until we find a better way. + // @ts-ignore + globalThis.__webpack_require__ = + ComponentMod.__next_rsc__.__webpack_require__ + + Component = createServerComponentRenderer(App, Component, { cachePrefix: pathname + (search ? `?${search}` : ''), inlinedTransformStream: serverComponentsInlinedTransformStream, staticTransformStream: serverComponentsPageDataTransformStream, @@ -1257,7 +1256,7 @@ export async function renderToHTML( ...props.pageProps, ...serverComponentProps, }, - isServerComponent + true ), serverComponentManifest ).pipeThrough(createBufferedTransformStream()) @@ -1425,7 +1424,8 @@ export async function renderToHTML( {renderPageTree( - EnhancedApp, + // AppServer is included in the EnhancedComponent in ServerComponentWrapper + isServerComponent ? React.Fragment : EnhancedApp, EnhancedComponent, { ...(isServerComponent ? props.pageProps : props), router }, isServerComponent diff --git a/test/integration/react-streaming-and-server-components/switchable-runtime/pages/_app.js b/test/integration/react-streaming-and-server-components/switchable-runtime/pages/_app.js new file mode 100644 index 0000000000000..57b0da8210940 --- /dev/null +++ b/test/integration/react-streaming-and-server-components/switchable-runtime/pages/_app.js @@ -0,0 +1,7 @@ +export default function App({ Component, pageProps }) { + return ( +
+ +
+ ) +} diff --git a/test/integration/react-streaming-and-server-components/test/switchable-runtime.test.js b/test/integration/react-streaming-and-server-components/test/switchable-runtime.test.js index eec271208a27f..a3e1f7b2c1ecf 100644 --- a/test/integration/react-streaming-and-server-components/test/switchable-runtime.test.js +++ b/test/integration/react-streaming-and-server-components/test/switchable-runtime.test.js @@ -38,12 +38,16 @@ async function testRoute(appPort, url, { isStatic, isEdge, isRSC }) { // Should be re-rendered. expect(renderedAt1).toBeLessThan(renderedAt2) } - const customAppServerHtml = '
{ From 6f90d196099de1637a4587bb3cf2792f2d57a63c Mon Sep 17 00:00:00 2001 From: Tim Neutkens Date: Thu, 5 May 2022 22:42:22 +0200 Subject: [PATCH 06/15] Add route loader (#36712) * Add route loader * Update to leverage new view-loader * fix lint Co-authored-by: JJ Kasper --- packages/next/build/entries.ts | 21 +++- .../build/webpack/loaders/next-view-loader.ts | 102 ++++++++++++++++-- packages/next/server/base-server.ts | 24 ----- packages/next/server/dev/hot-reloader.ts | 18 +++- packages/next/server/load-components.ts | 6 -- packages/next/server/view-render.tsx | 22 ++-- 6 files changed, 143 insertions(+), 50 deletions(-) diff --git a/packages/next/build/entries.ts b/packages/next/build/entries.ts index 1e0eacd258905..faf8f7224a6e4 100644 --- a/packages/next/build/entries.ts +++ b/packages/next/build/entries.ts @@ -285,6 +285,15 @@ export function getEdgeServerEntry(opts: { return `next-middleware-ssr-loader?${stringify(loaderParams)}!` } +export function getViewsEntry(opts: { pagePath: string; viewsDir: string }) { + const loaderParams = { + pagePath: opts.pagePath, + viewsDir: opts.viewsDir, + } + + return `next-view-loader?${stringify(loaderParams)}!` +} + export function getServerlessEntry(opts: { absolutePagePath: string buildId: string @@ -357,6 +366,11 @@ export async function createEntrypoints(params: CreateEntrypointsParams) { const getEntryHandler = (mappings: Record, isViews: boolean) => async (page: string) => { + // TODO: @timneutkens do not pass layouts to entry here + if (isViews && page.endsWith('/layout')) { + return + } + const bundleFile = normalizePagePath(page) const clientBundlePath = posix.join('pages', bundleFile) const serverBundlePath = posix.join( @@ -388,7 +402,12 @@ export async function createEntrypoints(params: CreateEntrypointsParams) { }) }, onServer: () => { - if (isTargetLikeServerless(target)) { + if (isViews && viewsDir) { + server[serverBundlePath] = getViewsEntry({ + pagePath: mappings[page], + viewsDir, + }) + } else if (isTargetLikeServerless(target)) { if (page !== '/_app' && page !== '/_document') { server[serverBundlePath] = getServerlessEntry({ ...params, diff --git a/packages/next/build/webpack/loaders/next-view-loader.ts b/packages/next/build/webpack/loaders/next-view-loader.ts index af12ebee00709..6085337c49b9f 100644 --- a/packages/next/build/webpack/loaders/next-view-loader.ts +++ b/packages/next/build/webpack/loaders/next-view-loader.ts @@ -1,17 +1,105 @@ +import path from 'path' import type webpack from 'webpack5' +import { NODE_RESOLVE_OPTIONS } from '../../webpack-config' + +function pathToUrlPath(pathname: string) { + let urlPath = pathname.replace(/^private-next-views-dir/, '') + + // For `views/layout.js` + if (urlPath === '') { + urlPath = '/' + } + + return urlPath +} + +async function resolveLayoutPathsByPage({ + pagePath, + resolve, +}: { + pagePath: string + resolve: (pathname: string) => Promise +}) { + const layoutPaths = new Map() + const parts = pagePath.split('/') + + for (let i = 1; i < parts.length; i++) { + const pathWithoutSlashLayout = parts.slice(0, i).join('/') + const layoutPath = `${pathWithoutSlashLayout}/layout` + + const resolvedLayoutPath = await resolve(layoutPath) + + let urlPath = pathToUrlPath(pathWithoutSlashLayout) + + layoutPaths.set(urlPath, resolvedLayoutPath) + } + + return layoutPaths +} + +const extensions = [ + ...NODE_RESOLVE_OPTIONS.extensions, + ...NODE_RESOLVE_OPTIONS.extensions.map((ext) => `.server${ext}`), + ...NODE_RESOLVE_OPTIONS.extensions.map((ext) => `.client${ext}`), +] +const resolveOptions: any = { + ...NODE_RESOLVE_OPTIONS, + extensions, +} const nextViewLoader: webpack.LoaderDefinitionFunction<{ - components: string[] -}> = function nextViewLoader() { + pagePath: string + viewsDir: string +}> = async function nextViewLoader() { const loaderOptions = this.getOptions() || {} + const resolve = this.getResolve(resolveOptions) + const viewsDir = loaderOptions.viewsDir - return ` - export const components = { - ${loaderOptions.components - .map((component) => `'${component}': () => import('${component}')`) - .join(',\n')} + const layoutPaths = await resolveLayoutPathsByPage({ + pagePath: loaderOptions.pagePath, + resolve: async (pathname) => { + try { + return await resolve(this.rootContext, pathname) + } catch (err: any) { + if (err.message.includes("Can't resolve")) { + return undefined + } + throw err + } + }, + }) + + const componentsCode = [] + for (const [layoutPath, resolvedLayoutPath] of layoutPaths) { + if (resolvedLayoutPath) { + this.addDependency(resolvedLayoutPath) + // use require so that we can bust the require cache + const codeLine = `'${layoutPath}': () => require('${resolvedLayoutPath}')` + componentsCode.push(codeLine) + } else { + for (const ext of extensions) { + this.addMissingDependency( + path.join(viewsDir, layoutPath, `layout${ext}`) + ) + } } + } + + // Add page itself to the list of components + componentsCode.push( + `'${pathToUrlPath(loaderOptions.pagePath).replace( + new RegExp(`/page\\.+(${extensions.join('|')})$`), + '' + // use require so that we can bust the require cache + )}': () => require('${loaderOptions.pagePath}')` + ) + + const result = ` + export const components = { + ${componentsCode.join(',\n')} + }; ` + return result } export default nextViewLoader diff --git a/packages/next/server/base-server.ts b/packages/next/server/base-server.ts index d80fd6fde3861..37070426f4291 100644 --- a/packages/next/server/base-server.ts +++ b/packages/next/server/base-server.ts @@ -1748,24 +1748,6 @@ export default abstract class Server { return null } - const gatherViewLayouts = async ( - viewPath: string, - result: FindComponentsResult - ): Promise => { - const layoutPaths = this.getViewPathLayouts(viewPath) - result.components.viewLayouts = await Promise.all( - layoutPaths.map(async (path) => { - const layoutRes = await this.findPageComponents(path) - return { - isRootLayout: path === '/layout', - Component: layoutRes?.components.Component!, - getStaticProps: layoutRes?.components.getStaticProps, - getServerSideProps: layoutRes?.components.getServerSideProps, - } - }) - ) - } - try { // Ensure a request to the URL /accounts/[id] will be treated as a dynamic // route correctly and not loaded immediately without parsing params. @@ -1778,9 +1760,6 @@ export default abstract class Server { const result = await this.findPageComponents(page, query) if (result) { try { - if (result.components.isViewPath) { - await gatherViewLayouts(page, result) - } return await this.renderToResponseWithComponents(ctx, result) } catch (err) { const isNoFallbackError = err instanceof NoFallbackError @@ -1812,9 +1791,6 @@ export default abstract class Server { ) if (dynamicRouteResult) { try { - if (dynamicRouteResult.components.isViewPath) { - await gatherViewLayouts(page, dynamicRouteResult) - } return await this.renderToResponseWithComponents( { ...ctx, diff --git a/packages/next/server/dev/hot-reloader.ts b/packages/next/server/dev/hot-reloader.ts index b0035377844b5..2fa7844e8f098 100644 --- a/packages/next/server/dev/hot-reloader.ts +++ b/packages/next/server/dev/hot-reloader.ts @@ -11,11 +11,16 @@ import { finalizeEntrypoint, getClientEntry, getEdgeServerEntry, + getViewsEntry, runDependingOnPageType, } from '../../build/entries' import { watchCompilers } from '../../build/output' import getBaseWebpackConfig from '../../build/webpack-config' -import { API_ROUTE, MIDDLEWARE_ROUTE } from '../../lib/constants' +import { + API_ROUTE, + MIDDLEWARE_ROUTE, + VIEWS_DIR_ALIAS, +} from '../../lib/constants' import { recursiveDelete } from '../../lib/recursive-delete' import { BLOCKED_PAGES } from '../../shared/lib/constants' import { __ApiPreviewProps } from '../api-utils' @@ -592,7 +597,16 @@ export default class HotReloader { entrypoints[bundlePath] = finalizeEntrypoint({ compilerType: 'server', name: bundlePath, - value: request, + value: + this.viewsDir && bundlePath.startsWith('views/') + ? getViewsEntry({ + pagePath: join( + VIEWS_DIR_ALIAS, + relative(this.viewsDir!, absolutePagePath) + ), + viewsDir: this.viewsDir!, + }) + : request, }) } }, diff --git a/packages/next/server/load-components.ts b/packages/next/server/load-components.ts index 7f44ccac93f1e..1bb854461630b 100644 --- a/packages/next/server/load-components.ts +++ b/packages/next/server/load-components.ts @@ -41,12 +41,6 @@ export type LoadComponentsReturnType = { AppMod: any AppServerMod: any isViewPath?: boolean - viewLayouts?: Array<{ - isRootLayout?: boolean - Component: NextComponentType - getStaticProps?: GetStaticProps - getServerSideProps?: GetServerSideProps - }> } export async function loadDefaultErrorComponents(distDir: string) { diff --git a/packages/next/server/view-render.tsx b/packages/next/server/view-render.tsx index d8bedf081b0fa..827e1a2a92e0f 100644 --- a/packages/next/server/view-render.tsx +++ b/packages/next/server/view-render.tsx @@ -216,13 +216,13 @@ export async function renderToHTML( const hasConcurrentFeatures = !!runtime const pageIsDynamic = isDynamicRoute(pathname) - const layouts = renderOpts.viewLayouts || [] - - layouts.push({ - Component: renderOpts.Component, - getStaticProps: renderOpts.getStaticProps, - getServerSideProps: renderOpts.getServerSideProps, - }) + const components = Object.keys(ComponentMod.components) + .sort() + .map((key) => { + const mod = ComponentMod.components[key]() + mod.Component = mod.default || mod + return mod + }) // Reads of this are cached on the `req` object, so this should resolve // instantly. There's no need to pass this data down from a previous @@ -239,11 +239,13 @@ export async function renderToHTML( const dataCache = new Map() - for (let i = layouts.length - 1; i >= 0; i--) { + for (let i = components.length - 1; i >= 0; i--) { const dataCacheKey = i.toString() - const layout = layouts[i] + const layout = components[i] - if (layout.isRootLayout) { + if (i === 0) { + // top-most layout is the root layout that renders + // the html/body tags RootLayout = layout.Component continue } From f6af0bc2d8edd30415d910ef9e239af99172debd Mon Sep 17 00:00:00 2001 From: Jiachi Liu Date: Fri, 6 May 2022 01:16:24 +0200 Subject: [PATCH 07/15] test: merge rsc custom app cases (#36713) the rsc & streaming test app is too big to build which is time consuming, move custom _app cases to switchable runtime test suite --- .../test/index.test.js | 25 ---------- .../test/switchable-runtime.test.js | 50 +++++++++++++++++-- .../test/utils.js | 1 - 3 files changed, 47 insertions(+), 29 deletions(-) diff --git a/test/integration/react-streaming-and-server-components/test/index.test.js b/test/integration/react-streaming-and-server-components/test/index.test.js index 2e4e62b8acd62..0ee2de4a0f784 100644 --- a/test/integration/react-streaming-and-server-components/test/index.test.js +++ b/test/integration/react-streaming-and-server-components/test/index.test.js @@ -5,7 +5,6 @@ import fs from 'fs-extra' import { fetchViaHTTP, - renderViaHTTP, nextBuild, runDevSuite, runProdSuite, @@ -15,7 +14,6 @@ import { appDir, nativeModuleTestAppDir, appPage, - appServerPage, error500Page, nextConfig, } from './utils' @@ -27,13 +25,6 @@ import basic from './basic' import runtime from './runtime' import { getNodeBuiltinModuleNotSupportedInEdgeRuntimeMessage } from 'next/dist/build/utils' -const rscAppPage = ` -import Container from '../components/container.server' -export default function App({children}) { - return {children} -} -` - const appWithGlobalCss = ` import '../styles.css' @@ -184,19 +175,6 @@ const nodejsRuntimeBasicSuite = { }, } -const customAppPageSuite = { - runTests: (context) => { - it('should render container in app', async () => { - const indexHtml = await renderViaHTTP(context.appPort, '/') - const indexFlight = await renderViaHTTP(context.appPort, '/?__flight__=1') - expect(indexHtml).toContain('container-server') - expect(indexFlight).toContain('container-server') - }) - }, - beforeAll: () => appServerPage.write(rscAppPage), - afterAll: () => appServerPage.delete(), -} - const cssSuite = { runTests: css, beforeAll: () => appPage.write(appWithGlobalCss), @@ -208,8 +186,5 @@ runProdSuite('Node.js runtime', appDir, nodejsRuntimeBasicSuite) runDevSuite('Edge runtime', appDir, edgeRuntimeBasicSuite) runProdSuite('Edge runtime', appDir, edgeRuntimeBasicSuite) -runDevSuite('Custom App', appDir, customAppPageSuite) -runProdSuite('Custom App', appDir, customAppPageSuite) - runDevSuite('CSS', appDir, cssSuite) runProdSuite('CSS', appDir, cssSuite) diff --git a/test/integration/react-streaming-and-server-components/test/switchable-runtime.test.js b/test/integration/react-streaming-and-server-components/test/switchable-runtime.test.js index a3e1f7b2c1ecf..f038bc168a19d 100644 --- a/test/integration/react-streaming-and-server-components/test/switchable-runtime.test.js +++ b/test/integration/react-streaming-and-server-components/test/switchable-runtime.test.js @@ -22,6 +22,48 @@ function splitLines(text) { .filter(Boolean) } +function getOccurrence(text, matcher) { + return (text.match(matcher) || []).length +} + +function flight(context) { + describe('flight response', () => { + it('should contain _app.server in flight response (node)', async () => { + const html = await renderViaHTTP(context.appPort, '/node-rsc') + const flightResponse = await renderViaHTTP( + context.appPort, + '/node-rsc?__flight__=1' + ) + expect( + getOccurrence(html, new RegExp(`class="app-server-root"`, 'g')) + ).toBe(1) + expect( + getOccurrence( + flightResponse, + new RegExp(`"className":\\s*"app-server-root"`, 'g') + ) + ).toBe(1) + }) + }) + + it('should contain _app.server in flight response (edge)', async () => { + const html = await renderViaHTTP(context.appPort, '/edge-rsc') + const flightResponse = await renderViaHTTP( + context.appPort, + '/edge-rsc?__flight__=1' + ) + expect( + getOccurrence(html, new RegExp(`class="app-server-root"`, 'g')) + ).toBe(1) + expect( + getOccurrence( + flightResponse, + new RegExp(`"className":\\s*"app-server-root"`, 'g') + ) + ).toBe(1) + }) +} + async function testRoute(appPort, url, { isStatic, isEdge, isRSC }) { const html1 = await renderViaHTTP(appPort, url) const renderedAt1 = +html1.match(/Time: (\d+)/)[1] @@ -44,9 +86,8 @@ async function testRoute(appPort, url, { isStatic, isEdge, isRSC }) { const [rootClass, oppositeRootClass] = isRSC ? [rootClasses[0], rootClasses[1]] : [rootClasses[1], rootClasses[0]] - const appServerOccurrence = - html1.match(new RegExp(`class="${rootClass}"`, 'g')) || [] - expect(appServerOccurrence.length).toBe(1) + + expect(getOccurrence(html1, new RegExp(`class="${rootClass}"`, 'g'))).toBe(1) expect(html1).not.toContain(`"${oppositeRootClass}"`) } @@ -67,6 +108,8 @@ describe('Switchable runtime (prod)', () => { await killApp(context.server) }) + flight(context) + it('should build /static as a static page with the nodejs runtime', async () => { await testRoute(context.appPort, '/static', { isStatic: true, @@ -281,6 +324,7 @@ describe('Switchable runtime (dev)', () => { await killApp(context.server) }) + flight(context) it('should support client side navigation to ssr rsc pages', async () => { let flightRequest = null diff --git a/test/integration/react-streaming-and-server-components/test/utils.js b/test/integration/react-streaming-and-server-components/test/utils.js index d1f19ed8e80fd..edfa21a5231ea 100644 --- a/test/integration/react-streaming-and-server-components/test/utils.js +++ b/test/integration/react-streaming-and-server-components/test/utils.js @@ -8,7 +8,6 @@ export const nativeModuleTestAppDir = join( '../unsupported-native-module' ) export const appPage = new File(join(appDir, 'pages/_app.js')) -export const appServerPage = new File(join(appDir, 'pages/_app.server.js')) export const error500Page = new File(join(appDir, 'pages/500.js')) export const nextConfig = new File(join(appDir, 'next.config.js')) From a1bb1c69ed158655a824bb4edb2690f8bf848527 Mon Sep 17 00:00:00 2001 From: Tim Neutkens Date: Fri, 6 May 2022 13:11:55 +0200 Subject: [PATCH 08/15] v12.1.7-canary.3 --- 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/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 | 14 +++++++------- packages/react-dev-overlay/package.json | 2 +- packages/react-refresh-utils/package.json | 2 +- 15 files changed, 22 insertions(+), 22 deletions(-) diff --git a/lerna.json b/lerna.json index def43d985a14c..880c0a0e8da54 100644 --- a/lerna.json +++ b/lerna.json @@ -16,5 +16,5 @@ "registry": "https://registry.npmjs.org/" } }, - "version": "12.1.7-canary.2" + "version": "12.1.7-canary.3" } diff --git a/packages/create-next-app/package.json b/packages/create-next-app/package.json index fc6c94ccb8158..18fdd35955372 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.7-canary.2", + "version": "12.1.7-canary.3", "keywords": [ "react", "next", diff --git a/packages/eslint-config-next/package.json b/packages/eslint-config-next/package.json index 3386e07675a7c..fbeceda394bcf 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.7-canary.2", + "version": "12.1.7-canary.3", "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.7-canary.2", + "@next/eslint-plugin-next": "12.1.7-canary.3", "@rushstack/eslint-patch": "^1.1.3", "@typescript-eslint/parser": "^5.21.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 a04257730c790..65fe921bfd010 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.7-canary.2", + "version": "12.1.7-canary.3", "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 fb16c23a6a085..79a04b70c1f04 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.7-canary.2", + "version": "12.1.7-canary.3", "main": "index.js", "license": "MIT", "repository": { diff --git a/packages/next-codemod/package.json b/packages/next-codemod/package.json index 75ff9fc1b5415..58e08ac058fc3 100644 --- a/packages/next-codemod/package.json +++ b/packages/next-codemod/package.json @@ -1,6 +1,6 @@ { "name": "@next/codemod", - "version": "12.1.7-canary.2", + "version": "12.1.7-canary.3", "license": "MIT", "dependencies": { "chalk": "4.1.0", diff --git a/packages/next-env/package.json b/packages/next-env/package.json index 5cc6906f67d6f..b96189e397aad 100644 --- a/packages/next-env/package.json +++ b/packages/next-env/package.json @@ -1,6 +1,6 @@ { "name": "@next/env", - "version": "12.1.7-canary.2", + "version": "12.1.7-canary.3", "keywords": [ "react", "next", diff --git a/packages/next-mdx/package.json b/packages/next-mdx/package.json index 5f226722c15dd..91dcfed36eaa7 100644 --- a/packages/next-mdx/package.json +++ b/packages/next-mdx/package.json @@ -1,6 +1,6 @@ { "name": "@next/mdx", - "version": "12.1.7-canary.2", + "version": "12.1.7-canary.3", "main": "index.js", "license": "MIT", "repository": { diff --git a/packages/next-plugin-storybook/package.json b/packages/next-plugin-storybook/package.json index 5207b41c7e87b..83a9bb9025422 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.7-canary.2", + "version": "12.1.7-canary.3", "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 ae673d685fe46..5dc7ebe0f285a 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.7-canary.2", + "version": "12.1.7-canary.3", "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 6d85b8b8b3f81..822faad5c3050 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.7-canary.2", + "version": "12.1.7-canary.3", "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 c2bd27437e9e3..7849d35af03f7 100644 --- a/packages/next-swc/package.json +++ b/packages/next-swc/package.json @@ -1,6 +1,6 @@ { "name": "@next/swc", - "version": "12.1.7-canary.2", + "version": "12.1.7-canary.3", "private": true, "scripts": { "build-native": "napi build --platform --cargo-name next_swc_napi native", diff --git a/packages/next/package.json b/packages/next/package.json index 5b1dc3be2409f..75d9acecdb145 100644 --- a/packages/next/package.json +++ b/packages/next/package.json @@ -1,6 +1,6 @@ { "name": "next", - "version": "12.1.7-canary.2", + "version": "12.1.7-canary.3", "description": "The React Framework", "main": "./dist/server/next.js", "license": "MIT", @@ -69,7 +69,7 @@ ] }, "dependencies": { - "@next/env": "12.1.7-canary.2", + "@next/env": "12.1.7-canary.3", "caniuse-lite": "^1.0.30001332", "postcss": "8.4.5", "styled-jsx": "5.0.2" @@ -117,11 +117,11 @@ "@hapi/accept": "5.0.2", "@napi-rs/cli": "2.4.4", "@napi-rs/triples": "1.1.0", - "@next/polyfill-module": "12.1.7-canary.2", - "@next/polyfill-nomodule": "12.1.7-canary.2", - "@next/react-dev-overlay": "12.1.7-canary.2", - "@next/react-refresh-utils": "12.1.7-canary.2", - "@next/swc": "12.1.7-canary.2", + "@next/polyfill-module": "12.1.7-canary.3", + "@next/polyfill-nomodule": "12.1.7-canary.3", + "@next/react-dev-overlay": "12.1.7-canary.3", + "@next/react-refresh-utils": "12.1.7-canary.3", + "@next/swc": "12.1.7-canary.3", "@peculiar/webcrypto": "1.3.1", "@taskr/clear": "1.1.0", "@taskr/esnext": "1.1.0", diff --git a/packages/react-dev-overlay/package.json b/packages/react-dev-overlay/package.json index 8bb0d4486bcdd..2e6d4eddbca85 100644 --- a/packages/react-dev-overlay/package.json +++ b/packages/react-dev-overlay/package.json @@ -1,6 +1,6 @@ { "name": "@next/react-dev-overlay", - "version": "12.1.7-canary.2", + "version": "12.1.7-canary.3", "description": "A development-only overlay for developing React applications.", "repository": { "url": "vercel/next.js", diff --git a/packages/react-refresh-utils/package.json b/packages/react-refresh-utils/package.json index 67c3dc9268362..7aec7ac3fef79 100644 --- a/packages/react-refresh-utils/package.json +++ b/packages/react-refresh-utils/package.json @@ -1,6 +1,6 @@ { "name": "@next/react-refresh-utils", - "version": "12.1.7-canary.2", + "version": "12.1.7-canary.3", "description": "An experimental package providing utilities for React Refresh.", "repository": { "url": "vercel/next.js", From e78c42c91b1d6cf985a58317b53c98c05d383f9f Mon Sep 17 00:00:00 2001 From: Tim Neutkens Date: Fri, 6 May 2022 18:11:11 +0200 Subject: [PATCH 09/15] Fix leftover todo in loader (#36734) --- packages/next/build/entries.ts | 5 ----- packages/next/build/index.ts | 3 ++- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/packages/next/build/entries.ts b/packages/next/build/entries.ts index faf8f7224a6e4..3adfc98268dfa 100644 --- a/packages/next/build/entries.ts +++ b/packages/next/build/entries.ts @@ -366,11 +366,6 @@ export async function createEntrypoints(params: CreateEntrypointsParams) { const getEntryHandler = (mappings: Record, isViews: boolean) => async (page: string) => { - // TODO: @timneutkens do not pass layouts to entry here - if (isViews && page.endsWith('/layout')) { - return - } - const bundleFile = normalizePagePath(page) const clientBundlePath = posix.join('pages', bundleFile) const serverBundlePath = posix.join( diff --git a/packages/next/build/index.ts b/packages/next/build/index.ts index 119a686c0be41..5884545a12ed5 100644 --- a/packages/next/build/index.ts +++ b/packages/next/build/index.ts @@ -318,10 +318,11 @@ export default async function build( .traceAsyncFn(() => recursiveReadDir( viewsDir, - new RegExp(`\\.(?:${config.pageExtensions.join('|')})$`) + new RegExp(`page\\.(?:${config.pageExtensions.join('|')})$`) ) ) } + // needed for static exporting since we want to replace with HTML // files From 49910ea9ac0e18b03f34d9b16ea67037e2f11bf7 Mon Sep 17 00:00:00 2001 From: Rich Haines Date: Sat, 7 May 2022 02:24:39 +0200 Subject: [PATCH 10/15] Updated the middleware api docs env section to remove dev and prod (#36739) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit …rding due to customer confusion This PR updates the middleware (edge functions) api docs environment section to remove the dev and prod wording due to customer confusion ## Bug - [ ] Related issues linked using `fixes #number` - [ ] Integration tests added - [ ] Errors have helpful link attached, see `contributing.md` ## Feature - [ ] Implements an existing feature request or RFC. Make sure the feature request has been accepted for implementation before opening a PR. - [ ] Related issues linked using `fixes #number` - [ ] 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` --- docs/api-reference/edge-runtime.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api-reference/edge-runtime.md b/docs/api-reference/edge-runtime.md index 3c722513acac3..5958f6d2dcd79 100644 --- a/docs/api-reference/edge-runtime.md +++ b/docs/api-reference/edge-runtime.md @@ -26,7 +26,7 @@ The Next.js Edge Runtime is based on standard Web APIs, which is used by [Middle ### Environment -- `process.env`: Holds an object with all environment variables for both production and development in the exact same way as any other page or API in Next.js +- `process.env`: Holds an object with all environment variables for both `next dev` and `next build` in the exact same way as any other page or API in Next.js ### Fetch From 51d962d60f19056eab4f1d6be44310b8f7aeaddd Mon Sep 17 00:00:00 2001 From: Tim Neutkens Date: Sat, 7 May 2022 15:37:14 +0200 Subject: [PATCH 11/15] Leverage pageExtensions for resolving in loader (#36747) ## Bug - [ ] Related issues linked using `fixes #number` - [ ] Integration tests added - [ ] Errors have helpful link attached, see `contributing.md` ## Feature - [ ] Implements an existing feature request or RFC. Make sure the feature request has been accepted for implementation before opening a PR. - [ ] Related issues linked using `fixes #number` - [ ] 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` --- packages/next/build/entries.ts | 26 +++++++++++++------ packages/next/build/index.ts | 1 + .../build/webpack/loaders/next-view-loader.ts | 26 ++++++++----------- packages/next/server/dev/hot-reloader.ts | 3 +++ 4 files changed, 33 insertions(+), 23 deletions(-) diff --git a/packages/next/build/entries.ts b/packages/next/build/entries.ts index 3adfc98268dfa..c93a660e4dd95 100644 --- a/packages/next/build/entries.ts +++ b/packages/next/build/entries.ts @@ -245,6 +245,7 @@ interface CreateEntrypointsParams { target: 'server' | 'serverless' | 'experimental-serverless-trace' viewsDir?: string viewPaths?: Record + pageExtensions: string[] } export function getEdgeServerEntry(opts: { @@ -285,13 +286,12 @@ export function getEdgeServerEntry(opts: { return `next-middleware-ssr-loader?${stringify(loaderParams)}!` } -export function getViewsEntry(opts: { pagePath: string; viewsDir: string }) { - const loaderParams = { - pagePath: opts.pagePath, - viewsDir: opts.viewsDir, - } - - return `next-view-loader?${stringify(loaderParams)}!` +export function getViewsEntry(opts: { + pagePath: string + viewsDir: string + pageExtensions: string[] +}) { + return `next-view-loader?${stringify(opts)}!` } export function getServerlessEntry(opts: { @@ -358,7 +358,16 @@ export function getClientEntry(opts: { } export async function createEntrypoints(params: CreateEntrypointsParams) { - const { config, pages, pagesDir, isDev, target, viewsDir, viewPaths } = params + const { + config, + pages, + pagesDir, + isDev, + target, + viewsDir, + viewPaths, + pageExtensions, + } = params const edgeServer: webpack5.EntryObject = {} const server: webpack5.EntryObject = {} const client: webpack5.EntryObject = {} @@ -401,6 +410,7 @@ export async function createEntrypoints(params: CreateEntrypointsParams) { server[serverBundlePath] = getViewsEntry({ pagePath: mappings[page], viewsDir, + pageExtensions, }) } else if (isTargetLikeServerless(target)) { if (page !== '/_app' && page !== '/_document') { diff --git a/packages/next/build/index.ts b/packages/next/build/index.ts index 5884545a12ed5..187ac67d8218f 100644 --- a/packages/next/build/index.ts +++ b/packages/next/build/index.ts @@ -376,6 +376,7 @@ export default async function build( target, viewsDir, viewPaths: mappedViewPaths, + pageExtensions: config.pageExtensions, }) ) diff --git a/packages/next/build/webpack/loaders/next-view-loader.ts b/packages/next/build/webpack/loaders/next-view-loader.ts index 6085337c49b9f..1f4ca331584fb 100644 --- a/packages/next/build/webpack/loaders/next-view-loader.ts +++ b/packages/next/build/webpack/loaders/next-view-loader.ts @@ -37,26 +37,22 @@ async function resolveLayoutPathsByPage({ return layoutPaths } -const extensions = [ - ...NODE_RESOLVE_OPTIONS.extensions, - ...NODE_RESOLVE_OPTIONS.extensions.map((ext) => `.server${ext}`), - ...NODE_RESOLVE_OPTIONS.extensions.map((ext) => `.client${ext}`), -] -const resolveOptions: any = { - ...NODE_RESOLVE_OPTIONS, - extensions, -} - const nextViewLoader: webpack.LoaderDefinitionFunction<{ pagePath: string viewsDir: string + pageExtensions: string[] }> = async function nextViewLoader() { - const loaderOptions = this.getOptions() || {} + const { viewsDir, pagePath, pageExtensions } = this.getOptions() || {} + + const extensions = pageExtensions.map((extension) => `.${extension}`) + const resolveOptions: any = { + ...NODE_RESOLVE_OPTIONS, + extensions, + } const resolve = this.getResolve(resolveOptions) - const viewsDir = loaderOptions.viewsDir const layoutPaths = await resolveLayoutPathsByPage({ - pagePath: loaderOptions.pagePath, + pagePath: pagePath, resolve: async (pathname) => { try { return await resolve(this.rootContext, pathname) @@ -87,11 +83,11 @@ const nextViewLoader: webpack.LoaderDefinitionFunction<{ // Add page itself to the list of components componentsCode.push( - `'${pathToUrlPath(loaderOptions.pagePath).replace( + `'${pathToUrlPath(pagePath).replace( new RegExp(`/page\\.+(${extensions.join('|')})$`), '' // use require so that we can bust the require cache - )}': () => require('${loaderOptions.pagePath}')` + )}': () => require('${pagePath}')` ) const result = ` diff --git a/packages/next/server/dev/hot-reloader.ts b/packages/next/server/dev/hot-reloader.ts index 2fa7844e8f098..8d8e7651f76ab 100644 --- a/packages/next/server/dev/hot-reloader.ts +++ b/packages/next/server/dev/hot-reloader.ts @@ -421,6 +421,7 @@ export default class HotReloader { pagesDir: this.pagesDir, previewMode: this.previewProps, target: 'server', + pageExtensions: this.config.pageExtensions, }) ) @@ -488,6 +489,7 @@ export default class HotReloader { pagesDir: this.pagesDir, previewMode: this.previewProps, target: 'server', + pageExtensions: this.config.pageExtensions, }) ).client, hasReactRoot: this.hasReactRoot, @@ -605,6 +607,7 @@ export default class HotReloader { relative(this.viewsDir!, absolutePagePath) ), viewsDir: this.viewsDir!, + pageExtensions: this.config.pageExtensions, }) : request, }) From a84672e63e1ffac0b0e9e04c6fe45628a88949d2 Mon Sep 17 00:00:00 2001 From: Jiachi Liu Date: Sat, 7 May 2022 20:45:40 +0200 Subject: [PATCH 12/15] Use react dom server node api to detect react root is enabled (#36749) x-ref: https://github.com/vercel/next.js/pull/36552#issuecomment-1120128946 x-ref: https://github.com/preactjs/next-plugin-preact/pull/59 `preact/compat` doesn't have `/server.browser` exports, to make it work with latest of next.js: * use `react-dom/server` to detect if it could opt-in streaming rendering by checking react 18 `renderToPipeableStream` API in short time fix. In long term `preact/compat`should support `/server.browser` that same with react 17. * Also filed a PR to `next-plugin-preact` to skip chunk-prepending to pages in edge compiler --- packages/next/bin/next.ts | 3 +-- packages/next/server/next.ts | 4 ++-- packages/next/server/view-render.tsx | 6 ++++-- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/packages/next/bin/next.ts b/packages/next/bin/next.ts index bb99c8ad64719..ad8597d917978 100755 --- a/packages/next/bin/next.ts +++ b/packages/next/bin/next.ts @@ -43,8 +43,7 @@ const args = arg( ) // Detect if react-dom is enabled streaming rendering mode -const shouldUseReactRoot = !!require('react-dom/server.browser') - .renderToReadableStream +const shouldUseReactRoot = !!require('react-dom/server').renderToPipeableStream // Version is inlined into the file using taskr build pipeline if (args['--version']) { diff --git a/packages/next/server/next.ts b/packages/next/server/next.ts index f2d46252a9a68..8469bf1b8bc1f 100644 --- a/packages/next/server/next.ts +++ b/packages/next/server/next.ts @@ -185,8 +185,8 @@ function createServer(options: NextServerOptions): NextServer { // Make sure env of custom server is overridden. // Use dynamic require to make sure it's executed in it's own context. - const ReactDOMServer = require('react-dom/server.browser') - const shouldUseReactRoot = !!ReactDOMServer.renderToReadableStream + const ReactDOMServer = require('react-dom/server') + const shouldUseReactRoot = !!ReactDOMServer.renderToPipeableStream if (shouldUseReactRoot) { ;(process.env as any).__NEXT_REACT_ROOT = 'true' } diff --git a/packages/next/server/view-render.tsx b/packages/next/server/view-render.tsx index 827e1a2a92e0f..ad8f2b1f52ff1 100644 --- a/packages/next/server/view-render.tsx +++ b/packages/next/server/view-render.tsx @@ -17,12 +17,14 @@ import { continueFromInitialStream, } from './node-web-streams-helper' import { FlushEffectsContext } from '../shared/lib/flush-effects' -// @ts-ignore react-dom/client exists when using React 18 -import ReactDOMServer from 'react-dom/server.browser' import { isDynamicRoute } from '../shared/lib/router/utils' import { tryGetPreviewData } from './api-utils/node' import DefaultRootLayout from '../lib/views-layout' +const ReactDOMServer = process.env.__NEXT_REACT_ROOT + ? require('react-dom/server.browser') + : require('react-dom/server') + export type RenderOptsPartial = { err?: Error | null dev?: boolean From 3fd1168504151495e7b1be81bc48d4097107ed9a Mon Sep 17 00:00:00 2001 From: Steven Tey Date: Sat, 7 May 2022 13:50:31 -0700 Subject: [PATCH 13/15] Fixed typo (#36753) ## Bug - [ ] Related issues linked using `fixes #number` - [ ] Integration tests added - [ ] Errors have helpful link attached, see `contributing.md` ## Feature - [ ] Implements an existing feature request or RFC. Make sure the feature request has been accepted for implementation before opening a PR. - [ ] Related issues linked using `fixes #number` - [ ] 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` --- docs/basic-features/data-fetching/client-side.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/basic-features/data-fetching/client-side.md b/docs/basic-features/data-fetching/client-side.md index 6b1768a6e901b..d738cbd2d742d 100644 --- a/docs/basic-features/data-fetching/client-side.md +++ b/docs/basic-features/data-fetching/client-side.md @@ -21,7 +21,7 @@ function Profile() { useEffect(() => { setLoading(true) - fetch('api/profile-data') + fetch('/api/profile-data') .then((res) => res.json()) .then((data) => { setData(data) From 26459ef097b0dc9bae43d414a36e3c1eaa30aac1 Mon Sep 17 00:00:00 2001 From: Sukka Date: Sun, 8 May 2022 20:19:33 +0800 Subject: [PATCH 14/15] replace use-subscription with use-sync-external-store (#36733) - [x] Make sure the linting passes by running `yarn lint` Back in 2019, React released the first version of `use-subscription` (https://github.com/facebook/react/pull/15022). At the time, we only has limited information about concurrent rendering, and #9026 add the initial concurrent mode support. In 2020, React provides a first-party official API `useMutableSource` (https://github.com/reactjs/rfcs/pull/147, https://github.com/facebook/react/pull/18000): > ... enables React components to safely and efficiently read from a mutable external source in Concurrent Mode. React 18 introduces `useMutableSource`'s replacement `useSyncExternalStore` (see details here: https://github.com/reactwg/react-18/discussions/86), and React changes `use-subscription` implementation to use `useSyncExternalStore` directly: https://github.com/facebook/react/pull/24289 > In React 18, `React.useSyncExternalStore` is a built-in replacement for `useSubscription`. > > This PR makes `useSubscription` simply use `React.useSyncExternalStore` when available. For pre-18, it uses a `use-sync-external-store` shim which is very similar in `use-subscription` but fixes some flaws with concurrent rendering. And according to `use-subscription`: > You may now migrate to [`use-sync-external-store`](https://www.npmjs.com/package/use-sync-external-store) directly instead, which has the same API as `React.useSyncExternalStore`. The `use-subscription` package is now a thin wrapper over `use-sync-external-store` and will not be updated further. The PR does exactly that: - Removes the precompiled `use-subscription` introduced in #35746 - Adds the `use-sync-external-store` to the dependencies. - The `use-sync-external-store` package enables compatibility with React 16 and React 17. - Do not pre-compile `use-sync-external-store` since it is also the dependency of some popular React state management libraries like `react-redux`, `zustand`, `valtio`, `@xstate/react` and `@apollo/client`, etc. By install - Replace `useSubscription` usage with `useSyncExternalStore` --- Ref: #9026, #35746 and #36159 Co-authored-by: Jiachi Liu <4800338+huozhi@users.noreply.github.com> --- .../next/compiled/use-subscription/LICENSE | 21 ---------------- .../next/compiled/use-subscription/index.js | 25 ------------------- .../compiled/use-subscription/package.json | 1 - packages/next/package.json | 4 +-- packages/next/shared/lib/loadable.js | 11 ++++++-- packages/next/taskfile.js | 19 -------------- yarn.lock | 10 +++----- 7 files changed, 15 insertions(+), 76 deletions(-) delete mode 100644 packages/next/compiled/use-subscription/LICENSE delete mode 100644 packages/next/compiled/use-subscription/index.js delete mode 100644 packages/next/compiled/use-subscription/package.json diff --git a/packages/next/compiled/use-subscription/LICENSE b/packages/next/compiled/use-subscription/LICENSE deleted file mode 100644 index b96dcb0480a0b..0000000000000 --- a/packages/next/compiled/use-subscription/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) Facebook, Inc. and its affiliates. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/packages/next/compiled/use-subscription/index.js b/packages/next/compiled/use-subscription/index.js deleted file mode 100644 index 94087da0e39de..0000000000000 --- a/packages/next/compiled/use-subscription/index.js +++ /dev/null @@ -1,25 +0,0 @@ -(function(){"use strict";var e={800:function(e){ -/* -object-assign -(c) Sindre Sorhus -@license MIT -*/ -var r=Object.getOwnPropertySymbols;var t=Object.prototype.hasOwnProperty;var u=Object.prototype.propertyIsEnumerable;function toObject(e){if(e===null||e===undefined){throw new TypeError("Object.assign cannot be called with null or undefined")}return Object(e)}function shouldUseNative(){try{if(!Object.assign){return false}var e=new String("abc");e[5]="de";if(Object.getOwnPropertyNames(e)[0]==="5"){return false}var r={};for(var t=0;t<10;t++){r["_"+String.fromCharCode(t)]=t}var u=Object.getOwnPropertyNames(r).map((function(e){return r[e]}));if(u.join("")!=="0123456789"){return false}var n={};"abcdefghijklmnopqrst".split("").forEach((function(e){n[e]=e}));if(Object.keys(Object.assign({},n)).join("")!=="abcdefghijklmnopqrst"){return false}return true}catch(e){return false}}e.exports=shouldUseNative()?Object.assign:function(e,n){var a;var i=toObject(e);var s;for(var c=1;c= 3.1.0", @@ -260,7 +261,6 @@ "tty-browserify": "0.0.1", "ua-parser-js": "0.7.28", "unistore": "3.4.1", - "use-subscription": "1.5.1", "util": "0.12.4", "uuid": "8.3.2", "vm-browserify": "1.1.2", diff --git a/packages/next/shared/lib/loadable.js b/packages/next/shared/lib/loadable.js index e4591e4af14a6..06df00dacf554 100644 --- a/packages/next/shared/lib/loadable.js +++ b/packages/next/shared/lib/loadable.js @@ -22,7 +22,8 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE // Modified to be compatible with webpack 4 / Next.js import React from 'react' -import { useSubscription } from 'next/dist/compiled/use-subscription' +import { useSyncExternalStore } from 'use-sync-external-store/shim' + import { LoadableContext } from './loadable-context' const ALL_INITIALIZERS = [] @@ -75,6 +76,7 @@ function createLoadableComponent(loadFn, options) { opts.lazy = React.lazy(opts.loader) } + /** @type LoadableSubscription */ let subscription = null function init() { if (!subscription) { @@ -116,7 +118,12 @@ function createLoadableComponent(loadFn, options) { init() const context = React.useContext(LoadableContext) - const state = useSubscription(subscription) + const state = useSyncExternalStore( + subscription.subscribe, + subscription.getCurrentValue, + subscription.getCurrentValue + ) + React.useImperativeHandle( ref, () => ({ diff --git a/packages/next/taskfile.js b/packages/next/taskfile.js index 3edcde3e5a6a6..58e0e94f4c8a4 100644 --- a/packages/next/taskfile.js +++ b/packages/next/taskfile.js @@ -306,24 +306,6 @@ export async function ncc_react_refresh_utils(task, opts) { } } -// eslint-disable-next-line camelcase -export async function ncc_use_subscription(task, opts) { - await task - .source( - opts.src || relative(__dirname, require.resolve('use-subscription')) - ) - .ncc({ - packageName: 'use-subscription', - externals: { - ...externals, - react: 'react', - 'react-dom': 'react-dom', - }, - target: 'es5', - }) - .target('compiled/use-subscription') -} - // eslint-disable-next-line camelcase externals['chalk'] = 'next/dist/compiled/chalk' export async function ncc_chalk(task, opts) { @@ -1652,7 +1634,6 @@ export async function ncc(task, opts) { 'ncc_node_html_parser', 'ncc_watchpack', 'ncc_chalk', - 'ncc_use_subscription', 'ncc_napirs_triples', 'ncc_etag', 'ncc_p_limit', diff --git a/yarn.lock b/yarn.lock index 3cae0cb367a2f..d949c8074ba6c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -21248,12 +21248,10 @@ url@^0.11.0: punycode "1.3.2" querystring "0.2.0" -use-subscription@1.5.1: - version "1.5.1" - resolved "https://registry.yarnpkg.com/use-subscription/-/use-subscription-1.5.1.tgz#73501107f02fad84c6dd57965beb0b75c68c42d1" - integrity sha512-Xv2a1P/yReAjAbhylMfFplFKj9GssgTwN7RlcTxBujFQcloStWNDQdc4g4NRWH9xS4i/FDk04vQBptAXoF3VcA== - dependencies: - object-assign "^4.1.1" +use-sync-external-store@1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.1.0.tgz#3343c3fe7f7e404db70f8c687adf5c1652d34e82" + integrity sha512-SEnieB2FPKEVne66NpXPd1Np4R1lTNKfjuy3XdIoPQKYBAFdzbzSZlSn1KJZUiihQLQC5Znot4SBz1EOTBwQAQ== use@^3.1.0: version "3.1.1" From 40e9891175d858e504c4f2fd2c42eb05ff22d9b0 Mon Sep 17 00:00:00 2001 From: Tim Neutkens Date: Sun, 8 May 2022 18:38:43 +0200 Subject: [PATCH 15/15] Add flight render starting point (#36760) ## Bug - [ ] Related issues linked using `fixes #number` - [ ] Integration tests added - [ ] Errors have helpful link attached, see `contributing.md` ## Feature - [ ] Implements an existing feature request or RFC. Make sure the feature request has been accepted for implementation before opening a PR. - [ ] Related issues linked using `fixes #number` - [ ] 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` --- packages/next/server/view-render.tsx | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/packages/next/server/view-render.tsx b/packages/next/server/view-render.tsx index ad8f2b1f52ff1..fbbac3d3151ec 100644 --- a/packages/next/server/view-render.tsx +++ b/packages/next/server/view-render.tsx @@ -219,9 +219,19 @@ export async function renderToHTML( const hasConcurrentFeatures = !!runtime const pageIsDynamic = isDynamicRoute(pathname) const components = Object.keys(ComponentMod.components) + .filter((path) => { + const { __flight__, __flight_router_path__: routerPath } = query + // Rendering part of the page is only allowed for flight data + if (__flight__ !== undefined && routerPath) { + // TODO: check the actual path + const pathLength = path.length + return pathLength >= routerPath.length + } + return true + }) .sort() - .map((key) => { - const mod = ComponentMod.components[key]() + .map((path) => { + const mod = ComponentMod.components[path]() mod.Component = mod.default || mod return mod })