2273 lines
82 KiB
JavaScript
2273 lines
82 KiB
JavaScript
import { dirname, resolve, basename, extname, relative, normalize, isAbsolute, join } from 'pathe';
|
|
import { createHooks, createDebugger } from 'hookable';
|
|
import { useNuxt, resolveFiles, defineNuxtModule, addPlugin, addTemplate, addComponent, updateTemplates, addVitePlugin, addWebpackPlugin, findPath, isIgnored, resolveAlias, addPluginTemplate, logger, resolvePath, nuxtCtx, tryResolveModule, installModule, loadNuxtConfig, normalizeTemplate, compileTemplate, normalizePlugin, templateUtils, importModule } from '@nuxt/kit';
|
|
import escapeRE from 'escape-string-regexp';
|
|
import fse from 'fs-extra';
|
|
import { encodePath, parseURL, stringifyQuery, parseQuery, joinURL, withTrailingSlash, withoutLeadingSlash } from 'ufo';
|
|
import { existsSync, readdirSync, statSync, promises } from 'node:fs';
|
|
import { genArrayFromRaw, genSafeVariableName, genImport, genDynamicImport, genObjectFromRawEntries, genString, genExport } from 'knitwork';
|
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
import { kebabCase, splitByCase, pascalCase, camelCase } from 'scule';
|
|
import { createUnplugin } from 'unplugin';
|
|
import { findStaticImports, findExports, parseStaticImport, resolvePath as resolvePath$1 } from 'mlly';
|
|
import { walk } from 'estree-walker';
|
|
import MagicString from 'magic-string';
|
|
import { globby } from 'globby';
|
|
import { hyphenate } from '@vue/shared';
|
|
import { parse, walk as walk$1, ELEMENT_NODE } from 'ultrahtml';
|
|
import { defineUnimportPreset, createUnimport, toImports, scanDirExports } from 'unimport';
|
|
import { createRequire } from 'node:module';
|
|
import { createTransformer } from 'unctx/transform';
|
|
import { stripLiteral } from 'strip-literal';
|
|
import { createNitro, scanHandlers, writeTypes, build as build$1, prepare, copyPublicAssets, prerender, createDevServer } from 'nitropack';
|
|
import defu from 'defu';
|
|
import { dynamicEventHandler } from 'h3';
|
|
import { createHeadCore } from 'unhead';
|
|
import { renderSSRHead } from '@unhead/ssr';
|
|
import chokidar from 'chokidar';
|
|
import { debounce } from 'perfect-debounce';
|
|
import { generateTypes, resolveSchema } from 'untyped';
|
|
import { hash } from 'ohash';
|
|
|
|
let _distDir = dirname(fileURLToPath(import.meta.url));
|
|
if (_distDir.match(/(chunks|shared)$/)) {
|
|
_distDir = dirname(_distDir);
|
|
}
|
|
const distDir = _distDir;
|
|
const pkgDir = resolve(distDir, "..");
|
|
resolve(distDir, "runtime");
|
|
|
|
function getNameFromPath(path) {
|
|
return kebabCase(basename(path).replace(extname(path), "")).replace(/["']/g, "");
|
|
}
|
|
function uniqueBy(arr, key) {
|
|
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;
|
|
}
|
|
function hasSuffix(path, suffix) {
|
|
return basename(path).replace(extname(path), "").endsWith(suffix);
|
|
}
|
|
|
|
async function resolvePagesRoutes() {
|
|
const nuxt = useNuxt();
|
|
const pagesDirs = nuxt.options._layers.map(
|
|
(layer) => resolve(layer.config.srcDir, layer.config.dir?.pages || "pages")
|
|
);
|
|
const allRoutes = (await Promise.all(
|
|
pagesDirs.map(async (dir) => {
|
|
const files = await resolveFiles(dir, `**/*{${nuxt.options.extensions.join(",")}}`);
|
|
files.sort();
|
|
return generateRoutesFromFiles(files, dir);
|
|
})
|
|
)).flat();
|
|
return uniqueBy(allRoutes, "path");
|
|
}
|
|
function generateRoutesFromFiles(files, pagesDir) {
|
|
const routes = [];
|
|
for (const file of files) {
|
|
const segments = relative(pagesDir, file).replace(new RegExp(`${escapeRE(extname(file))}$`), "").split("/");
|
|
const route = {
|
|
name: "",
|
|
path: "",
|
|
file,
|
|
children: []
|
|
};
|
|
let parent = routes;
|
|
for (let i = 0; i < segments.length; i++) {
|
|
const segment = segments[i];
|
|
const tokens = parseSegment(segment);
|
|
const segmentName = tokens.map(({ value }) => value).join("");
|
|
route.name += (route.name && "-") + segmentName;
|
|
const child = parent.find((parentRoute) => parentRoute.name === route.name && !parentRoute.path.endsWith("(.*)*"));
|
|
if (child && child.children) {
|
|
parent = child.children;
|
|
route.path = "";
|
|
} else if (segmentName === "index" && !route.path) {
|
|
route.path += "/";
|
|
} else if (segmentName !== "index") {
|
|
route.path += getRoutePath(tokens);
|
|
}
|
|
}
|
|
parent.push(route);
|
|
}
|
|
return prepareRoutes(routes);
|
|
}
|
|
function getRoutePath(tokens) {
|
|
return tokens.reduce((path, token) => {
|
|
return path + (token.type === 2 /* optional */ ? `:${token.value}?` : token.type === 1 /* dynamic */ ? `:${token.value}` : token.type === 3 /* catchall */ ? `:${token.value}(.*)*` : encodePath(token.value));
|
|
}, "/");
|
|
}
|
|
const PARAM_CHAR_RE = /[\w\d_.]/;
|
|
function parseSegment(segment) {
|
|
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 */ : 3 /* catchall */,
|
|
value: buffer
|
|
});
|
|
buffer = "";
|
|
}
|
|
while (i < segment.length) {
|
|
const c = segment[i];
|
|
switch (state) {
|
|
case 0 /* initial */:
|
|
buffer = "";
|
|
if (c === "[") {
|
|
state = 2 /* dynamic */;
|
|
} else {
|
|
i--;
|
|
state = 1 /* static */;
|
|
}
|
|
break;
|
|
case 1 /* static */:
|
|
if (c === "[") {
|
|
consumeBuffer();
|
|
state = 2 /* dynamic */;
|
|
} else {
|
|
buffer += c;
|
|
}
|
|
break;
|
|
case 4 /* catchall */:
|
|
case 2 /* dynamic */:
|
|
case 3 /* optional */:
|
|
if (buffer === "...") {
|
|
buffer = "";
|
|
state = 4 /* catchall */;
|
|
}
|
|
if (c === "[" && state === 2 /* dynamic */) {
|
|
state = 3 /* optional */;
|
|
}
|
|
if (c === "]" && (state !== 3 /* optional */ || buffer[buffer.length - 1] === "]")) {
|
|
if (!buffer) {
|
|
throw new Error("Empty param");
|
|
} else {
|
|
consumeBuffer();
|
|
}
|
|
state = 0 /* initial */;
|
|
} else if (PARAM_CHAR_RE.test(c)) {
|
|
buffer += c;
|
|
} else ;
|
|
break;
|
|
}
|
|
i++;
|
|
}
|
|
if (state === 2 /* dynamic */) {
|
|
throw new Error(`Unfinished param "${buffer}"`);
|
|
}
|
|
consumeBuffer();
|
|
return tokens;
|
|
}
|
|
function prepareRoutes(routes, parent) {
|
|
for (const route of routes) {
|
|
if (route.name) {
|
|
route.name = route.name.replace(/-index$/, "");
|
|
}
|
|
if (parent && route.path.startsWith("/")) {
|
|
route.path = route.path.slice(1);
|
|
}
|
|
if (route.children?.length) {
|
|
route.children = prepareRoutes(route.children, route);
|
|
}
|
|
if (route.children?.find((childRoute) => childRoute.path === "")) {
|
|
delete route.name;
|
|
}
|
|
}
|
|
return routes;
|
|
}
|
|
function normalizeRoutes(routes, metaImports = /* @__PURE__ */ new Set()) {
|
|
return {
|
|
imports: metaImports,
|
|
routes: genArrayFromRaw(routes.map((route) => {
|
|
const file = normalize(route.file);
|
|
const metaImportName = genSafeVariableName(file) + "Meta";
|
|
metaImports.add(genImport(`${file}?macro=true`, [{ name: "default", as: metaImportName }]));
|
|
let aliasCode = `${metaImportName}?.alias || []`;
|
|
if (Array.isArray(route.alias) && route.alias.length) {
|
|
aliasCode = `${JSON.stringify(route.alias)}.concat(${aliasCode})`;
|
|
}
|
|
return {
|
|
...Object.fromEntries(Object.entries(route).map(([key, value]) => [key, JSON.stringify(value)])),
|
|
name: `${metaImportName}?.name ?? ${route.name ? JSON.stringify(route.name) : "undefined"}`,
|
|
path: `${metaImportName}?.path ?? ${JSON.stringify(route.path)}`,
|
|
children: route.children ? normalizeRoutes(route.children, metaImports).routes : [],
|
|
meta: route.meta ? `{...(${metaImportName} || {}), ...${JSON.stringify(route.meta)}}` : metaImportName,
|
|
alias: aliasCode,
|
|
redirect: route.redirect ? JSON.stringify(route.redirect) : `${metaImportName}?.redirect || undefined`,
|
|
component: genDynamicImport(file, { interopDefault: true })
|
|
};
|
|
}))
|
|
};
|
|
}
|
|
|
|
const CODE_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 = createUnplugin((options) => {
|
|
return {
|
|
name: "nuxt:pages-macros-transform",
|
|
enforce: "post",
|
|
transformInclude(id) {
|
|
const query = parseMacroQuery(id);
|
|
id = normalize(id);
|
|
const isPagesDir = options.dirs.some((dir) => typeof dir === "string" ? id.startsWith(dir) : dir.test(id));
|
|
if (!isPagesDir && !query.macro) {
|
|
return false;
|
|
}
|
|
const { pathname } = parseURL(decodeURIComponent(pathToFileURL(id).href));
|
|
return /\.(m?[jt]sx?|vue)/.test(pathname);
|
|
},
|
|
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({ source: id, includeContent: true }) : void 0
|
|
};
|
|
}
|
|
}
|
|
const hasMacro = code.match(/\bdefinePageMeta\s*\(\s*/);
|
|
if (!query.macro) {
|
|
if (hasMacro) {
|
|
walk(this.parse(code, {
|
|
sourceType: "module",
|
|
ecmaVersion: "latest"
|
|
}), {
|
|
enter(_node) {
|
|
if (_node.type !== "CallExpression" || _node.callee.type !== "Identifier") {
|
|
return;
|
|
}
|
|
const node = _node;
|
|
const name = "name" in node.callee && node.callee.name;
|
|
if (name === "definePageMeta") {
|
|
s.overwrite(node.start, node.end, "false && {}");
|
|
}
|
|
}
|
|
});
|
|
}
|
|
return result();
|
|
}
|
|
const imports = findStaticImports(code);
|
|
const scriptImport = imports.find((i) => parseMacroQuery(i.specifier).type === "script");
|
|
if (scriptImport) {
|
|
const specifier = rewriteQuery(scriptImport.specifier);
|
|
s.overwrite(0, code.length, `export { default } from ${JSON.stringify(specifier)}`);
|
|
return result();
|
|
}
|
|
const currentExports = findExports(code);
|
|
for (const match of currentExports) {
|
|
if (match.type !== "default" || !match.specifier) {
|
|
continue;
|
|
}
|
|
const specifier = rewriteQuery(match.specifier);
|
|
s.overwrite(0, code.length, `export { default } from ${JSON.stringify(specifier)}`);
|
|
return result();
|
|
}
|
|
if (!hasMacro && !code.includes("export { default }") && !code.includes("__nuxt_page_meta")) {
|
|
s.overwrite(0, code.length, CODE_EMPTY + (options.dev ? CODE_HMR : ""));
|
|
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.keys(parsed.namedImports || {}),
|
|
parsed.namespacedImport
|
|
].filter(Boolean)) {
|
|
importMap.set(name, i);
|
|
}
|
|
}
|
|
walk(this.parse(code, {
|
|
sourceType: "module",
|
|
ecmaVersion: "latest"
|
|
}), {
|
|
enter(_node) {
|
|
if (_node.type !== "CallExpression" || _node.callee.type !== "Identifier") {
|
|
return;
|
|
}
|
|
const node = _node;
|
|
const name = "name" in node.callee && node.callee.name;
|
|
if (name !== "definePageMeta") {
|
|
return;
|
|
}
|
|
const meta = node.arguments[0];
|
|
let contents = `const __nuxt_page_meta = ${code.slice(meta.start, meta.end) || "{}"}
|
|
export default __nuxt_page_meta` + (options.dev ? CODE_HMR : "");
|
|
function addImport(name2) {
|
|
if (name2 && importMap.has(name2)) {
|
|
const importValue = importMap.get(name2).code;
|
|
if (!addedImports.has(importValue)) {
|
|
contents = importMap.get(name2).code + "\n" + contents;
|
|
addedImports.add(importValue);
|
|
}
|
|
}
|
|
}
|
|
walk(meta, {
|
|
enter(_node2) {
|
|
if (_node2.type === "CallExpression") {
|
|
const node2 = _node2;
|
|
addImport("name" in node2.callee && node2.callee.name);
|
|
}
|
|
if (_node2.type === "Identifier") {
|
|
const node2 = _node2;
|
|
addImport(node2.name);
|
|
}
|
|
}
|
|
});
|
|
s.overwrite(0, code.length, contents);
|
|
}
|
|
});
|
|
if (!s.hasChanged() && !code.includes("__nuxt_page_meta")) {
|
|
s.overwrite(0, code.length, CODE_EMPTY + (options.dev ? CODE_HMR : ""));
|
|
}
|
|
return result();
|
|
},
|
|
vite: {
|
|
handleHotUpdate: {
|
|
order: "pre",
|
|
handler: ({ modules }) => {
|
|
const index = modules.findIndex((i) => i.id?.includes("?macro=true"));
|
|
if (index !== -1) {
|
|
modules.splice(index, 1);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
};
|
|
});
|
|
function rewriteQuery(id) {
|
|
const query = stringifyQuery({ macro: "true", ...parseMacroQuery(id) });
|
|
return id.replace(/\?.+$/, "?" + query);
|
|
}
|
|
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 pagesModule = defineNuxtModule({
|
|
meta: {
|
|
name: "pages"
|
|
},
|
|
setup(_options, nuxt) {
|
|
const pagesDirs = nuxt.options._layers.map(
|
|
(layer) => resolve(layer.config.srcDir, layer.config.dir?.pages || "pages")
|
|
);
|
|
const isNonEmptyDir = (dir) => existsSync(dir) && readdirSync(dir).length;
|
|
const isPagesEnabled = () => {
|
|
if (typeof nuxt.options.pages === "boolean") {
|
|
return nuxt.options.pages;
|
|
}
|
|
if (nuxt.options._layers.some((layer) => existsSync(resolve(layer.config.srcDir, "app/router.options.ts")))) {
|
|
return true;
|
|
}
|
|
if (pagesDirs.some((dir) => isNonEmptyDir(dir))) {
|
|
return true;
|
|
}
|
|
return false;
|
|
};
|
|
nuxt.options.pages = isPagesEnabled();
|
|
if (!nuxt.options.pages) {
|
|
addPlugin(resolve(distDir, "app/plugins/router"));
|
|
addTemplate({
|
|
filename: "pages.mjs",
|
|
getContents: () => "export { useRoute } from '#app'"
|
|
});
|
|
addComponent({
|
|
name: "NuxtPage",
|
|
filePath: resolve(distDir, "pages/runtime/page-placeholder")
|
|
});
|
|
return;
|
|
}
|
|
const runtimeDir = resolve(distDir, "pages/runtime");
|
|
nuxt.hook("prepare:types", ({ references }) => {
|
|
references.push({ types: "vue-router" });
|
|
});
|
|
nuxt.hook("imports:sources", (sources) => {
|
|
const routerImports = sources.find((s) => s.from === "#app" && s.imports.includes("onBeforeRouteLeave"));
|
|
if (routerImports) {
|
|
routerImports.from = "vue-router";
|
|
}
|
|
});
|
|
nuxt.hook("builder:watch", async (event, path) => {
|
|
const dirs = [
|
|
nuxt.options.dir.pages,
|
|
nuxt.options.dir.layouts,
|
|
nuxt.options.dir.middleware
|
|
].filter(Boolean);
|
|
const pathPattern = new RegExp(`(^|\\/)(${dirs.map(escapeRE).join("|")})/`);
|
|
if (event !== "change" && path.match(pathPattern)) {
|
|
await updateTemplates({
|
|
filter: (template) => template.filename === "routes.mjs"
|
|
});
|
|
}
|
|
});
|
|
nuxt.hook("app:resolve", (app) => {
|
|
if (app.mainComponent.includes("@nuxt/ui-templates")) {
|
|
app.mainComponent = resolve(runtimeDir, "app.vue");
|
|
}
|
|
app.middleware.unshift({
|
|
name: "validate",
|
|
path: resolve(runtimeDir, "validate"),
|
|
global: true
|
|
});
|
|
});
|
|
if (!nuxt.options.dev && nuxt.options._generate) {
|
|
const prerenderRoutes = /* @__PURE__ */ new Set();
|
|
nuxt.hook("modules:done", () => {
|
|
nuxt.hook("pages:extend", (pages) => {
|
|
prerenderRoutes.clear();
|
|
const processPages = (pages2, currentPath = "/") => {
|
|
for (const page of pages2) {
|
|
if (page.path.match(/^\/?:.*(\?|\(\.\*\)\*)$/) && !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);
|
|
}
|
|
}
|
|
};
|
|
processPages(pages);
|
|
});
|
|
});
|
|
nuxt.hook("nitro:build:before", (nitro) => {
|
|
for (const route of nitro.options.prerender.routes || []) {
|
|
if (route === "/") {
|
|
continue;
|
|
}
|
|
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" }
|
|
);
|
|
});
|
|
const pageMetaOptions = {
|
|
dev: nuxt.options.dev,
|
|
sourcemap: nuxt.options.sourcemap.server || nuxt.options.sourcemap.client,
|
|
dirs: nuxt.options._layers.map(
|
|
(layer) => resolve(layer.config.srcDir, layer.config.dir?.pages || "pages")
|
|
)
|
|
};
|
|
addVitePlugin(PageMetaPlugin.vite(pageMetaOptions));
|
|
addWebpackPlugin(PageMetaPlugin.webpack(pageMetaOptions));
|
|
addPlugin(resolve(runtimeDir, "router"));
|
|
const getSources = (pages) => pages.flatMap(
|
|
(p) => [relative(nuxt.options.srcDir, p.file), ...getSources(p.children || [])]
|
|
);
|
|
nuxt.hook("build:manifest", async (manifest) => {
|
|
const pages = await resolvePagesRoutes();
|
|
await nuxt.callHook("pages:extend", pages);
|
|
const sourceFiles = getSources(pages);
|
|
for (const key in manifest) {
|
|
if (manifest[key].isEntry) {
|
|
manifest[key].dynamicImports = manifest[key].dynamicImports?.filter((i) => !sourceFiles.includes(i));
|
|
}
|
|
}
|
|
});
|
|
addTemplate({
|
|
filename: "routes.mjs",
|
|
async getContents() {
|
|
const pages = await resolvePagesRoutes();
|
|
await nuxt.callHook("pages:extend", pages);
|
|
const { routes, imports } = normalizeRoutes(pages);
|
|
return [...imports, `export default ${routes}`].join("\n");
|
|
}
|
|
});
|
|
addTemplate({
|
|
filename: "pages.mjs",
|
|
getContents: () => "export { useRoute } from 'vue-router'"
|
|
});
|
|
nuxt.options.vite.optimizeDeps = nuxt.options.vite.optimizeDeps || {};
|
|
nuxt.options.vite.optimizeDeps.include = nuxt.options.vite.optimizeDeps.include || [];
|
|
nuxt.options.vite.optimizeDeps.include.push("vue-router");
|
|
addTemplate({
|
|
filename: "router.options.mjs",
|
|
getContents: async () => {
|
|
const routerOptionsFiles = (await Promise.all(nuxt.options._layers.map(
|
|
async (layer) => await findPath(resolve(layer.config.srcDir, "app/router.options"))
|
|
))).filter(Boolean);
|
|
routerOptionsFiles.push(resolve(runtimeDir, "router.options"));
|
|
const configRouterOptions = genObjectFromRawEntries(Object.entries(nuxt.options.router.options).map(([key, value]) => [key, genString(value)]));
|
|
return [
|
|
...routerOptionsFiles.map((file, index) => genImport(file, `routerOptions${index}`)),
|
|
`const configRouterOptions = ${configRouterOptions}`,
|
|
"export default {",
|
|
"...configRouterOptions,",
|
|
...routerOptionsFiles.map((_, index) => `...routerOptions${index},`).reverse(),
|
|
"}"
|
|
].join("\n");
|
|
}
|
|
});
|
|
addTemplate({
|
|
filename: "types/middleware.d.ts",
|
|
getContents: ({ app }) => {
|
|
const composablesFile = resolve(runtimeDir, "composables");
|
|
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(" | ") || "string"}`,
|
|
`declare module ${genString(composablesFile)} {`,
|
|
" interface PageMeta {",
|
|
" middleware?: MiddlewareKey | NavigationGuard | Array<MiddlewareKey | NavigationGuard>",
|
|
" }",
|
|
"}"
|
|
].join("\n");
|
|
}
|
|
});
|
|
addTemplate({
|
|
filename: "types/layouts.d.ts",
|
|
getContents: ({ app }) => {
|
|
const composablesFile = resolve(runtimeDir, "composables");
|
|
return [
|
|
"import { ComputedRef, Ref } from 'vue'",
|
|
`export type LayoutKey = ${Object.keys(app.layouts).map((name) => genString(name)).join(" | ") || "string"}`,
|
|
`declare module ${genString(composablesFile)} {`,
|
|
" interface PageMeta {",
|
|
" layout?: false | LayoutKey | Ref<LayoutKey> | ComputedRef<LayoutKey>",
|
|
" }",
|
|
"}"
|
|
].join("\n");
|
|
}
|
|
});
|
|
addComponent({
|
|
name: "NuxtPage",
|
|
filePath: resolve(distDir, "pages/runtime/page")
|
|
});
|
|
nuxt.hook("prepare:types", ({ references }) => {
|
|
references.push({ path: resolve(nuxt.options.buildDir, "types/middleware.d.ts") });
|
|
references.push({ path: resolve(nuxt.options.buildDir, "types/layouts.d.ts") });
|
|
});
|
|
}
|
|
});
|
|
|
|
const components = ["NoScript", "Link", "Base", "Title", "Meta", "Style", "Head", "Html", "Body"];
|
|
const metaModule = defineNuxtModule({
|
|
meta: {
|
|
name: "meta"
|
|
},
|
|
setup(options, nuxt) {
|
|
const runtimeDir = nuxt.options.alias["#head"] || resolve(distDir, "head/runtime");
|
|
nuxt.options.build.transpile.push("@vueuse/head");
|
|
nuxt.options.alias["#head"] = runtimeDir;
|
|
const componentsPath = resolve(runtimeDir, "components");
|
|
for (const componentName of components) {
|
|
addComponent({
|
|
name: componentName,
|
|
filePath: componentsPath,
|
|
export: componentName,
|
|
kebabName: componentName
|
|
});
|
|
}
|
|
addPlugin({ src: resolve(runtimeDir, "lib/vueuse-head.plugin") });
|
|
}
|
|
});
|
|
|
|
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 componentsPluginTemplate = {
|
|
filename: "components.plugin.mjs",
|
|
getContents({ options }) {
|
|
const globalComponents = options.getComponents().filter((c) => c.global === true);
|
|
return `import { defineAsyncComponent } from 'vue'
|
|
import { defineNuxtPlugin } from '#app'
|
|
|
|
const components = ${genObjectFromRawEntries(globalComponents.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}))`];
|
|
}))}
|
|
|
|
export default defineNuxtPlugin(nuxtApp => {
|
|
for (const name in components) {
|
|
nuxtApp.vueApp.component(name, components[name])
|
|
nuxtApp.vueApp.component('Lazy' + name, components[name])
|
|
}
|
|
})
|
|
`;
|
|
}
|
|
};
|
|
const componentsTemplate = {
|
|
getContents({ options }) {
|
|
const imports = /* @__PURE__ */ new Set();
|
|
imports.add("import { defineAsyncComponent } from 'vue'");
|
|
let num = 0;
|
|
const components = options.getComponents(options.mode).flatMap((c) => {
|
|
const exp = c.export === "default" ? "c.default || c" : `c['${c.export}']`;
|
|
const comment = createImportMagicComments(c);
|
|
const isClient = c.mode === "client";
|
|
const definitions = [];
|
|
if (isClient) {
|
|
num++;
|
|
const identifier = `__nuxt_component_${num}`;
|
|
imports.add(genImport("#app/components/client-only", [{ name: "createClientOnly" }]));
|
|
imports.add(genImport(c.filePath, [{ name: c.export, as: identifier }]));
|
|
definitions.push(`export const ${c.pascalName} = /*#__PURE__*/ createClientOnly(${identifier})`);
|
|
} else {
|
|
definitions.push(genExport(c.filePath, [{ name: c.export, as: c.pascalName }]));
|
|
}
|
|
definitions.push(`export const Lazy${c.pascalName} = defineAsyncComponent(${genDynamicImport(c.filePath, { comment })}.then(c => ${isClient ? `createClientOnly(${exp})` : exp}))`);
|
|
return definitions;
|
|
});
|
|
return [
|
|
...imports,
|
|
...components,
|
|
`export const componentNames = ${JSON.stringify(options.getComponents().map((c) => c.pascalName))}`
|
|
].join("\n");
|
|
}
|
|
};
|
|
const componentsTypeTemplate = {
|
|
filename: "components.d.ts",
|
|
getContents: ({ options, nuxt }) => {
|
|
const buildDir = nuxt.options.buildDir;
|
|
const componentTypes = options.getComponents().map((c) => [
|
|
c.pascalName,
|
|
`typeof ${genDynamicImport(isAbsolute(c.filePath) ? relative(buildDir, c.filePath).replace(/(?<=\w)\.(?!vue)\w+$/g, "") : c.filePath.replace(/(?<=\w)\.(?!vue)\w+$/g, ""), { wrapper: false })}['${c.export}']`
|
|
]);
|
|
return `// Generated by components discovery
|
|
declare module '@vue/runtime-core' {
|
|
export interface GlobalComponents {
|
|
${componentTypes.map(([pascalName, type]) => ` '${pascalName}': ${type}`).join("\n")}
|
|
${componentTypes.map(([pascalName, type]) => ` 'Lazy${pascalName}': ${type}`).join("\n")}
|
|
}
|
|
}
|
|
|
|
${componentTypes.map(([pascalName, type]) => `export const ${pascalName}: ${type}`).join("\n")}
|
|
${componentTypes.map(([pascalName, type]) => `export const Lazy${pascalName}: ${type}`).join("\n")}
|
|
|
|
export const componentNames: string[]
|
|
`;
|
|
}
|
|
};
|
|
|
|
async function scanComponents(dirs, srcDir) {
|
|
const components = [];
|
|
const filePaths = /* @__PURE__ */ new Set();
|
|
const scannedPaths = [];
|
|
for (const dir of dirs) {
|
|
const resolvedNames = /* @__PURE__ */ new Map();
|
|
const files = (await globby(dir.pattern, { cwd: dir.path, ignore: dir.ignore })).sort();
|
|
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 global = /\.(global)$/.test(fileName) || dir.global;
|
|
const mode = fileName.match(/(?<=\.)(client|server)(\.global)?$/)?.[1] || "all";
|
|
fileName = fileName.replace(/(\.(client|server))?(\.global)?$/, "");
|
|
if (fileName.toLowerCase() === "index") {
|
|
fileName = dir.pathPrefix === false ? basename(dirname(filePath)) : "";
|
|
}
|
|
const fileNameParts = splitByCase(fileName);
|
|
const componentNameParts = [];
|
|
while (prefixParts.length && (prefixParts[0] || "").toLowerCase() !== (fileNameParts[0] || "").toLowerCase()) {
|
|
componentNameParts.push(prefixParts.shift());
|
|
}
|
|
const componentName = pascalCase(componentNameParts) + pascalCase(fileNameParts);
|
|
const suffix = mode !== "all" ? `-${mode}` : "";
|
|
if (resolvedNames.has(componentName + suffix) || resolvedNames.has(componentName)) {
|
|
console.warn(
|
|
`Two component files resolving to the same name \`${componentName}\`:
|
|
|
|
- ${filePath}
|
|
- ${resolvedNames.get(componentName)}`
|
|
);
|
|
continue;
|
|
}
|
|
resolvedNames.set(componentName + suffix, filePath);
|
|
const pascalName = pascalCase(componentName).replace(/["']/g, "");
|
|
const kebabName = hyphenate(componentName);
|
|
const shortPath = relative(srcDir, filePath);
|
|
const chunkName = "components/" + kebabName + suffix;
|
|
let component = {
|
|
mode,
|
|
global,
|
|
prefetch: Boolean(dir.prefetch),
|
|
preload: Boolean(dir.preload),
|
|
filePath,
|
|
pascalName,
|
|
kebabName,
|
|
chunkName,
|
|
shortPath,
|
|
export: "default"
|
|
};
|
|
if (typeof dir.extendComponent === "function") {
|
|
component = await dir.extendComponent(component) || component;
|
|
}
|
|
if (!components.some((c) => c.pascalName === component.pascalName && ["all", component.mode].includes(c.mode))) {
|
|
components.push(component);
|
|
}
|
|
}
|
|
scannedPaths.push(dir.path);
|
|
}
|
|
return components;
|
|
}
|
|
|
|
function isVueTemplate(id) {
|
|
if (id.endsWith(".vue")) {
|
|
return true;
|
|
}
|
|
const { search } = parseURL(decodeURIComponent(pathToFileURL(id).href));
|
|
if (!search) {
|
|
return false;
|
|
}
|
|
const query = parseQuery(search);
|
|
if (query.macro) {
|
|
return true;
|
|
}
|
|
if (!("vue" in query) || query.type === "style") {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
const loaderPlugin = createUnplugin((options) => {
|
|
const exclude = options.transform?.exclude || [];
|
|
const include = options.transform?.include || [];
|
|
return {
|
|
name: "nuxt:components-loader",
|
|
enforce: "post",
|
|
transformInclude(id) {
|
|
if (exclude.some((pattern) => id.match(pattern))) {
|
|
return false;
|
|
}
|
|
if (include.some((pattern) => id.match(pattern))) {
|
|
return true;
|
|
}
|
|
return isVueTemplate(id);
|
|
},
|
|
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(/(?<=[ (])_?resolveComponent\(\s*["'](lazy-|Lazy)?([^'"]*?)["'][\s,]*[^)]*\)/g, (full, lazy, name) => {
|
|
const component = findComponent(components, name, options.mode);
|
|
if (component) {
|
|
let identifier = map.get(component) || `__nuxt_component_${num++}`;
|
|
map.set(component, identifier);
|
|
const isClientOnly = component.mode === "client";
|
|
if (isClientOnly) {
|
|
imports.add(genImport("#app/components/client-only", [{ name: "createClientOnly" }]));
|
|
identifier += "_client";
|
|
}
|
|
if (lazy) {
|
|
imports.add(genImport("vue", [{ name: "defineAsyncComponent", as: "__defineAsyncComponent" }]));
|
|
identifier += "_lazy";
|
|
imports.add(`const ${identifier} = /*#__PURE__*/ __defineAsyncComponent(${genDynamicImport(component.filePath, { interopDefault: true })}${isClientOnly ? ".then(c => createClientOnly(c))" : ""})`);
|
|
} else {
|
|
imports.add(genImport(component.filePath, [{ name: component.export, as: identifier }]));
|
|
if (isClientOnly) {
|
|
imports.add(`const ${identifier}_wrapped = /*#__PURE__*/ 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({ source: id, includeContent: true }) : void 0
|
|
};
|
|
}
|
|
}
|
|
};
|
|
});
|
|
function findComponent(components, name, mode) {
|
|
const id = pascalCase(name).replace(/["']/g, "");
|
|
const component = components.find((component2) => id === component2.pascalName && ["all", mode, void 0].includes(component2.mode));
|
|
if (!component && components.some((component2) => id === component2.pascalName)) {
|
|
return components.find((component2) => component2.pascalName === "ServerPlaceholder");
|
|
}
|
|
return component;
|
|
}
|
|
|
|
const PLACEHOLDER_RE = /^(v-slot|#)(fallback|placeholder)/;
|
|
const TreeShakeTemplatePlugin = createUnplugin((options) => {
|
|
const regexpMap = /* @__PURE__ */ new WeakMap();
|
|
return {
|
|
name: "nuxt:tree-shake-template",
|
|
enforce: "pre",
|
|
transformInclude(id) {
|
|
const { pathname } = parseURL(decodeURIComponent(pathToFileURL(id).href));
|
|
return pathname.endsWith(".vue");
|
|
},
|
|
async transform(code, id) {
|
|
const template = code.match(/<template>([\s\S]*)<\/template>/);
|
|
if (!template) {
|
|
return;
|
|
}
|
|
const components = options.getComponents();
|
|
if (!regexpMap.has(components)) {
|
|
const clientOnlyComponents2 = components.filter((c) => c.mode === "client" && !components.some((other) => other.mode !== "client" && other.pascalName === c.pascalName)).flatMap((c) => [c.pascalName, c.kebabName]).concat(["ClientOnly", "client-only"]);
|
|
const tags = clientOnlyComponents2.map((component) => `<(${component})[^>]*>[\\s\\S]*?<\\/(${component})>`);
|
|
regexpMap.set(components, [new RegExp(`(${tags.join("|")})`, "g"), clientOnlyComponents2]);
|
|
}
|
|
const [COMPONENTS_RE, clientOnlyComponents] = regexpMap.get(components);
|
|
if (!COMPONENTS_RE.test(code)) {
|
|
return;
|
|
}
|
|
const s = new MagicString(code);
|
|
const ast = parse(template[0]);
|
|
await walk$1(ast, (node) => {
|
|
if (node.type !== ELEMENT_NODE || !clientOnlyComponents.includes(node.name) || !node.children?.length) {
|
|
return;
|
|
}
|
|
const fallback = node.children.find(
|
|
(n) => n.name === "template" && Object.entries(n.attributes)?.flat().some((attr) => PLACEHOLDER_RE.test(attr))
|
|
);
|
|
try {
|
|
const text = fallback ? code.slice(template.index + fallback.loc[0].start, template.index + fallback.loc[fallback.loc.length - 1].end) : "";
|
|
s.overwrite(template.index + node.loc[0].end, template.index + node.loc[node.loc.length - 1].start, text);
|
|
} catch (err) {
|
|
}
|
|
});
|
|
if (s.hasChanged()) {
|
|
return {
|
|
code: s.toString(),
|
|
map: options.sourcemap ? s.generateMap({ source: id, includeContent: true }) : void 0
|
|
};
|
|
}
|
|
}
|
|
};
|
|
});
|
|
|
|
const isPureObjectOrString = (val) => !Array.isArray(val) && typeof val === "object" || typeof val === "string";
|
|
const isDirectory = (p) => {
|
|
try {
|
|
return statSync(p).isDirectory();
|
|
} catch (_e) {
|
|
return false;
|
|
}
|
|
};
|
|
function compareDirByPathLength({ path: pathA }, { path: pathB }) {
|
|
return pathB.split(/[\\/]/).filter(Boolean).length - pathA.split(/[\\/]/).filter(Boolean).length;
|
|
}
|
|
const DEFAULT_COMPONENTS_DIRS_RE = /\/components$|\/components\/global$/;
|
|
const componentsModule = defineNuxtModule({
|
|
meta: {
|
|
name: "components",
|
|
configKey: "components"
|
|
},
|
|
defaults: {
|
|
dirs: []
|
|
},
|
|
setup(componentOptions, nuxt) {
|
|
let componentDirs = [];
|
|
const context = {
|
|
components: []
|
|
};
|
|
const getComponents = (mode) => {
|
|
return mode && mode !== "all" ? context.components.filter((c) => c.mode === mode || c.mode === "all") : context.components;
|
|
};
|
|
const normalizeDirs = (dir, cwd) => {
|
|
if (Array.isArray(dir)) {
|
|
return dir.map((dir2) => normalizeDirs(dir2, cwd)).flat().sort(compareDirByPathLength);
|
|
}
|
|
if (dir === true || dir === void 0) {
|
|
return [
|
|
{ path: resolve(cwd, "components/global"), global: true },
|
|
{ path: resolve(cwd, "components") }
|
|
];
|
|
}
|
|
if (typeof dir === "string") {
|
|
return [
|
|
{ path: resolve(cwd, resolveAlias(dir)) }
|
|
];
|
|
}
|
|
if (!dir) {
|
|
return [];
|
|
}
|
|
const dirs = (dir.dirs || [dir]).map((dir2) => typeof dir2 === "string" ? { path: dir2 } : dir2).filter((_dir) => _dir.path);
|
|
return dirs.map((_dir) => ({
|
|
..._dir,
|
|
path: resolve(cwd, resolveAlias(_dir.path))
|
|
}));
|
|
};
|
|
nuxt.hook("app:resolve", async () => {
|
|
const allDirs = nuxt.options._layers.map((layer) => normalizeDirs(layer.config.components, layer.config.srcDir)).flat();
|
|
await nuxt.callHook("components:dirs", allDirs);
|
|
componentDirs = allDirs.filter(isPureObjectOrString).map((dir) => {
|
|
const dirOptions = typeof dir === "object" ? dir : { path: dir };
|
|
const dirPath = resolveAlias(dirOptions.path);
|
|
const transpile = typeof dirOptions.transpile === "boolean" ? dirOptions.transpile : "auto";
|
|
const extensions = (dirOptions.extensions || nuxt.options.extensions).map((e) => e.replace(/^\./g, ""));
|
|
const present = isDirectory(dirPath);
|
|
if (!present && !DEFAULT_COMPONENTS_DIRS_RE.test(dirOptions.path)) {
|
|
console.warn("Components directory not found: `" + dirPath + "`");
|
|
}
|
|
return {
|
|
global: componentOptions.global,
|
|
...dirOptions,
|
|
enabled: true,
|
|
path: dirPath,
|
|
extensions,
|
|
pattern: dirOptions.pattern || `**/*.{${extensions.join(",")},}`,
|
|
ignore: [
|
|
"**/*{M,.m,-m}ixin.{js,ts,jsx,tsx}",
|
|
"**/*.d.ts",
|
|
...dirOptions.ignore || []
|
|
],
|
|
transpile: transpile === "auto" ? dirPath.includes("node_modules") : transpile
|
|
};
|
|
}).filter((d) => d.enabled);
|
|
componentDirs = [
|
|
...componentDirs.filter((dir) => !dir.path.includes("node_modules")),
|
|
...componentDirs.filter((dir) => dir.path.includes("node_modules"))
|
|
];
|
|
nuxt.options.build.transpile.push(...componentDirs.filter((dir) => dir.transpile).map((dir) => dir.path));
|
|
});
|
|
addTemplate({ ...componentsTypeTemplate, options: { getComponents } });
|
|
addPluginTemplate({ ...componentsPluginTemplate, options: { getComponents } });
|
|
addTemplate({ ...componentsTemplate, filename: "components.server.mjs", options: { getComponents, mode: "server" } });
|
|
addTemplate({ ...componentsTemplate, filename: "components.client.mjs", options: { getComponents, mode: "client" } });
|
|
nuxt.hook("vite:extendConfig", (config, { isClient }) => {
|
|
const mode = isClient ? "client" : "server";
|
|
config.resolve.alias["#components"] = resolve(nuxt.options.buildDir, `components.${mode}.mjs`);
|
|
});
|
|
nuxt.hook("webpack:config", (configs) => {
|
|
for (const config of configs) {
|
|
const mode = config.name === "server" ? "server" : "client";
|
|
config.resolve.alias["#components"] = resolve(nuxt.options.buildDir, `components.${mode}.mjs`);
|
|
}
|
|
});
|
|
nuxt.hook("build:manifest", (manifest) => {
|
|
const sourceFiles = getComponents().filter((c) => c.global).map((c) => relative(nuxt.options.srcDir, c.filePath));
|
|
for (const key in manifest) {
|
|
if (manifest[key].isEntry) {
|
|
manifest[key].dynamicImports = manifest[key].dynamicImports?.filter((i) => !sourceFiles.includes(i));
|
|
}
|
|
}
|
|
});
|
|
nuxt.hook("app:templates", async () => {
|
|
const newComponents = await scanComponents(componentDirs, nuxt.options.srcDir);
|
|
await nuxt.callHook("components:extend", newComponents);
|
|
for (const component of newComponents) {
|
|
if (component.mode === "client" && !newComponents.some((c) => c.pascalName === component.pascalName && c.mode === "server")) {
|
|
newComponents.push({
|
|
...component,
|
|
mode: "server",
|
|
filePath: resolve(distDir, "app/components/server-placeholder"),
|
|
chunkName: "components/" + component.kebabName
|
|
});
|
|
}
|
|
}
|
|
context.components = newComponents;
|
|
});
|
|
nuxt.hook("prepare:types", ({ references, tsConfig }) => {
|
|
tsConfig.compilerOptions.paths["#components"] = [relative(nuxt.options.rootDir, resolve(nuxt.options.buildDir, "components"))];
|
|
references.push({ path: resolve(nuxt.options.buildDir, "components.d.ts") });
|
|
});
|
|
nuxt.hook("builder:watch", async (event, path) => {
|
|
if (!["add", "unlink"].includes(event)) {
|
|
return;
|
|
}
|
|
const fPath = resolve(nuxt.options.srcDir, path);
|
|
if (componentDirs.find((dir) => fPath.startsWith(dir.path))) {
|
|
await updateTemplates({
|
|
filter: (template) => [
|
|
"components.plugin.mjs",
|
|
"components.d.ts",
|
|
"components.server.mjs",
|
|
"components.client.mjs"
|
|
].includes(template.filename)
|
|
});
|
|
}
|
|
});
|
|
nuxt.hook("vite:extendConfig", (config, { isClient, isServer }) => {
|
|
const mode = isClient ? "client" : "server";
|
|
config.plugins = config.plugins || [];
|
|
config.plugins.push(loaderPlugin.vite({
|
|
sourcemap: nuxt.options.sourcemap[mode],
|
|
getComponents,
|
|
mode
|
|
}));
|
|
if (nuxt.options.experimental.treeshakeClientOnly && isServer) {
|
|
config.plugins.push(TreeShakeTemplatePlugin.vite({
|
|
sourcemap: nuxt.options.sourcemap[mode],
|
|
getComponents
|
|
}));
|
|
}
|
|
});
|
|
nuxt.hook("webpack:config", (configs) => {
|
|
configs.forEach((config) => {
|
|
const mode = config.name === "client" ? "client" : "server";
|
|
config.plugins = config.plugins || [];
|
|
config.plugins.push(loaderPlugin.webpack({
|
|
sourcemap: nuxt.options.sourcemap[mode],
|
|
getComponents,
|
|
mode
|
|
}));
|
|
if (nuxt.options.experimental.treeshakeClientOnly && mode === "server") {
|
|
config.plugins.push(TreeShakeTemplatePlugin.webpack({
|
|
sourcemap: nuxt.options.sourcemap[mode],
|
|
getComponents
|
|
}));
|
|
}
|
|
});
|
|
});
|
|
}
|
|
});
|
|
|
|
const TransformPlugin = createUnplugin(({ ctx, options, sourcemap }) => {
|
|
return {
|
|
name: "nuxt:imports-transform",
|
|
enforce: "post",
|
|
transformInclude(id) {
|
|
const { pathname, search } = parseURL(decodeURIComponent(pathToFileURL(id).href));
|
|
const query = parseQuery(search);
|
|
if (options.transform?.include?.some((pattern) => id.match(pattern))) {
|
|
return true;
|
|
}
|
|
if (options.transform?.exclude?.some((pattern) => id.match(pattern))) {
|
|
return false;
|
|
}
|
|
if (id.endsWith(".vue") || "macro" in query || "vue" in query && (query.type === "template" || query.type === "script" || "setup" in query)) {
|
|
return true;
|
|
}
|
|
if (pathname.match(/\.((c|m)?j|t)sx?$/g)) {
|
|
return true;
|
|
}
|
|
},
|
|
async transform(code, id) {
|
|
id = normalize(id);
|
|
const isNodeModule = id.match(/[\\/]node_modules[\\/]/) && !options.transform?.include?.some((pattern) => id.match(pattern));
|
|
if (isNodeModule && !code.match(/(['"])#imports\1/)) {
|
|
return;
|
|
}
|
|
const { s } = await ctx.injectImports(code, id, { autoImport: options.autoImport && !isNodeModule });
|
|
if (s.hasChanged()) {
|
|
return {
|
|
code: s.toString(),
|
|
map: sourcemap ? s.generateMap({ source: id, includeContent: true }) : void 0
|
|
};
|
|
}
|
|
}
|
|
};
|
|
});
|
|
|
|
const commonPresets = [
|
|
defineUnimportPreset({
|
|
from: "#head",
|
|
imports: [
|
|
"useHead"
|
|
]
|
|
}),
|
|
defineUnimportPreset({
|
|
from: "vue-demi",
|
|
imports: [
|
|
"isVue2",
|
|
"isVue3"
|
|
]
|
|
})
|
|
];
|
|
const appPreset = defineUnimportPreset({
|
|
from: "#app",
|
|
imports: [
|
|
"useAsyncData",
|
|
"useLazyAsyncData",
|
|
"refreshNuxtData",
|
|
"clearNuxtData",
|
|
"defineNuxtComponent",
|
|
"useNuxtApp",
|
|
"defineNuxtPlugin",
|
|
"useRuntimeConfig",
|
|
"useState",
|
|
"useFetch",
|
|
"useLazyFetch",
|
|
"useCookie",
|
|
"useRequestHeaders",
|
|
"useRequestEvent",
|
|
"setResponseStatus",
|
|
"setPageLayout",
|
|
"useRouter",
|
|
"useRoute",
|
|
"defineNuxtRouteMiddleware",
|
|
"navigateTo",
|
|
"abortNavigation",
|
|
"addRouteMiddleware",
|
|
"showError",
|
|
"clearError",
|
|
"isNuxtError",
|
|
"useError",
|
|
"createError",
|
|
"defineNuxtLink",
|
|
"useAppConfig",
|
|
"updateAppConfig",
|
|
"defineAppConfig",
|
|
"preloadComponents",
|
|
"preloadRouteComponents",
|
|
"prefetchComponents",
|
|
"loadPayload",
|
|
"preloadPayload",
|
|
"isPrerendered"
|
|
]
|
|
});
|
|
const routerPreset = defineUnimportPreset({
|
|
from: "#app",
|
|
imports: [
|
|
"onBeforeRouteLeave",
|
|
"onBeforeRouteUpdate"
|
|
]
|
|
});
|
|
const vuePreset = defineUnimportPreset({
|
|
from: "vue",
|
|
imports: [
|
|
"withCtx",
|
|
"withDirectives",
|
|
"withKeys",
|
|
"withMemo",
|
|
"withModifiers",
|
|
"withScopeId",
|
|
"onActivated",
|
|
"onBeforeMount",
|
|
"onBeforeUnmount",
|
|
"onBeforeUpdate",
|
|
"onDeactivated",
|
|
"onErrorCaptured",
|
|
"onMounted",
|
|
"onRenderTracked",
|
|
"onRenderTriggered",
|
|
"onServerPrefetch",
|
|
"onUnmounted",
|
|
"onUpdated",
|
|
"computed",
|
|
"customRef",
|
|
"isProxy",
|
|
"isReactive",
|
|
"isReadonly",
|
|
"isRef",
|
|
"markRaw",
|
|
"proxyRefs",
|
|
"reactive",
|
|
"readonly",
|
|
"ref",
|
|
"shallowReactive",
|
|
"shallowReadonly",
|
|
"shallowRef",
|
|
"toRaw",
|
|
"toRef",
|
|
"toRefs",
|
|
"triggerRef",
|
|
"unref",
|
|
"watch",
|
|
"watchEffect",
|
|
"isShallow",
|
|
"effect",
|
|
"effectScope",
|
|
"getCurrentScope",
|
|
"onScopeDispose",
|
|
"defineComponent",
|
|
"defineAsyncComponent",
|
|
"resolveComponent",
|
|
"getCurrentInstance",
|
|
"h",
|
|
"inject",
|
|
"nextTick",
|
|
"provide",
|
|
"useAttrs",
|
|
"useCssModule",
|
|
"useCssVars",
|
|
"useSlots",
|
|
"useTransitionState"
|
|
]
|
|
});
|
|
const defaultPresets = [
|
|
...commonPresets,
|
|
appPreset,
|
|
routerPreset,
|
|
vuePreset
|
|
];
|
|
|
|
const importsModule = defineNuxtModule({
|
|
meta: {
|
|
name: "imports",
|
|
configKey: "imports"
|
|
},
|
|
defaults: {
|
|
autoImport: true,
|
|
presets: defaultPresets,
|
|
global: false,
|
|
imports: [],
|
|
dirs: [],
|
|
transform: {
|
|
include: [],
|
|
exclude: void 0
|
|
}
|
|
},
|
|
async setup(options, nuxt) {
|
|
const presets = JSON.parse(JSON.stringify(options.presets));
|
|
await nuxt.callHook("imports:sources", presets);
|
|
const ctx = createUnimport({
|
|
presets,
|
|
imports: options.imports,
|
|
virtualImports: ["#imports"],
|
|
addons: {
|
|
vueTemplate: options.autoImport
|
|
}
|
|
});
|
|
let composablesDirs = [];
|
|
for (const layer of nuxt.options._layers) {
|
|
composablesDirs.push(resolve(layer.config.srcDir, "composables"));
|
|
composablesDirs.push(resolve(layer.config.srcDir, "utils"));
|
|
for (const dir of layer.config.imports?.dirs ?? []) {
|
|
if (!dir) {
|
|
continue;
|
|
}
|
|
composablesDirs.push(resolve(layer.config.srcDir, dir));
|
|
}
|
|
}
|
|
await nuxt.callHook("imports:dirs", composablesDirs);
|
|
composablesDirs = composablesDirs.map((dir) => normalize(dir));
|
|
addTemplate({
|
|
filename: "imports.mjs",
|
|
getContents: async () => await ctx.toExports() + '\nif (process.dev) { console.warn("[nuxt] `#imports` should be transformed with real imports. There seems to be something wrong with the imports plugin.") }'
|
|
});
|
|
nuxt.options.alias["#imports"] = join(nuxt.options.buildDir, "imports");
|
|
if (nuxt.options.dev && options.global) {
|
|
addPluginTemplate({
|
|
filename: "imports.mjs",
|
|
getContents: async () => {
|
|
const imports = await ctx.getImports();
|
|
const importStatement = toImports(imports);
|
|
const globalThisSet = imports.map((i) => `globalThis.${i.as} = ${i.as};`).join("\n");
|
|
return `${importStatement}
|
|
|
|
${globalThisSet}
|
|
|
|
export default () => {};`;
|
|
}
|
|
});
|
|
} else {
|
|
addVitePlugin(TransformPlugin.vite({ ctx, options, sourcemap: nuxt.options.sourcemap.server || nuxt.options.sourcemap.client }));
|
|
addWebpackPlugin(TransformPlugin.webpack({ ctx, options, sourcemap: nuxt.options.sourcemap.server || nuxt.options.sourcemap.client }));
|
|
}
|
|
const regenerateImports = async () => {
|
|
ctx.clearDynamicImports();
|
|
await ctx.modifyDynamicImports(async (imports) => {
|
|
imports.push(...await scanDirExports(composablesDirs));
|
|
await nuxt.callHook("imports:extend", imports);
|
|
});
|
|
};
|
|
await regenerateImports();
|
|
addDeclarationTemplates(ctx, options);
|
|
nuxt.hook("prepare:types", ({ references }) => {
|
|
references.push({ path: resolve(nuxt.options.buildDir, "types/imports.d.ts") });
|
|
references.push({ path: resolve(nuxt.options.buildDir, "imports.d.ts") });
|
|
});
|
|
const templates = [
|
|
"types/imports.d.ts",
|
|
"imports.d.ts",
|
|
"imports.mjs"
|
|
];
|
|
nuxt.hook("builder:watch", async (_, path) => {
|
|
const _resolved = resolve(nuxt.options.srcDir, path);
|
|
if (composablesDirs.find((dir) => _resolved.startsWith(dir))) {
|
|
await updateTemplates({
|
|
filter: (template) => templates.includes(template.filename)
|
|
});
|
|
}
|
|
});
|
|
nuxt.hook("builder:generateApp", async () => {
|
|
await regenerateImports();
|
|
});
|
|
}
|
|
});
|
|
function addDeclarationTemplates(ctx, options) {
|
|
const nuxt = useNuxt();
|
|
const stripExtension = (path) => path.replace(/\.[a-z]+$/, "");
|
|
const resolved = {};
|
|
const r = ({ from }) => {
|
|
if (resolved[from]) {
|
|
return resolved[from];
|
|
}
|
|
let path = resolveAlias(from);
|
|
if (isAbsolute(path)) {
|
|
path = relative(join(nuxt.options.buildDir, "types"), path);
|
|
}
|
|
path = stripExtension(path);
|
|
resolved[from] = path;
|
|
return path;
|
|
};
|
|
addTemplate({
|
|
filename: "imports.d.ts",
|
|
getContents: () => ctx.toExports(nuxt.options.buildDir)
|
|
});
|
|
addTemplate({
|
|
filename: "types/imports.d.ts",
|
|
getContents: async () => "// Generated by auto imports\n" + (options.autoImport ? await ctx.generateTypeDeclarations({ resolvePath: r }) : "// Implicit auto importing is disabled, you can use explicitly import from `#imports` instead.")
|
|
});
|
|
}
|
|
|
|
const version = "3.0.0";
|
|
|
|
const _require = createRequire(import.meta.url);
|
|
const vueAppPatterns = (nuxt) => [
|
|
[/^(nuxt3|nuxt)$/, "`nuxt3`/`nuxt` cannot be imported directly. Instead, import runtime Nuxt composables from `#app` or `#imports`."],
|
|
[/^((|~|~~|@|@@)\/)?nuxt\.config(\.|$)/, "Importing directly from a `nuxt.config` file is not allowed. Instead, use runtime config or a module."],
|
|
[/(^|node_modules\/)@vue\/composition-api/],
|
|
...nuxt.options.modules.filter((m) => typeof m === "string").map((m) => [new RegExp(`^${escapeRE(m)}$`), "Importing directly from module entry points is not allowed."]),
|
|
...[/(^|node_modules\/)@nuxt\/kit/, /^nitropack/].map((i) => [i, "This module cannot be imported in the Vue part of your app."]),
|
|
[new RegExp(escapeRE(join(nuxt.options.srcDir, nuxt.options.dir.server || "server")) + "\\/(api|routes|middleware|plugins)\\/"), "Importing from server is not allowed in the Vue part of your app."]
|
|
];
|
|
const ImportProtectionPlugin = createUnplugin(function(options) {
|
|
const cache = {};
|
|
const importersToExclude = options?.exclude || [];
|
|
return {
|
|
name: "nuxt:import-protection",
|
|
enforce: "pre",
|
|
resolveId(id, importer) {
|
|
if (!importer) {
|
|
return;
|
|
}
|
|
if (id.startsWith(".")) {
|
|
id = join(importer, "..", id);
|
|
}
|
|
if (isAbsolute(id)) {
|
|
id = relative(options.rootDir, id);
|
|
}
|
|
if (importersToExclude.some((p) => typeof p === "string" ? importer === p : p.test(importer))) {
|
|
return;
|
|
}
|
|
const invalidImports = options.patterns.filter(([pattern]) => pattern instanceof RegExp ? pattern.test(id) : pattern === id);
|
|
let matched = false;
|
|
for (const match of invalidImports) {
|
|
cache[id] = cache[id] || /* @__PURE__ */ new Map();
|
|
const [pattern, warning] = match;
|
|
if (cache[id].has(pattern)) {
|
|
continue;
|
|
}
|
|
const relativeImporter = isAbsolute(importer) ? relative(options.rootDir, importer) : importer;
|
|
logger.error(warning || "Invalid import", `[importing \`${id}\` from \`${relativeImporter}\`]`);
|
|
cache[id].set(pattern, true);
|
|
matched = true;
|
|
}
|
|
if (matched) {
|
|
return _require.resolve("unenv/runtime/mock/proxy");
|
|
}
|
|
return null;
|
|
}
|
|
};
|
|
});
|
|
|
|
const UnctxTransformPlugin = (nuxt) => {
|
|
const transformer = createTransformer({
|
|
asyncFunctions: ["defineNuxtPlugin", "defineNuxtRouteMiddleware"]
|
|
});
|
|
let app;
|
|
nuxt.hook("app:resolve", (_app) => {
|
|
app = _app;
|
|
});
|
|
return createUnplugin((options = {}) => ({
|
|
name: "unctx:transfrom",
|
|
enforce: "post",
|
|
transformInclude(id) {
|
|
id = normalize(id).replace(/\?.*$/, "");
|
|
return app?.plugins.some((i) => i.src === id) || app?.middleware.some((m) => m.path === id);
|
|
},
|
|
transform(code, id) {
|
|
const result = transformer.transform(code);
|
|
if (result) {
|
|
return {
|
|
code: result.code,
|
|
map: options.sourcemap ? result.magicString.generateMap({ source: id, includeContent: true }) : void 0
|
|
};
|
|
}
|
|
}
|
|
}));
|
|
};
|
|
|
|
const TreeShakePlugin = createUnplugin((options) => {
|
|
const COMPOSABLE_RE = new RegExp(`($\\s+)(${options.treeShake.join("|")})(?=\\()`, "gm");
|
|
return {
|
|
name: "nuxt:server-treeshake:transfrom",
|
|
enforce: "post",
|
|
transformInclude(id) {
|
|
const { pathname, search } = parseURL(decodeURIComponent(pathToFileURL(id).href));
|
|
const { type } = parseQuery(search);
|
|
if (pathname.endsWith(".vue") && (type === "script" || !search)) {
|
|
return true;
|
|
}
|
|
if (pathname.match(/\.((c|m)?j|t)sx?$/g)) {
|
|
return true;
|
|
}
|
|
},
|
|
transform(code, id) {
|
|
if (!code.match(COMPOSABLE_RE)) {
|
|
return;
|
|
}
|
|
const s = new MagicString(code);
|
|
const strippedCode = stripLiteral(code);
|
|
for (const match of strippedCode.matchAll(COMPOSABLE_RE) || []) {
|
|
s.overwrite(match.index, match.index + match[0].length, `${match[1]} /*#__PURE__*/ false && ${match[2]}`);
|
|
}
|
|
if (s.hasChanged()) {
|
|
return {
|
|
code: s.toString(),
|
|
map: options.sourcemap ? s.generateMap({ source: id, includeContent: true }) : void 0
|
|
};
|
|
}
|
|
}
|
|
};
|
|
});
|
|
|
|
const DevOnlyPlugin = createUnplugin((options) => {
|
|
const DEVONLY_COMP_RE = /<dev-?only>(:?[\s\S]*)<\/dev-?only>/gmi;
|
|
return {
|
|
name: "nuxt:server-devonly:transfrom",
|
|
enforce: "pre",
|
|
transformInclude(id) {
|
|
const { pathname, search } = parseURL(decodeURIComponent(pathToFileURL(id).href));
|
|
const { type } = parseQuery(search);
|
|
if (pathname.endsWith(".vue") && (type === "template" || !search)) {
|
|
return true;
|
|
}
|
|
},
|
|
transform(code, id) {
|
|
if (!code.match(DEVONLY_COMP_RE)) {
|
|
return;
|
|
}
|
|
const s = new MagicString(code);
|
|
const strippedCode = stripLiteral(code);
|
|
for (const match of strippedCode.matchAll(DEVONLY_COMP_RE) || []) {
|
|
s.remove(match.index, match.index + match[0].length);
|
|
}
|
|
if (s.hasChanged()) {
|
|
return {
|
|
code: s.toString(),
|
|
map: options.sourcemap ? s.generateMap({ source: id, includeContent: true }) : void 0
|
|
};
|
|
}
|
|
}
|
|
};
|
|
});
|
|
|
|
const addModuleTranspiles = (opts = {}) => {
|
|
const nuxt = useNuxt();
|
|
const modules = [
|
|
...opts.additionalModules || [],
|
|
...nuxt.options.modules,
|
|
...nuxt.options._modules
|
|
].map((m) => typeof m === "string" ? m : Array.isArray(m) ? m[0] : m.src).filter((m) => typeof m === "string").map((m) => m.split("node_modules/").pop());
|
|
nuxt.options.build.transpile = nuxt.options.build.transpile.map((m) => typeof m === "string" ? m.split("node_modules/").pop() : m).filter((x) => !!x);
|
|
function isTranspilePresent(mod) {
|
|
return nuxt.options.build.transpile.some((t) => !(t instanceof Function) && (t instanceof RegExp ? t.test(mod) : new RegExp(t).test(mod)));
|
|
}
|
|
for (const module of modules) {
|
|
if (!isTranspilePresent(module)) {
|
|
nuxt.options.build.transpile.push(module);
|
|
}
|
|
}
|
|
};
|
|
|
|
async function initNitro(nuxt) {
|
|
const _nitroConfig = nuxt.options.nitro || {};
|
|
const nitroConfig = defu(_nitroConfig, {
|
|
debug: nuxt.options.debug,
|
|
rootDir: nuxt.options.rootDir,
|
|
workspaceDir: nuxt.options.workspaceDir,
|
|
srcDir: nuxt.options.serverDir,
|
|
dev: nuxt.options.dev,
|
|
buildDir: nuxt.options.buildDir,
|
|
esbuild: {
|
|
options: {
|
|
exclude: [
|
|
new RegExp(`node_modules\\/(?!${nuxt.options._layers.map((l) => l.cwd.match(/(?<=\/)node_modules\/(.+)$/)?.[1]).filter(Boolean).map((dir) => escapeRE(dir)).join("|")})`)
|
|
]
|
|
}
|
|
},
|
|
analyze: nuxt.options.build.analyze && {
|
|
template: "treemap",
|
|
projectRoot: nuxt.options.rootDir,
|
|
filename: join(nuxt.options.rootDir, ".nuxt/stats", "{name}.html")
|
|
},
|
|
scanDirs: nuxt.options._layers.map((layer) => (layer.config.serverDir || layer.config.srcDir) && resolve(layer.cwd, layer.config.serverDir || resolve(layer.config.srcDir, "server"))).filter(Boolean),
|
|
renderer: resolve(distDir, "core/runtime/nitro/renderer"),
|
|
errorHandler: resolve(distDir, "core/runtime/nitro/error"),
|
|
nodeModulesDirs: nuxt.options.modulesDir,
|
|
handlers: nuxt.options.serverHandlers,
|
|
devHandlers: [],
|
|
baseURL: nuxt.options.app.baseURL,
|
|
virtual: {
|
|
"#internal/nuxt.config.mjs": () => nuxt.vfs["#build/nuxt.config"]
|
|
},
|
|
routeRules: {
|
|
"/__nuxt_error": { cache: false }
|
|
},
|
|
runtimeConfig: {
|
|
...nuxt.options.runtimeConfig,
|
|
nitro: {
|
|
envPrefix: "NUXT_",
|
|
...nuxt.options.runtimeConfig.nitro
|
|
}
|
|
},
|
|
typescript: {
|
|
generateTsConfig: false
|
|
},
|
|
publicAssets: [
|
|
{ dir: resolve(nuxt.options.buildDir, "dist/client") },
|
|
...nuxt.options._layers.map((layer) => join(layer.config.srcDir, layer.config.dir?.public || "public")).filter((dir) => existsSync(dir)).map((dir) => ({ dir }))
|
|
],
|
|
prerender: {
|
|
crawlLinks: nuxt.options._generate ?? void 0,
|
|
routes: [].concat(nuxt.options.generate.routes).concat(nuxt.options._generate ? [nuxt.options.ssr ? "/" : "/index.html", "/200.html", "/404.html"] : [])
|
|
},
|
|
sourceMap: nuxt.options.sourcemap.server,
|
|
externals: {
|
|
inline: [
|
|
...nuxt.options.dev ? [] : [
|
|
...nuxt.options.experimental.externalVue ? [] : ["vue", "@vue/"],
|
|
"@nuxt/",
|
|
nuxt.options.buildDir
|
|
],
|
|
"nuxt/dist",
|
|
"nuxt3/dist",
|
|
distDir
|
|
]
|
|
},
|
|
alias: {
|
|
...nuxt.options.experimental.externalVue ? {} : {
|
|
"vue/compiler-sfc": "vue/compiler-sfc",
|
|
"vue/server-renderer": "vue/server-renderer",
|
|
vue: await resolvePath(`vue/dist/vue.cjs${nuxt.options.dev ? "" : ".prod"}.js`)
|
|
},
|
|
"estree-walker": "unenv/runtime/mock/proxy",
|
|
"@babel/parser": "unenv/runtime/mock/proxy",
|
|
"@vue/compiler-core": "unenv/runtime/mock/proxy",
|
|
"@vue/compiler-dom": "unenv/runtime/mock/proxy",
|
|
"@vue/compiler-ssr": "unenv/runtime/mock/proxy",
|
|
"@vue/devtools-api": "vue-devtools-stub",
|
|
"#paths": resolve(distDir, "core/runtime/nitro/paths"),
|
|
...nuxt.options.alias
|
|
},
|
|
replace: {
|
|
"process.env.NUXT_NO_SSR": nuxt.options.ssr === false,
|
|
"process.env.NUXT_EARLY_HINTS": nuxt.options.experimental.writeEarlyHints !== false,
|
|
"process.env.NUXT_NO_SCRIPTS": !!nuxt.options.experimental.noScripts && !nuxt.options.dev,
|
|
"process.env.NUXT_INLINE_STYLES": !!nuxt.options.experimental.inlineSSRStyles,
|
|
"process.env.NUXT_PAYLOAD_EXTRACTION": !!nuxt.options.experimental.payloadExtraction,
|
|
"process.dev": nuxt.options.dev,
|
|
__VUE_PROD_DEVTOOLS__: false
|
|
},
|
|
rollupConfig: {
|
|
plugins: []
|
|
}
|
|
});
|
|
const head = createHeadCore();
|
|
head.push(nuxt.options.app.head);
|
|
const headChunk = await renderSSRHead(head);
|
|
nitroConfig.virtual["#head-static"] = `export default ${JSON.stringify(headChunk)}`;
|
|
if (!nuxt.options.ssr) {
|
|
nitroConfig.virtual["#build/dist/server/server.mjs"] = "export default () => {}";
|
|
}
|
|
if (!nuxt.options.experimental.inlineSSRStyles) {
|
|
nitroConfig.virtual["#build/dist/server/styles.mjs"] = "export default {}";
|
|
}
|
|
nitroConfig.rollupConfig.plugins.push(
|
|
ImportProtectionPlugin.rollup({
|
|
rootDir: nuxt.options.rootDir,
|
|
patterns: [
|
|
...["#app", /^#build(\/|$)/].map((p) => [p, "Vue app aliases are not allowed in server routes."])
|
|
],
|
|
exclude: [/core[\\/]runtime[\\/]nitro[\\/]renderer/]
|
|
})
|
|
);
|
|
await nuxt.callHook("nitro:config", nitroConfig);
|
|
const nitro = await createNitro(nitroConfig);
|
|
nuxt._nitro = nitro;
|
|
await nuxt.callHook("nitro:init", nitro);
|
|
nitro.vfs = nuxt.vfs = nitro.vfs || nuxt.vfs || {};
|
|
nuxt.hook("close", () => nitro.hooks.callHook("close"));
|
|
nitro.hooks.hook("prerender:routes", (routes) => {
|
|
nuxt.callHook("prerender:routes", { routes });
|
|
});
|
|
const devMiddlewareHandler = dynamicEventHandler();
|
|
nitro.options.devHandlers.unshift({ handler: devMiddlewareHandler });
|
|
nitro.options.devHandlers.push(...nuxt.options.devServerHandlers);
|
|
nitro.options.handlers.unshift({
|
|
route: "/__nuxt_error",
|
|
lazy: true,
|
|
handler: resolve(distDir, "core/runtime/nitro/renderer")
|
|
});
|
|
nuxt.hook("prepare:types", async (opts) => {
|
|
if (!nuxt.options.dev) {
|
|
await scanHandlers(nitro);
|
|
await writeTypes(nitro);
|
|
}
|
|
opts.references.push({ path: resolve(nuxt.options.buildDir, "types/nitro.d.ts") });
|
|
});
|
|
nuxt.hook("build:done", async () => {
|
|
await nuxt.callHook("nitro:build:before", nitro);
|
|
if (nuxt.options.dev) {
|
|
await build$1(nitro);
|
|
} else {
|
|
await prepare(nitro);
|
|
await copyPublicAssets(nitro);
|
|
await prerender(nitro);
|
|
if (!nuxt.options._generate) {
|
|
await build$1(nitro);
|
|
} else {
|
|
const distDir2 = resolve(nuxt.options.rootDir, "dist");
|
|
if (!existsSync(distDir2)) {
|
|
await promises.symlink(nitro.options.output.publicDir, distDir2, "junction").catch(() => {
|
|
});
|
|
}
|
|
}
|
|
}
|
|
});
|
|
if (nuxt.options.dev) {
|
|
nuxt.hook("webpack:compile", ({ compiler }) => {
|
|
compiler.outputFileSystem = { ...fse, join };
|
|
});
|
|
nuxt.hook("webpack:compiled", () => {
|
|
nuxt.server.reload();
|
|
});
|
|
nuxt.hook("vite:compiled", () => {
|
|
nuxt.server.reload();
|
|
});
|
|
nuxt.hook("server:devHandler", (h) => {
|
|
devMiddlewareHandler.set(h);
|
|
});
|
|
nuxt.server = createDevServer(nitro);
|
|
const waitUntilCompile = new Promise((resolve2) => nitro.hooks.hook("compiled", () => resolve2()));
|
|
nuxt.hook("build:done", () => waitUntilCompile);
|
|
}
|
|
}
|
|
|
|
function createNuxt(options) {
|
|
const hooks = createHooks();
|
|
const nuxt = {
|
|
_version: version,
|
|
options,
|
|
hooks,
|
|
callHook: hooks.callHook,
|
|
addHooks: hooks.addHooks,
|
|
hook: hooks.hook,
|
|
ready: () => initNuxt(nuxt),
|
|
close: () => Promise.resolve(hooks.callHook("close", nuxt)),
|
|
vfs: {}
|
|
};
|
|
return nuxt;
|
|
}
|
|
async function initNuxt(nuxt) {
|
|
nuxt.hooks.addHooks(nuxt.options.hooks);
|
|
nuxtCtx.set(nuxt);
|
|
nuxt.hook("close", () => nuxtCtx.unset());
|
|
nuxt.hook("prepare:types", (opts) => {
|
|
opts.references.push({ types: "nuxt" });
|
|
opts.references.push({ path: resolve(nuxt.options.buildDir, "types/plugins.d.ts") });
|
|
if (nuxt.options.typescript.shim) {
|
|
opts.references.push({ path: resolve(nuxt.options.buildDir, "types/vue-shim.d.ts") });
|
|
}
|
|
opts.references.push({ path: resolve(nuxt.options.buildDir, "types/schema.d.ts") });
|
|
opts.references.push({ path: resolve(nuxt.options.buildDir, "types/app.config.d.ts") });
|
|
for (const layer of nuxt.options._layers) {
|
|
const declaration = join(layer.cwd, "index.d.ts");
|
|
if (fse.existsSync(declaration)) {
|
|
opts.references.push({ path: declaration });
|
|
}
|
|
}
|
|
});
|
|
const config = {
|
|
rootDir: nuxt.options.rootDir,
|
|
exclude: [join(nuxt.options.rootDir, "index.html")],
|
|
patterns: vueAppPatterns(nuxt)
|
|
};
|
|
addVitePlugin(ImportProtectionPlugin.vite(config));
|
|
addWebpackPlugin(ImportProtectionPlugin.webpack(config));
|
|
addVitePlugin(UnctxTransformPlugin(nuxt).vite({ sourcemap: nuxt.options.sourcemap.server || nuxt.options.sourcemap.client }));
|
|
addWebpackPlugin(UnctxTransformPlugin(nuxt).webpack({ sourcemap: nuxt.options.sourcemap.server || nuxt.options.sourcemap.client }));
|
|
if (!nuxt.options.dev) {
|
|
const removeFromServer = ["onBeforeMount", "onMounted", "onBeforeUpdate", "onRenderTracked", "onRenderTriggered", "onActivated", "onDeactivated", "onBeforeUnmount"];
|
|
const removeFromClient = ["onServerPrefetch", "onRenderTracked", "onRenderTriggered"];
|
|
addVitePlugin(TreeShakePlugin.vite({ sourcemap: nuxt.options.sourcemap.server, treeShake: removeFromServer }), { client: false });
|
|
addVitePlugin(TreeShakePlugin.vite({ sourcemap: nuxt.options.sourcemap.client, treeShake: removeFromClient }), { server: false });
|
|
addWebpackPlugin(TreeShakePlugin.webpack({ sourcemap: nuxt.options.sourcemap.server, treeShake: removeFromServer }), { client: false });
|
|
addWebpackPlugin(TreeShakePlugin.webpack({ sourcemap: nuxt.options.sourcemap.client, treeShake: removeFromClient }), { server: false });
|
|
addVitePlugin(DevOnlyPlugin.vite({ sourcemap: nuxt.options.sourcemap.server || nuxt.options.sourcemap.client }));
|
|
addWebpackPlugin(DevOnlyPlugin.webpack({ sourcemap: nuxt.options.sourcemap.server || nuxt.options.sourcemap.client }));
|
|
}
|
|
if (nuxt.options.experimental.noScripts && !nuxt.options.dev) {
|
|
nuxt.hook("build:manifest", async (manifest) => {
|
|
for (const file in manifest) {
|
|
if (manifest[file].resourceType === "script") {
|
|
await fse.rm(resolve(nuxt.options.buildDir, "dist/client", withoutLeadingSlash(nuxt.options.app.buildAssetsDir), manifest[file].file), { force: true });
|
|
manifest[file].file = "";
|
|
}
|
|
}
|
|
});
|
|
}
|
|
nuxt.options.build.transpile.push(
|
|
...nuxt.options._layers.filter((i) => i.cwd.includes("node_modules")).map((i) => i.cwd)
|
|
);
|
|
await nuxt.callHook("modules:before");
|
|
const modulesToInstall = [
|
|
...nuxt.options.modules,
|
|
...nuxt.options._modules
|
|
];
|
|
addComponent({
|
|
name: "NuxtWelcome",
|
|
filePath: tryResolveModule("@nuxt/ui-templates/templates/welcome.vue")
|
|
});
|
|
addComponent({
|
|
name: "NuxtLayout",
|
|
filePath: resolve(nuxt.options.appDir, "components/layout")
|
|
});
|
|
addComponent({
|
|
name: "NuxtErrorBoundary",
|
|
filePath: resolve(nuxt.options.appDir, "components/nuxt-error-boundary")
|
|
});
|
|
addComponent({
|
|
name: "ClientOnly",
|
|
filePath: resolve(nuxt.options.appDir, "components/client-only")
|
|
});
|
|
addComponent({
|
|
name: "DevOnly",
|
|
filePath: resolve(nuxt.options.appDir, "components/dev-only")
|
|
});
|
|
addComponent({
|
|
name: "ServerPlaceholder",
|
|
filePath: resolve(nuxt.options.appDir, "components/server-placeholder")
|
|
});
|
|
addComponent({
|
|
name: "NuxtLink",
|
|
filePath: resolve(nuxt.options.appDir, "components/nuxt-link")
|
|
});
|
|
addComponent({
|
|
name: "NuxtLoadingIndicator",
|
|
filePath: resolve(nuxt.options.appDir, "components/nuxt-loading-indicator")
|
|
});
|
|
if (!nuxt.options.dev && nuxt.options.experimental.payloadExtraction) {
|
|
addPlugin(resolve(nuxt.options.appDir, "plugins/payload.client"));
|
|
}
|
|
if (nuxt.options.experimental.crossOriginPrefetch) {
|
|
addPlugin(resolve(nuxt.options.appDir, "plugins/cross-origin-prefetch.client"));
|
|
}
|
|
if (nuxt.options.builder === "@nuxt/webpack-builder") {
|
|
addPlugin(resolve(nuxt.options.appDir, "plugins/preload.server"));
|
|
}
|
|
if (nuxt.options.debug) {
|
|
addPlugin(resolve(nuxt.options.appDir, "plugins/debug"));
|
|
}
|
|
for (const m of modulesToInstall) {
|
|
if (Array.isArray(m)) {
|
|
await installModule(m[0], m[1]);
|
|
} else {
|
|
await installModule(m, {});
|
|
}
|
|
}
|
|
await nuxt.callHook("modules:done");
|
|
nuxt.options.build.transpile = nuxt.options.build.transpile.map((t) => typeof t === "string" ? normalize(t) : t);
|
|
addModuleTranspiles();
|
|
await initNitro(nuxt);
|
|
await nuxt.callHook("ready", nuxt);
|
|
}
|
|
async function loadNuxt(opts) {
|
|
const options = await loadNuxtConfig(opts);
|
|
options.appDir = options.alias["#app"] = resolve(distDir, "app");
|
|
options._majorVersion = 3;
|
|
options._modules.push(pagesModule, metaModule, componentsModule);
|
|
options._modules.push([importsModule, {
|
|
transform: {
|
|
include: options._layers.filter((i) => i.cwd && i.cwd.includes("node_modules")).map((i) => new RegExp(`(^|\\/)${escapeRE(i.cwd.split("node_modules/").pop())}(\\/|$)(?!node_modules\\/)`))
|
|
}
|
|
}]);
|
|
options.modulesDir.push(resolve(options.workspaceDir, "node_modules"));
|
|
options.modulesDir.push(resolve(pkgDir, "node_modules"));
|
|
options.build.transpile.push("@nuxt/ui-templates");
|
|
options.alias["vue-demi"] = resolve(options.appDir, "compat/vue-demi");
|
|
options.alias["@vue/composition-api"] = resolve(options.appDir, "compat/capi");
|
|
if (options.telemetry !== false && !process.env.NUXT_TELEMETRY_DISABLED) {
|
|
options._modules.push("@nuxt/telemetry");
|
|
}
|
|
const nuxt = createNuxt(options);
|
|
if (nuxt.options.debug) {
|
|
createDebugger(nuxt.hooks, { tag: "nuxt" });
|
|
}
|
|
if (opts.ready !== false) {
|
|
await nuxt.ready();
|
|
}
|
|
return nuxt;
|
|
}
|
|
|
|
const vueShim = {
|
|
filename: "types/vue-shim.d.ts",
|
|
getContents: () => [
|
|
"declare module '*.vue' {",
|
|
" import { DefineComponent } from '@vue/runtime-core'",
|
|
" const component: DefineComponent<{}, {}, any>",
|
|
" export default component",
|
|
"}"
|
|
].join("\n")
|
|
};
|
|
const appComponentTemplate = {
|
|
filename: "app-component.mjs",
|
|
getContents: (ctx) => genExport(ctx.app.mainComponent, ["default"])
|
|
};
|
|
const rootComponentTemplate = {
|
|
filename: "root-component.mjs",
|
|
getContents: (ctx) => genExport(ctx.app.rootComponent, ["default"])
|
|
};
|
|
const errorComponentTemplate = {
|
|
filename: "error-component.mjs",
|
|
getContents: (ctx) => genExport(ctx.app.errorComponent, ["default"])
|
|
};
|
|
const cssTemplate = {
|
|
filename: "css.mjs",
|
|
getContents: (ctx) => ctx.nuxt.options.css.map((i) => genImport(i)).join("\n")
|
|
};
|
|
const clientPluginTemplate = {
|
|
filename: "plugins/client.mjs",
|
|
getContents(ctx) {
|
|
const clientPlugins = ctx.app.plugins.filter((p) => !p.mode || p.mode !== "server");
|
|
const exports = [];
|
|
const imports = [];
|
|
for (const plugin of clientPlugins) {
|
|
const path = relative(ctx.nuxt.options.rootDir, plugin.src);
|
|
const variable = genSafeVariableName(path).replace(/_(45|46|47)/g, "_") + "_" + hash(path);
|
|
exports.push(variable);
|
|
imports.push(genImport(plugin.src, variable));
|
|
}
|
|
return [
|
|
...imports,
|
|
`export default ${genArrayFromRaw(exports)}`
|
|
].join("\n");
|
|
}
|
|
};
|
|
const serverPluginTemplate = {
|
|
filename: "plugins/server.mjs",
|
|
getContents(ctx) {
|
|
const serverPlugins = ctx.app.plugins.filter((p) => !p.mode || p.mode !== "client");
|
|
const exports = [];
|
|
const imports = [];
|
|
for (const plugin of serverPlugins) {
|
|
const path = relative(ctx.nuxt.options.rootDir, plugin.src);
|
|
const variable = genSafeVariableName(path).replace(/_(45|46|47)/g, "_") + "_" + hash(path);
|
|
exports.push(variable);
|
|
imports.push(genImport(plugin.src, variable));
|
|
}
|
|
return [
|
|
...imports,
|
|
`export default ${genArrayFromRaw(exports)}`
|
|
].join("\n");
|
|
}
|
|
};
|
|
const pluginsDeclaration = {
|
|
filename: "types/plugins.d.ts",
|
|
getContents: (ctx) => {
|
|
const EXTENSION_RE = new RegExp(`(?<=\\w)(${ctx.nuxt.options.extensions.map((e) => escapeRE(e)).join("|")})$`, "g");
|
|
const tsImports = ctx.app.plugins.map((p) => (isAbsolute(p.src) ? relative(join(ctx.nuxt.options.buildDir, "types"), p.src) : p.src).replace(EXTENSION_RE, ""));
|
|
return `// Generated by Nuxt'
|
|
import type { Plugin } from '#app'
|
|
|
|
type Decorate<T extends Record<string, any>> = { [K in keyof T as K extends string ? \`$\${K}\` : never]: T[K] }
|
|
|
|
type InjectionType<A extends Plugin> = A extends Plugin<infer T> ? Decorate<T> : unknown
|
|
|
|
type NuxtAppInjections =
|
|
${tsImports.map((p) => `InjectionType<typeof ${genDynamicImport(p, { wrapper: false })}.default>`).join(" &\n ")}
|
|
|
|
declare module '#app' {
|
|
interface NuxtApp extends NuxtAppInjections { }
|
|
}
|
|
|
|
declare module '@vue/runtime-core' {
|
|
interface ComponentCustomProperties extends NuxtAppInjections { }
|
|
}
|
|
|
|
export { }
|
|
`;
|
|
}
|
|
};
|
|
const adHocModules = ["router", "pages", "imports", "meta", "components"];
|
|
const schemaTemplate = {
|
|
filename: "types/schema.d.ts",
|
|
getContents: async ({ nuxt }) => {
|
|
const moduleInfo = nuxt.options._installedModules.map((m) => ({
|
|
...m.meta || {},
|
|
importName: m.entryPath || m.meta?.name
|
|
})).filter((m) => m.configKey && m.name && !adHocModules.includes(m.name));
|
|
const relativeRoot = relative(resolve(nuxt.options.buildDir, "types"), nuxt.options.rootDir);
|
|
return [
|
|
"import { NuxtModule } from '@nuxt/schema'",
|
|
"declare module '@nuxt/schema' {",
|
|
" interface NuxtConfig {",
|
|
...moduleInfo.filter(Boolean).map(
|
|
(meta) => ` [${genString(meta.configKey)}]?: typeof ${genDynamicImport(meta.importName.startsWith(".") ? "./" + join(relativeRoot, meta.importName) : meta.importName, { wrapper: false })}.default extends NuxtModule<infer O> ? Partial<O> : Record<string, any>`
|
|
),
|
|
" }",
|
|
generateTypes(
|
|
await resolveSchema(Object.fromEntries(Object.entries(nuxt.options.runtimeConfig).filter(([key]) => key !== "public"))),
|
|
{
|
|
interfaceName: "RuntimeConfig",
|
|
addExport: false,
|
|
addDefaults: false,
|
|
allowExtraKeys: false,
|
|
indentation: 2
|
|
}
|
|
),
|
|
generateTypes(
|
|
await resolveSchema(nuxt.options.runtimeConfig.public),
|
|
{
|
|
interfaceName: "PublicRuntimeConfig",
|
|
addExport: false,
|
|
addDefaults: false,
|
|
allowExtraKeys: false,
|
|
indentation: 2
|
|
}
|
|
),
|
|
"}"
|
|
].join("\n");
|
|
}
|
|
};
|
|
const layoutTemplate = {
|
|
filename: "layouts.mjs",
|
|
getContents({ app }) {
|
|
const layoutsObject = genObjectFromRawEntries(Object.values(app.layouts).map(({ name, file }) => {
|
|
return [name, genDynamicImport(file, { interopDefault: true })];
|
|
}));
|
|
return [
|
|
`export default ${layoutsObject}`
|
|
].join("\n");
|
|
}
|
|
};
|
|
const middlewareTemplate = {
|
|
filename: "middleware.mjs",
|
|
getContents({ app }) {
|
|
const globalMiddleware = app.middleware.filter((mw) => mw.global);
|
|
const namedMiddleware = app.middleware.filter((mw) => !mw.global);
|
|
const namedMiddlewareObject = genObjectFromRawEntries(namedMiddleware.map((mw) => [mw.name, genDynamicImport(mw.path)]));
|
|
return [
|
|
...globalMiddleware.map((mw) => genImport(mw.path, genSafeVariableName(mw.name))),
|
|
`export const globalMiddleware = ${genArrayFromRaw(globalMiddleware.map((mw) => genSafeVariableName(mw.name)))}`,
|
|
`export const namedMiddleware = ${namedMiddlewareObject}`
|
|
].join("\n");
|
|
}
|
|
};
|
|
const clientConfigTemplate = {
|
|
filename: "nitro.client.mjs",
|
|
getContents: () => `
|
|
export const useRuntimeConfig = () => window?.__NUXT__?.config || {}
|
|
`
|
|
};
|
|
const appConfigDeclarationTemplate = {
|
|
filename: "types/app.config.d.ts",
|
|
getContents: ({ app, nuxt }) => {
|
|
return `
|
|
import type { Defu } from 'defu'
|
|
${app.configs.map((id, index) => `import ${`cfg${index}`} from ${JSON.stringify(id.replace(/(?<=\w)\.\w+$/g, ""))}`).join("\n")}
|
|
|
|
declare const inlineConfig = ${JSON.stringify(nuxt.options.appConfig, null, 2)}
|
|
type ResolvedAppConfig = Defu<typeof inlineConfig, [${app.configs.map((_id, index) => `typeof cfg${index}`).join(", ")}]>
|
|
|
|
declare module '@nuxt/schema' {
|
|
interface AppConfig extends ResolvedAppConfig { }
|
|
}
|
|
`;
|
|
}
|
|
};
|
|
const appConfigTemplate = {
|
|
filename: "app.config.mjs",
|
|
write: true,
|
|
getContents: async ({ app, nuxt }) => {
|
|
return `
|
|
import { defuFn } from '${await _resolveId("defu")}'
|
|
|
|
const inlineConfig = ${JSON.stringify(nuxt.options.appConfig, null, 2)}
|
|
|
|
${app.configs.map((id, index) => `import ${`cfg${index}`} from ${JSON.stringify(id)}`).join("\n")}
|
|
|
|
export default defuFn(${app.configs.map((_id, index) => `cfg${index}`).concat(["inlineConfig"]).join(", ")})
|
|
`;
|
|
}
|
|
};
|
|
const publicPathTemplate = {
|
|
filename: "paths.mjs",
|
|
async getContents({ nuxt }) {
|
|
return [
|
|
`import { joinURL } from '${await _resolveId("ufo")}'`,
|
|
!nuxt.options.dev && "import { useRuntimeConfig } from '#internal/nitro'",
|
|
nuxt.options.dev ? `const appConfig = ${JSON.stringify(nuxt.options.app)}` : "const appConfig = useRuntimeConfig().app",
|
|
"export const baseURL = () => appConfig.baseURL",
|
|
"export const buildAssetsDir = () => appConfig.buildAssetsDir",
|
|
"export const buildAssetsURL = (...path) => joinURL(publicAssetsURL(), buildAssetsDir(), ...path)",
|
|
"export const publicAssetsURL = (...path) => {",
|
|
" const publicBase = appConfig.cdnURL || appConfig.baseURL",
|
|
" return path.length ? joinURL(publicBase, ...path) : publicBase",
|
|
"}",
|
|
"if (process.client) {",
|
|
" globalThis.__buildAssetsURL = buildAssetsURL",
|
|
" globalThis.__publicAssetsURL = publicAssetsURL",
|
|
"}"
|
|
].filter(Boolean).join("\n");
|
|
}
|
|
};
|
|
const nuxtConfigTemplate = {
|
|
filename: "nuxt.config.mjs",
|
|
getContents: (ctx) => {
|
|
return Object.entries(ctx.nuxt.options.app).map(([k, v]) => `export const ${camelCase("app-" + k)} = ${JSON.stringify(v)}`).join("\n\n");
|
|
}
|
|
};
|
|
function _resolveId(id) {
|
|
return resolvePath$1(id, {
|
|
url: [
|
|
global.__NUXT_PREPATHS__,
|
|
import.meta.url,
|
|
process.cwd(),
|
|
global.__NUXT_PATHS__
|
|
]
|
|
});
|
|
}
|
|
|
|
const defaultTemplates = {
|
|
__proto__: null,
|
|
vueShim: vueShim,
|
|
appComponentTemplate: appComponentTemplate,
|
|
rootComponentTemplate: rootComponentTemplate,
|
|
errorComponentTemplate: errorComponentTemplate,
|
|
cssTemplate: cssTemplate,
|
|
clientPluginTemplate: clientPluginTemplate,
|
|
serverPluginTemplate: serverPluginTemplate,
|
|
pluginsDeclaration: pluginsDeclaration,
|
|
schemaTemplate: schemaTemplate,
|
|
layoutTemplate: layoutTemplate,
|
|
middlewareTemplate: middlewareTemplate,
|
|
clientConfigTemplate: clientConfigTemplate,
|
|
appConfigDeclarationTemplate: appConfigDeclarationTemplate,
|
|
appConfigTemplate: appConfigTemplate,
|
|
publicPathTemplate: publicPathTemplate,
|
|
nuxtConfigTemplate: nuxtConfigTemplate
|
|
};
|
|
|
|
function createApp(nuxt, options = {}) {
|
|
return defu(options, {
|
|
dir: nuxt.options.srcDir,
|
|
extensions: nuxt.options.extensions,
|
|
plugins: [],
|
|
templates: []
|
|
});
|
|
}
|
|
async function generateApp(nuxt, app, options = {}) {
|
|
await resolveApp(nuxt, app);
|
|
app.templates = Object.values(defaultTemplates).concat(nuxt.options.build.templates);
|
|
await nuxt.callHook("app:templates", app);
|
|
app.templates = app.templates.map((tmpl) => normalizeTemplate(tmpl));
|
|
const templateContext = { utils: templateUtils, nuxt, app };
|
|
await Promise.all(app.templates.filter((template) => !options.filter || options.filter(template)).map(async (template) => {
|
|
const contents = await compileTemplate(template, templateContext);
|
|
const fullPath = template.dst || resolve(nuxt.options.buildDir, template.filename);
|
|
nuxt.vfs[fullPath] = contents;
|
|
const aliasPath = "#build/" + template.filename.replace(/\.\w+$/, "");
|
|
nuxt.vfs[aliasPath] = contents;
|
|
if (process.platform === "win32") {
|
|
nuxt.vfs[fullPath.replace(/\//g, "\\")] = contents;
|
|
}
|
|
if (template.write) {
|
|
await promises.mkdir(dirname(fullPath), { recursive: true });
|
|
await promises.writeFile(fullPath, contents, "utf8");
|
|
}
|
|
}));
|
|
await nuxt.callHook("app:templatesGenerated", app);
|
|
}
|
|
async function resolveApp(nuxt, app) {
|
|
if (!app.mainComponent) {
|
|
app.mainComponent = await findPath(
|
|
nuxt.options._layers.flatMap((layer) => [`${layer.config.srcDir}/App`, `${layer.config.srcDir}/app`])
|
|
);
|
|
}
|
|
if (!app.mainComponent) {
|
|
app.mainComponent = tryResolveModule("@nuxt/ui-templates/templates/welcome.vue");
|
|
}
|
|
if (!app.rootComponent) {
|
|
app.rootComponent = await findPath(["~/app.root", resolve(nuxt.options.appDir, "components/nuxt-root.vue")]);
|
|
}
|
|
if (!app.errorComponent) {
|
|
app.errorComponent = await findPath(["~/error"]) || resolve(nuxt.options.appDir, "components/nuxt-error-page.vue");
|
|
}
|
|
app.layouts = {};
|
|
for (const config of nuxt.options._layers.map((layer) => layer.config)) {
|
|
const layoutFiles = await resolveFiles(config.srcDir, `${config.dir?.layouts || "layouts"}/*{${nuxt.options.extensions.join(",")}}`);
|
|
for (const file of layoutFiles) {
|
|
const name = getNameFromPath(file);
|
|
app.layouts[name] = app.layouts[name] || { name, file };
|
|
}
|
|
}
|
|
app.middleware = [];
|
|
for (const config of nuxt.options._layers.map((layer) => layer.config)) {
|
|
const middlewareFiles = await resolveFiles(config.srcDir, `${config.dir?.middleware || "middleware"}/*{${nuxt.options.extensions.join(",")}}`);
|
|
app.middleware.push(...middlewareFiles.map((file) => {
|
|
const name = getNameFromPath(file);
|
|
return { name, path: file, global: hasSuffix(file, ".global") };
|
|
}));
|
|
}
|
|
app.plugins = [
|
|
...nuxt.options.plugins.map(normalizePlugin)
|
|
];
|
|
for (const config of nuxt.options._layers.map((layer) => layer.config)) {
|
|
app.plugins.push(...[
|
|
...config.plugins || [],
|
|
...config.srcDir ? await resolveFiles(config.srcDir, [
|
|
`${config.dir?.plugins || "plugins"}/*.{ts,js,mjs,cjs,mts,cts}`,
|
|
`${config.dir?.plugins || "plugins"}/*/index.*{ts,js,mjs,cjs,mts,cts}`
|
|
]) : []
|
|
].map((plugin) => normalizePlugin(plugin)));
|
|
}
|
|
app.middleware = uniqueBy(await resolvePaths(app.middleware, "path"), "name");
|
|
app.plugins = uniqueBy(await resolvePaths(app.plugins, "src"), "src");
|
|
app.configs = [];
|
|
for (const config of nuxt.options._layers.map((layer) => layer.config)) {
|
|
const appConfigPath = await findPath(resolve(config.srcDir, "app.config"));
|
|
if (appConfigPath) {
|
|
app.configs.push(appConfigPath);
|
|
}
|
|
}
|
|
await nuxt.callHook("app:resolve", app);
|
|
app.middleware = uniqueBy(await resolvePaths(app.middleware, "path"), "name");
|
|
app.plugins = uniqueBy(await resolvePaths(app.plugins, "src"), "src");
|
|
}
|
|
function resolvePaths(items, key) {
|
|
return Promise.all(items.map(async (item) => {
|
|
if (!item[key]) {
|
|
return item;
|
|
}
|
|
return {
|
|
...item,
|
|
[key]: await resolvePath(resolveAlias(item[key]))
|
|
};
|
|
}));
|
|
}
|
|
|
|
async function build(nuxt) {
|
|
const app = createApp(nuxt);
|
|
const generateApp$1 = debounce(() => generateApp(nuxt, app), void 0, { leading: true });
|
|
await generateApp$1();
|
|
if (nuxt.options.dev) {
|
|
watch(nuxt);
|
|
nuxt.hook("builder:watch", async (event, path) => {
|
|
if (event !== "change" && /^(app\.|error\.|plugins\/|middleware\/|layouts\/)/i.test(path)) {
|
|
if (path.startsWith("app")) {
|
|
app.mainComponent = void 0;
|
|
}
|
|
if (path.startsWith("error")) {
|
|
app.errorComponent = void 0;
|
|
}
|
|
await generateApp$1();
|
|
}
|
|
});
|
|
nuxt.hook("builder:generateApp", (options) => {
|
|
if (options) {
|
|
return generateApp(nuxt, app, options);
|
|
}
|
|
return generateApp$1();
|
|
});
|
|
}
|
|
await nuxt.callHook("build:before");
|
|
if (!nuxt.options._prepare) {
|
|
await bundle(nuxt);
|
|
await nuxt.callHook("build:done");
|
|
}
|
|
if (!nuxt.options.dev) {
|
|
await nuxt.callHook("close", nuxt);
|
|
}
|
|
}
|
|
function watch(nuxt) {
|
|
const watcher = chokidar.watch(nuxt.options._layers.map((i) => i.config.srcDir).filter(Boolean), {
|
|
...nuxt.options.watchers.chokidar,
|
|
cwd: nuxt.options.srcDir,
|
|
ignoreInitial: true,
|
|
ignored: [
|
|
isIgnored,
|
|
".nuxt",
|
|
"node_modules"
|
|
]
|
|
});
|
|
watcher.on("all", (event, path) => nuxt.callHook("builder:watch", event, normalize(path)));
|
|
nuxt.hook("close", () => watcher.close());
|
|
return watcher;
|
|
}
|
|
async function bundle(nuxt) {
|
|
try {
|
|
const { bundle: bundle2 } = typeof nuxt.options.builder === "string" ? await importModule(nuxt.options.builder, { paths: nuxt.options.rootDir }) : nuxt.options.builder;
|
|
return bundle2(nuxt);
|
|
} catch (error) {
|
|
await nuxt.callHook("build:error", error);
|
|
if (error.toString().includes("Cannot find module '@nuxt/webpack-builder'")) {
|
|
throw new Error([
|
|
"Could not load `@nuxt/webpack-builder`. You may need to add it to your project dependencies, following the steps in `https://github.com/nuxt/framework/pull/2812`."
|
|
].join("\n"));
|
|
}
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
export { build, createNuxt, loadNuxt };
|