@d1g1tal/tsbuild 1.2.3 → 1.3.0
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.
- package/README.md +134 -33
- package/dist/{PWB3B4C6.js → 3HKDLNVH.js} +117 -25
- package/dist/{DSHNVGWV.js → LEZQQWX3.js} +1 -1
- package/dist/index.d.ts +12 -1
- package/dist/index.js +1 -1
- package/dist/tsbuild.js +2 -2
- package/package.json +2 -6
package/README.md
CHANGED
|
@@ -8,9 +8,11 @@
|
|
|
8
8
|
[](https://nodejs.org)
|
|
9
9
|
[](https://www.typescriptlang.org/)
|
|
10
10
|
|
|
11
|
-
A
|
|
11
|
+
A TypeScript build tool that combines three tools into one workflow: **TypeScript's type system** for correctness, **esbuild** for speed, and **SWC** for legacy decorator metadata (optional, not installed by default). Built for modern ESM-only projects on Node.js 20.16.0+.
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
TC39 standard decorators are supported natively — no additional dependencies needed. SWC is only required if you are still using `experimentalDecorators` with `emitDecoratorMetadata`.
|
|
14
|
+
|
|
15
|
+
> **Note:** This is a personal project I built for my own use and decided to share. It works well for me, but it's not battle-hardened for every setup. If you need something production-proven, [tsup](https://tsup.egoist.dev/) is excellent, or take a look at the newer [tsdown](https://tsdown.dev/) by [void(0)](https://voidzero.dev/).
|
|
14
16
|
|
|
15
17
|
## Features
|
|
16
18
|
|
|
@@ -19,7 +21,8 @@ A self-hosting TypeScript build tool that combines the best of three worlds: **T
|
|
|
19
21
|
- 📦 **Declaration Bundling** - Automatically bundles `.d.ts` files into single entry points
|
|
20
22
|
- ⚡ **Incremental Builds** - Intelligent caching with `.tsbuildinfo` for fast rebuilds
|
|
21
23
|
- 👁️ **Watch Mode** - File watching with automatic rebuilds on changes
|
|
22
|
-
- 🎨 **
|
|
24
|
+
- 🎨 **TC39 Decorators** - Native support for standard decorators, no extra dependencies required
|
|
25
|
+
- 🔧 **Legacy Decorator Metadata** - Optional SWC integration for `emitDecoratorMetadata` when using `experimentalDecorators` (install `@swc/core` separately)
|
|
23
26
|
- 🔌 **Plugin System** - Extensible architecture with custom esbuild plugins
|
|
24
27
|
- 🎯 **ESM-Only** - Pure ESM project with no CommonJS support by design
|
|
25
28
|
- 🧹 **Clean Builds** - Optional output directory cleaning before builds
|
|
@@ -28,13 +31,48 @@ A self-hosting TypeScript build tool that combines the best of three worlds: **T
|
|
|
28
31
|
|
|
29
32
|
## Why tsbuild?
|
|
30
33
|
|
|
31
|
-
|
|
34
|
+
Most TypeScript build setups involve a compromise: use `tsc` alone and lose bundling speed, or use esbuild/swc alone and lose accurate type checking and declaration generation. tsbuild aims to give you both by running each tool for what it's actually good at.
|
|
35
|
+
|
|
36
|
+
The build runs in two phases:
|
|
37
|
+
|
|
38
|
+
1. **Type Checking** - TypeScript validates types and, if `declaration` is enabled, captures `.d.ts` files into memory (no disk I/O)
|
|
39
|
+
2. **Output** - Once type checking completes, two things happen in parallel:
|
|
40
|
+
- esbuild transpiles and bundles the JavaScript
|
|
41
|
+
- If declarations were captured in phase 1, a custom bundler consolidates the `.d.ts` files into final entry points
|
|
42
|
+
|
|
43
|
+
If `declaration` is not enabled, phase 2 is just the esbuild step.
|
|
44
|
+
|
|
45
|
+
## Quick Start
|
|
46
|
+
|
|
47
|
+
The only thing tsbuild requires in `tsconfig.json` is an `outDir`. Everything else carries over from your existing config:
|
|
48
|
+
|
|
49
|
+
```jsonc
|
|
50
|
+
{
|
|
51
|
+
"compilerOptions": {
|
|
52
|
+
"outDir": "./dist"
|
|
53
|
+
// ... your existing TypeScript config
|
|
54
|
+
},
|
|
55
|
+
"tsbuild": {} // entry points are inferred from package.json automatically
|
|
56
|
+
}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Then, if tsbuild is installed globally:
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
tsbuild
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Or if installed locally as a dev dependency, add a script to `package.json` and run it:
|
|
32
66
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
67
|
+
```json
|
|
68
|
+
{ "scripts": { "build": "tsbuild" } }
|
|
69
|
+
```
|
|
36
70
|
|
|
37
|
-
|
|
71
|
+
```bash
|
|
72
|
+
pnpm build
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
That's it. tsbuild reads your `compilerOptions`, infers entry points from your `package.json`, and builds. See [Configuration Options](#configuration-options) for everything you can customise.
|
|
38
76
|
|
|
39
77
|
## Installation
|
|
40
78
|
|
|
@@ -57,10 +95,7 @@ With a global install, your projects can use `tsbuild` in `package.json` scripts
|
|
|
57
95
|
Install as a dev dependency for per-project version pinning (recommended for CI/CD environments):
|
|
58
96
|
|
|
59
97
|
```bash
|
|
60
|
-
# pnpm
|
|
61
|
-
pnpm add -D @d1g1tal/tsbuild --no-optional
|
|
62
|
-
|
|
63
|
-
# pnpm - with SWC dependency (For legacy decorator metadata)
|
|
98
|
+
# pnpm
|
|
64
99
|
pnpm add -D @d1g1tal/tsbuild
|
|
65
100
|
|
|
66
101
|
# npm
|
|
@@ -70,6 +105,8 @@ npm install -D @d1g1tal/tsbuild
|
|
|
70
105
|
yarn add -D @d1g1tal/tsbuild
|
|
71
106
|
```
|
|
72
107
|
|
|
108
|
+
`@swc/core` is **not a dependency** and will never be installed automatically. It is only needed if you use `experimentalDecorators` with `emitDecoratorMetadata` — see [Decorator Metadata](#decorator-metadata) for details.
|
|
109
|
+
|
|
73
110
|
> **Note:** When installed only as a local dev dependency, the `tsbuild` command is not available directly in your terminal. Use it through `package.json` scripts (e.g., `pnpm build`) or invoke it explicitly with `pnpm exec tsbuild` / `npx tsbuild`.
|
|
74
111
|
|
|
75
112
|
### Requirements
|
|
@@ -81,7 +118,22 @@ yarn add -D @d1g1tal/tsbuild
|
|
|
81
118
|
|
|
82
119
|
### Configuration
|
|
83
120
|
|
|
84
|
-
|
|
121
|
+
#### Your tsconfig.json Does the Heavy Lifting
|
|
122
|
+
|
|
123
|
+
Because tsbuild uses the TypeScript compiler API directly, it reads your `compilerOptions` automatically. There is no need to re-declare `target`, `module`, `lib`, `strict`, `paths`, `moduleResolution`, `baseUrl`, or any other TypeScript settings in a separate config — they are already in your `tsconfig.json`, and tsbuild honours them as-is.
|
|
124
|
+
|
|
125
|
+
The `tsbuild` section only covers options that don't belong in `compilerOptions`: bundling behaviour, entry points, watch mode, output formatting, and similar build-specific settings.
|
|
126
|
+
|
|
127
|
+
This means your type-checker and your build always use the exact same TypeScript configuration — no drift, no duplication.
|
|
128
|
+
|
|
129
|
+
The only `compilerOptions` setting tsbuild requires:
|
|
130
|
+
- **`outDir`** — determines where built files are written
|
|
131
|
+
|
|
132
|
+
Declaration generation is **not required**. If `declaration: true` is already set in your `tsconfig.json`, tsbuild will automatically generate and bundle `.d.ts` files. If it's not set, tsbuild skips that step — no changes needed either way.
|
|
133
|
+
|
|
134
|
+
Everything else carries over automatically.
|
|
135
|
+
|
|
136
|
+
Add a `tsbuild` property to your `tsconfig.json` with only the options you need to customise:
|
|
85
137
|
|
|
86
138
|
```jsonc
|
|
87
139
|
{
|
|
@@ -92,13 +144,13 @@ Add a `tsbuild` property to your `tsconfig.json`:
|
|
|
92
144
|
"target": "ESNext",
|
|
93
145
|
"module": "ESNext",
|
|
94
146
|
"lib": [ "ESNext", "DOM" ],
|
|
95
|
-
"outDir": "./dist",
|
|
147
|
+
"outDir": "./dist",
|
|
96
148
|
// ... other TypeScript options
|
|
97
149
|
},
|
|
98
150
|
"tsbuild": {
|
|
99
151
|
"clean": true, // Remove all files from output directory before building (default: true)
|
|
100
152
|
"platform": "node", // Will default to "browser" if "DOM" is found in "lib", otherwise "node"
|
|
101
|
-
"entryPoints": {
|
|
153
|
+
"entryPoints": { // Optional - tsbuild can infer entry points from package.json if not provided
|
|
102
154
|
"cli": "./src/cli.ts",
|
|
103
155
|
"index": "./src/index.ts"
|
|
104
156
|
},
|
|
@@ -111,6 +163,8 @@ Add a `tsbuild` property to your `tsconfig.json`:
|
|
|
111
163
|
|
|
112
164
|
### CLI Commands
|
|
113
165
|
|
|
166
|
+
The examples below use the bare `tsbuild` command, which works when tsbuild is installed globally. If it's installed locally as a dev dependency, run these through `package.json` scripts (`pnpm build`, etc.) or prefix with `pnpm exec`/`npx` (e.g., `pnpm exec tsbuild --watch`).
|
|
167
|
+
|
|
114
168
|
```bash
|
|
115
169
|
# Build once
|
|
116
170
|
tsbuild
|
|
@@ -155,6 +209,39 @@ tsbuild --version # or -v
|
|
|
155
209
|
}
|
|
156
210
|
```
|
|
157
211
|
|
|
212
|
+
## Incremental Builds
|
|
213
|
+
|
|
214
|
+
tsbuild uses two separate caches to speed up repeated builds, and two flags to control them.
|
|
215
|
+
|
|
216
|
+
### How it works
|
|
217
|
+
|
|
218
|
+
Enable incremental compilation in `tsconfig.json`:
|
|
219
|
+
|
|
220
|
+
```jsonc
|
|
221
|
+
{
|
|
222
|
+
"compilerOptions": {
|
|
223
|
+
"incremental": true
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
With this set, each build maintains two caches inside a `.tsbuild/` directory:
|
|
229
|
+
|
|
230
|
+
| Cache | File | What it stores |
|
|
231
|
+
|-------|------|----------------|
|
|
232
|
+
| TypeScript | `.tsbuild/.tsbuildinfo` | Which source files changed and their type information |
|
|
233
|
+
| DTS cache | `.tsbuild/dts_cache.v8.br` | Pre-processed declaration files (Brotli-compressed) |
|
|
234
|
+
|
|
235
|
+
On each build, TypeScript reads `.tsbuildinfo` to determine what changed and only re-emits those files. Changed `.d.ts` files overwrite their entries in the DTS cache; unchanged entries remain valid. If nothing changed, TypeScript skips emission entirely and the output phase is skipped too — this is why incremental rebuilds with no changes take ~5ms.
|
|
236
|
+
|
|
237
|
+
### Flags
|
|
238
|
+
|
|
239
|
+
**`--force` (`-f`)** — Runs the output phase (esbuild + DTS bundling) even when TypeScript detects no changes. Useful when something outside the source files changed (e.g. an environment variable or esbuild config) and you need to regenerate output without touching the caches.
|
|
240
|
+
|
|
241
|
+
**`--clearCache` (`-c`)** — Deletes the entire `.tsbuild/` directory before building, wiping both `.tsbuildinfo` and the DTS cache. The next build runs as if it's the first time. Use this when you suspect the cache is stale or after significant config changes.
|
|
242
|
+
|
|
243
|
+
**Normal build (no flags)** — TypeScript compares source file hashes against `.tsbuildinfo`, re-emits only what changed, and the DTS cache is updated accordingly.
|
|
244
|
+
|
|
158
245
|
## Configuration Options
|
|
159
246
|
|
|
160
247
|
tsbuild supports a comprehensive set of options (full schema available in [`schema.json`](./schema.json)):
|
|
@@ -255,29 +342,46 @@ By default, bare specifiers (e.g., `lodash`) are treated as external when `platf
|
|
|
255
342
|
}
|
|
256
343
|
```
|
|
257
344
|
|
|
258
|
-
**Note:**
|
|
345
|
+
**Note:** All `compilerOptions` (including `target`, `outDir`, `module`, `strict`, `paths`, etc.) come from `tsconfig.json` and are not duplicated in the `tsbuild` section. The `force` and `minify` options are generally more useful as CLI flags (`--force`, `--minify`) than as persistent config values.
|
|
259
346
|
|
|
260
347
|
## Advanced Features
|
|
261
348
|
|
|
262
349
|
### Decorator Metadata
|
|
263
350
|
|
|
264
|
-
|
|
351
|
+
#### TC39 Standard Decorators (recommended)
|
|
352
|
+
|
|
353
|
+
Standard decorators work out of the box — just use them in your code. No configuration, no extra packages.
|
|
265
354
|
|
|
266
355
|
```jsonc
|
|
267
356
|
{
|
|
268
357
|
"compilerOptions": {
|
|
269
|
-
"
|
|
270
|
-
|
|
358
|
+
"target": "ESNext"
|
|
359
|
+
// No experimentalDecorators needed
|
|
271
360
|
}
|
|
272
361
|
}
|
|
273
362
|
```
|
|
274
363
|
|
|
275
|
-
|
|
364
|
+
#### Legacy Decorators with Metadata (`experimentalDecorators`)
|
|
365
|
+
|
|
366
|
+
If you are using the older decorator proposal with `emitDecoratorMetadata`, tsbuild delegates the transform to SWC so that metadata is emitted correctly through the esbuild pipeline. This requires `@swc/core` to be installed manually — it is **not** included with tsbuild:
|
|
276
367
|
|
|
277
368
|
```bash
|
|
278
369
|
pnpm add -D @swc/core
|
|
279
370
|
```
|
|
280
371
|
|
|
372
|
+
Then enable the flags in `tsconfig.json`:
|
|
373
|
+
|
|
374
|
+
```jsonc
|
|
375
|
+
{
|
|
376
|
+
"compilerOptions": {
|
|
377
|
+
"experimentalDecorators": true,
|
|
378
|
+
"emitDecoratorMetadata": true
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
tsbuild detects these flags automatically and uses SWC. If `@swc/core` is not installed when these flags are set, the build will fail with a clear message telling you to install it.
|
|
384
|
+
|
|
281
385
|
### Custom Plugins
|
|
282
386
|
|
|
283
387
|
tsbuild supports custom esbuild plugins:
|
|
@@ -331,7 +435,7 @@ The declaration bundling system (`src/dts/declaration-bundler.ts`) is a custom i
|
|
|
331
435
|
|
|
332
436
|
This custom bundler works entirely with in-memory declaration files, avoiding the overhead of duplicate TypeScript Program creation with some other bundlers.
|
|
333
437
|
|
|
334
|
-
When a circular dependency is detected between declaration files, tsbuild emits a warning with the full cycle path (e.g., `a.d.ts -> b.d.ts -> a.d.ts`) rather than failing silently or crashing.
|
|
438
|
+
When a circular dependency is detected between declaration files, tsbuild emits a warning with the full cycle path (e.g., `a.d.ts -> b.d.ts -> a.d.ts`) and continues rather than failing silently or crashing.
|
|
335
439
|
|
|
336
440
|
## Performance
|
|
337
441
|
|
|
@@ -339,7 +443,7 @@ tsbuild is designed for speed:
|
|
|
339
443
|
|
|
340
444
|
- **Incremental builds** - Only recompiles changed files
|
|
341
445
|
- **In-memory declarations** - No intermediate disk I/O for `.d.ts` files
|
|
342
|
-
- **Parallel processing** -
|
|
446
|
+
- **Parallel processing** - Declaration bundling and transpilation run in parallel after type checking completes
|
|
343
447
|
- **Smart caching** - Leverages `.tsbuildinfo` for TypeScript incremental compilation
|
|
344
448
|
|
|
345
449
|
Typical build times for the tsbuild project itself:
|
|
@@ -367,9 +471,9 @@ The TypeScript declaration bundling system was originally inspired by rollup-plu
|
|
|
367
471
|
## Limitations
|
|
368
472
|
|
|
369
473
|
- **ESM Only** - No CommonJS support by design
|
|
370
|
-
- **Node.js 20.16.0+** - Requires modern Node.js
|
|
371
|
-
- **
|
|
372
|
-
- **
|
|
474
|
+
- **Node.js 20.16.0+** - Requires a modern Node.js version
|
|
475
|
+
- **Personal project** - Works well for my use cases, but hasn't been tested across every environment or edge case
|
|
476
|
+
- **Plugins are programmatic only** - Custom esbuild plugins can't be declared in `tsconfig.json`; they require using the `TypeScriptProject` API directly
|
|
373
477
|
- **tsBuildInfoFile Path Changes** - When changing the `tsBuildInfoFile` path in `tsconfig.json`, the old `.tsbuildinfo` file at the previous location will not be automatically cleaned up and must be manually removed
|
|
374
478
|
|
|
375
479
|
## Comparison with Other Tools
|
|
@@ -377,9 +481,10 @@ The TypeScript declaration bundling system was originally inspired by rollup-plu
|
|
|
377
481
|
| Feature | tsbuild | tsup | tsc |
|
|
378
482
|
|---------|---------|------|-----|
|
|
379
483
|
| Type Checking | ✅ Full | ✅ Full | ✅ Full |
|
|
380
|
-
|
|
|
484
|
+
| Bundling | ✅ esbuild | ✅ esbuild | ❌ N/A |
|
|
381
485
|
| Declaration Bundling | ✅ Custom Bundler | ✅ rollup-plugin-dts | ❌ N/A |
|
|
382
|
-
|
|
|
486
|
+
| TC39 Decorators | ✅ Native | ✅ Native | ✅ Native |
|
|
487
|
+
| Legacy Decorator Metadata | ✅ SWC (manual install) | ✅ SWC | ✅ Native |
|
|
383
488
|
| CommonJS Support | ❌ None | ✅ Yes | ✅ Yes |
|
|
384
489
|
| Watch Mode | ✅ Yes | ✅ Yes | ✅ Yes |
|
|
385
490
|
| Incremental Builds | ✅ Yes | ⚠️ Limited | ✅ Yes |
|
|
@@ -412,16 +517,12 @@ pnpm lint
|
|
|
412
517
|
|
|
413
518
|
## Contributing
|
|
414
519
|
|
|
415
|
-
This is a personal
|
|
520
|
+
Contributions and feedback are welcome. This is a personal project, so response times may vary, but issues and pull requests will be reviewed.
|
|
416
521
|
|
|
417
522
|
## License
|
|
418
523
|
|
|
419
|
-
|
|
524
|
+
MIT
|
|
420
525
|
|
|
421
526
|
## Author
|
|
422
527
|
|
|
423
528
|
D1g1talEntr0py
|
|
424
|
-
|
|
425
|
-
---
|
|
426
|
-
|
|
427
|
-
**Remember:** For production projects, use [tsup](https://tsup.egoist.dev/) instead. tsbuild is an educational and experimental project exploring how modern build tools can be composed together.
|
|
@@ -340,6 +340,19 @@ var Logger = class _Logger {
|
|
|
340
340
|
const prefix = indent ? " \u2514\u2500" : "\u2713";
|
|
341
341
|
console.log(TextFormat.green(`${prefix} ${message}`));
|
|
342
342
|
}
|
|
343
|
+
/**
|
|
344
|
+
* Logs sub-step timing entries in a tree format below a parent step.
|
|
345
|
+
* @param steps The sub-steps to log.
|
|
346
|
+
*/
|
|
347
|
+
static subSteps(steps) {
|
|
348
|
+
const maxNameLength = steps.reduce((max, { name }) => Math.max(max, name.length), 0);
|
|
349
|
+
const maxDurationLength = steps.reduce((max, { duration }) => Math.max(max, duration.length), 0);
|
|
350
|
+
for (let i = 0, length = steps.length; i < length; i++) {
|
|
351
|
+
const { name, duration } = steps[i];
|
|
352
|
+
const prefix = i === length - 1 ? " \u2514\u2500" : " \u251C\u2500";
|
|
353
|
+
console.log(`${TextFormat.dim(prefix)} ${TextFormat.bold(name.padEnd(maxNameLength))} ${TextFormat.cyan(duration.padStart(maxDurationLength))}`);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
343
356
|
/**
|
|
344
357
|
* Logs a success message.
|
|
345
358
|
* @param message The message to log.
|
|
@@ -1495,13 +1508,14 @@ function closeOnExit(value, _context) {
|
|
|
1495
1508
|
// src/decorators/performance-logger.ts
|
|
1496
1509
|
import { PerformanceObserver, performance } from "perf_hooks";
|
|
1497
1510
|
var type = "measure";
|
|
1511
|
+
var pendingSteps = [];
|
|
1498
1512
|
var _PerformanceLogger_decorators, _init;
|
|
1499
1513
|
_PerformanceLogger_decorators = [closeOnExit];
|
|
1500
1514
|
var _PerformanceLogger = class _PerformanceLogger {
|
|
1501
1515
|
performanceObserver;
|
|
1502
1516
|
constructor() {
|
|
1503
1517
|
this.performanceObserver = new PerformanceObserver((list) => {
|
|
1504
|
-
for (const { name, duration, detail: { message, result = [] } } of list.getEntriesByType(type).reverse()) {
|
|
1518
|
+
for (const { name, duration, detail: { message, result = [], steps } } of list.getEntriesByType(type).reverse()) {
|
|
1505
1519
|
if (message === "Build") {
|
|
1506
1520
|
Logger.separator();
|
|
1507
1521
|
if (process.exitCode) {
|
|
@@ -1513,6 +1527,9 @@ var _PerformanceLogger = class _PerformanceLogger {
|
|
|
1513
1527
|
}
|
|
1514
1528
|
} else {
|
|
1515
1529
|
Logger.step(`${message} ${TextFormat.dim(`(${_PerformanceLogger.formatDuration(duration)})`)}`);
|
|
1530
|
+
if (steps?.length) {
|
|
1531
|
+
Logger.subSteps(steps);
|
|
1532
|
+
}
|
|
1516
1533
|
if (result.length > 0) {
|
|
1517
1534
|
Logger.success("", ...result);
|
|
1518
1535
|
}
|
|
@@ -1533,6 +1550,9 @@ var _PerformanceLogger = class _PerformanceLogger {
|
|
|
1533
1550
|
if (logResult) {
|
|
1534
1551
|
options.detail.result = result;
|
|
1535
1552
|
}
|
|
1553
|
+
if (pendingSteps.length > 0) {
|
|
1554
|
+
options.detail.steps = pendingSteps.splice(0);
|
|
1555
|
+
}
|
|
1536
1556
|
({ startTime: options.end } = performance.mark(propertyKey));
|
|
1537
1557
|
performance.measure(propertyKey, options);
|
|
1538
1558
|
return result;
|
|
@@ -1575,6 +1595,9 @@ _PerformanceLogger = __decorateElement(_init, 0, "PerformanceLogger", _Performan
|
|
|
1575
1595
|
__runInitializers(_init, 1, _PerformanceLogger);
|
|
1576
1596
|
var PerformanceLogger = _PerformanceLogger;
|
|
1577
1597
|
var measure = new PerformanceLogger().measure;
|
|
1598
|
+
function addPerformanceStep(name, duration) {
|
|
1599
|
+
pendingSteps.push({ name, duration });
|
|
1600
|
+
}
|
|
1578
1601
|
|
|
1579
1602
|
// src/decorators/debounce.ts
|
|
1580
1603
|
var _DebounceManager_decorators, _init2;
|
|
@@ -1643,11 +1666,17 @@ function debounce(wait) {
|
|
|
1643
1666
|
}
|
|
1644
1667
|
|
|
1645
1668
|
// src/file-manager.ts
|
|
1646
|
-
import {
|
|
1669
|
+
import { createSourceFile as createSourceFile2, ScriptTarget as ScriptTarget2 } from "typescript";
|
|
1647
1670
|
var FileManager = class {
|
|
1648
1671
|
hasEmittedFiles = false;
|
|
1649
1672
|
declarationFiles = /* @__PURE__ */ new Map();
|
|
1650
1673
|
cache;
|
|
1674
|
+
/** Raw declaration text captured during emit, pending pre-processing */
|
|
1675
|
+
pendingFiles = [];
|
|
1676
|
+
/** Buffered .tsbuildinfo content for async write (avoids sync I/O during emit) */
|
|
1677
|
+
pendingBuildInfo;
|
|
1678
|
+
/** Background cache save promise — awaited in initialize() and close() */
|
|
1679
|
+
pendingSave;
|
|
1651
1680
|
/**
|
|
1652
1681
|
* Creates a new file manager.
|
|
1653
1682
|
* @param buildCache - Optional build cache for incremental builds
|
|
@@ -1668,7 +1697,13 @@ var FileManager = class {
|
|
|
1668
1697
|
* ```
|
|
1669
1698
|
*/
|
|
1670
1699
|
async initialize() {
|
|
1700
|
+
if (this.pendingSave) {
|
|
1701
|
+
await this.pendingSave;
|
|
1702
|
+
this.pendingSave = void 0;
|
|
1703
|
+
}
|
|
1671
1704
|
this.hasEmittedFiles = false;
|
|
1705
|
+
this.pendingFiles.length = 0;
|
|
1706
|
+
this.pendingBuildInfo = void 0;
|
|
1672
1707
|
if (this.cache) {
|
|
1673
1708
|
await this.cache.restore(this.declarationFiles);
|
|
1674
1709
|
} else {
|
|
@@ -1684,14 +1719,22 @@ var FileManager = class {
|
|
|
1684
1719
|
* ```typescript
|
|
1685
1720
|
* await manager.initialize();
|
|
1686
1721
|
* program.emit(undefined, manager.fileWriter, undefined, true);
|
|
1687
|
-
* const hasEmitted =
|
|
1722
|
+
* const hasEmitted = manager.finalize();
|
|
1688
1723
|
* if (hasEmitted) { // Continue with build }
|
|
1689
1724
|
* ```
|
|
1690
1725
|
*/
|
|
1691
|
-
|
|
1726
|
+
finalize() {
|
|
1727
|
+
this.processEmittedFiles();
|
|
1728
|
+
const buildInfoWrite = this.pendingBuildInfo ? Files.write(this.pendingBuildInfo.path, this.pendingBuildInfo.text) : void 0;
|
|
1729
|
+
this.pendingBuildInfo = void 0;
|
|
1692
1730
|
if (this.cache && this.hasEmittedFiles) {
|
|
1693
|
-
|
|
1731
|
+
this.pendingSave = Promise.all([buildInfoWrite, this.cache.save(this.declarationFiles)]).then(() => {
|
|
1732
|
+
});
|
|
1733
|
+
} else if (buildInfoWrite) {
|
|
1734
|
+
this.pendingSave = buildInfoWrite;
|
|
1694
1735
|
}
|
|
1736
|
+
this.pendingSave?.catch(() => {
|
|
1737
|
+
});
|
|
1695
1738
|
return this.cache === void 0 || this.hasEmittedFiles;
|
|
1696
1739
|
}
|
|
1697
1740
|
/**
|
|
@@ -1746,8 +1789,25 @@ var FileManager = class {
|
|
|
1746
1789
|
* Clears all stored declaration files.
|
|
1747
1790
|
*/
|
|
1748
1791
|
close() {
|
|
1792
|
+
this.pendingSave?.then(() => {
|
|
1793
|
+
}, () => {
|
|
1794
|
+
});
|
|
1795
|
+
this.pendingSave = void 0;
|
|
1796
|
+
this.pendingFiles.length = 0;
|
|
1797
|
+
this.pendingBuildInfo = void 0;
|
|
1749
1798
|
this.declarationFiles.clear();
|
|
1750
1799
|
}
|
|
1800
|
+
/**
|
|
1801
|
+
* Awaits any in-flight background I/O (cache save, .tsbuildinfo write).
|
|
1802
|
+
* Call this when you need to guarantee all pending writes have completed,
|
|
1803
|
+
* e.g., before reading the cache file from a different instance.
|
|
1804
|
+
*/
|
|
1805
|
+
async flush() {
|
|
1806
|
+
if (this.pendingSave) {
|
|
1807
|
+
await this.pendingSave;
|
|
1808
|
+
this.pendingSave = void 0;
|
|
1809
|
+
}
|
|
1810
|
+
}
|
|
1751
1811
|
/**
|
|
1752
1812
|
* Writes a single declaration file to disk.
|
|
1753
1813
|
* @param projectDirectory - Project root for calculating relative paths
|
|
@@ -1761,21 +1821,32 @@ var FileManager = class {
|
|
|
1761
1821
|
}
|
|
1762
1822
|
/**
|
|
1763
1823
|
* Function that intercepts file writes during TypeScript emit.
|
|
1764
|
-
*
|
|
1765
|
-
*
|
|
1824
|
+
* Captures raw text for deferred pre-processing and buffers .tsbuildinfo for async I/O.
|
|
1825
|
+
* Designed to be as fast as possible since it runs inside TypeScript's synchronous emit() call.
|
|
1766
1826
|
* @param filePath - The path of the file being written
|
|
1767
1827
|
* @param text - The content of the file being written
|
|
1768
1828
|
*/
|
|
1769
1829
|
fileWriter = (filePath, text) => {
|
|
1770
1830
|
if (this.cache?.isBuildInfoFile(filePath)) {
|
|
1771
|
-
|
|
1831
|
+
this.pendingBuildInfo = { path: filePath, text };
|
|
1772
1832
|
} else {
|
|
1773
|
-
this.
|
|
1833
|
+
this.pendingFiles.push({ path: filePath, text });
|
|
1774
1834
|
}
|
|
1775
1835
|
if (!this.hasEmittedFiles) {
|
|
1776
1836
|
this.hasEmittedFiles = true;
|
|
1777
1837
|
}
|
|
1778
1838
|
};
|
|
1839
|
+
/**
|
|
1840
|
+
* Pre-processes all declaration files captured during emit.
|
|
1841
|
+
* Runs createSourceFile + DeclarationProcessor.preProcess for each pending file,
|
|
1842
|
+
* then clears the pending queue.
|
|
1843
|
+
*/
|
|
1844
|
+
processEmittedFiles() {
|
|
1845
|
+
for (const { path, text } of this.pendingFiles) {
|
|
1846
|
+
this.declarationFiles.set(path, DeclarationProcessor.preProcess(createSourceFile2(path, text, ScriptTarget2.Latest, true)));
|
|
1847
|
+
}
|
|
1848
|
+
this.pendingFiles.length = 0;
|
|
1849
|
+
}
|
|
1779
1850
|
/**
|
|
1780
1851
|
* Custom inspection method for better type representation.
|
|
1781
1852
|
* @returns The string 'FileManager'
|
|
@@ -1971,10 +2042,11 @@ function inferEntryPoints(packageJson, outDir, sourceDir = "src") {
|
|
|
1971
2042
|
|
|
1972
2043
|
// src/type-script-project.ts
|
|
1973
2044
|
import { build as esbuild, formatMessages } from "esbuild";
|
|
1974
|
-
import {
|
|
2045
|
+
import { performance as performance2 } from "node:perf_hooks";
|
|
2046
|
+
import { sys as sys2, createIncrementalProgram, formatDiagnostics, formatDiagnosticsWithColorAndContext, parseJsonConfigFileContent, readConfigFile, findConfigFile } from "typescript";
|
|
1975
2047
|
var globCharacters = /[*?\\[\]!].*$/;
|
|
1976
2048
|
var domPredicate = (lib) => lib.toUpperCase() === "DOM";
|
|
1977
|
-
var diagnosticsHost = { getNewLine: () =>
|
|
2049
|
+
var diagnosticsHost = { getNewLine: () => sys2.newLine, getCurrentDirectory: sys2.getCurrentDirectory, getCanonicalFileName: (fileName) => fileName };
|
|
1978
2050
|
var _triggerRebuild_dec, _processDeclarations_dec, _transpile_dec, _typeCheck_dec, _build_dec, _TypeScriptProject_decorators, _init3;
|
|
1979
2051
|
_TypeScriptProject_decorators = [closeOnExit], _build_dec = [measure("Build")], _typeCheck_dec = [measure("Type-checking")], _transpile_dec = [measure("Transpile", true)], _processDeclarations_dec = [measure("Process Declarations", true)], _triggerRebuild_dec = [debounce(100)];
|
|
1980
2052
|
var _TypeScriptProject = class _TypeScriptProject {
|
|
@@ -1983,7 +2055,7 @@ var _TypeScriptProject = class _TypeScriptProject {
|
|
|
1983
2055
|
* @param directory - Project root directory (defaults to current working directory)
|
|
1984
2056
|
* @param options - Project options to merge with tsconfig.json
|
|
1985
2057
|
*/
|
|
1986
|
-
constructor(directory =
|
|
2058
|
+
constructor(directory = sys2.getCurrentDirectory(), options = {}) {
|
|
1987
2059
|
__runInitializers(_init3, 5, this);
|
|
1988
2060
|
__publicField(this, "fileWatcher");
|
|
1989
2061
|
__publicField(this, "builderProgram");
|
|
@@ -2015,7 +2087,7 @@ var _TypeScriptProject = class _TypeScriptProject {
|
|
|
2015
2087
|
return Files.empty(this.buildConfiguration.outDir);
|
|
2016
2088
|
}
|
|
2017
2089
|
async build() {
|
|
2018
|
-
Logger.header(`\u{1F680} tsbuild v${"1.
|
|
2090
|
+
Logger.header(`\u{1F680} tsbuild v${"1.3.0"}${this.configuration.compilerOptions.incremental ? " [incremental]" : ""}`);
|
|
2019
2091
|
try {
|
|
2020
2092
|
const processes = [];
|
|
2021
2093
|
const filesWereEmitted = await this.typeCheck();
|
|
@@ -2053,11 +2125,19 @@ var _TypeScriptProject = class _TypeScriptProject {
|
|
|
2053
2125
|
}
|
|
2054
2126
|
async typeCheck() {
|
|
2055
2127
|
await this.fileManager.initialize();
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2128
|
+
performance2.mark("emit:start");
|
|
2129
|
+
const { diagnostics: emitDiagnostics } = this.builderProgram.emit(void 0, this.fileManager.fileWriter, void 0, true);
|
|
2130
|
+
addPerformanceStep("Emit", _TypeScriptProject.elapsed("emit:start"));
|
|
2131
|
+
performance2.mark("diagnostics:start");
|
|
2132
|
+
const allDiagnostics = [...this.builderProgram.getSemanticDiagnostics(), ...emitDiagnostics];
|
|
2133
|
+
addPerformanceStep("Diagnostics", _TypeScriptProject.elapsed("diagnostics:start"));
|
|
2134
|
+
if (allDiagnostics.length > 0) {
|
|
2135
|
+
_TypeScriptProject.handleTypeErrors("Type-checking failed", allDiagnostics, this.directory);
|
|
2136
|
+
}
|
|
2137
|
+
performance2.mark("finalize:start");
|
|
2138
|
+
const result = this.fileManager.finalize();
|
|
2139
|
+
addPerformanceStep("Finalize", _TypeScriptProject.elapsed("finalize:start"));
|
|
2140
|
+
return result;
|
|
2061
2141
|
}
|
|
2062
2142
|
async transpile() {
|
|
2063
2143
|
const plugins = [outputPlugin()];
|
|
@@ -2066,7 +2146,7 @@ var _TypeScriptProject = class _TypeScriptProject {
|
|
|
2066
2146
|
}
|
|
2067
2147
|
if (this.configuration.compilerOptions.emitDecoratorMetadata) {
|
|
2068
2148
|
try {
|
|
2069
|
-
const { swcDecoratorMetadataPlugin } = await import("./
|
|
2149
|
+
const { swcDecoratorMetadataPlugin } = await import("./LEZQQWX3.js");
|
|
2070
2150
|
plugins.push(swcDecoratorMetadataPlugin);
|
|
2071
2151
|
} catch {
|
|
2072
2152
|
throw new ConfigurationError("emitDecoratorMetadata is enabled but @swc/core is not installed. Install it with: pnpm add -D @swc/core");
|
|
@@ -2223,7 +2303,7 @@ var _TypeScriptProject = class _TypeScriptProject {
|
|
|
2223
2303
|
* @returns Resolved configuration and TypeScript parser results
|
|
2224
2304
|
*/
|
|
2225
2305
|
static resolveConfiguration(directory, typeScriptOptions) {
|
|
2226
|
-
const configResult = readConfigFile(findConfigFile(directory,
|
|
2306
|
+
const configResult = readConfigFile(findConfigFile(directory, sys2.fileExists) ?? "./tsconfig.json", sys2.readFile);
|
|
2227
2307
|
if (configResult.error !== void 0) {
|
|
2228
2308
|
throw new ConfigurationError(formatDiagnostics([configResult.error], diagnosticsHost));
|
|
2229
2309
|
}
|
|
@@ -2233,7 +2313,7 @@ var _TypeScriptProject = class _TypeScriptProject {
|
|
|
2233
2313
|
const hasExplicitEntryPoints = typeScriptOptions.tsbuild?.entryPoints !== void 0 || configResult.config.tsbuild?.entryPoints !== void 0;
|
|
2234
2314
|
let inferredEntryPoints;
|
|
2235
2315
|
if (!hasExplicitEntryPoints && bundle) {
|
|
2236
|
-
const packageJsonContent =
|
|
2316
|
+
const packageJsonContent = sys2.readFile(Paths.join(directory, "package.json"));
|
|
2237
2317
|
if (packageJsonContent) {
|
|
2238
2318
|
try {
|
|
2239
2319
|
const pkgJson = JSON.parse(packageJsonContent);
|
|
@@ -2272,7 +2352,7 @@ var _TypeScriptProject = class _TypeScriptProject {
|
|
|
2272
2352
|
...typeScriptOptions.compilerOptions
|
|
2273
2353
|
}
|
|
2274
2354
|
};
|
|
2275
|
-
const { options, fileNames, errors } = parseJsonConfigFileContent(baseConfig,
|
|
2355
|
+
const { options, fileNames, errors } = parseJsonConfigFileContent(baseConfig, sys2, directory);
|
|
2276
2356
|
return {
|
|
2277
2357
|
...baseConfig,
|
|
2278
2358
|
compilerOptions: {
|
|
@@ -2372,11 +2452,11 @@ var _TypeScriptProject = class _TypeScriptProject {
|
|
|
2372
2452
|
const [[firstFileName, { line: firstLine }] = ["", { line: 0 }]] = filesWithErrors;
|
|
2373
2453
|
const relativeFirstFileName = Paths.relative(projectDirectory, firstFileName);
|
|
2374
2454
|
if (errorCount === 1) {
|
|
2375
|
-
Logger.error(`Found 1 error in ${relativeFirstFileName}:${firstLine + 1}${
|
|
2455
|
+
Logger.error(`Found 1 error in ${relativeFirstFileName}:${firstLine + 1}${sys2.newLine}`);
|
|
2376
2456
|
} else if (fileCount === 1) {
|
|
2377
|
-
Logger.error(`Found ${errorCount} errors in the same file, starting at: ${relativeFirstFileName}:${firstLine + 1}${
|
|
2457
|
+
Logger.error(`Found ${errorCount} errors in the same file, starting at: ${relativeFirstFileName}:${firstLine + 1}${sys2.newLine}`);
|
|
2378
2458
|
} else {
|
|
2379
|
-
Logger.error(`Found ${errorCount} errors in ${fileCount} files.${
|
|
2459
|
+
Logger.error(`Found ${errorCount} errors in ${fileCount} files.${sys2.newLine}`);
|
|
2380
2460
|
Logger.error("Errors Files");
|
|
2381
2461
|
for (const [fileName, { count, line }] of filesWithErrors) {
|
|
2382
2462
|
Logger.error(` ${count} ${fileName}:${line + 1}`);
|
|
@@ -2384,6 +2464,18 @@ var _TypeScriptProject = class _TypeScriptProject {
|
|
|
2384
2464
|
}
|
|
2385
2465
|
throw new TypeCheckError(message, formatDiagnostics(diagnostics, diagnosticsHost));
|
|
2386
2466
|
}
|
|
2467
|
+
/**
|
|
2468
|
+
* Calculates elapsed time since a performance mark and clears the mark.
|
|
2469
|
+
* @param markName - The name of the performance mark to measure from
|
|
2470
|
+
* @returns Formatted elapsed time string (e.g., '42ms')
|
|
2471
|
+
*/
|
|
2472
|
+
static elapsed(markName) {
|
|
2473
|
+
const endMark = performance2.mark(`${markName}:end`);
|
|
2474
|
+
const duration = endMark.startTime - performance2.getEntriesByName(markName, "mark")[0].startTime;
|
|
2475
|
+
performance2.clearMarks(markName);
|
|
2476
|
+
performance2.clearMarks(endMark.name);
|
|
2477
|
+
return `${~~duration}ms`;
|
|
2478
|
+
}
|
|
2387
2479
|
};
|
|
2388
2480
|
_init3 = __decoratorStart(null);
|
|
2389
2481
|
__decorateElement(_init3, 1, "build", _build_dec, _TypeScriptProject);
|
|
@@ -7,7 +7,6 @@ import {
|
|
|
7
7
|
|
|
8
8
|
// src/plugins/decorator-metadata.ts
|
|
9
9
|
import { dirname } from "node:path";
|
|
10
|
-
import { transformFile } from "@swc/core";
|
|
11
10
|
var swcOptions = {
|
|
12
11
|
jsc: {
|
|
13
12
|
parser: { syntax: "typescript", decorators: true },
|
|
@@ -30,6 +29,7 @@ var swcDecoratorMetadataPlugin = {
|
|
|
30
29
|
setup(build) {
|
|
31
30
|
build.initialOptions.keepNames = true;
|
|
32
31
|
build.onLoad({ filter: typeScriptExtensionExpression }, async ({ path }) => {
|
|
32
|
+
const { transformFile } = await import("@swc/core");
|
|
33
33
|
const result = await transformFile(path, swcOptions);
|
|
34
34
|
if (result.map) {
|
|
35
35
|
const sources = [];
|
package/dist/index.d.ts
CHANGED
|
@@ -41,9 +41,14 @@ interface Closable {
|
|
|
41
41
|
close: Callable;
|
|
42
42
|
}
|
|
43
43
|
type ClosableConstructor = Constructor<any[], Closable>;
|
|
44
|
+
type PerformanceSubStep = {
|
|
45
|
+
name: string;
|
|
46
|
+
duration: string;
|
|
47
|
+
};
|
|
44
48
|
type PerformanceEntryDetail<T = unknown[]> = {
|
|
45
49
|
message: string;
|
|
46
50
|
result?: T;
|
|
51
|
+
steps?: PerformanceSubStep[];
|
|
47
52
|
};
|
|
48
53
|
type DetailedPerformanceMeasureOptions<R> = PrettyModify<PerformanceMeasureOptions, {
|
|
49
54
|
detail: PerformanceEntryDetail<R>;
|
|
@@ -337,7 +342,13 @@ declare class TypeScriptProject implements Closable {
|
|
|
337
342
|
* @param projectDirectory - The project directory.
|
|
338
343
|
*/
|
|
339
344
|
private static handleTypeErrors;
|
|
345
|
+
/**
|
|
346
|
+
* Calculates elapsed time since a performance mark and clears the mark.
|
|
347
|
+
* @param markName - The name of the performance mark to measure from
|
|
348
|
+
* @returns Formatted elapsed time string (e.g., '42ms')
|
|
349
|
+
*/
|
|
350
|
+
private static elapsed;
|
|
340
351
|
}
|
|
341
352
|
|
|
342
353
|
export { TypeScriptProject };
|
|
343
|
-
export type { AbsolutePath, AsyncEntryPoints, Brand, BuildCache, BuildConfiguration, CachedDeclaration, Closable, ClosableConstructor, CompilerOptionOverrides, ConditionalPath, DetailedPerformanceEntry, DetailedPerformanceMeasureOptions, EntryPoints, EsTarget, FormatSupplier, Function, InferredFunction, JsonString, JsxRenderingMode, LogEntryType, MethodFunction, OptionalReturn, Path, Pattern, PendingFileChange, ProjectBuildConfiguration, ProjectDependencies, ReadConfigResult, RelativePath, SourceMap, TypeScriptConfiguration, TypeScriptOptions, TypedFunction, WrittenFile };
|
|
354
|
+
export type { AbsolutePath, AsyncEntryPoints, Brand, BuildCache, BuildConfiguration, CachedDeclaration, Closable, ClosableConstructor, CompilerOptionOverrides, ConditionalPath, DetailedPerformanceEntry, DetailedPerformanceMeasureOptions, EntryPoints, EsTarget, FormatSupplier, Function, InferredFunction, JsonString, JsxRenderingMode, LogEntryType, MethodFunction, OptionalReturn, Path, Pattern, PendingFileChange, PerformanceSubStep, ProjectBuildConfiguration, ProjectDependencies, ReadConfigResult, RelativePath, SourceMap, TypeScriptConfiguration, TypeScriptOptions, TypedFunction, WrittenFile };
|
package/dist/index.js
CHANGED
package/dist/tsbuild.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import {
|
|
3
3
|
BuildError,
|
|
4
4
|
TypeScriptProject
|
|
5
|
-
} from "./
|
|
5
|
+
} from "./3HKDLNVH.js";
|
|
6
6
|
import "./7FPDHUPW.js";
|
|
7
7
|
|
|
8
8
|
// src/tsbuild.ts
|
|
@@ -30,7 +30,7 @@ if (help) {
|
|
|
30
30
|
process.exit(0);
|
|
31
31
|
}
|
|
32
32
|
if (version) {
|
|
33
|
-
console.log("1.
|
|
33
|
+
console.log("1.3.0");
|
|
34
34
|
process.exit(0);
|
|
35
35
|
}
|
|
36
36
|
var typeScriptOptions = {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@d1g1tal/tsbuild",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"packageManager": "pnpm@10.
|
|
3
|
+
"version": "1.3.0",
|
|
4
|
+
"packageManager": "pnpm@10.30.3",
|
|
5
5
|
"description": "A fast, ESM-only TypeScript build tool combining the TypeScript API for type checking and declaration generation, esbuild for bundling, and SWC for decorator metadata.",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"exports": {
|
|
@@ -72,9 +72,5 @@
|
|
|
72
72
|
"typescript": "5.9.3",
|
|
73
73
|
"typescript-eslint": "^8.56.1",
|
|
74
74
|
"vitest": "^4.0.18"
|
|
75
|
-
},
|
|
76
|
-
"optionalDependencies": {
|
|
77
|
-
"@swc/core": "^1.15.13",
|
|
78
|
-
"@swc/types": "^0.1.25"
|
|
79
75
|
}
|
|
80
76
|
}
|