@d1g1tal/tsbuild 1.2.3 → 1.2.5

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 CHANGED
@@ -8,9 +8,11 @@
8
8
  [![Node.js](https://img.shields.io/node/v/%40d1g1tal/tsbuild)](https://nodejs.org)
9
9
  [![TypeScript](https://img.shields.io/badge/TypeScript-5.9-blue?logo=typescript&logoColor=white)](https://www.typescriptlang.org/)
10
10
 
11
- A self-hosting TypeScript build tool that combines the best of three worlds: **TypeScript's type system**, **esbuild's speed**, and **SWC's decorator metadata support**. tsbuild is designed for modern ESM-only projects targeting Node.js 20.16.0+.
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
- > **⚠️ Note:** This is an experimental project for personal use. For production use, consider [tsup](https://tsup.egoist.dev/) instead, which is mature, battle-tested, and widely adopted. Or check out the new [tsdown](https://tsdown.dev/) by [void(0)](https://voidzero.dev/).
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
- - 🎨 **Decorator Metadata** - Optional SWC integration for `emitDecoratorMetadata` support for legacy decorators (Will probably be removed at some point in favor of native ESM decorators only)
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
- tsbuild orchestrates a sophisticated three-phase build process:
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
- 1. **Type Checking Phase** - TypeScript compiler validates types and emits `.d.ts` files into memory (not disk)
34
- 2. **Transpile Phase** - esbuild bundles JavaScript with custom plugins for module resolution and output formatting
35
- 3. **DTS Bundle Phase** - Custom minimal bundler combines declaration files through dependency graph traversal and AST transformation
67
+ ```json
68
+ { "scripts": { "build": "tsbuild" } }
69
+ ```
36
70
 
37
- This separation of concerns ensures TypeScript handles correctness, esbuild handles speed, and the custom bundler handles declaration consolidation efficiently without creating duplicate TypeScript Programs.
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 - no SWC dependency (Optional. For legacy decorator metadata. Native ESM decorators are supported without SWC)
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
- Add a `tsbuild` property to your `tsconfig.json`:
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", // default value
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:** The `target` and `outDir` options come from `tsconfig.json` `compilerOptions` and cannot be overridden in the `tsbuild` section. The `force` and `minify` options are typically better controlled via CLI flags (`--force`, `--minify`) rather than in the config.
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
- tsbuild supports `emitDecoratorMetadata` through SWC integration:
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
- "experimentalDecorators": true,
270
- "emitDecoratorMetadata": true
358
+ "target": "ESNext"
359
+ // No experimentalDecorators needed
271
360
  }
272
361
  }
273
362
  ```
274
363
 
275
- You must have `@swc/core` installed (as an optional dependency) for this feature to work:
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** - Type checking and transpilation run in parallel when possible
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 features
371
- - **Experimental** - Personal project, not recommended for production use
372
- - **Limited Configuration in tsconfig.json** - Some options (like `plugins`) are only available via programmatic API
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
- | Fast Bundling | ✅ esbuild | ✅ esbuild | ❌ N/A |
484
+ | Bundling | ✅ esbuild | ✅ esbuild | ❌ N/A |
381
485
  | Declaration Bundling | ✅ Custom Bundler | ✅ rollup-plugin-dts | ❌ N/A |
382
- | Decorator Metadata | ✅ SWC (optional) | ✅ SWC | ✅ Native |
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 experimental project. While contributions are welcome, please note that the project is not actively maintained for production use.
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
- ISC
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.
@@ -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 = [];
@@ -2015,7 +2015,7 @@ var _TypeScriptProject = class _TypeScriptProject {
2015
2015
  return Files.empty(this.buildConfiguration.outDir);
2016
2016
  }
2017
2017
  async build() {
2018
- Logger.header(`\u{1F680} tsbuild v${"1.2.3"}${this.configuration.compilerOptions.incremental ? " [incremental]" : ""}`);
2018
+ Logger.header(`\u{1F680} tsbuild v${"1.2.5"}${this.configuration.compilerOptions.incremental ? " [incremental]" : ""}`);
2019
2019
  try {
2020
2020
  const processes = [];
2021
2021
  const filesWereEmitted = await this.typeCheck();
@@ -2053,9 +2053,10 @@ var _TypeScriptProject = class _TypeScriptProject {
2053
2053
  }
2054
2054
  async typeCheck() {
2055
2055
  await this.fileManager.initialize();
2056
- const { diagnostics } = this.builderProgram.emit(void 0, this.fileManager.fileWriter, void 0, true);
2057
- if (diagnostics.length > 0) {
2058
- _TypeScriptProject.handleTypeErrors("Type-checking failed", diagnostics, this.directory);
2056
+ const { diagnostics: emitDiagnostics } = this.builderProgram.emit(void 0, this.fileManager.fileWriter, void 0, true);
2057
+ const allDiagnostics = [...this.builderProgram.getSemanticDiagnostics(), ...emitDiagnostics];
2058
+ if (allDiagnostics.length > 0) {
2059
+ _TypeScriptProject.handleTypeErrors("Type-checking failed", allDiagnostics, this.directory);
2059
2060
  }
2060
2061
  return this.fileManager.finalize();
2061
2062
  }
@@ -2066,7 +2067,7 @@ var _TypeScriptProject = class _TypeScriptProject {
2066
2067
  }
2067
2068
  if (this.configuration.compilerOptions.emitDecoratorMetadata) {
2068
2069
  try {
2069
- const { swcDecoratorMetadataPlugin } = await import("./DSHNVGWV.js");
2070
+ const { swcDecoratorMetadataPlugin } = await import("./LEZQQWX3.js");
2070
2071
  plugins.push(swcDecoratorMetadataPlugin);
2071
2072
  } catch {
2072
2073
  throw new ConfigurationError("emitDecoratorMetadata is enabled but @swc/core is not installed. Install it with: pnpm add -D @swc/core");
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  TypeScriptProject
3
- } from "./PWB3B4C6.js";
3
+ } from "./O7CHVFME.js";
4
4
  import "./7FPDHUPW.js";
5
5
  export {
6
6
  TypeScriptProject
package/dist/tsbuild.js CHANGED
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  BuildError,
4
4
  TypeScriptProject
5
- } from "./PWB3B4C6.js";
5
+ } from "./O7CHVFME.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.2.3");
33
+ console.log("1.2.5");
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.2.3",
4
- "packageManager": "pnpm@10.29.3",
3
+ "version": "1.2.5",
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
  }