@nuxt/module-builder
Version:
Complete solution for building Nuxt modules
218 lines (215 loc) • 9.49 kB
JavaScript
import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.mjs";
import { n as name, r as version } from "./package-1Cra3U-o.mjs";
import { n as sharedArgs, t as resolveCwdArg } from "./_shared-CfHVT2WD.mjs";
import { defineCommand } from "citty";
import { consola } from "consola";
import { existsSync, promises } from "node:fs";
import { pathToFileURL } from "node:url";
import { basename, dirname, extname, join, normalize, resolve } from "pathe";
import { filename } from "pathe/utils";
import { readPackageJSON } from "pkg-types";
import { parse } from "tsconfck";
import { defu } from "defu";
import { createJiti } from "jiti";
import { anyOf, createRegExp } from "magic-regexp";
import { findExports, findTypeExports, resolvePath } from "mlly";
import { convertCompilerOptionsFromJson } from "typescript";
//#region src/commands/build.ts
var build_exports = /* @__PURE__ */ __exportAll({ default: () => build_default });
var build_default = defineCommand({
meta: {
name: "build",
description: "Build module for distribution"
},
args: {
...sharedArgs,
outDir: {
type: "string",
default: "dist",
description: "Build directory"
},
sourcemap: {
type: "boolean",
default: false,
description: "Generate sourcemaps"
},
stub: {
type: "boolean",
default: false,
description: "Stub dist instead of actually building it for development"
}
},
async run(context) {
const { build } = await import("unbuild");
const cwd = resolveCwdArg(context.args);
const jiti = createJiti(cwd);
await build(cwd, false, {
declaration: "node16",
sourcemap: context.args.sourcemap,
stub: context.args.stub,
stubOptions: { absoluteJitiPath: true },
outDir: context.args.outDir,
entries: ["src/module", {
input: "src/runtime/",
outDir: `${context.args.outDir}/runtime`,
addRelativeDeclarationExtensions: true,
ext: "js",
pattern: [
"**",
"!**/*.stories.{js,cts,mts,ts,jsx,tsx}",
"!**/*.{spec,test}.{js,cts,mts,ts,jsx,tsx}"
],
esbuild: {
jsxImportSource: "vue",
jsx: "automatic",
jsxFactory: "h"
}
}],
rollup: {
esbuild: { target: "esnext" },
emitCJS: false,
cjsBridge: false
},
externals: [
/dist[\\/]runtime[\\/]/,
"@nuxt/schema",
"@nuxt/schema-nightly",
"@nuxt/schema-edge",
"@nuxt/kit",
"@nuxt/kit-nightly",
"@nuxt/kit-edge",
"#app",
"#app/nuxt",
"nuxt",
"nuxt-nightly",
"nuxt-edge",
"nuxt3",
"vue",
"vue-demi"
],
hooks: {
async "mkdist:entry:options"(_ctx, entry, options) {
options.typescript = defu(options.typescript, { compilerOptions: await loadTSCompilerOptions(entry.input) });
},
async "rollup:options"(ctx, options) {
const [entry] = ctx.buildEntries;
const mergedCompilerOptions = defu({
noEmit: false,
paths: { "#app/nuxt": ["./node_modules/nuxt/dist/app/nuxt"] }
}, ctx.options.rollup.dts.compilerOptions, await loadTSCompilerOptions(entry.path));
ctx.options.rollup.dts.compilerOptions = convertCompilerOptionsFromJson(mergedCompilerOptions, entry.path).options;
options.plugins ||= [];
if (!Array.isArray(options.plugins)) options.plugins = [options.plugins];
const runtimeEntries = ctx.options.entries.filter((entry) => entry.builder === "mkdist");
const RUNTIME_RE = createRegExp(anyOf(...runtimeEntries.map((entry) => basename(entry.input))).and(anyOf("/", "\\")));
options.plugins.unshift({
name: "nuxt-module-builder:runtime-externals",
async resolveId(id, importer) {
if (!RUNTIME_RE.test(id)) return;
const resolved = await this.resolve(id, importer, { skipSelf: true });
if (!resolved) return;
const normalizedId = normalize(resolved.id);
for (const entry of runtimeEntries) {
if (!entry.outDir || !normalizedId.includes(entry.input)) continue;
const name = filename(normalizedId) || basename(normalizedId, extname(normalizedId));
const distFile = await resolvePath(join(dirname(pathToFileURL(normalizedId).href.replace(entry.input, entry.outDir)), name));
if (distFile) return {
external: true,
id: distFile
};
}
}
});
},
async "rollup:done"(ctx) {
const moduleEntryPath = resolve(ctx.options.outDir, "module.mjs");
const moduleFn = await jiti.import(pathToFileURL(moduleEntryPath).toString(), { default: true }).catch((err) => {
consola.error(err);
consola.error("Cannot load module. Please check dist:", moduleEntryPath);
return null;
});
if (!moduleFn) return;
const moduleMeta = await moduleFn.getMeta?.() || {};
if (ctx.pkg) {
if (!moduleMeta.name) moduleMeta.name = ctx.pkg.name;
if (!moduleMeta.version) moduleMeta.version = ctx.pkg.version;
}
moduleMeta.builder = {
[name]: version,
unbuild: await readPackageJSON("unbuild").then((r) => r.version).catch(() => "unknown")
};
const metaFile = resolve(ctx.options.outDir, "module.json");
await promises.writeFile(metaFile, JSON.stringify(moduleMeta, null, 2), "utf8");
await writeTypes(ctx.options.outDir, ctx.options.stub);
},
async "build:done"(ctx) {
const logs = [...ctx.warnings].filter((l) => l.startsWith("Potential missing package.json files:"));
if (logs.filter((l) => l.match(/\.d\.ts/)).length > 0) consola.warn(`\`@nuxt/module-builder\` will no longer generate \`.d.ts\` declaration files. You can update these paths to use the \`.d.mts\` extension instead.`);
if (logs.filter((l) => l.match(/module\.cjs/)).length > 0) consola.warn(`\`@nuxt/module-builder\` will no longer generate \`module.cjs\` as this is not required for Nuxt v3+. You can safely remove replace this with \`module.mjs\` in your \`package.json\`.`);
const pkg = await readPackageJSON(cwd);
if (pkg?.types && !existsSync(resolve(cwd, pkg.types))) consola.warn(`Please remove the \`types\` field from package.json as it is no longer required for Bundler TypeScript module resolution. Instead, you can use \`typesVersions\` to support subpath export types for Node10, if required.`);
}
}
});
}
});
async function writeTypes(distDir, isStub) {
const dtsFile = resolve(distDir, "types.d.mts");
if (existsSync(dtsFile)) return;
const moduleReExports = [];
if (!isStub) {
const moduleTypesFile = resolve(distDir, "module.d.mts");
const normalisedModuleTypes = (await promises.readFile(moduleTypesFile, "utf8").catch(() => "")).replace(/export\s*\{.*?\}/gs, (match) => match.replace(/\b(type|interface)\b/g, ""));
for (const e of findExports(normalisedModuleTypes)) moduleReExports.push(e);
for (const i of findTypeExports(normalisedModuleTypes)) moduleReExports.push(i);
}
const appShims = [];
const schemaShims = [];
const moduleImports = [];
const schemaImports = [];
const moduleExports = [];
const hasTypeExport = (name) => isStub || moduleReExports.find((exp) => exp.names?.includes(name));
if (!hasTypeExport("ModuleOptions")) {
schemaImports.push("NuxtModule");
moduleImports.push("default as Module");
moduleExports.push(`export type ModuleOptions = typeof Module extends NuxtModule<infer O> ? Partial<O> : Record<string, any>`);
}
if (hasTypeExport("ModuleHooks")) {
moduleImports.push("ModuleHooks");
schemaShims.push(" interface NuxtHooks extends ModuleHooks {}");
}
if (hasTypeExport("ModuleRuntimeHooks")) {
moduleImports.push("ModuleRuntimeHooks");
appShims.push(` interface RuntimeNuxtHooks extends ModuleRuntimeHooks {}`);
}
if (hasTypeExport("ModuleRuntimeConfig")) {
moduleImports.push("ModuleRuntimeConfig");
schemaShims.push(" interface RuntimeConfig extends ModuleRuntimeConfig {}");
}
if (hasTypeExport("ModulePublicRuntimeConfig")) {
moduleImports.push("ModulePublicRuntimeConfig");
schemaShims.push(" interface PublicRuntimeConfig extends ModulePublicRuntimeConfig {}");
}
const dtsContents = `
${schemaImports.length ? `import type { ${schemaImports.join(", ")} } from '@nuxt/schema'` : ""}
${moduleImports.length ? `import type { ${moduleImports.join(", ")} } from './module.mjs'` : ""}
${appShims.length ? `declare module '#app' {\n${appShims.join("\n")}\n}\n` : ""}
${schemaShims.length ? `declare module '@nuxt/schema' {\n${schemaShims.join("\n")}\n}\n` : ""}
${moduleExports.length ? `\n${moduleExports.join("\n")}` : ""}
${isStub ? "export * from \"./module.mjs\"" : ""}
${moduleReExports.filter((e) => e.type === "named" || e.type === "default").map((e) => `\nexport { ${e.names.map((n) => (n === "default" ? "" : "type ") + n).join(", ")} } from '${e.specifier || "./module.mjs"}'`).join("\n")}
${moduleReExports.filter((e) => e.type === "star").map((e) => `\nexport * from '${e.specifier || "./module.mjs"}'`).join("\n")}
`.trim().replace(/[\n\r]{3,}/g, "\n\n") + "\n";
await promises.writeFile(dtsFile, dtsContents, "utf8");
}
async function loadTSCompilerOptions(path) {
const config = await parse(path);
const resolvedCompilerOptions = config?.tsconfig.compilerOptions || {};
for (const { tsconfig, tsconfigFile } of config.extended || []) for (const alias in tsconfig.compilerOptions?.paths || {}) resolvedCompilerOptions.paths[alias] = resolvedCompilerOptions.paths[alias].map((p) => {
if (!/^\.{1,2}(?:\/|$)/.test(p)) return p;
return resolve(dirname(tsconfigFile), p);
});
return resolvedCompilerOptions;
}
//#endregion
export { build_exports as n, build_default as t };