zshy 0.0.8 β†’ 0.0.10

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
@@ -39,13 +39,13 @@
39
39
  ### Features
40
40
 
41
41
  - 🧱 **Dual-module builds** β€” Builds ESM and CJS outputs from a single TypeScript source file
42
- - πŸ‘‘ **Powered by `tsc`** β€” No bundling, no extra configs, just good old-fashioned `tsc`
43
- - 🟦 **No config file** β€” Reads only from your `package.json` and `tsconfig.json` (configurable)
42
+ - πŸ‘‘ **Powered by `tsc`** β€” The gold standard for TypeScript transpilation
43
+ - πŸ“¦ **Bundler-free** β€” No bundler or bundler configs involved
44
+ - 🟦 **No config file** β€” Reads from your `package.json` and `tsconfig.json`
44
45
  - πŸ“ **Declarative entrypoint map** β€” Specify your TypeScript entrypoints in `package.json#/zshy`
45
46
  - πŸ€– **Auto-generated `"exports"`** β€” Writes `"exports"` map directly into your `package.json`
46
47
  - 🐚 **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`
48
+ - πŸ“‚ **Unopinionated** β€” Use any file structure or import extension syntax you like
49
49
  - βš›οΈ **Supports `.tsx`** β€” Rewrites to `.js/.cjs/.mjs` per your `tsconfig.json#/jsx*` settings
50
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
51
  - 🐌 **Blazing fast** β€” Just kidding, it's slow. But [it's worth it](#is-it-fast).
@@ -55,42 +55,6 @@
55
55
  <br/>
56
56
  <br/>
57
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
58
  <h2 align="center">Quickstart</h2>
95
59
 
96
60
  <br/>
@@ -105,15 +69,15 @@ pnpm add --save-dev zshy
105
69
 
106
70
  <br/>
107
71
 
108
- ### 2. Add the `"zshy"` field to your `package.json`
109
-
110
- Specify your package entrypoint with the `"zshy"` key in `package.json`.
72
+ ### 2. Specify your entrypoint(s) in `package.json#zshy`:
111
73
 
112
- ```jsonc
74
+ ```diff
113
75
  {
114
76
  "name": "my-pkg",
115
77
  "version": "1.0.0",
116
- "zshy": "./src/index.ts" // πŸ‘ˆ package entrypoint
78
+ + "zshy": {
79
+ + ".": "./src/index.ts"
80
+ + }
117
81
  }
118
82
  ```
119
83
 
@@ -121,8 +85,10 @@ Specify your package entrypoint with the `"zshy"` key in `package.json`.
121
85
 
122
86
  ### 3. Run a build
123
87
 
88
+ Run a build with `npx zshy`:
89
+
124
90
  ```bash
125
- $ npx zshy
91
+ $ npx zshy # use --dry-run to try it out without writing/updating files
126
92
 
127
93
  β†’ Starting zshy build πŸ’
128
94
  β†’ Detected project root: /Users/colinmcd94/Documents/projects/zshy
@@ -137,7 +103,7 @@ $ npx zshy
137
103
  β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•§β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•
138
104
  β†’ Resolved build paths:
139
105
  ╔══════════╀════════════════╗
140
- β•‘ Location β”‚ Resolved pathh β•‘
106
+ β•‘ Location β”‚ Resolved path β•‘
141
107
  β•Ÿβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β•’
142
108
  β•‘ rootDir β”‚ ./src β•‘
143
109
  β•‘ outDir β”‚ ./dist β•‘
@@ -150,22 +116,80 @@ $ npx zshy
150
116
  β†’ Build complete! βœ…
151
117
  ```
152
118
 
153
- Alernatively, add a `"build"` script to your `package.json`:
119
+ > **Add a `"build"` script to your `package.json`**
120
+ >
121
+ > ```diff
122
+ > {
123
+ > // ...
124
+ > "scripts": {
125
+ > + "build": "zshy"
126
+ > }
127
+ > }
128
+ > ```
129
+ >
130
+ > Then, to run a build:
131
+ >
132
+ > ```bash
133
+ > $ npm run build
134
+ > ```
135
+
136
+ <br/>
137
+
138
+ <br/>
139
+
140
+ <h2 align="center">How it works</h2>
141
+
142
+ Vanilla `tsc` does not perform _extension rewriting_; it will only ever transpile a `.ts` file to a `.js` file (never `.cjs` or `.mjs`). This is the fundamental limitation that forces library authors to use bundlers or bundler-powered tools like `tsup`, `tsdown`, or `unbuild`...
143
+
144
+ ...until now! `zshy` works around this limitation using the official [TypeScript Compiler API](https://github.com/microsoft/TypeScript/wiki/Using-the-Compiler-API), which provides some powerful (and criminally under-utilized) hooks for customizing file extensions during the `tsc` build process.
145
+
146
+ Using these hooks, `zshy` transpiles each `.ts` file to `.js/.d.ts` (ESM) and `.cjs/.d.cts` (CommonJS):
147
+
148
+ ```bash
149
+ $ tree .
150
+ β”œβ”€β”€ package.json
151
+ β”œβ”€β”€ src
152
+ β”‚ └── index.ts
153
+ └── dist # generated
154
+ β”œβ”€β”€ index.js
155
+ β”œβ”€β”€ index.cjs
156
+ β”œβ”€β”€ index.d.ts
157
+ └── index.d.cts
158
+ ```
159
+
160
+ Similarly, all relative `import`/`export` statements are rewritten to include the appropriate file extension. (Other tools like `tsup` or `tsdown` do the same, but they require a bundler to do so.)
161
+
162
+ | Original path | Result (ESM) | Result (CJS) |
163
+ | ------------------ | ------------------ | ------------------- |
164
+ | `from "./util"` | `from "./util.js"` | `from "./util.cjs"` |
165
+ | `from "./util.ts"` | `from "./util.js"` | `from "./util.cjs"` |
166
+ | `from "./util.js"` | `from "./util.js"` | `from "./util.cjs"` |
167
+
168
+ Finally, `zshy` automatically writes `"exports"` into your `package.json`:
154
169
 
155
170
  ```diff
156
171
  {
157
172
  // ...
158
- "scripts": {
159
- + "build": "zshy"
160
- }
173
+ "zshy": {
174
+ "exports": "./src/index.ts"
175
+ },
176
+ + "exports": { // auto-generated by zshy
177
+ + ".": {
178
+ + "types": "./dist/index.d.cts",
179
+ + "import": "./dist/index.js",
180
+ + "require": "./dist/index.cjs"
181
+ + }
182
+ + }
161
183
  }
162
184
  ```
163
185
 
164
- Then, to run a build:
186
+ The result is a tool that I consider to be the "holy grail" of TypeScript library build tools:
165
187
 
166
- ```bash
167
- $ npm run build
168
- ```
188
+ - performs dual-module (ESM + CJS) builds
189
+ - type checks your code
190
+ - leverages `tsc` for gold-standard transpilation
191
+ - doesn't require a bundler
192
+ - doesn't require another config file (just `package.json` and `tsconfig.json`)
169
193
 
170
194
  <br/>
171
195
  <br/>
@@ -288,7 +312,7 @@ If your package is a CLI, specify your CLI entrypoint in `package.json#/zshy/bin
288
312
  }
