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
JavaScript
;
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;