@d1g1tal/tsbuild 1.8.6 → 1.8.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -23,19 +23,21 @@ import {
23
23
  dtsCacheVersion,
24
24
  format,
25
25
  newLine,
26
+ outputManifestFile,
26
27
  processEnvExpansionPattern,
27
28
  sourceScriptExtensionExpression,
28
29
  toEsTarget,
29
30
  toJsxRenderingMode,
30
31
  typeMatcher
31
- } from "./JKGYA2AW.js";
32
+ } from "./QL4XMDMJ.js";
32
33
 
33
34
  // src/files.ts
34
- import { dirname } from "node:path";
35
+ import { dirname, join } from "node:path";
35
36
  import { serialize, deserialize } from "node:v8";
36
37
  import { brotliDecompress, brotliCompress } from "node:zlib";
37
38
  import { access, constants, mkdir, readdir, readFile, rm, writeFile } from "node:fs/promises";
38
- var Files = class _Files {
39
+ var windowsDrivePathRegex = /^[A-Za-z]:[\\/]/;
40
+ var Files = class {
39
41
  constructor() {
40
42
  }
41
43
  /**
@@ -55,13 +57,31 @@ var Files = class _Files {
55
57
  }
56
58
  }
57
59
  /**
58
- * Clear a directory by removing all files and subdirectories.
60
+ * Clear a directory by removing all of its entries in parallel.
61
+ * Uses readdir + parallel rm so libuv's threadpool can unlink subtrees concurrently,
62
+ * which is significantly faster than a single recursive `rm` for large output trees.
63
+ * The directory itself is preserved (no mkdir needed afterward).
59
64
  * @param directory The path to the directory to clear.
60
65
  */
61
66
  static async empty(directory) {
62
- if (await _Files.exists(directory)) {
63
- await Promise.all((await readdir(directory)).map((file) => rm(Paths.join(directory, file), defaultCleanOptions)));
67
+ let entries;
68
+ try {
69
+ entries = await readdir(directory);
70
+ } catch (error) {
71
+ if (error.code === "ENOENT") {
72
+ await mkdir(directory, defaultDirOptions);
73
+ return;
74
+ }
75
+ throw error;
76
+ }
77
+ if (entries.length === 0) {
78
+ return;
64
79
  }
80
+ const removals = new Array(entries.length);
81
+ for (let i = 0, length = entries.length; i < length; i++) {
82
+ removals[i] = rm(join(directory, entries[i]), defaultCleanOptions);
83
+ }
84
+ await Promise.all(removals);
65
85
  }
66
86
  /**
67
87
  * Write data to a file.
@@ -95,9 +115,16 @@ var Files = class _Files {
95
115
  * Normalize a file path to an absolute path.
96
116
  * @param path The file path to normalize.
97
117
  * @returns The normalized absolute path.
118
+ * @throws {TypeError} if path is relative and not a valid URL
98
119
  */
99
120
  static normalizePath(path) {
100
- return path.startsWith("/") || path.startsWith("file://") ? path : new URL(path, import.meta.url).pathname;
121
+ if (path.startsWith("/") || path.startsWith("file://") || windowsDrivePathRegex.test(path)) {
122
+ return path;
123
+ }
124
+ if (!path.includes("://")) {
125
+ throw new TypeError(`Files.normalizePath requires an absolute path, got: ${path}`);
126
+ }
127
+ return new URL(path, import.meta.url).pathname;
101
128
  }
102
129
  /**
103
130
  * Decompress a Brotli-compressed buffer.
@@ -390,6 +417,9 @@ var Logger = class _Logger {
390
417
  }
391
418
  };
392
419
 
420
+ // src/type-script-project.ts
421
+ import { rm as rm2 } from "node:fs/promises";
422
+
393
423
  // src/dts/declaration-bundler.ts
394
424
  import MagicString2 from "magic-string";
395
425
  import { mkdir as mkdir2, writeFile as writeFile2 } from "node:fs/promises";
@@ -472,6 +502,7 @@ import {
472
502
  isEmptyStatement,
473
503
  isEnumDeclaration,
474
504
  isExportDeclaration,
505
+ isExportSpecifier,
475
506
  isFunctionDeclaration,
476
507
  isIdentifier,
477
508
  isImportDeclaration,
@@ -491,6 +522,7 @@ import {
491
522
  getCombinedModifierFlags
492
523
  } from "typescript";
493
524
  import MagicString from "magic-string";
525
+ var commaCharacter = 44;
494
526
  var DeclarationProcessor = class _DeclarationProcessor {
495
527
  constructor() {
496
528
  }
@@ -695,21 +727,31 @@ var DeclarationProcessor = class _DeclarationProcessor {
695
727
  }
696
728
  const { flags } = node.declarationList;
697
729
  const prefix = `declare ${flags & NodeFlags.Let ? "let" : flags & NodeFlags.Const ? "const" : "var"} `;
698
- let commaPos = 0;
699
- for (const declarationNode of node.declarationList.getChildren().find((n) => n.kind === SyntaxKind2.SyntaxList)?.getChildren() ?? []) {
700
- if (declarationNode.kind === SyntaxKind2.CommaToken) {
701
- code.remove(commaPos = declarationNode.getStart(), declarationNode.getEnd());
702
- } else if (commaPos) {
703
- code.appendLeft(commaPos, `;${newLine}`);
704
- const start = declarationNode.getFullStart();
705
- const slice = code.slice(start, declarationNode.getStart());
706
- const whitespace = slice.length - slice.trimStart().length;
707
- if (whitespace) {
708
- code.overwrite(start, start + whitespace, prefix);
709
- } else {
710
- code.appendLeft(start, prefix);
730
+ const sourceText = sourceFile.text;
731
+ for (let i = 1; i < declarations.length; i++) {
732
+ const prev = declarations[i - 1];
733
+ const curr = declarations[i];
734
+ let commaPos = -1;
735
+ const limit = curr.getStart();
736
+ for (let p = prev.end; p < limit; p++) {
737
+ if (sourceText.charCodeAt(p) === commaCharacter) {
738
+ commaPos = p;
739
+ break;
711
740
  }
712
741
  }
742
+ if (commaPos === -1) {
743
+ continue;
744
+ }
745
+ code.remove(commaPos, commaPos + 1);
746
+ code.appendLeft(commaPos, `;${newLine}`);
747
+ const start = curr.getFullStart();
748
+ const slice = sourceText.substring(start, curr.getStart());
749
+ const whitespace = slice.length - slice.trimStart().length;
750
+ if (whitespace) {
751
+ code.overwrite(start, start + whitespace, prefix);
752
+ } else {
753
+ code.appendLeft(start, prefix);
754
+ }
713
755
  }
714
756
  }
715
757
  }
@@ -776,16 +818,8 @@ var DeclarationProcessor = class _DeclarationProcessor {
776
818
  magic.overwrite(node.moduleSpecifier.getStart() + 1, node.moduleSpecifier.getEnd() - 1, replacement);
777
819
  }
778
820
  }
779
- if (isModuleDeclaration(node) && node.body && isModuleBlock(node.body)) {
780
- for (const bodyStatement of node.body.statements) {
781
- if (isExportDeclaration(bodyStatement) && bodyStatement.exportClause && !isNamespaceExport(bodyStatement.exportClause)) {
782
- for (const { name, propertyName } of bodyStatement.exportClause.elements) {
783
- if (propertyName && isIdentifier(propertyName) && isIdentifier(name) && propertyName.getText() === name.getText()) {
784
- magic.remove(propertyName.getStart(), name.getStart());
785
- }
786
- }
787
- }
788
- }
821
+ if (isExportSpecifier(node) && node.propertyName && isIdentifier(node.propertyName) && isIdentifier(node.name) && node.propertyName.text === node.name.text) {
822
+ magic.remove(node.propertyName.getStart(), node.name.getStart());
789
823
  }
790
824
  forEachChild(node, visitNode);
791
825
  }
@@ -808,7 +842,9 @@ import {
808
842
  isClassDeclaration as isClassDeclaration2,
809
843
  isVariableStatement as isVariableStatement2,
810
844
  isModuleDeclaration as isModuleDeclaration2,
845
+ isModuleBlock as isModuleBlock2,
811
846
  isNamedExports as isNamedExports2,
847
+ isNamedImports,
812
848
  isIdentifier as isIdentifier2,
813
849
  isNamespaceImport,
814
850
  isQualifiedName,
@@ -818,35 +854,31 @@ import {
818
854
  } from "typescript";
819
855
  var nodeModules = "/node_modules/";
820
856
  var emptySet = /* @__PURE__ */ new Set();
821
- var importPattern = /^import\s*(?:type\s*)?\{\s*([^}]+)\s*\}\s*from\s*['"]([^'"]+)['"]\s*;?\s*$/;
822
- var typePrefixPattern = /^type:/;
823
857
  function mergeImports(imports) {
824
- const moduleImports = /* @__PURE__ */ new Map();
825
- const nonMergeableImports = /* @__PURE__ */ new Set();
826
- for (const importStatement of imports) {
827
- const match = importPattern.exec(importStatement);
828
- if (match) {
829
- const [, namesString, moduleSpecifier] = match;
830
- const isType = importStatement.includes("import type");
831
- const key = `${isType ? "type:" : ""}${moduleSpecifier}`;
832
- let entry = moduleImports.get(key);
833
- if (entry === void 0) {
834
- entry = { names: /* @__PURE__ */ new Set(), isType };
835
- moduleImports.set(key, entry);
836
- }
837
- for (const name of namesString.split(",")) {
838
- entry.names.add(name.trim());
839
- }
840
- } else {
841
- nonMergeableImports.add(importStatement);
858
+ const merged = /* @__PURE__ */ new Map();
859
+ const raw = /* @__PURE__ */ new Set();
860
+ for (const imp of imports) {
861
+ if (imp.kind === "raw") {
862
+ raw.add(imp.text);
863
+ continue;
864
+ }
865
+ const key = `${imp.isType ? "type:" : ""}${imp.specifier}`;
866
+ let entry = merged.get(key);
867
+ if (entry === void 0) {
868
+ entry = { specifier: imp.specifier, isType: imp.isType, names: /* @__PURE__ */ new Set() };
869
+ merged.set(key, entry);
870
+ }
871
+ for (const name of imp.names) {
872
+ entry.names.add(name);
842
873
  }
843
874
  }
844
875
  const result = [];
845
- for (const [key, { names, isType }] of moduleImports) {
846
- result.push(`${isType ? "import type" : "import"} { ${[...names].sort().join(", ")} } from "${key.replace(typePrefixPattern, "")}";`);
876
+ for (const { specifier, isType, names } of merged.values()) {
877
+ const sorted = Array.from(names).sort();
878
+ result.push(`${isType ? "import type" : "import"} { ${sorted.join(", ")} } from "${specifier}";`);
847
879
  }
848
- for (const imp of nonMergeableImports) {
849
- result.push(imp);
880
+ for (const text of raw) {
881
+ result.push(text);
850
882
  }
851
883
  return result;
852
884
  }
@@ -859,6 +891,8 @@ var DeclarationBundler = class _DeclarationBundler {
859
891
  options;
860
892
  /** WeakMap cache for identifier collection to avoid re-parsing same source files */
861
893
  identifierCache = /* @__PURE__ */ new WeakMap();
894
+ /** SourceFile cache keyed by path — survives across multiple bundle() calls (entry points) */
895
+ sourceFileCache = /* @__PURE__ */ new Map();
862
896
  /** Module resolution cache for this bundler instance */
863
897
  moduleResolutionCache = /* @__PURE__ */ new Map();
864
898
  /** Pre-computed set of directory prefixes from declaration file paths for O(1) directoryExists lookups */
@@ -926,6 +960,7 @@ var DeclarationBundler = class _DeclarationBundler {
926
960
  clearExternalFiles() {
927
961
  this.externalDeclarationFiles.clear();
928
962
  this.moduleResolutionCache.clear();
963
+ this.sourceFileCache.clear();
929
964
  }
930
965
  /**
931
966
  * Convert a source file path to its corresponding declaration file path
@@ -954,21 +989,6 @@ var DeclarationBundler = class _DeclarationBundler {
954
989
  }
955
990
  return bestMatch ?? sourcePath;
956
991
  }
957
- /**
958
- * Extract import statements from declaration file content using AST
959
- * Handles: import { X } from 'module', import * as X from 'module', export { X } from 'module'
960
- * @param sourceFile - The parsed source file AST
961
- * @returns Array of module specifiers that are imported
962
- */
963
- extractImports({ statements }) {
964
- const imports = [];
965
- for (const statement of statements) {
966
- if ((isImportDeclaration2(statement) || isExportDeclaration2(statement)) && statement.moduleSpecifier) {
967
- imports.push(statement.moduleSpecifier.text);
968
- }
969
- }
970
- return imports;
971
- }
972
992
  /**
973
993
  * Builds an O(1) matcher from a mixed Pattern array by splitting into a Set<string> for
974
994
  * exact/sub-path checks and a RegExp[] for regex tests. Called once per bundler instance.
@@ -1050,22 +1070,29 @@ var DeclarationBundler = class _DeclarationBundler {
1050
1070
  return;
1051
1071
  }
1052
1072
  const { code, typeReferences, fileReferences } = cached;
1053
- const sourceFile = createSourceFile(path, code, ScriptTarget.Latest, true);
1073
+ let sourceFile = this.sourceFileCache.get(path);
1074
+ if (sourceFile === void 0) {
1075
+ sourceFile = createSourceFile(path, code, ScriptTarget.Latest, true);
1076
+ this.sourceFileCache.set(path, sourceFile);
1077
+ }
1054
1078
  const identifiers = this.collectIdentifiers(sourceFile.statements, sourceFile);
1055
1079
  const module = { path, code, imports: /* @__PURE__ */ new Set(), typeReferences: new Set(typeReferences), fileReferences: new Set(fileReferences), sourceFile, identifiers };
1056
1080
  const bundledSpecs = /* @__PURE__ */ new Set();
1057
- for (const specifier of this.extractImports(sourceFile)) {
1058
- if (this.matchExternal(specifier)) {
1059
- continue;
1060
- }
1061
- const resolvedPath = this.resolveModule(specifier, path);
1062
- if (resolvedPath?.includes(nodeModules) && !this.matchNoExternal(specifier)) {
1063
- continue;
1064
- }
1065
- if (resolvedPath && (this.declarationFiles.has(resolvedPath) || this.externalDeclarationFiles.has(resolvedPath))) {
1066
- module.imports.add(resolvedPath);
1067
- bundledSpecs.add(specifier);
1068
- visit(resolvedPath);
1081
+ for (const statement of sourceFile.statements) {
1082
+ if ((isImportDeclaration2(statement) || isExportDeclaration2(statement)) && statement.moduleSpecifier) {
1083
+ const specifier = statement.moduleSpecifier.text;
1084
+ if (this.matchExternal(specifier)) {
1085
+ continue;
1086
+ }
1087
+ const resolvedPath = this.resolveModule(specifier, path);
1088
+ if (resolvedPath?.includes(nodeModules) && !this.matchNoExternal(specifier)) {
1089
+ continue;
1090
+ }
1091
+ if (resolvedPath && (this.declarationFiles.has(resolvedPath) || this.externalDeclarationFiles.has(resolvedPath))) {
1092
+ module.imports.add(resolvedPath);
1093
+ bundledSpecs.add(specifier);
1094
+ visit(resolvedPath);
1095
+ }
1069
1096
  }
1070
1097
  }
1071
1098
  modules.set(path, module);
@@ -1160,7 +1187,12 @@ var DeclarationBundler = class _DeclarationBundler {
1160
1187
  if (statement.name && isIdentifier2(statement.name)) {
1161
1188
  values.add(statement.name.text);
1162
1189
  }
1163
- collectNestedIdentifiers(statement.getChildren());
1190
+ const body = statement.body;
1191
+ if (body && isModuleBlock2(body)) {
1192
+ collectNestedIdentifiers(body.statements);
1193
+ } else if (body && isModuleDeclaration2(body)) {
1194
+ collectNestedIdentifiers([body]);
1195
+ }
1164
1196
  }
1165
1197
  }
1166
1198
  result = { types, values };
@@ -1188,7 +1220,13 @@ var DeclarationBundler = class _DeclarationBundler {
1188
1220
  const magic = new MagicString2(code);
1189
1221
  const moduleRenames = /* @__PURE__ */ new Map();
1190
1222
  const exportsMapper = (name) => moduleRenames.get(name) ?? name;
1191
- for (const name of [...typeIdentifiers, ...valueIdentifiers]) {
1223
+ for (const name of typeIdentifiers) {
1224
+ const renamed = renameMap.get(`${name}:${modulePath}`);
1225
+ if (renamed) {
1226
+ moduleRenames.set(name, renamed);
1227
+ }
1228
+ }
1229
+ for (const name of valueIdentifiers) {
1192
1230
  const renamed = renameMap.get(`${name}:${modulePath}`);
1193
1231
  if (renamed) {
1194
1232
  moduleRenames.set(name, renamed);
@@ -1199,7 +1237,21 @@ var DeclarationBundler = class _DeclarationBundler {
1199
1237
  if (isImportDeclaration2(statement)) {
1200
1238
  const moduleSpecifier = statement.moduleSpecifier.text;
1201
1239
  if (this.matchExternal(moduleSpecifier) || !bundledImportPaths.has(moduleSpecifier)) {
1202
- externalImports.push(code.substring(statement.pos, statement.end).trim());
1240
+ const importClause = statement.importClause;
1241
+ const isTypeOnly = importClause?.isTypeOnly === true;
1242
+ const namedBindings = importClause?.namedBindings;
1243
+ if (importClause && !importClause.name && namedBindings && isNamedImports(namedBindings)) {
1244
+ const names = [];
1245
+ for (const element of namedBindings.elements) {
1246
+ const local = element.name.text;
1247
+ const original = element.propertyName?.text;
1248
+ const prefix = element.isTypeOnly ? "type " : "";
1249
+ names.push(original ? `${prefix}${original} as ${local}` : `${prefix}${local}`);
1250
+ }
1251
+ externalImports.push({ kind: "named", specifier: moduleSpecifier, isType: isTypeOnly, names });
1252
+ } else {
1253
+ externalImports.push({ kind: "raw", text: code.substring(statement.pos, statement.end).trim() });
1254
+ }
1203
1255
  } else if (statement.importClause?.namedBindings && isNamespaceImport(statement.importClause.namedBindings)) {
1204
1256
  bundledNamespaceAliases.add(statement.importClause.namedBindings.name.text);
1205
1257
  }
@@ -1228,9 +1280,13 @@ var DeclarationBundler = class _DeclarationBundler {
1228
1280
  magic.remove(statement.pos, statement.end);
1229
1281
  }
1230
1282
  }
1231
- if (moduleRenames.size > 0) {
1283
+ const hasRenames = moduleRenames.size > 0;
1284
+ const hasBundledAliases = bundledNamespaceAliases.size > 0;
1285
+ if (hasRenames || hasBundledAliases) {
1232
1286
  const visit = (node) => {
1233
- if (isIdentifier2(node)) {
1287
+ if (hasBundledAliases && isQualifiedName(node) && isIdentifier2(node.left) && bundledNamespaceAliases.has(node.left.text)) {
1288
+ magic.remove(node.left.getStart(), node.right.getStart());
1289
+ } else if (hasRenames && isIdentifier2(node)) {
1234
1290
  const renamed = moduleRenames.get(node.text);
1235
1291
  if (renamed) {
1236
1292
  magic.overwrite(node.getStart(), node.end, renamed);
@@ -1244,15 +1300,6 @@ var DeclarationBundler = class _DeclarationBundler {
1244
1300
  }
1245
1301
  }
1246
1302
  }
1247
- if (bundledNamespaceAliases.size > 0) {
1248
- const visitQualified = (node) => {
1249
- if (isQualifiedName(node) && isIdentifier2(node.left) && bundledNamespaceAliases.has(node.left.text)) {
1250
- magic.remove(node.left.getStart(), node.right.getStart());
1251
- }
1252
- forEachChild2(node, visitQualified);
1253
- };
1254
- forEachChild2(sourceFile, visitQualified);
1255
- }
1256
1303
  const finalValueExportsSet = new Set(valueExports.map(exportsMapper));
1257
1304
  const finalValueExports = [...finalValueExportsSet];
1258
1305
  const finalTypeExports = [...new Set(typeExports.map(exportsMapper).filter((t) => !finalValueExportsSet.has(t)))];
@@ -1265,27 +1312,30 @@ var DeclarationBundler = class _DeclarationBundler {
1265
1312
  * @returns Object containing combined code, all exported identifiers, and all declarations from bundled modules
1266
1313
  */
1267
1314
  combineModules(sortedModules, bundledSpecifiers) {
1268
- const allTypeReferences = [];
1269
- const allFileReferences = [];
1315
+ const typeReferencesSet = /* @__PURE__ */ new Set();
1316
+ const fileReferencesSet = /* @__PURE__ */ new Set();
1270
1317
  const allExternalImports = [];
1271
- const allTypeExports = [];
1272
- const allValueExports = [];
1318
+ const valueExportsSet = /* @__PURE__ */ new Set();
1319
+ const typeExportsSeen = /* @__PURE__ */ new Set();
1320
+ const orderedTypeExports = [];
1273
1321
  const codeBlocks = [];
1274
1322
  const allDeclarations = /* @__PURE__ */ new Set();
1275
1323
  const declarationSources = /* @__PURE__ */ new Map();
1276
1324
  const renameMap = /* @__PURE__ */ new Map();
1277
1325
  for (const { path, identifiers } of sortedModules) {
1278
1326
  for (const name of identifiers.types) {
1279
- if (!declarationSources.has(name)) {
1280
- declarationSources.set(name, /* @__PURE__ */ new Set());
1327
+ let set = declarationSources.get(name);
1328
+ if (set === void 0) {
1329
+ declarationSources.set(name, set = /* @__PURE__ */ new Set());
1281
1330
  }
1282
- declarationSources.get(name).add(path);
1331
+ set.add(path);
1283
1332
  }
1284
1333
  for (const name of identifiers.values) {
1285
- if (!declarationSources.has(name)) {
1286
- declarationSources.set(name, /* @__PURE__ */ new Set());
1334
+ let set = declarationSources.get(name);
1335
+ if (set === void 0) {
1336
+ declarationSources.set(name, set = /* @__PURE__ */ new Set());
1287
1337
  }
1288
- declarationSources.get(name).add(path);
1338
+ set.add(path);
1289
1339
  }
1290
1340
  }
1291
1341
  for (const [name, sourcesSet] of declarationSources) {
@@ -1304,14 +1354,27 @@ var DeclarationBundler = class _DeclarationBundler {
1304
1354
  }
1305
1355
  }
1306
1356
  for (const { path, typeReferences, fileReferences, sourceFile, code, identifiers } of sortedModules) {
1307
- allTypeReferences.push(...typeReferences);
1308
- allFileReferences.push(...fileReferences);
1357
+ for (const r of typeReferences) {
1358
+ typeReferencesSet.add(r);
1359
+ }
1360
+ for (const r of fileReferences) {
1361
+ fileReferencesSet.add(r);
1362
+ }
1309
1363
  const bundledForThisModule = bundledSpecifiers.get(path) ?? emptySet;
1310
1364
  const { code: strippedCode, externalImports, typeExports, valueExports } = this.stripImportsExports(code, sourceFile, identifiers, bundledForThisModule, renameMap, path);
1311
- allExternalImports.push(...externalImports);
1365
+ for (const imp of externalImports) {
1366
+ allExternalImports.push(imp);
1367
+ }
1312
1368
  if (!path.includes(nodeModules)) {
1313
- allValueExports.push(...valueExports);
1314
- allTypeExports.push(...typeExports);
1369
+ for (const exp of valueExports) {
1370
+ valueExportsSet.add(exp);
1371
+ }
1372
+ for (const exp of typeExports) {
1373
+ if (!typeExportsSeen.has(exp)) {
1374
+ typeExportsSeen.add(exp);
1375
+ orderedTypeExports.push(exp);
1376
+ }
1377
+ }
1315
1378
  for (const name of identifiers.types) {
1316
1379
  allDeclarations.add(name);
1317
1380
  }
@@ -1323,27 +1386,37 @@ var DeclarationBundler = class _DeclarationBundler {
1323
1386
  codeBlocks.push(strippedCode.trim());
1324
1387
  }
1325
1388
  }
1326
- const typeReferencesSet = new Set(allTypeReferences);
1327
- const uniqueFileReferences = [...new Set(allFileReferences)];
1328
1389
  const mergedExternalImports = mergeImports(allExternalImports);
1329
1390
  for (const imp of mergedExternalImports) {
1330
1391
  if (imp.includes('"node:') || imp.includes("'node:")) {
1331
1392
  typeReferencesSet.add("node");
1332
1393
  }
1333
1394
  }
1334
- const uniqueTypeReferences = [...typeReferencesSet];
1335
- const finalValueExportsSet = new Set(allValueExports);
1336
- const finalValueExports = [...finalValueExportsSet];
1337
- const finalTypeExports = [...new Set(allTypeExports.filter((typeExport) => !finalValueExportsSet.has(typeExport)))];
1395
+ const finalValueExports = [...valueExportsSet];
1396
+ const finalTypeExports = [];
1397
+ for (const typeExport of orderedTypeExports) {
1398
+ if (!valueExportsSet.has(typeExport)) {
1399
+ finalTypeExports.push(typeExport);
1400
+ }
1401
+ }
1338
1402
  const outputParts = [];
1339
- if (uniqueFileReferences.length > 0) {
1340
- outputParts.push(...uniqueFileReferences.map((ref) => `/// <reference path="${ref}" />`), "");
1403
+ if (fileReferencesSet.size > 0) {
1404
+ for (const ref of fileReferencesSet) {
1405
+ outputParts.push(`/// <reference path="${ref}" />`);
1406
+ }
1407
+ outputParts.push("");
1341
1408
  }
1342
- if (uniqueTypeReferences.length > 0) {
1343
- outputParts.push(...uniqueTypeReferences.map((ref) => `/// <reference types="${ref}" />`), "");
1409
+ if (typeReferencesSet.size > 0) {
1410
+ for (const ref of typeReferencesSet) {
1411
+ outputParts.push(`/// <reference types="${ref}" />`);
1412
+ }
1413
+ outputParts.push("");
1344
1414
  }
1345
1415
  if (mergedExternalImports.length > 0) {
1346
- outputParts.push(...mergedExternalImports, "");
1416
+ for (const imp of mergedExternalImports) {
1417
+ outputParts.push(imp);
1418
+ }
1419
+ outputParts.push("");
1347
1420
  }
1348
1421
  outputParts.push(codeBlocks.join(newLine + newLine));
1349
1422
  if (finalTypeExports.length > 0 || finalValueExports.length > 0) {
@@ -1403,14 +1476,23 @@ async function bundleDeclarations(options) {
1403
1476
  await mkdir2(options.compilerOptions.outDir, defaultDirOptions);
1404
1477
  }
1405
1478
  const dtsBundler = new DeclarationBundler(options);
1406
- await new Promise((resolve3) => void setImmediate(resolve3));
1407
1479
  const bundleTasks = [];
1408
- for (const [entryName, entryPoint] of Object.entries(options.entryPoints)) {
1480
+ const bundleEntryPoint = (entryName, entryPoint) => {
1409
1481
  const content = dtsBundler.bundle(entryPoint);
1410
1482
  if (content.length > 0) {
1411
1483
  const outPath = Paths.join(options.compilerOptions.outDir, `${entryName}${FileExtension.DTS}`);
1412
1484
  bundleTasks.push(writeFile2(outPath, content, Encoding.utf8).then(() => ({ path: Paths.relative(options.currentDirectory, outPath), size: content.length })));
1413
1485
  }
1486
+ };
1487
+ if (options.parallelTranspile) {
1488
+ for (const [entryName, entryPoint] of Object.entries(options.entryPoints)) {
1489
+ await new Promise((resolve3) => void setImmediate(resolve3));
1490
+ bundleEntryPoint(entryName, entryPoint);
1491
+ }
1492
+ } else {
1493
+ for (const [entryName, entryPoint] of Object.entries(options.entryPoints)) {
1494
+ bundleEntryPoint(entryName, entryPoint);
1495
+ }
1414
1496
  }
1415
1497
  const results = await Promise.all(bundleTasks);
1416
1498
  dtsBundler.clearExternalFiles();
@@ -1574,12 +1656,12 @@ async function resolvePlugins(plugins, projectDir) {
1574
1656
  }
1575
1657
 
1576
1658
  // src/plugins/iife.ts
1577
- import { mkdir as mkdir3, readFile as readFile2, writeFile as writeFile3 } from "node:fs/promises";
1578
- import { basename as basename2, dirname as dirname2, join, resolve as resolve2 } from "node:path";
1659
+ import { mkdir as mkdir3, writeFile as writeFile3 } from "node:fs/promises";
1660
+ import { basename as basename2, dirname as dirname2, join as join2, resolve as resolve2 } from "node:path";
1661
+ var namespace = "iife";
1662
+ var fileExtensionRegex = /\.[^.]+$/;
1579
1663
  var textDecoder = new TextDecoder();
1580
- var jsExtension = ".js";
1581
1664
  function iifePlugin(options) {
1582
- const globalName = options?.globalName;
1583
1665
  const files = [];
1584
1666
  return {
1585
1667
  files,
@@ -1594,14 +1676,14 @@ function iifePlugin(options) {
1594
1676
  if (!outdir) {
1595
1677
  return;
1596
1678
  }
1679
+ build.initialOptions.write = false;
1597
1680
  const sourcemap = build.initialOptions.sourcemap;
1598
1681
  const entryPointNames = extractEntryNames(build.initialOptions.entryPoints);
1599
- build.onEnd(async ({ metafile }) => {
1600
- if (!metafile || entryPointNames.length === 0) {
1682
+ build.onEnd(async ({ outputFiles }) => {
1683
+ if (!outputFiles || outputFiles.length === 0 || entryPointNames.length === 0) {
1601
1684
  return;
1602
1685
  }
1603
- const written = await buildIife(metafile.outputs, entryPointNames, outdir, globalName, sourcemap);
1604
- files.push(...written);
1686
+ files.push(...await buildIife(outputFiles, entryPointNames, outdir, options?.globalName, sourcemap));
1605
1687
  });
1606
1688
  }
1607
1689
  }
@@ -1615,16 +1697,15 @@ function extractEntryNames(entryPoints) {
1615
1697
  const names = [];
1616
1698
  for (const entry of entryPoints) {
1617
1699
  if (typeof entry === "string") {
1618
- names.push(basename2(entry).replace(/\.[^.]+$/, ""));
1700
+ names.push(basename2(entry).replace(fileExtensionRegex, ""));
1619
1701
  } else {
1620
- names.push(entry.out ?? basename2(entry.in).replace(/\.[^.]+$/, ""));
1702
+ names.push(entry.out ?? basename2(entry.in).replace(fileExtensionRegex, ""));
1621
1703
  }
1622
1704
  }
1623
1705
  return names;
1624
1706
  }
1625
1707
  return Object.keys(entryPoints);
1626
1708
  }
1627
- var namespace = "iife";
1628
1709
  function wrapAsIife(text, globalName) {
1629
1710
  const exportStart = text.lastIndexOf("export");
1630
1711
  if (exportStart === -1) {
@@ -1667,36 +1748,47 @@ ${text.slice(0, exportStart)}
1667
1748
  ${assignment}
1668
1749
  })();${text.slice(exportEnd)}`;
1669
1750
  }
1670
- async function buildIife(outputs, entryPointNames, outdir, globalName, sourcemap) {
1751
+ async function buildIife(primaryOutputs, entryPointNames, outdir, globalName, sourcemap) {
1671
1752
  const { build: esbuild } = await import("esbuild");
1672
1753
  const fileContents = /* @__PURE__ */ new Map();
1673
- for (const outputPath of Object.keys(outputs)) {
1674
- if (outputPath.endsWith(jsExtension)) {
1675
- fileContents.set(resolve2(outputPath), await readFile2(outputPath, "utf8"));
1754
+ const primaryWrites = [];
1755
+ const ensuredDirs = /* @__PURE__ */ new Map();
1756
+ for (const file of primaryOutputs) {
1757
+ const absolute = resolve2(file.path);
1758
+ if (absolute.endsWith(FileExtension.JS)) {
1759
+ fileContents.set(absolute, file.text);
1760
+ }
1761
+ const dir = dirname2(absolute);
1762
+ let dirReady = ensuredDirs.get(dir);
1763
+ if (dirReady === void 0) {
1764
+ dirReady = mkdir3(dir, { recursive: true });
1765
+ ensuredDirs.set(dir, dirReady);
1676
1766
  }
1767
+ primaryWrites.push(dirReady.then(() => writeFile3(absolute, file.contents)));
1677
1768
  }
1678
1769
  const validEntries = [];
1679
1770
  for (const name of entryPointNames) {
1680
- const path = Paths.absolute(outdir, name + jsExtension);
1771
+ const path = Paths.absolute(outdir, name + FileExtension.JS);
1681
1772
  if (fileContents.has(path)) {
1682
1773
  validEntries.push({ name, path });
1683
1774
  }
1684
1775
  }
1685
1776
  if (validEntries.length === 0) {
1777
+ await Promise.all(primaryWrites);
1686
1778
  return [];
1687
1779
  }
1688
- const hasSourceMap = sourcemap !== void 0 && sourcemap !== false;
1780
+ const sourcemapValue = sourcemap !== void 0 && sourcemap !== false ? "external" : false;
1689
1781
  const plugins = [virtualLoaderPlugin(fileContents)];
1690
- const iifeOutdir = join(outdir, namespace);
1782
+ const iifeOutdir = join2(outdir, namespace);
1691
1783
  await mkdir3(iifeOutdir, { recursive: true });
1692
1784
  const results = await Promise.all(validEntries.map(({ name, path }) => {
1693
1785
  return esbuild({
1694
1786
  entryPoints: { [name]: path },
1695
1787
  bundle: true,
1696
- format: "esm",
1788
+ format,
1697
1789
  splitting: false,
1698
1790
  outdir: iifeOutdir,
1699
- sourcemap: hasSourceMap ? "external" : false,
1791
+ sourcemap: sourcemapValue,
1700
1792
  write: false,
1701
1793
  logLevel: "warning",
1702
1794
  plugins
@@ -1705,24 +1797,25 @@ async function buildIife(outputs, entryPointNames, outdir, globalName, sourcemap
1705
1797
  const written = [];
1706
1798
  const writes = [];
1707
1799
  const cwd = process.cwd();
1708
- for (const { outputFiles: iifeFiles } of results) {
1709
- const outputFilePaths = new Set(iifeFiles.map(({ path }) => path));
1710
- for (const { path, contents } of iifeFiles) {
1711
- if (path.endsWith(jsExtension)) {
1712
- let text = wrapAsIife(textDecoder.decode(contents), globalName);
1800
+ for (const { outputFiles } of results) {
1801
+ const outputFilePaths = new Set(outputFiles.map(({ path }) => path));
1802
+ for (const { path, contents } of outputFiles) {
1803
+ let text, size = contents.byteLength;
1804
+ if (path.endsWith(FileExtension.JS)) {
1805
+ text = wrapAsIife(textDecoder.decode(contents), globalName);
1713
1806
  if (outputFilePaths.has(`${path}.map`)) {
1714
1807
  text += `
1715
1808
  //# sourceMappingURL=${basename2(path)}.map`;
1809
+ size = Buffer.byteLength(text);
1716
1810
  }
1717
- writes.push(writeFile3(path, text));
1718
- written.push({ path: Paths.relative(cwd, path), size: Buffer.byteLength(text) });
1719
1811
  } else {
1720
- writes.push(writeFile3(path, contents));
1721
- written.push({ path: Paths.relative(cwd, path), size: contents.byteLength });
1812
+ text = contents;
1722
1813
  }
1814
+ writes.push(writeFile3(path, text));
1815
+ written.push({ path: Paths.relative(cwd, path), size });
1723
1816
  }
1724
1817
  }
1725
- await Promise.all(writes);
1818
+ await Promise.all([...writes, ...primaryWrites]);
1726
1819
  return written;
1727
1820
  }
1728
1821
  function virtualLoaderPlugin(fileContents) {
@@ -1741,10 +1834,7 @@ function virtualLoaderPlugin(fileContents) {
1741
1834
  return { external: true };
1742
1835
  }
1743
1836
  const resolved = resolve2(args.resolveDir, args.path);
1744
- if (fileContents.has(resolved)) {
1745
- return { path: resolved, namespace };
1746
- }
1747
- return { external: true };
1837
+ return fileContents.has(resolved) ? { path: resolved, namespace } : { external: true };
1748
1838
  });
1749
1839
  build.onLoad({ filter: /.*/, namespace }, (args) => {
1750
1840
  const contents = fileContents.get(args.path);
@@ -1918,9 +2008,6 @@ _PerformanceLogger = __decorateElement(_init, 0, "PerformanceLogger", _Performan
1918
2008
  __runInitializers(_init, 1, _PerformanceLogger);
1919
2009
  var PerformanceLogger = _PerformanceLogger;
1920
2010
  var measure = new PerformanceLogger().measure;
1921
- function addPerformanceStep(name, ms) {
1922
- pendingSteps.push({ name, duration: `${ms}ms`, ms });
1923
- }
1924
2011
 
1925
2012
  // src/decorators/debounce.ts
1926
2013
  var _DebounceManager_decorators, _init2;
@@ -2053,20 +2140,34 @@ var FileManager = class {
2053
2140
  */
2054
2141
  finalize() {
2055
2142
  this.processEmittedFiles();
2143
+ return this.cache === void 0 || this.hasEmittedFiles;
2144
+ }
2145
+ /**
2146
+ * Persists the .tsbuildinfo file and the dts cache to disk in the background. Call this
2147
+ * AFTER the build's parallel phases (transpile + dts bundling) have completed so the writes
2148
+ * (and Brotli compression for the dts cache) don't compete with esbuild for libuv threadpool
2149
+ * slots.
2150
+ */
2151
+ persistCache() {
2056
2152
  const tasks = [];
2057
2153
  if (this.pendingBuildInfo) {
2058
2154
  tasks.push(Files.write(this.pendingBuildInfo.path, this.pendingBuildInfo.text));
2155
+ this.pendingBuildInfo = void 0;
2059
2156
  }
2060
- this.pendingBuildInfo = void 0;
2061
- if (this.cache && this.hasEmittedFiles) {
2157
+ if (this.cache !== void 0 && this.hasEmittedFiles) {
2062
2158
  tasks.push(this.cache.save(this.declarationFiles));
2063
2159
  }
2064
- this.pendingSave = Promise.all(tasks).then(() => {
2160
+ if (tasks.length === 0) {
2161
+ return;
2162
+ }
2163
+ const save = tasks.length === 1 ? tasks[0].then(() => {
2164
+ }, () => {
2165
+ }) : Promise.all(tasks).then(() => {
2065
2166
  }, () => {
2066
2167
  });
2067
- this.pendingSave?.catch(() => {
2168
+ this.pendingSave = this.pendingSave === void 0 ? save : Promise.all([this.pendingSave, save]).then(() => {
2169
+ }, () => {
2068
2170
  });
2069
- return this.cache === void 0 || this.hasEmittedFiles;
2070
2171
  }
2071
2172
  /**
2072
2173
  * Retrieves all stored declaration files.
@@ -2195,13 +2296,21 @@ var FileManager = class {
2195
2296
  };
2196
2297
 
2197
2298
  // src/incremental-build-cache.ts
2198
- import { rmSync } from "node:fs";
2199
- var IncrementalBuildCache = class {
2299
+ import { existsSync, readFileSync, rmSync } from "node:fs";
2300
+ import { mkdir as mkdir4, writeFile as writeFile4 } from "node:fs/promises";
2301
+ var IncrementalBuildCache = class _IncrementalBuildCache {
2200
2302
  buildInfoPath;
2201
2303
  cacheDirectoryPath;
2202
2304
  cacheFilePath;
2305
+ outputsManifestPath;
2203
2306
  /** Pre-loading promise started in constructor for async cache restoration */
2204
2307
  cacheLoaded;
2308
+ /**
2309
+ * Manifest snapshot captured synchronously at construction. Held in memory so it survives
2310
+ * `invalidate()` (which deletes the on-disk manifest as part of clearing `.tsbuild`) and so
2311
+ * subsequent in-process reads are race-free.
2312
+ */
2313
+ outputsSnapshot;
2205
2314
  /** Set to true when invalidate() is called to prevent stale cache from being restored */
2206
2315
  invalidated = false;
2207
2316
  /**
@@ -2213,7 +2322,9 @@ var IncrementalBuildCache = class {
2213
2322
  this.buildInfoPath = Paths.join(projectRoot, tsBuildInfoFile);
2214
2323
  this.cacheDirectoryPath = Paths.join(projectRoot, cacheDirectory);
2215
2324
  this.cacheFilePath = Paths.join(this.cacheDirectoryPath, dtsCacheFile);
2325
+ this.outputsManifestPath = Paths.join(this.cacheDirectoryPath, outputManifestFile);
2216
2326
  this.cacheLoaded = this.loadCache();
2327
+ this.outputsSnapshot = _IncrementalBuildCache.loadOutputsSync(this.outputsManifestPath);
2217
2328
  }
2218
2329
  /**
2219
2330
  * Loads the cache file asynchronously using V8 deserialization.
@@ -2242,7 +2353,7 @@ var IncrementalBuildCache = class {
2242
2353
  if (cache === void 0) {
2243
2354
  return;
2244
2355
  }
2245
- for (const [fileName, content] of Object.entries(cache.files)) {
2356
+ for (const [fileName, content] of cache.files) {
2246
2357
  target.set(fileName, content);
2247
2358
  }
2248
2359
  }
@@ -2252,7 +2363,39 @@ var IncrementalBuildCache = class {
2252
2363
  * @param source - The declaration files to cache
2253
2364
  */
2254
2365
  async save(source) {
2255
- await Files.writeCompressed(this.cacheFilePath, { version: dtsCacheVersion, files: Object.fromEntries(source) });
2366
+ await Files.writeCompressed(this.cacheFilePath, { version: dtsCacheVersion, files: source });
2367
+ }
2368
+ /**
2369
+ * Loads the previous build's output manifest synchronously.
2370
+ * @param manifestPath - Absolute path to the manifest file
2371
+ * @returns The recorded outputs, or undefined when missing/unreadable/malformed.
2372
+ */
2373
+ static loadOutputsSync(manifestPath) {
2374
+ try {
2375
+ const parsed = JSON.parse(readFileSync(manifestPath, "utf8"));
2376
+ return Array.isArray(parsed) ? parsed : void 0;
2377
+ } catch {
2378
+ return void 0;
2379
+ }
2380
+ }
2381
+ /**
2382
+ * Returns the project-relative output paths recorded by the previous build, or undefined if none.
2383
+ * The snapshot is captured at construction time and survives `invalidate()`.
2384
+ * @returns The recorded outputs from the prior build, or undefined when unavailable.
2385
+ */
2386
+ getPreviousOutputs() {
2387
+ return this.outputsSnapshot;
2388
+ }
2389
+ /**
2390
+ * Persists the project-relative output paths produced by the current build.
2391
+ * Updates the in-memory snapshot immediately so subsequent getPreviousOutputs() calls
2392
+ * (in watch mode) return the freshly written list without re-reading disk.
2393
+ * @param outputs - Project-relative output paths
2394
+ */
2395
+ async saveOutputs(outputs) {
2396
+ this.outputsSnapshot = outputs.slice();
2397
+ await mkdir4(this.cacheDirectoryPath, defaultDirOptions);
2398
+ await writeFile4(this.outputsManifestPath, JSON.stringify(this.outputsSnapshot), "utf8");
2256
2399
  }
2257
2400
  /** Invalidates the build cache by removing the cache directory. */
2258
2401
  invalidate() {
@@ -2277,6 +2420,24 @@ var IncrementalBuildCache = class {
2277
2420
  isValid() {
2278
2421
  return !this.invalidated;
2279
2422
  }
2423
+ /**
2424
+ * Synchronously checks whether persisted incremental state exists on disk.
2425
+ * When the .tsbuildinfo file is missing, the next typecheck will perform a full emit,
2426
+ * making it safe to clean the output directory eagerly in parallel with type checking.
2427
+ * @returns True when the .tsbuildinfo file is present and the cache hasn't been invalidated.
2428
+ */
2429
+ hasPersistedState() {
2430
+ return !this.invalidated && existsSync(this.buildInfoPath);
2431
+ }
2432
+ /**
2433
+ * Synchronously checks whether a manifest snapshot from a prior build is available.
2434
+ * Survives `invalidate()` so the manifest-driven cleanup path can be used on
2435
+ * `--clearCache` and `--force` runs as well.
2436
+ * @returns True when an output manifest snapshot is held in memory.
2437
+ */
2438
+ hasPersistedManifest() {
2439
+ return this.outputsSnapshot !== void 0;
2440
+ }
2280
2441
  /**
2281
2442
  * Custom inspection tag for type.
2282
2443
  * @returns The string 'IncrementalBuildCache'
@@ -2311,8 +2472,7 @@ function outputToSourcePath(outputPath, outDir, sourceDir) {
2311
2472
  const relativePortion = normalizedOutput.slice(normalizedOutDir.length + 1);
2312
2473
  for (const [outExt, srcExt] of outputToSourceExtension) {
2313
2474
  if (relativePortion.endsWith(outExt)) {
2314
- const stem = relativePortion.slice(0, -outExt.length);
2315
- return `./${sourceDir}/${stem}${srcExt}`;
2475
+ return `./${sourceDir}/${relativePortion.slice(0, -outExt.length)}${srcExt}`;
2316
2476
  }
2317
2477
  }
2318
2478
  return void 0;
@@ -2404,7 +2564,7 @@ var domPredicate = (lib) => lib.toUpperCase() === "DOM";
2404
2564
  var tsLogo = TextFormat.bgBlue(TextFormat.bold(TextFormat.whiteBright(" TS ")));
2405
2565
  var diagnosticsHost = { getNewLine: () => sys2.newLine, getCurrentDirectory: sys2.getCurrentDirectory, getCanonicalFileName: (fileName) => fileName };
2406
2566
  var _triggerRebuild_dec, _processDeclarations_dec, _transpile_dec, _typeCheck_dec, _build_dec, _TypeScriptProject_decorators, _init3;
2407
- _TypeScriptProject_decorators = [closeOnExit], _build_dec = [measure("Build")], _typeCheck_dec = [measure("Type-checking")], _transpile_dec = [measure("Transpile", true)], _processDeclarations_dec = [measure("Bundle Declarations", true)], _triggerRebuild_dec = [debounce(100)];
2567
+ _TypeScriptProject_decorators = [closeOnExit], _build_dec = [measure("Build")], _typeCheck_dec = [measure("Type-checking/Emit", true)], _transpile_dec = [measure("Transpile", true)], _processDeclarations_dec = [measure("Bundle Declarations", true)], _triggerRebuild_dec = [debounce(100)];
2408
2568
  var _TypeScriptProject = class _TypeScriptProject {
2409
2569
  /**
2410
2570
  * Creates a TypeScript project and prepares it for building/bundling.
@@ -2421,6 +2581,9 @@ var _TypeScriptProject = class _TypeScriptProject {
2421
2581
  __publicField(this, "buildConfiguration");
2422
2582
  __publicField(this, "pendingChanges", []);
2423
2583
  __publicField(this, "buildDependencies", /* @__PURE__ */ new Set());
2584
+ __publicField(this, "pendingStaleOutputsCleanup");
2585
+ /** Identity of the Program that populated buildDependencies — skip re-walking when unchanged */
2586
+ __publicField(this, "buildDependenciesProgram");
2424
2587
  __publicField(this, "dependencyPaths");
2425
2588
  this.directory = Paths.absolute(directory);
2426
2589
  this.configuration = _TypeScriptProject.resolveConfiguration(this.directory, options);
@@ -2431,6 +2594,10 @@ var _TypeScriptProject = class _TypeScriptProject {
2431
2594
  this.fileManager = new FileManager(buildCache);
2432
2595
  this.builderProgram = createIncrementalProgram({ rootNames, options: this.configuration.compilerOptions, projectReferences, configFileParsingDiagnostics });
2433
2596
  this.buildConfiguration = { entryPoints: this.getEntryPoints(entryPoints), target: toEsTarget(target), outDir, ...tsbuildOptions };
2597
+ this.dependencyPaths = Files.read(Paths.absolute(this.directory, "package.json")).then((content) => {
2598
+ const { dependencies = {}, peerDependencies = {} } = Json.parse(content);
2599
+ return [.../* @__PURE__ */ new Set([...Object.keys(dependencies), ...Object.keys(peerDependencies)])];
2600
+ }).catch(() => []);
2434
2601
  }
2435
2602
  /**
2436
2603
  * Cleans the output directory
@@ -2439,13 +2606,67 @@ var _TypeScriptProject = class _TypeScriptProject {
2439
2606
  async clean() {
2440
2607
  return Files.empty(this.buildConfiguration.outDir);
2441
2608
  }
2609
+ /**
2610
+ * Removes outputs that the previous build wrote but the current build did not — e.g. renamed
2611
+ * entry points or content-hashed chunks whose hash changed. Restricted to files under outDir
2612
+ * for safety. Fire-and-forget: scheduled after build completion so it never inflates timings.
2613
+ * @param previous - Project-relative paths recorded by the previous build (or undefined)
2614
+ * @param current - Project-relative paths produced by the current build
2615
+ */
2616
+ cleanupStaleOutputs(previous, current) {
2617
+ if (previous === void 0 || previous.length === 0) {
2618
+ return;
2619
+ }
2620
+ const currentSet = new Set(current);
2621
+ const outDirRel = Paths.relative(this.directory, this.buildConfiguration.outDir);
2622
+ const prefix = `${outDirRel}/`;
2623
+ const stale = [];
2624
+ for (const path of previous) {
2625
+ if (currentSet.has(path)) {
2626
+ continue;
2627
+ }
2628
+ if (path !== outDirRel && !path.startsWith(prefix)) {
2629
+ continue;
2630
+ }
2631
+ stale.push(Paths.absolute(this.directory, path));
2632
+ }
2633
+ if (stale.length === 0) {
2634
+ return;
2635
+ }
2636
+ const removals = new Array(stale.length);
2637
+ for (let i = 0, length = stale.length; i < length; i++) {
2638
+ removals[i] = rm2(stale[i], { force: true });
2639
+ }
2640
+ const cleanup = Promise.all(removals).then(() => void 0).catch(() => void 0).finally(() => {
2641
+ if (this.pendingStaleOutputsCleanup === cleanup) {
2642
+ this.pendingStaleOutputsCleanup = void 0;
2643
+ }
2644
+ });
2645
+ this.pendingStaleOutputsCleanup = cleanup;
2646
+ }
2647
+ /**
2648
+ * Waits for fire-and-forget stale-output cleanup to settle.
2649
+ * Useful for deterministic tests that need to assert post-cleanup filesystem state.
2650
+ */
2651
+ async flushBackgroundCleanup() {
2652
+ await this.pendingStaleOutputsCleanup;
2653
+ }
2442
2654
  async build() {
2443
- Logger.header(`${tsLogo} tsbuild v${"1.8.6"}${this.configuration.compilerOptions.incremental && this.configuration.buildCache?.isValid() ? " [incremental]" : ""}`);
2655
+ Logger.header(`${tsLogo} tsbuild v${"1.8.7"}${this.configuration.compilerOptions.incremental && this.configuration.buildCache?.isValid() ? " [incremental]" : ""}`);
2444
2656
  try {
2445
2657
  const processes = [];
2658
+ const buildCache = this.configuration.buildCache;
2659
+ const force = this.configuration.tsbuild.force;
2660
+ const cleanEnabled = this.configuration.clean && !this.configuration.compilerOptions.noEmit;
2661
+ const useManifest = cleanEnabled && buildCache !== void 0 && buildCache.hasPersistedManifest();
2662
+ const previousOutputs = useManifest ? buildCache.getPreviousOutputs() : void 0;
2663
+ const willEmit = force || buildCache?.hasPersistedState() !== true;
2664
+ const eagerCleanPromise = cleanEnabled && willEmit && !useManifest ? this.clean() : void 0;
2446
2665
  const filesWereEmitted = await this.typeCheck();
2447
- if ((filesWereEmitted || this.configuration.tsbuild.force) && !this.configuration.compilerOptions.noEmit) {
2448
- if (this.configuration.clean) {
2666
+ if ((filesWereEmitted || force) && !this.configuration.compilerOptions.noEmit) {
2667
+ if (eagerCleanPromise !== void 0) {
2668
+ await eagerCleanPromise;
2669
+ } else if (cleanEnabled && !useManifest) {
2449
2670
  await this.clean();
2450
2671
  }
2451
2672
  if (this.configuration.compilerOptions.declaration) {
@@ -2454,20 +2675,41 @@ var _TypeScriptProject = class _TypeScriptProject {
2454
2675
  if (!this.configuration.compilerOptions.emitDeclarationOnly) {
2455
2676
  processes.push(this.transpile());
2456
2677
  }
2678
+ } else if (eagerCleanPromise !== void 0) {
2679
+ await eagerCleanPromise;
2457
2680
  }
2458
- for (const result of await Promise.allSettled(processes)) {
2681
+ const settled = await Promise.allSettled(processes);
2682
+ const newOutputs = [];
2683
+ for (const result of settled) {
2459
2684
  if (result.status === "rejected") {
2460
2685
  this.handleBuildError(result.reason);
2686
+ continue;
2687
+ }
2688
+ for (const { path } of result.value) {
2689
+ newOutputs.push(path);
2461
2690
  }
2462
2691
  }
2692
+ this.fileManager.persistCache();
2693
+ if (buildCache !== void 0 && newOutputs.length > 0) {
2694
+ if (previousOutputs !== void 0) {
2695
+ this.cleanupStaleOutputs(previousOutputs, newOutputs);
2696
+ }
2697
+ void buildCache.saveOutputs(newOutputs).catch(() => {
2698
+ });
2699
+ }
2463
2700
  } catch (error) {
2464
2701
  this.handleBuildError(error);
2465
2702
  } finally {
2466
2703
  if (this.buildConfiguration.watch.enabled) {
2467
- this.buildDependencies.clear();
2468
- for (const { isDeclarationFile, fileName } of this.builderProgram.getProgram().getSourceFiles()) {
2469
- if (!isDeclarationFile) {
2470
- this.buildDependencies.add(Paths.relative(this.directory, fileName));
2704
+ const program = this.builderProgram.getProgram();
2705
+ if (this.buildDependenciesProgram !== program) {
2706
+ this.buildDependenciesProgram = program;
2707
+ this.buildDependencies.clear();
2708
+ const dirWithSlash = this.directory + "/";
2709
+ for (const { isDeclarationFile, fileName } of program.getSourceFiles()) {
2710
+ if (!isDeclarationFile && fileName.startsWith(dirWithSlash)) {
2711
+ this.buildDependencies.add(Paths.relative(this.directory, fileName));
2712
+ }
2471
2713
  }
2472
2714
  }
2473
2715
  if (this.fileWatcher === void 0 || this.fileWatcher.isClosed()) {
@@ -2489,35 +2731,31 @@ var _TypeScriptProject = class _TypeScriptProject {
2489
2731
  ...this.builderProgram.getSemanticDiagnostics(),
2490
2732
  ...this.configuration.compilerOptions.declaration ? this.builderProgram.getDeclarationDiagnostics() : []
2491
2733
  ];
2492
- addPerformanceStep("Diagnostics", _TypeScriptProject.elapsed("diagnostics:start"));
2493
- performance2.mark("emit:start");
2494
2734
  this.builderProgram.emit(void 0, this.fileManager.fileWriter, void 0, true);
2495
- addPerformanceStep("Emit", _TypeScriptProject.elapsed("emit:start"));
2496
2735
  } else {
2497
- performance2.mark("emit:start");
2498
2736
  const { diagnostics } = this.builderProgram.emit(void 0, this.fileManager.fileWriter, void 0, true);
2499
- addPerformanceStep("Emit", _TypeScriptProject.elapsed("emit:start"));
2500
- performance2.mark("diagnostics:start");
2501
2737
  allDiagnostics = [...this.builderProgram.getSemanticDiagnostics(), ...diagnostics];
2502
- addPerformanceStep("Diagnostics", _TypeScriptProject.elapsed("diagnostics:start"));
2503
2738
  }
2504
2739
  if (allDiagnostics.length > 0) {
2505
2740
  _TypeScriptProject.handleTypeErrors("Type-checking failed", allDiagnostics, this.directory);
2506
2741
  }
2507
- performance2.mark("finalize:start");
2508
- const emitted = this.fileManager.finalize();
2509
- addPerformanceStep("Finalize", _TypeScriptProject.elapsed("finalize:start"));
2510
- return emitted || !this.configuration.compilerOptions.declaration;
2742
+ return this.fileManager.finalize() || !this.configuration.compilerOptions.declaration;
2511
2743
  }
2512
2744
  async transpile() {
2513
2745
  const { build: esbuild, formatMessages } = await import("esbuild");
2514
- const plugins = [outputPlugin()];
2746
+ const plugins = [];
2747
+ let iife;
2748
+ if (this.buildConfiguration.iife) {
2749
+ iife = iifePlugin(this.buildConfiguration.iife === true ? void 0 : this.buildConfiguration.iife);
2750
+ plugins.push(iife.plugin);
2751
+ }
2752
+ plugins.push(outputPlugin());
2515
2753
  if (this.buildConfiguration.noExternal.length > 0) {
2516
2754
  plugins.push(externalModulesPlugin({ dependencies: await this.getProjectDependencyPaths(), noExternal: this.buildConfiguration.noExternal }));
2517
2755
  }
2518
2756
  if (this.configuration.compilerOptions.emitDecoratorMetadata) {
2519
2757
  try {
2520
- const { swcDecoratorMetadataPlugin } = await import("./ZEGDBXXN.js");
2758
+ const { swcDecoratorMetadataPlugin } = await import("./5DXLYKZU.js");
2521
2759
  plugins.push(swcDecoratorMetadataPlugin);
2522
2760
  } catch {
2523
2761
  throw new ConfigurationError("emitDecoratorMetadata is enabled but @swc/core is not installed. Install it with: pnpm add -D @swc/core");
@@ -2526,11 +2764,6 @@ var _TypeScriptProject = class _TypeScriptProject {
2526
2764
  if (this.buildConfiguration.plugins?.length) {
2527
2765
  plugins.push(...await resolvePlugins(this.buildConfiguration.plugins, this.directory));
2528
2766
  }
2529
- let iife;
2530
- if (this.buildConfiguration.iife) {
2531
- iife = iifePlugin(this.buildConfiguration.iife === true ? void 0 : this.buildConfiguration.iife);
2532
- plugins.push(iife.plugin);
2533
- }
2534
2767
  const define = {};
2535
2768
  if (this.buildConfiguration.env !== void 0) {
2536
2769
  const envExpansion = new RegExp(processEnvExpansionPattern, "g");
@@ -2626,7 +2859,9 @@ var _TypeScriptProject = class _TypeScriptProject {
2626
2859
  close() {
2627
2860
  this.fileWatcher?.close();
2628
2861
  this.fileManager.close();
2862
+ this.pendingStaleOutputsCleanup = void 0;
2629
2863
  this.buildDependencies.clear();
2864
+ this.buildDependenciesProgram = void 0;
2630
2865
  this.pendingChanges.length = 0;
2631
2866
  }
2632
2867
  async processDeclarations() {
@@ -2647,7 +2882,9 @@ var _TypeScriptProject = class _TypeScriptProject {
2647
2882
  rootDir: this.configuration.compilerOptions.rootDir,
2648
2883
  outDir: this.configuration.compilerOptions.outDir,
2649
2884
  moduleResolution: this.configuration.compilerOptions.moduleResolution
2650
- }
2885
+ },
2886
+ // Only yield to event loop if transpile is running in parallel
2887
+ parallelTranspile: !this.configuration.compilerOptions.emitDeclarationOnly
2651
2888
  });
2652
2889
  }
2653
2890
  async triggerRebuild() {
@@ -2675,6 +2912,7 @@ var _TypeScriptProject = class _TypeScriptProject {
2675
2912
  }
2676
2913
  }
2677
2914
  this.pendingChanges.length = 0;
2915
+ await this.fileManager.flush();
2678
2916
  this.builderProgram = createIncrementalProgram({ rootNames, options: this.configuration.compilerOptions, projectReferences: this.configuration.projectReferences, configFileParsingDiagnostics: this.configuration.configFileParsingDiagnostics });
2679
2917
  await this.build();
2680
2918
  }
@@ -2779,15 +3017,12 @@ var _TypeScriptProject = class _TypeScriptProject {
2779
3017
  return expandedEntryPoints;
2780
3018
  }
2781
3019
  /**
2782
- * Gets the project dependency paths, cached after first call.
2783
- * Reads package.json and caches it for reuse.
3020
+ * Gets the project dependency paths.
3021
+ * The promise is started in the constructor so it overlaps with TS Program creation.
2784
3022
  * @returns A promise that resolves to an array of project dependency paths.
2785
3023
  */
2786
3024
  getProjectDependencyPaths() {
2787
- return this.dependencyPaths ??= Files.read(Paths.absolute(this.directory, "package.json")).then((content) => {
2788
- const { dependencies = {}, peerDependencies = {} } = Json.parse(content);
2789
- return [.../* @__PURE__ */ new Set([...Object.keys(dependencies), ...Object.keys(peerDependencies)])];
2790
- });
3025
+ return this.dependencyPaths;
2791
3026
  }
2792
3027
  /**
2793
3028
  * Handles build errors by logging unexpected errors and setting appropriate exit codes.