UNPKG

vite-plugin-dts-build

Version:

Fast .d.ts builds for Vite (worker + incremental) with optional dual ESM/CJS support.

584 lines (583 loc) 20.9 kB
"use strict"; var __defProp = Object.defineProperty; var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value); Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" }); const node_worker_threads = require("node:worker_threads"); const promises = require("node:fs/promises"); const node_path = require("node:path"); const node_url = require("node:url"); const node_process = require("node:process"); const node_perf_hooks = require("node:perf_hooks"); const ts = require("typescript"); const promisePool = require("@supercharge/promise-pool"); const log = require("../log-BqpMRi9a.cjs"); var _documentCurrentScript = typeof document !== "undefined" ? document.currentScript : null; const { ModuleKind, ModuleResolutionKind } = ts; let _dirname, _filename; let _workerName; if (typeof { url: typeof document === "undefined" ? require("url").pathToFileURL(__filename).href : _documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === "SCRIPT" && _documentCurrentScript.src || new URL("cjs/index.cjs", document.baseURI).href } !== "undefined") { _filename = node_url.fileURLToPath(typeof document === "undefined" ? require("url").pathToFileURL(__filename).href : _documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === "SCRIPT" && _documentCurrentScript.src || new URL("cjs/index.cjs", document.baseURI).href); _dirname = node_path.dirname(_filename); _workerName = "worker.js"; } else { _dirname = __dirname; _filename = __filename; _workerName = "worker.cjs"; } function createWorker(options) { return new node_worker_threads.Worker(node_path.join(_dirname, _workerName), { workerData: options }); } function waitBuildInWorker(worker, startTime) { return new Promise((resolve2, reject) => { worker.once("message", (message) => { log.printInfo(`Declaration files built in ${measureTime(startTime)}ms.`); worker.removeAllListeners(); resolve2(message); }); worker.once("error", (err) => { reject(err); }); worker.once("exit", (code) => { reject(new Error(`Worker stopped with exit code ${code}`)); }); }); } function waitCopyInWorker(worker, afterBuild) { return new Promise((resolve2, reject) => { worker?.once("error", (err) => { reject(err); }); worker?.on("exit", async () => { log.printInfo("Copy files completed."); Promise.resolve(afterBuild()).then(() => resolve2()); }); }); } function measureTime(startTime) { const elapsed = node_perf_hooks.performance.now() - startTime; const duration = Math.round(elapsed); return duration; } function noop() { } function dts(options = {}) { const { afterBuild = noop, ...workerOptions } = options; let workerInstance; const runState = { hasStartRun: false, canWriteRun: false }; return { name: "vite-plugin-dts-build", enforce: "pre", apply: "build", async buildStart() { if (runState.hasStartRun) { return; } runState.hasStartRun = true; const startTime = node_perf_hooks.performance.now(); log.printInfo("Starting TypeScript build..."); workerInstance = createWorker(workerOptions); const result = await waitBuildInWorker(workerInstance, startTime); if (result === "build-end") { runState.canWriteRun = true; } }, async writeBundle() { if (!runState.canWriteRun || workerInstance === void 0) { return; } runState.canWriteRun = false; log.printInfo("Starting Copy files..."); workerInstance?.postMessage("copy-start"); await waitCopyInWorker(workerInstance, afterBuild); workerInstance = void 0; }, watchChange(_id, change) { if (change.event === "update") { runState.hasStartRun = false; runState.canWriteRun = false; } } }; } function dtsForEsm(options = {}) { const { packageRedirect = false, cacheDir = node_path.resolve(PackageJson.projectRootPath, ".cache", "typescript-esm"), outDir = node_path.join(PackageJson.projectRootPath, "dist", "esm"), afterBuild, ...restOptions } = options; return dts({ ...restOptions, cacheDir, outDir, compilerOptions: { module: ModuleKind.NodeNext, moduleResolution: ModuleResolutionKind.NodeNext, ...restOptions.compilerOptions ?? {} }, afterBuild: async () => { if (await PackageJson.isCjsProject(PackageJson.projectRootPath)) { await renameDeclarationFiles(outDir, "esm"); } if (packageRedirect) { await generatePackageJsonRedirects("import"); } if (afterBuild) { await Promise.resolve(afterBuild()); } } }); } function dtsForCjs(options = {}) { const { packageRedirect = true, cacheDir = node_path.resolve(PackageJson.projectRootPath, ".cache", "typescript-cjs"), outDir = node_path.join(PackageJson.projectRootPath, "dist", "cjs"), afterBuild, ...restOptions } = options; return dts({ ...restOptions, cacheDir, outDir, compilerOptions: { module: ModuleKind.CommonJS, moduleResolution: ModuleResolutionKind.Node10, ...restOptions.compilerOptions ?? {} }, afterBuild: async () => { if (await PackageJson.isEsmProject(PackageJson.projectRootPath)) { await renameDeclarationFiles(outDir, "cjs"); } if (packageRedirect) { await generatePackageJsonRedirects("require"); } if (afterBuild) { await Promise.resolve(afterBuild()); } } }); } const _PackageJson = class _PackageJson { constructor() { } static get projectRootPath() { if (!_PackageJson.projectRoot) { _PackageJson.projectRoot = node_process.cwd(); } return _PackageJson.projectRoot; } static async getData(rootDir) { if (!_PackageJson.data) { const file = node_path.join(rootDir ?? _PackageJson.projectRootPath, "package.json"); _PackageJson.data = JSON.parse(await promises.readFile(file, "utf8")); } return _PackageJson.data; } static async isEsmProject(rootDir) { const data = await _PackageJson.getData(rootDir); return data.type === "module"; } static async isCjsProject(rootDir) { const data = await _PackageJson.getData(rootDir); return data.type === "commonjs" || data.type === void 0; } }; __publicField(_PackageJson, "projectRoot"); __publicField(_PackageJson, "data"); let PackageJson = _PackageJson; async function renameDeclarationFiles(dir, type) { try { const allFiles = await collectDeclarationFiles(dir); if (allFiles.length === 0) { return; } console.log(`Processing ${allFiles.length} declaration files...`); const { errors } = await promisePool.PromisePool.for(allFiles).withConcurrency(10).process(async (fullPath) => { await processDtsFile(fullPath, type); }); if (errors.length > 0) { console.error(`${errors.length} files failed to process`); } } catch (error) { console.error(`Error processing: ${getErrorMessage(error)}`); } } async function collectDeclarationFiles(dir, fileList = []) { try { const fileOrDirs = await promises.readdir(dir, { withFileTypes: true }); const subDirectories = []; for (const fileOrDir of fileOrDirs) { const fullPath = node_path.join(dir, fileOrDir.name); if (fileOrDir.isDirectory()) { subDirectories.push(fullPath); } else if (fileOrDir.name.endsWith(".d.ts")) { fileList.push(fullPath); } } if (subDirectories.length > 0) { await promisePool.PromisePool.for(subDirectories).withConcurrency(8).process(async (subDir) => { await collectDeclarationFiles(subDir, fileList); }); } return fileList; } catch (error) { console.error(`Error reading directory ${dir}: ${getErrorMessage(error)}`); return fileList; } } function getErrorMessage(error) { if (error instanceof Error) { return error.message; } return String(error); } const IMPORT_REGEX = /import ['"](.+)\.js['"];?$/gm; const IMPORT_FROM_REGEX = /from ['"](.+)\.js['"];?$/gm; const REQUIRE_REGEX = /require\(['"]([^'"\n]+)\.js['"]\)/g; const DYNAMIC_IMPORT_CALL_REGEX = /import\(['"]([^'"\n]+)\.js['"]\)/g; const SOURCE_MAP_LINE_REGEX = /(\/\/# sourceMappingURL=)([^\n]+?)\.d\.ts\.map/; const SOURCE_MAP_BLOCK_REGEX = /(\/\*# sourceMappingURL=)([^*]+?)\.d\.ts\.map(\s*\*\/)?/; async function processDtsFile(fullPath, type) { const jsExt = type === "esm" ? "mjs" : "cjs"; const tsExt = type === "esm" ? "mts" : "cts"; const content = await promises.readFile(fullPath, "utf8"); const modifiedContent = content.replace(IMPORT_REGEX, `import '$1.${jsExt}';`).replace(IMPORT_FROM_REGEX, `from '$1.${jsExt}';`).replace(REQUIRE_REGEX, `require('$1.${jsExt}')`).replace(DYNAMIC_IMPORT_CALL_REGEX, `import('$1.${jsExt}')`); const sourceMapUpdated = modifiedContent.replace(SOURCE_MAP_LINE_REGEX, `$1$2.d.${tsExt}.map`).replace(SOURCE_MAP_BLOCK_REGEX, `$1$2.d.${tsExt}.map$3`); const newPath = fullPath.replace(".d.ts", `.d.${tsExt}`); await promises.writeFile(newPath, sourceMapUpdated, "utf8"); const oldMapPath = `${fullPath}.map`; const newMapPath = `${newPath}.map`; try { const mapRaw = await promises.readFile(oldMapPath, "utf8"); try { const mapJson = JSON.parse(mapRaw); mapJson.file = node_path.basename(newPath); await promises.writeFile(newMapPath, JSON.stringify(mapJson), "utf8"); await promises.unlink(oldMapPath).catch(() => { }); } catch (e) { console.warn(`Failed to update source map ${oldMapPath}: ${getErrorMessage(e)}`); } } catch { } await promises.unlink(fullPath); } async function generatePackageJsonRedirects(prefer = "require") { const rootDir = node_path.resolve(PackageJson.projectRootPath); const pkg = await PackageJson.getData(rootDir); if (pkg.exports != null) { const tasks = await computeRedirectStubs({ pkg, rootDir, prefer }); await writeRedirectStubs(tasks); } } async function computeRedirectStubs({ pkg, rootDir, prefer = "require" }) { const expNormalized = normalizeExports(pkg.exports); if (!expNormalized) return []; const exp = await expandWildCardExports(expNormalized, rootDir); const rootTypes = typeof pkg.types === "string" ? pkg.types : void 0; const packageVersion = typeof pkg.version === "string" ? pkg.version : void 0; const branchOrder = prefer === "import" ? ["import", "node", "default", "require", "browser"] : ["require", "node", "default", "import", "browser"]; const results = []; for (const [key, entry] of Object.entries(exp)) { if (!isStubAbleKey(key)) continue; if (typeof entry === "string" && exportKeyDirectMatch(key, entry)) { continue; } const { main, types } = selectTargets(entry, { branchOrder, rootTypes }); const subDirRel = normalizeSubpath(key); const stubDir = node_path.join(rootDir, subDirRel); const stubJsonPath = node_path.join(stubDir, "package.json"); if (!main) { results.push({ stubDir, stubJsonPath, stub: null }); continue; } if (isAlreadyNode10Resolved(key, main)) { log.printWarn(`Skip redirect stub for export key '${key}' -> '${main}' (already Node.js 10 resolvable).`); continue; } const mainAbs = node_path.resolve(rootDir, main); const mainRel = toPosixRelative(stubDir, mainAbs); const stub = { private: true, main: mainRel, version: packageVersion }; if (types) { const typesAbs = node_path.resolve(rootDir, types); stub.types = toPosixRelative(stubDir, typesAbs); } results.push({ stubDir, stubJsonPath, stub }); } return results; } const LEADING_DOT_SLASH_RE = /^\.\//; const TRAILING_EXT_RE = /\.[^./]+$/; function normalizeSubpath(p) { return p.replace(LEADING_DOT_SLASH_RE, ""); } function stripExt(p) { return p.replace(TRAILING_EXT_RE, ""); } function exportKeyDirectMatch(key, value) { return normalizeSubpath(key) === normalizeSubpath(value); } function isAlreadyNode10Resolved(key, main) { const keyBase = normalizeSubpath(key); const mainBase = normalizeSubpath(main); const candidates = []; ["js", "cjs", "mjs", "json", "node"].forEach((ext) => { candidates.push(`${keyBase}.${ext}`); }); ["js", "cjs", "mjs", "json", "node"].forEach((ext) => { candidates.push(`${keyBase}/index.${ext}`); }); candidates.push(keyBase); if (candidates.includes(mainBase)) return true; if (stripExt(mainBase) === keyBase) return true; if (stripExt(mainBase) === stripExt(keyBase)) return true; return false; } async function writeRedirectStubs(tasks) { for (const task of tasks) { if (!task.stub || !task.stubDir || !task.stubJsonPath) continue; const dirStat = await promises.stat(task.stubDir).catch((err) => err); if (dirStat && !(dirStat instanceof Error) && !dirStat.isDirectory()) { log.printWarn(`Cannot create redirect stub for '${task.stubDir}' because a file already exists at that path.`); continue; } await promises.mkdir(task.stubDir, { recursive: true }); const json = JSON.stringify(task.stub, null, 2) + "\n"; await promises.writeFile(task.stubJsonPath, json, "utf8"); } } function normalizeExports(exportsField) { if (!exportsField) return null; if (typeof exportsField === "string" || Array.isArray(exportsField)) { return { ".": exportsField }; } if (isPlainObject(exportsField)) { return exportsField; } return null; } async function expandWildCardExports(exp, rootDir) { const result = {}; for (const [key, target] of Object.entries(exp)) { if (!key.includes("*")) { result[key] = target; continue; } const tokens = await collectTokensFromExportTarget(target, rootDir); if (tokens.size === 0) { continue; } for (const token of tokens) { const concreteKey = key.replace("*", token); const concreteTarget = replaceStarInTarget(target, token); result[concreteKey] = concreteTarget; } } return result; } async function collectTokensFromExportTarget(target, rootDir, acc = /* @__PURE__ */ new Set()) { if (typeof target === "string") { if (target.includes("*")) { for (const t of await inferTokensFromPattern(target, rootDir)) { acc.add(t); } } return acc; } if (Array.isArray(target)) { for (const item of target) { await collectTokensFromExportTarget(item, rootDir, acc); } return acc; } if (isPlainObject(target)) { for (const v of Object.values(target)) { await collectTokensFromExportTarget(v, rootDir, acc); } } return acc; } function replaceStarInTarget(target, token) { if (typeof target === "string") { return target.includes("*") ? target.replace("*", token) : target; } if (Array.isArray(target)) { return target.map((v) => replaceStarInTarget(v, token)); } if (isPlainObject(target)) { const out = {}; for (const [k, v] of Object.entries(target)) { out[k] = replaceStarInTarget(v, token); } return out; } return target; } async function inferTokensFromPattern(pattern, rootDir) { const starIndex = pattern.indexOf("*"); if (starIndex === -1) return []; const before = pattern.slice(0, starIndex); const after = pattern.slice(starIndex + 1); const lastSlash = before.lastIndexOf("/"); const dirPart = lastSlash === -1 ? "." : before.slice(0, lastSlash); const filePrefix = lastSlash === -1 ? before : before.slice(lastSlash + 1); const fileSuffix = after.includes("/") ? after.slice(0, after.indexOf("/")) : after; const absDir = node_path.resolve(rootDir, dirPart.replace(/^\.\//, "")); let entries = []; try { const dirStat = await promises.stat(absDir).catch(() => null); if (!dirStat || !dirStat.isDirectory()) return []; entries = await promises.readdir(absDir, { withFileTypes: true }); } catch { return []; } const tokens = []; for (const entry of entries) { if (!entry.isFile()) continue; const name = entry.name; if (!name.startsWith(filePrefix)) continue; if (!name.endsWith(fileSuffix)) continue; const core = name.substring(filePrefix.length, name.length - fileSuffix.length); if (core.length === 0) continue; tokens.push(core); } return tokens; } function isPlainObject(value) { return !!value && typeof value === "object" && !Array.isArray(value); } function isStubAbleKey(key) { if (!key.startsWith("./")) return false; if (key === "." || key === "./") return false; if (key === "./package.json") return false; if (key.includes("*")) return false; return true; } function getRemainingOrderedKeys(obj, branchOrder) { const exclude = /* @__PURE__ */ new Set([...branchOrder, "default", "types"]); return Object.keys(obj).filter((k) => !exclude.has(k)).sort(); } function selectTargets(entry, { branchOrder, rootTypes }) { const mainPick = pickMain(entry, branchOrder, void 0); const chosenBranch = mainPick?.branch; const typesPick = pickTypes(entry, chosenBranch, branchOrder) ?? rootTypes; return { main: mainPick?.path, types: typesPick }; } function pickMain(exportTarget, branchOrder, inheritedBranch) { if (typeof exportTarget === "string") return { path: exportTarget, branch: inheritedBranch }; if (Array.isArray(exportTarget)) { for (const candidate of exportTarget) { const result = pickMain(candidate, branchOrder, inheritedBranch); if (result?.path != null) return result; } return void 0; } if (isPlainObject(exportTarget)) { for (const condition of branchOrder) { if (condition in exportTarget) { const conditionalValue = exportTarget[condition]; const result = pickMain(conditionalValue, branchOrder, condition); if (result?.path != null) return result; } } const explicitDefault = exportTarget.default; if (typeof explicitDefault === "string") { return { path: explicitDefault, branch: inheritedBranch }; } for (const propKey of getRemainingOrderedKeys(exportTarget, branchOrder)) { const propValue = exportTarget[propKey]; if (typeof propValue === "string") return { path: propValue, branch: propKey }; const result = pickMain(propValue, branchOrder, propKey); if (result?.path) return result; } } return void 0; } function pickTypes(exportTarget, chosenBranch, branchOrder) { if (typeof exportTarget === "string") return void 0; if (Array.isArray(exportTarget)) { for (const candidate of exportTarget) { const found = pickTypes(candidate, chosenBranch, branchOrder); if (found) return found; } return void 0; } if (isPlainObject(exportTarget)) { if (chosenBranch && exportTarget[chosenBranch]) { const chosenBranchValueRaw = exportTarget[chosenBranch]; if (Array.isArray(chosenBranchValueRaw)) { for (const element of chosenBranchValueRaw) { if (isPlainObject(element) && typeof element.types === "string") { return element.types; } } } else if (isPlainObject(chosenBranchValueRaw)) { const chosenBranchValue = chosenBranchValueRaw; if (typeof chosenBranchValue.types === "string") return chosenBranchValue.types; } } if (typeof exportTarget.types === "string") return exportTarget.types; for (const condition of branchOrder) { if (condition !== chosenBranch && exportTarget[condition] && isPlainObject(exportTarget[condition])) { const conditionValue = exportTarget[condition]; const typesPath = conditionValue.types; if (typeof typesPath === "string") return typesPath; } } for (const key of getRemainingOrderedKeys(exportTarget, branchOrder)) { const nestedValue = exportTarget[key]; const found = pickTypes(nestedValue, chosenBranch, branchOrder); if (found) return found; } for (const condition of branchOrder) { if (exportTarget[condition] && isPlainObject(exportTarget[condition])) { const nestedValue = exportTarget[condition]; const found = pickTypes(nestedValue, chosenBranch, branchOrder); if (found) return found; } } } return void 0; } function toPosixRelative(fromDir, toAbs) { let rel = node_path.relative(fromDir, toAbs); rel = rel.split(node_path.sep).join("/"); if (!rel.startsWith(".") && !rel.startsWith("/")) rel = "./" + rel; return rel; } exports.dts = dts; exports.dtsForCjs = dtsForCjs; exports.dtsForEsm = dtsForEsm;