zshy 0.0.1 → 0.0.2
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/LICENSE +21 -0
- package/README.md +356 -0
- package/out/index.cjs +491 -0
- package/out/index.d.cts +2 -0
- package/out/index.d.ts +2 -0
- package/out/index.js +453 -0
- package/out/plugins/a.cjs +4 -0
- package/out/plugins/a.d.cts +1 -0
- package/out/plugins/a.d.ts +1 -0
- package/out/plugins/a.js +1 -0
- package/out/plugins/b.cjs +4 -0
- package/out/plugins/b.d.cts +1 -0
- package/out/plugins/b.d.ts +1 -0
- package/out/plugins/b.js +1 -0
- package/out/utils.cjs +259 -0
- package/out/utils.d.cts +9 -0
- package/out/utils.d.ts +9 -0
- package/out/utils.js +221 -0
- package/package.json +85 -12
- package/index.d.ts +0 -1
- package/index.js +0 -2
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Colin McDonnell
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,356 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
|
|
3
|
+
<h1 align="center">⚜️<br/><code>zshy</code></h1>
|
|
4
|
+
<p align="center">Gold-standard build tool for TypeScript libraries.
|
|
5
|
+
<br/>
|
|
6
|
+
by <a href="https://x.com/colinhacks">@colinhacks</a>
|
|
7
|
+
</p>
|
|
8
|
+
</p>
|
|
9
|
+
<br/>
|
|
10
|
+
|
|
11
|
+
<p align="center">
|
|
12
|
+
<!-- <a href="https://github.com/colinhacks/zshy/actions?query=branch%3Amain"><img src="https://github.com/colinhacks/zshy/actions/workflows/test.yml/badge.svg?event=push&branch=main" alt="zshy CI status" /></a> -->
|
|
13
|
+
<a href="https://opensource.org/licenses/MIT" rel="nofollow"><img src="https://img.shields.io/github/license/colinhacks/zshy" alt="License"></a>
|
|
14
|
+
<a href="https://www.npmjs.com/package/zshy" rel="nofollow"><img src="https://img.shields.io/npm/dw/zshy.svg" alt="npm"></a>
|
|
15
|
+
<a href="https://github.com/colinhacks/zshy" rel="nofollow"><img src="https://img.shields.io/github/stars/colinhacks/zshy" alt="stars"></a>
|
|
16
|
+
</p>
|
|
17
|
+
|
|
18
|
+
<div align="center">
|
|
19
|
+
<a href="https://github.com/colinhacks/zshy">GitHub</a>
|
|
20
|
+
<span> • </span>
|
|
21
|
+
<a href="https://twitter.com/colinhacks">𝕏</a>
|
|
22
|
+
<span> • </span>
|
|
23
|
+
<a href="https://bsky.app/profile/colinhacks.com">Bluesky</a>
|
|
24
|
+
<br />
|
|
25
|
+
</div>
|
|
26
|
+
|
|
27
|
+
<br/>
|
|
28
|
+
<br/>
|
|
29
|
+
<br/>
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
<!-- ## What is `zshy`? -->
|
|
33
|
+
|
|
34
|
+
<h2 align="center">What is <code>zshy</code>?</h2>
|
|
35
|
+
|
|
36
|
+
`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
|
+
|
|
38
|
+
### Features
|
|
39
|
+
|
|
40
|
+
- 🧱 **Dual-module builds** — Builds ESM and CJS outputs from a single TypeScript source file
|
|
41
|
+
- 👑 **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
|
+
- 🤖 **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!
|
|
50
|
+
|
|
51
|
+
<br/>
|
|
52
|
+
<br/>
|
|
53
|
+
<h2 align="center">Usage</h2>
|
|
54
|
+
|
|
55
|
+
<br/>
|
|
56
|
+
|
|
57
|
+
### 1. Install `zshy` as a dev dependency:
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
npm install --save-dev zshy
|
|
61
|
+
yarn add --dev zshy
|
|
62
|
+
pnpm add --save-dev zshy
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
<br/>
|
|
66
|
+
|
|
67
|
+
### 2. Add the `"zshy"` field to your `package.json`
|
|
68
|
+
|
|
69
|
+
Specify your package entrypoint with the `"zshy"` key in `package.json`.
|
|
70
|
+
|
|
71
|
+
```jsonc
|
|
72
|
+
{
|
|
73
|
+
"name": "my-pkg",
|
|
74
|
+
"version": "1.0.0",
|
|
75
|
+
"zshy": "./src/index.ts" // package entrypoint
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
More complicated packages can specify subpaths or wildcard exports with `"zshy.exports"`:
|
|
80
|
+
|
|
81
|
+
```jsonc
|
|
82
|
+
{
|
|
83
|
+
"name": "my-pkg",
|
|
84
|
+
"version": "1.0.0",
|
|
85
|
+
|
|
86
|
+
"zshy": {
|
|
87
|
+
"exports": {
|
|
88
|
+
".": "./src/index.ts", // root entrypoints
|
|
89
|
+
"./utils": "./src/utils.ts", // subpath
|
|
90
|
+
"./plugins/*": "./src/plugins/*" // wildcards
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
<br/>
|
|
97
|
+
|
|
98
|
+
### 3. Run a build
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
$ npx zshy
|
|
102
|
+
|
|
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...
|
|
108
|
+
╔════════════════════╤═════════════════════════════╗
|
|
109
|
+
║ Subpath │ Entrypoint ║
|
|
110
|
+
╟────────────────────┼─────────────────────────────╢
|
|
111
|
+
║ "my-pkg" │ ./src/index.ts ║
|
|
112
|
+
╟────────────────────┼─────────────────────────────╢
|
|
113
|
+
║ "my-pkg/utils" │ ./src/utils.ts ║
|
|
114
|
+
╟────────────────────┼─────────────────────────────╢
|
|
115
|
+
║ "my-pkg/plugins/*" │ ./src/plugins/* (5 matches) ║
|
|
116
|
+
╚════════════════════╧═════════════════════════════╝
|
|
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!
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
Alernatively, add a `"build"` script to your `package.json`:
|
|
142
|
+
|
|
143
|
+
```diff
|
|
144
|
+
{
|
|
145
|
+
// ...
|
|
146
|
+
"scripts": {
|
|
147
|
+
+ "build": "zshy"
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
Then, to run a build:
|
|
153
|
+
|
|
154
|
+
```bash
|
|
155
|
+
$ npm run build
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
<br/>
|
|
160
|
+
<br/>
|
|
161
|
+
<h2 align="center">FAQ</h2>
|
|
162
|
+
|
|
163
|
+
<br/>
|
|
164
|
+
|
|
165
|
+
### How does `zshy` resolve entrypoints?
|
|
166
|
+
|
|
167
|
+
It reads your `package.json#zshy` config:
|
|
168
|
+
|
|
169
|
+
```jsonc
|
|
170
|
+
// package.json
|
|
171
|
+
{
|
|
172
|
+
"name": "my-pkg",
|
|
173
|
+
"version": "1.0.0",
|
|
174
|
+
"zshy": {
|
|
175
|
+
"exports": {
|
|
176
|
+
".": "./src/index.ts",
|
|
177
|
+
"./utils": "./src/utils.ts",
|
|
178
|
+
"./plugins/*": "./src/plugins/*" // matches all .ts/.tsx files in ./src/plugins
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
**Note** — Since `zshy` computes an exact set of resolved entrypoints, your `"files"`, `"include"`, and `"exclude"` settings in `tsconfig.json` are ignored during the build.
|
|
185
|
+
|
|
186
|
+
<br/>
|
|
187
|
+
|
|
188
|
+
### Does `zshy` respect my `tsconfig.json` compiler options?
|
|
189
|
+
|
|
190
|
+
Yes! With some strategic overrides:
|
|
191
|
+
|
|
192
|
+
- **`module`**: Overridden (`"commonjs"` for CJS build, `"esnext"` for ESM build)
|
|
193
|
+
- **`moduleResolution`**: Overridden (`"node10"` for CJS, `"bundler"` for ESM)
|
|
194
|
+
- **`declaration`/`noEmit`/`emitDeclarationOnly`**: Overridden to ensure proper output
|
|
195
|
+
- **`verbatimModuleSyntax`**: Set to `false` to allow multiple build formats
|
|
196
|
+
|
|
197
|
+
All other options are respected, including:
|
|
198
|
+
|
|
199
|
+
- `rootDir` (defaults to the common ancestor directory of all entrypoints)
|
|
200
|
+
- `outDir` (defaults to `./dist`)
|
|
201
|
+
- `declarationDir` (defaults to `./dist` — you probably shouldn't set this explicitly)
|
|
202
|
+
- `target` (defaults to `es2020`)
|
|
203
|
+
- `jsx`
|
|
204
|
+
|
|
205
|
+
<br/>
|
|
206
|
+
|
|
207
|
+
### Do I need to use a specific file structure?
|
|
208
|
+
|
|
209
|
+
No. You can organize your source however you like; `zshy` will transpile your entrypoints and all the files they import, respecting your `tsconfig.json` settings.
|
|
210
|
+
|
|
211
|
+
> **Comparison** — `tshy` requires you to put your source in a `./src` directory, and always builds to `./dist/esm` and `./dist/cjs`.
|
|
212
|
+
|
|
213
|
+
<br/>
|
|
214
|
+
|
|
215
|
+
### What files does `zshy` create?
|
|
216
|
+
|
|
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`:
|
|
218
|
+
|
|
219
|
+
- `.js` + `.d.ts` (ESM)
|
|
220
|
+
- `.cjs` + `.d.cts` (CJS)
|
|
221
|
+
|
|
222
|
+
```bash
|
|
223
|
+
$ tree out
|
|
224
|
+
|
|
225
|
+
.
|
|
226
|
+
├── package.json # if type == "module"
|
|
227
|
+
├── src
|
|
228
|
+
│ └── index.ts
|
|
229
|
+
└── out
|
|
230
|
+
├── index.js
|
|
231
|
+
├── index.d.ts
|
|
232
|
+
├── index.cts
|
|
233
|
+
└── index.d.cts
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
Otherwise, the package is considered *default-CJS* and the ESM build files will be rewritten as `.mjs`/`.d.mts`.
|
|
237
|
+
|
|
238
|
+
- `.mjs` + `.d.mts` (ESM)
|
|
239
|
+
- `.js` + `.d.ts` (CJS)
|
|
240
|
+
|
|
241
|
+
```bash
|
|
242
|
+
$ tree out
|
|
243
|
+
.
|
|
244
|
+
├── package.json # if type != "module"
|
|
245
|
+
├── src
|
|
246
|
+
│ └── index.ts
|
|
247
|
+
└── out
|
|
248
|
+
├── index.js
|
|
249
|
+
├── index.d.ts
|
|
250
|
+
├── index.mjs
|
|
251
|
+
└── index.d.mts
|
|
252
|
+
```
|
|
253
|
+
|
|
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).
|
|
255
|
+
|
|
256
|
+
<br/>
|
|
257
|
+
|
|
258
|
+
### How does extension rewriting work?
|
|
259
|
+
|
|
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!):
|
|
261
|
+
|
|
262
|
+
- **`ts.TransformerFactory`**: Provides AST transformations to rewrite import/export extensions before module conversion
|
|
263
|
+
- **`ts.CompilerHost#writeFile`**: Handles output file extension changes (`.js` → `.cjs`/`.mjs`)
|
|
264
|
+
|
|
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.
|
|
266
|
+
|
|
267
|
+
<br/>
|
|
268
|
+
|
|
269
|
+
### Can I use extension-less imports?
|
|
270
|
+
|
|
271
|
+
Yes! `zshy` supports whatever import style you prefer:
|
|
272
|
+
- `from "./utils"`: classic extensionless imports
|
|
273
|
+
- `from "./utils.js"`: ESM-friendly extensioned imports
|
|
274
|
+
- `from "./util.ts"`: recently supported natively via[`rewriteRelativeImportExtensions`](https://www.typescriptlang.org/tsconfig/#rewriteRelativeImportExtensions)
|
|
275
|
+
|
|
276
|
+
Use whatever you like; `zshy` will rewrite extensionless and `.ts` imports/exports to have the appropriate file extension.
|
|
277
|
+
|
|
278
|
+
> **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
|
+
|
|
280
|
+
<br/>
|
|
281
|
+
|
|
282
|
+
### How does it generate `package.json#exports`?
|
|
283
|
+
|
|
284
|
+
Your exports map is automatically written into your `package.json` when you run `zshy`. The generated exports map looks like this:
|
|
285
|
+
|
|
286
|
+
```diff
|
|
287
|
+
{
|
|
288
|
+
"zshy": {
|
|
289
|
+
"exports": {
|
|
290
|
+
".": "./src/index.ts",
|
|
291
|
+
"./utils": "./src/utils.ts",
|
|
292
|
+
"./plugins/*": "./src/plugins/*"
|
|
293
|
+
}
|
|
294
|
+
},
|
|
295
|
+
+ "exports": { // auto-generated by zshy
|
|
296
|
+
+ ".": {
|
|
297
|
+
+ "types": "./out/index.d.cts",
|
|
298
|
+
+ "import": "./out/index.js",
|
|
299
|
+
+ "require": "./out/index.cjs"
|
|
300
|
+
+ },
|
|
301
|
+
+ "./utils": {
|
|
302
|
+
+ "types": "./out/utils.d.cts",
|
|
303
|
+
+ "import": "./out/utils.js",
|
|
304
|
+
+ "require": "./out/utils.cjs"
|
|
305
|
+
+ },
|
|
306
|
+
+ "./plugins/*": {
|
|
307
|
+
+ "import": "./out/src/plugins/*",
|
|
308
|
+
+ "require": "./out/src/plugins/*"
|
|
309
|
+
+ }
|
|
310
|
+
+ }
|
|
311
|
+
}
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
<br/>
|
|
315
|
+
|
|
316
|
+
### Why `.d.cts` for `"types"`?
|
|
317
|
+
|
|
318
|
+
The `"types"` field always points to the CJS declaration file (`.d.cts`). This is an intentional design choice.
|
|
319
|
+
|
|
320
|
+
**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:
|
|
321
|
+
|
|
322
|
+
```ts
|
|
323
|
+
import mod from "pkg"; ^^^^^
|
|
324
|
+
// ^ 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
|
+
```
|
|
326
|
+
|
|
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.
|
|
328
|
+
|
|
329
|
+
> **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
|
+
|
|
331
|
+
<br/>
|
|
332
|
+
|
|
333
|
+
### Can it support React Native legacy or non-Node.js environments?
|
|
334
|
+
|
|
335
|
+
Yes! This is one of the key reasons `zshy` was originally developed for Zod.
|
|
336
|
+
|
|
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*:
|
|
338
|
+
|
|
339
|
+
1. Remove `"type": "module"` from your `package.json` (if present)
|
|
340
|
+
2. Put your source files in your package root (not in a `src` directory)
|
|
341
|
+
3. Set `outDir: "."` in your `tsconfig.json`
|
|
342
|
+
|
|
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:
|
|
344
|
+
|
|
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
|
|
348
|
+
|
|
349
|
+
<br/>
|
|
350
|
+
|
|
351
|
+
### Is it fast?
|
|
352
|
+
|
|
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 😇
|