vanilla-builder 0.2.0 → 0.3.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 CHANGED
@@ -1,232 +1,607 @@
1
1
  # vanilla-builder
2
2
 
3
- [![CI](https://github.com/MarioACo/vanilla-builder/actions/workflows/ci.yml/badge.svg)](https://github.com/MarioACo/vanilla-builder/actions/workflows/ci.yml) [![npm](https://img.shields.io/npm/v/vanilla-builder)](https://www.npmjs.com/package/vanilla-builder) [![MIT](https://img.shields.io/badge/license-MIT-blue)](LICENSE)
3
+ [![CI](https://github.com/MarioACo/vanilla-builder/actions/workflows/ci.yml/badge.svg)](https://github.com/MarioACo/vanilla-builder/actions/workflows/ci.yml) [![npm](https://img.shields.io/npm/v/vanilla-builder)](https://www.npmjs.com/package/vanilla-builder) [![Node](https://img.shields.io/badge/node-%3E%3D16-brightgreen)](#requirements) [![MIT](https://img.shields.io/badge/license-MIT-blue)](LICENSE)
4
4
 
5
5
  **English** · [Español](README.es.md)
6
6
 
7
- Bundle, minify and obfuscate **vanilla** web projects (HTML, CSS and JavaScript, no framework) **without losing anything**.
8
- It follows the real dependencies of every page, verifies the output before publishing it and never deletes what it did not create.
7
+ **A production build for plain HTML, CSS and JavaScript sites — no framework, no config, nothing lost.**
9
8
 
10
- - **Zero config**: point it at a folder with `.html` files and you are done.
11
- - **Lossless**: every project file ends up with a known fate (processed, bundled, copied or deliberately skipped), and the output is verified before it replaces `dist/`.
12
- - **Fast**: obfuscation and terser run in parallel threads, and a persistent cache makes rebuilds of unchanged files almost free (obfuscated 40-page test site: 11–18 s in 0.1.0 → 7 s first build, 1.8 s rebuild). See [Speed](#speed).
13
- - **You decide what is a library**: third-party code is detected and never obfuscated; correct it from the HTML, the file itself or `builder deps`. See [Dependencies](#third-party-dependencies-and-obfuscation).
14
- - **TypeScript included**: `<script src="app.ts">` just works; the output is `.js`.
15
- - **Lightweight**: the base install is 8 packages (most of it is the esbuild binary). The obfuscator and terser are only installed if you use them.
16
- - **Node 16 or newer** (tested on 16, 18, 20, 22 and 24, on Linux, Windows and macOS).
9
+ Point it at a folder with `.html` files. It finds every script, stylesheet, image and page they use, bundles and minifies them, can obfuscate your JavaScript (while leaving third-party libraries alone), checks the result, and only then writes `dist/`.
17
10
 
18
- ## Install
11
+ ```text
12
+ my-site/ my-site/dist/
13
+ ├── index.html ├── index.html ← minified, paths updated
14
+ ├── about.html ├── about.html
15
+ ├── css/main.css (+ @imports) ├── css/main.css ← one file, minified
16
+ ├── js/app.js (+ imports) → ├── js/app.js ← bundled, minified, optionally obfuscated
17
+ ├── js/vendor/jquery.min.js ├── js/vendor/jquery.min.js ← detected as a library, never obfuscated
18
+ ├── img/… ├── img/… ← copied as is
19
+ └── .env └── (not published: secrets are excluded)
20
+ ```
21
+
22
+ ## Contents
23
+
24
+ - [Why vanilla-builder](#why-vanilla-builder)
25
+ - [How it compares](#how-it-compares)
26
+ - [Requirements](#requirements)
27
+ - [Quick start](#quick-start)
28
+ - [Common tasks](#common-tasks)
29
+ - [How it works](#how-it-works)
30
+ - [Third-party libraries and obfuscation](#third-party-libraries-and-obfuscation)
31
+ - [TypeScript](#typescript)
32
+ - [Configuration](#configuration)
33
+ - [Plugins](#plugins)
34
+ - [GitHub Action](#github-action)
35
+ - [Command-line reference](#command-line-reference)
36
+ - [Safety guarantees](#safety-guarantees)
37
+ - [Performance](#performance)
38
+ - [JavaScript API](#javascript-api)
39
+ - [Troubleshooting](#troubleshooting)
40
+ - [Known limitations](#known-limitations)
41
+ - [Contributing](#contributing)
42
+
43
+ ## Why vanilla-builder
44
+
45
+ Tools like Vite or webpack assume an app with an entry point and a module graph. Many real sites are not like that: several HTML pages, classic `<script>` tags that share globals, a copied `jquery.min.js`, a service worker with a list of files. vanilla-builder is made for those sites.
46
+
47
+ - **Zero configuration.** Every complete `.html` file is an entry point. Nothing to declare.
48
+ - **Nothing is lost.** Every file in the project ends with a known fate (processed, bundled, copied or deliberately excluded) and you can see which one. Files it cannot trace are still published.
49
+ - **Verified output.** The build is written to a temporary folder, checked (every reference resolves, every JS file parses, every source file is accounted for) and only then swapped into `dist/`. A failed build never leaves you with a broken `dist/`.
50
+ - **Safe for classic scripts.** It only merges or minifies scripts when it can prove the global variables stay the same.
51
+ - **Obfuscation that respects libraries.** Your code is protected; jQuery, Bootstrap and npm packages are detected and left as they are. You can correct the detection in one line.
52
+ - **TypeScript without setup.** `<script src="app.ts">` works; the output is `.js`.
53
+ - **Fast.** Parallel threads and a persistent cache: rebuilding an obfuscated 40-page site takes under 2 seconds ([numbers](#performance)).
54
+ - **Production extras built in.** Target browsers from `browserslist`, optional image optimization, size budgets that fail CI, plugins, and a GitHub Action with a size report on every pull request.
55
+
56
+ ## How it compares
57
+
58
+ The same test sites built with each tool's default settings for a multi-page site, then opened in Chromium next to the original ([full results and method](bench/compare/RESULTS.md), reproducible with `cd bench/compare && npm install && node run.mjs`):
59
+
60
+ | Test site | vanilla-builder | Parcel 2.16 | Vite 8.3 |
61
+ | --- | --- | --- | --- |
62
+ | Classic scripts sharing globals, a worker, a copied library | ✅ identical | ❌ build fails (worker created from a string) | ❌ 404s: classic scripts are not published |
63
+ | ES modules, import map, service worker, CSS module script | ✅ identical | ❌ build fails (service worker from a string) | ❌ build fails (CSS import attribute) |
64
+ | npm packages from modules and classic scripts | ✅ identical | ✅ identical | ❌ 404s: classic scripts are not published |
65
+
66
+ Build time and size on a generated 40-page site (cold, no cache), counting only outputs whose pages work:
67
+
68
+ | | Time | Output (gzip) |
69
+ | --- | ---: | ---: |
70
+ | vanilla-builder, publishing only referenced files (`"unreferenced": "skip"`) | **1.2 s** | **64.8 KB** |
71
+ | vanilla-builder, default (also publishes unreferenced files) | 1.2 s | 156.6 KB |
72
+ | Parcel | 2.1–2.9 s | 102.6 KB |
73
+ | Vite | 0.3 s | — (output broken: 12 problems on 3 sample pages, such as 404s for classic scripts) |
74
+
75
+ These tools aim at different things: Vite and Parcel shine for apps with an entry point and a module graph. vanilla-builder is for sites that are a folder of pages and must keep working exactly as before.
76
+
77
+ ## Requirements
78
+
79
+ - **Node.js 16 or newer.** Tested on Node 16, 18, 20, 22 and 24, on Linux, Windows and macOS.
80
+ - Optional, only if you use them: [`javascript-obfuscator`](https://www.npmjs.com/package/javascript-obfuscator) for `--obfuscate`, [`terser`](https://www.npmjs.com/package/terser) for `--minifier terser`, [`browserslist`](https://www.npmjs.com/package/browserslist) for target browsers and [`sharp`](https://www.npmjs.com/package/sharp) (Node 20+) for `--images`.
81
+
82
+ On Node 16 and 17, npm prints an `EBADENGINE` warning because esbuild declares Node 18+. It is only a warning; the full test suite passes on Node 16.
83
+
84
+ ## Quick start
85
+
86
+ **1. Install it in your project:**
19
87
 
20
88
  ```bash
21
89
  npm install --save-dev vanilla-builder
22
- npm install --save-dev javascript-obfuscator # only if you obfuscate
23
- npm install --save-dev terser # only if you want maximum compression
24
90
  ```
25
91
 
26
- > On Node 16 and 17 npm prints an `EBADENGINE` warning because esbuild declares Node 18+. It is only a warning:
27
- > the whole test suite (browser tests included) passes when building with Node 16.
92
+ **2. Build:**
28
93
 
29
- ## Usage
94
+ ```bash
95
+ npx builder build
96
+ ```
97
+
98
+ The result is in `dist/`. The terminal shows a table with each output file and its size before, after, gzipped and brotli-compressed, then confirms the verification:
99
+
100
+ ```text
101
+ ✔ 3 html · 8 js · 1 css · 1 json · 4 svg · 1 text (0.21 s)
102
+ ✔ Verification: 18 files written, 17 references checked, 8 valid JS, 0 errors
103
+ ✔ Coverage: 21/21 source files with a known destination
104
+ ```
105
+
106
+ **3. Try the result in the browser:**
30
107
 
31
108
  ```bash
32
- npx builder build my-site # builds into my-site/dist
33
- npx builder build my-site --obfuscate # also obfuscates your JavaScript (medium level)
34
- npx builder analyze my-site # shows what will happen to each file, writes nothing
35
- npx builder deps my-site # which JS files are libraries (not obfuscated) and which are yours
36
- npx builder init my-site # creates builder.config.json from what it detected
37
- npx builder dev my-site # builds, watches and serves dist/ with live reload
38
- npx builder serve my-site # serves dist/ to try it out
109
+ npx builder serve # http://127.0.0.1:5173
39
110
  ```
40
111
 
41
- Common `build` options (all of them have a config equivalent):
112
+ Optionally, add scripts to your `package.json`:
42
113
 
43
- | Option | What it does |
44
- | --- | --- |
45
- | `--obfuscate [low\|medium\|high\|max]` | Obfuscates your JS. Third-party libraries are detected and left untouched. |
46
- | `--dep <pattern>` / `--not-dep <pattern>` | Manually mark a dependency / correct a detection. |
47
- | `--obfuscate-deps` | Obfuscate dependencies too. |
48
- | `--minifier terser` | Second compression pass with terser (even smaller JS). |
49
- | `--compress [gzip\|brotli]` | Also emits `.gz` and `.br` files for servers that serve precompressed assets. |
50
- | `--hash` | Hashed JS/CSS file names for long-term caching. |
51
- | `--sourcemap [linked\|hidden\|inline]` | Source maps. |
52
- | `--target es2018` | Transpile to a specific level (default: whatever your own code already uses). |
53
- | `--drop-console` | Removes `console.*` and `debugger`. |
54
- | `--entry`, `--include`, `--ignore` | Explicit entries, forced files and exclusions (repeatable). |
55
- | `--workers <n\|auto>` | Threads for obfuscation/terser (`0` = no threads). |
56
- | `--no-cache` | Ignore the cache from previous builds. |
57
- | `--json` | JSON output for CI (exit code 1 if anything fails), including time per phase. |
58
-
59
- Run `npx builder help` for the full list.
60
-
61
- ## What it detects on its own
62
-
63
- Starting from each `.html` file, it follows:
64
-
65
- - Classic and `type="module"` `<script src>`, inline scripts and **import maps**.
66
- - `<link rel="stylesheet">`, CSS `@import` and `url()` (including `image-set()`), `<style>` and `style` attributes.
67
- - Images, `srcset`, video, audio, icons, manifest, `og:image`, `<iframe>`, `<object>`, SVG `<use>`…
68
- - Links to other pages, dynamic `import()`, `new URL('./x', import.meta.url)`.
69
- - Workers, service workers and `importScripts()`.
70
- - Paths written as strings in JS (for example a service worker precache list): those files are also published at their original path.
71
-
72
- What cannot be followed (a `fetch(variable)`, a server template…) **is not lost**: the folder structure is preserved and everything unreferenced is still published (`unreferenced: "process"`).
73
-
74
- ## Guarantees
75
-
76
- | Typical bundler risk | How vanilla-builder avoids it |
77
- | --- | --- |
78
- | Concatenating classic scripts breaks globals | Only merges consecutive scripts with the same `defer`/strict mode, no `document.currentScript`, no `let/const` redeclarations, and a safe `;` separator. |
79
- | Minification creates new globals or renames yours | Compares globals before and after; if they differ it falls back to a more conservative minification or keeps the original. |
80
- | The obfuscator breaks exports or `with { type }` attributes | Checks exports, imports and attributes after obfuscating; if they do not match, that file is not obfuscated. |
81
- | esbuild silently drops `with { type: 'css' }` | Import attributes are forcibly preserved. |
82
- | HTML minification changes the text | Own minifier on top of parse5 plus a structural check (elements, attributes and text). `<pre>`, `<textarea>` and templates stay intact; if the CSS uses `white-space: pre`, whitespace is not collapsed. |
83
- | Stale `integrity` (SRI) blocks scripts | Recomputed whenever the content changes. |
84
- | Legacy encodings or BOM | Bytes are preserved (HTML) or JS/CSS is emitted with ASCII escapes. |
85
- | Reformatted JSON loses precision | Only whitespace outside strings is removed: `2.0` and huge numbers stay the same. |
86
- | Wiping the wrong `dist/` | Never empties a folder it did not create (marked with `.builder-manifest.json`), nor the project folder, nor the root. |
87
- | Half-finished build | Writes into a temp folder, verifies it, and only then replaces `dist/`. |
88
- | Publishing secrets | `.env*`, `.pem/.key` keys, `node_modules`, `package.json` and tool configs are excluded by default. |
114
+ ```json
115
+ {
116
+ "scripts": {
117
+ "dev": "builder dev",
118
+ "build": "builder build"
119
+ }
120
+ }
121
+ ```
122
+
123
+ > Commands accept a folder: `npx builder build my-site` builds `my-site/` into `my-site/dist/`. Without one, the current folder is used.
124
+ >
125
+ > Messages are in English, or in Spanish when your system is (`--lang es|en` or `BUILDER_LANG` to choose). `--json` field names are always English.
126
+
127
+ Prefer to start from something that works? See the runnable [examples](examples/): a classic multi-page site, a TypeScript app and an obfuscated app with size budgets.
128
+
129
+ ## Common tasks
130
+
131
+ ### Develop with live reload
132
+
133
+ ```bash
134
+ npx builder dev # builds, watches for changes, serves dist/ and reloads the browser
135
+ npx builder dev --port 3000 --open
136
+ ```
137
+
138
+ When only CSS changes, stylesheets are swapped in place without reloading the page, so you keep its state.
139
+
140
+ ### Publish a static site (Netlify, GitHub Pages, S3, nginx…)
141
+
142
+ ```bash
143
+ npx builder build --hash --compress
144
+ ```
145
+
146
+ - `--hash` renames JS and CSS to `name-<hash>.js` so you can cache them forever; HTML references are updated.
147
+ - `--compress` also writes `.gz` and `.br` next to each file for servers that serve precompressed files (nginx `gzip_static`, `brotli_static`).
148
+
149
+ If the site is served from a subfolder (`https://example.com/shop/`), add `--base /shop/` so absolute paths (`/js/app.js`) resolve correctly.
150
+
151
+ ### Protect your JavaScript
152
+
153
+ ```bash
154
+ npm install --save-dev javascript-obfuscator
155
+ npx builder build --obfuscate # medium level (recommended)
156
+ npx builder build --obfuscate high
157
+ ```
158
+
159
+ Third-party libraries are detected and not obfuscated. Check what was detected with `npx builder deps` — see [Third-party libraries](#third-party-libraries-and-obfuscation).
160
+
161
+ ### Make JavaScript as small as possible
162
+
163
+ ```bash
164
+ npm install --save-dev terser
165
+ npx builder build --minifier terser --drop-console
166
+ ```
167
+
168
+ ### Support older browsers
169
+
170
+ Declare your browsers the standard way and Builder uses them for CSS and JavaScript: missing vendor prefixes are added (`-webkit-backdrop-filter`), nested CSS is flattened and newer JavaScript syntax is rewritten.
171
+
172
+ ```json
173
+ // package.json
174
+ { "browserslist": ["> 0.5%", "last 2 versions", "not dead"] }
175
+ ```
176
+
177
+ ```bash
178
+ npm install --save-dev browserslist
179
+ ```
180
+
181
+ A `.browserslistrc` file works too, and so does `--browsers "<query>"`. Without browsers, your code keeps its own syntax level.
182
+
183
+ ### Optimize images
184
+
185
+ ```bash
186
+ npm install --save-dev sharp
187
+ npx builder build --images # PNG lossless, JPEG/WebP at quality 80
188
+ npx builder build --image-quality 70
189
+ ```
190
+
191
+ PNG files are recompressed without loss and checked pixel by pixel. JPEG and WebP are re-encoded at the chosen quality (this is lossy, visually equivalent at the default), keeping EXIF orientation and color profiles. File names stay the same, and an image is only replaced when the result is smaller.
192
+
193
+ ### Keep sizes under control
194
+
195
+ ```json
196
+ {
197
+ "budgets": {
198
+ "js/**/*.js": "50 KB",
199
+ "css/app.css": "20 KB gzip",
200
+ "total": "1 MB brotli"
201
+ }
202
+ }
203
+ ```
89
204
 
90
- The test suite opens every example site **original and built in Chromium** and requires identical results, with no errors and no 404s, across 5 variants (default, high obfuscation + hash, max obfuscation, terser + precompression, unminified).
205
+ If any file (or the total) goes over its budget, the build fails before touching `dist/` and lists what is over. In CI you can also pass `--budget "total=1MB gzip"`.
91
206
 
92
- ## Third-party dependencies and obfuscation
207
+ ### See what will happen before building
93
208
 
94
- Obfuscating jQuery, Bootstrap or an npm package protects nothing of yours, makes them slower and may violate their license. So when obfuscating, these are detected as dependencies and left untouched:
209
+ ```bash
210
+ npx builder analyze
211
+ ```
95
212
 
96
- - Files in `vendor/`, `node_modules/`, `third-party/`…
97
- - Well-known library names (`jquery-3.7.1.min.js`, `bootstrap.bundle.js`…) and compiled builds (`.min.js`, `.umd.js`).
98
- - Files named like a package in your `package.json` `dependencies` (`js/chart.umd.js` when you depend on `chart.js`).
99
- - UMD/AMD wrappers (the format libraries are published in), license headers with a version (`/*! Foo v1.2.0 */`) and already-minified code.
100
- - npm packages imported by your module code are split into `assets/vendor/` (not obfuscated, with their shared state intact).
213
+ Lists every file with what will happen to it, the detected libraries and workers, broken references that already exist in your source, and excluded files. Nothing is written.
101
214
 
102
- See what was detected and why:
215
+ ### Use it in CI
103
216
 
104
217
  ```bash
105
- npx builder deps my-site
218
+ npx builder build --json > build-report.json
106
219
  ```
107
220
 
108
- Something wrong? There are three ways to fix it, pick the one that suits you:
221
+ - Exit code `0` on success and non-zero if the build or the verification fails; in that case `dist/` is left untouched and the JSON has `"ok": false` with the error.
222
+ - The JSON includes outputs, sizes, warnings, detected dependencies and time per phase (`perf.phases`).
223
+ - To reuse the cache between runs, set `BUILDER_CACHE_DIR` to a folder your CI caches.
224
+ - On GitHub, the [GitHub Action](#github-action) does all of this and adds a size report to each run and pull request.
225
+
226
+ ## How it works
227
+
228
+ 1. **Discover.** Every complete HTML page is parsed. From it, Builder follows scripts (classic and modules, inline and import maps), stylesheets (`@import`, `url()`, `image-set()`), images and `srcset`, media, icons, manifests, `og:image`, iframes, links to other pages, dynamic `import()`, `new URL('./x', import.meta.url)`, workers, service workers, `importScripts()` and paths written as strings in your code (such as a precache list).
229
+ 2. **Process.**
230
+ - ES modules are bundled with [esbuild](https://esbuild.github.io/) (code splitting included). The syntax level of your own code is kept: nothing is transpiled unless you ask with `--target`.
231
+ - Consecutive classic scripts are merged only when it is safe, then minified.
232
+ - CSS is bundled and minified; HTML is minified with a conservative minifier that keeps `<pre>`, `<textarea>` and templates intact.
233
+ - JSON loses only whitespace. Everything else (images, fonts, data) is copied.
234
+ 3. **Protect (optional).** Your JavaScript is obfuscated; libraries are not.
235
+ 4. **Verify.** In a temporary folder: every reference resolves, every JS file parses, sizes match, every source file has a destination.
236
+ 5. **Publish.** The temporary folder replaces `dist/`.
237
+
238
+ Files nothing references are still minified and published at their original path (`unreferenced: "process"`), so a `fetch(variable)` or a server template that loads a file keeps working.
239
+
240
+ ## Third-party libraries and obfuscation
241
+
242
+ Obfuscating jQuery, Bootstrap or an npm package protects nothing of yours, makes it slower and may break its license. When obfuscating, these are treated as libraries and left untouched:
243
+
244
+ | Signal | Example |
245
+ | --- | --- |
246
+ | Lives in a library folder | `vendor/`, `node_modules/`, `third-party/`, `external/`… |
247
+ | Well-known library name | `jquery-3.7.1.min.js`, `bootstrap.bundle.js`, `chart.umd.js` |
248
+ | Already compiled | `.min.js`, `.umd.js`, or already-minified code |
249
+ | Named like a package you depend on | `js/swiper.js` when `package.json` lists `swiper` in `dependencies` |
250
+ | Library wrapper | UMD/AMD (`typeof define === "function" && define.amd`) |
251
+ | License header with a version | `/*! Foo v1.2.0 */` |
252
+
253
+ npm packages imported by your modules (`import confetti from 'canvas-confetti'`) are bundled from `node_modules`. When obfuscating, they are split into separate files in `assets/vendor/`, left unobfuscated and shared between pages.
254
+
255
+ ### Check and correct the detection
256
+
257
+ ```bash
258
+ npx builder deps
259
+ ```
260
+
261
+ ```text
262
+ Third-party libraries (not obfuscated) (3)
263
+ • js/vendor/jquery.min.js — in vendor/
264
+ • js/chart.js — named like the "chart.js" package in your package.json
265
+ • js/slider.js — UMD/AMD wrapper (published library format)
266
+
267
+ Your code (obfuscated with --obfuscate) (1)
268
+ • js/app.js
269
+ ```
270
+
271
+ Add `--json` for `{ "dependencies": [{ "rel", "reason" }], "own": [] }`.
272
+
273
+ If something is classified wrong, fix it in whichever place suits you:
274
+
275
+ #### In the HTML
109
276
 
110
277
  ```html
111
- <!-- 1. In the HTML -->
112
- <script src="js/my-lib.js" data-builder="dependency"></script>
113
- <script src="js/app.min.js" data-builder="own"></script>
278
+ <script src="js/my-lib.js" data-builder="dependency"></script> <!-- a library: don't obfuscate -->
279
+ <script src="js/app.min.js" data-builder="own"></script> <!-- my code: obfuscate -->
114
280
  ```
115
281
 
282
+ #### At the top of the file
283
+
116
284
  ```js
117
- /* 2. At the top of the file itself */
118
- /* @builder-dependency */ // or: /* @builder-own */
285
+ /* @builder-dependency */ // or /* @builder-own */
119
286
  ```
120
287
 
288
+ #### From the terminal
289
+
290
+ Writes `builder.config.json` for you and keeps your other settings:
291
+
121
292
  ```bash
122
- # 3. From the terminal (edits builder.config.json for you, keeps the rest of it)
123
- npx builder deps add "js/libs/**" # these are libraries: don't obfuscate
124
- npx builder deps own js/app.min.js # this is my code: obfuscate it
125
- npx builder deps reset js/app.min.js # back to automatic detection
293
+ npx builder deps add "js/libs/**" # these are libraries
294
+ npx builder deps own js/app.min.js # this is my code
295
+ npx builder deps reset js/app.min.js # back to automatic detection
126
296
  ```
127
297
 
128
- The config file has the last word over the marks, and the marks over the automatic detection:
298
+ #### In the configuration
129
299
 
130
300
  ```json
131
301
  {
132
- "obfuscate": "high",
133
302
  "dependencies": {
134
303
  "include": ["js/my-library.js"],
135
- "exclude": ["js/app.min.js"],
136
- "obfuscate": false
304
+ "exclude": ["js/app.min.js"]
137
305
  }
138
306
  }
139
307
  ```
140
308
 
141
- `include` marks dependencies that were not detected; `exclude` says a file is yours and should be obfuscated; `obfuscate: true` obfuscates everything.
309
+ Priority: configuration › marks in the HTML or file › automatic detection. To obfuscate everything, libraries included, use `--obfuscate-deps` (`"dependencies": { "obfuscate": true }`).
142
310
 
143
- Levels: `low` (almost free), `medium` (recommended), `high` and `max` (also defends against being reformatted or tampered with; heavier and slower). Options that change behavior (`debugProtection`, `disableConsoleOutput`, `renameGlobals`) are never turned on automatically; pass them with an object: `"obfuscate": { "preset": "high", "debugProtection": true }`. Your license comments (`/*! … */`, `@license`) are kept.
311
+ ### Obfuscation levels
312
+
313
+ | Level | Use it for |
314
+ | --- | --- |
315
+ | `low` | Renaming and string hiding. Almost no runtime cost. |
316
+ | `medium` | **Recommended.** Adds control-flow flattening, dead code and encoded strings. |
317
+ | `high` | Stronger transformations; larger and slower code. |
318
+ | `max` | Everything, plus protection against reformatting. Heaviest. |
319
+
320
+ Every obfuscated file is checked: the global variables of classic scripts and the imports/exports of modules must stay the same, or that file is published unobfuscated with a warning. Options that change behavior (`debugProtection`, `disableConsoleOutput`, `renameGlobals`) are never enabled automatically; pass them explicitly: `"obfuscate": { "preset": "high", "debugProtection": true }`. License comments (`/*! … */`, `@license`) are preserved.
144
321
 
145
322
  ## TypeScript
146
323
 
147
- Point your HTML at `.ts` files (or at the `.js` name, as TypeScript itself recommends) and build:
324
+ Reference `.ts` files from your HTML — or the `.js` name, as TypeScript recommends — and build. No extra install.
148
325
 
149
326
  ```html
150
327
  <script type="module" src="js/main.ts"></script>
151
328
  ```
152
329
 
153
- - Each `.ts`/`.mts`/`.cts` is transpiled with esbuild using your `tsconfig.json` (types removed, syntax level untouched), then goes through the same pipeline as JavaScript: bundling, obfuscation, verification.
154
- - The output is `.js` (`.mjs`/`.cjs`) and the HTML is rewritten to point to it. `import './x.ts'`, `import './x'`, `import './dir'` and `new URL('./worker.ts', import.meta.url)` keep working.
155
- - `.d.ts` files and `*.test.ts` are not published.
156
- - Builder does not type-check: run `tsc --noEmit` in your CI for that.
157
-
158
- ## Speed
159
-
160
- Measured with `npm run bench` (a generated site with 40 pages, 240 modules, 120 classic scripts, 30 CSS files and 200 images; median of 3 runs on an i7-11700F, Node 24, Windows):
330
+ - `.ts`, `.mts` and `.cts` files are transpiled with esbuild using your `tsconfig.json`. Types are removed; the syntax level is kept.
331
+ - The output is `.js` (`.mjs`, `.cjs`) and the HTML is updated to point to it.
332
+ - These keep working: `import './util'`, `import './util.ts'`, `import './util.js'` (when the file is `util.ts`), `import './components'` (folder with `index.ts`) and `new URL('./worker.ts', import.meta.url)`.
333
+ - `.d.ts` and `*.test.ts` files are not published.
334
+ - Types are not checked. Run `tsc --noEmit` in your editor or CI for that.
161
335
 
162
- | Build | 0.1.0 | 0.2.0, first build | 0.2.0, rebuild (cache) |
163
- | --- | ---: | ---: | ---: |
164
- | Default | 3.7 s | 1.9 s | 1.4 s |
165
- | `--obfuscate` | 11.5–17.9 s | 7.0 s | 1.8 s |
166
- | `--hash --compress` | 2.6–3.8 s | 2.1 s | 1.1 s |
167
- | `--minifier terser` | 5.6–6.0 s | 3.7 s | 1.6 s |
168
-
169
- How:
170
-
171
- - **Threads**: obfuscation, terser and the final syntax check run in worker threads (`workers: "auto"` ≈ physical cores, max 8). The main thread works too, and threads are only started when there is enough queued work to pay for their startup, so small projects are never slower.
172
- - **Persistent cache**: results are stored by content in `node_modules/.cache/vanilla-builder` (or the system temp folder). Same input + same options + same tool versions = same output, byte for byte; the test suite checks it. Set `BUILDER_CACHE_DIR` to keep it between CI runs. `--no-cache` or `"cache": false` turns it off.
173
- - **Parallel and in order**: independent files are processed at the same time but registered in the original order, so the output does not depend on timing.
174
- - `--json` includes `perf.phases` (milliseconds per phase) to see where the time goes in your project.
336
+ ## Configuration
175
337
 
176
- ## Configuration (`builder.config.json`)
338
+ Everything works without configuration. To change defaults, create `builder.config.json` in the project folder (`npx builder init` writes one based on your project). Every option can also be set from the command line, which takes precedence. Misspelled options are rejected with a suggestion.
177
339
 
178
340
  ```json
179
341
  {
180
342
  "outDir": "dist",
181
- "entries": "auto",
182
- "include": [],
183
- "ignore": [],
184
- "unreferenced": "process",
185
- "obfuscate": false,
186
- "dependencies": { "detect": true, "obfuscate": false, "include": [], "exclude": [] },
187
- "hash": false,
188
- "sourcemap": false,
189
- "compress": false,
190
- "cache": true,
191
- "workers": "auto",
192
- "base": "/",
193
- "sri": "auto",
194
- "html": { "minify": true, "removeComments": true, "collapseWhitespace": "auto" },
195
- "css": { "minify": true, "bundle": true, "target": "esnext" },
196
- "js": { "minify": true, "bundle": true, "target": "auto", "minifier": "esbuild", "mergeClassic": "auto", "drop": [], "legalComments": "eof", "chunkDir": "assets" }
343
+ "obfuscate": "medium",
344
+ "hash": true,
345
+ "compress": true,
346
+ "ignore": ["drafts/**"],
347
+ "js": { "drop": ["console", "debugger"] }
197
348
  }
198
349
  ```
199
350
 
200
- - `entries`: `"auto"` takes every complete HTML file; it also accepts a list of `.html`, `.js` or `.css` files.
201
- - `unreferenced`: what to do with files nothing references: `process` (minify and publish), `copy` or `skip`.
202
- - `js.target: "auto"` uses the syntax level of your own code: it neither transpiles nor introduces newer syntax than you already use.
203
- - `builder.config.mjs` is also accepted (export an object or a function). Misspelled options raise an error with a suggestion.
351
+ `builder.config.mjs` is also accepted; it can export an object or a function (sync or async) that returns one.
352
+
353
+ ### All options
354
+
355
+ | Option | Default | Description |
356
+ | --- | --- | --- |
357
+ | `root` | `"."` | Project folder, relative to the config file. |
358
+ | `outDir` | `"dist"` | Output folder. Builder refuses to empty a folder it did not create. |
359
+ | `entries` | `"auto"` | `"auto"` uses every complete HTML page. Or a list of `.html`, `.js` or `.css` files/patterns. |
360
+ | `include` | `[]` | Patterns always published, even if nothing references them (e.g. `"data/**"`). |
361
+ | `ignore` | `[]` | Patterns never published (e.g. `"drafts/**"`). |
362
+ | `defaultIgnore` | `true` | Exclude secrets (`.env*`, `*.pem`, `*.key`), `node_modules`, `package.json`, lockfiles, tool configs, tests and `.d.ts`. |
363
+ | `unreferenced` | `"process"` | Files nothing references: `"process"` (minify and publish), `"copy"` (publish as is) or `"skip"`. |
364
+ | `base` | `"/"` | Public base path of the site, for absolute URLs (`"/shop/"`). |
365
+ | `hash` | `false` | Content hash in JS/CSS file names (`assets/app-3f9a1c2b.js`). |
366
+ | `sourcemap` | `false` | `true` (linked `.map` files), `"hidden"` (no comment in the code) or `"inline"`. |
367
+ | `compress` | `false` | `true` (gzip and brotli), `"gzip"` or `"brotli"`: precompressed copies next to each file. |
368
+ | `sri` | `"auto"` | Subresource Integrity. `"auto"` recomputes existing `integrity` attributes; `true` also adds them to every local script and stylesheet; `false` removes them. |
369
+ | `verify` | `true` | Verify the output before publishing. Turning it off is not recommended. |
370
+ | `clean` | `true` | Replace `outDir` completely. With `false`, new files are copied over the existing ones. |
371
+ | `cache` | `true` | Reuse results from previous builds. |
372
+ | `workers` | `"auto"` | Threads for obfuscation and terser: `"auto"`, a number, or `0` for none. |
373
+ | `browsers` | `"auto"` | Target browsers: `"auto"` reads `.browserslistrc` or `browserslist` in `package.json`; or a query (`"> 0.5%, not dead"`); or `false`. Applies to `css.target` and `js.target` when they are at their defaults. Needs `browserslist`. |
374
+ | `images` | `false` | `true` or `{ "quality": 1-100 }`: optimize PNG (lossless), JPEG and WebP. Needs `sharp`. |
375
+ | `budgets` | `false` | Size limits by output pattern or `"total"`: `{ "js/*.js": "50 KB", "total": "1 MB gzip" }`. Over budget = failed build. |
376
+ | `plugins` | `[]` | See [Plugins](#plugins). |
377
+ | `obfuscate` | `false` | `false`, `true` (= `"medium"`), `"low"`, `"medium"`, `"high"`, `"max"`, or an object of [javascript-obfuscator options](https://github.com/javascript-obfuscator/javascript-obfuscator#javascript-obfuscator-options) with an optional `preset`. |
378
+ | `dependencies.detect` | `true` | Detect third-party libraries automatically. |
379
+ | `dependencies.include` | `[]` | Patterns that are libraries (not obfuscated). |
380
+ | `dependencies.exclude` | `[]` | Patterns that are your code (obfuscated even if they look like a library). |
381
+ | `dependencies.obfuscate` | `false` | Obfuscate libraries too. |
382
+ | `html.minify` | `true` | Minify HTML. |
383
+ | `html.removeComments` | `true` | Remove HTML comments. Meaningful ones are kept: `<!--! … -->`, IE conditionals, SSI and ESI. |
384
+ | `html.collapseWhitespace` | `"auto"` | Collapse whitespace between tags. `"auto"` turns it off if your CSS uses `white-space: pre`. |
385
+ | `css.minify` | `true` | Minify CSS. |
386
+ | `css.bundle` | `true` | Inline `@import`ed files. |
387
+ | `css.target` | `"esnext"` | CSS syntax target, e.g. `["chrome90", "safari15"]`. |
388
+ | `js.minify` | `true` | Minify JavaScript. |
389
+ | `js.bundle` | `true` | Bundle ES modules. With `false`, each module is minified in place. |
390
+ | `js.target` | `"auto"` | `"auto"` keeps the syntax level of your code; or `"es2018"`, `["chrome90", "safari15"]`… |
391
+ | `js.minifier` | `"esbuild"` | `"terser"` adds a second, stronger compression pass. |
392
+ | `js.mergeClassic` | `"auto"` | Merge consecutive classic `<script>` tags when it is safe (off when source maps are on). |
393
+ | `js.drop` | `[]` | Remove `"console"` and/or `"debugger"`. |
394
+ | `js.keepNames` | `false` | Keep function and class names (`fn.name`). |
395
+ | `js.define` | `{}` | Replace global identifiers at build time, e.g. `{ "DEBUG": "false" }`. |
396
+ | `js.legalComments` | `"eof"` | Where license comments go: `"eof"`, `"inline"` or `"none"`. |
397
+ | `js.chunkDir` | `"assets"` | Folder for shared chunks, hashed files and vendor packages. |
398
+ | `json.minify` | `true` | Remove whitespace from `.json` and `.webmanifest` (values are never rewritten). |
399
+
400
+ ## Plugins
401
+
402
+ For anything Builder does not do on its own. A plugin is an object with hooks; declare it in `builder.config.mjs`:
403
+
404
+ ```js
405
+ // builder.config.mjs
406
+ export default {
407
+ plugins: [
408
+ {
409
+ name: 'version',
410
+ filter: /\.(html|js)$/, // optional: which files transform() sees
411
+ transform: ({ path, code }) => code.replaceAll('__VERSION__', '1.4.0'),
412
+ buildStart: ({ config, mode }) => {}, // before anything is read
413
+ buildEnd: ({ result, outDir }) => {}, // after dist/ is written (build only)
414
+ },
415
+ ],
416
+ };
417
+ ```
418
+
419
+ - `transform` receives the **source** of each HTML, CSS, JS and TS file and returns the new code (or nothing to leave it). It runs before processing, so hashes, SRI and the final verification all see the plugin's result.
420
+ - In `builder.config.json`, name an installed package instead: `"plugins": ["my-plugin", ["other-plugin", { "option": 1 }]]`. The package exports the plugin object, or a function that receives the options and returns it.
421
+ - If a plugin throws, the build stops with a message naming the plugin and the file.
422
+
423
+ ## GitHub Action
424
+
425
+ ```yaml
426
+ # .github/workflows/build.yml
427
+ on: [push, pull_request]
428
+ permissions:
429
+ contents: read
430
+ pull-requests: write # only for comment: true
431
+ jobs:
432
+ build:
433
+ runs-on: ubuntu-latest
434
+ steps:
435
+ - uses: actions/checkout@v4
436
+ - uses: MarioACo/vanilla-builder@v0.3.0
437
+ with:
438
+ args: --hash --compress # any build options
439
+ comment: true # size report on pull requests
440
+ ```
441
+
442
+ It builds with a cache kept between runs, writes a size summary on the run page, comments on pull requests (updating the same comment on each push) and fails the job when the build, the verification or a budget fails. Inputs: `directory`, `args`, `version` (when the project does not install vanilla-builder), `node-version`, `cache`, `comment`. Outputs: `total-size`, `total-gzip`, `report` (path to the JSON report).
204
443
 
205
- ## API
444
+ ## Command-line reference
445
+
446
+ ```text
447
+ builder <command> [folder] [options]
448
+ ```
449
+
450
+ | Command | Description |
451
+ | --- | --- |
452
+ | `build` | Build the project into `dist/`. |
453
+ | `dev` | Build, watch for changes, serve `dist/` and reload the browser. |
454
+ | `serve` | Serve the existing `dist/`. |
455
+ | `analyze` | Show what would happen to every file. Writes nothing. |
456
+ | `deps` | List which JavaScript files are libraries and which are yours. |
457
+ | `deps add\|own\|reset <pattern…>` | Mark files as libraries, as your code, or back to automatic. Use `--dir <folder>` for another project. |
458
+ | `init` | Create `builder.config.json` from what was detected (`--force` to overwrite). |
459
+ | `help`, `--version` | Help and version. |
460
+
461
+ ### Build options
462
+
463
+ | Option | Description |
464
+ | --- | --- |
465
+ | `--out <folder>` | Output folder (default `dist`). |
466
+ | `--config <file>` | Configuration file (default `builder.config.json`). |
467
+ | `--entry <file>` | Explicit entry point. Repeatable. |
468
+ | `--include <pattern>` / `--ignore <pattern>` | Always publish / never publish. Repeatable. |
469
+ | `--obfuscate [level]` | Obfuscate your JavaScript: `low`, `medium` (default), `high`, `max`. |
470
+ | `--dep <pattern>` / `--not-dep <pattern>` | Mark as library / as your code. Repeatable. |
471
+ | `--obfuscate-deps` | Obfuscate libraries too. |
472
+ | `--minifier terser` | Extra compression pass with terser. |
473
+ | `--compress [gzip\|brotli]` | Write precompressed `.gz`/`.br` files (both by default). |
474
+ | `--hash` | Content hash in JS/CSS file names. |
475
+ | `--sourcemap [linked\|hidden\|inline]` | Generate source maps. |
476
+ | `--target <target>` | JavaScript target, e.g. `es2018`. |
477
+ | `--drop-console` | Remove `console.*` and `debugger`. |
478
+ | `--no-minify` | Do not minify (useful to debug a problem). |
479
+ | `--no-merge` | Do not merge classic scripts. |
480
+ | `--unreferenced <mode>` | `process`, `copy` or `skip`. |
481
+ | `--base <path>` | Public base path, e.g. `/shop/`. |
482
+ | `--workers <n\|auto>` | Number of threads (`0` = none). |
483
+ | `--browsers <query>` / `--no-browsers` | Target browsers (browserslist query) / ignore the project's browsers. |
484
+ | `--images`, `--image-quality <1-100>` | Optimize images (needs `sharp`). |
485
+ | `--budget <pattern=size>` | Size budget, e.g. `"total=1MB gzip"`. Repeatable; replaces the configured budgets. |
486
+ | `--lang <en\|es>` | Message language. |
487
+ | `--no-cache` | Ignore the cache. |
488
+ | `--no-verify` | Skip the final verification (not recommended). |
489
+ | `--force` | Allow emptying an output folder Builder did not create. |
490
+ | `--keep-failed` | Keep the temporary folder if verification fails, to inspect it. |
491
+ | `--json` | Machine-readable result. |
492
+ | `--verbose` / `--quiet` | More detail (notes, copied and excluded files) / errors only. |
493
+
494
+ `dev` and `serve` also accept `--port <n>` (default `5173`), `--host <host>` (default `127.0.0.1`) and `--open`. Unknown options are rejected with a suggestion (`--obfusacte` → did you mean `--obfuscate`?).
495
+
496
+ ## Safety guarantees
497
+
498
+ | Common build-tool risk | What vanilla-builder does |
499
+ | --- | --- |
500
+ | Merging classic scripts breaks shared globals | Merges only consecutive scripts with the same `defer` and strict mode, without `document.currentScript` or conflicting `let`/`const`, using a safe separator. |
501
+ | Minification renames or adds globals | Compares globals before and after; falls back to a safer minification, or keeps the original. |
502
+ | Obfuscation breaks a module | Compares imports, exports and import attributes after obfuscating; on mismatch, the file is published unobfuscated. |
503
+ | HTML minification changes text | Structural comparison of elements, attributes and text; `<pre>`, `<textarea>` and templates untouched. |
504
+ | Stale `integrity` attributes block scripts | Recomputed whenever the content changes. |
505
+ | Legacy encodings or BOM get corrupted | HTML bytes are preserved; JS and CSS are emitted with ASCII escapes. |
506
+ | JSON numbers lose precision | Only whitespace outside strings is removed. |
507
+ | Deleting the wrong folder | Never empties a folder it did not create (marked by `.builder-manifest.json`), the project folder, your home folder or a drive root. |
508
+ | A failed build leaves a broken site | Output is written and verified in a temporary folder before replacing `dist/`. |
509
+ | Publishing secrets | `.env*`, keys, `node_modules`, `package.json` and tool configs are excluded by default. |
510
+
511
+ Every release is tested by opening example sites — original and built — in a real Chromium and requiring identical behavior with no console errors and no 404s, in five variants (default, high obfuscation + hash, max obfuscation, terser + precompression, unminified), building with Node 16 and with current Node.
512
+
513
+ ## Performance
514
+
515
+ Measured with `npm run bench` on a generated site with 40 pages, 240 modules, 120 classic scripts, 30 CSS files and 200 images (median of 3 runs; Intel i7-11700F, Node 24, Windows):
516
+
517
+ | Build | First build | Rebuild (cache) | v0.1.0 |
518
+ | --- | ---: | ---: | ---: |
519
+ | Default | 1.9 s | 1.4 s | 3.7 s |
520
+ | `--obfuscate` | 7.0 s | 1.8 s | 11.5–17.9 s |
521
+ | `--hash --compress` | 2.1 s | 1.1 s | 2.6–3.8 s |
522
+ | `--minifier terser` | 3.7 s | 1.6 s | 5.6–6.0 s |
523
+
524
+ - **Threads.** Obfuscation, terser and the final syntax check run in worker threads (`"auto"` ≈ physical cores, up to 8). Threads start only when there is enough work to pay for their startup, so small projects are not slower.
525
+ - **Persistent cache.** Slow results are stored by content in `node_modules/.cache/vanilla-builder` (or the system temp folder). Same input, options and tool versions produce the same output byte for byte — the test suite checks it. Override the location with `BUILDER_CACHE_DIR`; disable with `--no-cache`.
526
+ - **Deterministic.** Independent files are processed in parallel but registered in their original order, so the output never depends on timing.
527
+ - To see where time goes in your project, look at `perf.phases` in `--json`.
528
+
529
+ ## JavaScript API
206
530
 
207
531
  ```js
208
532
  import { build, analyze } from 'vanilla-builder';
209
533
 
210
- const result = await build({ dir: 'my-site', overrides: { obfuscate: 'high' } });
211
- console.log(result.report.totals, result.warnings);
534
+ const result = await build({
535
+ dir: 'my-site', // project folder (default: current folder)
536
+ overrides: { obfuscate: 'high' }, // same shape as builder.config.json
537
+ });
538
+
539
+ console.log(result.report.totals); // { before, size, gzip, brotli, files }
540
+ console.log(result.warnings); // string[]
541
+ console.log(result.dependencies); // [{ rel, reason }]
212
542
  ```
213
543
 
214
- ## Development
544
+ - `build(options)` builds and writes `outDir`. Options: `dir`, `config` (path to a config file), `overrides`, `force`, `keepFailed`, `log` (function called with progress messages).
545
+ - `analyze(options)` returns the same result without writing anything.
546
+ - Errors are thrown as `BuildError` (exported), with a `details` array.
547
+ - `startServer({ dir, port, host })` serves a folder, as `builder serve` does.
215
548
 
216
- ```bash
217
- npm test # unit + integration (Node 18+; on Node 16: npm run test:node16)
218
- npm run bench # speed on a generated site, first build and with cache
219
- npm run test:browser # real equivalence in Chromium (finds Chrome/Edge or uses BUILDER_CHROMIUM)
220
- BUILDER_NODE=/path/to/node16 npm run test:browser # build with another Node and verify in the browser
221
- ```
549
+ ## Troubleshooting
550
+
551
+ ### `The output folder … already has files and was not created by Builder`
552
+
553
+ Builder never empties a folder it did not create. Choose another folder with `--out`, empty it yourself, or pass `--force` if you are sure.
554
+
555
+ ### `Missing optional package "javascript-obfuscator"` (or `terser`, `browserslist`, `sharp`)
222
556
 
223
- ## Known limits
557
+ Install it in your project, e.g. `npm install --save-dev javascript-obfuscator`. These are optional so the base install stays small.
224
558
 
225
- - No obfuscator makes code impossible to read: it only makes it much more expensive.
226
- - With `hash: true`, JS/CSS files are renamed; if your code requests them by string, the original path is also published and you get a warning.
227
- - Collapsing HTML whitespace changes `textContent` (not what you see). To keep it byte for byte: `"html": { "collapseWhitespace": false }`.
228
- - `import()` with computed paths and no fixed prefix makes that group of modules minify file by file in place (without bundling), so paths do not break.
229
- - TypeScript: no type checking, no `.tsx`/JSX, and source maps point to the type-stripped JavaScript, not to the `.ts` lines.
559
+ ### `target browsers are set in package.json (browserslist), but the "browserslist" package is missing`
560
+
561
+ Your project declares browsers (maybe for another tool) but `browserslist` is not installed, so Builder keeps your code as it is. Install it (`npm install --save-dev browserslist`) to use them, or set `"browsers": false` to silence the warning.
562
+
563
+ ### A file is missing from `dist/`
564
+
565
+ Run `npx builder analyze --verbose`. It lists every file with its destination, and every excluded file with the reason. Files excluded by default (secrets, configs, tests) can still be published with `--include "<pattern>"`.
566
+
567
+ ### A library was obfuscated, or my code was not
568
+
569
+ Run `npx builder deps` to see the reason for each file, then correct it with `data-builder`, a `@builder-*` comment or `npx builder deps add|own` ([details](#check-and-correct-the-detection)).
570
+
571
+ ### `Two different results want to be written to …`
572
+
573
+ This usually means a folder in your project has the same name as `js.chunkDir` (`assets` by default), or the same module is used under different import maps. Set `js.chunkDir` to another name.
574
+
575
+ ### `"base" must start with "/"` when using Git Bash on Windows
576
+
577
+ Git Bash converts arguments that look like Unix paths, so `--base /shop/` arrives as `C:/Program Files/Git/shop/`. Put `"base": "/shop/"` in `builder.config.json`, or run `MSYS_NO_PATHCONV=1 npx builder build --base /shop/`. PowerShell and cmd are not affected.
578
+
579
+ ### Something behaves differently after the build
580
+
581
+ Build with `--no-minify` to rule out minification, or without `--obfuscate` to rule out obfuscation. If you find a difference, please [open an issue](https://github.com/MarioACo/vanilla-builder/issues) with the smallest example you can share.
582
+
583
+ ## Known limitations
584
+
585
+ - No obfuscator makes code impossible to read; it only makes it much more expensive to understand.
586
+ - With `hash`, JS and CSS are renamed. If your code requests one of them by a path written as text, the original path is published too and you get a warning.
587
+ - Collapsing HTML whitespace changes `textContent` but not what is displayed. To keep HTML text byte for byte, set `"html": { "collapseWhitespace": false }`.
588
+ - `import()` with a fully computed path makes that group of modules minify file by file instead of bundling, so paths keep working.
589
+ - TypeScript: no type checking, no `.tsx`/JSX, and source maps point to the type-stripped JavaScript.
590
+ - `--images` re-encodes JPEG and WebP with loss (PNG is lossless). Modern formats (WebP/AVIF from JPEG) are not generated, because that would require changing your HTML.
591
+
592
+ ## Contributing
593
+
594
+ Issues and pull requests are welcome.
595
+
596
+ ```bash
597
+ git clone https://github.com/MarioACo/vanilla-builder.git
598
+ cd vanilla-builder
599
+ npm install
600
+ npm test # unit and integration tests (Node 18+; on Node 16: npm run test:node16)
601
+ npm run test:browser # original vs built sites in Chromium (uses Chrome/Edge or BUILDER_CHROMIUM)
602
+ npm run bench # performance on a generated site
603
+ cd bench/compare && npm install && node run.mjs # comparison with Parcel and Vite
604
+ ```
230
605
 
231
606
  ## License
232
607