@mgz-app/viteforge 1.0.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.
Files changed (37) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +738 -0
  3. package/dist/index.cjs +3359 -0
  4. package/dist/index.d.ts +319 -0
  5. package/dist/index.d.ts.map +1 -0
  6. package/dist/index.mjs +3319 -0
  7. package/dist/plugins/base64-assets.d.ts +3 -0
  8. package/dist/plugins/base64-assets.d.ts.map +1 -0
  9. package/dist/plugins/calculate-built-size.d.ts +6 -0
  10. package/dist/plugins/calculate-built-size.d.ts.map +1 -0
  11. package/dist/plugins/dev-reload.d.ts +31 -0
  12. package/dist/plugins/dev-reload.d.ts.map +1 -0
  13. package/dist/plugins/glsl/index.d.ts +5 -0
  14. package/dist/plugins/glsl/index.d.ts.map +1 -0
  15. package/dist/plugins/glsl/load-shader.d.ts +4 -0
  16. package/dist/plugins/glsl/load-shader.d.ts.map +1 -0
  17. package/dist/plugins/glsl/types.d.ts +21 -0
  18. package/dist/plugins/glsl/types.d.ts.map +1 -0
  19. package/dist/plugins/glsl-import.d.ts +4 -0
  20. package/dist/plugins/glsl-import.d.ts.map +1 -0
  21. package/dist/plugins/index.cjs +3038 -0
  22. package/dist/plugins/index.d.ts +11 -0
  23. package/dist/plugins/index.d.ts.map +1 -0
  24. package/dist/plugins/index.mjs +3017 -0
  25. package/dist/plugins/inject-nonce.d.ts +6 -0
  26. package/dist/plugins/inject-nonce.d.ts.map +1 -0
  27. package/dist/plugins/post-processing.d.ts +46 -0
  28. package/dist/plugins/post-processing.d.ts.map +1 -0
  29. package/dist/plugins/restart-on-rebuild.d.ts +36 -0
  30. package/dist/plugins/restart-on-rebuild.d.ts.map +1 -0
  31. package/dist/plugins/text-file.d.ts +17 -0
  32. package/dist/plugins/text-file.d.ts.map +1 -0
  33. package/dist/plugins/txt-loader.d.ts +3 -0
  34. package/dist/plugins/txt-loader.d.ts.map +1 -0
  35. package/dist/plugins/worker.d.ts +7 -0
  36. package/dist/plugins/worker.d.ts.map +1 -0
  37. package/package.json +75 -0
