diff --git a/bench/readdir/.gitignore b/bench/readdir/.gitignore deleted file mode 100644 index 116caa12788c7..0000000000000 --- a/bench/readdir/.gitignore +++ /dev/null @@ -1 +0,0 @@ -fixtures diff --git a/bench/readdir/create-fixtures.sh b/bench/readdir/create-fixtures.sh deleted file mode 100644 index afda00b81dda1..0000000000000 --- a/bench/readdir/create-fixtures.sh +++ /dev/null @@ -1,4 +0,0 @@ -# Uses https://github.com/divmain/fuzzponent -mkdir fixtures -cd fixtures -fuzzponent -d 2 -s 20 diff --git a/bench/readdir/glob.js b/bench/readdir/glob.js deleted file mode 100644 index 170f4fb050eb5..0000000000000 --- a/bench/readdir/glob.js +++ /dev/null @@ -1,24 +0,0 @@ -import { join } from 'path' -import { promisify } from 'util' -import globMod from 'glob' - -const glob = promisify(globMod) -const resolveDataDir = join(__dirname, 'fixtures', '**/*') - -async function test() { - const time = process.hrtime() - await glob(resolveDataDir) - - const hrtime = process.hrtime(time) - const nanoseconds = hrtime[0] * 1e9 + hrtime[1] - const milliseconds = nanoseconds / 1e6 - console.log(milliseconds) -} - -async function run() { - for (let i = 0; i < 50; i++) { - await test() - } -} - -run() diff --git a/bench/readdir/recursive-readdir.js b/bench/readdir/recursive-readdir.js deleted file mode 100644 index 2167f30336553..0000000000000 --- a/bench/readdir/recursive-readdir.js +++ /dev/null @@ -1,21 +0,0 @@ -import { join } from 'path' -import { recursiveReadDir } from 'next/dist/lib/recursive-readdir' -const resolveDataDir = join(__dirname, 'fixtures') - -async function test() { - const time = process.hrtime() - await recursiveReadDir(resolveDataDir, /\.js$/) - - const hrtime = process.hrtime(time) - const nanoseconds = hrtime[0] * 1e9 + hrtime[1] - const milliseconds = nanoseconds / 1e6 - console.log(milliseconds) -} - -async function run() { - for (let i = 0; i < 50; i++) { - await test() - } -} - -run() diff --git a/packages/next/src/build/index.ts b/packages/next/src/build/index.ts index a86398b9d5658..6449c5f781349 100644 --- a/packages/next/src/build/index.ts +++ b/packages/next/src/build/index.ts @@ -448,11 +448,11 @@ export default async function build( const pagesPaths = !appDirOnly && pagesDir - ? await nextBuildSpan - .traceChild('collect-pages') - .traceAsyncFn(() => - recursiveReadDir(pagesDir, validFileMatcher.isPageFile) - ) + ? await nextBuildSpan.traceChild('collect-pages').traceAsyncFn(() => + recursiveReadDir(pagesDir, { + pathnameFilter: validFileMatcher.isPageFile, + }) + ) : [] const middlewareDetectionRegExp = new RegExp( @@ -512,16 +512,14 @@ export default async function build( const appPaths = await nextBuildSpan .traceChild('collect-app-paths') .traceAsyncFn(() => - recursiveReadDir( - appDir, - (absolutePath) => + recursiveReadDir(appDir, { + pathnameFilter: (absolutePath) => validFileMatcher.isAppRouterPage(absolutePath) || // For now we only collect the root /not-found page in the app // directory as the 404 fallback validFileMatcher.isRootNotFound(absolutePath), - undefined, - (part) => part.startsWith('_') - ) + ignorePartFilter: (part) => part.startsWith('_'), + }) ) mappedAppPages = nextBuildSpan diff --git a/packages/next/src/lib/recursive-readdir.ts b/packages/next/src/lib/recursive-readdir.ts index 251bf397195ee..d67effb698a65 100644 --- a/packages/next/src/lib/recursive-readdir.ts +++ b/packages/next/src/lib/recursive-readdir.ts @@ -1,59 +1,184 @@ -import { Dirent, promises } from 'fs' -import { join } from 'path' +import fs from 'fs/promises' +import path from 'path' + +type Filter = (pathname: string) => boolean + +type Result = { + directories: string[] + pathnames: string[] + links: string[] +} + +export type RecursiveReadDirOptions = { + /** + * Filter to ignore files with absolute pathnames, false to ignore. + */ + pathnameFilter?: Filter + + /** + * Filter to ignore files and directories with absolute pathnames, false to + * ignore. + */ + ignoreFilter?: Filter + + /** + * Filter to ignore files and directories with the pathname part, false to + * ignore. + */ + ignorePartFilter?: Filter + + /** + * Whether to sort the results, true by default. + */ + sortPathnames?: boolean + + /** + * Whether to return relative pathnames, true by default. + */ + relativePathnames?: boolean +} /** - * Recursively read directory - * Returns array holding all relative paths + * Recursively reads a directory and returns the list of pathnames. + * + * @param rootDirectory the directory to read + * @param options options to control the behavior of the recursive read + * @returns the list of pathnames */ export async function recursiveReadDir( - /** Directory to read */ - dir: string, - /** Filter for the file path */ - filter: (absoluteFilePath: string) => boolean, - /** Filter for the file path */ - ignore?: (absoluteFilePath: string) => boolean, - ignorePart?: (partName: string) => boolean, - /** This doesn't have to be provided, it's used for the recursion */ - arr: string[] = [], - /** Used to replace the initial path, only the relative path is left, it's faster than path.relative. */ - rootDir: string = dir + rootDirectory: string, + options: RecursiveReadDirOptions = {} ): Promise { - const result = await promises.readdir(dir, { withFileTypes: true }) - - await Promise.all( - result.map(async (part: Dirent) => { - const absolutePath = join(dir, part.name) - const relativePath = absolutePath.replace(rootDir, '') - if (ignore && ignore(absolutePath)) return - if (ignorePart && ignorePart(part.name)) return - - // readdir does not follow symbolic links - // if part is a symbolic link, follow it using stat - let isDirectory = part.isDirectory() - if (part.isSymbolicLink()) { - const stats = await promises.stat(absolutePath) - isDirectory = stats.isDirectory() - } + // Grab our options. + const { + pathnameFilter, + ignoreFilter, + ignorePartFilter, + sortPathnames = true, + relativePathnames = true, + } = options - if (isDirectory) { - await recursiveReadDir( - absolutePath, - filter, - ignore, - ignorePart, - arr, - rootDir - ) - return - } + // The list of pathnames to return. + const pathnames: string[] = [] + + /** + * Coerces the pathname to be relative if requested. + */ + const coerce = relativePathnames + ? (pathname: string) => pathname.replace(rootDirectory, '') + : (pathname: string) => pathname + + // The queue of directories to scan. + let directories: string[] = [rootDirectory] + + while (directories.length > 0) { + // Load all the files in each directory at the same time. + const results = await Promise.all( + directories.map(async (directory) => { + const result: Result = { directories: [], pathnames: [], links: [] } + + try { + const dir = await fs.readdir(directory, { withFileTypes: true }) + for (const file of dir) { + // If enabled, ignore the file if it matches the ignore filter. + if (ignorePartFilter && ignorePartFilter(file.name)) { + continue + } + + // Handle each file. + const absolutePathname = path.join(directory, file.name) + + // If enabled, ignore the file if it matches the ignore filter. + if (ignoreFilter && ignoreFilter(absolutePathname)) { + continue + } + + // If the file is a directory, then add it to the list of directories, + // they'll be scanned on a later pass. + if (file.isDirectory()) { + result.directories.push(absolutePathname) + } else if (file.isSymbolicLink()) { + result.links.push(absolutePathname) + } else if (!pathnameFilter || pathnameFilter(absolutePathname)) { + result.pathnames.push(coerce(absolutePathname)) + } + } + } catch (err: any) { + // This can only happen when the underlying directory was removed. If + // anything other than this error occurs, re-throw it. + // if (err.code !== 'ENOENT') throw err + if (err.code !== 'ENOENT' || directory === rootDirectory) throw err + + // The error occurred, so abandon reading this directory. + return null + } + + return result + }) + ) + + // Empty the directories, we'll fill it later if some of the files are + // directories. + directories = [] + + // Keep track of any symbolic links we find, we'll resolve them later. + const links = [] + + // For each result of directory scans... + for (const result of results) { + // If the directory was removed, then skip it. + if (!result) continue + + // Add any directories to the list of directories to scan. + directories.push(...result.directories) + + // Add any symbolic links to the list of symbolic links to resolve. + links.push(...result.links) + + // Add any file pathnames to the list of pathnames. + pathnames.push(...result.pathnames) + } + + // Resolve all the symbolic links we found if any. + if (links.length > 0) { + const resolved = await Promise.all( + links.map(async (absolutePathname) => { + try { + return await fs.stat(absolutePathname) + } catch (err: any) { + // This can only happen when the underlying link was removed. If + // anything other than this error occurs, re-throw it. + if (err.code !== 'ENOENT') throw err + + // The error occurred, so abandon reading this directory. + return null + } + }) + ) + + for (let i = 0; i < links.length; i++) { + const stats = resolved[i] + + // If the link was removed, then skip it. + if (!stats) continue + + // We would have already ignored the file if it matched the ignore + // filter, so we don't need to check it again. + const absolutePathname = links[i] - if (!filter(absolutePath)) { - return + if (stats.isDirectory()) { + directories.push(absolutePathname) + } else if (!pathnameFilter || pathnameFilter(absolutePathname)) { + pathnames.push(coerce(absolutePathname)) + } } + } + } - arr.push(relativePath) - }) - ) + // Sort the pathnames in place if requested. + if (sortPathnames) { + pathnames.sort() + } - return arr.sort() + return pathnames } diff --git a/packages/next/src/lib/typescript/getTypeScriptIntent.ts b/packages/next/src/lib/typescript/getTypeScriptIntent.ts index bb103a10ebed8..919274352f47a 100644 --- a/packages/next/src/lib/typescript/getTypeScriptIntent.ts +++ b/packages/next/src/lib/typescript/getTypeScriptIntent.ts @@ -32,11 +32,10 @@ export async function getTypeScriptIntent( const tsFilesRegex = /.*\.(ts|tsx)$/ const excludedRegex = /(node_modules|.*\.d\.ts$)/ for (const dir of intentDirs) { - const typescriptFiles = await recursiveReadDir( - dir, - (name) => tsFilesRegex.test(name), - (name) => excludedRegex.test(name) - ) + const typescriptFiles = await recursiveReadDir(dir, { + pathnameFilter: (name) => tsFilesRegex.test(name), + ignoreFilter: (name) => excludedRegex.test(name), + }) if (typescriptFiles.length) { return { firstTimeSetup: true } } diff --git a/packages/next/src/lib/wait.ts b/packages/next/src/lib/wait.ts new file mode 100644 index 0000000000000..4476631bc989d --- /dev/null +++ b/packages/next/src/lib/wait.ts @@ -0,0 +1,8 @@ +/** + * Wait for a given number of milliseconds and then resolve. + * + * @param ms the number of milliseconds to wait + */ +export async function wait(ms: number) { + return new Promise((resolve) => setTimeout(resolve, ms)) +} diff --git a/packages/next/src/server/dev/next-dev-server.ts b/packages/next/src/server/dev/next-dev-server.ts index 5e366a5982a52..6c75285d77afe 100644 --- a/packages/next/src/server/dev/next-dev-server.ts +++ b/packages/next/src/server/dev/next-dev-server.ts @@ -54,7 +54,7 @@ import { DevAppPageRouteMatcherProvider } from '../future/route-matcher-provider import { DevAppRouteRouteMatcherProvider } from '../future/route-matcher-providers/dev/dev-app-route-route-matcher-provider' import { PagesManifest } from '../../build/webpack/plugins/pages-manifest-plugin' import { NodeManifestLoader } from '../future/route-matcher-providers/helpers/manifest-loaders/node-manifest-loader' -import { CachedFileReader } from '../future/route-matcher-providers/dev/helpers/file-reader/cached-file-reader' +import { BatchedFileReader } from '../future/route-matcher-providers/dev/helpers/file-reader/batched-file-reader' import { DefaultFileReader } from '../future/route-matcher-providers/dev/helpers/file-reader/default-file-reader' import { NextBuildContext } from '../../build/build-context' import { IncrementalCache } from '../lib/incremental-cache' @@ -208,10 +208,17 @@ export default class DevServer extends Server { this.dir ) const extensions = this.nextConfig.pageExtensions - const fileReader = new CachedFileReader(new DefaultFileReader()) + const extensionsExpression = new RegExp(`\\.(?:${extensions.join('|')})$`) // If the pages directory is available, then configure those matchers. if (pagesDir) { + const fileReader = new BatchedFileReader( + new DefaultFileReader({ + // Only allow files that have the correct extensions. + pathnameFilter: (pathname) => extensionsExpression.test(pathname), + }) + ) + matchers.push( new DevPagesRouteMatcherProvider( pagesDir, @@ -231,6 +238,17 @@ export default class DevServer extends Server { } if (appDir) { + // We create a new file reader for the app directory because we don't want + // to include any folders or files starting with an underscore. This will + // prevent the reader from wasting time reading files that we know we + // don't care about. + const fileReader = new BatchedFileReader( + new DefaultFileReader({ + // Ignore any directory prefixed with an underscore. + ignorePartFilter: (part) => part.startsWith('_'), + }) + ) + matchers.push( new DevAppPageRouteMatcherProvider(appDir, extensions, fileReader) ) diff --git a/packages/next/src/server/future/route-matcher-providers/dev/helpers/file-reader/cached-file-reader.test.ts b/packages/next/src/server/future/route-matcher-providers/dev/helpers/file-reader/batched-file-reader.test.ts similarity index 91% rename from packages/next/src/server/future/route-matcher-providers/dev/helpers/file-reader/cached-file-reader.test.ts rename to packages/next/src/server/future/route-matcher-providers/dev/helpers/file-reader/batched-file-reader.test.ts index 16f2aa32e54bb..36342762bfce9 100644 --- a/packages/next/src/server/future/route-matcher-providers/dev/helpers/file-reader/cached-file-reader.test.ts +++ b/packages/next/src/server/future/route-matcher-providers/dev/helpers/file-reader/batched-file-reader.test.ts @@ -1,4 +1,4 @@ -import { CachedFileReader } from './cached-file-reader' +import { BatchedFileReader } from './batched-file-reader' import { FileReader } from './file-reader' describe('CachedFileReader', () => { @@ -18,7 +18,7 @@ describe('CachedFileReader', () => { } }), } - const cached = new CachedFileReader(reader) + const cached = new BatchedFileReader(reader) const results = await Promise.all([ cached.read('/pages'), @@ -49,7 +49,7 @@ describe('CachedFileReader', () => { } }), } - const cached = new CachedFileReader(reader) + const cached = new BatchedFileReader(reader) await Promise.all( ['reject', 'resolve', 'reject', 'resolve'].map(async (directory) => { diff --git a/packages/next/src/server/future/route-matcher-providers/dev/helpers/file-reader/cached-file-reader.ts b/packages/next/src/server/future/route-matcher-providers/dev/helpers/file-reader/batched-file-reader.ts similarity index 93% rename from packages/next/src/server/future/route-matcher-providers/dev/helpers/file-reader/cached-file-reader.ts rename to packages/next/src/server/future/route-matcher-providers/dev/helpers/file-reader/batched-file-reader.ts index c9db00d97953b..c6ad88b2f6f35 100644 --- a/packages/next/src/server/future/route-matcher-providers/dev/helpers/file-reader/cached-file-reader.ts +++ b/packages/next/src/server/future/route-matcher-providers/dev/helpers/file-reader/batched-file-reader.ts @@ -1,6 +1,6 @@ import { FileReader } from './file-reader' -interface CachedFileReaderBatch { +interface FileReaderBatch { completed: boolean directories: Array callbacks: Array<{ @@ -13,8 +13,8 @@ interface CachedFileReaderBatch { * CachedFileReader will deduplicate requests made to the same folder structure * to scan for files. */ -export class CachedFileReader implements FileReader { - private batch?: CachedFileReaderBatch +export class BatchedFileReader implements FileReader { + private batch?: FileReaderBatch constructor(private readonly reader: FileReader) {} @@ -30,13 +30,13 @@ export class CachedFileReader implements FileReader { }) } - private getOrCreateBatch(): CachedFileReaderBatch { + private getOrCreateBatch(): FileReaderBatch { // If there is an existing batch and it's not completed, then reuse it. if (this.batch && !this.batch.completed) { return this.batch } - const batch: CachedFileReaderBatch = { + const batch: FileReaderBatch = { completed: false, directories: [], callbacks: [], diff --git a/packages/next/src/server/future/route-matcher-providers/dev/helpers/file-reader/default-file-reader.ts b/packages/next/src/server/future/route-matcher-providers/dev/helpers/file-reader/default-file-reader.ts index 1372d198778d8..96a463fe3b83c 100644 --- a/packages/next/src/server/future/route-matcher-providers/dev/helpers/file-reader/default-file-reader.ts +++ b/packages/next/src/server/future/route-matcher-providers/dev/helpers/file-reader/default-file-reader.ts @@ -1,56 +1,57 @@ -import { type Dirent } from 'fs' -import fs from 'fs/promises' -import path from 'path' -import { FileReader } from './file-reader' - +import type { FileReader } from './file-reader' + +import { + RecursiveReadDirOptions, + recursiveReadDir, +} from '../../../../../../lib/recursive-readdir' + +export type DefaultFileReaderOptions = Pick< + RecursiveReadDirOptions, + 'pathnameFilter' | 'ignoreFilter' | 'ignorePartFilter' +> + +/** + * Reads all the files in the directory and its subdirectories following any + * symbolic links. + */ export class DefaultFileReader implements FileReader { - public async read(dir: string): Promise> { - const pathnames: string[] = [] - - let directories: string[] = [dir] - - while (directories.length > 0) { - // Load all the files in each directory at the same time. - const results = await Promise.all( - directories.map(async (directory) => { - let files: Dirent[] - try { - files = await fs.readdir(directory, { withFileTypes: true }) - } catch (err: any) { - // This can only happen when the underlying directory was removed. If - // anything other than this error occurs, re-throw it. - if (err.code !== 'ENOENT') throw err - - // The error occurred, so abandon reading this directory. - files = [] - } - - return { directory, files } - }) - ) - - // Empty the directories, we'll fill it later if some of the files are - // directories. - directories = [] - - // For each result of directory scans... - for (const { files, directory } of results) { - // And for each file in it... - for (const file of files) { - // Handle each file. - const pathname = path.join(directory, file.name) - - // If the file is a directory, then add it to the list of directories, - // they'll be scanned on a later pass. - if (file.isDirectory()) { - directories.push(pathname) - } else { - pathnames.push(pathname) - } - } - } - } + /** + * Filter to ignore files with absolute pathnames. If undefined, no files are + * ignored. + */ + private readonly options: Readonly + + /** + * Creates a new file reader. + * + * @param pathnameFilter filter to ignore files with absolute pathnames, false to ignore + * @param ignoreFilter filter to ignore files and directories with absolute pathnames, false to ignore + * @param ignorePartFilter filter to ignore files and directories with the pathname part, false to ignore + */ + constructor(options: Readonly) { + this.options = options + } - return pathnames + /** + * Reads all the files in the directory and its subdirectories following any + * symbolic links. + * + * @param dir the directory to read + * @returns a promise that resolves to the list of files + */ + public async read(dir: string): Promise> { + return recursiveReadDir(dir, { + pathnameFilter: this.options.pathnameFilter, + ignoreFilter: this.options.ignoreFilter, + ignorePartFilter: this.options.ignorePartFilter, + + // We don't need to sort the results because we're not depending on the + // order of the results. + sortPathnames: false, + + // We want absolute pathnames because we're going to be comparing them + // with other absolute pathnames. + relativePathnames: false, + }) } } diff --git a/packages/next/src/server/lib/recursive-readdir-sync.ts b/packages/next/src/server/lib/recursive-readdir-sync.ts deleted file mode 100644 index 07549a88bbc36..0000000000000 --- a/packages/next/src/server/lib/recursive-readdir-sync.ts +++ /dev/null @@ -1,31 +0,0 @@ -import fs from 'fs' -import { sep } from 'path' - -/** - * Recursively read directory - * Returns array holding all relative paths - */ -export function recursiveReadDirSync( - /** The directory to read */ - dir: string, - /** This doesn't have to be provided, it's used for the recursion */ - arr: string[] = [], - /** Used to remove the initial path suffix and leave only the relative, faster than path.relative. */ - rootDirLength = dir.length -): string[] { - // Use opendirSync for better memory usage - const result = fs.opendirSync(dir) - - let part: fs.Dirent | null - while ((part = result.readSync())) { - const absolutePath = dir + sep + part.name - if (part.isDirectory()) { - recursiveReadDirSync(absolutePath, arr, rootDirLength) - } else { - arr.push(absolutePath.slice(rootDirLength)) - } - } - - result.closeSync() - return arr -} diff --git a/packages/next/src/server/lib/router-utils/filesystem.ts b/packages/next/src/server/lib/router-utils/filesystem.ts index 5930967833c0a..e09af618061d7 100644 --- a/packages/next/src/server/lib/router-utils/filesystem.ts +++ b/packages/next/src/server/lib/router-utils/filesystem.ts @@ -141,10 +141,9 @@ export async function setupFsCheck(opts: { buildId = await fs.readFile(buildIdPath, 'utf8') try { - for (let file of await recursiveReadDir(publicFolderPath, () => true)) { - file = normalizePathSep(file) - // ensure filename is encoded - publicFolderItems.add(encodeURI(file)) + for (const file of await recursiveReadDir(publicFolderPath)) { + // Ensure filename is encoded and normalized. + publicFolderItems.add(encodeURI(normalizePathSep(file))) } } catch (err: any) { if (err.code !== 'ENOENT') { @@ -153,13 +152,9 @@ export async function setupFsCheck(opts: { } try { - for (let file of await recursiveReadDir( - legacyStaticFolderPath, - () => true - )) { - file = normalizePathSep(file) - // ensure filename is encoded - legacyStaticFolderItems.add(encodeURI(file)) + for (const file of await recursiveReadDir(legacyStaticFolderPath)) { + // Ensure filename is encoded and normalized. + legacyStaticFolderItems.add(encodeURI(normalizePathSep(file))) } Log.warn( `The static directory has been deprecated in favor of the public directory. https://nextjs.org/docs/messages/static-dir-deprecated` @@ -171,14 +166,10 @@ export async function setupFsCheck(opts: { } try { - for (let file of await recursiveReadDir( - nextStaticFolderPath, - () => true - )) { - file = normalizePathSep(file) - // ensure filename is encoded + for (const file of await recursiveReadDir(nextStaticFolderPath)) { + // Ensure filename is encoded and normalized. nextStaticFolderItems.add( - path.posix.join('/_next/static', encodeURI(file)) + path.posix.join('/_next/static', encodeURI(normalizePathSep(file))) ) } } catch (err) { diff --git a/packages/next/src/server/load-components.ts b/packages/next/src/server/load-components.ts index 26ac8b843e7fd..45f190c589cca 100644 --- a/packages/next/src/server/load-components.ts +++ b/packages/next/src/server/load-components.ts @@ -25,6 +25,7 @@ import { interopDefault } from '../lib/interop-default' import { getTracer } from './lib/trace/tracer' import { LoadComponentsSpan } from './lib/trace/constants' import { loadManifest } from './load-manifest' +import { wait } from '../lib/wait' export type ManifestItem = { id: number | string files: string[] @@ -51,32 +52,6 @@ export type LoadComponentsReturnType = { pathname: string } -async function loadDefaultErrorComponentsImpl( - distDir: string -): Promise { - const Document = interopDefault(require('next/dist/pages/_document')) - const AppMod = require('next/dist/pages/_app') - const App = interopDefault(AppMod) - - // Load the compiled route module for this builtin error. - // TODO: (wyattjoh) replace this with just exporting the route module when the transition is complete - const ComponentMod = - require('./future/route-modules/pages/builtin/_error') as typeof import('./future/route-modules/pages/builtin/_error') - const Component = ComponentMod.routeModule.userland.default - - return { - App, - Document, - Component, - pageConfig: {}, - buildManifest: require(join(distDir, `fallback-${BUILD_MANIFEST}`)), - reactLoadableManifest: {}, - ComponentMod, - pathname: '/_error', - routeModule: ComponentMod.routeModule, - } -} - /** * Load manifest file with retries, defaults to 3 attempts. */ @@ -90,7 +65,8 @@ async function loadManifestWithRetries( } catch (err) { attempts-- if (attempts <= 0) throw err - await new Promise((resolve) => setTimeout(resolve, 100)) + + await wait(100) } } } @@ -98,19 +74,47 @@ async function loadManifestWithRetries( async function loadJSManifest( manifestPath: string, name: string, - entryname: string + entryName: string ): Promise { process.env.NEXT_MINIMAL ? // @ts-ignore __non_webpack_require__(manifestPath) : require(manifestPath) try { - return JSON.parse((globalThis as any)[name][entryname]) as T + return JSON.parse((globalThis as any)[name][entryName]) as T } catch (err) { return undefined } } +async function loadDefaultErrorComponentsImpl( + distDir: string +): Promise { + const Document = interopDefault(require('next/dist/pages/_document')) + const AppMod = require('next/dist/pages/_app') + const App = interopDefault(AppMod) + + // Load the compiled route module for this builtin error. + // TODO: (wyattjoh) replace this with just exporting the route module when the transition is complete + const ComponentMod = + require('./future/route-modules/pages/builtin/_error') as typeof import('./future/route-modules/pages/builtin/_error') + const Component = ComponentMod.routeModule.userland.default + + return { + App, + Document, + Component, + pageConfig: {}, + buildManifest: await loadManifestWithRetries( + join(distDir, `fallback-${BUILD_MANIFEST}`) + ), + reactLoadableManifest: {}, + ComponentMod, + pathname: '/_error', + routeModule: ComponentMod.routeModule, + } +} + async function loadComponentsImpl({ distDir, pathname, diff --git a/test/integration/build-output/test/index.test.js b/test/integration/build-output/test/index.test.js index 9c9742d943b6a..ecefcf8b726e7 100644 --- a/test/integration/build-output/test/index.test.js +++ b/test/integration/build-output/test/index.test.js @@ -183,9 +183,9 @@ describe('Build Output', () => { }) it('should not emit extracted comments', async () => { - const files = await recursiveReadDir(join(appDir, '.next'), (f) => - /\.txt|\.LICENSE\./.test(f) - ) + const files = await recursiveReadDir(join(appDir, '.next'), { + pathnameFilter: (f) => /\.txt|\.LICENSE\./.test(f), + }) expect(files).toEqual([]) }) }) diff --git a/test/integration/production-browser-sourcemaps/test/index.test.js b/test/integration/production-browser-sourcemaps/test/index.test.js index b8a8fe3dc6140..29439848660f9 100644 --- a/test/integration/production-browser-sourcemaps/test/index.test.js +++ b/test/integration/production-browser-sourcemaps/test/index.test.js @@ -8,10 +8,7 @@ const appDir = join(__dirname, '../') function runTests() { it('includes sourcemaps for all browser files', async () => { - const browserFiles = await recursiveReadDir( - join(appDir, '.next', 'static'), - (f) => /.*/.test(f) - ) + const browserFiles = await recursiveReadDir(join(appDir, '.next', 'static')) const jsFiles = browserFiles.filter( (file) => file.endsWith('.js') && file.includes('/pages/') ) diff --git a/test/integration/production/test/index.test.js b/test/integration/production/test/index.test.js index d41fd51b6f339..ed861434b6c1a 100644 --- a/test/integration/production/test/index.test.js +++ b/test/integration/production/test/index.test.js @@ -648,13 +648,13 @@ describe('Production Usage', () => { const cssStaticAssets = await recursiveReadDir( join(__dirname, '..', '.next', 'static'), - (f) => /\.css$/.test(f) + { pathnameFilter: (f) => /\.css$/.test(f) } ) expect(cssStaticAssets.length).toBeGreaterThanOrEqual(1) expect(cssStaticAssets[0]).toMatch(/[\\/]css[\\/]/) const mediaStaticAssets = await recursiveReadDir( join(__dirname, '..', '.next', 'static'), - (f) => /\.svg$/.test(f) + { pathnameFilter: (f) => /\.svg$/.test(f) } ) expect(mediaStaticAssets.length).toBeGreaterThanOrEqual(1) expect(mediaStaticAssets[0]).toMatch(/[\\/]media[\\/]/) diff --git a/test/integration/production/test/security.js b/test/integration/production/test/security.js index 2d2d7a35bf65f..edb1692f0d068 100644 --- a/test/integration/production/test/security.js +++ b/test/integration/production/test/security.js @@ -98,9 +98,9 @@ module.exports = (context) => { const buildId = readFileSync(join(__dirname, '../.next/BUILD_ID'), 'utf8') const readPath = join(__dirname, `../.next/static/${buildId}`) - const buildFiles = await recursiveReadDir(readPath, (f) => - /\.js$/.test(f) - ) + const buildFiles = await recursiveReadDir(readPath, { + pathnameFilter: (f) => /\.js$/.test(f), + }) if (buildFiles.length < 1) { throw new Error('Could not locate any build files') diff --git a/test/unit/recursive-delete.test.ts b/test/unit/recursive-delete.test.ts index 495d98991462f..237790bbf8905 100644 --- a/test/unit/recursive-delete.test.ts +++ b/test/unit/recursive-delete.test.ts @@ -21,9 +21,7 @@ describe('recursiveDelete', () => { await recursiveCopy(resolveDataDir, testResolveDataDir) await fs.symlink('./aa', join(testResolveDataDir, 'symlink')) await recursiveDelete(testResolveDataDir) - const result = await recursiveReadDir(testResolveDataDir, (f) => - /.*/.test(f) - ) + const result = await recursiveReadDir(testResolveDataDir) expect(result.length).toBe(0) } finally { await recursiveDelete(testResolveDataDir) @@ -39,17 +37,13 @@ describe('recursiveDelete', () => { // preserve cache dir await recursiveDelete(testpreservefileDir, /^cache/) - const result = await recursiveReadDir(testpreservefileDir, (f) => - /.*/.test(f) - ) + const result = await recursiveReadDir(testpreservefileDir) expect(result.length).toBe(1) } finally { // Ensure test cleanup await recursiveDelete(testpreservefileDir) - const cleanupResult = await recursiveReadDir(testpreservefileDir, (f) => - /.*/.test(f) - ) + const cleanupResult = await recursiveReadDir(testpreservefileDir) expect(cleanupResult.length).toBe(0) } }) diff --git a/test/unit/recursive-readdir.test.ts b/test/unit/recursive-readdir.test.ts index 666ea770d7ced..a07cba7bd9791 100644 --- a/test/unit/recursive-readdir.test.ts +++ b/test/unit/recursive-readdir.test.ts @@ -8,7 +8,9 @@ const dirWithPages = join(resolveDataDir, 'readdir', 'pages') describe('recursiveReadDir', () => { it('should work', async () => { - const result = await recursiveReadDir(dirWithPages, (f) => /\.js/.test(f)) + const result = await recursiveReadDir(dirWithPages, { + pathnameFilter: (f) => /\.js/.test(f), + }) const pages = [ /^[\\/]index\.js/, /^[\\/]prefered\.js/,