diff --git a/packages/next/build/entries.ts b/packages/next/build/entries.ts index 7181d6f8c971818..7aa7b83f6230e47 100644 --- a/packages/next/build/entries.ts +++ b/packages/next/build/entries.ts @@ -17,7 +17,7 @@ import { __ApiPreviewProps } from '../server/api-utils' import { isTargetLikeServerless } from '../server/utils' import { warn } from './output/log' import { parse } from '../build/swc' -import { isFlightPage, withoutRSCExtensions } from './utils' +import { isServerComponentPage, withoutRSCExtensions } from './utils' import { normalizePathSep } from '../shared/lib/page-path/normalize-path-sep' import { normalizePagePath } from '../shared/lib/page-path/normalize-page-path' @@ -251,7 +251,10 @@ export function getEdgeServerEntry(opts: { absolutePagePath: opts.absolutePagePath, buildId: opts.buildId, dev: opts.isDev, - isServerComponent: isFlightPage(opts.config, opts.absolutePagePath), + isServerComponent: isServerComponentPage( + opts.config, + opts.absolutePagePath + ), page: opts.page, stringifiedConfig: JSON.stringify(opts.config), } diff --git a/packages/next/build/index.ts b/packages/next/build/index.ts index e096a315d190628..4171a17f1983102 100644 --- a/packages/next/build/index.ts +++ b/packages/next/build/index.ts @@ -100,7 +100,7 @@ import { copyTracedFiles, isReservedPage, isCustomErrorPage, - isFlightPage, + isServerComponentPage, } from './utils' import getBaseWebpackConfig from './webpack-config' import { PagesManifest } from './webpack/plugins/pages-manifest-plugin' @@ -196,7 +196,11 @@ export default async function build( setGlobal('telemetry', telemetry) const publicDir = path.join(dir, 'public') - const pagesDir = findPagesDir(dir) + const { pages: pagesDir, root: rootDir } = findPagesDir( + dir, + config.experimental.rootDir + ) + const hasPublicDir = await fileExists(publicDir) telemetry.record( @@ -230,7 +234,7 @@ export default async function build( .traceAsyncFn(() => verifyTypeScriptSetup( dir, - pagesDir, + [pagesDir, rootDir].filter(Boolean) as string[], !ignoreTypeScriptErrors, config, cacheDir @@ -973,7 +977,7 @@ export default async function build( : undefined if (hasServerComponents && pagePath) { - if (isFlightPage(config, pagePath)) { + if (isServerComponentPage(config, pagePath)) { isServerComponent = true } } diff --git a/packages/next/build/utils.ts b/packages/next/build/utils.ts index 74e29050550be44..70e29d4f8b874a1 100644 --- a/packages/next/build/utils.ts +++ b/packages/next/build/utils.ts @@ -1101,7 +1101,7 @@ export function withoutRSCExtensions(pageExtensions: string[]): string[] { ) } -export function isFlightPage( +export function isServerComponentPage( nextConfig: NextConfigComplete, filePath: string ): boolean { diff --git a/packages/next/lib/eslint/runLintCheck.ts b/packages/next/lib/eslint/runLintCheck.ts index a7229a6f5911259..e994f8528680c22 100644 --- a/packages/next/lib/eslint/runLintCheck.ts +++ b/packages/next/lib/eslint/runLintCheck.ts @@ -171,7 +171,8 @@ async function lint( } } - const pagesDir = findPagesDir(baseDir) + // TODO: should we apply these rules to "root" dir as well? + const pagesDir = findPagesDir(baseDir).pages if (nextEslintPluginIsEnabled) { let updatedPagesDir = false diff --git a/packages/next/lib/find-pages-dir.ts b/packages/next/lib/find-pages-dir.ts index a4acde163903835..862849fd00dec1c 100644 --- a/packages/next/lib/find-pages-dir.ts +++ b/packages/next/lib/find-pages-dir.ts @@ -10,22 +10,37 @@ export const existsSync = (f: string): boolean => { } } -export function findPagesDir(dir: string): string { - // prioritize ./pages over ./src/pages - let curDir = path.join(dir, 'pages') +function findDir(dir: string, name: 'pages' | 'root'): string | null { + // prioritize ./${name} over ./src/${name} + let curDir = path.join(dir, name) if (existsSync(curDir)) return curDir - curDir = path.join(dir, 'src/pages') + curDir = path.join(dir, 'src', name) if (existsSync(curDir)) return curDir - // Check one level up the tree to see if the pages directory might be there - if (existsSync(path.join(dir, '..', 'pages'))) { + return null +} + +export function findPagesDir( + dir: string, + root?: boolean +): { pages: string; root?: string } { + const pagesDir = findDir(dir, 'pages') + let rootDir: undefined | string + + if (root) { + rootDir = findDir(dir, 'root') || undefined + } + + // TODO: allow "root" dir without pages dir + if (pagesDir === null) { throw new Error( - '> No `pages` directory found. Did you mean to run `next` in the parent (`../`) directory?' + "> Couldn't find a `pages` directory. Please create one under the project root" ) } - throw new Error( - "> Couldn't find a `pages` directory. Please create one under the project root" - ) + return { + pages: pagesDir, + root: rootDir, + } } diff --git a/packages/next/lib/typescript/getTypeScriptIntent.ts b/packages/next/lib/typescript/getTypeScriptIntent.ts index dee44e1b1f1e74a..a8aa7ab69fb80a7 100644 --- a/packages/next/lib/typescript/getTypeScriptIntent.ts +++ b/packages/next/lib/typescript/getTypeScriptIntent.ts @@ -8,7 +8,7 @@ export type TypeScriptIntent = { firstTimeSetup: boolean } export async function getTypeScriptIntent( baseDir: string, - pagesDir: string, + intentDirs: string[], config: NextConfigComplete ): Promise { const tsConfigPath = path.join(baseDir, config.typescript.tsconfigPath) @@ -28,13 +28,15 @@ export async function getTypeScriptIntent( // project for the user when we detect TypeScript files. So, we need to check // the `pages/` directory for a TypeScript file. // Checking all directories is too slow, so this is a happy medium. - const typescriptFiles = await recursiveReadDir( - pagesDir, - /.*\.(ts|tsx)$/, - /(node_modules|.*\.d\.ts)/ - ) - if (typescriptFiles.length) { - return { firstTimeSetup: true } + for (const dir of intentDirs) { + const typescriptFiles = await recursiveReadDir( + dir, + /.*\.(ts|tsx)$/, + /(node_modules|.*\.d\.ts)/ + ) + if (typescriptFiles.length) { + return { firstTimeSetup: true } + } } return false diff --git a/packages/next/lib/verifyTypeScriptSetup.ts b/packages/next/lib/verifyTypeScriptSetup.ts index 01939302caae771..e4e47742c422878 100644 --- a/packages/next/lib/verifyTypeScriptSetup.ts +++ b/packages/next/lib/verifyTypeScriptSetup.ts @@ -32,7 +32,7 @@ const requiredPackages = [ export async function verifyTypeScriptSetup( dir: string, - pagesDir: string, + intentDirs: string[], typeCheckPreflight: boolean, config: NextConfigComplete, cacheDir?: string @@ -41,7 +41,7 @@ export async function verifyTypeScriptSetup( try { // Check if the project uses TypeScript: - const intent = await getTypeScriptIntent(dir, pagesDir, config) + const intent = await getTypeScriptIntent(dir, intentDirs, config) if (!intent) { return { version: null } } diff --git a/packages/next/server/dev/next-dev-server.ts b/packages/next/server/dev/next-dev-server.ts index d23353c1ceeefc6..c935563c58b0ae6 100644 --- a/packages/next/server/dev/next-dev-server.ts +++ b/packages/next/server/dev/next-dev-server.ts @@ -96,6 +96,8 @@ export default class DevServer extends Server { protected sortedRoutes?: string[] private addedUpgradeListener = false private pagesDir: string + // @ts-ignore TODO: add implementation + private rootDir?: string protected staticPathsWorker?: { [key: string]: any } & { loadStaticPaths: typeof import('./static-paths-worker').loadStaticPaths @@ -175,7 +177,13 @@ export default class DevServer extends Server { } this.isCustomServer = !options.isNextDevCommand - this.pagesDir = findPagesDir(this.dir) + // TODO: hot-reload root/pages dirs? + const { pages: pagesDir, root: rootDir } = findPagesDir( + this.dir, + this.nextConfig.experimental.rootDir + ) + this.pagesDir = pagesDir + this.rootDir = rootDir } protected getBuildId(): string { @@ -361,7 +369,12 @@ export default class DevServer extends Server { async prepare(): Promise { setGlobal('distDir', this.distDir) setGlobal('phase', PHASE_DEVELOPMENT_SERVER) - await verifyTypeScriptSetup(this.dir, this.pagesDir, false, this.nextConfig) + await verifyTypeScriptSetup( + this.dir, + [this.pagesDir!, this.rootDir].filter(Boolean) as string[], + false, + this.nextConfig + ) this.customRoutes = await loadCustomRoutes(this.nextConfig)