From 19fbbeb2bb9be2766ae1ac2a58ab1999b53a8221 Mon Sep 17 00:00:00 2001 From: Jiachi Liu Date: Wed, 5 Oct 2022 00:16:44 +0200 Subject: [PATCH] Refactor app dir related flags (#41166) simplify the `appDir` passing down --- packages/next-plugin-storybook/preset.js | 2 +- packages/next/build/entries.ts | 8 ++--- packages/next/build/index.ts | 10 +++--- packages/next/build/jest/jest.ts | 7 +++- packages/next/build/swc/jest-transformer.js | 1 + packages/next/build/swc/options.js | 7 +++- packages/next/build/webpack-config.ts | 33 ++++++++++--------- .../build/webpack/config/blocks/css/index.ts | 12 +++---- .../config/blocks/css/loaders/client.ts | 6 ++-- .../config/blocks/css/loaders/font-loader.ts | 2 +- .../config/blocks/css/loaders/global.ts | 2 +- .../config/blocks/css/loaders/modules.ts | 2 +- packages/next/build/webpack/config/index.ts | 8 +++-- packages/next/build/webpack/config/utils.ts | 1 + .../loaders/next-edge-ssr-loader/index.ts | 2 +- .../loaders/next-edge-ssr-loader/render.ts | 3 +- .../build/webpack/loaders/next-swc-loader.js | 2 ++ .../webpack/plugins/middleware-plugin.ts | 2 +- packages/next/cli/next-lint.ts | 4 +-- packages/next/export/index.ts | 7 ++-- packages/next/lib/eslint/runLintCheck.ts | 5 ++- packages/next/lib/find-pages-dir.ts | 26 +++++++++------ packages/next/lib/verifyAndLint.ts | 16 +++++---- packages/next/server/base-server.ts | 7 ++-- packages/next/server/config-schema.ts | 3 -- packages/next/server/dev/hot-reloader.ts | 15 +++++---- packages/next/server/dev/next-dev-server.ts | 9 ++--- packages/next/server/next-server.ts | 19 +++++++---- packages/next/server/web-server.ts | 4 +++ 29 files changed, 132 insertions(+), 93 deletions(-) diff --git a/packages/next-plugin-storybook/preset.js b/packages/next-plugin-storybook/preset.js index 8a70445c8d13..0e83637c8fd4 100644 --- a/packages/next-plugin-storybook/preset.js +++ b/packages/next-plugin-storybook/preset.js @@ -6,8 +6,8 @@ const getWebpackConfig = require('next/dist/build/webpack-config').default const CWD = process.cwd() async function webpackFinal(config) { - const pagesDir = findPagesDir(CWD) const nextConfig = await loadConfig(PHASE_PRODUCTION_BUILD, CWD) + const pagesDir = findPagesDir(CWD, !!nextConfig.experimental.appDir) const nextWebpackConfig = await getWebpackConfig(CWD, { pagesDir, entrypoints: {}, diff --git a/packages/next/build/entries.ts b/packages/next/build/entries.ts index 6b0a6e0a3902..b01a29b94d89 100644 --- a/packages/next/build/entries.ts +++ b/packages/next/build/entries.ts @@ -164,7 +164,7 @@ export function getEdgeServerEntry(opts: { page: string pages: { [page: string]: string } middleware?: Partial - pagesType?: 'app' | 'pages' | 'root' + pagesType: 'app' | 'pages' | 'root' appDirLoader?: string }) { if (isMiddlewareFile(opts.page)) { @@ -526,13 +526,13 @@ export function finalizeEntrypoint({ compilerType, value, isServerComponent, - appDir, + hasAppDir, }: { compilerType?: CompilerNameValues name: string value: ObjectValue isServerComponent?: boolean - appDir?: boolean + hasAppDir?: boolean }): ObjectValue { const entry = typeof value !== 'object' || Array.isArray(value) @@ -575,7 +575,7 @@ export function finalizeEntrypoint({ name !== CLIENT_STATIC_FILES_RUNTIME_REACT_REFRESH ) { // TODO-APP: this is a temporary fix. @shuding is going to change the handling of server components - if (appDir && entry.import.includes('flight')) { + if (hasAppDir && entry.import.includes('flight')) { return { dependOn: CLIENT_STATIC_FILES_RUNTIME_MAIN_APP, ...entry, diff --git a/packages/next/build/index.ts b/packages/next/build/index.ts index 8533413b197d..c3b13bde241e 100644 --- a/packages/next/build/index.ts +++ b/packages/next/build/index.ts @@ -301,10 +301,8 @@ export default async function build( setGlobal('telemetry', telemetry) const publicDir = path.join(dir, 'public') - const { pages: pagesDir, appDir } = findPagesDir( - dir, - config.experimental.appDir - ) + const hasAppDir = !!config.experimental.appDir + const { pagesDir, appDir } = findPagesDir(dir, hasAppDir) const hasPublicDir = await fileExists(publicDir) @@ -396,7 +394,7 @@ export default async function build( config.experimental.cpus, config.experimental.workerThreads, telemetry, - !!config.experimental.appDir + hasAppDir ) }), ]) @@ -1990,7 +1988,7 @@ export default async function build( combinedPages.length > 0 || useStatic404 || useDefaultStatic500 || - config.experimental.appDir + hasAppDir ) { const staticGenerationSpan = nextBuildSpan.traceChild('static-generation') diff --git a/packages/next/build/jest/jest.ts b/packages/next/build/jest/jest.ts index 1dbd27da1037..cb4b50887e3a 100644 --- a/packages/next/build/jest/jest.ts +++ b/packages/next/build/jest/jest.ts @@ -64,14 +64,18 @@ export default function nextJest(options: { dir?: string } = {}) { let resolvedBaseUrl let isEsmProject = false let pagesDir: string | undefined + let hasServerComponents: boolean | undefined if (options.dir) { const resolvedDir = resolve(options.dir) - pagesDir = findPagesDir(resolvedDir).pages const packageConfig = loadClosestPackageJson(resolvedDir) isEsmProject = packageConfig.type === 'module' nextConfig = await getConfig(resolvedDir) + const hasAppDir = !!nextConfig.experimental.appDir + const findPagesDirResult = findPagesDir(resolvedDir, hasAppDir) + hasServerComponents = !!findPagesDirResult.appDir + pagesDir = findPagesDirResult.pagesDir setUpEnv(resolvedDir, nextConfig) // TODO: revisit when bug in SWC is fixed that strips `.css` const result = await loadJsConfig(resolvedDir, nextConfig) @@ -134,6 +138,7 @@ export default function nextJest(options: { dir?: string } = {}) { nextConfig, jsConfig, resolvedBaseUrl, + hasServerComponents, isEsmProject, pagesDir, }, diff --git a/packages/next/build/swc/jest-transformer.js b/packages/next/build/swc/jest-transformer.js index cc11890430ef..ac912dfd8fc7 100644 --- a/packages/next/build/swc/jest-transformer.js +++ b/packages/next/build/swc/jest-transformer.js @@ -48,6 +48,7 @@ module.exports = { jsConfig: inputOptions.jsConfig, resolvedBaseUrl: inputOptions.resolvedBaseUrl, pagesDir: inputOptions.pagesDir, + hasServerComponents: inputOptions.hasServerComponents, esm: isSupportEsm && isEsm(Boolean(inputOptions.isEsmProject), filename, jestConfig), diff --git a/packages/next/build/swc/options.js b/packages/next/build/swc/options.js index 71043d3bd003..6411fb427cba 100644 --- a/packages/next/build/swc/options.js +++ b/packages/next/build/swc/options.js @@ -34,6 +34,7 @@ function getBaseSWCOptions({ swcCacheDir, isServerLayer, relativeFilePathFromRoot, + hasServerComponents, }) { const parserConfig = getParserOptions({ filename, jsConfig }) const paths = jsConfig?.compilerOptions?.paths @@ -119,7 +120,7 @@ function getBaseSWCOptions({ modularizeImports: nextConfig?.experimental?.modularizeImports, relay: nextConfig?.compiler?.relay, emotion: getEmotionOptions(nextConfig, development), - serverComponents: nextConfig?.experimental?.appDir + serverComponents: hasServerComponents ? { isServer: !!isServerLayer, } @@ -180,6 +181,7 @@ export function getJestSWCOptions({ nextConfig, jsConfig, pagesDir, + hasServerComponents, // This is not passed yet as "paths" resolving needs a test first // resolvedBaseUrl, }) { @@ -191,6 +193,7 @@ export function getJestSWCOptions({ globalWindow: !isServer, nextConfig, jsConfig, + hasServerComponents, // resolvedBaseUrl, }) @@ -226,6 +229,7 @@ export function getLoaderSWCOptions({ supportedBrowsers, swcCacheDir, relativeFilePathFromRoot, + hasServerComponents, // This is not passed yet as "paths" resolving is handled by webpack currently. // resolvedBaseUrl, }) { @@ -240,6 +244,7 @@ export function getLoaderSWCOptions({ swcCacheDir, isServerLayer, relativeFilePathFromRoot, + hasServerComponents, }) const isNextDist = nextDistPath.test(filename) diff --git a/packages/next/build/webpack-config.ts b/packages/next/build/webpack-config.ts index 7cff337e20cc..99d48bf29f3a 100644 --- a/packages/next/build/webpack-config.ts +++ b/packages/next/build/webpack-config.ts @@ -33,9 +33,9 @@ import { execOnce } from '../shared/lib/utils' import { NextConfigComplete } from '../server/config-shared' import { finalizeEntrypoint } from './entries' import * as Log from './output/log' -import { build as buildConfiguration } from './webpack/config' +import { buildConfiguration } from './webpack/config' import MiddlewarePlugin, { - handleWebpackExtenalForEdgeRuntime, + handleWebpackExternalForEdgeRuntime, } from './webpack/plugins/middleware-plugin' import BuildManifestPlugin from './webpack/plugins/build-manifest-plugin' import { JsConfigPathsPlugin } from './webpack/plugins/jsconfig-paths-plugin' @@ -563,6 +563,10 @@ export default async function getBaseWebpackConfig( rewrites.afterFiles.length > 0 || rewrites.fallback.length > 0 + const hasAppDir = !!config.experimental.appDir + const hasConcurrentFeatures = hasReactRoot + const hasServerComponents = hasAppDir + // Only error in first one compiler (client) once if (isClient) { if (!hasReactRoot) { @@ -571,7 +575,7 @@ export default async function getBaseWebpackConfig( '`experimental.runtime` requires React 18 to be installed.' ) } - if (config.experimental.appDir) { + if (hasAppDir) { throw new Error( '`experimental.appDir` requires React 18 to be installed.' ) @@ -579,8 +583,6 @@ export default async function getBaseWebpackConfig( } } - const hasConcurrentFeatures = hasReactRoot - const hasServerComponents = !!config.experimental.appDir const disableOptimizedLoading = hasConcurrentFeatures ? true : config.experimental.disableOptimizedLoading @@ -654,6 +656,7 @@ export default async function getBaseWebpackConfig( pagesDir, cwd: dir, development: dev, + hasServerComponents, hasReactRefresh: dev && isClient, hasJsxRuntime: true, }, @@ -682,6 +685,7 @@ export default async function getBaseWebpackConfig( isServer: isNodeServer || isEdgeServer, rootDir: dir, pagesDir, + hasServerComponents, hasReactRefresh: dev && isClient, fileReading: config.experimental.swcFileReading, nextConfig: config, @@ -745,7 +749,7 @@ export default async function getBaseWebpackConfig( ) ) .replace(/\\/g, '/'), - ...(config.experimental.appDir + ...(hasAppDir ? { [CLIENT_STATIC_FILES_RUNTIME_MAIN_APP]: dev ? [ @@ -1215,7 +1219,7 @@ export default async function getBaseWebpackConfig( '{}', 'react-dom': '{}', }, - handleWebpackExtenalForEdgeRuntime, + handleWebpackExternalForEdgeRuntime, ] : []), ] @@ -1520,7 +1524,7 @@ export default async function getBaseWebpackConfig( }, module: { rules: [ - ...(config.experimental.appDir && !isClient && !isEdgeServer + ...(hasAppDir && !isClient && !isEdgeServer ? [ { issuerLayer: WEBPACK_LAYERS.server, @@ -1827,7 +1831,7 @@ export default async function getBaseWebpackConfig( appDir: dir, esmExternals: config.experimental.esmExternals, outputFileTracingRoot: config.experimental.outputFileTracingRoot, - appDirEnabled: !!config.experimental.appDir, + appDirEnabled: hasAppDir, } ), // Moment.js is an extremely popular library that bundles large locale files @@ -1872,7 +1876,7 @@ export default async function getBaseWebpackConfig( serverless: isLikeServerless, dev, isEdgeRuntime: isEdgeServer, - appDirEnabled: !!config.experimental.appDir, + appDirEnabled: hasAppDir, }), // MiddlewarePlugin should be after DefinePlugin so NEXT_PUBLIC_* // replacement is done before its process.env.* handling @@ -1888,7 +1892,7 @@ export default async function getBaseWebpackConfig( rewrites, isDevFallback, exportRuntime: hasConcurrentFeatures, - appDirEnabled: !!config.experimental.appDir, + appDirEnabled: hasAppDir, }), new ProfilingPlugin({ runWebpackSpan }), config.optimizeFonts && @@ -1919,9 +1923,7 @@ export default async function getBaseWebpackConfig( minimized: true, }, }), - !!config.experimental.appDir && - isClient && - new AppBuildManifestPlugin({ dev }), + hasAppDir && isClient && new AppBuildManifestPlugin({ dev }), hasServerComponents && (isClient ? new FlightManifestPlugin({ @@ -2200,6 +2202,7 @@ export default async function getBaseWebpackConfig( customAppFile: pagesDir ? new RegExp(escapeStringRegexp(path.join(pagesDir, `_app`))) : undefined, + hasAppDir, isDevelopment: dev, isServer: isNodeServer || isEdgeServer, isEdgeRuntime: isEdgeServer, @@ -2588,7 +2591,7 @@ export default async function getBaseWebpackConfig( value: entry[name], compilerType, name, - appDir: config.experimental.appDir, + hasAppDir, }) } diff --git a/packages/next/build/webpack/config/blocks/css/index.ts b/packages/next/build/webpack/config/blocks/css/index.ts index 9de0f420288c..bb8a8d8570c2 100644 --- a/packages/next/build/webpack/config/blocks/css/index.ts +++ b/packages/next/build/webpack/config/blocks/css/index.ts @@ -257,7 +257,7 @@ export const css = curry(async function css( // CSS Modules support must be enabled on the server and client so the class // names are available for SSR or Prerendering. - if (ctx.experimental.appDir && !ctx.isProduction) { + if (ctx.hasAppDir && !ctx.isProduction) { fns.push( loader({ oneOf: [ @@ -373,7 +373,7 @@ export const css = curry(async function css( ) } - if (!ctx.experimental.appDir) { + if (!ctx.hasAppDir) { // Throw an error for CSS Modules used outside their supported scope fns.push( loader({ @@ -393,7 +393,7 @@ export const css = curry(async function css( } if (ctx.isServer) { - if (ctx.experimental.appDir && !ctx.isProduction) { + if (ctx.hasAppDir && !ctx.isProduction) { fns.push( loader({ oneOf: [ @@ -420,7 +420,7 @@ export const css = curry(async function css( ) } } else { - if (ctx.experimental.appDir) { + if (ctx.hasAppDir) { fns.push( loader({ oneOf: [ @@ -552,7 +552,7 @@ export const css = curry(async function css( oneOf: [ markRemovable({ test: [regexCssGlobal, regexSassGlobal], - issuer: ctx.experimental.appDir + issuer: ctx.hasAppDir ? { // If it's inside the app dir, but not importing from a layout file, // throw an error. @@ -597,7 +597,7 @@ export const css = curry(async function css( } // Enable full mini-css-extract-plugin hmr for prod mode pages or app dir - if (ctx.isClient && (ctx.isProduction || ctx.experimental.appDir)) { + if (ctx.isClient && (ctx.isProduction || ctx.hasAppDir)) { // Extract CSS as CSS file(s) in the client-side production bundle. const MiniCssExtractPlugin = require('../../../plugins/mini-css-extract-plugin').default diff --git a/packages/next/build/webpack/config/blocks/css/loaders/client.ts b/packages/next/build/webpack/config/blocks/css/loaders/client.ts index 78d662643b46..be889904ebc7 100644 --- a/packages/next/build/webpack/config/blocks/css/loaders/client.ts +++ b/packages/next/build/webpack/config/blocks/css/loaders/client.ts @@ -1,16 +1,16 @@ import type { webpack } from 'next/dist/compiled/webpack/webpack' export function getClientStyleLoader({ - isAppDir, + hasAppDir, isDevelopment, assetPrefix, }: { - isAppDir: boolean + hasAppDir: boolean isDevelopment: boolean assetPrefix: string }): webpack.RuleSetUseItem { // Keep next-style-loader for development mode in `pages/` - if (isDevelopment && !isAppDir) { + if (isDevelopment && !hasAppDir) { return { loader: 'next-style-loader', options: { diff --git a/packages/next/build/webpack/config/blocks/css/loaders/font-loader.ts b/packages/next/build/webpack/config/blocks/css/loaders/font-loader.ts index c48efd1d7591..0da13432aba4 100644 --- a/packages/next/build/webpack/config/blocks/css/loaders/font-loader.ts +++ b/packages/next/build/webpack/config/blocks/css/loaders/font-loader.ts @@ -15,7 +15,7 @@ export function getFontLoader( // loader loaders.push( getClientStyleLoader({ - isAppDir: !!ctx.experimental.appDir, + hasAppDir: ctx.hasAppDir, isDevelopment: ctx.isDevelopment, assetPrefix: ctx.assetPrefix, }) diff --git a/packages/next/build/webpack/config/blocks/css/loaders/global.ts b/packages/next/build/webpack/config/blocks/css/loaders/global.ts index 6e4a4e6bbcf5..4f8c40205c05 100644 --- a/packages/next/build/webpack/config/blocks/css/loaders/global.ts +++ b/packages/next/build/webpack/config/blocks/css/loaders/global.ts @@ -16,7 +16,7 @@ export function getGlobalCssLoader( // loader loaders.push( getClientStyleLoader({ - isAppDir: !!ctx.experimental.appDir, + hasAppDir: ctx.hasAppDir, isDevelopment: ctx.isDevelopment, assetPrefix: ctx.assetPrefix, }) diff --git a/packages/next/build/webpack/config/blocks/css/loaders/modules.ts b/packages/next/build/webpack/config/blocks/css/loaders/modules.ts index 6f7c0937ed70..4048058dbf2d 100644 --- a/packages/next/build/webpack/config/blocks/css/loaders/modules.ts +++ b/packages/next/build/webpack/config/blocks/css/loaders/modules.ts @@ -16,7 +16,7 @@ export function getCssModuleLoader( // loader loaders.push( getClientStyleLoader({ - isAppDir: !!ctx.experimental.appDir, + hasAppDir: ctx.hasAppDir, isDevelopment: ctx.isDevelopment, assetPrefix: ctx.assetPrefix, }) diff --git a/packages/next/build/webpack/config/index.ts b/packages/next/build/webpack/config/index.ts index 201403bae8c2..bc51ff125371 100644 --- a/packages/next/build/webpack/config/index.ts +++ b/packages/next/build/webpack/config/index.ts @@ -1,14 +1,16 @@ import type { webpack } from 'next/dist/compiled/webpack/webpack' import type { NextConfigComplete } from '../../../server/config-shared' +import type { ConfigurationContext } from './utils' import { base } from './blocks/base' import { css } from './blocks/css' import { images } from './blocks/images' -import { ConfigurationContext, pipe } from './utils' +import { pipe } from './utils' -export async function build( +export async function buildConfiguration( config: webpack.Configuration, { + hasAppDir, supportedBrowsers, rootDirectory, customAppFile, @@ -23,6 +25,7 @@ export async function build( experimental, disableStaticImages, }: { + hasAppDir: boolean supportedBrowsers: string[] | undefined rootDirectory: string customAppFile: RegExp | undefined @@ -39,6 +42,7 @@ export async function build( } ): Promise { const ctx: ConfigurationContext = { + hasAppDir, supportedBrowsers, rootDirectory, customAppFile, diff --git a/packages/next/build/webpack/config/utils.ts b/packages/next/build/webpack/config/utils.ts index b8355ad52ef5..3c9092bf6ca0 100644 --- a/packages/next/build/webpack/config/utils.ts +++ b/packages/next/build/webpack/config/utils.ts @@ -2,6 +2,7 @@ import type { webpack } from 'next/dist/compiled/webpack/webpack' import type { NextConfigComplete } from '../../../server/config-shared' export type ConfigurationContext = { + hasAppDir: boolean supportedBrowsers: string[] | undefined rootDirectory: string customAppFile: RegExp | undefined diff --git a/packages/next/build/webpack/loaders/next-edge-ssr-loader/index.ts b/packages/next/build/webpack/loaders/next-edge-ssr-loader/index.ts index 19d649f50878..c9a9c12316c3 100644 --- a/packages/next/build/webpack/loaders/next-edge-ssr-loader/index.ts +++ b/packages/next/build/webpack/loaders/next-edge-ssr-loader/index.ts @@ -13,7 +13,7 @@ export type EdgeSSRLoaderQuery = { page: string stringifiedConfig: string appDirLoader?: string - pagesType?: 'app' | 'pages' | 'root' + pagesType: 'app' | 'pages' | 'root' sriEnabled: boolean hasFontLoaders: boolean } diff --git a/packages/next/build/webpack/loaders/next-edge-ssr-loader/render.ts b/packages/next/build/webpack/loaders/next-edge-ssr-loader/render.ts index 6210f458c776..6eb8ffa3f09b 100644 --- a/packages/next/build/webpack/loaders/next-edge-ssr-loader/render.ts +++ b/packages/next/build/webpack/loaders/next-edge-ssr-loader/render.ts @@ -32,7 +32,7 @@ export function getRender({ buildId, fontLoaderManifest, }: { - pagesType?: 'app' | 'pages' | 'root' + pagesType: 'app' | 'pages' | 'root' dev: boolean page: string appMod: any @@ -69,6 +69,7 @@ export function getRender({ minimalMode: true, webServerConfig: { page, + pagesType, extendRenderOpts: { buildId, runtime: SERVER_RUNTIME.edge, diff --git a/packages/next/build/webpack/loaders/next-swc-loader.js b/packages/next/build/webpack/loaders/next-swc-loader.js index 4826ab9156c0..959549747bde 100644 --- a/packages/next/build/webpack/loaders/next-swc-loader.js +++ b/packages/next/build/webpack/loaders/next-swc-loader.js @@ -46,6 +46,7 @@ async function loaderTransform(parentTrace, source, inputSourceMap) { jsConfig, supportedBrowsers, swcCacheDir, + hasServerComponents, } = loaderOptions const isPageFile = filename.startsWith(pagesDir) const relativeFilePathFromRoot = path.relative(rootDir, filename) @@ -63,6 +64,7 @@ async function loaderTransform(parentTrace, source, inputSourceMap) { supportedBrowsers, swcCacheDir, relativeFilePathFromRoot, + hasServerComponents, }) const programmaticOptions = { diff --git a/packages/next/build/webpack/plugins/middleware-plugin.ts b/packages/next/build/webpack/plugins/middleware-plugin.ts index 0d2908a9e46a..e0e206bb1154 100644 --- a/packages/next/build/webpack/plugins/middleware-plugin.ts +++ b/packages/next/build/webpack/plugins/middleware-plugin.ts @@ -868,7 +868,7 @@ export default class MiddlewarePlugin { } } -export async function handleWebpackExtenalForEdgeRuntime({ +export async function handleWebpackExternalForEdgeRuntime({ request, context, contextInfo, diff --git a/packages/next/cli/next-lint.ts b/packages/next/cli/next-lint.ts index 83f0a53bb9de..a678fa2f9b12 100755 --- a/packages/next/cli/next-lint.ts +++ b/packages/next/cli/next-lint.ts @@ -187,8 +187,9 @@ const nextLint: cliCommand = async (argv) => { const distDir = join(baseDir, nextConfig.distDir) const defaultCacheLocation = join(distDir, 'cache', 'eslint/') + const hasAppDir = !!nextConfig.experimental.appDir - runLintCheck(baseDir, pathsToLint, { + runLintCheck(baseDir, pathsToLint, hasAppDir, { lintDuringBuild: false, eslintOptions: eslintOptions(args, defaultCacheLocation), reportErrorsOnly: reportErrorsOnly, @@ -196,7 +197,6 @@ const nextLint: cliCommand = async (argv) => { formatter, outputFile, strict, - hasAppDir: !!nextConfig.experimental.appDir, }) .then(async (lintResults) => { const lintOutput = diff --git a/packages/next/export/index.ts b/packages/next/export/index.ts index 5cb8eb05842f..9650a5424806 100644 --- a/packages/next/export/index.ts +++ b/packages/next/export/index.ts @@ -147,6 +147,7 @@ export default async function exportApp( configuration?: NextConfigComplete ): Promise { const nextExportSpan = span.traceChild('next-export') + const hasAppDir = !!options.appPaths return nextExportSpan.traceAsyncFn(async () => { dir = resolve(dir) @@ -389,7 +390,7 @@ export default async function exportApp( nextScriptWorkers: nextConfig.experimental.nextScriptWorkers, optimizeFonts: nextConfig.optimizeFonts as FontConfig, largePageDataBytes: nextConfig.experimental.largePageDataBytes, - serverComponents: !!nextConfig.experimental.appDir, + serverComponents: hasAppDir, fontLoaderManifest: nextConfig.experimental.fontLoaders ? require(join(distDir, 'server', `${FONT_LOADER_MANIFEST}.json`)) : undefined, @@ -422,7 +423,7 @@ export default async function exportApp( return exportMap }) - if (options.buildExport && nextConfig.experimental.appDir) { + if (options.buildExport && hasAppDir) { // @ts-expect-error untyped renderOpts.serverComponentManifest = require(join( distDir, @@ -617,7 +618,7 @@ export default async function exportApp( nextConfig.experimental.disableOptimizedLoading, parentSpanId: pageExportSpan.id, httpAgentOptions: nextConfig.httpAgentOptions, - serverComponents: !!nextConfig.experimental.appDir, + serverComponents: hasAppDir, appPaths: options.appPaths || [], enableUndici: nextConfig.experimental.enableUndici, }) diff --git a/packages/next/lib/eslint/runLintCheck.ts b/packages/next/lib/eslint/runLintCheck.ts index 6e735741dcd2..96e9118e6fcb 100644 --- a/packages/next/lib/eslint/runLintCheck.ts +++ b/packages/next/lib/eslint/runLintCheck.ts @@ -185,7 +185,7 @@ async function lint( } } - const pagesDir = findPagesDir(baseDir, hasAppDir).pages + const pagesDir = findPagesDir(baseDir, hasAppDir).pagesDir const pagesDirRules = pagesDir ? ['@next/next/no-html-link-for-pages'] : [] if (nextEslintPluginIsEnabled) { @@ -277,6 +277,7 @@ async function lint( export async function runLintCheck( baseDir: string, lintDirs: string[], + hasAppDir: boolean, opts: { lintDuringBuild?: boolean eslintOptions?: any @@ -285,7 +286,6 @@ export async function runLintCheck( formatter?: string | null outputFile?: string | null strict?: boolean - hasAppDir: boolean } ): ReturnType { const { @@ -296,7 +296,6 @@ export async function runLintCheck( formatter = null, outputFile = null, strict = false, - hasAppDir, } = opts try { // Find user's .eslintrc file diff --git a/packages/next/lib/find-pages-dir.ts b/packages/next/lib/find-pages-dir.ts index 2edec19c3f99..0de35966f561 100644 --- a/packages/next/lib/find-pages-dir.ts +++ b/packages/next/lib/find-pages-dir.ts @@ -23,21 +23,27 @@ function findDir(dir: string, name: 'pages' | 'app'): string | null { export function findPagesDir( dir: string, - appDirEnabled?: boolean -): { pages: string | undefined; appDir: string | undefined } { + isAppDirEnabled: boolean +): { + pagesDir: string | undefined + appDir: string | undefined +} { const pagesDir = findDir(dir, 'pages') || undefined let appDir: undefined | string - if (appDirEnabled) { + if (isAppDirEnabled) { appDir = findDir(dir, 'app') || undefined - if (appDirEnabled == null && pagesDir == null) { - throw new Error( - "> Couldn't find any `pages` or `app` directory. Please create one under the project root" - ) - } + } + const hasAppDir = + !!appDir && fs.existsSync(appDir) && fs.statSync(appDir).isDirectory() + + if (hasAppDir && appDir == null && pagesDir == null) { + throw new Error( + "> Couldn't find any `pages` or `app` directory. Please create one under the project root" + ) } - if (!appDirEnabled) { + if (!isAppDirEnabled) { if (pagesDir == null) { throw new Error( "> Couldn't find a `pages` directory. Please create one under the project root" @@ -46,7 +52,7 @@ export function findPagesDir( } return { - pages: pagesDir, + pagesDir, appDir, } } diff --git a/packages/next/lib/verifyAndLint.ts b/packages/next/lib/verifyAndLint.ts index b5e47d423f82..40da86f39ba2 100644 --- a/packages/next/lib/verifyAndLint.ts +++ b/packages/next/lib/verifyAndLint.ts @@ -39,13 +39,17 @@ export async function verifyAndLint( [] ) - const lintResults = await lintWorkers.runLintCheck(dir, lintDirs, { + const lintResults = await lintWorkers.runLintCheck( + dir, + lintDirs, hasAppDir, - lintDuringBuild: true, - eslintOptions: { - cacheLocation, - }, - }) + { + lintDuringBuild: true, + eslintOptions: { + cacheLocation, + }, + } + ) const lintOutput = typeof lintResults === 'string' ? lintResults : lintResults?.output diff --git a/packages/next/server/base-server.ts b/packages/next/server/base-server.ts index d26be948965d..2735dd3382da 100644 --- a/packages/next/server/base-server.ts +++ b/packages/next/server/base-server.ts @@ -185,6 +185,7 @@ export default abstract class Server { protected distDir: string protected publicDir: string protected hasStaticDir: boolean + protected hasAppDir: boolean protected pagesManifest?: PagesManifest protected appPathsManifest?: PagesManifest protected buildId: string @@ -237,6 +238,7 @@ export default abstract class Server { protected abstract getPublicDir(): string protected abstract getHasStaticDir(): boolean + protected abstract getHasAppDir(): boolean protected abstract getPagesManifest(): PagesManifest | undefined protected abstract getAppPathsManifest(): PagesManifest | undefined protected abstract getBuildId(): string @@ -353,6 +355,7 @@ export default abstract class Server { : require('path').join(this.dir, this.nextConfig.distDir) this.publicDir = this.getPublicDir() this.hasStaticDir = !minimalMode && this.getHasStaticDir() + this.hasAppDir = this.getHasAppDir() // Only serverRuntimeConfig needs the default // publicRuntimeConfig gets it's default in client/index.js @@ -366,7 +369,7 @@ export default abstract class Server { this.buildId = this.getBuildId() this.minimalMode = minimalMode || !!process.env.NEXT_PRIVATE_MINIMAL_MODE - const serverComponents = !!this.nextConfig.experimental.appDir + const serverComponents = this.hasAppDir this.serverComponentManifest = serverComponents ? this.getServerComponentManifest() : undefined @@ -1588,7 +1591,7 @@ export default abstract class Server { // map the route to the actual bundle name protected getOriginalAppPaths(route: string) { - if (this.nextConfig.experimental.appDir) { + if (this.hasAppDir) { const originalAppPath = this.appPathRoutes?.[route] if (!originalAppPath) { diff --git a/packages/next/server/config-schema.ts b/packages/next/server/config-schema.ts index b0fef4eeb818..745148e3ebcb 100644 --- a/packages/next/server/config-schema.ts +++ b/packages/next/server/config-schema.ts @@ -240,9 +240,6 @@ const configSchema = { }, type: 'object', }, - appDir: { - type: 'boolean', - }, browsersListForSwc: { type: 'boolean', }, diff --git a/packages/next/server/dev/hot-reloader.ts b/packages/next/server/dev/hot-reloader.ts index 0718a3633e19..84314c211b96 100644 --- a/packages/next/server/dev/hot-reloader.ts +++ b/packages/next/server/dev/hot-reloader.ts @@ -217,7 +217,7 @@ export default class HotReloader { this.config = config this.hasReactRoot = !!process.env.__NEXT_REACT_ROOT - this.hasServerComponents = this.hasReactRoot && !!config.experimental.appDir + this.hasServerComponents = this.hasReactRoot && !!this.appDir this.previewProps = previewProps this.rewrites = rewrites this.hotReloaderSpan = trace('hot-reloader', undefined, { @@ -595,7 +595,8 @@ export default class HotReloader { } } - const isAppPath = !!this.appDir && bundlePath.startsWith('app/') + const hasAppDir = !!this.appDir + const isAppPath = hasAppDir && bundlePath.startsWith('app/') const staticInfo = isEntry ? await getPageStaticInfo({ pageFilePath: entryData.absolutePagePath, @@ -643,9 +644,9 @@ export default class HotReloader { pages: this.pagesMapping, isServerComponent, appDirLoader, - pagesType: isAppPath ? 'app' : undefined, + pagesType: isAppPath ? 'app' : 'pages', }), - appDir: this.config.experimental.appDir, + hasAppDir, }) }, onClient: () => { @@ -656,7 +657,7 @@ export default class HotReloader { name: bundlePath, compilerType: COMPILER_NAMES.client, value: entryData.request, - appDir: this.config.experimental.appDir, + hasAppDir, }) } else { entries[entryKey].status = BUILDING @@ -667,7 +668,7 @@ export default class HotReloader { absolutePagePath: entryData.absolutePagePath, page, }), - appDir: this.config.experimental.appDir, + hasAppDir, }) } }, @@ -705,7 +706,7 @@ export default class HotReloader { pageExtensions: this.config.pageExtensions, }) : relativeRequest, - appDir: this.config.experimental.appDir, + hasAppDir, }) }, }) diff --git a/packages/next/server/dev/next-dev-server.ts b/packages/next/server/dev/next-dev-server.ts index edb45aa98284..8b4da6444666 100644 --- a/packages/next/server/dev/next-dev-server.ts +++ b/packages/next/server/dev/next-dev-server.ts @@ -190,11 +190,8 @@ export default class DevServer extends Server { } this.isCustomServer = !options.isNextDevCommand - // TODO: hot-reload root/pages dirs? - const { pages: pagesDir, appDir } = findPagesDir( - this.dir, - this.nextConfig.experimental.appDir - ) + + const { pagesDir, appDir } = findPagesDir(this.dir, this.hasAppDir) this.pagesDir = pagesDir this.appDir = appDir } @@ -1355,7 +1352,7 @@ export default class DevServer extends Server { // When the new page is compiled, we need to reload the server component // manifest. - if (this.nextConfig.experimental.appDir) { + if (!!this.appDir) { this.serverComponentManifest = super.getServerComponentManifest() this.serverCSSManifest = super.getServerCSSManifest() } diff --git a/packages/next/server/next-server.ts b/packages/next/server/next-server.ts index 68aab94a4ac7..54dced889513 100644 --- a/packages/next/server/next-server.ts +++ b/packages/next/server/next-server.ts @@ -286,7 +286,7 @@ export default class NextNodeServer extends BaseServer { fs: this.getCacheFilesystem(), dev, serverDistDir: this.serverDistDir, - appDir: this.nextConfig.experimental.appDir, + appDir: this.hasAppDir, maxMemoryCacheSize: this.nextConfig.experimental.isrMemoryCacheSize, flushToDisk: !this.minimalMode && this.nextConfig.experimental.isrFlushToDisk, @@ -323,7 +323,7 @@ export default class NextNodeServer extends BaseServer { } protected getAppPathsManifest(): PagesManifest | undefined { - if (this.nextConfig.experimental.appDir) { + if (this.hasAppDir) { const appPathsManifestPath = join(this.serverDistDir, APP_PATHS_MANIFEST) return require(appPathsManifestPath) } @@ -474,6 +474,13 @@ export default class NextNodeServer extends BaseServer { ] } + protected getHasAppDir(): boolean { + const appDirectory = join(this.dir, 'app') + return ( + fs.existsSync(appDirectory) && fs.statSync(appDirectory).isDirectory() + ) + } + protected generateStaticRoutes(): Route[] { return this.hasStaticDir ? [ @@ -823,7 +830,7 @@ export default class NextNodeServer extends BaseServer { renderOpts.serverCSSManifest = this.serverCSSManifest renderOpts.fontLoaderManifest = this.fontLoaderManifest - if (this.nextConfig.experimental.appDir && renderOpts.isAppPath) { + if (this.hasAppDir && renderOpts.isAppPath) { return appRenderToHTMLOrFlight( req.originalRequest, res.originalResponse, @@ -882,7 +889,7 @@ export default class NextNodeServer extends BaseServer { this._isLikeServerless, this.renderOpts.dev, locales, - this.nextConfig.experimental.appDir + this.hasAppDir ) } @@ -998,12 +1005,12 @@ export default class NextNodeServer extends BaseServer { } protected getServerComponentManifest() { - if (!this.nextConfig.experimental.appDir) return undefined + if (!this.hasAppDir) return undefined return require(join(this.distDir, 'server', FLIGHT_MANIFEST + '.json')) } protected getServerCSSManifest() { - if (!this.nextConfig.experimental.appDir) return undefined + if (!this.hasAppDir) return undefined return require(join( this.distDir, 'server', diff --git a/packages/next/server/web-server.ts b/packages/next/server/web-server.ts index 2c58740b41d9..664accbbaabb 100644 --- a/packages/next/server/web-server.ts +++ b/packages/next/server/web-server.ts @@ -31,6 +31,7 @@ import { getNamedRouteRegex } from '../shared/lib/router/utils/route-regex' interface WebServerOptions extends Options { webServerConfig: { page: string + pagesType: 'app' | 'pages' | 'root' loadComponent: ( pathname: string ) => Promise @@ -88,6 +89,9 @@ export default class NextWebServer extends BaseServer { // The web server does not need to load the env config. This is done by the // runtime already. } + protected getHasAppDir() { + return this.serverOptions.webServerConfig.pagesType === 'app' + } protected getHasStaticDir() { return false }