289
313
  ```
290
314
 
291
- When you run `zshy`, it will automatically add the appropriate `"bin"` field to your `package.json`:
315
+ The `"bin"` field is automatically written into your `package.json`:
292
316
 
293
317
  ```diff
294
318
  {
@@ -300,13 +324,12 @@ When you run `zshy`, it will automatically add the appropriate `"bin"` field to
300
324
  "bin": "./src/cli.ts"
301
325
  },
302
326
  + "bin": {
303
- + "my-cli": "./dist/cli.cjs" // CLI entrypoint
327
+ + "my-cli": "./dist/cli.cjs" // CommonJS entrypoint
304
328
  + }
305
329
  }
306
330
  ```
307
331
 
308
332
  <br/>
309
-
310
333
  <br/>
311
334
  <br/>
312
335
 
@@ -409,16 +432,22 @@ $ tree dist
409
432
 
410
433
  ### How does extension rewriting work?
411
434
 
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.
435
+ `zshy` uses the [TypeScript Compiler API](https://github.com/microsoft/TypeScript/wiki/Using-the-Compiler-API) to rewrite file extensions during the `tsc` emit step.
413
436
 
414
- Depending on the build format being targeted, `zshy` will:
437
+ - If `"type": "module"`
438
+ - `.ts` becomes `.js`/`.d.ts` (ESM) and `.cjs`/`.d.cts` (CJS)
439
+ - Otherwise:
440
+ - `.ts` becomes `.mjs`/`.d.mts` (ESM) and `.js`/`.d.ts` (CJS)
415
441
 
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`
442
+ Similarly, all relative `import`/`export` statements are rewritten to account for the new file extensions.
420
443
 
421
- TypeScript provides dedicated hooks for performing such transforms (though they are criminally under-utilized).
444
+ | Original path | Result (ESM) | Result (CJS) |
445
+ | ------------------ | ------------------ | ------------------- |
446
+ | `from "./util"` | `from "./util.js"` | `from "./util.cjs"` |
447
+ | `from "./util.ts"` | `from "./util.js"` | `from "./util.cjs"` |
448
+ | `from "./util.js"` | `from "./util.js"` | `from "./util.cjs"` |
449
+
450
+ TypeScript's Compiler API provides dedicated hooks for performing such transforms (though they are criminally under-utilized).
422
451
 
423
452
  - **`ts.TransformerFactory`**: Provides AST transformations to rewrite import/export extensions before module conversion
424
453
  - **`ts.CompilerHost#writeFile`**: Handles output file extension changes (`.js` β†’ `.cjs`/`.mjs`)
@@ -479,14 +508,16 @@ Your exports map is automatically written into your `package.json` when you run
479
508
 
480
509
  The `"types"` field always points to the CJS declaration file (`.d.cts`). This is an intentional design choice.
481
510
 
482
- **It solves "Masquerading as ESM" issue**. Put more simply, you can always `import` a CJS package from ESM, but you can't `require` an ES module from a CJS environment. You've likely seen this dreaded error before:
511
+ **It solves the "Masquerading as ESM" issue**. You've likely seen this dreaded error before:
483
512
 
484
513
  ```ts
485
514
  import mod from "pkg"; ^^^^^
486
515
  // ^ 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.
487
516
  ```
488
517
 
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.
518
+ Simply put: ESM files can `require` CommonJS, but CommonJS files can't `import` ESM. By having `"types"` point to the `.d.cts` declarations, this error will never happen. Technically, we're tricking TypeScript into thinking our code is CommonJS; in practice, this has no real consequences and maximizes compatibility.
519
+
520
+ > 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.
490
521
 
491
522
  > **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.
492
523
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zshy",
3
- "version": "0.0.8",
3
+ "version": "0.0.10",
4
4
  "type": "module",
5
5
  "description": "Gold-standard build tool for TypeScript libraries",
6
6
  "keywords": [