zshy 0.0.5 → 0.0.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.
package/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  <p align="center">
2
2
 
3
- <h1 align="center">⚜️<br/><code>zshy</code></h1>
4
- <p align="center">Gold-standard build tool for TypeScript libraries.
3
+ <h1 align="center">🐒<br/><code>zshy</code></h1>
4
+ <p align="center">The no-bundler build tool for TypeScript libraries. Powered by <code>tsc</code>.
5
5
  <br/>
6
6
  by <a href="https://x.com/colinhacks">@colinhacks</a>
7
7
  </p>
@@ -15,42 +15,83 @@
15
15
  <a href="https://github.com/colinhacks/zshy" rel="nofollow"><img src="https://img.shields.io/github/stars/colinhacks/zshy" alt="stars"></a>
16
16
  </p>
17
17
 
18
- <div align="center">
18
+ <!-- <div align="center">
19
19
  <a href="https://github.com/colinhacks/zshy">GitHub</a>
20
20
  <span>&nbsp;&nbsp;•&nbsp;&nbsp;</span>
21
21
  <a href="https://twitter.com/colinhacks">𝕏</a>
22
22
  <span>&nbsp;&nbsp;•&nbsp;&nbsp;</span>
23
23
  <a href="https://bsky.app/profile/colinhacks.com">Bluesky</a>
24
24
  <br />
25
- </div>
25
+ </div> -->
26
26
 
27
27
  <br/>
28
28
  <br/>
29
29
  <br/>
30
30
 
31
-
32
31
  <!-- ## What is `zshy`? -->
33
32
 
34
33
  <h2 align="center">What is <code>zshy</code>?</h2>
35
34
 
