UNPKG

@nuxt/module-builder

Version:

Complete solution for building Nuxt modules

246 lines (241 loc) 8.65 kB
import { promises, existsSync } from 'node:fs'; import { pathToFileURL } from 'node:url'; import { resolve, basename, join, dirname } from 'pathe'; import { filename } from 'pathe/utils'; import { readPackageJSON } from 'pkg-types'; import { parse } from 'tsconfck'; import { defu } from 'defu'; import { createRegExp, anyOf } from 'magic-regexp'; import { consola } from 'consola'; import { resolvePath, findExports } from 'mlly'; import { defineCommand } from 'citty'; import { n as name, v as version } from '../shared/module-builder.617a9be8.mjs'; const build = defineCommand({ meta: { name: "build", description: "Build module for distribution" }, args: { cwd: { type: "string", description: "Current working directory" }, rootDir: { type: "positional", description: "Root directory", required: false }, outDir: { type: "string" }, sourcemap: { type: "boolean" }, stub: { type: "boolean" } }, async run(context) { const { build } = await import('unbuild'); const cwd = resolve(context.args.cwd || context.args.rootDir || "."); const outDir = context.args.outDir || "dist"; await build(cwd, false, { declaration: true, sourcemap: context.args.sourcemap, stub: context.args.stub, outDir, entries: [ "src/module", { input: "src/runtime/", outDir: `${outDir}/runtime`, addRelativeDeclarationExtensions: true, ext: "js", esbuild: { jsxImportSource: "vue", jsx: "automatic", jsxFactory: "h" } } ], rollup: { esbuild: { target: "esnext" }, emitCJS: false, cjsBridge: true }, externals: [ /dist\/runtime\//, "@nuxt/schema", "@nuxt/schema-nightly", "@nuxt/schema-edge", "@nuxt/kit", "@nuxt/kit-nightly", "@nuxt/kit-edge", "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) { options.plugins || (options.plugins = []); if (!Array.isArray(options.plugins)) options.plugins = [options.plugins]; const runtimeEntries = ctx.options.entries.filter((entry) => entry.builder === "mkdist"); const runtimeDirs = runtimeEntries.map((entry) => basename(entry.input)); const RUNTIME_RE = createRegExp(anyOf(...runtimeDirs).and("/")); 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; for (const entry of runtimeEntries) { if (!resolved.id.includes(entry.input)) continue; const distFile = await resolvePath(join(dirname(resolved.id.replace(entry.input, entry.outDir)), filename(resolved.id))); if (distFile) { return { external: true, id: distFile }; } } } }); }, async "rollup:done"(ctx) { await writeCJSStub(ctx.options.outDir); const moduleEntryPath = resolve(ctx.options.outDir, "module.mjs"); const moduleFn = await import(pathToFileURL(moduleEntryPath).toString()).then((r) => r.default || r).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 function writeTypes(distDir, isStub) { const dtsFile = resolve(distDir, "types.d.ts"); const dtsFileMts = resolve(distDir, "types.d.mts"); if (existsSync(dtsFile)) { return; } const moduleReExports = []; if (!isStub) { const moduleTypesFile = resolve(distDir, "module.d.ts"); const moduleTypes = await promises.readFile(moduleTypesFile, "utf8").catch(() => ""); const normalisedModuleTypes = moduleTypes.replace(/export\s*\{.*?\}/gs, (match) => match.replace(/\b(type|interface)\b/g, "")); for (const e of findExports(normalisedModuleTypes)) { moduleReExports.push(e); } } const appShims = []; const schemaShims = []; const moduleImports = []; const schemaImports = []; const moduleExports = []; const hasTypeExport = (name2) => isStub || moduleReExports.find((exp) => exp.names?.includes(name2)); 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'` : ""} ${appShims.length ? `declare module '#app' { ${appShims.join("\n")} } ` : ""} ${schemaShims.length ? `declare module '@nuxt/schema' { ${schemaShims.join("\n")} } ` : ""} ${moduleExports.length ? ` ${moduleExports.join("\n")}` : ""} ${isStub ? 'export * from "./module"' : ""} ${moduleReExports[0] ? ` export { ${moduleReExports[0].names.map((n) => (n === "default" ? "" : "type ") + n).join(", ")} } from './module'` : ""} `.trim().replace(/[\n\r]{3,}/g, "\n\n") + "\n"; await promises.writeFile(dtsFile, dtsContents, "utf8"); if (!existsSync(dtsFileMts)) { await promises.writeFile(dtsFileMts, dtsContents.replace(/\.\/module/g, "./module.js"), "utf8"); } } async function writeCJSStub(distDir) { const cjsStubFile = resolve(distDir, "module.cjs"); if (existsSync(cjsStubFile)) { return; } const cjsStub = `module.exports = function(...args) { return import('./module.mjs').then(m => m.default.call(this, ...args)) } const _meta = module.exports.meta = require('./module.json') module.exports.getMeta = () => Promise.resolve(_meta) `; await promises.writeFile(cjsStubFile, cjsStub, "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; } export { build as default };