package/README.md ADDED
@@ -0,0 +1,738 @@
1
+ # @mgz-app/viteforge
2
+
3
+ Build and serve utilities for Vite. Pre-configured presets for apps, packages, and libraries, plus a collection of plugins for asset embedding, shader loading, HTML compression, dev reload, and more.
4
+
5
+ I built this for game jams. The goal: write a game, run one command, and get a single self-contained HTML file. No server required and no dependencies at runtime. The HTML file works like a binary: I can just send it to someone, they double-click it, and it runs (or, I host it anywhere and it just works). Everything (JavaScript, CSS, textures, models, shaders, audio) is inlined and compressed into one file that decompresses and boots itself in the browser.
6
+
7
+ Install this package and Vite. That's it.
8
+
9
+ ```json
10
+ {
11
+ "devDependencies": {
12
+ "vite": "^7.0.0",
13
+ "@mgz-app/viteforge": "^1.0.0"
14
+ }
15
+ }
16
+ ```
17
+
18
+ ---
19
+
20
+ ## Table of Contents
21
+
22
+ - [Quick Start](#quick-start)
23
+ - [Configuration Presets](#configuration-presets)
24
+ - [defineAppConfig](#defineappconfigoptions) - Single-page applications
25
+ - [definePackageConfig](#definepackageconfigoptions) - Reusable packages
26
+ - [defineLibraryConfig](#definelibraryconfigoptions) - npm libraries
27
+ - [defineViteConfig](#defineviteconfigoptions) - Base configuration
28
+ - [Library Presets](#library-presets)
29
+ - [Plugins](#plugins)
30
+ - [base64AssetPlugin](#base64assetplugin) - Embed assets as base64
31
+ - [txtLoaderPlugin](#txtloaderplugin) - Load text files as strings
32
+ - [textFilePlugin](#textfileplugin) - Virtual modules from files
33
+ - [injectNoncePlugin](#injectnonceplugin) - CSP nonce injection
34
+ - [calculateBuiltSizePlugin](#calculatebuiltsizeplugin) - Build size reporting
35
+ - [restartOnRebuildPlugin](#restartonrebuildplugin) - Auto-restart servers
36
+ - [getGLSLPlugin](#getglslplugin) - GLSL/WGSL shader imports
37
+ - [postProcessingPlugin](#postprocessingplugin) - HTML compression
38
+ - [workerPlugin](#workerplugin) - Web Worker bundling
39
+ - [devReloadPlugin](#devreloadplugin) - Browser reload on server rebuild
40
+ - [Dev Reload (Server + Client)](#dev-reload-server--client)
41
+ - [Building a Universal Library](#building-a-universal-library)
42
+ - [Monorepo Usage](#monorepo-usage)
43
+ - [Configuration Reference](#configuration-reference)
44
+ - [Re-exported Utilities](#re-exported-utilities)
45
+
46
+ ---
47
+
48
+ ## Quick Start
49
+
50
+ ### Single-page application
51
+
52
+ ```ts
53
+ // vite.config.ts
54
+ import { defineAppConfig, defineConfig } from "@mgz-app/viteforge";
55
+
56
+ export default defineConfig(defineAppConfig({ port: 3000 }));
57
+ ```
58
+
59
+ ```json
60
+ {
61
+ "scripts": {
62
+ "dev": "vite",
63
+ "build": "vite build",
64
+ "preview": "vite preview"
65
+ }
66
+ }
67
+ ```
68
+
69
+ ### npm library
70
+
71
+ ```ts
72
+ // vite.config.ts
73
+ import { defineConfig, defineLibraryConfig } from "@mgz-app/viteforge";
74
+
75
+ export default defineConfig(
76
+ defineLibraryConfig({
77
+ name: "my-lib",
78
+ preset: "npm-full"
79
+ })
80
+ );
81
+ ```
82
+
83
+ ```json
84
+ {
85
+ "scripts": {
86
+ "build": "vite build",
87
+ "dev": "vite build --watch"
88
+ }
89
+ }
90
+ ```
91
+
92
+ ### Node.js server with auto-restart
93
+
94
+ ```ts
95
+ // vite.config.ts
96
+ import { defineConfig, defineLibraryConfig, restartOnRebuildPlugin } from "@mgz-app/viteforge";
97
+
98
+ export default defineConfig(
99
+ defineLibraryConfig({
100
+ name: "my-server",
101
+ preset: "npm-esm",
102
+ additionalPlugins: [
103
+ restartOnRebuildPlugin({ startCommand: "node ./dist/index.mjs" })
104
+ ]
105
+ })
106
+ );
107
+ ```
108
+
109
+ ```json
110
+ {
111
+ "scripts": {
112
+ "dev": "vite build --watch",
113
+ "build": "vite build",
114
+ "start": "node ./dist/index.mjs"
115
+ }
116
+ }
117
+ ```
118
+
119
+ ---
120
+
121
+ ## Configuration Presets
122
+
123
+ ### `defineAppConfig(options)`
124
+
125
+ For single-page applications. Enables all the bells and whistles by default.
126
+
127
+ What you get:
128
+ - Single-file output in build mode (all JS/CSS inlined into one HTML file)
129
+ - HTML minification in build mode
130
+ - Base64 asset inlining
131
+ - CSP nonce injection
132
+ - Build size reporting
133
+ - Terser minification (drops console/debugger, strips comments)
134
+
135
+ ```ts
136
+ import { defineAppConfig, defineConfig } from "@mgz-app/viteforge";
137
+
138
+ export default defineConfig(
139
+ defineAppConfig({
140
+ port: 8080,
141
+ previewPort: 8081,
142
+ isDevMode: process.env.DEV_MODE === "true"
143
+ })
144
+ );
145
+ ```
146
+
147
+ | Feature | Dev Mode | Build Mode |
148
+ |---------|----------|------------|
149
+ | Single file output | No | Yes |
150
+ | HTML minification | No | Yes |
151
+ | JS minification | No | Yes (terser) |
152
+ | Base64 assets | Yes | Yes |
153
+ | CSP nonce | Yes | Yes |
154
+ | Build size report | No | Yes |
155
+
156
+ ### `definePackageConfig(options)`
157
+
158
+ Same as `defineAppConfig`, but with CSP nonce injection disabled. Use for packages that export components but don't have their own HTML.
159
+
160
+ ```ts
161
+ import { defineConfig, definePackageConfig } from "@mgz-app/viteforge";
162
+
163
+ export default defineConfig(definePackageConfig({ port: 8090 }));
164
+ ```
165
+
166
+ ### `defineLibraryConfig(options)`
167
+
168
+ For npm packages and libraries. Generates multiple output formats, TypeScript declarations, and includes a Vitest configuration.
169
+
170
+ ```ts
171
+ import { defineConfig, defineLibraryConfig } from "@mgz-app/viteforge";
172
+
173
+ export default defineConfig(
174
+ defineLibraryConfig({
175
+ name: "@my-org/my-lib",
176
+ preset: "npm-full",
177
+ external: ["three"],
178
+ globals: { three: "THREE" }
179
+ })
180
+ );
181
+ ```
182
+
183
+ See [Library Presets](#library-presets) for all available presets and [Building a Universal Library](#building-a-universal-library) for a complete walkthrough.
184
+
185
+ #### Options
186
+
187
+ | Option | Type | Default | Description |
188
+ |--------|------|---------|-------------|
189
+ | `name` | `string` | **(required)** | Package name |
190
+ | `preset` | `LibraryPreset` | `"npm-full"` | Output format preset |
191
+ | `entry` | `string` | `"src/index.ts"` | Entry point |
192
+ | `external` | `(string \| RegExp)[]` | `[]` | Dependencies to exclude from bundle |
193
+ | `globals` | `Record<string, string>` | `{}` | UMD/IIFE global variable mappings |
194
+ | `globalName` | `string` | auto-generated | Global name for UMD/IIFE builds |
195
+ | `buildMode` | `"mode-aware" \| "static"` | `"mode-aware"` | `mode-aware`: sourcemaps in dev, minify in prod. `static`: always sourcemaps, never minify |
196
+ | `useBase64Assets` | `boolean` | `false` | Enable `?base64` asset imports |
197
+ | `outDir` | `string` | `"./dist"` | Output directory |
198
+ | `formats` | `string[]` | from preset | Override preset formats |
199
+ | `rollupTypes` | `boolean` | `true` | Roll up `.d.ts` into a single file |
200
+ | `alwaysEmptyOutDir` | `boolean` | `true` | Empty output dir before each build |
201
+ | `preserveModules` | `boolean` | `false` | Emit one file per source module for fine-grained tree-shaking |
202
+ | `testEnvironment` | `"node" \| "jsdom"` | `"node"` | Vitest environment |
203
+ | `setupFiles` | `string[]` | - | Vitest setup files |
204
+ | `testTimeout` | `number` | - | Vitest timeout (ms) |
205
+ | `passWithNoTests` | `boolean` | `false` | Pass if no test files found |
206
+ | `additionalPlugins` | `Plugin[]` | `[]` | Additional Vite plugins |
207
+ | `minifiedBundles` | `boolean` | auto | Generate separate `.min.js` bundles |
208
+
209
+ ### `defineViteConfig(options)`
210
+
211
+ The base configuration that all presets build on. Use it when you want full control.
212
+
213
+ ```ts
214
+ import { defineConfig, defineViteConfig } from "@mgz-app/viteforge";
215
+
216
+ export default defineConfig(
217
+ defineViteConfig({
218
+ port: 3000,
219
+ useBase64Assets: true,
220
+ useNonceInjection: false,
221
+ singleFile: false,
222
+ minify: "esbuild"
223
+ })
224
+ );
225
+ ```
226
+
227
+ #### Options
228
+
229
+ | Option | Type | Default | Description |
230
+ |--------|------|---------|-------------|
231
+ | `root` | `string` | `process.cwd()` | Project root directory |
232
+ | `port` | `number` | `4173` | Dev server port |
233
+ | `previewPort` | `number` | `4174` | Preview server port |
234
+ | `outDir` | `string` | `"./dist"` | Build output directory |
235
+ | `cacheDir` | `string` | `".vite"` | Vite cache directory |
236
+ | `singleFile` | `boolean` | build: `true`, dev: `false` | Bundle into single HTML file |
237
+ | `minifyHtml` | `boolean` | build: `true`, dev: `false` | Minify HTML output |
238
+ | `minify` | `boolean \| "terser" \| "esbuild"` | `"terser"` | JS minification strategy |
239
+ | `useBase64Assets` | `boolean` | `true` | Enable base64 asset plugin |
240
+ | `useNonceInjection` | `boolean` | `true` | Enable CSP nonce injection |
241
+ | `calculateBuiltSize` | `boolean` | `true` | Report build size |
242
+ | `isDevMode` | `boolean` | `false` | Dev mode (strips CSP meta tag) |
243
+ | `additionalPlugins` | `any[]` | `[]` | Additional Vite plugins |
244
+
245
+ ---
246
+
247
+ ## Library Presets
248
+
249
+ | Preset | Formats | Use Case |
250
+ |--------|---------|----------|
251
+ | `"internal"` | ES | Packages consumed only within your monorepo |
252
+ | `"npm-esm"` | ES | Modern ESM-only npm packages |
253
+ | `"npm-full"` | ES + CJS | Maximum npm compatibility (default) |
254
+ | `"cdn"` | UMD + IIFE | Script tags and CDN distribution |
255
+ | `"universal"` | ES + CJS + UMD + IIFE | Every distribution channel |
256
+
257
+ Output file extensions:
258
+
259
+ | Format | Extension | Loaded via |
260
+ |--------|-----------|------------|
261
+ | ES | `.mjs` | `import` |
262
+ | CJS | `.cjs` | `require()` |
263
+ | UMD | `.umd.js` | AMD, CommonJS, or `<script>` tag |
264
+ | IIFE | `.iife.js` | `<script>` tag |
265
+
266
+ ---
267
+
268
+ ## Plugins
269
+
270
+ All plugins can be imported from the package root or from the `/plugins` subpath:
271
+
272
+ ```ts
273
+ import { base64AssetPlugin } from "@mgz-app/viteforge";
274
+ // or
275
+ import { base64AssetPlugin } from "@mgz-app/viteforge/plugins";
276
+ ```
277
+
278
+ ### `base64AssetPlugin()`
279
+
280
+ Embeds assets as base64 data URIs directly in your JavaScript bundle.
281
+
282
+ ```ts
283
+ // Import an asset — returns a data URI string
284
+ import model from "./model.glb";
285
+ // "data:model/gltf-binary;base64,..."
286
+
287
+ // Use ?base64 for raw base64 (no data URI prefix) — works with any file type
288
+ import wasmBase64 from "./physics.wasm?base64";
289
+ // "SGVsbG8gd29ybGQ="
290
+
291
+ // Or use the base64: prefix — same behavior, reads like intent
292
+ import wasmBase64 from "base64:./physics.wasm";
293
+
294
+ // Both syntaxes work with node_modules
295
+ import font from "some-package/font.ttf?base64";
296
+ // or
297
+ import font from "base64:some-package/font.ttf";
298
+ ```
299
+
300
+ Supported extensions (auto-detected, returned as data URIs):
301
+ `.glb`, `.gltf`, `.fbx`, `.obj`, `.png`, `.jpg`, `.jpeg`, `.gif`, `.webm`, `.mp4`, `.mp3`, `.ogg`, `.wav`, `.ttf`, `.otf`
302
+
303
+ SVGs get special treatment: they are optimized with svgo and returned as URL-encoded data URIs (smaller than base64 for SVGs).
304
+
305
+ The `?base64` query works with **any** file type and returns a raw base64 string.
306
+
307
+ ### `txtLoaderPlugin()`
308
+
309
+ Loads text-based files as string exports. Always enabled in all presets.
310
+
311
+ ```ts
312
+ import readme from "./data.txt";
313
+ import lut from "./color-grading.cube";
314
+ import lut2 from "./lookup.3dl";
315
+ ```
316
+
317
+ Supported: `.txt`, `.cube` (LUT), `.3dl` (LUT)
318
+
319
+ ### `textFilePlugin(virtualModuleId, filePath)`
320
+
321
+ Creates a virtual module that exports a file's contents as a string. Useful for embedding generated code.
322
+
323
+ ```ts
324
+ // vite.config.ts
325
+ import { defineConfig, resolve, textFilePlugin } from "@mgz-app/viteforge";
326
+
327
+ export default defineConfig({
328
+ plugins: [
329
+ textFilePlugin("iframe-runner-code", resolve(__dirname, "./dist/iframe-runner.js"))
330
+ ]
331
+ });
332
+
333
+ // In your code
334
+ import iframeCode from "iframe-runner-code";
335
+ ```
336
+
337
+ ### `injectNoncePlugin(options?)`
338
+
339
+ Injects a CSP nonce into all `<script>` tags and updates the CSP `<meta>` tag. A new random nonce is generated on each build.
340
+
341
+ ```ts
342
+ injectNoncePlugin({ isDevMode: false })
343
+ ```
344
+
345
+ Your HTML must include a CSP meta tag:
346
+
347
+ ```html
348
+ <meta http-equiv="Content-Security-Policy" content="script-src 'self';">
349
+ ```
350
+
351
+ Set `isDevMode: true` to remove the CSP meta tag entirely (for easier debugging in development).
352
+
353
+ ### `calculateBuiltSizePlugin(options?)`
354
+
355
+ Logs the total build output size in bytes, SI (kB/MB), and IEC (KiB/MiB) after compilation. Only runs in build mode.
356
+
357
+ ```ts
358
+ calculateBuiltSizePlugin({ outputDir: "./dist" })
359
+ ```
360
+
361
+ ### `restartOnRebuildPlugin(options)`
362
+
363
+ Kills and restarts a Node.js process after each rebuild. Designed for `vite build --watch`.
364
+
365
+ ```ts
366
+ restartOnRebuildPlugin({
367
+ startCommand: "node ./dist/index.mjs",
368
+ delay: 100 // ms to wait before starting (default: 100)
369
+ })
370
+ ```
371
+
372
+ ### `getGLSLPlugin()`
373
+
374
+ Enables importing GLSL and WGSL shader files. This is an async function — use `await`.
375
+
376
+ ```ts
377
+ // vite.config.ts
378
+ import { defineConfig, getGLSLPlugin } from "@mgz-app/viteforge";
379
+
380
+ export default defineConfig({
381
+ plugins: [await getGLSLPlugin()]
382
+ });
383
+
384
+ // In your code
385
+ import vertexShader from "./shaders/vertex.vert";
386
+ import fragmentShader from "./shaders/fragment.frag";
387
+ import computeShader from "./shaders/compute.wgsl";
388
+ ```
389
+
390
+ Supported: `.glsl`, `.wgsl`, `.vert`, `.vs`, `.frag`, `.fs`
391
+
392
+ Shaders support `#include` directives for composing shaders from multiple files.
393
+
394
+ ### `postProcessingPlugin(options?)`
395
+
396
+ Compresses the final HTML bundle using deflate. Extracts all inline JS/CSS, compresses into a binary blob, and wraps it in a self-extracting HTML page. The result is a single HTML file that decompresses and runs itself.
397
+
398
+ ```ts
399
+ postProcessingPlugin({
400
+ enabled: true,
401
+ compressionLevel: 9, // 1-9 (default: 9)
402
+ logStats: true, // Log compression stats (default: true)
403
+ titleString: "My Game", // HTML <title> tag
404
+ iconString: '<svg>...</svg>', // SVG favicon (optimized with svgo)
405
+ usePako: false // false: native DecompressionStream (default)
406
+ // true: bundle pako for older browsers
407
+ })
408
+ ```
409
+
410
+ Output example:
411
+ ```
412
+ [post-processing] Compression complete:
413
+ Original HTML: 1.14 MB
414
+ Script content: 1.14 MB
415
+ Compressed blob: 404.84 KB (34.8% of script)
416
+ Final HTML: 406.11 KB
417
+ Total savings: 758.74 KB (65.1% reduction)
418
+ ```
419
+
420
+ Set `usePako: true` if you need to support Firefox < 105 or Safari < 16.4 (browsers without `DecompressionStream`).
421
+
422
+ ### `workerPlugin()`
423
+
424
+ Bundles `.worker` imports as separate IIFE bundles and returns blob URLs for `new Worker()` instantiation.
425
+
426
+ ```ts
427
+ // vite.config.ts
428
+ export default defineConfig({
429
+ plugins: [workerPlugin()]
430
+ });
431
+
432
+ // In your code
433
+ import workerUrl from "./physics.worker";
434
+ const worker = new Worker(workerUrl);
435
+ ```
436
+
437
+ The worker file should be a `.ts` file — the plugin appends `.ts` when resolving. The worker is compiled as a minified IIFE with all dependencies bundled (no externals).
438
+
439
+ ### `devReloadPlugin(options?)`
440
+
441
+ Starts a lightweight WebSocket server during `vite build --watch`. When a rebuild completes, all connected browser clients receive a reload signal.
442
+
443
+ See [Dev Reload](#dev-reload-server--client) for the full setup.
444
+
445
+ ```ts
446
+ devReloadPlugin({ port: 21816 }) // default port
447
+ ```
448
+
449
+ ---
450
+
451
+ ## Dev Reload (Server + Client)
452
+
453
+ When your Node.js server serves the client directly (no separate Vite dev server), you need a way to tell the browser "the server rebuilt, reload." This is a two-piece setup:
454
+
455
+ ### Server side
456
+
457
+ Add `devReloadPlugin` to your **server's** vite.config.ts. It starts a WebSocket server inside the Vite build process and sends a `"change"` message to all connected browsers after every rebuild.
458
+
459
+ ```ts
460
+ // server/vite.config.ts
461
+ import { defineConfig, defineLibraryConfig, devReloadPlugin } from "@mgz-app/viteforge";
462
+
463
+ export default defineConfig(
464
+ defineLibraryConfig({
465
+ name: "my-server",
466
+ preset: "npm-esm",
467
+ additionalPlugins: [
468
+ devReloadPlugin({ port: 21816 })
469
+ ]
470
+ })
471
+ );
472
+ ```
473
+
474
+ ### Client side
475
+
476
+ Use `devReloadClientScript()` to get a JavaScript snippet that connects to the WebSocket server and calls `location.reload()` on any message. Inject it into your client HTML during development builds only.
477
+
478
+ ```ts
479
+ // client/vite.config.ts
480
+ import { defineAppConfig, defineConfig, devReloadClientScript } from "@mgz-app/viteforge";
481
+
482
+ const isDev = process.env.DEV_MODE === "true";
483
+
484
+ export default defineConfig(
485
+ defineAppConfig({
486
+ additionalPlugins: isDev
487
+ ? [
488
+ {
489
+ name: "inject-dev-reload",
490
+ transformIndexHtml(html) {
491
+ const script = devReloadClientScript({ port: 21816 });
492
+ return html.replace("</head>", `<script>${script}</script></head>`);
493
+ }
494
+ }
495
+ ]
496
+ : []
497
+ })
498
+ );
499
+ ```
500
+
501
+ The client snippet auto-reconnects (up to 5 times) when the connection drops — which happens when the server process restarts.
502
+
503
+ In production, you simply don't add either plugin. No dev code in your prod builds.
504
+
505
+ ---
506
+
507
+ ## Building a Universal Library
508
+
509
+ A complete recipe for a library consumable via ESM, CJS, UMD (AMD/CDN), and IIFE (script tag).
510
+
511
+ ### vite.config.ts
512
+
513
+ ```ts
514
+ import { defineConfig, defineLibraryConfig } from "@mgz-app/viteforge";
515
+
516
+ export default defineConfig(
517
+ defineLibraryConfig({
518
+ name: "@my-org/my-lib",
519
+ preset: "universal",
520
+ globalName: "MyLib",
521
+ external: ["three"],
522
+ globals: { three: "THREE" }
523
+ })
524
+ );
525
+ ```
526
+
527
+ ### package.json
528
+
529
+ ```json
530
+ {
531
+ "name": "@my-org/my-lib",
532
+ "type": "module",
533
+ "main": "./dist/index.cjs",
534
+ "module": "./dist/index.mjs",
535
+ "browser": "./dist/index.umd.js",
536
+ "unpkg": "./dist/index.umd.js",
537
+ "jsdelivr": "./dist/index.umd.js",
538
+ "types": "./dist/index.d.ts",
539
+ "exports": {
540
+ ".": {
541
+ "types": "./dist/index.d.ts",
542
+ "import": "./dist/index.mjs",
543
+ "require": "./dist/index.cjs"
544
+ }
545
+ },
546
+ "files": ["./dist"]
547
+ }
548
+ ```
549
+
550
+ **Order matters in `exports`.** `types` must come first.
551
+
552
+ **Do NOT put `"browser"` inside the `exports` field** — it causes bundlers to pick UMD over ESM. The top-level `"browser"` field is fine for legacy tooling.
553
+
554
+ ### tsconfig.lib.json
555
+
556
+ Required by the dts plugin:
557
+
558
+ ```json
559
+ {
560
+ "extends": "./tsconfig.json",
561
+ "compilerOptions": {
562
+ "declaration": true,
563
+ "declarationMap": true,
564
+ "emitDeclarationOnly": true
565
+ },
566
+ "include": ["src/**/*"],
567
+ "exclude": ["node_modules", "dist", "**/*.test.ts", "**/*.spec.ts"]
568
+ }
569
+ ```
570
+
571
+ ### Output
572
+
573
+ ```
574
+ dist/
575
+ index.mjs # ESM
576
+ index.cjs # CJS
577
+ index.umd.js # UMD
578
+ index.iife.js # IIFE
579
+ index.d.ts # TypeScript declarations
580
+ index.d.ts.map # Declaration source maps
581
+ ```
582
+
583
+ ### How consumers use it
584
+
585
+ ```ts
586
+ // ESM
587
+ import { MyClass } from "@my-org/my-lib";
588
+
589
+ // CJS
590
+ const { MyClass } = require("@my-org/my-lib");
591
+
592
+ // CDN
593
+ <script src="https://unpkg.com/@my-org/my-lib"></script>
594
+ <script>
595
+ const { MyClass } = window.MyLib;
596
+ </script>
597
+ ```
598
+
599
+ ### Best practices
600
+
601
+ Use **named exports only**. Avoid default exports — they cause CJS consumers to need `.default`:
602
+
603
+ ```ts
604
+ // Don't do this
605
+ export default MyClass;
606
+
607
+ // Do this
608
+ export { MyClass };
609
+ ```
610
+
611
+ ---
612
+
613
+ ## Monorepo Usage
614
+
615
+ The presets produce clean configs with no monorepo-specific settings. If you're in a monorepo with workspace packages, use `mergeConfig` to layer your monorepo defaults on top:
616
+
617
+ ```ts
618
+ // shared/monorepo-config.ts
619
+ import { defineAppConfig, mergeConfig } from "@mgz-app/viteforge";
620
+
621
+ const monorepoDefaults = {
622
+ server: {
623
+ watch: { ignored: ["!**/node_modules/@my-org/**"] },
624
+ fs: { allow: ["../.."] }
625
+ },
626
+ optimizeDeps: {
627
+ exclude: ["@my-org/*"],
628
+ include: ["three"]
629
+ }
630
+ };
631
+
632
+ export const defineMonorepoAppConfig = (opts) =>
633
+ (env) => mergeConfig(defineAppConfig(opts)(env), monorepoDefaults);
634
+ ```
635
+
636
+ ```ts
637
+ // apps/my-app/vite.config.ts
638
+ import { defineConfig } from "@mgz-app/viteforge";
639
+ import { defineMonorepoAppConfig } from "../../shared/monorepo-config";
640
+
641
+ export default defineConfig(defineMonorepoAppConfig({ port: 3000 }));
642
+ ```
643
+
644
+ ---
645
+
646
+ ## Configuration Reference
647
+
648
+ ### Terser options
649
+
650
+ Production builds use aggressive terser settings by default:
651
+
652
+ - Mangles all names (toplevel, module, eval)
653
+ - Drops `console.*` and `debugger` statements
654
+ - Strips all comments
655
+ - 2 optimization passes
656
+
657
+ The `terserOptions` object is exported if you need to reference or extend it.
658
+
659
+ ### Asset handling
660
+
661
+ All presets recognize these file types as assets:
662
+ `.glb`, `.gltf`, `.bin`, `.wasm`, `.png`, `.jpg`, `.svg`, `.jpeg`, `.gif`, `.mp4`, `.webm`, `.mp3`, `.wav`, `.ogg`, `.ttf`, `.otf`
663
+
664
+ In build mode, assets are inlined (the inline limit is set to 900MB — effectively everything).
665
+
666
+ ---
667
+
668
+ ## Re-exported Utilities
669
+
670
+ The package re-exports these for convenience so you don't need extra imports:
671
+
672
+ ```ts
673
+ import { defineConfig, mergeConfig, resolve, dts } from "@mgz-app/viteforge";
674
+ import type { Plugin } from "@mgz-app/viteforge";
675
+ ```
676
+
677
+ - `defineConfig` and `mergeConfig` from `vite`
678
+ - `dts` from `vite-plugin-dts`
679
+ - `resolve` from `path`
680
+ - `Plugin` type from `vite`
681
+
682
+ ---
683
+
684
+ ## Releasing
685
+
686
+ This package uses [Changesets](https://github.com/changesets/changesets) for versioning and publishing.
687
+
688
+ ### When you make a change
689
+
690
+ After making your changes, create a changeset to describe what changed:
691
+
692
+ ```sh
693
+ pnpm changeset
694
+ ```
695
+
696
+ You'll be prompted to:
697
+ 1. Select the package
698
+ 2. Choose the bump type:
699
+ - **patch** — bug fixes, dependency updates, internal refactors
700
+ - **minor** — new plugins, new config options, new features
701
+ - **major** — breaking changes to existing presets, removed options, renamed exports
702
+ 3. Write a summary of the change
703
+
704
+ Commit the changeset file with your code.
705
+
706
+ ### How publishing works
707
+
708
+ When you push to `master` (or merge a PR):
709
+
710
+ 1. The CI **release** workflow detects pending changeset files
711
+ 2. It opens a "Version Package" PR that bumps the version in `package.json` and updates `CHANGELOG.md`
712
+ 3. When you merge that PR, the workflow publishes to npm automatically via trusted publishing (OIDC)
713
+
714
+ No npm tokens to manage or rotate. GitHub Actions authenticates directly with npm.
715
+
716
+ ### First-time setup
717
+
718
+ The very first publish must be done manually because trusted publishing requires the package to already exist on npm.
719
+
720
+ 1. Use your existing npm token (or create a temporary 90-day granular one at Profile > Access Tokens)
721
+ 2. Publish manually:
722
+ ```sh
723
+ npm publish --access public --//registry.npmjs.org/:_authToken=$(cat ~/.npmtoken)
724
+ ```
725
+ 3. Configure trusted publishing on npm: go to `https://www.npmjs.com/package/@mgz-app/viteforge/access`, click **GitHub Actions**, and fill in:
726
+ - **Organization or user**: `marcogomez` (case-sensitive)
727
+ - **Repository**: `viteforge`
728
+ - **Workflow filename**: `release.yml`
729
+ - **Allowed actions**: select "npm publish"
730
+ 4. Delete the temporary npm token
731
+
732
+ ### Manual publishing (if needed)
733
+
734
+ ```sh
735
+ pnpm changeset # create a changeset
736
+ pnpm run version # bump version + update CHANGELOG
737
+ pnpm run release # publish to npm
738
+ ```