import fs from "fs"; import { Worker } from "next/dist/compiled/jest-worker"; import { join as pathJoin } from "path"; import { ampValidation } from "../../build/output"; import { INSTRUMENTATION_HOOK_FILENAME, PUBLIC_DIR_MIDDLEWARE_CONFLICT } from "../../lib/constants"; import { findPagesDir } from "../../lib/find-pages-dir"; import { PHASE_DEVELOPMENT_SERVER, PAGES_MANIFEST, APP_PATHS_MANIFEST } from "../../shared/lib/constants"; import Server, { WrappedBuildError } from "../next-server"; import { normalizePagePath } from "../../shared/lib/page-path/normalize-page-path"; import { pathHasPrefix } from "../../shared/lib/router/utils/path-has-prefix"; import { removePathPrefix } from "../../shared/lib/router/utils/remove-path-prefix"; import { Telemetry } from "../../telemetry/storage"; import { setGlobal } from "../../trace"; import { findPageFile } from "../lib/find-page-file"; import { getNodeOptionsWithoutInspect } from "../lib/utils"; import { withCoalescedInvoke } from "../../lib/coalesced-function"; import { loadDefaultErrorComponents } from "../load-default-error-components"; import { DecodeError, MiddlewareNotFoundError } from "../../shared/lib/utils"; import * as Log from "../../build/output/log"; import isError, { getProperError } from "../../lib/is-error"; import { isMiddlewareFile } from "../../build/utils"; import { formatServerError } from "../../lib/format-server-error"; import { DevRouteMatcherManager } from "../future/route-matcher-managers/dev-route-matcher-manager"; import { DevPagesRouteMatcherProvider } from "../future/route-matcher-providers/dev/dev-pages-route-matcher-provider"; import { DevPagesAPIRouteMatcherProvider } from "../future/route-matcher-providers/dev/dev-pages-api-route-matcher-provider"; import { DevAppPageRouteMatcherProvider } from "../future/route-matcher-providers/dev/dev-app-page-route-matcher-provider"; import { DevAppRouteRouteMatcherProvider } from "../future/route-matcher-providers/dev/dev-app-route-route-matcher-provider"; import { NodeManifestLoader } from "../future/route-matcher-providers/helpers/manifest-loaders/node-manifest-loader"; 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 LRUCache from "next/dist/compiled/lru-cache"; import { getMiddlewareRouteMatcher } from "../../shared/lib/router/utils/middleware-route-matcher"; // Load ReactDevOverlay only when needed let ReactDevOverlayImpl; const ReactDevOverlay = (props)=>{ if (ReactDevOverlayImpl === undefined) { ReactDevOverlayImpl = require("next/dist/compiled/@next/react-dev-overlay/dist/client").ReactDevOverlay; } return ReactDevOverlayImpl(props); }; export default class DevServer extends Server { getStaticPathsWorker() { const worker = new Worker(require.resolve("./static-paths-worker"), { maxRetries: 1, // For dev server, it's not necessary to spin up too many workers as long as you are not doing a load test. // This helps reusing the memory a lot. numWorkers: 1, enableWorkerThreads: this.nextConfig.experimental.workerThreads, forkOptions: { env: { ...process.env, // discard --inspect/--inspect-brk flags from process.env.NODE_OPTIONS. Otherwise multiple Node.js debuggers // would be started if user launch Next.js in debugging mode. The number of debuggers is linked to // the number of workers Next.js tries to launch. The only worker users are interested in debugging // is the main Next.js one NODE_OPTIONS: getNodeOptionsWithoutInspect() } } }); worker.getStdout().pipe(process.stdout); worker.getStderr().pipe(process.stderr); return worker; } constructor(options){ var _this_nextConfig_experimental_amp, _this_nextConfig_experimental; try { // Increase the number of stack frames on the server Error.stackTraceLimit = 50; } catch {} super({ ...options, dev: true }); /** * The promise that resolves when the server is ready. When this is unset * the server is ready. */ this.ready = Promise.withResolvers(); this.bundlerService = options.bundlerService; this.originalFetch = global.fetch; this.renderOpts.dev = true; this.renderOpts.appDirDevErrorLogger = (err)=>this.logErrorWithOriginalStack(err, "app-dir"); this.renderOpts.ErrorDebug = ReactDevOverlay; this.staticPathsCache = new LRUCache({ // 5MB max: 5 * 1024 * 1024, length (value) { return JSON.stringify(value.staticPaths).length; } }); this.renderOpts.ampSkipValidation = ((_this_nextConfig_experimental = this.nextConfig.experimental) == null ? void 0 : (_this_nextConfig_experimental_amp = _this_nextConfig_experimental.amp) == null ? void 0 : _this_nextConfig_experimental_amp.skipValidation) ?? false; this.renderOpts.ampValidator = (html, pathname)=>{ const validatorPath = this.nextConfig.experimental && this.nextConfig.experimental.amp && this.nextConfig.experimental.amp.validator; const AmpHtmlValidator = require("next/dist/compiled/amphtml-validator"); return AmpHtmlValidator.getInstance(validatorPath).then((validator)=>{ const result = validator.validateString(html); ampValidation(pathname, result.errors.filter((e)=>e.severity === "ERROR").filter((e)=>this._filterAmpDevelopmentScript(html, e)), result.errors.filter((e)=>e.severity !== "ERROR")); }); }; const { pagesDir, appDir } = findPagesDir(this.dir); this.pagesDir = pagesDir; this.appDir = appDir; } getRouteMatchers() { const { pagesDir, appDir } = findPagesDir(this.dir); const ensurer = { ensure: async (match)=>{ await this.ensurePage({ definition: match.definition, page: match.definition.page, clientOnly: false }); } }; const matchers = new DevRouteMatcherManager(super.getRouteMatchers(), ensurer, this.dir); const extensions = this.nextConfig.pageExtensions; 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, extensions, fileReader, this.localeNormalizer)); matchers.push(new DevPagesAPIRouteMatcherProvider(pagesDir, extensions, fileReader, this.localeNormalizer)); } 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)); matchers.push(new DevAppRouteRouteMatcherProvider(appDir, extensions, fileReader)); } return matchers; } getBuildId() { return "development"; } async prepareImpl() { var _this_ready; setGlobal("distDir", this.distDir); setGlobal("phase", PHASE_DEVELOPMENT_SERVER); const telemetry = new Telemetry({ distDir: this.distDir }); await super.prepareImpl(); await this.runInstrumentationHookIfAvailable(); await this.matchers.reload(); (_this_ready = this.ready) == null ? void 0 : _this_ready.resolve(); this.ready = undefined; // This is required by the tracing subsystem. setGlobal("appDir", this.appDir); setGlobal("pagesDir", this.pagesDir); setGlobal("telemetry", telemetry); process.on("unhandledRejection", (reason)=>{ this.logErrorWithOriginalStack(reason, "unhandledRejection").catch(()=>{}); }); process.on("uncaughtException", (err)=>{ this.logErrorWithOriginalStack(err, "uncaughtException").catch(()=>{}); }); } async close() {} async hasPage(pathname) { let normalizedPath; try { normalizedPath = normalizePagePath(pathname); } catch (err) { console.error(err); // if normalizing the page fails it means it isn't valid // so it doesn't exist so don't throw and return false // to ensure we return 404 instead of 500 return false; } if (isMiddlewareFile(normalizedPath)) { return findPageFile(this.dir, normalizedPath, this.nextConfig.pageExtensions, false).then(Boolean); } let appFile = null; let pagesFile = null; if (this.appDir) { appFile = await findPageFile(this.appDir, normalizedPath + "/page", this.nextConfig.pageExtensions, true); } if (this.pagesDir) { pagesFile = await findPageFile(this.pagesDir, normalizedPath, this.nextConfig.pageExtensions, false); } if (appFile && pagesFile) { return false; } return Boolean(appFile || pagesFile); } async runMiddleware(params) { try { const result = await super.runMiddleware({ ...params, onWarning: (warn)=>{ this.logErrorWithOriginalStack(warn, "warning"); } }); if ("finished" in result) { return result; } result.waitUntil.catch((error)=>{ this.logErrorWithOriginalStack(error, "unhandledRejection"); }); return result; } catch (error) { if (error instanceof DecodeError) { throw error; } /** * We only log the error when it is not a MiddlewareNotFound error as * in that case we should be already displaying a compilation error * which is what makes the module not found. */ if (!(error instanceof MiddlewareNotFoundError)) { this.logErrorWithOriginalStack(error); } const err = getProperError(error); err.middleware = true; const { request, response, parsedUrl } = params; /** * When there is a failure for an internal Next.js request from * middleware we bypass the error without finishing the request * so we can serve the required chunks to render the error. */ if (request.url.includes("/_next/static") || request.url.includes("/__nextjs_original-stack-frame")) { return { finished: false }; } response.statusCode = 500; await this.renderError(err, request, response, parsedUrl.pathname); return { finished: true }; } } async runEdgeFunction(params) { try { return super.runEdgeFunction({ ...params, onWarning: (warn)=>{ this.logErrorWithOriginalStack(warn, "warning"); } }); } catch (error) { if (error instanceof DecodeError) { throw error; } this.logErrorWithOriginalStack(error, "warning"); const err = getProperError(error); const { req, res, page } = params; res.statusCode = 500; await this.renderError(err, req, res, page); return null; } } async handleRequest(req, res, parsedUrl) { var _this_ready; await ((_this_ready = this.ready) == null ? void 0 : _this_ready.promise); return await super.handleRequest(req, res, parsedUrl); } async run(req, res, parsedUrl) { var _this_ready; await ((_this_ready = this.ready) == null ? void 0 : _this_ready.promise); const { basePath } = this.nextConfig; let originalPathname = null; // TODO: see if we can remove this in the future if (basePath && pathHasPrefix(parsedUrl.pathname || "/", basePath)) { // strip basePath before handling dev bundles // If replace ends up replacing the full url it'll be `undefined`, meaning we have to default it to `/` originalPathname = parsedUrl.pathname; parsedUrl.pathname = removePathPrefix(parsedUrl.pathname || "/", basePath); } const { pathname } = parsedUrl; if (pathname.startsWith("/_next")) { if (fs.existsSync(pathJoin(this.publicDir, "_next"))) { throw new Error(PUBLIC_DIR_MIDDLEWARE_CONFLICT); } } if (originalPathname) { // restore the path before continuing so that custom-routes can accurately determine // if they should match against the basePath or not parsedUrl.pathname = originalPathname; } try { return await super.run(req, res, parsedUrl); } catch (error) { const err = getProperError(error); formatServerError(err); this.logErrorWithOriginalStack(err).catch(()=>{}); if (!res.sent) { res.statusCode = 500; try { return await this.renderError(err, req, res, pathname, { __NEXT_PAGE: isError(err) && err.page || pathname || "" }); } catch (internalErr) { console.error(internalErr); res.body("Internal Server Error").send(); } } } } async logErrorWithOriginalStack(err, type) { await this.bundlerService.logErrorWithOriginalStack(err, type); } getPagesManifest() { return NodeManifestLoader.require(pathJoin(this.serverDistDir, PAGES_MANIFEST)) ?? undefined; } getAppPathsManifest() { if (!this.hasAppDir) return undefined; return NodeManifestLoader.require(pathJoin(this.serverDistDir, APP_PATHS_MANIFEST)) ?? undefined; } getMiddleware() { var _this_middleware; // We need to populate the match // field as it isn't serializable if (((_this_middleware = this.middleware) == null ? void 0 : _this_middleware.match) === null) { this.middleware.match = getMiddlewareRouteMatcher(this.middleware.matchers || []); } return this.middleware; } getNextFontManifest() { return undefined; } async hasMiddleware() { return this.hasPage(this.actualMiddlewareFile); } async ensureMiddleware() { return this.ensurePage({ page: this.actualMiddlewareFile, clientOnly: false, definition: undefined }); } async runInstrumentationHookIfAvailable() { if (this.actualInstrumentationHookFile && await this.ensurePage({ page: this.actualInstrumentationHookFile, clientOnly: false, definition: undefined }).then(()=>true).catch(()=>false)) { NextBuildContext.hasInstrumentationHook = true; try { const instrumentationHook = await require(pathJoin(this.distDir, "server", INSTRUMENTATION_HOOK_FILENAME)); await instrumentationHook.register(); } catch (err) { err.message = `An error occurred while loading instrumentation hook: ${err.message}`; throw err; } } } async ensureEdgeFunction({ page, appPaths }) { return this.ensurePage({ page, appPaths, clientOnly: false, definition: undefined }); } generateRoutes(_dev) { // In development we expose all compiled files for react-error-overlay's line show feature // We use unshift so that we're sure the routes is defined before Next's default routes // routes.unshift({ // match: getPathMatch('/_next/development/:path*'), // type: 'route', // name: '_next/development catchall', // fn: async (req, res, params) => { // const p = pathJoin(this.distDir, ...(params.path || [])) // await this.serveStatic(req, res, p) // return { // finished: true, // } // }, // }) } _filterAmpDevelopmentScript(html, event) { if (event.code !== "DISALLOWED_SCRIPT_TAG") { return true; } const snippetChunks = html.split("\n"); let snippet; if (!(snippet = html.split("\n")[event.line - 1]) || !(snippet = snippet.substring(event.col))) { return true; } snippet = snippet + snippetChunks.slice(event.line).join("\n"); snippet = snippet.substring(0, snippet.indexOf("")); return !snippet.includes("data-amp-development-mode-only"); } async getStaticPaths({ pathname, requestHeaders, page, isAppPath }) { // we lazy load the staticPaths to prevent the user // from waiting on them for the page to load in dev mode const __getStaticPaths = async ()=>{ const { configFileName, publicRuntimeConfig, serverRuntimeConfig, httpAgentOptions } = this.nextConfig; const { locales, defaultLocale } = this.nextConfig.i18n || {}; const staticPathsWorker = this.getStaticPathsWorker(); try { const pathsResult = await staticPathsWorker.loadStaticPaths({ distDir: this.distDir, pathname, config: { configFileName, publicRuntimeConfig, serverRuntimeConfig }, httpAgentOptions, locales, defaultLocale, page, isAppPath, requestHeaders, incrementalCacheHandlerPath: this.nextConfig.experimental.incrementalCacheHandlerPath, fetchCacheKeyPrefix: this.nextConfig.experimental.fetchCacheKeyPrefix, isrFlushToDisk: this.nextConfig.experimental.isrFlushToDisk, maxMemoryCacheSize: this.nextConfig.experimental.isrMemoryCacheSize }); return pathsResult; } finally{ // we don't re-use workers so destroy the used one staticPathsWorker.end(); } }; const result = this.staticPathsCache.get(pathname); const nextInvoke = withCoalescedInvoke(__getStaticPaths)(`staticPaths-${pathname}`, []).then((res)=>{ const { paths: staticPaths = [], fallback } = res.value; if (!isAppPath && this.nextConfig.output === "export") { if (fallback === "blocking") { throw new Error('getStaticPaths with "fallback: blocking" cannot be used with "output: export". See more info here: https://nextjs.org/docs/advanced-features/static-html-export'); } else if (fallback === true) { throw new Error('getStaticPaths with "fallback: true" cannot be used with "output: export". See more info here: https://nextjs.org/docs/advanced-features/static-html-export'); } } const value = { staticPaths, fallbackMode: fallback === "blocking" ? "blocking" : fallback === true ? "static" : fallback }; this.staticPathsCache.set(pathname, value); return value; }).catch((err)=>{ this.staticPathsCache.del(pathname); if (!result) throw err; Log.error(`Failed to generate static paths for ${pathname}:`); console.error(err); }); if (result) { return result; } return nextInvoke; } restorePatchedGlobals() { global.fetch = this.originalFetch; } async ensurePage(opts) { await this.bundlerService.ensurePage(opts); } async findPageComponents({ page, query, params, isAppPath, appPaths = null, shouldEnsure }) { var _this_ready; await ((_this_ready = this.ready) == null ? void 0 : _this_ready.promise); const compilationErr = await this.getCompilationError(page); if (compilationErr) { // Wrap build errors so that they don't get logged again throw new WrappedBuildError(compilationErr); } try { if (shouldEnsure || this.renderOpts.customServer) { await this.ensurePage({ page, appPaths, clientOnly: false, definition: undefined }); } this.nextFontManifest = super.getNextFontManifest(); // before we re-evaluate a route module, we want to restore globals that might // have been patched previously to their original state so that we don't // patch on top of the previous patch, which would keep the context of the previous // patched global in memory, creating a memory leak. this.restorePatchedGlobals(); return await super.findPageComponents({ page, query, params, isAppPath, shouldEnsure }); } catch (err) { if (err.code !== "ENOENT") { throw err; } return null; } } async getFallbackErrorComponents() { await this.bundlerService.getFallbackErrorComponents(); return await loadDefaultErrorComponents(this.distDir); } async getCompilationError(page) { return await this.bundlerService.getCompilationError(page); } } //# sourceMappingURL=next-dev-server.js.map