import fs, { promises, existsSync, readdirSync, writeFileSync, statSync, readFileSync, mkdirSync } from 'node:fs'; import { mkdir, readFile, readdir, writeFile, rm, stat, unlink, open } from 'node:fs/promises'; import { randomUUID } from 'node:crypto'; import { AsyncLocalStorage } from 'node:async_hooks'; import { dirname, resolve, normalize, basename, extname, relative, isAbsolute, join } from 'pathe'; import { createHooks, createDebugger } from 'hookable'; import ignore from 'ignore'; import { tryUseNuxt, directoryToURL, useLogger, useNuxt, resolveFiles, resolvePath, defineNuxtModule, findPath, addPlugin, addTemplate, addTypeTemplate, addComponent, useNitro, addBuildPlugin, isIgnored, resolveAlias, addPluginTemplate, addVitePlugin, createIsIgnored, updateTemplates, tryResolveModule, normalizeModuleTranspilePath, resolveNuxtModule, resolveIgnorePatterns, logger as logger$1, createResolver, importModule, tryImportModule, runWithNuxtContext, nuxtCtx, loadNuxtConfig, addWebpackPlugin, addServerPlugin, installModule, addServerTemplate, addServerHandler, addRouteMiddleware, normalizeTemplate, compileTemplate, normalizePlugin, templateUtils } from '@nuxt/kit'; import { resolvePackageJSON, readPackageJSON } from 'pkg-types'; import { hash, serialize } from 'ohash'; import consola, { consola as consola$1 } from 'consola'; import onChange from 'on-change'; import { colors } from 'consola/utils'; import { resolveCompatibilityDatesFromEnv, formatDate } from 'compatx'; import escapeRE from 'escape-string-regexp'; import { withTrailingSlash, parseURL, parseQuery, joinURL, withLeadingSlash, encodePath, withoutLeadingSlash } from 'ufo'; import { ImpoundPlugin } from 'impound'; import defu$1, { defu } from 'defu'; import { satisfies, gt } from 'semver'; import { isWindows, hasTTY, isCI } from 'std-env'; import { genArrayFromRaw, genSafeVariableName, genImport, genDynamicImport, genObjectFromRawEntries, genString, genExport } from 'knitwork'; import { resolveModulePath } from 'exsolve'; import { createRoutesContext } from 'unplugin-vue-router'; import { resolveOptions } from 'unplugin-vue-router/options'; import { toRouteMatcher, createRouter, exportMatcher } from 'radix3'; import { fileURLToPath, pathToFileURL } from 'node:url'; import { runInNewContext } from 'node:vm'; import { filename } from 'pathe/utils'; import { klona } from 'klona'; import { walk as walk$1 } from 'estree-walker'; import { transform as transform$1 } from 'esbuild'; import { parseSync } from 'oxc-parser'; import { splitByCase, kebabCase, pascalCase, camelCase } from 'scule'; import { createUnplugin } from 'unplugin'; import { findStaticImports, findExports, parseStaticImport, parseNodeModulePath, lookupNodeModuleSubpath } from 'mlly'; import MagicString from 'magic-string'; import { stripLiteral } from 'strip-literal'; import { unheadVueComposablesImports } from '@unhead/vue'; import { globby } from 'globby'; import { parse, walk as walk$2, ELEMENT_NODE } from 'ultrahtml'; import { createUnimport, defineUnimportPreset, toExports, scanDirExports } from 'unimport'; import { parseQuery as parseQuery$1 } from 'vue-router'; import { createTransformer } from 'unctx/transform'; import { cpus } from 'node:os'; import { createNitro, scanHandlers, writeTypes, copyPublicAssets, prepare, build as build$1, prerender, createDevServer } from 'nitropack'; import { dynamicEventHandler } from 'h3'; import { watch as watch$1 } from 'chokidar'; import { debounce } from 'perfect-debounce'; import { resolveSchema, generateTypes } from 'untyped'; import untypedPlugin from 'untyped/babel-plugin'; import { createJiti } from 'jiti'; import { resolve as resolve$1 } from 'node:path'; import { glob } from 'tinyglobby'; import { parseTar, createTar } from 'nanotar'; let _distDir = dirname(fileURLToPath(import.meta.url)); if (_distDir.match(/(chunks|shared)$/)) { _distDir = dirname(_distDir); } const distDir = _distDir; const pkgDir = resolve(distDir, ".."); async function resolveTypePath(path, subpath, searchPaths = tryUseNuxt()?.options.modulesDir) { try { const r = resolveModulePath(path, { from: searchPaths?.map((d) => directoryToURL(d)), conditions: ["types", "import", "require"], extensions: [".js", ".mjs", ".cjs", ".ts", ".mts", ".cts"] }); if (subpath) { return r.replace(/(?:\.d)?\.[mc]?[jt]s$/, ""); } const rootPath = await resolvePackageJSON(r); return dirname(rootPath); } catch { return null; } } function toArray(value) { return Array.isArray(value) ? value : [value]; } async function isDirectory$1(path) { return (await promises.lstat(path)).isDirectory(); } const logger = useLogger("nuxt"); async function transform(input, options) { return await transform$1(input, { ...tryUseNuxt()?.options.esbuild.options, ...options }); } function walk(ast, callback) { return walk$1(ast, { enter(node, parent, key, index) { callback.scopeTracker?.processNodeEnter(node); callback.enter?.call(this, node, parent, { key, index, ast }); }, leave(node, parent, key, index) { callback.scopeTracker?.processNodeLeave(node); callback.leave?.call(this, node, parent, { key, index, ast }); } }); } function parseAndWalk(code, sourceFilename, callback) { const lang = sourceFilename.match(/\.[cm]?([jt]sx?)$/)?.[1] || "ts"; const ast = parseSync(sourceFilename, code, { sourceType: "module", lang }); walk(ast.program, typeof callback === "function" ? { enter: callback } : callback); return ast.program; } function withLocations(node) { return node; } function isChildScope(a, b) { return a.startsWith(b) && a.length > b.length; } class BaseNode { scope; node; constructor(node, scope) { this.node = node; this.scope = scope; } /** * Check if the node is defined under a specific scope. * @param scope */ isUnderScope(scope) { return isChildScope(this.scope, scope); } } class IdentifierNode extends BaseNode { type = "Identifier"; get start() { return this.node.start; } get end() { return this.node.end; } } class FunctionParamNode extends BaseNode { type = "FunctionParam"; fnNode; constructor(node, scope, fnNode) { super(node, scope); this.fnNode = fnNode; } get start() { return this.fnNode.start; } get end() { return this.fnNode.end; } } class FunctionNode extends BaseNode { type = "Function"; get start() { return this.node.start; } get end() { return this.node.end; } } class VariableNode extends BaseNode { type = "Variable"; variableNode; constructor(node, scope, variableNode) { super(node, scope); this.variableNode = variableNode; } get start() { return this.variableNode.start; } get end() { return this.variableNode.end; } } class ImportNode extends BaseNode { type = "Import"; importNode; constructor(node, scope, importNode) { super(node, scope); this.importNode = importNode; } get start() { return this.importNode.start; } get end() { return this.importNode.end; } } class CatchParamNode extends BaseNode { type = "CatchParam"; catchNode; constructor(node, scope, catchNode) { super(node, scope); this.catchNode = catchNode; } get start() { return this.catchNode.start; } get end() { return this.catchNode.end; } } class ScopeTracker { scopeIndexStack = []; scopeIndexKey = ""; scopes = /* @__PURE__ */ new Map(); options; isFrozen = false; constructor(options = {}) { this.options = options; } updateScopeIndexKey() { this.scopeIndexKey = this.scopeIndexStack.slice(0, -1).join("-"); } pushScope() { this.scopeIndexStack.push(0); this.updateScopeIndexKey(); } popScope() { this.scopeIndexStack.pop(); if (this.scopeIndexStack[this.scopeIndexStack.length - 1] !== void 0) { this.scopeIndexStack[this.scopeIndexStack.length - 1]++; } if (!this.options.keepExitedScopes) { this.scopes.delete(this.scopeIndexKey); } this.updateScopeIndexKey(); } declareIdentifier(name, data) { if (this.isFrozen) { return; } let scope = this.scopes.get(this.scopeIndexKey); if (!scope) { scope = /* @__PURE__ */ new Map(); this.scopes.set(this.scopeIndexKey, scope); } scope.set(name, data); } declareFunctionParameter(param, fn) { if (this.isFrozen) { return; } const identifiers = getPatternIdentifiers(param); for (const identifier of identifiers) { this.declareIdentifier(identifier.name, new FunctionParamNode(identifier, this.scopeIndexKey, fn)); } } declarePattern(pattern, parent) { if (this.isFrozen) { return; } const identifiers = getPatternIdentifiers(pattern); for (const identifier of identifiers) { this.declareIdentifier( identifier.name, parent.type === "VariableDeclaration" ? new VariableNode(identifier, this.scopeIndexKey, parent) : parent.type === "CatchClause" ? new CatchParamNode(identifier, this.scopeIndexKey, parent) : new FunctionParamNode(identifier, this.scopeIndexKey, parent) ); } } processNodeEnter(node) { switch (node.type) { case "Program": case "BlockStatement": case "StaticBlock": this.pushScope(); break; case "FunctionDeclaration": if (node.id?.name) { this.declareIdentifier(node.id.name, new FunctionNode(node, this.scopeIndexKey)); } this.pushScope(); for (const param of node.params) { this.declareFunctionParameter(withLocations(param), node); } break; case "FunctionExpression": this.pushScope(); if (node.id?.name) { this.declareIdentifier(node.id.name, new FunctionNode(node, this.scopeIndexKey)); } this.pushScope(); for (const param of node.params) { this.declareFunctionParameter(withLocations(param), node); } break; case "ArrowFunctionExpression": this.pushScope(); for (const param of node.params) { this.declareFunctionParameter(withLocations(param), node); } break; case "VariableDeclaration": for (const decl of node.declarations) { this.declarePattern(withLocations(decl.id), node); } break; case "ClassDeclaration": if (node.id?.name) { this.declareIdentifier(node.id.name, new IdentifierNode(withLocations(node.id), this.scopeIndexKey)); } break; case "ClassExpression": this.pushScope(); if (node.id?.name) { this.declareIdentifier(node.id.name, new IdentifierNode(withLocations(node.id), this.scopeIndexKey)); } break; case "ImportDeclaration": for (const specifier of node.specifiers) { this.declareIdentifier(specifier.local.name, new ImportNode(withLocations(specifier), this.scopeIndexKey, node)); } break; case "CatchClause": this.pushScope(); if (node.param) { this.declarePattern(withLocations(node.param), node); } break; case "ForStatement": case "ForOfStatement": case "ForInStatement": this.pushScope(); if (node.type === "ForStatement" && node.init?.type === "VariableDeclaration") { for (const decl of node.init.declarations) { this.declarePattern(withLocations(decl.id), withLocations(node.init)); } } else if ((node.type === "ForOfStatement" || node.type === "ForInStatement") && node.left.type === "VariableDeclaration") { for (const decl of node.left.declarations) { this.declarePattern(withLocations(decl.id), withLocations(node.left)); } } break; } } processNodeLeave(node) { switch (node.type) { case "Program": case "BlockStatement": case "CatchClause": case "FunctionDeclaration": case "ArrowFunctionExpression": case "StaticBlock": case "ClassExpression": case "ForStatement": case "ForOfStatement": case "ForInStatement": this.popScope(); break; case "FunctionExpression": this.popScope(); this.popScope(); break; } } isDeclared(name) { if (!this.scopeIndexKey) { return this.scopes.get("")?.has(name) || false; } const indices = this.scopeIndexKey.split("-").map(Number); for (let i = indices.length; i >= 0; i--) { if (this.scopes.get(indices.slice(0, i).join("-"))?.has(name)) { return true; } } return false; } getDeclaration(name) { if (!this.scopeIndexKey) { return this.scopes.get("")?.get(name) ?? null; } const indices = this.scopeIndexKey.split("-").map(Number); for (let i = indices.length; i >= 0; i--) { const node = this.scopes.get(indices.slice(0, i).join("-"))?.get(name); if (node) { return node; } } return null; } getCurrentScope() { return this.scopeIndexKey; } /** * Check if the current scope is a child of a specific scope. * @example * ```ts * // current scope is 0-1 * isCurrentScopeUnder('0') // true * isCurrentScopeUnder('0-1') // false * ``` * * @param scope the parent scope * @returns `true` if the current scope is a child of the specified scope, `false` otherwise (also when they are the same) */ isCurrentScopeUnder(scope) { return isChildScope(this.scopeIndexKey, scope); } /** * Freezes the scope tracker, preventing further declarations. * It also resets the scope index stack to its initial state, so that the scope tracker can be reused. * * This is useful for second passes through the AST. */ freeze() { this.isFrozen = true; this.scopeIndexStack = []; this.updateScopeIndexKey(); } } function getPatternIdentifiers(pattern) { const identifiers = []; function collectIdentifiers(pattern2) { switch (pattern2.type) { case "Identifier": identifiers.push(pattern2); break; case "AssignmentPattern": collectIdentifiers(withLocations(pattern2.left)); break; case "RestElement": collectIdentifiers(withLocations(pattern2.argument)); break; case "ArrayPattern": for (const element of pattern2.elements) { if (element) { collectIdentifiers(withLocations(element.type === "RestElement" ? element.argument : element)); } } break; case "ObjectPattern": for (const property of pattern2.properties) { collectIdentifiers(withLocations(property.type === "RestElement" ? property.argument : property.value)); } break; } } collectIdentifiers(pattern); return identifiers; } function isNotReferencePosition(node, parent) { if (!parent || node.type !== "Identifier") { return false; } switch (parent.type) { case "FunctionDeclaration": case "FunctionExpression": case "ArrowFunctionExpression": if (parent.type !== "ArrowFunctionExpression" && parent.id === node) { return true; } if (parent.params.length) { for (const param of parent.params) { const identifiers = getPatternIdentifiers(withLocations(param)); if (identifiers.includes(node)) { return true; } } } return false; case "ClassDeclaration": case "ClassExpression": return parent.id === node; case "MethodDefinition": return parent.key === node; case "PropertyDefinition": return parent.key === node; case "VariableDeclarator": return getPatternIdentifiers(withLocations(parent.id)).includes(node); case "CatchClause": if (!parent.param) { return false; } return getPatternIdentifiers(withLocations(parent.param)).includes(node); case "Property": return parent.key === node && parent.value !== node; case "MemberExpression": return parent.property === node; } return false; } function getUndeclaredIdentifiersInFunction(node) { const scopeTracker = new ScopeTracker({ keepExitedScopes: true }); const undeclaredIdentifiers = /* @__PURE__ */ new Set(); function isIdentifierUndeclared(node2, parent) { return !isNotReferencePosition(node2, parent) && !scopeTracker.isDeclared(node2.name); } walk(node, { scopeTracker }); scopeTracker.freeze(); walk(node, { scopeTracker, enter(node2, parent) { if (node2.type === "Identifier" && isIdentifierUndeclared(node2, parent)) { undeclaredIdentifiers.add(node2.name); } } }); return Array.from(undeclaredIdentifiers); } 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)); const segments = resolveComponentNameSegments(fileName.toLowerCase() === "index" ? "" : fileName, prefixParts).filter(Boolean); return kebabCase(segments).replace(QUOTE_RE, ""); } function hasSuffix(path, suffix) { return basename(path, extname(path)).endsWith(suffix); } function resolveComponentNameSegments(fileName, prefixParts) { 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 + "/") || // e.g Item/Item/Item.vue -> Item prefixPart.toLowerCase() === fileNamePartsContent && prefixParts[index + 1] && prefixParts[index] === prefixParts[index + 1]) { componentNameParts.length = index; } index--; } return [...componentNameParts, ...fileNameParts]; } function isVue(id, opts = {}) { const { search } = parseURL(decodeURIComponent(pathToFileURL(id).href)); if (id.endsWith(".vue") && !search) { return true; } if (!search) { return false; } const query = parseQuery(search); if (query.nuxt_component) { return false; } if (query.macro && (search === "?macro=true" || !opts.type || opts.type.includes("script"))) { return true; } const type = "setup" in query ? "script" : query.type; if (!("vue" in query) || opts.type && !opts.type.includes(type)) { return false; } return true; } const JS_RE$1 = /\.(?:[cm]?j|t)sx?$/; function isJS(id) { const { pathname } = parseURL(decodeURIComponent(pathToFileURL(id).href)); return JS_RE$1.test(pathname); } function getLoader(id) { const { pathname } = parseURL(decodeURIComponent(pathToFileURL(id).href)); const ext = extname(pathname); if (ext === ".vue") { return "vue"; } if (!JS_RE$1.test(ext)) { return null; } return ext.endsWith("x") ? "tsx" : "ts"; } function matchWithStringOrRegex(value, matcher) { if (typeof matcher === "string") { return value === matcher; } else if (matcher instanceof RegExp) { return matcher.test(value); } return false; } 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$/; const enUSComparator = new Intl.Collator("en-US"); async function resolvePagesRoutes(pattern, nuxt = useNuxt()) { const pagesDirs = nuxt.options._layers.map( (layer) => resolve(layer.config.srcDir, (layer.config.rootDir === nuxt.options.rootDir ? nuxt.options.dir : layer.config.dir)?.pages || "pages") ); const scannedFiles = []; for (const dir of pagesDirs) { const files = await resolveFiles(dir, pattern); scannedFiles.push(...files.map((file) => ({ relativePath: relative(dir, file), absolutePath: file }))); } scannedFiles.sort((a, b) => enUSComparator.compare(a.relativePath, b.relativePath)); const allRoutes = generateRoutesFromFiles(uniqueBy(scannedFiles, "relativePath"), { shouldUseServerComponents: !!nuxt.options.experimental.componentIslands }); const pages = uniqueBy(allRoutes, "path"); const shouldAugment = nuxt.options.experimental.scanPageMeta || nuxt.options.experimental.typedPages; if (shouldAugment === false) { await nuxt.callHook("pages:extend", pages); return pages; } const augmentCtx = { extraExtractionKeys: /* @__PURE__ */ new Set(["middleware", ...nuxt.options.experimental.extraPageMetaExtractionKeys]), fullyResolvedPaths: new Set(scannedFiles.map((file) => file.absolutePath)) }; 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; } const INDEX_PAGE_RE = /\/index$/; function generateRoutesFromFiles(files, options = {}) { if (!files.length) { return []; } const routes = []; const sortedFiles = [...files].sort((a, b) => a.relativePath.length - b.relativePath.length); for (const file of sortedFiles) { const segments = file.relativePath.replace(new RegExp(`${escapeRE(extname(file.relativePath))}$`), "").split("/"); const route = { name: "", path: "", file: file.absolutePath, children: [] }; let parent = routes; const lastSegment = segments[segments.length - 1]; if (lastSegment.endsWith(".server")) { segments[segments.length - 1] = lastSegment.replace(".server", ""); if (options.shouldUseServerComponents) { route.mode = "server"; } } else if (lastSegment.endsWith(".client")) { segments[segments.length - 1] = lastSegment.replace(".client", ""); route.mode = "client"; } for (let i = 0; i < segments.length; i++) { const segment = segments[i]; const tokens = parseSegment(segment, file.absolutePath); if (tokens.every((token) => token.type === 4 /* group */)) { continue; } const segmentName = tokens.map(({ value, type }) => type === 4 /* group */ ? "" : value).join(""); route.name += (route.name && "/") + segmentName; const routePath = getRoutePath(tokens, segments[i + 1] !== void 0 && segments[i + 1] !== "index"); const path = withLeadingSlash(joinURL(route.path, routePath.replace(INDEX_PAGE_RE, "/"))); const child = parent.find((parentRoute) => parentRoute.name === route.name && parentRoute.path === path); if (child && child.children) { parent = child.children; route.path = ""; } else if (segmentName === "index" && !route.path) { route.path += "/"; } else if (segmentName !== "index") { route.path += routePath; } } parent.push(route); } return prepareRoutes(routes); } 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 fileContent = route.file in vfs ? vfs[route.file] : fs.readFileSync(ctx.fullyResolvedPaths?.has(route.file) ? route.file : await resolvePath(route.file), "utf-8"); const routeMeta = getRouteMeta(fileContent, route.file, ctx.extraExtractionKeys); if (route.meta) { routeMeta.meta = { ...routeMeta.meta, ...route.meta }; } 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 = /[^>]*)>(?[\s\S]*?)<\/script[^>]*>/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_META_RE = /definePageMeta\([\s\S]*?\)/; const defaultExtractionKeys = ["name", "path", "props", "alias", "redirect", "middleware"]; const DYNAMIC_META_KEY = "__nuxt_dynamic_meta_key"; const pageContentsCache = {}; const metaCache$1 = {}; function getRouteMeta(contents, absolutePath, extraExtractionKeys = /* @__PURE__ */ new Set()) { if (!(absolutePath in pageContentsCache) || pageContentsCache[absolutePath] !== contents) { pageContentsCache[absolutePath] = contents; delete metaCache$1[absolutePath]; } if (absolutePath in metaCache$1 && metaCache$1[absolutePath]) { return klona(metaCache$1[absolutePath]); } const loader = getLoader(absolutePath); const scriptBlocks = !loader ? null : loader === "vue" ? extractScriptContent(contents) : [{ code: contents, loader }]; if (!scriptBlocks) { metaCache$1[absolutePath] = {}; return {}; } const extractedMeta = {}; const extractionKeys = /* @__PURE__ */ new Set([...defaultExtractionKeys, ...extraExtractionKeys]); for (const script of scriptBlocks) { if (!PAGE_META_RE.test(script.code)) { continue; } const dynamicProperties = /* @__PURE__ */ new Set(); let foundMeta = false; parseAndWalk(script.code, absolutePath.replace(/\.\w+$/, "." + script.loader), (node) => { if (foundMeta) { return; } if (node.type !== "ExpressionStatement" || node.expression.type !== "CallExpression" || node.expression.callee.type !== "Identifier" || node.expression.callee.name !== "definePageMeta") { return; } foundMeta = true; const pageMetaArgument = node.expression.arguments[0]; if (pageMetaArgument?.type !== "ObjectExpression") { logger.warn(`\`definePageMeta\` must be called with an object literal (reading \`${absolutePath}\`).`); return; } for (const key of extractionKeys) { const property = pageMetaArgument.properties.find((property2) => property2.type === "Property" && property2.key.type === "Identifier" && property2.key.name === key); if (!property) { continue; } const propertyValue = withLocations(property.value); const { value, serializable } = isSerializable(script.code, propertyValue); 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)) { extractedMeta.meta ??= {}; extractedMeta.meta[key] = value; } else { extractedMeta[key] = value; } } for (const property of pageMetaArgument.properties) { if (property.type !== "Property") { continue; } const isIdentifierOrLiteral = property.key.type === "Literal" || property.key.type === "Identifier"; if (!isIdentifierOrLiteral) { continue; } const name = property.key.type === "Identifier" ? property.key.name : String(property.value); if (!extraExtractionKeys.has(name)) { dynamicProperties.add("meta"); break; } } if (dynamicProperties.size) { extractedMeta.meta ??= {}; extractedMeta.meta[DYNAMIC_META_KEY] = dynamicProperties; } }); } metaCache$1[absolutePath] = extractedMeta; return klona(extractedMeta); } const COLON_RE = /:/g; function getRoutePath(tokens, hasSucceedingSegment = false) { return tokens.reduce((path, token) => { return path + (token.type === 2 /* optional */ ? `:${token.value}?` : token.type === 1 /* dynamic */ ? `:${token.value}()` : token.type === 3 /* catchall */ ? hasSucceedingSegment ? `:${token.value}([^/]*)*` : `:${token.value}(.*)*` : token.type === 4 /* group */ ? "" : encodePath(token.value).replace(COLON_RE, "\\:")); }, "/"); } const PARAM_CHAR_RE = /[\w.]/; function parseSegment(segment, absolutePath) { let state = 0 /* initial */; let i = 0; let buffer = ""; const tokens = []; function consumeBuffer() { if (!buffer) { return; } if (state === 0 /* initial */) { throw new Error("wrong state"); } tokens.push({ type: state === 1 /* static */ ? 0 /* static */ : state === 2 /* dynamic */ ? 1 /* dynamic */ : state === 3 /* optional */ ? 2 /* optional */ : state === 4 /* catchall */ ? 3 /* catchall */ : 4 /* group */, value: buffer }); buffer = ""; } while (i < segment.length) { const c = segment[i]; switch (state) { case 0 /* initial */: buffer = ""; if (c === "[") { state = 2 /* dynamic */; } else if (c === "(") { state = 5 /* group */; } else { i--; state = 1 /* static */; } break; case 1 /* static */: if (c === "[") { consumeBuffer(); state = 2 /* dynamic */; } else if (c === "(") { consumeBuffer(); state = 5 /* group */; } else { buffer += c; } break; case 4 /* catchall */: case 2 /* dynamic */: case 3 /* optional */: case 5 /* group */: if (buffer === "...") { buffer = ""; state = 4 /* catchall */; } if (c === "[" && state === 2 /* dynamic */) { state = 3 /* optional */; } if (c === "]" && (state !== 3 /* optional */ || segment[i - 1] === "]")) { if (!buffer) { throw new Error("Empty param"); } else { consumeBuffer(); } state = 0 /* initial */; } else if (c === ")" && state === 5 /* group */) { if (!buffer) { throw new Error("Empty group"); } else { consumeBuffer(); } state = 0 /* initial */; } else if (c && PARAM_CHAR_RE.test(c)) { buffer += c; } else if (state === 2 /* dynamic */ || state === 3 /* optional */) { if (c !== "[" && c !== "]") { logger.warn(`'\`${c}\`' is not allowed in a dynamic route parameter and has been ignored. Consider renaming \`${absolutePath}\`.`); } } break; } i++; } if (state === 2 /* dynamic */) { throw new Error(`Unfinished param "${buffer}"`); } consumeBuffer(); return tokens; } function findRouteByName(name, routes) { for (const route of routes) { if (route.name === name) { return route; } } return findRouteByName(name, routes); } const NESTED_PAGE_RE = /\//g; function prepareRoutes(routes, parent, names = /* @__PURE__ */ new Set()) { for (const route of routes) { if (route.name) { route.name = route.name.replace(INDEX_PAGE_RE, "").replace(NESTED_PAGE_RE, "-"); if (names.has(route.name)) { const existingRoute = findRouteByName(route.name, routes); const extra = existingRoute?.name ? `is the same as \`${existingRoute.file}\`` : "is a duplicate"; logger.warn(`Route name generated for \`${route.file}\` ${extra}. You may wish to set a custom name using \`definePageMeta\` within the page file.`); } } if (parent && route.path[0] === "/") { route.path = route.path.slice(1); } if (route.children?.length) { route.children = prepareRoutes(route.children, route, names); } if (route.children?.find((childRoute) => childRoute.path === "")) { delete route.name; } if (route.name) { names.add(route.name); } } return routes; } function serializeRouteValue(value, skipSerialisation = false) { if (skipSerialisation || value === void 0) { return void 0; } return JSON.stringify(value); } function normalizeRoutes(routes, metaImports = /* @__PURE__ */ new Set(), options) { 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)); 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 pageImport = page._sync && page.mode !== "client" ? pageImportName : genDynamicImport(file); const metaRoute = { name: `${metaImportName}?.name ?? ${route.name}`, path: `${metaImportName}?.path ?? ${route.path}`, props: `${metaImportName}?.props ?? ${route.props ?? false}`, meta: `${metaImportName} || {}`, alias: `${metaImportName}?.alias || []`, redirect: `${metaImportName}?.redirect`, component: page.mode === "server" ? `() => createIslandPage(${route.name})` : page.mode === "client" ? `() => createClientPage(${pageImport})` : pageImport }; if (page.mode === "server") { metaImports.add(` let _createIslandPage async function createIslandPage (name) { _createIslandPage ||= await import(${JSON.stringify(options?.serverComponentRuntime)}).then(r => r.createIslandPage) return _createIslandPage(name) };`); } 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; })) }; } const PATH_TO_NITRO_GLOB_RE = /\/[^:/]*:\w.*$/; function pathToNitroGlob(path) { if (!path) { return null; } if (path.indexOf(":") !== path.lastIndexOf(":")) { return null; } return path.replace(PATH_TO_NITRO_GLOB_RE, "/**"); } function resolveRoutePaths(page, parent = "/") { return [ joinURL(parent, page.path), ...page.children?.flatMap((child) => resolveRoutePaths(child, joinURL(parent, page.path))) || [] ]; } function isSerializable(code, node) { const propertyValue = withLocations(node); if (propertyValue.type === "ObjectExpression") { const valueString = code.slice(propertyValue.start, propertyValue.end); try { return { value: JSON.parse(runInNewContext(`JSON.stringify(${valueString})`, {})), serializable: true }; } catch { return { serializable: false }; } } if (propertyValue.type === "ArrayExpression") { const values = []; for (const element of propertyValue.elements) { if (!element) { continue; } const { serializable, value } = isSerializable(code, element); if (!serializable) { return { serializable: false }; } values.push(value); } return { value: values, serializable: true }; } if (propertyValue.type === "Literal" && (typeof propertyValue.value === "string" || typeof propertyValue.value === "boolean" || typeof propertyValue.value === "number" || propertyValue.value === null)) { return { value: propertyValue.value, serializable: true }; } return { serializable: false }; } const ROUTE_RULE_RE = /\bdefineRouteRules\(/; const ruleCache = {}; function extractRouteRules(code, path) { if (!ROUTE_RULE_RE.test(code)) { return null; } if (code in ruleCache) { return ruleCache[code] || null; } const loader = getLoader(path); if (!loader) { return null; } let rule = null; const contents = loader === "vue" ? extractScriptContent(code) : [{ code, loader }]; for (const script of contents) { if (rule) { break; } code = script?.code || code; parseAndWalk(code, "file." + (script?.loader || "ts"), (node) => { if (node.type !== "CallExpression" || node.callee.type !== "Identifier") { return; } if (node.callee.name === "defineRouteRules") { const rulesString = code.slice(node.start, node.end); try { rule = JSON.parse(runInNewContext(rulesString.replace("defineRouteRules", "JSON.stringify"), {})); } catch { throw new Error("[nuxt] Error parsing route rules. They should be JSON-serializable."); } } }); } ruleCache[code] = rule; return rule; } function getMappedPages(pages, paths = {}, prefix = "") { for (const page of pages) { if (page.file) { const filename = normalize(page.file); paths[filename] = pathToNitroGlob(prefix + page.path); } if (page.children?.length) { getMappedPages(page.children, paths, page.path + "/"); } } return paths; } 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", transformInclude(id) { return !!parseMacroQuery(id).macro; }, transform(code, id) { const query = parseMacroQuery(id); if (query.type && query.type !== "script") { return; } const s = new MagicString(code); function result() { if (s.hasChanged()) { return { code: s.toString(), map: options.sourcemap ? s.generateMap({ hires: true }) : void 0 }; } } 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 } = parseURL(decodeURIComponent(pathToFileURL(id).href)); logger.error(`The file \`${pathname}\` is not a valid page as it has no content.`); } 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 = `${node.start}-${node.end}`; if (addedDeclarations.has(codeSectionKey)) { return; } addedDeclarations.add(codeSectionKey); declarationNodes.push(node); } 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({ keepExitedScopes: 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") { logger.error(`Await expressions are not supported in definePageMeta. File: '${id}'`); throw new Error("await in definePageMeta"); } if (isNotReferencePosition(node, parent) || node.type !== "Identifier") { return; } addImportOrDeclaration(node.name, scopeTrackerNode); } }); } } else if (scopeTrackerNode?.type === "Function") { if (scopeTrackerNode.node.type === "ArrowFunctionExpression") { return; } const name = scopeTrackerNode.node.id?.name; if (!name) { return; } addDeclaration(scopeTrackerNode); const undeclaredIdentifiers = getUndeclaredIdentifiersInFunction(scopeTrackerNode.node); for (const name2 of undeclaredIdentifiers) { addImportOrDeclaration(name2); } } } const ast = parseAndWalk(code, id + (query.lang ? "." + query.lang : ".ts"), { scopeTracker }); 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 = withLocations(node.arguments[0]); if (!meta) { return; } const metaCode = code.slice(meta.start, meta.end); const m = new MagicString(metaCode); if (meta.type === "ObjectExpression") { for (let i = 0; i < meta.properties.length; i++) { const prop = withLocations(meta.properties[i]); if (prop.type === "Property" && prop.key.type === "Identifier" && options.extractedKeys?.includes(prop.key.name)) { const { serializable } = isSerializable(metaCode, prop.value); if (!serializable) { continue; } const nextProperty = withLocations(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, ""); } } } } const definePageMetaScope = scopeTracker.getCurrentScope(); walk(meta, { scopeTracker, enter(node2, parent) { if (isNotReferencePosition(node2, parent) || node2.type !== "Identifier") { return; } const declaration = scopeTracker.getDeclaration(node2.name); if (declaration) { if (declaration.isUnderScope(definePageMetaScope) && (scopeTracker.isCurrentScopeUnder(declaration.scope) || declaration.start < node2.start)) { return; } } if (isStaticIdentifier(node2.name)) { addImport(node2.name); } else if (declaration) { processDeclaration(declaration); } } }); const importStatements = Array.from(addedImports).join("\n"); const declarations = declarationNodes.sort((a, b) => a.start - b.start).map((node2) => code.slice(node2.start, node2.end)).join("\n"); const extracted = [ importStatements, declarations, `const __nuxt_page_meta = ${m.toString() || "null"} export default __nuxt_page_meta` + (options.dev ? CODE_HMR : "") ].join("\n"); s.overwrite(0, code.length, extracted.trim()); } }); if (instances > 1) { throw new Error("Multiple `definePageMeta` calls are not supported. File: " + id.replace(/\?.+$/, "")); } 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.routesPath && options.isPage?.(file)) { const macroModule = server.moduleGraph.getModuleById(file + "?macro=true"); const routesModule = server.moduleGraph.getModuleById("virtual:nuxt:" + encodeURIComponent(options.routesPath)); return [ ...modules, ...macroModule ? [macroModule] : [], ...routesModule ? [routesModule] : [] ]; } } } } }; }); const QUERY_START_RE = /^\?/; const MACRO_RE = /¯o=true/; function rewriteQuery(id) { return id.replace(/\?.+$/, (r) => "?macro=true&" + r.replace(QUERY_START_RE, "").replace(MACRO_RE, "")); } function parseMacroQuery(id) { const { search } = parseURL(decodeURIComponent(isAbsolute(id) ? pathToFileURL(id).href : id).replace(/\?macro=true$/, "")); const query = parseQuery(search); if (id.includes("?macro=true")) { return { macro: "true", ...query }; } return query; } const QUOTED_SPECIFIER_RE = /(["']).*\1/; function getQuotedSpecifier(id) { return id.match(QUOTED_SPECIFIER_RE)?.[0]; } const INJECTION_RE_TEMPLATE = /\b_ctx\.\$route\b/g; const INJECTION_RE_SCRIPT = /\bthis\.\$route\b/g; 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 } }, handler(code) { if (code.includes("_ctx._.provides[__nuxt_route_symbol") || code.includes("this._.provides[__nuxt_route_symbol")) { return; } let replaced = false; const s = new MagicString(code); const strippedCode = stripLiteral(code); const replaceMatches = (regExp, replacement) => { for (const match of strippedCode.matchAll(regExp)) { const start = match.index; const end = start + match[0].length; s.overwrite(start, end, replacement); replaced ||= true; } }; replaceMatches(INJECTION_RE_TEMPLATE, "(_ctx._.provides[__nuxt_route_symbol] || _ctx.$route)"); replaceMatches(INJECTION_RE_SCRIPT, "(this._.provides[__nuxt_route_symbol] || this.$route)"); if (replaced) { s.prepend("import { PageRouteSymbol as __nuxt_route_symbol } from '#app/components/injections';\n"); } if (s.hasChanged()) { return { code: s.toString(), map: nuxt.options.sourcemap.client || nuxt.options.sourcemap.server ? s.generateMap({ hires: true }) : void 0 }; } } } }; }); const OPTIONAL_PARAM_RE = /^\/?:.*(?:\?|\(\.\*\)\*)$/; const runtimeDir = resolve(distDir, "pages/runtime"); 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; } const pagesModule = 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 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; const useExperimentalTypedPages = nuxt.options.experimental.typedPages; const builtInRouterOptions = await findPath(resolve(runtimeDir, "router.options")) || resolve(runtimeDir, "router.options"); const pagesDirs = nuxt.options._layers.map( (layer) => resolve(layer.config.srcDir, (layer.config.rootDir === nuxt.options.rootDir ? nuxt.options : layer.config).dir?.pages || "pages") ); nuxt.options.alias["#vue-router"] = "vue-router"; const routerPath = await resolveTypePath("vue-router", "", nuxt.options.modulesDir) || "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; } const routerOptionsFiles = await resolveRouterOptions(nuxt, builtInRouterOptions); if (routerOptionsFiles.filter((p) => !p.optional).length > 0) { return true; } if (pagesDirs.some((dir) => isNonEmptyDir(dir))) { return true; } const pages = await resolvePagesRoutes(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")) { logger.warn("Using server pages with `ssr: false` is not supported with auto-detected component islands. Set `experimental.componentIslands` to `true`."); } }); 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 || layer.cwd, pagesDir) ]; }); nuxt.hooks.hook("builder:watch", async (event, relativePath) => { const path = resolve(nuxt.options.srcDir, relativePath); if (restartPaths.some((p) => p === path || path.startsWith(p + "/"))) { const newSetting = await isPagesEnabled(); if (options.enabled !== newSetting) { logger.info("Pages", newSetting ? "enabled" : "disabled"); return nuxt.callHook("restart"); } } }); if (!options.enabled) { addPlugin(resolve(distDir, "app/plugins/router")); addTemplate({ filename: "pages.mjs", getContents: () => [ "export { useRoute } from '#app/composables/router'", "export const START_LOCATION = Symbol('router:start-location')" ].join("\n") }); addTemplate({ filename: "router.options.mjs", getContents: () => { return [ "export const hashMode = false", "export default {}" ].join("\n"); } }); addTypeTemplate({ filename: "types/middleware.d.ts", getContents: () => [ "declare module 'nitropack' {", " interface NitroRouteConfig {", " appMiddleware?: string | string[] | Record", " }", "}", "export {}" ].join("\n") }, { nuxt: true, nitro: true }); addComponent({ name: "NuxtPage", priority: 10, // built-in that we do not expect the user to override filePath: resolve(distDir, "pages/runtime/page-placeholder") }); nuxt.hook("nitro:init", (nitro) => { if (nuxt.options.dev || !nuxt.options.ssr || !nitro.options.static || !nitro.options.prerender.crawlLinks) { return; } nitro.options.prerender.routes.push("/"); }); return; } if (useExperimentalTypedPages) { const declarationFile = "./types/typed-router.d.ts"; const typedRouterOptions = { routesFolder: [], dts: resolve(nuxt.options.buildDir, declarationFile), logs: nuxt.options.debug && nuxt.options.debug.router, async beforeWriteFiles(rootPage) { rootPage.children.forEach((child) => child.delete()); const pages = nuxt.apps.default?.pages || await resolvePagesRoutes(options.pattern, nuxt); if (nuxt.apps.default) { nuxt.apps.default.pages = pages; } const addedPagePaths = /* @__PURE__ */ new Set(); function addPage(parent, page, basePath = "") { const absolutePagePath = joinURL(basePath, page.path); const route = addedPagePaths.has(absolutePagePath) ? parent : /^\//.test(page.path) ? rootPage.insert(page.path, page.file) : parent.insert(page.path, page.file); addedPagePaths.add(absolutePagePath); if (page.meta) { route.addToMeta(page.meta); } if (page.alias) { route.addAlias(page.alias); } if (page.name) { route.name = page.name; } if (page.children) { page.children.forEach((child) => addPage(route, child, absolutePagePath)); } } for (const page of pages) { addPage(rootPage, page); } } }; nuxt.hook("prepare:types", ({ references }) => { references.push({ path: declarationFile }); references.push({ types: "unplugin-vue-router/client" }); }); const context = createRoutesContext(resolveOptions(typedRouterOptions)); const dtsFile = resolve(nuxt.options.buildDir, declarationFile); await mkdir(dirname(dtsFile), { recursive: true }); await context.scanPages(false); if (nuxt.options._prepare || !nuxt.options.dev) { const dts = await readFile(dtsFile, "utf-8"); addTemplate({ filename: "types/typed-router.d.ts", getContents: () => dts }); } nuxt.hook("app:templatesGenerated", async (_app, _templates, options2) => { if (!options2?.filter || options2.filter({ filename: "routes.mjs" })) { await context.scanPages(); } }); } nuxt.hook("prepare:types", ({ references }) => { references.push({ types: useExperimentalTypedPages ? "vue-router/auto-routes" : "vue-router" }); }); nuxt.hook("imports:sources", (sources) => { const routerImports = sources.find((s) => s.from === "#app/composables/router" && s.imports.includes("onBeforeRouteLeave")); if (routerImports) { routerImports.from = "vue-router"; } }); const updateTemplatePaths = nuxt.options._layers.flatMap((l) => { const dir = l.config.rootDir === nuxt.options.rootDir ? nuxt.options.dir : l.config.dir; return [ resolve(l.config.srcDir || l.cwd, dir?.pages || "pages") + "/", resolve(l.config.srcDir || l.cwd, dir?.layouts || "layouts") + "/", resolve(l.config.srcDir || l.cwd, dir?.middleware || "middleware") + "/" ]; }); function isPage(file, pages = nuxt.apps.default?.pages) { if (!pages) { return false; } return pages.some((page) => page.file === file) || pages.some((page) => page.children && isPage(file, page.children)); } nuxt.hooks.hookOnce("app:templates", async (app) => { app.pages ||= await resolvePagesRoutes(options.pattern, nuxt); }); nuxt.hook("builder:watch", async (event, relativePath) => { const path = resolve(nuxt.options.srcDir, relativePath); const shouldAlwaysRegenerate = nuxt.options.experimental.scanPageMeta && isPage(path); if (event === "change" && !shouldAlwaysRegenerate) { return; } if (shouldAlwaysRegenerate || updateTemplatePaths.some((dir) => path.startsWith(dir))) { nuxt.apps.default.pages = await resolvePagesRoutes(options.pattern, nuxt); } }); nuxt.hook("app:resolve", (app) => { if (app.mainComponent === resolve(nuxt.options.appDir, "components/welcome.vue")) { app.mainComponent = resolve(runtimeDir, "app.vue"); } app.middleware.unshift({ name: "validate", path: resolve(runtimeDir, "validate"), global: true }); }); nuxt.hook("app:resolve", (app) => { const nitro = useNitro(); if (nitro.options.prerender.crawlLinks || Object.values(nitro.options.routeRules).some((rule) => rule.prerender)) { app.plugins.push({ src: resolve(runtimeDir, "plugins/prerender.server"), mode: "server" }); } }); const prerenderRoutes = /* @__PURE__ */ new Set(); function processPages(pages, currentPath = "/") { for (const page of pages) { if (OPTIONAL_PARAM_RE.test(page.path) && !page.children?.length) { prerenderRoutes.add(currentPath); } if (page.path.includes(":")) { continue; } const route = joinURL(currentPath, page.path); prerenderRoutes.add(route); if (page.children) { processPages(page.children, route); } } } nuxt.hook("pages:extend", (pages) => { if (nuxt.options.dev) { return; } prerenderRoutes.clear(); processPages(pages); }); nuxt.hook("nitro:build:before", (nitro) => { if (nuxt.options.dev || nuxt.options.router.options.hashMode) { return; } if (!nitro.options.static && !nitro.options.prerender.crawlLinks) { const routeRulesMatcher = toRouteMatcher(createRouter({ routes: nitro.options.routeRules })); for (const route of prerenderRoutes) { const rules = defu({}, ...routeRulesMatcher.matchAll(route).reverse()); if (rules.prerender) { nitro.options.prerender.routes.push(route); } } } if (!nitro.options.static || !nitro.options.prerender.crawlLinks) { return; } if (nuxt.options.ssr) { const [firstPage] = [...prerenderRoutes].sort(); nitro.options.prerender.routes.push(firstPage || "/"); return; } for (const route of nitro.options.prerender.routes || []) { prerenderRoutes.add(route); } nitro.options.prerender.routes = Array.from(prerenderRoutes); }); nuxt.hook("imports:extend", (imports) => { imports.push( { name: "definePageMeta", as: "definePageMeta", from: resolve(runtimeDir, "composables") }, { name: "useLink", as: "useLink", from: "vue-router" } ); if (nuxt.options.experimental.inlineRouteRules) { imports.push({ name: "defineRouteRules", as: "defineRouteRules", from: resolve(runtimeDir, "composables") }); } }); if (nuxt.options.experimental.inlineRouteRules) { let pageToGlobMap = {}; nuxt.hook("pages:extend", (pages) => { pageToGlobMap = getMappedPages(pages); }); const inlineRules = {}; let updateRouteConfig; nuxt.hook("nitro:init", (nitro) => { updateRouteConfig = () => nitro.updateConfig({ routeRules: defu(inlineRules, nitro.options._config.routeRules) }); }); const updatePage = async function updatePage2(path) { const glob = pageToGlobMap[path]; const code = path in nuxt.vfs ? nuxt.vfs[path] : await readFile(path, "utf-8"); try { const extractedRule = await extractRouteRules(code, path); if (extractedRule) { if (!glob) { const relativePath = relative(nuxt.options.srcDir, path); logger.error(`Could not set inline route rules in \`~/${relativePath}\` as it could not be mapped to a Nitro route.`); return; } inlineRules[glob] = extractedRule; } else if (glob) { delete inlineRules[glob]; } } catch (e) { if (e.toString().includes("Error parsing route rules")) { const relativePath = relative(nuxt.options.srcDir, path); logger.error(`Error parsing route rules within \`~/${relativePath}\`. They should be JSON-serializable.`); } else { logger.error(e); } } }; nuxt.hook("builder:watch", async (event, relativePath) => { const path = resolve(nuxt.options.srcDir, relativePath); if (!(path in pageToGlobMap)) { return; } if (event === "unlink") { delete inlineRules[path]; delete pageToGlobMap[path]; } else { await updatePage(path); } await updateRouteConfig?.(); }); nuxt.hooks.hookOnce("pages:extend", async () => { for (const page in pageToGlobMap) { await updatePage(page); } await updateRouteConfig?.(); }); } const componentStubPath = await resolvePath(resolve(runtimeDir, "component-stub")); if (nuxt.options.test && nuxt.options.dev) { nuxt.hook("pages:extend", (routes) => { routes.push({ _sync: true, path: "/__nuxt_component_test__/:pathMatch(.*)", file: componentStubPath }); }); } if (nuxt.options.experimental.appManifest) { nuxt.hook("pages:extend", (routes) => { const nitro = useNitro(); let resolvedRoutes; for (const [path, rule] of Object.entries(nitro.options.routeRules)) { if (!rule.redirect) { continue; } resolvedRoutes ||= routes.flatMap((route) => resolveRoutePaths(route)); if (resolvedRoutes.includes(path)) { continue; } routes.push({ _sync: true, path: path.replace(/\/[^/]*\*\*/, "/:pathMatch(.*)"), file: componentStubPath }); } }); } const extractedKeys = nuxt.options.future.compatibilityVersion === 4 ? [...defaultExtractionKeys, "middleware", ...nuxt.options.experimental.extraPageMetaExtractionKeys] : ["middleware", ...nuxt.options.experimental.extraPageMetaExtractionKeys]; nuxt.hook("modules:done", () => { addBuildPlugin(PageMetaPlugin({ dev: nuxt.options.dev, sourcemap: !!nuxt.options.sourcemap.server || !!nuxt.options.sourcemap.client, isPage, routesPath: resolve(nuxt.options.buildDir, "routes.mjs"), extractedKeys: nuxt.options.experimental.scanPageMeta ? extractedKeys : [] })); }); addPlugin(resolve(runtimeDir, "plugins/prefetch.client")); if (nuxt.options.experimental.templateRouteInjection) { addBuildPlugin(RouteInjectionPlugin(nuxt), { server: false }); } addPlugin(resolve(runtimeDir, "plugins/router")); const getSources = (pages) => pages.filter((p) => Boolean(p.file)).flatMap( (p) => [relative(nuxt.options.srcDir, p.file), ...p.children?.length ? getSources(p.children) : []] ); nuxt.hook("build:manifest", (manifest) => { if (nuxt.options.dev) { return; } const sourceFiles = nuxt.apps.default?.pages?.length ? getSources(nuxt.apps.default.pages) : []; for (const [key, chunk] of Object.entries(manifest)) { if (chunk.src && Object.values(nuxt.apps).some((app) => app.pages?.some((page) => page.mode === "server" && page.file === join(nuxt.options.srcDir, chunk.src)))) { delete manifest[key]; continue; } if (chunk.isEntry) { chunk.dynamicImports = chunk.dynamicImports?.filter((i) => !sourceFiles.includes(i)); } } }); const serverComponentRuntime = await findPath(join(distDir, "components/runtime/server-component")) ?? join(distDir, "components/runtime/server-component"); const clientComponentRuntime = await findPath(join(distDir, "components/runtime/client-component")) ?? join(distDir, "components/runtime/client-component"); addTemplate({ filename: "routes.mjs", getContents({ app }) { if (!app.pages) { return ROUTES_HMR_CODE + "export default []"; } const { routes, imports } = normalizeRoutes(app.pages, /* @__PURE__ */ new Set(), { serverComponentRuntime, clientComponentRuntime, overrideMeta: !!nuxt.options.experimental.scanPageMeta }); return ROUTES_HMR_CODE + [...imports, `export default ${routes}`].join("\n"); } }); addTemplate({ filename: "pages.mjs", getContents: () => "export { START_LOCATION, useRoute } from 'vue-router'" }); nuxt.options.vite.resolve ||= {}; nuxt.options.vite.resolve.dedupe ||= []; nuxt.options.vite.resolve.dedupe.push("vue-router"); addTemplate({ filename: "router.options.mjs", getContents: async ({ nuxt: nuxt2 }) => { const routerOptionsFiles = await resolveRouterOptions(nuxt2, builtInRouterOptions); const configRouterOptions = genObjectFromRawEntries(Object.entries(nuxt2.options.router.options).map(([key, value]) => [key, genString(value)])); return [ ...routerOptionsFiles.map((file, index) => genImport(file.path, `routerOptions${index}`)), `const configRouterOptions = ${configRouterOptions}`, `export const hashMode = ${[...routerOptionsFiles.filter((o) => o.path !== builtInRouterOptions).map((_, index) => `routerOptions${index}.hashMode`).reverse(), nuxt2.options.router.options.hashMode].join(" ?? ")}`, "export default {", "...configRouterOptions,", ...routerOptionsFiles.map((_, index) => `...routerOptions${index},`), "}" ].join("\n"); } }); addTypeTemplate({ filename: "types/middleware.d.ts", getContents: ({ app }) => { const namedMiddleware = app.middleware.filter((mw) => !mw.global); return [ "import type { NavigationGuard } from 'vue-router'", `export type MiddlewareKey = ${namedMiddleware.map((mw) => genString(mw.name)).join(" | ") || "never"}`, "declare module 'nuxt/app' {", " interface PageMeta {", " middleware?: MiddlewareKey | NavigationGuard | Array", " }", "}" ].join("\n"); } }); addTypeTemplate({ filename: "types/nitro-middleware.d.ts", getContents: ({ app }) => { const namedMiddleware = app.middleware.filter((mw) => !mw.global); return [ `export type MiddlewareKey = ${namedMiddleware.map((mw) => genString(mw.name)).join(" | ") || "never"}`, "declare module 'nitropack' {", " interface NitroRouteConfig {", " appMiddleware?: MiddlewareKey | MiddlewareKey[] | Record", " }", "}" ].join("\n"); } }, { nuxt: true, nitro: true }); addTypeTemplate({ filename: "types/layouts.d.ts", getContents: ({ app }) => { return [ "import type { ComputedRef, MaybeRef } from 'vue'", `export type LayoutKey = ${Object.keys(app.layouts).map((name) => genString(name)).join(" | ") || "string"}`, "declare module 'nuxt/app' {", " interface PageMeta {", " layout?: MaybeRef | ComputedRef", " }", "}" ].join("\n"); } }); if (nuxt.options.experimental.viewTransition) { addTypeTemplate({ filename: "types/view-transitions.d.ts", getContents: () => { return [ "declare module 'nuxt/app' {", " interface PageMeta {", " viewTransition?: boolean | 'always'", " }", "}", "export {}" ].join("\n"); } }); } addComponent({ name: "NuxtPage", priority: 10, // built-in that we do not expect the user to override filePath: resolve(distDir, "pages/runtime/page") }); } }); const ROUTES_HMR_CODE = ( /* js */ ` if (import.meta.hot) { import.meta.hot.accept((mod) => { const router = import.meta.hot.data.router const generateRoutes = import.meta.hot.data.generateRoutes if (!router || !generateRoutes) { import.meta.hot.invalidate('[nuxt] Cannot replace routes because there is no active router. Reloading.') return } router.clearRoutes() const routes = generateRoutes(mod.default || mod) function addRoutes (routes) { for (const route of routes) { router.addRoute(route) } router.replace(router.currentRoute.value.fullPath) } if (routes && 'then' in routes) { routes.then(addRoutes) } else { addRoutes(routes) } }) } export function handleHotUpdate(_router, _generateRoutes) { if (import.meta.hot) { import.meta.hot.data ||= {} import.meta.hot.data.router = _router import.meta.hot.data.generateRoutes = _generateRoutes } } ` ); const UNHEAD_LIB_RE = /node_modules[/\\](?:@unhead[/\\][^/\\]+|unhead)[/\\]/; function toImports(specifiers) { return specifiers.map((specifier) => { const imported = specifier.imported; const isNamedImport = imported && imported.name !== specifier.local.name; return isNamedImport ? `${imported.name} as ${specifier.local.name}` : specifier.local.name; }); } const UnheadVue = "@unhead/vue"; const UnheadVueRE = /@unhead\/vue/; const UnheadImportsPlugin = (options) => createUnplugin(() => { return { name: "nuxt:head:unhead-imports", enforce: "post", transformInclude(id) { id = normalize(id); return (isJS(id) || isVue(id, { type: ["script"] })) && !id.startsWith("virtual:") && !id.startsWith(normalize(distDir)) && !UNHEAD_LIB_RE.test(id); }, transform: { filter: { code: { include: UnheadVueRE } }, handler(code, id) { const s = new MagicString(code); const importsToAdd = []; parseAndWalk(code, id, function(node) { if (node.type === "ImportDeclaration" && [UnheadVue, "#app/composables/head"].includes(String(node.source.value))) { importsToAdd.push(...node.specifiers); const { start, end } = withLocations(node); s.remove(start, end); } }); const importsFromUnhead = importsToAdd.filter((specifier) => unheadVueComposablesImports[UnheadVue].includes(specifier.imported?.name)); const importsFromHead = importsToAdd.filter((specifier) => !unheadVueComposablesImports[UnheadVue].includes(specifier.imported?.name)); if (importsFromUnhead.length) { if (!normalize(id).includes("node_modules")) { logger.warn(`You are importing from \`${UnheadVue}\` in \`./${relative(normalize(options.rootDir), normalize(id))}\`. Please import from \`#imports\` instead for full type safety.`); } s.prepend(`${genImport("#app/composables/head", toImports(importsFromUnhead))} `); } if (importsFromHead.length) { s.prepend(`${genImport(UnheadVue, toImports(importsFromHead))} `); } if (s.hasChanged()) { return { code: s.toString(), map: options.sourcemap ? s.generateMap({ hires: true }) : void 0 }; } } } }; }); const components = ["NoScript", "Link", "Base", "Title", "Meta", "Style", "Head", "Html", "Body"]; const metaModule = defineNuxtModule({ meta: { name: "nuxt:meta", configKey: "unhead" }, setup(options, nuxt) { const runtimeDir = resolve(distDir, "head/runtime"); nuxt.options.build.transpile.push("@unhead/vue"); const isNuxtV4 = nuxt.options._majorVersion === 4 || nuxt.options.future?.compatibilityVersion === 4; const componentsPath = resolve(runtimeDir, "components"); for (const componentName of components) { addComponent({ name: componentName, filePath: componentsPath, export: componentName, // built-in that we do not expect the user to override priority: 10, // kebab case version of these tags is not valid kebabName: componentName }); } if (!nuxt.options.dev) { nuxt.options.optimization.treeShake.composables.client["@unhead/vue"] = [ "useServerHead", "useServerSeoMeta", "useServerHeadSafe" ]; } nuxt.options.alias["#unhead/composables"] = resolve(runtimeDir, "composables", isNuxtV4 ? "v4" : "v3"); addBuildPlugin(UnheadImportsPlugin({ sourcemap: !!nuxt.options.sourcemap.server, rootDir: nuxt.options.rootDir })); const importPaths = nuxt.options.modulesDir.map((d) => directoryToURL(d)); const unheadPlugins = resolveModulePath("@unhead/vue/plugins", { try: true, from: importPaths }) || "@unhead/vue/plugins"; if (nuxt.options.experimental.polyfillVueUseHead) { nuxt.options.alias["@vueuse/head"] = resolveModulePath("@unhead/vue", { try: true, from: importPaths }) || "@unhead/vue"; addPlugin({ src: resolve(runtimeDir, "plugins/vueuse-head-polyfill") }); } addTemplate({ filename: "unhead-options.mjs", getContents() { if (isNuxtV4 && !options.legacy) { return ` export default { disableDefaults: true, }`; } const disableCapoSorting = !nuxt.options.experimental.headNext; return `import { DeprecationsPlugin, PromisesPlugin, TemplateParamsPlugin, AliasSortingPlugin } from ${JSON.stringify(unheadPlugins)}; export default { disableDefaults: true, disableCapoSorting: ${Boolean(disableCapoSorting)}, plugins: [DeprecationsPlugin, PromisesPlugin, TemplateParamsPlugin, AliasSortingPlugin], }`; } }); addTemplate({ filename: "unhead.config.mjs", getContents() { return [ `export const renderSSRHeadOptions = ${JSON.stringify(options.renderSSRHeadOptions || {})}` ].join("\n"); } }); nuxt.hooks.hook("nitro:config", (config) => { config.virtual["#internal/unhead-options.mjs"] = () => nuxt.vfs["#build/unhead-options.mjs"]; config.virtual["#internal/unhead.config.mjs"] = () => nuxt.vfs["#build/unhead.config.mjs"]; }); addPlugin({ src: resolve(runtimeDir, "plugins/unhead") }); } }); const createImportMagicComments = (options) => { const { chunkName, prefetch, preload } = options; return [ `webpackChunkName: "${chunkName}"`, prefetch === true || typeof prefetch === "number" ? `webpackPrefetch: ${prefetch}` : false, preload === true || typeof preload === "number" ? `webpackPreload: ${preload}` : false ].filter(Boolean).join(", "); }; const emptyComponentsPlugin = ` import { defineNuxtPlugin } from '#app/nuxt' export default defineNuxtPlugin({ name: 'nuxt:global-components', }) `; const componentsPluginTemplate = { filename: "components.plugin.mjs", getContents({ app }) { const lazyGlobalComponents = /* @__PURE__ */ new Set(); const syncGlobalComponents = /* @__PURE__ */ new Set(); for (const component of app.components) { if (component.global === "sync") { syncGlobalComponents.add(component.pascalName); } else if (component.global) { lazyGlobalComponents.add(component.pascalName); } } if (!lazyGlobalComponents.size && !syncGlobalComponents.size) { return emptyComponentsPlugin; } const lazyComponents = [...lazyGlobalComponents]; const syncComponents = [...syncGlobalComponents]; return `import { defineNuxtPlugin } from '#app/nuxt' import { ${[...lazyComponents.map((c) => "Lazy" + c), ...syncComponents].join(", ")} } from '#components' const lazyGlobalComponents = [ ${lazyComponents.map((c) => `["${c}", Lazy${c}]`).join(",\n")}, ${syncComponents.map((c) => `["${c}", ${c}]`).join(",\n")} ] export default defineNuxtPlugin({ name: 'nuxt:global-components', setup (nuxtApp) { for (const [name, component] of lazyGlobalComponents) { nuxtApp.vueApp.component(name, component) nuxtApp.vueApp.component('Lazy' + name, component) } } }) `; } }; const componentNamesTemplate = { filename: "component-names.mjs", getContents({ app }) { return `export const componentNames = ${JSON.stringify(app.components.filter((c) => !c.island).map((c) => c.pascalName))}`; } }; const componentsIslandsTemplate = { // components.islands.mjs' getContents({ app, nuxt }) { if (!nuxt.options.experimental.componentIslands) { return "export const islandComponents = {}"; } const components = app.components; const pages = app.pages; const islands = components.filter( (component) => component.island || // .server components without a corresponding .client component will need to be rendered as an island component.mode === "server" && !components.some((c) => c.pascalName === component.pascalName && c.mode === "client") ); const pageExports = pages?.filter((p) => p.mode === "server" && p.file && p.name).map((p) => { return `"page_${p.name}": defineAsyncComponent(${genDynamicImport(p.file)}.then(c => c.default || c))`; }) || []; return [ "import { defineAsyncComponent } from 'vue'", "export const islandComponents = import.meta.client ? {} : {", islands.map( (c) => { const exp = c.export === "default" ? "c.default || c" : `c['${c.export}']`; const comment = createImportMagicComments(c); return ` "${c.pascalName}": defineAsyncComponent(${genDynamicImport(c.filePath, { comment })}.then(c => ${exp}))`; } ).concat(pageExports).join(",\n"), "}" ].join("\n"); } }; const NON_VUE_RE = /\b\.(?!vue)\w+$/g; const componentsTypeTemplate = { filename: "components.d.ts", getContents: ({ app, nuxt }) => { const buildDir = nuxt.options.buildDir; const componentTypes = app.components.filter((c) => !c.island).map((c) => { const type = `typeof ${genDynamicImport(isAbsolute(c.filePath) ? relative(buildDir, c.filePath).replace(NON_VUE_RE, "") : c.filePath.replace(NON_VUE_RE, ""), { wrapper: false })}['${c.export}']`; return [ c.pascalName, c.island || c.mode === "server" ? `IslandComponent<${type}>` : type ]; }); const islandType = "type IslandComponent = T & DefineComponent<{}, {refresh: () => Promise}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, SlotsType<{ fallback: { error: unknown } }>>"; return ` import type { DefineComponent, SlotsType } from 'vue' ${nuxt.options.experimental.componentIslands ? islandType : ""} type HydrationStrategies = { hydrateOnVisible?: IntersectionObserverInit | true hydrateOnIdle?: number | true hydrateOnInteraction?: keyof HTMLElementEventMap | Array | true hydrateOnMediaQuery?: string hydrateAfter?: number hydrateWhen?: boolean hydrateNever?: true } type LazyComponent = (T & DefineComponent void }>) interface _GlobalComponents { ${componentTypes.map(([pascalName, type]) => ` '${pascalName}': ${type}`).join("\n")} ${componentTypes.map(([pascalName, type]) => ` 'Lazy${pascalName}': LazyComponent<${type}>`).join("\n")} } declare module 'vue' { export interface GlobalComponents extends _GlobalComponents { } } ${componentTypes.map(([pascalName, type]) => `export const ${pascalName}: ${type}`).join("\n")} ${componentTypes.map(([pascalName, type]) => `export const Lazy${pascalName}: LazyComponent<${type}>`).join("\n")} export const componentNames: string[] `; } }; const componentsMetadataTemplate = { filename: "components.json", write: true, getContents: ({ app }) => JSON.stringify(app.components, null, 2) }; const ISLAND_RE = /\.island(?:\.global)?$/; const GLOBAL_RE = /\.global(?:\.island)?$/; const COMPONENT_MODE_RE = /(?<=\.)(client|server)(\.global|\.island)*$/; const MODE_REPLACEMENT_RE = /(\.(client|server))?(\.global|\.island)*$/; async function scanComponents(dirs, srcDir) { const components = []; const filePaths = /* @__PURE__ */ new Set(); const scannedPaths = []; for (const dir of dirs) { if (dir.enabled === false) { continue; } const resolvedNames = /* @__PURE__ */ new Map(); const files = (await globby(dir.pattern, { cwd: dir.path, ignore: dir.ignore })).sort(); if (files.length) { const siblings = new Set(await readdir(dirname(dir.path)).catch(() => [])); const directory = basename(dir.path); if (!siblings.has(directory)) { const directoryLowerCase = directory.toLowerCase(); for (const sibling of siblings) { if (sibling.toLowerCase() === directoryLowerCase) { const nuxt = useNuxt(); const original = relative(nuxt.options.srcDir, dir.path); const corrected = relative(nuxt.options.srcDir, join(dirname(dir.path), sibling)); logger.warn(`Components not scanned from \`~/${corrected}\`. Did you mean to name the directory \`~/${original}\` instead?`); break; } } } } for (const _file of files) { const filePath = join(dir.path, _file); if (scannedPaths.find((d) => filePath.startsWith(withTrailingSlash(d))) || isIgnored(filePath)) { continue; } if (filePaths.has(filePath)) { continue; } filePaths.add(filePath); const prefixParts = [].concat( dir.prefix ? splitByCase(dir.prefix) : [], dir.pathPrefix !== false ? splitByCase(relative(dir.path, dirname(filePath))) : [] ); let fileName = basename(filePath, extname(filePath)); const island = ISLAND_RE.test(fileName) || dir.island; const global = GLOBAL_RE.test(fileName) || dir.global; const mode = island ? "server" : fileName.match(COMPONENT_MODE_RE)?.[1] || "all"; fileName = fileName.replace(MODE_REPLACEMENT_RE, ""); if (fileName.toLowerCase() === "index") { fileName = dir.pathPrefix === false ? basename(dirname(filePath)) : ""; } const suffix = mode !== "all" ? `-${mode}` : ""; const componentNameSegments = resolveComponentNameSegments(fileName.replace(QUOTE_RE, ""), prefixParts); const pascalName = pascalCase(componentNameSegments); if (LAZY_COMPONENT_NAME_REGEX.test(pascalName)) { logger.warn(`The component \`${pascalName}\` (in \`${filePath}\`) is using the reserved "Lazy" prefix used for dynamic imports, which may cause it to break at runtime.`); } if (resolvedNames.has(pascalName + suffix) || resolvedNames.has(pascalName)) { warnAboutDuplicateComponent(pascalName, filePath, resolvedNames.get(pascalName) || resolvedNames.get(pascalName + suffix)); continue; } resolvedNames.set(pascalName + suffix, filePath); const kebabName = kebabCase(componentNameSegments); const shortPath = relative(srcDir, filePath); const chunkName = "components/" + kebabName + suffix; let component = { // inheritable from directory configuration mode, global, island, prefetch: Boolean(dir.prefetch), preload: Boolean(dir.preload), // specific to the file filePath, pascalName, kebabName, chunkName, shortPath, export: "default", // by default, give priority to scanned components priority: dir.priority ?? 1, // @ts-expect-error untyped property _scanned: true }; if (typeof dir.extendComponent === "function") { component = await dir.extendComponent(component) || component; } if (!pascalName) { logger.warn(`Component did not resolve to a file name in \`~/${relative(srcDir, filePath)}\`.`); continue; } const validModes = /* @__PURE__ */ new Set(["all", component.mode]); const existingComponent = components.find((c) => c.pascalName === component.pascalName && validModes.has(c.mode)); if (existingComponent) { const existingPriority = existingComponent.priority ?? 0; const newPriority = component.priority ?? 0; if (newPriority > existingPriority) { components.splice(components.indexOf(existingComponent), 1, component); } if (newPriority > 0 && newPriority === existingPriority) { warnAboutDuplicateComponent(pascalName, filePath, existingComponent.filePath); } continue; } components.push(component); } scannedPaths.push(dir.path); } return components; } function warnAboutDuplicateComponent(componentName, filePath, duplicatePath) { logger.warn( `Two component files resolving to the same name \`${componentName}\`: - ${filePath} - ${duplicatePath}` ); } const LAZY_COMPONENT_NAME_REGEX = /^Lazy(?=[A-Z])/; const REPLACE_COMPONENT_TO_DIRECT_IMPORT_RE = /(?<=[ (])_?resolveComponent\(\s*(?["'`])(?lazy-|Lazy(?=[A-Z]))?(?Idle|Visible|idle-|visible-|Interaction|interaction-|MediaQuery|media-query-|If|if-|Never|never-|Time|time-)?(?[^'"`]*)\k[^)]*\)/g; const LoaderPlugin = (options) => createUnplugin(() => { const exclude = options.transform?.exclude || []; const include = options.transform?.include || []; const nuxt = tryUseNuxt(); return { name: "nuxt:components-loader", enforce: "post", transformInclude(id) { if (exclude.some((pattern) => pattern.test(id))) { return false; } if (include.some((pattern) => pattern.test(id))) { return true; } return isVue(id, { type: ["template", "script"] }) || !!id.match(SX_RE); }, transform(code, id) { const components = options.getComponents(); let num = 0; const imports = /* @__PURE__ */ new Set(); const map = /* @__PURE__ */ new Map(); const s = new MagicString(code); s.replace(REPLACE_COMPONENT_TO_DIRECT_IMPORT_RE, (full, ...args) => { const { lazy, modifier, name } = args.pop(); const normalComponent = findComponent(components, name, options.mode); const modifierComponent = !normalComponent && modifier ? findComponent(components, modifier + name, options.mode) : null; const component = normalComponent || modifierComponent; if (component) { const internalInstall = component._internal_install; if (internalInstall && nuxt?.options.test === false) { if (!nuxt.options.dev) { const relativePath = relative(nuxt.options.rootDir, id); throw new Error(`[nuxt] \`~/${relativePath}\` is using \`${component.pascalName}\` which requires \`${internalInstall}\``); } import('../chunks/features.mjs').then(({ installNuxtModule }) => installNuxtModule(internalInstall)); } let identifier = map.get(component) || `__nuxt_component_${num++}`; map.set(component, identifier); const isServerOnly = !component._raw && component.mode === "server" && !components.some((c) => c.pascalName === component.pascalName && c.mode === "client"); if (isServerOnly) { imports.add(genImport(options.serverComponentRuntime, [{ name: "createServerComponent" }])); imports.add(`const ${identifier} = createServerComponent(${JSON.stringify(component.pascalName)})`); if (!options.experimentalComponentIslands) { logger.warn(`Standalone server components (\`${name}\`) are not yet supported without enabling \`experimental.componentIslands\`.`); } return identifier; } const isClientOnly = !component._raw && component.mode === "client"; if (isClientOnly) { imports.add(genImport("#app/components/client-only", [{ name: "createClientOnly" }])); identifier += "_client"; } if (lazy) { const dynamicImport = `${genDynamicImport(component.filePath, { interopDefault: false })}.then(c => c.${component.export ?? "default"} || c)`; if (modifier && normalComponent) { const relativePath = relative(options.srcDir, component.filePath); switch (modifier) { case "Visible": case "visible-": imports.add(genImport(options.clientDelayedComponentRuntime, [{ name: "createLazyVisibleComponent" }])); identifier += "_lazy_visible"; imports.add(`const ${identifier} = createLazyVisibleComponent(${JSON.stringify(relativePath)}, ${dynamicImport})`); break; case "Interaction": case "interaction-": imports.add(genImport(options.clientDelayedComponentRuntime, [{ name: "createLazyInteractionComponent" }])); identifier += "_lazy_event"; imports.add(`const ${identifier} = createLazyInteractionComponent(${JSON.stringify(relativePath)}, ${dynamicImport})`); break; case "Idle": case "idle-": imports.add(genImport(options.clientDelayedComponentRuntime, [{ name: "createLazyIdleComponent" }])); identifier += "_lazy_idle"; imports.add(`const ${identifier} = createLazyIdleComponent(${JSON.stringify(relativePath)}, ${dynamicImport})`); break; case "MediaQuery": case "media-query-": imports.add(genImport(options.clientDelayedComponentRuntime, [{ name: "createLazyMediaQueryComponent" }])); identifier += "_lazy_media"; imports.add(`const ${identifier} = createLazyMediaQueryComponent(${JSON.stringify(relativePath)}, ${dynamicImport})`); break; case "If": case "if-": imports.add(genImport(options.clientDelayedComponentRuntime, [{ name: "createLazyIfComponent" }])); identifier += "_lazy_if"; imports.add(`const ${identifier} = createLazyIfComponent(${JSON.stringify(relativePath)}, ${dynamicImport})`); break; case "Never": case "never-": imports.add(genImport(options.clientDelayedComponentRuntime, [{ name: "createLazyNeverComponent" }])); identifier += "_lazy_never"; imports.add(`const ${identifier} = createLazyNeverComponent(${JSON.stringify(relativePath)}, ${dynamicImport})`); break; case "Time": case "time-": imports.add(genImport(options.clientDelayedComponentRuntime, [{ name: "createLazyTimeComponent" }])); identifier += "_lazy_time"; imports.add(`const ${identifier} = createLazyTimeComponent(${JSON.stringify(relativePath)}, ${dynamicImport})`); break; } } else { imports.add(genImport("vue", [{ name: "defineAsyncComponent", as: "__defineAsyncComponent" }])); identifier += "_lazy"; imports.add(`const ${identifier} = __defineAsyncComponent(${dynamicImport}${isClientOnly ? ".then(c => createClientOnly(c))" : ""})`); } } else { imports.add(genImport(component.filePath, [{ name: component._raw ? "default" : component.export, as: identifier }])); if (isClientOnly) { imports.add(`const ${identifier}_wrapped = createClientOnly(${identifier})`); identifier += "_wrapped"; } } return identifier; } return full; }); if (imports.size) { s.prepend([...imports, ""].join("\n")); } if (s.hasChanged()) { return { code: s.toString(), map: options.sourcemap ? s.generateMap({ hires: true }) : void 0 }; } } }; }); function findComponent(components, name, mode) { const id = pascalCase(name).replace(QUOTE_RE, ""); const validModes = /* @__PURE__ */ new Set(["all", mode, void 0]); const component = components.find((component2) => id === component2.pascalName && validModes.has(component2.mode)); if (component) { return component; } const otherModeComponent = components.find((component2) => id === component2.pascalName); if (mode === "server" && otherModeComponent) { return components.find((c) => c.pascalName === "ServerPlaceholder"); } return otherModeComponent; } const SCRIPT_RE$1 = /]*>/gi; const HAS_SLOT_OR_CLIENT_RE = /]*>|nuxt-client/; const TEMPLATE_RE$1 = /