UNPKG

nuxt

Version:

Nuxt is a free and open-source framework with an intuitive and extendable way to create type-safe, performant and production-grade full-stack web applications and websites with Vue.js.

1,252 lines 333 kB
import process from "node:process"; import fs, { existsSync, mkdirSync, promises, readdirSync, statSync, writeFileSync } from "node:fs"; import { mkdir, open, readFile, readdir, rm, stat, unlink, writeFile } from "node:fs/promises"; import { createHash, randomUUID } from "node:crypto"; import { AsyncLocalStorage } from "node:async_hooks"; import { basename, dirname, extname, isAbsolute, join, normalize, parse, relative, resolve } from "pathe"; import { createDebugger, createHooks } from "hookable"; import ignore from "ignore"; import { addBuildPlugin, addComponent, addImportsSources, addPlugin, addPluginTemplate, addRouteMiddleware, addTemplate, addTypeTemplate, addVitePlugin, buildDiagnostics, componentDiagnostics, configDiagnostics, createIsIgnored, createResolver, defineNuxtModule, directoryToURL, findPath, getLayerDirectories, headDiagnostics, importModule, installModules, isIgnored, loadNuxtConfig, normalizeModuleTranspilePath, normalizePlugin, normalizeTemplate, nuxtCtx, packageName, pageDiagnostics, pluginDiagnostics, resolveAlias, resolveDeclarationPath, resolveFiles, resolveIgnorePatterns, resolveModuleWithOptions, resolvePath, resolveTypePaths, runWithNuxtContext, tryUseNuxt, updateTemplates, useLogger, useNitro, useNuxt } from "@nuxt/kit"; import { readPackage, readPackageJSON, resolvePackageJSON } from "pkg-types"; import { hash, isEqual, serialize } from "ohash"; import onChange from "on-change"; import { formatDate, resolveCompatibilityDatesFromEnv } from "compatx"; import escapeStringRegexp from "escape-string-regexp"; import { joinURL, withTrailingSlash, withoutLeadingSlash } from "ufo"; import { ImpoundPlugin } from "impound"; import { defu } from "defu"; import { coerce, satisfies } from "verkit"; import { hasTTY, isCI, provider } from "std-env"; import { genArrayFromRaw, genDynamicImport, genDynamicTypeImport, genExport, genImport, genInlineTypeImport, genObjectFromRawEntries, genObjectKey, genSafeVariableName, genString } from "knitwork"; import { resolveModulePath } from "exsolve"; import { addDependency } from "nypm"; import { filename, resolveAlias as resolveAlias$1, reverseResolveAlias } from "pathe/utils"; import { createRoutesContext, resolveOptions } from "vue-router/unplugin"; import { addRoute, createRouter, findAllRoutes } from "rou3"; import { fileURLToPath, pathToFileURL } from "node:url"; import picomatch from "picomatch"; import { klona } from "klona"; import { ScopeTracker, getUndeclaredIdentifiersInFunction, isBindingIdentifier, parseAndWalk, walk } from "oxc-walker"; import { addFile, buildTree, compileParsePath, removeFile, toVueRouter4, vueRouterToRou3 } from "unrouting"; import { camelCase, kebabCase, pascalCase, splitByCase } from "scule"; import { createUnplugin } from "unplugin"; import { findExports, findStaticImports, parseStaticImport } from "mlly"; import MagicString from "magic-string"; import { generateTransform, rolldownString } from "rolldown-string"; import { streamingIifeCode } from "unhead/stream/iife"; import { unheadVueComposablesImports } from "@unhead/vue"; import { createUnimport, defineUnimportPreset, scanDirExports, toExports, toTypeDeclarationFile, toTypeReExports } from "unimport"; import { glob } from "tinyglobby"; import { ELEMENT_NODE, parse as parse$1, walk as walk$1 } from "ultrahtml"; import { isObject } from "@vue/shared"; import { consola } from "consola"; import { createTar, parseTar } from "nanotar"; import { createTransformer, getTransformFilter } from "unctx/transform"; import { performance } from "node:perf_hooks"; import { colors } from "consola/utils"; import { watch } from "chokidar"; import { debounce } from "perfect-debounce"; import { generateTypes, resolveSchema } from "untyped"; import untypedPlugin from "untyped/babel-plugin"; import { createJiti } from "jiti"; import { minifySync, transformSync } from "rolldown/utils"; import { Diagnostic } from "nostics"; //#region \0rolldown/runtime.js var __defProp = Object.defineProperty; var __exportAll = (all, no_symbols) => { let target = {}; for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" }); return target; }; //#endregion //#region src/utils.ts /** @since 3.9.0 */ function toArray(value) { return Array.isArray(value) ? value : [value]; } async function isDirectory(path) { try { return (await promises.lstat(path)).isDirectory(); } catch (err) { const code = err.code; if (code === "ENOENT" || code === "ENOTDIR") return false; throw err; } } function isDirectorySync(path) { try { return statSync(path, { throwIfNoEntry: false })?.isDirectory() ?? false; } catch (err) { if (err.code === "ENOTDIR") return false; throw err; } } const LEADING_DOT_RE = /^\.+/g; /** * Normalizes a file extension from a string to just the extension part (without the dot). * In case the string does not contain a dot, it returns the string as is. * * @example * normalizeExtension('.ts') // 'ts' * normalizeExtension('.d.ts') // 'd.ts' * normalizeExtension('ts') // 'ts' * normalizeExtension('d.ts') // 'ts' */ function normalizeExtension(input) { return input.replace(LEADING_DOT_RE, ""); } function stripExtension(path) { return path.replace(/\.[^./\\]+$/, ""); } function isWhitespace(char) { const c = typeof char === "string" ? char.charCodeAt(0) : char; return c === 32 || c === 9 || c === 10 || c === 13 || c === 12; } const JS_EXT_RE = /^[^?]*\.(?:[jt]sx?|[cm][jt]s)(?:$|\?)/; const NUXT_LIB_RE = /^[^?]*node_modules\/(?:nuxt|nuxt3|nuxt-nightly|@nuxt)\//; const STYLE_QUERY_RE$1 = /[?&]type=style/; const MACRO_QUERY_RE$2 = /[?&]macro(?:=|&|$)/; const DECLARATION_EXTENSIONS = [ "d.ts", "d.mts", "d.cts", "d.vue.ts", "d.vue.mts", "d.vue.cts" ]; const logger = useLogger("nuxt"); function resolveToAlias(path, nuxt = tryUseNuxt()) { return reverseResolveAlias(path, { ...nuxt?.options.alias || {}, ...strippedAtAliases$1 }).pop() || path; } const strippedAtAliases$1 = { "@": "", "@@": "" }; //#endregion //#region src/core/features.ts var features_exports = /* @__PURE__ */ __exportAll({ ensurePackageInstalled: () => ensurePackageInstalled, installNuxtModule: () => installNuxtModule }); const isStackblitz = provider === "stackblitz"; async function promptToInstall(name, installCommand, options) { for (const parent of options.searchPaths || []) if (await resolvePackageJSON(name, { parent }).catch(() => null)) return true; configDiagnostics.NUXT_B5011({ name }); if (isCI) return false; if (options.prompt === true || options.prompt !== false && !isStackblitz) { if (await logger.prompt(`Do you want to install ${name} package?`, { type: "confirm", name: "confirm", initial: true }) !== true) return false; } logger.info(`Installing ${name}...`); try { await installCommand(); logger.success(`Installed ${name}`); return true; } catch (err) { buildDiagnostics.NUXT_B1004({ packages: name, cause: err }); return false; } } const installPrompts = /* @__PURE__ */ new Set(); function installNuxtModule(name, options) { if (installPrompts.has(name)) return; installPrompts.add(name); const nuxt = useNuxt(); return promptToInstall(name, async () => { const { runCommand } = await import("@nuxt/cli"); await runCommand("module", [ "add", name, "--cwd", nuxt.options.rootDir ]); }, { rootDir: nuxt.options.rootDir, searchPaths: nuxt.options.modulesDir, ...options }); } function ensurePackageInstalled(name, options) { return promptToInstall(name, () => addDependency(name, { cwd: options.rootDir, dev: true }), options); } //#endregion //#region src/dirs.ts let _distDir = dirname(fileURLToPath(import.meta.url)); if (/(?:chunks|shared)$/.test(_distDir)) _distDir = dirname(_distDir); const distDir = _distDir; const pkgDir = resolve(distDir, ".."); //#endregion //#region src/core/utils/names.ts function getNameFromPath(path, relativeTo) { const relativePath = relativeTo ? normalize(path).replace(withTrailingSlash(normalize(relativeTo)), "") : basename(path); const prefixParts = splitByCase(dirname(relativePath)); const fileName = basename(relativePath, extname(relativePath)); return kebabCase(resolveComponentNameSegments(fileName.toLowerCase() === "index" ? "" : fileName, prefixParts).filter(Boolean)).replace(QUOTE_RE, ""); } function hasSuffix(path, suffix) { return basename(path, extname(path)).endsWith(suffix); } function resolveComponentNameSegments(fileName, prefixParts) { /** * Array of fileName parts split by case, / or - * @example third-component -> ['third', 'component'] * @example AwesomeComponent -> ['Awesome', 'Component'] */ const fileNameParts = splitByCase(fileName); const fileNamePartsContent = fileNameParts.join("/").toLowerCase(); const componentNameParts = prefixParts.flatMap((p) => splitByCase(p)); let index = prefixParts.length - 1; const matchedSuffix = []; while (index >= 0) { const prefixPart = prefixParts[index]; matchedSuffix.unshift(...splitByCase(prefixPart).map((p) => p.toLowerCase())); const matchedSuffixContent = matchedSuffix.join("/"); if (fileNamePartsContent === matchedSuffixContent || fileNamePartsContent.startsWith(matchedSuffixContent + "/") || prefixPart.toLowerCase() === fileNamePartsContent && prefixParts[index + 1] && prefixParts[index] === prefixParts[index + 1]) componentNameParts.length = index; index--; } return [...componentNameParts, ...fileNameParts]; } //#endregion //#region src/core/utils/plugins.ts /** * Split a bundler module ID into its pathname and search (query) parts. * * Module IDs from Vite/webpack are already-normalized filesystem paths * that may carry query strings (e.g. `?vue&type=style&lang=css`). */ function parseModuleId(id) { const qIndex = id.indexOf("?"); if (qIndex === -1) return { pathname: id, search: "" }; return { pathname: id.slice(0, qIndex), search: id.slice(qIndex) }; } const NUXT_COMPONENT_RE = /[?&]nuxt_component=/; const MACRO_RE$1 = /[?&]macro=/; const VUE_QUERY_RE = /[?&]vue(?:&|$)/; const SETUP_QUERY_RE = /[?&]setup(?:=|&|$)/; const TYPE_QUERY_RE = /[?&]type=([^&]*)/; function isVue(id, opts = {}) { const { search } = parseModuleId(id); if (id.endsWith(".vue") && !search) return true; if (!search) return false; if (NUXT_COMPONENT_RE.test(search)) return false; if (MACRO_RE$1.test(search) && (search === "?macro=true" || !opts.type || opts.type.includes("script"))) return true; if (!VUE_QUERY_RE.test(search)) return false; if (opts.type) { const type = SETUP_QUERY_RE.test(search) ? "script" : TYPE_QUERY_RE.exec(search)?.[1]; if (!type || !opts.type.includes(type)) return false; } return true; } const JS_RE = /\.(?:[cm]?[jt]s|[jt]sx)$/; /** Matches module IDs for Vue files (ignoring query strings). */ const VUE_ID_RE = /\.vue(?:\?|$)/; function isJS(id) { const { pathname } = parseModuleId(id); return JS_RE.test(pathname); } function getLoader(id) { const { pathname } = parseModuleId(id); const ext = extname(pathname); if (ext === ".vue") return "vue"; if (!JS_RE.test(ext)) return null; return ext.endsWith("x") ? "tsx" : "ts"; } //#endregion //#region src/core/utils/index.ts function uniqueBy(arr, key) { if (arr.length < 2) return arr; const res = []; const seen = /* @__PURE__ */ new Set(); for (const item of arr) { if (seen.has(item[key])) continue; seen.add(item[key]); res.push(item); } return res; } const QUOTE_RE = /["']/g; const EXTENSION_RE = /\b\.\w+$/g; const SX_RE = /\.[tj]sx$/; //#endregion //#region src/pages/utils.ts function createPagesContext(options = {}) { const modes = options.shouldUseServerComponents ? ["server", "client"] : ["client"]; const treeOptions = { roots: options.roots, modes, warn: (message) => pageDiagnostics.NUXT_B4011({ message }) }; const emitOptions = { onDuplicateRouteName: (_name, file, existingFile) => { pageDiagnostics.NUXT_B4004({ file, existingFile }); }, attrs: { mode: modes } }; const compiledParse = compileParsePath(treeOptions); let tree = buildTree([], treeOptions); const trackedFiles = /* @__PURE__ */ new Set(); return { emit() { return toVueRouter4(tree, emitOptions); }, addFile(filePath, priority = 0) { addFile(tree, { path: filePath, priority }, compiledParse); trackedFiles.add(filePath); }, removeFile(filePath) { const removed = removeFile(tree, filePath); if (removed) trackedFiles.delete(filePath); return removed; }, rebuild(files) { tree = buildTree(files, treeOptions); trackedFiles.clear(); for (const f of files) trackedFiles.add(f.path); }, trackedFiles }; } async function resolvePagesRoutes(pattern, nuxt = useNuxt(), ctx, originalPagePaths) { const pagesDirs = getLayerDirectories(nuxt).map((d) => d.appPages); const inputFiles = []; for (let priority = 0; priority < pagesDirs.length; priority++) { const dir = pagesDirs[priority]; const files = await resolveFiles(dir, pattern); for (const file of files) inputFiles.push({ path: file, priority }); } let pages; if (ctx) { ctx.rebuild(inputFiles); pages = ctx.emit(); } else { const oneShot = createPagesContext({ roots: pagesDirs, shouldUseServerComponents: !!nuxt.options.experimental.componentIslands }); oneShot.rebuild(inputFiles); pages = oneShot.emit(); } return augmentAndResolve(pages, ctx?.trackedFiles ?? new Set(inputFiles.map((f) => f.path)), nuxt, originalPagePaths); } async function augmentAndResolve(pages, trackedFiles, nuxt = useNuxt(), originalPagePaths) { const shouldAugment = nuxt.options.experimental.scanPageMeta || nuxt.options.experimental.typedPages; if (shouldAugment === false) { await nuxt.callHook("pages:extend", pages); return pages; } const extraPageMetaExtractionKeys = nuxt.options?.experimental?.extraPageMetaExtractionKeys || []; const augmentCtx = { extraExtractionKeys: /* @__PURE__ */ new Set(["middleware", ...extraPageMetaExtractionKeys]), fullyResolvedPaths: trackedFiles, originalPagePaths }; if (shouldAugment === "after-resolve") { await nuxt.callHook("pages:extend", pages); await augmentPages(pages, nuxt.vfs, augmentCtx); } else { const augmentedPages = await augmentPages(pages, nuxt.vfs, augmentCtx); await nuxt.callHook("pages:extend", pages); await augmentPages(pages, nuxt.vfs, { pagesToSkip: augmentedPages, ...augmentCtx }); augmentedPages?.clear(); } await nuxt.callHook("pages:resolved", pages); return pages; } async function augmentPages(routes, vfs, ctx = {}) { ctx.augmentedPages ??= /* @__PURE__ */ new Set(); for (const route of routes) { if (route.file && !ctx.pagesToSkip?.has(route.file)) { const routeMeta = getRouteMeta(vfs[route.file] ?? fs.readFileSync(ctx.fullyResolvedPaths?.has(route.file) ? route.file : await resolvePath(route.file), "utf-8"), route.file, ctx.extraExtractionKeys); if (route.meta) routeMeta.meta = defu({}, routeMeta.meta, route.meta); if (route.rules) routeMeta.rules = defu({}, routeMeta.rules, route.rules); if (ctx.augmentedPages.has(route.file)) { delete routeMeta.name; delete routeMeta.path; } if (routeMeta.path !== void 0 && routeMeta.path !== route.path) ctx.originalPagePaths?.set(route, route.path); Object.assign(route, routeMeta); ctx.augmentedPages.add(route.file); } if (route.children && route.children.length > 0) await augmentPages(route.children, vfs, ctx); } return ctx.augmentedPages; } const SFC_SCRIPT_RE = /<script(?=\s|>)(?<attrs>[^>]*)>(?<content>[\s\S]*?)<\/script\s*>/gi; function extractScriptContent(sfc) { const contents = []; for (const match of sfc.matchAll(SFC_SCRIPT_RE)) if (match?.groups?.content) contents.push({ loader: match.groups.attrs && /[tj]sx/.test(match.groups.attrs) ? "tsx" : "ts", code: match.groups.content.trim() }); return contents; } const PAGE_EXTRACT_RE = new RegExp(`\\b(${["definePageMeta", "defineRouteRules"].join("|")})\\b`, "g"); const defaultExtractionKeys = [ "name", "path", "props", "alias", "redirect", "middleware" ]; const DYNAMIC_META_KEY = "__nuxt_dynamic_meta_key"; const STATIC_EXPRESSION_WRAPPERS = /* @__PURE__ */ new Set([ "ParenthesizedExpression", "TSAsExpression", "TSNonNullExpression", "TSSatisfiesExpression", "TSTypeAssertion" ]); function unwrapStaticExpression(node) { let current = node; while (current && STATIC_EXPRESSION_WRAPPERS.has(current.type)) current = current.expression; return current; } const pageContentsCache = {}; const extractCache = {}; function getRouteMeta(contents, absolutePath, extraExtractionKeys = /* @__PURE__ */ new Set()) { if (!(absolutePath in pageContentsCache) || pageContentsCache[absolutePath] !== contents) { pageContentsCache[absolutePath] = contents; delete extractCache[absolutePath]; } if (absolutePath in extractCache && extractCache[absolutePath]) return klona(extractCache[absolutePath]); const loader = getLoader(absolutePath); const scriptBlocks = !loader ? null : loader === "vue" ? extractScriptContent(contents) : [{ code: contents, loader }]; if (!scriptBlocks) { extractCache[absolutePath] = {}; return {}; } const extractedData = {}; const extractionKeys = /* @__PURE__ */ new Set([...defaultExtractionKeys, ...extraExtractionKeys]); for (const script of scriptBlocks) { const found = {}; for (const macro of script.code.matchAll(PAGE_EXTRACT_RE)) found[macro[1]] = false; if (Object.keys(found).length === 0) continue; const dynamicProperties = /* @__PURE__ */ new Set(); parseAndWalk(script.code, absolutePath.replace(/\.\w+$/, "." + script.loader), (node) => { if (node.type !== "ExpressionStatement" || node.expression.type !== "CallExpression" || node.expression.callee.type !== "Identifier") return; const fnName = node.expression.callee.name; if (fnName in found === false || found[fnName] !== false) return; found[fnName] = true; const code = script.code; const pageExtractArgument = unwrapStaticExpression(node.expression.arguments[0]); if (pageExtractArgument?.type !== "ObjectExpression") { pageDiagnostics.NUXT_B4005({ fnName, file: absolutePath, receivedType: String(pageExtractArgument?.type) }); return; } if (fnName === "defineRouteRules") { const { value, serializable } = isSerializable(code, pageExtractArgument); if (!serializable) { pageDiagnostics.NUXT_B4006({ fnName, file: absolutePath }); return; } extractedData.rules = value; return; } if (fnName === "definePageMeta") { for (const key of extractionKeys) { const property = pageExtractArgument.properties.find((property) => property.type === "Property" && property.key.type === "Identifier" && property.key.name === key); if (!property) continue; const { value, serializable } = isSerializable(code, property.value); if (!serializable) { logger.debug(`Skipping extraction of \`${key}\` metadata as it is not JSON-serializable (reading \`${absolutePath}\`).`); dynamicProperties.add(extraExtractionKeys.has(key) ? "meta" : key); continue; } if (extraExtractionKeys.has(key)) { extractedData.meta ??= {}; extractedData.meta[key] = value; } else extractedData[key] = value; } for (const property of pageExtractArgument.properties) { if (property.type !== "Property") continue; if (!(property.key.type === "Literal" || property.key.type === "Identifier")) continue; const name = property.key.type === "Identifier" ? property.key.name : String(property.value); if (!extractionKeys.has(name)) { dynamicProperties.add("meta"); break; } } if (dynamicProperties.size) { extractedData.meta ??= {}; extractedData.meta[DYNAMIC_META_KEY] = dynamicProperties; } } }); } extractCache[absolutePath] = extractedData; return klona(extractedData); } function serializeRouteValue(value, skipSerialisation = false) { if (skipSerialisation || value === void 0) return; return JSON.stringify(value); } function normalizeComponent(page, pageImport, routeName, islandKey) { if (page.mode === "server") return `() => createIslandPage(${routeName}, import.meta.server ? ${islandKey} : undefined)`; if (page.mode === "client") return `() => createClientPage(${pageImport})`; return pageImport; } function normalizeComponentWithName(page, isSyncImport, pageImportName, pageImport, routeName, metaRouteName, islandKey) { if (isSyncImport) return `Object.assign(${pageImportName}, { __name: ${metaRouteName} })`; if (page.mode === "server") return `() => createIslandPage(${routeName}, import.meta.server ? ${islandKey} : undefined)`; if (page.mode === "client") return `() => createClientPage(${pageImport}).then((c) => Object.assign(c, { __name: ${metaRouteName} }))`; return `${pageImport}.then((m) => Object.assign(m.default, { __name: ${metaRouteName} }))`; } function normalizeRoutes(routes, metaImports = /* @__PURE__ */ new Set(), options) { const nuxt = useNuxt(); return { imports: metaImports, routes: genArrayFromRaw(routes.map((page) => { const markedDynamic = page.meta?.[DYNAMIC_META_KEY] ?? /* @__PURE__ */ new Set(); const metaFiltered = {}; let skipMeta = true; for (const key in page.meta || {}) if (key !== DYNAMIC_META_KEY && page.meta[key] !== void 0) { skipMeta = false; metaFiltered[key] = page.meta[key]; } const skipAlias = toArray(page.alias).every((val) => !val); const route = { path: serializeRouteValue(page.path), props: serializeRouteValue(page.props), name: serializeRouteValue(page.name), meta: serializeRouteValue(metaFiltered, skipMeta), alias: serializeRouteValue(toArray(page.alias), skipAlias), redirect: serializeRouteValue(page.redirect) }; for (const key of [...defaultExtractionKeys, "meta"]) if (route[key] === void 0) delete route[key]; if (page.children?.length) route.children = normalizeRoutes(page.children, metaImports, options).routes; if (!page.file) return route; const file = normalize(page.file); const pageImportName = genSafeVariableName(filename(file) + hash(file).replace(/-/g, "_")); const metaImportName = pageImportName + "Meta"; metaImports.add(genImport(`${file}?macro=true`, [{ name: "default", as: metaImportName }])); if (page._sync) metaImports.add(genImport(file, [{ name: "default", as: pageImportName }])); const isSyncImport = page._sync && page.mode !== "client"; const pageImport = isSyncImport ? pageImportName : genDynamicImport(file); const metaRouteName = `${metaImportName}?.name ?? ${route.name}`; const islandKey = page.mode === "server" && page.file ? JSON.stringify(hash(relative(nuxt.options.rootDir, page.file))) : void 0; const component = nuxt.options.experimental.normalizePageNames ? normalizeComponentWithName(page, isSyncImport, pageImportName, pageImport, route.name, metaRouteName, islandKey) : normalizeComponent(page, pageImport, route.name, islandKey); let componentsObject; if (page.components) { const viewEntries = []; for (const viewName in page.components) { if (viewName === "default") continue; const viewFile = normalize(page.components[viewName]); viewEntries.push(`${JSON.stringify(viewName)}: ${genDynamicImport(viewFile)}`); } if (viewEntries.length > 0) componentsObject = `{ default: ${component}, ${viewEntries.join(", ")} }`; } const metaRoute = { name: metaRouteName, path: `${metaImportName}?.path ?? ${route.path}`, props: `${metaImportName}?.props ?? ${route.props ?? false}`, meta: `${metaImportName} || {}`, alias: `${metaImportName}?.alias || []`, redirect: `${metaImportName}?.redirect`, component }; if (componentsObject) metaRoute.components = componentsObject; if (page.mode === "server") metaImports.add(` let _createIslandPage async function createIslandPage (name, islandKey) { _createIslandPage ||= await import(${JSON.stringify(options?.serverComponentRuntime)}).then(r => r.createIslandPage) return _createIslandPage(name, islandKey) };`); else if (page.mode === "client") metaImports.add(` let _createClientPage async function createClientPage(loader) { _createClientPage ||= await import(${JSON.stringify(options?.clientComponentRuntime)}).then(r => r.createClientPage) return _createClientPage(loader); }`); if (route.children) metaRoute.children = route.children; if (route.meta) metaRoute.meta = `{ ...(${metaImportName} || {}), ...${route.meta} }`; if (options?.overrideMeta) { for (const key of ["name", "path"]) { if (markedDynamic.has(key)) continue; metaRoute[key] = route[key] ?? `${metaImportName}?.${key}`; } for (const key of [ "meta", "alias", "redirect", "props" ]) { if (markedDynamic.has(key)) continue; if (route[key] == null) { delete metaRoute[key]; continue; } metaRoute[key] = route[key]; } } else { if (route.alias != null) metaRoute.alias = `${route.alias}.concat(${metaImportName}?.alias || [])`; if (route.redirect != null) metaRoute.redirect = route.redirect; } return metaRoute; })) }; } function resolveRoutePaths(page, parent = "/") { return [joinURL(parent, page.path), ...page.children?.flatMap((child) => resolveRoutePaths(child, joinURL(parent, page.path))) || []]; } const EMPTY_PARAM_REGEXP_RE = /:(\w+)\(\)([+*?]?)/g; function canonicalizeParams(path) { return path.replace(EMPTY_PARAM_REGEXP_RE, ":$1$2"); } /** * Strip the leading segments an absolute child path shares with its parent's `fullPath`. * Inserting only the remainder avoids re-declaring the parent's params on the child node. * The remainder only determines the node's own params, as the absolute child path is * re-applied as a path override afterwards and becomes the route's `fullPath`. A child * path that shares nothing with its parent therefore still resolves correctly. */ function relativizeToParent(parentFullPath, childPath) { if (!childPath.startsWith("/")) return childPath; const parentSegments = canonicalizeParams(parentFullPath).split("/"); const childSegments = childPath.split("/"); let index = 0; while (index < parentSegments.length && index < childSegments.length && canonicalizeParams(childSegments[index]) === parentSegments[index]) index++; return index === 0 ? childPath : childSegments.slice(index).join("/"); } function isSerializable(code, node) { node = unwrapStaticExpression(node) || node; if (node.type === "Literal") { if (typeof node.value === "string" || typeof node.value === "number" || typeof node.value === "boolean" || node.value === null) return { value: node.value, serializable: true }; return { serializable: false }; } if (node.type === "UnaryExpression" && (node.operator === "-" || node.operator === "+")) { const arg = node.argument; if (arg.type === "Literal" && typeof arg.value === "number") return { value: node.operator === "-" ? -arg.value : arg.value, serializable: true }; return { serializable: false }; } if (node.type === "ArrayExpression") { const values = []; for (const element of node.elements) { if (!element || element.type === "SpreadElement") return { serializable: false }; const { serializable, value } = isSerializable(code, element); if (!serializable) return { serializable: false }; values.push(value); } return { value: values, serializable: true }; } if (node.type === "ObjectExpression") { const value = {}; for (const property of node.properties) { if (property.type !== "Property" || property.computed || property.kind !== "init" || property.method) return { serializable: false }; let key; if (property.key.type === "Identifier") key = property.key.name; else if (property.key.type === "Literal" && (typeof property.key.value === "string" || typeof property.key.value === "number")) key = String(property.key.value); else return { serializable: false }; const { serializable, value: propertyValue } = isSerializable(code, property.value); if (!serializable) return { serializable: false }; value[key] = propertyValue; } return { value, serializable: true }; } return { serializable: false }; } function toRou3Patterns(pages, prefix = "/") { const routes = []; for (const page of pages) { const path = page.path.replace(/\([^)]*\)/g, "").replace(/:(\w+)\*.*/g, (_, name) => `**:${name}`).replace(/:([^/*]*)/g, (_, name) => `:${name.replace(/\W/g, (r) => r === "?" ? "" : "_")}`); routes.push(joinURL(prefix, path)); if (page.children) routes.push(...toRou3Patterns(page.children, joinURL(prefix, path))); } return routes; } //#endregion //#region src/pages/route-rules.ts function globRouteRulesFromPages(pages) { return collectRouteRulesFromPages(pages, {}, ""); } function collectRouteRulesFromPages(pages, paths, prefix) { for (const page of pages) { if (page.rules) { if (Object.keys(page.rules).length) { const path = prefix + page.path; const { patterns, issues } = vueRouterToRou3(path, { collapse: true }); if (issues.length) for (const issue of issues) pageDiagnostics.NUXT_B4016({ path, detail: issue.message }); else for (const pattern of patterns) { if (pattern in paths && !isEqual(paths[pattern], page.rules)) pageDiagnostics.NUXT_B4017({ path, pattern }); paths[pattern] = page.rules; } } delete page.rules; } if (page.children?.length) collectRouteRulesFromPages(page.children, paths, prefix + page.path + "/"); } return paths; } function removePagesRules(routes) { for (const route of routes) { delete route.rules; if (route.children?.length) removePagesRules(route.children); } } //#endregion //#region src/pages/plugins/page-meta.ts const HAS_MACRO_RE = /\bdefinePageMeta\s*\(\s*/; const CODE_EMPTY = ` const __nuxt_page_meta = null export default __nuxt_page_meta `; const CODE_DEV_EMPTY = ` const __nuxt_page_meta = {} export default __nuxt_page_meta `; const CODE_HMR = ` // Vite if (import.meta.hot) { import.meta.hot.accept(mod => { Object.assign(__nuxt_page_meta, mod) }) } // webpack if (import.meta.webpackHot) { import.meta.webpackHot.accept((err) => { if (err) { window.location = window.location.href } }) }`; const PageMetaPlugin = (options = {}) => createUnplugin(() => { return { name: "nuxt:pages-macros-transform", enforce: "post", transform: { filter: { id: { include: /[?&]macro=true\b/, exclude: [/(?:\?|%3F).*type=(?:style|template)/] }, code: { include: [ HAS_MACRO_RE, /\bfrom\s+["'][^"'?]*\?[^"']*type=script[^"']*["']/, /export\s+\{\s*default\s*\}\s+from\s+["'][^"'?]*\?[^"']*type=script[^"']*["']/, /^(?!.*__nuxt_page_meta)(?!.*export\s+\{\s*default\s*\})(?!.*\bdefinePageMeta\s*\()[\s\S]*$/ ] } }, handler(code, id, transformMeta) { const query = parseMacroQuery(id); if (query.type && query.type !== "script") return; const s = rolldownString(code, id, transformMeta); function result() { return generateTransform(s, id); } const hasMacro = HAS_MACRO_RE.test(code); const imports = findStaticImports(code); const scriptImport = imports.find((i) => parseMacroQuery(i.specifier).type === "script"); if (scriptImport) { const reorderedQuery = rewriteQuery(scriptImport.specifier); const quotedSpecifier = getQuotedSpecifier(scriptImport.code)?.replace(scriptImport.specifier, reorderedQuery) ?? JSON.stringify(reorderedQuery); s.overwrite(0, code.length, `export { default } from ${quotedSpecifier}`); return result(); } const currentExports = findExports(code); for (const match of currentExports) { if (match.type !== "default" || !match.specifier) continue; const reorderedQuery = rewriteQuery(match.specifier); const quotedSpecifier = getQuotedSpecifier(match.code)?.replace(match.specifier, reorderedQuery) ?? JSON.stringify(reorderedQuery); s.overwrite(0, code.length, `export { default } from ${quotedSpecifier}`); return result(); } if (!hasMacro && !code.includes("export { default }") && !code.includes("__nuxt_page_meta")) { if (!code) { s.append(options.dev ? CODE_DEV_EMPTY + CODE_HMR : CODE_EMPTY); const { pathname } = parseModuleId(id); pageDiagnostics.NUXT_B4001({ pathname }); } else s.overwrite(0, code.length, options.dev ? CODE_DEV_EMPTY + CODE_HMR : CODE_EMPTY); return result(); } const importMap = /* @__PURE__ */ new Map(); const addedImports = /* @__PURE__ */ new Set(); for (const i of imports) { const parsed = parseStaticImport(i); for (const name of [ parsed.defaultImport, ...Object.values(parsed.namedImports || {}), parsed.namespacedImport ].filter(Boolean)) importMap.set(name, i); } function isStaticIdentifier(name) { return !!(name && importMap.has(name)); } function addImport(name) { if (!isStaticIdentifier(name)) return; const importValue = importMap.get(name).code.trim(); if (!addedImports.has(importValue)) addedImports.add(importValue); } const declarationNodes = []; const addedDeclarations = /* @__PURE__ */ new Set(); function addDeclaration(node) { const codeSectionKey = `${resolveStart(node)}-${resolveEnd(node)}`; if (addedDeclarations.has(codeSectionKey)) return; addedDeclarations.add(codeSectionKey); declarationNodes.push(node); } /** * Adds an import or a declaration to the extracted code. * @param name The name of the import or declaration to add. * @param node The node that is currently being processed. (To detect self-references) */ function addImportOrDeclaration(name, node) { if (isStaticIdentifier(name)) addImport(name); else { const declaration = scopeTracker.getDeclaration(name); if (declaration && declaration !== node) processDeclaration(declaration); } } const scopeTracker = new ScopeTracker({ preserveExitedScopes: true }); function processDeclaration(scopeTrackerNode) { if (scopeTrackerNode?.type === "Variable") { addDeclaration(scopeTrackerNode); for (const decl of scopeTrackerNode.variableNode.declarations) { if (!decl.init) continue; walk(decl.init, { enter: (node, parent) => { if (node.type === "AwaitExpression") { const codeSnippet = code.slice(node.start, Math.min(node.end, node.start + 80)); throw pageDiagnostics.NUXT_B4002({ codeSnippet, offset: node.start }); } if (isBindingIdentifier(node, parent) || node.type !== "Identifier") return; addImportOrDeclaration(node.name, scopeTrackerNode); } }); } } else if (scopeTrackerNode?.type === "Function") { if (scopeTrackerNode.node.type === "ArrowFunctionExpression") return; if (!scopeTrackerNode.node.id?.name) return; addDeclaration(scopeTrackerNode); const undeclaredIdentifiers = getUndeclaredIdentifiersInFunction(scopeTrackerNode.node); for (const name of undeclaredIdentifiers) addImportOrDeclaration(name); } } const { program: ast } = parseAndWalk(code, id, { scopeTracker, parseOptions: { lang: query.lang ?? "ts" } }); scopeTracker.freeze(); let instances = 0; walk(ast, { scopeTracker, enter: (node) => { if (node.type !== "CallExpression" || node.callee.type !== "Identifier") return; if (!("name" in node.callee) || node.callee.name !== "definePageMeta") return; instances++; const meta = node.arguments[0]; if (!meta) return; const metaCode = code.slice(meta.start, meta.end); const m = new MagicString(metaCode); if (meta.type === "ObjectExpression") { const omitProp = (prop, i) => { const nextProperty = meta.properties[i + 1]; if (nextProperty) m.overwrite(prop.start - meta.start, nextProperty.start - meta.start, ""); else if (code[prop.end] === ",") m.overwrite(prop.start - meta.start, prop.end - meta.start + 1, ""); else m.overwrite(prop.start - meta.start, prop.end - meta.start, ""); }; for (let i = 0; i < meta.properties.length; i++) { const prop = meta.properties[i]; if (prop.type !== "Property" || prop.key.type !== "Identifier") continue; if (options.extractedKeys?.includes(prop.key.name)) { const { serializable } = isSerializable(metaCode, prop.value); if (serializable) omitProp(prop, i); } else if (prop.key.name === "layout" && prop.value.type === "ObjectExpression") { for (const layoutProp of prop.value.properties) { if (layoutProp.type !== "Property" || layoutProp.key.type !== "Identifier") continue; if (layoutProp.key.name === "name") m.appendLeft(prop.start - meta.start, `layout: ${code.slice(layoutProp.value.start, layoutProp.value.end)},\n`); else if (layoutProp.key.name === "props") m.appendLeft(prop.start - meta.start, `layoutProps: ${code.slice(layoutProp.value.start, layoutProp.value.end)},\n`); } omitProp(prop, i); } } } const definePageMetaScope = scopeTracker.getCurrentScope(); walk(meta, { scopeTracker, enter(node, parent) { if (isBindingIdentifier(node, parent) || node.type !== "Identifier") return; const declaration = scopeTracker.getDeclaration(node.name); if (declaration) { if (declaration.isUnderScope(definePageMetaScope) && (scopeTracker.isCurrentScopeUnder(declaration.scope) || resolveStart(declaration) < node.start)) return; } if (isStaticIdentifier(node.name)) addImport(node.name); else if (declaration) processDeclaration(declaration); } }); const extracted = [ Array.from(addedImports).join("\n"), declarationNodes.sort((a, b) => resolveStart(a) - resolveStart(b)).map((node) => code.slice(resolveStart(node), resolveEnd(node))).join("\n"), `const __nuxt_page_meta = ${m.toString() || "null"}\nexport default __nuxt_page_meta` + (options.dev ? CODE_HMR : "") ].join("\n"); s.overwrite(0, code.length, extracted.trim()); } }); if (instances > 1) throw pageDiagnostics.NUXT_B4003({ callCount: instances, file: id }); if (!s.hasChanged() && !code.includes("__nuxt_page_meta")) s.overwrite(0, code.length, options.dev ? CODE_DEV_EMPTY + CODE_HMR : CODE_EMPTY); return result(); } }, vite: { handleHotUpdate: { order: "post", handler: ({ file, modules, server }) => { if (options.routesId && options.isPage?.(file)) { const macroModule = server.moduleGraph.getModuleById(file + "?macro=true"); const routesModule = server.moduleGraph.getModuleById(options.routesId); const realModules = []; for (const mod of modules) if (mod.id && MACRO_STRIP_RE.test(mod.id)) { const realModule = server.moduleGraph.getModuleById(mod.id.replace(MACRO_STRIP_RE, (r) => r.endsWith("&") ? "?" : "")); if (realModule) realModules.push(realModule); } return [ ...modules, ...realModules, ...macroModule ? [macroModule] : [], ...routesModule ? [routesModule] : [] ]; } } } } }; }); const QUERY_START_RE = /^\?/; const MACRO_RE = /&macro=true/; function rewriteQuery(id) { return id.replace(/\?.+$/, (r) => "?macro=true&" + r.replace(QUERY_START_RE, "").replace(MACRO_RE, "")); } const MACRO_QUERY_RE$1 = /[?&]macro=true(?:&|$)/; const MACRO_STRIP_RE = /\?macro=true&?/; const TYPE_PARAM_RE = /[?&]type=([^?&]+)/; const LANG_PARAM_RE = /[?&]lang=([^?&]+)/; function parseMacroQuery(id) { const { search } = parseModuleId(id); const query = { type: TYPE_PARAM_RE.exec(search)?.[1], lang: LANG_PARAM_RE.exec(search)?.[1] ?? void 0 }; if (MACRO_QUERY_RE$1.test(search)) query.macro = "true"; return query; } const QUOTED_SPECIFIER_RE = /(["']).*\1/; function getQuotedSpecifier(id) { return id.match(QUOTED_SPECIFIER_RE)?.[0]; } function resolveStart(node) { return "fnNode" in node ? node.fnNode.start : node.start; } function resolveEnd(node) { return "fnNode" in node ? node.fnNode.end : node.end; } //#endregion //#region src/core/plugins/virtual.ts const PREFIX = "virtual:nuxt:"; const PREFIX_RE = /^\/?virtual:nuxt:/; function toVirtualId(absolutePath, nuxt) { return PREFIX + encodeURIComponent(relative(nuxt.options.rootDir, absolutePath)); } function fromVirtualId(id, nuxt) { const search = id.match(QUERY_RE)?.[0] || ""; const relativePart = withoutQuery(decodeURIComponent(withoutPrefix(id))); return resolve(nuxt.options.rootDir, relativePart) + search; } const RELATIVE_ID_RE = /^\.{1,2}[\\/]/; const VirtualFSPlugin = (nuxt, options) => createUnplugin((_, meta) => { const extensions = ["", ...nuxt.options.extensions]; const alias = { ...nuxt.options.alias, ...options.alias }; const resolveWithExt = (id) => { for (const suffix of ["", "." + options.mode]) for (const ext of extensions) { const rId = id + suffix + ext; if (rId in nuxt.vfs) return rId; } }; function resolveId(id, importer) { id = resolveAlias(id, alias); if (PREFIX_RE.test(id)) id = fromVirtualId(id, nuxt); const search = id.match(QUERY_RE)?.[0] || ""; id = withoutQuery(id); if (process.platform === "win32" && isAbsolute(id)) id = resolve(id); const resolvedId = resolveWithExt(id); if (resolvedId) return toVirtualId(resolvedId, nuxt) + search; if (importer && RELATIVE_ID_RE.test(id)) { const path = resolve(dirname(withoutQuery(fromVirtualId(importer, nuxt))), id); const resolved = resolveWithExt(path); if (resolved) return toVirtualId(resolved, nuxt) + search; } } const relevantAliases = /* @__PURE__ */ new Set(); for (const key in alias) { const value = alias[key]; if (value && Object.keys(nuxt.vfs).some((vfsPath) => vfsPath.startsWith(value))) relevantAliases.add(escapeDirectory(key)); } const vfsEntries = /* @__PURE__ */ new Set(); for (const key in nuxt.vfs) if (!key.startsWith("#build/") && !key.startsWith(nuxt.options.buildDir)) vfsEntries.add(escapeDirectory(dirname(key))); const filter = { id: [ PREFIX_RE, RELATIVE_ID_RE, /^#build\//, new RegExp("^(\\w:)?" + escapeDirectory(nuxt.options.buildDir)), ...Array.from(vfsEntries).map((id) => new RegExp("^" + id)), ...relevantAliases.size ? [new RegExp("^" + Array.from(relevantAliases).join("|") + "([\\\\/]|$)")] : [] ] }; return { name: "nuxt:virtual", resolveId: meta.framework === "vite" ? void 0 : { order: "pre", filter, handler: resolveId }, vite: { resolveId: { order: "pre", filter, handler(id, importer) { const res = resolveId(id, importer); if (res) return res; if (importer && PREFIX_RE.test(importer) && RELATIVE_ID_RE.test(id)) return this.resolve?.(id, withoutQuery(fromVirtualId(importer, nuxt)), { skipSelf: true }); } } }, load: { filter: { id: PREFIX_RE }, handler(id) { const key = withoutQuery(fromVirtualId(id, nuxt)); return { code: nuxt.vfs[key] || "", map: null }; } } }; }); function withoutPrefix(id) { return id.replace(PREFIX_RE, ""); } const QUERY_RE = /\?.*$/; function withoutQuery(id) { return id.replace(QUERY_RE, ""); } function escapeDirectory(path) { return escapeStringRegexp(path).replace(/\//g, "[\\\\/]"); } //#endregion //#region src/pages/plugins/route-injection.ts const INJECTION_SINGLE_RE = /\bthis\.\$route\b|\b_ctx\.\$route\b/; const RouteInjectionPlugin = (_nuxt) => createUnplugin(() => { return { name: "nuxt:route-injection-plugin", enforce: "post", transformInclude(id) { return isVue(id, { type: ["template", "script"] }); }, transform: { filter: { code: { include: INJECTION_SINGLE_RE, exclude: [`_ctx._.provides[__nuxt_route_symbol`, "this._.provides[__nuxt_route_symbol"] } }, handler(code, id, meta) { const s = rolldownString(code, id, meta); parseAndWalk(code, id, (node) => { if (node.type !== "MemberExpression") return; if (node.object.type === "ThisExpression" && node.property.type === "Identifier" && node.property.name === "$route") { s.overwrite(node.start, node.end, "(this._.provides[__nuxt_route_symbol] || this.$route)"); return; } if (node.object.type === "Identifier" && node.object.name === "_ctx" && node.property.type === "Identifier" && node.property.name === "$route") s.overwrite(node.start, node.end, "(_ctx._.provides[__nuxt_route_symbol] || _ctx.$route)"); }); if (s.hasChanged()) s.prepend("import { PageRouteSymbol as __nuxt_route_symbol } from '#app/components/injections';\n"); return generateTransform(s, id); } } }; }); //#endregion //#region src/pages/module.ts const OPTIONAL_PARAM_RE = /^\/?:.*(?:\?|\(\.\*\)\*)$/; const pagesImportPresets = [ { imports: ["definePageMeta"], from: "#app/composables/pages" }, { imports: ["PageMeta"], from: "#app/composables/pages", type: true }, { imports: ["useLink"], from: "vue-router" } ]; const routeRulesPresets = [{ imports: ["defineRouteRules"], from: "#app/composables/pages" }]; async function resolveRouterOptions(nuxt, builtInRouterOptions) { const context = { files: [] }; for (const layer of nuxt.options._layers) { const path = await findPath(resolve(layer.config.srcDir, layer.config.dir?.app || "app", "router.options")); if (path) context.files.unshift({ path }); } context.files.unshift({ path: builtInRouterOptions, optional: true }); await nuxt.callHook("pages:routerOptions", context); return context.files; } var module_default$4 = defineNuxtModule({ meta: { name: "nuxt:pages", configKey: "pages" }, defaults: (nuxt) => ({ enabled: typeof nuxt.options.pages === "boolean" ? nuxt.options.pages : void 0, pattern: `**/*{${nuxt.options.extensions.join(",")}}` }), async setup(_options, nuxt) { const runtimeDir = resolve(distDir, "pages/runtime"); const options = typeof _options === "boolean" ? { enabled: _options ?? nuxt.options.pages, pattern: `**/*{${nuxt.options.extensions.join(",")}}` } : { ..._options }; options.pattern = Array.isArray(options.pattern) ? [...new Set(options.pattern)] : options.pattern; let inlineRulesCache = {}; let updateRouteConfig; if (nuxt.options.experimental.inlineRouteRules) nuxt.hook("nitro:init", (nitro) => { updateRouteConfig = async (inlineRules) => { if (!isEqual(inlineRulesCache, inlineRules)) { await nitro.updateConfig({ routeRules: defu(inlineRules, nitro.options._config.routeRules) }); inlineRulesCache = inlineRules; } }; }); const useExperimentalTypedPages = nuxt.options.experimental.typedPages; const builtInRouterOptions = await findPath(resolve(runtimeDir, "router.options")) || resolve(runtimeDir, "router.options"); const pagesDirs = getLayerDirectories(nuxt).map((dirs) => dirs.appPages); const pagesCtx = nuxt.options.dev ? createPagesContext({ roots: pagesDirs, shouldUseServerComponents: !!nuxt.options.experimental.componentIslands }) : void 0; const isPagePattern = picomatch(Array.isArray(options.pattern) ? options.pattern : [options.pattern]); const originalPagePaths = /* @__PURE__ */ new WeakMap(); const handleRouteRules = async (pages) => { if (nuxt.options.experimental.inlineRouteRules) { const routeRules = globRouteRulesFromPages(pages); await updateRouteConfig?.(routeRules); } else removePagesRules(pages); }; const resolvePagesRoutes$1 = async (pattern, nuxt) => { const pages = await resolvePagesRoutes(pattern, nuxt, pagesCtx, originalPagePaths); await handleRouteRules(pages); return pages; }; /** Emit from existing tree + augment + hooks + route rules (used for incremental updates). */ const augmentAndResolvePages = async (pages, trackedFiles, nuxt) => { const resolved = await augmentAndResolve(pages, trackedFiles, nuxt, originalPagePaths); await handleRouteRules(resolved); return resolved; }; nuxt.options.alias["#vue-router"] = "vue-router"; const routerPath = (await resolveTypePaths(["vue-router"], nuxt.options.modulesDir))[0]?.[1] || "vue-router"; nuxt.hook("prepare:types", ({ tsConfig }) => { tsConfig.compilerOptions ||= {}; tsConfig.compilerOptions.paths ||= {}; tsConfig.compilerOptions.paths["#vue-router"] = [routerPath]; delete tsConfig.compilerOptions.paths["#vue-router/*"]; }); const isNonEmptyDir = (dir) => existsSync(dir) && readdirSync(dir).length; const userPreference = options.enabled; const isPagesEnabled = async () => { if (typeof userPreference === "boolean") return userPreference; if ((await resolveRouterOptions(nuxt, builtInRouterOptions)).filter((p) => !p.optional).length > 0) return true; if (pagesDirs.some((dir) => isNonEmptyDir(dir))) return true; const pages = await resolvePagesRoutes$1(options.pattern, nuxt); if (pages.length) { if (nuxt.apps.default) nuxt.apps.default.pages = pages; return true; } return false; }; options.enabled = await isPagesEnabled(); nuxt.options.pages = options; Object.defineProperty(nuxt.options.pages, "toString", { enumerable: false, get: () => () => options.enabled }); if (nuxt.options.dev && options.enabled) addPlugin(resolve(runtimeDir, "plugins/check-if-page-unused")); nuxt.hook("app:templates", (app) => { if (!nuxt.options.ssr && app.pages?.some((p) => p.mode === "server")) pageDiagnostics.NUXT_B4008(); }); const restartPaths = nuxt.options._layers.flatMap((layer) => { const pagesDir = (layer.config.rootDir === nuxt.options.rootDir ? nuxt.options.dir : layer.config.dir)?.pages || "pages"; return [resolve(layer.config.srcDir || layer.cwd, layer.config.dir?.app || "app", "router.options.ts"), resolve(layer.config.srcDir |