36
35
  `zshy` is a simple, zero-config build tool for transpiling TypeScript libraries. It was originally created as internal build tool for [Zod](https://github.com/colinhacks/zod) but is now available as a general-purpose tool for TypeScript libraries.
37
36
 
37
+ <br/>
38
+
38
39
  ### Features
39
40
 
40
41
  - 🧱 **Dual-module builds** — Builds ESM and CJS outputs from a single TypeScript source file
41
42
  - 👑 **Powered by `tsc`** — No bundling, no extra configs, just good old-fashioned `tsc`
42
- - 📝 **Declarative config** — No build scripts, just a simple `"zshy"` field in your `package.json`
43
+ - 🟦 **No config file** — Reads only from your `package.json` and `tsconfig.json` (configurable)
44
+ - 📝 **Declarative entrypoint map** — Specify your TypeScript entrypoints in `package.json#/zshy`
43
45
  - 🤖 **Auto-generated `"exports"`** — Writes `"exports"` map directly into your `package.json`
44
- - 🟦 **Respects your `tsconfig.json`**
45
- - 📂 **Unopinionated about file structure** — Use any file structure you like
46
- - 🔗 **Unopinionated about import extensions** — Use any import syntax TypeScript supports: extensionless, `.js`, `.ts`
47
- - ⚛️ **Supports `.tsx`**
48
- - 📱 **Supports React Native**
49
- - 🐌 **Blazing fast** — Just kidding, it's slow. But `tsc` is about to get 10x faster!
46
+ - 🐚 **CLI-friendly** First-class `"bin"` support
47
+ - 📂 **Supports any file structure** — Use any file structure you like
48
+ - 🔗 **Supports extensionless imports** — Use any import syntax TypeScript supports: extensionless, `.js`, `.ts`
49
+ - ⚛️ **Supports `.tsx`** — Rewrites to `.js/.cjs/.mjs` per your `tsconfig.json#/jsx*` settings
50
+ - 📱 **Supports React Native** — Supports a [flat build mode](#can-it-support-react-native-legacy-or-non-nodejs-environments) designed for bundlers that don't support `package.json#/exports`
51
+ - 🐌 **Blazing fast** — Just kidding, it's slow. But [it's worth it](#is-it-fast).
50
52
 
51
53
  <br/>
54
+
52
55
  <br/>
53
- <h2 align="center">Usage</h2>
56
+ <br/>
57
+
58
+ <h2 align="center">How does it work?</h2>
59
+
60
+ Each `.ts` file is transpiled to `.js/.d.ts` (ESM) and `.cjs/.d.cts` (CommonJS).
61
+
62
+ ```bash
63
+ $ tree .
64
+ ├── package.json # if type == "module"
65
+ ├── src
66
+ │ └── index.ts
67
+ └── dist # generated
68
+ ├── index.js
69
+ ├── index.cjs
70
+ ├── index.d.ts
71
+ └── index.d.cts
72
+ ```
73
+
74
+ All relative `import`/`export` statements are rewritten to the appropriate extension during the build:
75
+
76
+ | Original path | Result (ESM) | Result (CJS) |
77
+ | ------------------ | ------------------ | ------------------- |
78
+ | `from "./util"` | `from "./util.js"` | `from "./util.cjs"` |
79
+ | `from "./util.ts"` | `from "./util.js"` | `from "./util.cjs"` |
80
+ | `from "./util.js"` | `from "./util.js"` | `from "./util.cjs"` |
81
+
82
+ Existing build tools (tsup, tsdown, etc) perform a similar transform during their bundling step. Unfortunately vanilla `tsc` [does not support extension rewriting](https://github.com/microsoft/TypeScript/issues/16577#issuecomment-754941937), leaving library authors with no choice but to use a bundler...
83
+
84
+ ...until now. `zshy` implements extension rewriting during the `tsc` build step via the official [TypeScript Compiler API](https://github.com/microsoft/TypeScript/wiki/Using-the-Compiler-API) — specifically, the `ts.TransformerFactory` API for defining AST-level code transforms. This obviates the need for a bundler. The result is a tool that I consider to be the "holy grail" of TypeScript library build tools:
85
+
86
+ - performs dual-module (ESM + CJS) builds
87
+ - type checks your code
88
+ - leverages `tsc` for gold-standard transpilation
89
+ - doesn't require a bundler
90
+ - doesn't require another config file (just `package.json` and `tsconfig.json`)
91
+
92
+ <br/>
93
+ <br/>
94
+ <h2 align="center">Quickstart</h2>
54
95
 
55
96
  <br/>
56
97
 
@@ -72,11 +113,85 @@ Specify your package entrypoint with the `"zshy"` key in `package.json`.
72
113
  {
73
114
  "name": "my-pkg",
74
115
  "version": "1.0.0",
75
- "zshy": "./src/index.ts" // package entrypoint
116
+ "zshy": "./src/index.ts" // 👈 package entrypoint
117
+ }
118
+ ```
119
+
120
+ <br/>
121
+
122
+ ### 3. Run a build
123
+
124
+ ```bash
125
+ $ npx zshy
126
+
127
+ → Starting zshy build 🐒
128
+ → Detected project root: /Users/colinmcd94/Documents/projects/zshy
129
+ → Reading package.json from ./package.json
130
+ → Reading tsconfig from ./tsconfig.json
131
+ → Cleaning up outDir...
132
+ → Determining entrypoints...
133
+ ╔════════════╤════════════════╗
134
+ ║ Subpath │ Entrypoint ║
135
+ ╟────────────┼────────────────╢
136
+ ║ "my-pkg" │ ./src/index.ts ║
137
+ ╚════════════╧════════════════╝
138
+ → Resolved build paths:
139
+ ╔══════════╤════════════════╗
140
+ ║ Location │ Resolved pathh ║
141
+ ╟──────────┼────────────────╢
142
+ ║ rootDir │ ./src ║
143
+ ║ outDir │ ./dist ║
144
+ ╚══════════╧════════════════╝
145
+ → Package is an ES module (package.json#/type is "module")
146
+ → Building CJS... (rewriting .ts -> .cjs/.d.cts)
147
+ → Building ESM...
148
+ → Updating package.json#/exports...
149
+ → Updating package.json#/bin...
150
+ → Build complete! ✅
151
+ ```
152
+
153
+ Alernatively, add a `"build"` script to your `package.json`:
154
+
155
+ ```diff
156
+ {
157
+ // ...
158
+ "scripts": {
159
+ + "build": "zshy"
160
+ }
76
161
  }
77
162
  ```
78
163
 
79
- More complicated packages can specify subpaths or wildcard exports with `"zshy.exports"`:
164
+ Then, to run a build:
165
+
166
+ ```bash
167
+ $ npm run build
168
+ ```
169
+
170
+ <br/>
171
+ <br/>
172
+
173
+ <h2 align="center">Usage</h2>
174
+
175
+ <br/>
176
+
177
+ ### Flags
178
+
179
+ ```sh
180
+ $ npm zshy --help
181
+ Usage: zshy [options]
182
+
183
+ Options:
184
+ -h, --help Show this help message
185
+ -p, --project <path> Path to tsconfig (default: ./tsconfig.json)
186
+ --verbose Enable verbose output
187
+ --dry-run Don't write any files or update package.json
188
+ ```
189
+
190
+ <br/>
191
+
192
+ ### Subpaths and wildcards
193
+
194
+ Multi-entrypoint packages can specify subpaths or wildcard exports with `package.json#/zshy/exports`:
80
195
 
81
196
  ```jsonc
82
197
  {
@@ -93,78 +208,115 @@ More complicated packages can specify subpaths or wildcard exports with `"zshy.e
93
208
  }
94
209
  ```
95
210
 
96
- <br/>
211
+ <details>
212
+ <summary>View typical build output</summary>
97
213
 
98
- ### 3. Run a build
214
+ When you run a build, you'''ll see something like this:
99
215
 
100
216
  ```bash
101
217
  $ npx zshy
102
218
 
103
- 💎 Starting zshy build...
104
- ⚙️ Detected project root: /path/to/my-pkg
105
- 📦 Reading package.json from ./package.json
106
- 📁 Reading tsconfig from ./tsconfig.json
107
- ➡️ Determining entrypoints...
219
+ Starting zshy build... 🐒
220
+ Detected project root: /path/to/my-pkg
221
+ Reading package.json from ./package.json
222
+ Reading tsconfig from ./tsconfig.json
223
+ Determining entrypoints...
108
224
  ╔════════════════════╤═════════════════════════════╗
109
225
  ║ Subpath │ Entrypoint ║
110
226
  ╟────────────────────┼─────────────────────────────╢
111
227
  ║ "my-pkg" │ ./src/index.ts ║
112
- ╟────────────────────┼─────────────────────────────╢
113
228
  ║ "my-pkg/utils" │ ./src/utils.ts ║
114
- ╟────────────────────┼─────────────────────────────╢
115
229
  ║ "my-pkg/plugins/*" │ ./src/plugins/* (5 matches) ║
116
230
  ╚════════════════════╧═════════════════════════════╝
117
- 📂 Transpiling from ./src (rootDir) to ./dist (outDir)
118
- 🟨 Package is ES module (package.json#type is "module")
119
- 🧱 Building CJS... (rewriting .ts -> .cjs/.d.cts)
120
- 🧱 Building ESM...
121
- 📦 Updating package.json exports...
122
- {
123
- ".": {
124
- "types": "./out/index.d.cts",
125
- "import": "./out/index.js",
126
- "require": "./out/index.cjs"
127
- },
128
- "./utils": {
129
- "types": "./out/utils.d.cts",
130
- "import": "./out/utils.js",
131
- "require": "./out/utils.cjs"
132
- },
133
- "./plugins/*": {
134
- "import": "./out/src/plugins/*",
135
- "require": "./out/src/plugins/*"
136
- }
137
- }
138
- 🎉 Build complete!
231
+ → Resolved build paths:
232
+ ╔══════════╤════════════════╗
233
+ Location Resolved pathh
234
+ ╟──────────┼────────────────╢
235
+ rootDir │ ./src ║
236
+ ║ outDir │ ./dist ║
237
+ ╚══════════╧════════════════╝
238
+ → Package is ES module (package.json#/type is "module")
239
+ → Building CJS... (rewriting .ts -> .cjs/.d.cts)
240
+ → Building ESM...
241
+ → Updating package.json exports...
242
+ → Build complete! ✅
139
243
  ```
140
244
 
141
- Alernatively, add a `"build"` script to your `package.json`:
245
+ And the generated `"exports"` map will look like this:
142
246
 
143
247
  ```diff
248
+ // package.json
144
249
  {
145
250
  // ...
146
- "scripts": {
147
- + "build": "zshy"
251
+ + "exports": {
252
+ + ".": {
253
+ + "types": "./dist/index.d.cts",
254
+ + "import": "./dist/index.js",
255
+ + "require": "./dist/index.cjs"
256
+ + },
257
+ + "./utils": {
258
+ + "types": "./dist/utils.d.cts",
259
+ + "import": "./dist/utils.js",
260
+ + "require": "./dist/utils.cjs"
261
+ + },
262
+ + "./plugins/*": {
263
+ + "types": "./dist/src/plugins/*",
264
+ + "import": "./dist/src/plugins/*",
265
+ + "require": "./dist/src/plugins/*"
266
+ + }
267
+ + }
268
+ }
269
+ ```
270
+
271
+ </details>
272
+
273
+ <br/>
274
+
275
+ ### Building CLIs (`"bin"` support)
276
+
277
+ If your package is a CLI, specify your CLI entrypoint in `package.json#/zshy/bin`. `zshy` will include this entrypoint in your builds and automatically set `"bin"` in your package.json.
278
+
279
+ ```jsonc
280
+ {
281
+ // package.json
282
+ "name": "my-cli",
283
+ "version": "1.0.0",
284
+ "type": "module",
285
+ "zshy": {
286
+ "bin": "./src/cli.ts" // 👈 specify CLI entrypoint
148
287
  }
149
288
  }
150
289
  ```
151
290
 
152
- Then, to run a build:
291
+ When you run `zshy`, it will automatically add the appropriate `"bin"` field to your `package.json`:
153
292
 
154
- ```bash
155
- $ npm run build
293
+ ```diff
294
+ {
295
+ // package.json
296
+ "name": "my-cli",
297
+ "version": "1.0.0",
298
+ "zshy": {
299
+ "exports": "./src/index.ts",
300
+ "bin": "./src/cli.ts"
301
+ },
302
+ + "bin": {
303
+ + "my-cli": "./dist/cli.cjs" // CLI entrypoint
304
+ + }
305
+ }
156
306
  ```
157
307
 
308
+ <br/>
158
309
 
159
310
  <br/>
160
311
  <br/>
161
- <h2 align="center">FAQ</h2>
312
+
313
+ <h2 align="center">FAQ for nerds</h2>
162
314
 
163
315
  <br/>
164
316
 
165
317
  ### How does `zshy` resolve entrypoints?
166
318
 
167
- It reads your `package.json#zshy` config:
319
+ It reads your `package.json#/zshy` config:
168
320
 
169
321
  ```jsonc
170
322
  // package.json
@@ -214,72 +366,82 @@ No. You can organize your source however you like; `zshy` will transpile your en
214
366
 
215
367
  ### What files does `zshy` create?
216
368
 
217
- It depends on your `package.json#type` field. If your package is ESM (that is, `"type": "module"` in `package.json`), the CJS build files will be generated as `.cjs`/`.d.cts`:
369
+ It depends on your `package.json#/type` field. If your package is ESM (that is, `"type": "module"` in `package.json`):
218
370
 
219
371
  - `.js` + `.d.ts` (ESM)
220
372
  - `.cjs` + `.d.cts` (CJS)
221
373
 
222
374
  ```bash
223
- $ tree out
375
+ $ tree dist
224
376
 
225
377
  .
226
378
  ├── package.json # if type == "module"
227
379
  ├── src
228
380
  │   └── index.ts
229
- └── out
230
-    ├── index.js
381
+ └── dist
382
+    ├── index.js
231
383
     ├── index.d.ts
232
384
     ├── index.cts
233
385
     └── index.d.cts
234
386
  ```
235
387
 
236
- Otherwise, the package is considered *default-CJS* and the ESM build files will be rewritten as `.mjs`/`.d.mts`.
388
+ Otherwise, the package is considered _default-CJS_ and the ESM build files will be rewritten as `.mjs`/`.d.mts`.
237
389
 
238
390
  - `.mjs` + `.d.mts` (ESM)
239
391
  - `.js` + `.d.ts` (CJS)
240
392
 
241
393
  ```bash
242
- $ tree out
394
+ $ tree dist
243
395
  .
244
396
  ├── package.json # if type != "module"
245
397
  ├── src
246
398
  │   └── index.ts
247
- └── out
399
+ └── dist
248
400
     ├── index.js
249
401
     ├── index.d.ts
250
402
     ├── index.mjs
251
403
     └── index.d.mts
252
404
  ```
253
405
 
254
- > **Comparison** — `tshy` generates plain `.js`/`.d.ts` files into separate `dist/esm` and `dist/cjs` directories, each with a stub `package.json` to enable proper module resolution in Node.js. This is more convoluted than the flat file structure generated by `zshy`. It also causes issues with [Module Federation](https://github.com/colinhacks/zod/issues/4656).
406
+ > **Comparison** — `tshy` generates plain `.js`/`.d.ts` files into separate `dist/esm` and `dist/cjs` directories, each with a stub `package.json` to enable proper module resolution in Node.js. This is more convoluted than the flat file structure generated by `zshy`. It also causes issues with [Module Federation](https://github.com/colinhacks/zod/issues/4656).
255
407
 
256
408
  <br/>
257
409
 
258
410
  ### How does extension rewriting work?
259
411
 
260
- `zshy` uses the [TypeScript Compiler API](https://github.com/microsoft/TypeScript/wiki/Using-the-Compiler-API) to perform extension rewriting during the `tsc` build process. TypeScript provides dedicated hooks for performing such transforms (though they are criminally under-utilized, until now!):
412
+ `zshy` uses the [TypeScript Compiler API](https://github.com/microsoft/TypeScript/wiki/Using-the-Compiler-API) to rewrite file extensions during the `tsc` build process. This makes it possible to generate CJS and ESM build outputs side-by-side.
413
+
414
+ Depending on the build format being targeted, `zshy` will:
415
+
416
+ - Rewrite `.ts` imports/exports to `.js`/`.cjs`/`.mjs`
417
+ - Rewrite extensionless imports/exports to `.js`/`.cjs`/`.mjs`
418
+ - Rewrite `.js` imports/exports to `.cjs`/`.mjs`
419
+ - Rename build output files to `.cjs`/`.mjs`/`.d.cts`/`.d.mts`
420
+
421
+ TypeScript provides dedicated hooks for performing such transforms (though they are criminally under-utilized).
261
422
 
262
423
  - **`ts.TransformerFactory`**: Provides AST transformations to rewrite import/export extensions before module conversion
263
424
  - **`ts.CompilerHost#writeFile`**: Handles output file extension changes (`.js` → `.cjs`/`.mjs`)
264
425
 
265
- > **Comparison** — `tshy` was designed to enable dual-package builds powered by the `tsc` compiler. To make this work, it relies on a specific file structure and the creation of temporary `package.json` files to accommodate the various idiosyncrasies of Node.js module resolution. TypeScript provides a robust API for AST transformations (`ts.TransformerFactory`) that `tshy` does not take advantage of.
426
+ > **Comparison** — `tshy` was designed to enable dual-package builds powered by the `tsc` compiler. To make this work, it relies on a specific file structure and the creation of temporary `package.json` files to accommodate the various idiosyncrasies of Node.js module resolution. It also requires the use of separate `dist/esm` and `dist/cjs` build subdirectories.
266
427
 
267
428
  <br/>
268
429
 
269
430
  ### Can I use extension-less imports?
270
431
 
271
- Yes! `zshy` supports whatever import style you prefer:
432
+ Yes! `zshy` supports whatever import style you prefer:
433
+
272
434
  - `from "./utils"`: classic extensionless imports
273
435
  - `from "./utils.js"`: ESM-friendly extensioned imports
274
436
  - `from "./util.ts"`: recently supported natively via[`rewriteRelativeImportExtensions`](https://www.typescriptlang.org/tsconfig/#rewriteRelativeImportExtensions)
275
437
 
276
- Use whatever you like; `zshy` will rewrite extensionless and `.ts` imports/exports to have the appropriate file extension.
438
+ Use whatever you like; `zshy` will rewrite all imports/exports properly during the build process.
277
439
 
278
440
  > **Comparison** — `tshy` forces you to use `.js` imports throughout your codebase. While this is generally a good practice, it's not always feasible, and there are hundreds of thousands of existing TypeScript codebases reliant on extensionless imports.
279
441
 
280
442
  <br/>
281
443
 
282
- ### How does it generate `package.json#exports`?
444
+ ### What about `package.json#/exports`?
283
445
 
284
446
  Your exports map is automatically written into your `package.json` when you run `zshy`. The generated exports map looks like this:
285
447
 
@@ -294,18 +456,18 @@ Your exports map is automatically written into your `package.json` when you run
294
456
  },
295
457
  + "exports": { // auto-generated by zshy
296
458
  + ".": {
297
- + "types": "./out/index.d.cts",
298
- + "import": "./out/index.js",
299
- + "require": "./out/index.cjs"
459
+ + "types": "./dist/index.d.cts",
460
+ + "import": "./dist/index.js",
461
+ + "require": "./dist/index.cjs"
300
462
  + },
301
463
  + "./utils": {
302
- + "types": "./out/utils.d.cts",
303
- + "import": "./out/utils.js",
304
- + "require": "./out/utils.cjs"
464
+ + "types": "./dist/utils.d.cts",
465
+ + "import": "./dist/utils.js",
466
+ + "require": "./dist/utils.cjs"
305
467
  + },
306
468
  + "./plugins/*": {
307
- + "import": "./out/src/plugins/*",
308
- + "require": "./out/src/plugins/*"
469
+ + "import": "./dist/src/plugins/*",
470
+ + "require": "./dist/src/plugins/*"
309
471
  + }
310
472
  + }
311
473
  }
@@ -324,7 +486,7 @@ import mod from "pkg"; ^^^^^
324
486
  // ^ The current file is a CommonJS module whose imports will produce 'require' calls; however, the referenced file is an ECMAScript module and cannot be imported with 'require'. Consider writing a dynamic 'import("pkg")' call instead.
325
487
  ```
326
488
 
327
- By having `"types"` point to the `.d.cts` declarations, this error will never happen. Technically, we're lying to TypeScript and telling it to always assume our code is CommonJS; in practice, this has no real consequences and maximizes compatibility. To learn more, read the ["Masquerading as ESM"](https://github.com/arethetypeswrong/arethetypeswrong.github.io/blob/main/docs/problems/FalseESM.md) and ["Masquerading as CJS"](https://github.com/arethetypeswrong/arethetypeswrong.github.io/blob/main/docs/problems/FalseCJS.md) writeups from Are The Types Wrong.
489
+ By having `"types"` point to the `.d.cts` declarations, this error will never happen. Technically, we're lying to TypeScript and telling it to always assume our code is CommonJS; in practice, this has no real consequences and maximizes compatibility. To learn more, read the ["Masquerading as ESM"](https://github.com/arethetypeswrong/arethetypeswrong.github.io/blob/main/docs/problems/FalseESM.md) and ["Masquerading as CJS"](https://github.com/arethetypeswrong/arethetypeswrong.github.io/blob/main/docs/problems/FalseCJS.md) writeups from Are The Types Wrong.
328
490
 
329
491
  > **Comparison** — `tshy` generates independent (but identical) `.d.ts` files in `dist/esm` and `dist/cjs`. This can cause [Excessively Deep](https://github.com/colinhacks/zod/issues/4422) errors if users of the library use declaration merging (`declare module {}`) for plugins/extensions. [Zod](https://github.com/colinhacks/zod), [day.js](https://day.js.org/), and others rely on this pattern for plugins.
330
492
 
@@ -332,25 +494,33 @@ By having `"types"` point to the `.d.cts` declarations, this error will never ha
332
494
 
333
495
  ### Can it support React Native legacy or non-Node.js environments?
334
496
 
335
- Yes! This is one of the key reasons `zshy` was originally developed for Zod.
497
+ Yes! This is one of the key reasons `zshy` was originally developed. Many environments don't support `package.json#/exports` yet:
336
498
 
337
- Many environments don't support `package.json#exports` yet (React Native, older bundlers, Node.js v10 or earlier, and many TypeScript projects using legacy configs). This causes issues for packages that want to use subpath imports to structure their package. Fortunately `zshy` unlocks a workaround I call a *flat build*:
499
+ - Node.js v12.7 or earlier
500
+ - React Native - The Metro bundler does not support `"exports"` by default
501
+ - TypeScript projects with legacy configs — e.g. `"module": "commonjs"`
502
+
503
+ This causes issues for packages that want to use subpath imports to structure their package. Fortunately `zshy` unlocks a workaround I call a _flat build_:
338
504
 
339
505
  1. Remove `"type": "module"` from your `package.json` (if present)
340
506
  2. Put your source files in your package root (not in a `src` directory)
341
507
  3. Set `outDir: "."` in your `tsconfig.json`
508
+ 4. Configure `"exclude"` in `package.json` to exclude all source files:
342
509
 
343
- With this setup, your build outputs (`index.js`, etc) will be written to disk right next to their corresponding source files. This lets you simulate subpath imports; imports like `"your-library/utils"` will generally resolve to `"your-library/utils/index.js"` in environments that predate modern module resolution. Zod uses this approach for broader compatibility with the following environments:
510
+ ```jsonc
511
+ {
512
+ // ...
513
+ "exclude": ["**/*.ts", "**/*.tsx", "**/*.cts", "**/*.mts", "node_modules"]
514
+ }
515
+ ```
344
516
 
345
- 1. **Node.js v12.7 or older**
346
- 2. **TypeScript projects using legacy configs** - e.g. `"module": "commonjs"`
347
- 3. **React Native** - The Metro bundler does not support `"exports"` by default
517
+ With this setup, your build outputs (`index.js`, etc) will be written to disk alongside to their corresponding source files. Older environments will resolve imports like `"your-library/utils"` to `"your-library/utils/index.js"`, effectively simulating subpath imports in environments that don't support them.
348
518
 
349
- <br/>
519
+ <br/>
350
520
 
351
521
  ### Is it fast?
352
522
 
353
- Not really. Typechecking with `tsc` is a lot slower than using a bundler that strips types. That said:
354
- 1) you *should* be type checking your code during builds,
355
- 2) TypeScript is [about to get 10x faster](https://devblogs.microsoft.com/typescript/typescript-native-port/), and
356
- 3) you just spent the last hour staring at a Cursor spinner anyway 😇
523
+ Not really. It uses `tsc` to typecheck your codebase, which is a lot slower than using a bundler that strips types. That said:
524
+
525
+ 1. You _should_ be type checking your code during builds
526
+ 2. TypeScript is [about to get 10x faster](https://devblogs.microsoft.com/typescript/typescript-native-port/)