@b9g/libuild 0.1.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 ADDED
@@ -0,0 +1,266 @@
1
+ # libuild
2
+
3
+ Zero-config library builds with ESBuild.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install -D @b9g/libuild
9
+ bun add -d @b9g/libuild
10
+ ```
11
+
12
+ ## Usage
13
+
14
+ ```bash
15
+ # Build your library (development mode - no package.json changes)
16
+ libuild build
17
+
18
+ # Build and update package.json for npm link
19
+ libuild build --save
20
+
21
+ # Build and publish to npm
22
+ libuild publish
23
+ ```
24
+
25
+ ## Features
26
+
27
+ - **Zero configuration** - Works out of the box
28
+ - **Structure-preserving builds** - Maintains src/ directory structure in dist/
29
+ - **Multiple formats** - ESM, CommonJS, and TypeScript declarations
30
+ - **Universal compatibility** - No runtime requirements (Node.js, Bun, Deno)
31
+ - **Smart entry detection** - Automatically finds all library entry points
32
+ - **Development-friendly** - Optional --save flag prevents git noise during development
33
+ - **Perfect npm link** - Works from both project root and dist directory
34
+ - **UMD builds** - Optional browser-compatible builds
35
+ - **Clean output** - Optimized package.json for consumers
36
+
37
+ ## Conventions
38
+
39
+ ### Entry Points
40
+ - **Library modules**: All top-level `.ts`/`.js` files in `src/` (excluding `_` prefixed files)
41
+ - **CLI binaries**: Any file referenced in `package.json` `bin` field gets compiled to standalone executable
42
+ - **UMD builds**: If `src/umd.ts` exists, creates browser-compatible UMD build
43
+
44
+ ### Output Structure
45
+ - **Structure-preserving**: `src/index.ts` → `dist/src/index.js` (maintains src/ directory)
46
+ - **ESM**: `.js` files with ES module syntax
47
+ - **CommonJS**: `.cjs` files for Node.js compatibility
48
+ - **TypeScript**: `.d.ts` declaration files (when TypeScript is available)
49
+ - **Clean package.json**: Optimized for consumers (no dev scripts)
50
+
51
+ ### Format Control
52
+ - **ESM-only**: Remove the `main` field from package.json to skip CommonJS builds
53
+ - **CommonJS detection**: Presence of `main` field enables `.cjs` builds
54
+ - **UMD builds**: Add `src/umd.ts` for browser-compatible builds
55
+
56
+ ### Export Aliases
57
+ - **Legacy support**: `./index.js` automatically aliases to `./index`
58
+ - **JSX runtime**: `src/jsx-runtime.ts` auto-creates `./jsx-dev-runtime` alias
59
+ - **Package.json**: Always exported as `./package.json`
60
+ - **Custom exports**: Existing exports in package.json are preserved and enhanced
61
+
62
+ ### Package.json Transformations
63
+ - **Development mode** (default): Root package.json unchanged, no git noise
64
+ - **--save mode**: Root package.json updated to point to `./dist/src/*` artifacts for npm link
65
+ - **Dist package.json**: Clean consumer-ready version with relative `src/` paths
66
+ - **Bin paths**: Automatically transformed from `src/` references to built artifacts
67
+ - **Exports field**: Generated for all entry points with proper types-first ordering
68
+
69
+ ## Examples
70
+
71
+ ### Simple Library
72
+
73
+ Given this structure:
74
+ ```
75
+ src/
76
+ index.ts
77
+ utils.ts
78
+ _internal.ts # ignored (underscore prefix)
79
+ ```
80
+
81
+ Produces:
82
+ ```
83
+ dist/
84
+ src/
85
+ index.js # ESM
86
+ index.cjs # CommonJS
87
+ index.d.ts # TypeScript declarations
88
+ utils.js
89
+ utils.cjs
90
+ utils.d.ts
91
+ package.json # Clean consumer version
92
+ ```
93
+
94
+ ### Library with CLI
95
+
96
+ ```
97
+ package.json:
98
+ {
99
+ "bin": { "mytool": "src/cli.js" }
100
+ }
101
+
102
+ src/
103
+ index.ts
104
+ cli.ts
105
+ ```
106
+
107
+ Produces:
108
+ ```
109
+ dist/
110
+ src/
111
+ index.js
112
+ index.cjs
113
+ index.d.ts
114
+ cli.js # Compiled CLI
115
+ cli.cjs
116
+ cli.d.ts
117
+ package.json # bin: { "mytool": "./src/cli.js" }
118
+ ```
119
+
120
+ ### ESM-Only Library
121
+
122
+ To build only ESM (no CommonJS), remove the `main` field:
123
+
124
+ ```json
125
+ // package.json
126
+ {
127
+ "name": "my-lib",
128
+ "module": "dist/src/index.js", // ESM entry
129
+ "types": "dist/src/index.d.ts"
130
+ // no "main" field = no CJS
131
+ }
132
+ ```
133
+
134
+ Produces:
135
+ ```
136
+ dist/
137
+ src/
138
+ index.js # ESM only
139
+ index.d.ts
140
+ utils.js # ESM only
141
+ utils.d.ts
142
+ package.json # ESM-only exports
143
+ ```
144
+
145
+ ### Multi-Format with UMD
146
+
147
+ ```
148
+ src/
149
+ index.ts
150
+ utils.ts
151
+ umd.ts # Browser build entry
152
+ ```
153
+
154
+ Produces:
155
+ ```
156
+ dist/
157
+ src/
158
+ index.js
159
+ index.cjs
160
+ index.d.ts
161
+ utils.js
162
+ utils.cjs
163
+ utils.d.ts
164
+ umd.js # UMD browser build
165
+ package.json
166
+ ```
167
+
168
+ ### Generated Package.json Exports
169
+
170
+ **Dual format** (ESM + CommonJS):
171
+ ```json
172
+ {
173
+ "main": "src/index.cjs",
174
+ "module": "src/index.js",
175
+ "types": "src/index.d.ts",
176
+ "exports": {
177
+ ".": {
178
+ "types": "./src/index.d.ts",
179
+ "import": "./src/index.js",
180
+ "require": "./src/index.cjs"
181
+ },
182
+ "./utils": {
183
+ "types": "./src/utils.d.ts",
184
+ "import": "./src/utils.js",
185
+ "require": "./src/utils.cjs"
186
+ },
187
+ "./utils.js": {
188
+ "types": "./src/utils.d.ts",
189
+ "import": "./src/utils.js",
190
+ "require": "./src/utils.cjs"
191
+ },
192
+ "./package.json": "./package.json"
193
+ }
194
+ }
195
+ ```
196
+
197
+ **ESM-only** (no `main` field in source):
198
+ ```json
199
+ {
200
+ "module": "src/index.js",
201
+ "types": "src/index.d.ts",
202
+ "type": "module",
203
+ "exports": {
204
+ ".": {
205
+ "types": "./src/index.d.ts",
206
+ "import": "./src/index.js"
207
+ },
208
+ "./utils": {
209
+ "types": "./src/utils.d.ts",
210
+ "import": "./src/utils.js"
211
+ },
212
+ "./utils.js": {
213
+ "types": "./src/utils.d.ts",
214
+ "import": "./src/utils.js"
215
+ },
216
+ "./package.json": "./package.json"
217
+ }
218
+ }
219
+ ```
220
+
221
+ **Root package.json** (with --save):
222
+ ```json
223
+ {
224
+ "main": "./dist/src/index.cjs",
225
+ "module": "./dist/src/index.js",
226
+ "types": "./dist/src/index.d.ts",
227
+ "exports": {
228
+ ".": {
229
+ "types": "./dist/src/index.d.ts",
230
+ "import": "./dist/src/index.js",
231
+ "require": "./dist/src/index.cjs"
232
+ }
233
+ }
234
+ }
235
+ ```
236
+
237
+ ## Commands
238
+
239
+ ### `libuild build` (default)
240
+ Builds your library in development mode:
241
+ - Compiles all entry points to multiple formats
242
+ - Generates TypeScript declarations
243
+ - Creates optimized package.json files
244
+ - Preserves root package.json (no git noise)
245
+
246
+ ### `libuild build --save`
247
+ Builds and updates root package.json for npm link:
248
+ - Everything from `libuild build`
249
+ - Updates root package.json to point to dist artifacts
250
+ - Perfect for testing with `npm link`
251
+
252
+ ### `libuild publish`
253
+ Builds and publishes to npm:
254
+ - Runs full build with --save
255
+ - Warns if root package.json is not private
256
+ - Publishes from dist directory with clean package.json
257
+
258
+ ## Requirements
259
+
260
+ - **Node.js 16+** or **Bun 1.0+** (for running libuild)
261
+ - **TypeScript** (optional, for .d.ts generation)
262
+ - No runtime requirements for library consumers
263
+
264
+ ## License
265
+
266
+ MIT
package/package.json ADDED
@@ -0,0 +1,78 @@
1
+ {
2
+ "name": "@b9g/libuild",
3
+ "version": "0.1.0",
4
+ "description": "Zero-config library builds",
5
+ "keywords": [
6
+ "build",
7
+ "library",
8
+ "bundler",
9
+ "bun",
10
+ "zero-config",
11
+ "typescript"
12
+ ],
13
+ "license": "MIT",
14
+ "repository": {
15
+ "type": "git",
16
+ "url": "https://github.com/bikeshaving/libuild.git"
17
+ },
18
+ "bugs": {
19
+ "url": "https://github.com/bikeshaving/libuild/issues"
20
+ },
21
+ "bin": {
22
+ "libuild": "./src/cli.js"
23
+ },
24
+ "dependencies": {
25
+ "esbuild": "^0.19.0",
26
+ "magic-string": "^0.30.0",
27
+ "typescript": "^5.0.0"
28
+ },
29
+ "engines": {
30
+ "bun": ">=1.0.0"
31
+ },
32
+ "type": "module",
33
+ "types": "src/libuild.d.ts",
34
+ "files": [
35
+ "README.md",
36
+ "src/"
37
+ ],
38
+ "main": "src/libuild.cjs",
39
+ "module": "src/libuild.js",
40
+ "exports": {
41
+ ".": {
42
+ "types": "./src/libuild.d.ts",
43
+ "import": "./src/libuild.js",
44
+ "require": "./src/libuild.cjs"
45
+ },
46
+ "./cli": {
47
+ "types": "./src/cli.d.ts",
48
+ "import": "./src/cli.js",
49
+ "require": "./src/cli.cjs"
50
+ },
51
+ "./cli.js": {
52
+ "types": "./src/cli.d.ts",
53
+ "import": "./src/cli.js",
54
+ "require": "./src/cli.cjs"
55
+ },
56
+ "./libuild": {
57
+ "types": "./src/libuild.d.ts",
58
+ "import": "./src/libuild.js",
59
+ "require": "./src/libuild.cjs"
60
+ },
61
+ "./libuild.js": {
62
+ "types": "./src/libuild.d.ts",
63
+ "import": "./src/libuild.js",
64
+ "require": "./src/libuild.cjs"
65
+ },
66
+ "./umd-plugin": {
67
+ "types": "./src/umd-plugin.d.ts",
68
+ "import": "./src/umd-plugin.js",
69
+ "require": "./src/umd-plugin.cjs"
70
+ },
71
+ "./umd-plugin.js": {
72
+ "types": "./src/umd-plugin.d.ts",
73
+ "import": "./src/umd-plugin.js",
74
+ "require": "./src/umd-plugin.cjs"
75
+ },
76
+ "./package.json": "./package.json"
77
+ }
78
+ }
@@ -0,0 +1,71 @@
1
+ // src/umd-plugin.ts
2
+ import * as FS from "fs/promises";
3
+ import * as Path from "path";
4
+ import MagicString from "magic-string";
5
+ function umdPlugin(options) {
6
+ return {
7
+ name: "umd-wrapper",
8
+ setup(build) {
9
+ build.onEnd(async (result) => {
10
+ if (result.errors.length > 0)
11
+ return;
12
+ const outputDir = build.initialOptions.outdir;
13
+ if (outputDir) {
14
+ const files = await FS.readdir(outputDir);
15
+ for (const file of files) {
16
+ if (file.endsWith(".js") && !file.endsWith(".js.map")) {
17
+ await wrapWithUmd(Path.join(outputDir, file), options.globalName);
18
+ }
19
+ }
20
+ }
21
+ });
22
+ }
23
+ };
24
+ }
25
+ async function wrapWithUmd(filePath, globalName) {
26
+ const code = await FS.readFile(filePath, "utf-8");
27
+ const ms = new MagicString(code);
28
+ const sourcemapComment = code.match(/\/\/# sourceMappingURL=.+$/m);
29
+ if (sourcemapComment) {
30
+ const index = code.lastIndexOf(sourcemapComment[0]);
31
+ ms.remove(index, index + sourcemapComment[0].length);
32
+ }
33
+ const exportsMatch = code.match(/\nmodule\.exports\s*=\s*([^;]+);?\s*$/);
34
+ if (exportsMatch) {
35
+ const index = code.lastIndexOf(exportsMatch[0]);
36
+ ms.overwrite(index, index + exportsMatch[0].length, `
37
+ return ${exportsMatch[1]};`);
38
+ }
39
+ const umdHeader = `(function (root, factory) {
40
+ if (typeof define === 'function' && define.amd) {
41
+ // AMD
42
+ define([], factory);
43
+ } else if (typeof module === 'object' && module.exports) {
44
+ // CommonJS
45
+ module.exports = factory();
46
+ } else {
47
+ // Browser globals
48
+ root.${globalName} = factory();
49
+ }
50
+ }(typeof self !== 'undefined' ? self : this, function () {
51
+ `;
52
+ const umdFooter = `
53
+ }));`;
54
+ ms.prepend(umdHeader);
55
+ ms.append(umdFooter);
56
+ const result = ms.toString();
57
+ const map = ms.generateMap({
58
+ file: Path.basename(filePath),
59
+ source: Path.basename(filePath).replace(".js", ".ts"),
60
+ includeContent: true
61
+ });
62
+ await FS.writeFile(filePath, result);
63
+ await FS.writeFile(filePath + ".map", map.toString());
64
+ await FS.writeFile(filePath, result + `
65
+ //# sourceMappingURL=${Path.basename(filePath)}.map`);
66
+ }
67
+
68
+ export {
69
+ umdPlugin
70
+ };
71
+ //# sourceMappingURL=chunk-EVLPGECD.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/umd-plugin.ts"],
4
+ "sourcesContent": ["import * as FS from \"fs/promises\";\nimport * as Path from \"path\";\nimport MagicString from \"magic-string\";\n\ninterface UmdPluginOptions {\n globalName: string;\n}\n\nexport function umdPlugin(options: UmdPluginOptions) {\n return {\n name: \"umd-wrapper\",\n setup(build: any) {\n build.onEnd(async (result: any) => {\n if (result.errors.length > 0) return;\n\n // ESBuild doesn't provide outputFiles by default unless write: false\n // We need to find the generated files in the output directory\n const outputDir = build.initialOptions.outdir;\n if (outputDir) {\n const files = await FS.readdir(outputDir);\n for (const file of files) {\n if (file.endsWith(\".js\") && !file.endsWith(\".js.map\")) {\n await wrapWithUmd(Path.join(outputDir, file), options.globalName);\n }\n }\n }\n });\n },\n };\n}\n\nasync function wrapWithUmd(filePath: string, globalName: string) {\n const code = await FS.readFile(filePath, \"utf-8\");\n const ms = new MagicString(code);\n\n // Remove sourcemap comment\n const sourcemapComment = code.match(/\\/\\/# sourceMappingURL=.+$/m);\n if (sourcemapComment) {\n const index = code.lastIndexOf(sourcemapComment[0]);\n ms.remove(index, index + sourcemapComment[0].length);\n }\n\n // Replace module.exports with return\n const exportsMatch = code.match(/\\nmodule\\.exports\\s*=\\s*([^;]+);?\\s*$/);\n if (exportsMatch) {\n const index = code.lastIndexOf(exportsMatch[0]);\n ms.overwrite(index, index + exportsMatch[0].length, `\\nreturn ${exportsMatch[1]};`);\n }\n\n // UMD wrapper\n const umdHeader = `(function (root, factory) {\n if (typeof define === 'function' && define.amd) {\n // AMD\n define([], factory);\n } else if (typeof module === 'object' && module.exports) {\n // CommonJS\n module.exports = factory();\n } else {\n // Browser globals\n root.${globalName} = factory();\n }\n}(typeof self !== 'undefined' ? self : this, function () {\n`;\n\n const umdFooter = `\n}));`;\n\n // Wrap with UMD\n ms.prepend(umdHeader);\n ms.append(umdFooter);\n\n // Generate sourcemap and write files\n const result = ms.toString();\n const map = ms.generateMap({\n file: Path.basename(filePath),\n source: Path.basename(filePath).replace(\".js\", \".ts\"),\n includeContent: true,\n });\n\n await FS.writeFile(filePath, result);\n await FS.writeFile(filePath + \".map\", map.toString());\n await FS.writeFile(filePath, result + `\\n//# sourceMappingURL=${Path.basename(filePath)}.map`);\n}\n"],
5
+ "mappings": ";AAAA,YAAY,QAAQ;AACpB,YAAY,UAAU;AACtB,OAAO,iBAAiB;AAMjB,SAAS,UAAU,SAA2B;AACnD,SAAO;AAAA,IACL,MAAM;AAAA,IACN,MAAM,OAAY;AAChB,YAAM,MAAM,OAAO,WAAgB;AACjC,YAAI,OAAO,OAAO,SAAS;AAAG;AAI9B,cAAM,YAAY,MAAM,eAAe;AACvC,YAAI,WAAW;AACb,gBAAM,QAAQ,MAAS,WAAQ,SAAS;AACxC,qBAAW,QAAQ,OAAO;AACxB,gBAAI,KAAK,SAAS,KAAK,KAAK,CAAC,KAAK,SAAS,SAAS,GAAG;AACrD,oBAAM,YAAiB,UAAK,WAAW,IAAI,GAAG,QAAQ,UAAU;AAAA,YAClE;AAAA,UACF;AAAA,QACF;AAAA,MACF,CAAC;AAAA,IACH;AAAA,EACF;AACF;AAEA,eAAe,YAAY,UAAkB,YAAoB;AAC/D,QAAM,OAAO,MAAS,YAAS,UAAU,OAAO;AAChD,QAAM,KAAK,IAAI,YAAY,IAAI;AAG/B,QAAM,mBAAmB,KAAK,MAAM,6BAA6B;AACjE,MAAI,kBAAkB;AACpB,UAAM,QAAQ,KAAK,YAAY,iBAAiB,CAAC,CAAC;AAClD,OAAG,OAAO,OAAO,QAAQ,iBAAiB,CAAC,EAAE,MAAM;AAAA,EACrD;AAGA,QAAM,eAAe,KAAK,MAAM,uCAAuC;AACvE,MAAI,cAAc;AAChB,UAAM,QAAQ,KAAK,YAAY,aAAa,CAAC,CAAC;AAC9C,OAAG,UAAU,OAAO,QAAQ,aAAa,CAAC,EAAE,QAAQ;AAAA,SAAY,aAAa,CAAC,CAAC,GAAG;AAAA,EACpF;AAGA,QAAM,YAAY;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,WAST,UAAU;AAAA;AAAA;AAAA;AAKnB,QAAM,YAAY;AAAA;AAIlB,KAAG,QAAQ,SAAS;AACpB,KAAG,OAAO,SAAS;AAGnB,QAAM,SAAS,GAAG,SAAS;AAC3B,QAAM,MAAM,GAAG,YAAY;AAAA,IACzB,MAAW,cAAS,QAAQ;AAAA,IAC5B,QAAa,cAAS,QAAQ,EAAE,QAAQ,OAAO,KAAK;AAAA,IACpD,gBAAgB;AAAA,EAClB,CAAC;AAED,QAAS,aAAU,UAAU,MAAM;AACnC,QAAS,aAAU,WAAW,QAAQ,IAAI,SAAS,CAAC;AACpD,QAAS,aAAU,UAAU,SAAS;AAAA,uBAA+B,cAAS,QAAQ,CAAC,MAAM;AAC/F;",
6
+ "names": []
7
+ }