vanilla-builder 0.0.0-stage → 0.2.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/CHANGELOG.md ADDED
@@ -0,0 +1,41 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project are documented here. The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and the project uses [Semantic Versioning](https://semver.org/).
4
+
5
+ ## [0.2.0] - 2026-11-09
6
+
7
+ ### Added
8
+
9
+ - **TypeScript**: `.ts`, `.mts` and `.cts` files are transpiled with esbuild (using `tsconfig.json`) and published as `.js`; the HTML is rewritten to the output. Extensionless, directory and `.ts` imports, and `new URL('./worker.ts', import.meta.url)`, keep working even without bundling. `.d.ts` and `*.test.ts` are not published.
10
+ - **`builder deps`**: lists which JS files are third-party libraries (and why) and which are yours; `builder deps add|own|reset <pattern…>` edits `builder.config.json` safely.
11
+ - Mark dependencies without config: `data-builder="dependency|own"` on a `<script>`, or `/* @builder-dependency */` / `/* @builder-own */` at the top of the file.
12
+ - New detection signals: UMD/AMD wrappers and files named like a package in `package.json` `dependencies`.
13
+ - **Worker threads** for obfuscation, terser and the final syntax check (`workers`, `--workers`), with the main thread helping while they start.
14
+ - **Persistent cache** of slow results by content (`cache`, `--no-cache`, `BUILDER_CACHE_DIR`); output is byte-identical with or without it.
15
+ - `perf` in the result and in `--json`: tasks, threads, cache hits and milliseconds per phase.
16
+ - `npm run bench`: reproducible benchmark on a generated site.
17
+
18
+ ### Changed
19
+
20
+ - Independent files (classic scripts, standalone files, CSS, module outputs, compression) are processed concurrently and registered in the original order.
21
+ - gzip/brotli sizes for the report are computed asynchronously, once, and shared with `--compress`.
22
+ - The ES level of each file is computed only when needed; global-variable checks use a single parse.
23
+
24
+ ## [0.1.0] - 2026-11-08
25
+
26
+ First public release.
27
+
28
+ ### Added
29
+
30
+ - `builder build | analyze | init | dev | serve` CLI and `build()` / `analyze()` API.
31
+ - Dependency discovery from every HTML page: classic and module scripts, import maps, CSS `@import`/`url()`/`image-set()`, media, icons, manifest, workers, service workers, dynamic `import()` and `new URL(..., import.meta.url)`.
32
+ - Lossless pipeline: every file gets a known fate, output is built in a temp folder, verified and only then swapped into `dist/`.
33
+ - Conservative HTML minifier on parse5 with structural verification.
34
+ - JS/CSS bundling and minification with esbuild, optional terser pass, safe merging of classic scripts.
35
+ - Optional obfuscation (`low`, `medium`, `high`, `max`) with javascript-obfuscator; third-party libraries are detected and left untouched unless requested.
36
+ - Content hashing, source maps, SRI recomputation, gzip/brotli precompression, `--json` output for CI.
37
+ - Projects opened through a symlink or junction (e.g. macOS `/tmp`) now build; the root is resolved to its real path.
38
+ - MIT license, CI on Node 16–24 (Linux, Windows, macOS) and browser equivalence tests in Chrome.
39
+
40
+ [0.2.0]: https://github.com/MarioACo/vanilla-builder/releases/tag/v0.2.0
41
+ [0.1.0]: https://github.com/MarioACo/vanilla-builder/releases/tag/v0.1.0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 MarioACo
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.es.md ADDED
@@ -0,0 +1,231 @@
1
+ # vanilla-builder
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/licencia-MIT-blue)](LICENSE)
4
+
5
+ [English](README.md) · **Español**
6
+
7
+ Empaqueta, minifica y ofusca proyectos web **vanilla** (HTML, CSS y JavaScript sin framework) **sin perder nada**.
8
+ Detecta solo las dependencias de cada página, verifica la salida antes de publicarla y nunca borra lo que no creó.
9
+
10
+ - **Cero configuración**: apúntalo a una carpeta con `.html` y listo.
11
+ - **Sin pérdida**: cada archivo del proyecto termina con un destino conocido (procesado, empaquetado, copiado u omitido a propósito) y la salida se verifica antes de reemplazar `dist/`.
12
+ - **Rápido**: la ofuscación y terser van en hilos paralelos, y una caché persistente hace casi gratis recompilar lo que no cambió (sitio de prueba de 40 páginas ofuscado: 11–18 s en 0.1.0 → 7 s la primera vez, 1,8 s al recompilar). Ver [Velocidad](#velocidad).
13
+ - **Tú decides qué es una librería**: el código de terceros se detecta y nunca se ofusca; lo corriges desde el HTML, el propio archivo o `builder deps`. Ver [Dependencias](#dependencias-de-terceros-y-ofuscación).
14
+ - **TypeScript incluido**: `<script src="app.ts">` funciona tal cual; la salida es `.js`.
15
+ - **Ligero**: la instalación base son 8 paquetes (el grueso es el binario de esbuild). El ofuscador y terser se instalan solo si los usas.
16
+ - **Node 16 o superior** (probado en 16, 18, 20, 22 y 24, en Linux, Windows y macOS).
17
+
18
+ ## Instalación
19
+
20
+ ```bash
21
+ npm install --save-dev vanilla-builder # o: npm link desde esta carpeta
22
+ npm install --save-dev javascript-obfuscator # solo si vas a ofuscar
23
+ npm install --save-dev terser # solo si quieres la compresión máxima
24
+ ```
25
+
26
+ > En Node 16 y 17, npm muestra un aviso `EBADENGINE` porque esbuild declara Node 18+. Es solo un aviso:
27
+ > toda la batería de pruebas (incluidas las de navegador) pasa compilando con Node 16.
28
+
29
+ ## Uso
30
+
31
+ ```bash
32
+ builder build mi-sitio # compila en mi-sitio/dist
33
+ builder build mi-sitio --obfuscate # además ofusca tu JavaScript (nivel medium)
34
+ builder analyze mi-sitio # muestra qué pasará con cada archivo, sin escribir nada
35
+ builder deps mi-sitio # qué JS son librerías (no se ofuscan) y cuáles son tuyos
36
+ builder init mi-sitio # crea builder.config.json con lo detectado
37
+ builder dev mi-sitio # compila, vigila cambios y sirve dist/ con recarga automática
38
+ builder serve mi-sitio # sirve dist/ para probarlo
39
+ ```
40
+
41
+ Opciones frecuentes de `build` (todas tienen equivalente en la configuración):
42
+
43
+ | Opción | Qué hace |
44
+ | --- | --- |
45
+ | `--obfuscate [low\|medium\|high\|max]` | Ofusca tu JS. Las librerías de terceros se detectan y no se tocan. |
46
+ | `--dep <patrón>` / `--not-dep <patrón>` | Marca a mano una dependencia / corrige una detección. |
47
+ | `--obfuscate-deps` | Ofusca también las dependencias. |
48
+ | `--minifier terser` | Segunda pasada de compresión con terser (JS aún más pequeño). |
49
+ | `--compress [gzip\|brotli]` | Genera además `.gz` y `.br` para servidores que sirven precomprimido. |
50
+ | `--hash` | Nombres con hash en JS/CSS para caché a largo plazo. |
51
+ | `--sourcemap [linked\|hidden\|inline]` | Source maps. |
52
+ | `--target es2018` | Transpila a un nivel concreto (por defecto: el del propio código). |
53
+ | `--drop-console` | Quita `console.*` y `debugger`. |
54
+ | `--entry`, `--include`, `--ignore` | Entradas explícitas, archivos forzados y excluidos (repetibles). |
55
+ | `--workers <n\|auto>` | Hilos para ofuscar/terser (`0` = sin hilos). |
56
+ | `--no-cache` | No usa la caché de compilaciones anteriores. |
57
+ | `--json` | Resultado en JSON para CI (código de salida 1 si algo falla), con el tiempo de cada fase. |
58
+
59
+ ## Qué detecta solo
60
+
61
+ Partiendo de cada `.html`, Builder sigue:
62
+
63
+ - `<script src>` clásicos y `type="module"`, scripts inline e **import maps**.
64
+ - `<link rel="stylesheet">`, `@import` y `url()` del CSS (también `image-set()`), `<style>` y atributos `style`.
65
+ - Imágenes, `srcset`, vídeo, audio, iconos, manifest, `og:image`, `<iframe>`, `<object>`, SVG `<use>`…
66
+ - Enlaces a otras páginas, `import()` dinámicos, `new URL('./x', import.meta.url)`.
67
+ - Workers, service workers e `importScripts()`.
68
+ - Rutas escritas como texto en el JS (por ejemplo la lista de precache de un service worker): esos archivos se publican también en su ruta original.
69
+
70
+ Lo que no se puede seguir (un `fetch(variable)`, una plantilla del servidor…) **no se pierde**: la estructura de carpetas se conserva y todo lo no referenciado se publica igualmente (`unreferenced: "process"`).
71
+
72
+ ## Garantías
73
+
74
+ | Riesgo típico de un builder | Cómo lo evita Builder |
75
+ | --- | --- |
76
+ | Unir scripts clásicos rompe variables globales | Solo une scripts consecutivos, con el mismo `defer`/modo estricto, sin `document.currentScript`, sin redeclarar `let/const` y con separador seguro (`;`). |
77
+ | La minificación crea globales nuevas o renombra las tuyas | Compara las globales antes y después; si cambian, usa una minificación más conservadora o deja el original. |
78
+ | El ofuscador rompe exportaciones o atributos `with { type }` | Verifica exportaciones, importaciones y atributos tras ofuscar; si no cuadran, no ofusca ese archivo. |
79
+ | esbuild borra en silencio `with { type: 'css' }` | Se fuerza la conservación de los atributos de import. |
80
+ | La minificación HTML cambia el texto | Minificador propio sobre parse5 y comprobación estructural (elementos, atributos y texto). `<pre>`, `<textarea>` y plantillas quedan intactos; si el CSS usa `white-space: pre`, no colapsa espacios. |
81
+ | `integrity` (SRI) obsoleto bloquea scripts | Se recalcula al cambiar el contenido. |
82
+ | Codificaciones antiguas o BOM | Se conservan los bytes (HTML) o se emite JS/CSS con escapes ASCII. |
83
+ | JSON reformateado pierde precisión | Solo se quitan espacios fuera de las cadenas: `2.0` y números enormes quedan igual. |
84
+ | Borrar `dist/` equivocado | Nunca vacía una carpeta que no creó (marca `.builder-manifest.json`), ni la del proyecto, ni la raíz. |
85
+ | Compilación fallida a medias | Se escribe en una carpeta temporal, se verifica y solo entonces reemplaza `dist/`. |
86
+ | Publicar secretos | `.env*`, claves `.pem/.key`, `node_modules`, `package.json` y configuración de herramientas quedan fuera por defecto. |
87
+
88
+ La batería de pruebas abre cada sitio de ejemplo **original y compilado en Chromium** y exige resultados idénticos, sin errores ni 404, con 5 variantes (por defecto, ofuscación alta + hash, ofuscación max, terser + precompresión, sin minificar).
89
+
90
+ ## Dependencias de terceros y ofuscación
91
+
92
+ Ofuscar jQuery, Bootstrap o un paquete de npm no protege nada tuyo, los hace más lentos y puede incumplir su licencia. Por eso, al ofuscar, esto se detecta como dependencia y no se toca:
93
+
94
+ - Archivos en `vendor/`, `node_modules/`, `third-party/`…
95
+ - Nombres de librerías conocidas (`jquery-3.7.1.min.js`, `bootstrap.bundle.js`…) y versiones compiladas (`.min.js`, `.umd.js`).
96
+ - Archivos que se llaman como un paquete de las `dependencies` de tu `package.json` (`js/chart.umd.js` si dependes de `chart.js`).
97
+ - Envoltorios UMD/AMD (el formato en que se publican las librerías), cabeceras de licencia con versión (`/*! Foo v1.2.0 */`) y código ya minificado.
98
+ - Los paquetes de npm que importa tu código de módulos se separan en `assets/vendor/` (sin ofuscar y con su estado compartido intacto).
99
+
100
+ Para ver qué se detectó y por qué:
101
+
102
+ ```bash
103
+ builder deps mi-sitio
104
+ ```
105
+
106
+ ¿Algo mal? Hay tres formas de corregirlo; usa la que te resulte más cómoda:
107
+
108
+ ```html
109
+ <!-- 1. En el HTML -->
110
+ <script src="js/mi-lib.js" data-builder="dependency"></script>
111
+ <script src="js/app.min.js" data-builder="own"></script>
112
+ ```
113
+
114
+ ```js
115
+ /* 2. Al principio del propio archivo */
116
+ /* @builder-dependency */ // o: /* @builder-own */
117
+ ```
118
+
119
+ ```bash
120
+ # 3. Desde la terminal (edita builder.config.json por ti y conserva el resto)
121
+ builder deps add "js/libs/**" # son librerías: no ofuscar
122
+ builder deps own js/app.min.js # es mi código: ofuscarlo
123
+ builder deps reset js/app.min.js # volver a la detección automática
124
+ ```
125
+
126
+ La configuración tiene la última palabra sobre las marcas, y las marcas sobre la detección automática:
127
+
128
+ ```json
129
+ {
130
+ "obfuscate": "high",
131
+ "dependencies": {
132
+ "include": ["js/mi-libreria.js"],
133
+ "exclude": ["js/app.min.js"],
134
+ "obfuscate": false
135
+ }
136
+ }
137
+ ```
138
+
139
+ `include` marca dependencias que no se detectaron; `exclude` indica que un archivo es tuyo y sí debe ofuscarse; `obfuscate: true` ofusca todo.
140
+
141
+ Niveles: `low` (casi sin coste), `medium` (recomendado), `high` y `max` (además se defiende de ser formateado o modificado; más pesado y lento). Las opciones que cambian el comportamiento (`debugProtection`, `disableConsoleOutput`, `renameGlobals`) nunca se activan solas; puedes pasarlas con un objeto: `"obfuscate": { "preset": "high", "debugProtection": true }`. Tus comentarios de licencia (`/*! … */`, `@license`) se conservan.
142
+
143
+ ## TypeScript
144
+
145
+ Apunta tu HTML a archivos `.ts` (o al nombre `.js`, como recomienda el propio TypeScript) y compila:
146
+
147
+ ```html
148
+ <script type="module" src="js/main.ts"></script>
149
+ ```
150
+
151
+ - Cada `.ts`/`.mts`/`.cts` se transpila con esbuild usando tu `tsconfig.json` (se quitan los tipos, el nivel de sintaxis no cambia) y luego pasa por el mismo proceso que el JavaScript: empaquetado, ofuscación y verificación.
152
+ - La salida es `.js` (`.mjs`/`.cjs`) y el HTML se reescribe para apuntar a ella. Siguen funcionando `import './x.ts'`, `import './x'`, `import './carpeta'` y `new URL('./worker.ts', import.meta.url)`.
153
+ - Los `.d.ts` y los `*.test.ts` no se publican.
154
+ - Builder no comprueba tipos: para eso, ejecuta `tsc --noEmit` en tu CI.
155
+
156
+ ## Velocidad
157
+
158
+ Medido con `npm run bench` (un sitio generado con 40 páginas, 240 módulos, 120 scripts clásicos, 30 CSS y 200 imágenes; mediana de 3 ejecuciones en un i7-11700F, Node 24, Windows):
159
+
160
+ | Compilación | 0.1.0 | 0.2.0, primera vez | 0.2.0, recompilar (caché) |
161
+ | --- | ---: | ---: | ---: |
162
+ | Por defecto | 3,7 s | 1,9 s | 1,4 s |
163
+ | `--obfuscate` | 11,5–17,9 s | 7,0 s | 1,8 s |
164
+ | `--hash --compress` | 2,6–3,8 s | 2,1 s | 1,1 s |
165
+ | `--minifier terser` | 5,6–6,0 s | 3,7 s | 1,6 s |
166
+
167
+ Cómo:
168
+
169
+ - **Hilos**: la ofuscación, terser y la comprobación final de sintaxis van en hilos de trabajo (`workers: "auto"` ≈ núcleos físicos, máximo 8). El hilo principal también trabaja, y los hilos solo arrancan cuando hay suficiente trabajo en cola para compensar su arranque, así que un proyecto pequeño nunca va más lento.
170
+ - **Caché persistente**: los resultados se guardan por contenido en `node_modules/.cache/vanilla-builder` (o en el temporal del sistema). Misma entrada + mismas opciones + mismas versiones = misma salida, byte a byte; las pruebas lo comprueban. Con `BUILDER_CACHE_DIR` puedes conservarla entre ejecuciones de CI. `--no-cache` o `"cache": false` la desactivan.
171
+ - **En paralelo y en orden**: los archivos independientes se procesan a la vez, pero se registran en el orden original, así que la salida no depende de los tiempos.
172
+ - `--json` incluye `perf.phases` (milisegundos por fase) para ver dónde se va el tiempo en tu proyecto.
173
+
174
+ ## Configuración (`builder.config.json`)
175
+
176
+ ```json
177
+ {
178
+ "outDir": "dist",
179
+ "entries": "auto",
180
+ "include": [],
181
+ "ignore": [],
182
+ "unreferenced": "process",
183
+ "obfuscate": false,
184
+ "dependencies": { "detect": true, "obfuscate": false, "include": [], "exclude": [] },
185
+ "hash": false,
186
+ "sourcemap": false,
187
+ "compress": false,
188
+ "cache": true,
189
+ "workers": "auto",
190
+ "base": "/",
191
+ "sri": "auto",
192
+ "html": { "minify": true, "removeComments": true, "collapseWhitespace": "auto" },
193
+ "css": { "minify": true, "bundle": true, "target": "esnext" },
194
+ "js": { "minify": true, "bundle": true, "target": "auto", "minifier": "esbuild", "mergeClassic": "auto", "drop": [], "legalComments": "eof", "chunkDir": "assets" }
195
+ }
196
+ ```
197
+
198
+ - `entries`: `"auto"` toma todos los HTML completos; también admite una lista de `.html`, `.js` o `.css`.
199
+ - `unreferenced`: qué hacer con lo que nada referencia: `process` (minificar y publicar), `copy` o `skip`.
200
+ - `js.target: "auto"` usa el nivel de sintaxis de tu propio código: no transpila ni introduce sintaxis más nueva que la que ya usas.
201
+ - También se acepta `builder.config.mjs` (exporta un objeto o una función). Las opciones mal escritas dan error con sugerencia.
202
+
203
+ ## API
204
+
205
+ ```js
206
+ import { build, analyze } from 'vanilla-builder';
207
+
208
+ const result = await build({ dir: 'mi-sitio', overrides: { obfuscate: 'high' } });
209
+ console.log(result.report.totals, result.warnings);
210
+ ```
211
+
212
+ ## Desarrollo
213
+
214
+ ```bash
215
+ npm test # unitarias + integración (Node 18+; en Node 16: npm run test:node16)
216
+ npm run bench # velocidad sobre un sitio generado, primera vez y con caché
217
+ npm run test:browser # equivalencia real en Chromium (busca Chrome/Edge o usa BUILDER_CHROMIUM)
218
+ BUILDER_NODE=/ruta/node16 npm run test:browser # compila con otro Node y verifica en el navegador
219
+ ```
220
+
221
+ ## Límites conocidos
222
+
223
+ - Ningún ofuscador hace el código imposible de leer: solo lo encarece mucho.
224
+ - Con `hash: true`, los JS/CSS cambian de nombre; si tu código los pide por texto, Builder publica también la ruta original y lo avisa.
225
+ - Colapsar espacios en HTML cambia `textContent` (no lo que se ve). Para conservarlo byte a byte: `"html": { "collapseWhitespace": false }`.
226
+ - `import()` con rutas calculadas sin prefijo fijo hace que ese grupo de módulos se minifique archivo por archivo en su sitio (sin empaquetar), para no romper rutas.
227
+ - TypeScript: no se comprueban tipos, no hay `.tsx`/JSX, y los source maps apuntan al JavaScript ya sin tipos, no a las líneas del `.ts`.
228
+
229
+ ## Licencia
230
+
231
+ [MIT](LICENSE) © MarioACo
package/README.md CHANGED
@@ -1,3 +1,233 @@
1
- # Temporary Holding Version
1
+ # vanilla-builder
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
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)
4
+
5
+ **English** · [Español](README.es.md)
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.
9
+
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).
17
+
18
+ ## Install
19
+
20
+ ```bash
21
+ 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
+ ```
25
+
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.
28
+
29
+ ## Usage
30
+
31
+ ```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
39
+ ```
40
+
41
+ Common `build` options (all of them have a config equivalent):
42
+
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. |
89
+
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).
91
+
92
+ ## Third-party dependencies and obfuscation
93
+
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:
95
+
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).
101
+
102
+ See what was detected and why:
103
+
104
+ ```bash
105
+ npx builder deps my-site
106
+ ```
107
+
108
+ Something wrong? There are three ways to fix it, pick the one that suits you:
109
+
110
+ ```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>
114
+ ```
115
+
116
+ ```js
117
+ /* 2. At the top of the file itself */
118
+ /* @builder-dependency */ // or: /* @builder-own */
119
+ ```
120
+
121
+ ```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
126
+ ```
127
+
128
+ The config file has the last word over the marks, and the marks over the automatic detection:
129
+
130
+ ```json
131
+ {
132
+ "obfuscate": "high",
133
+ "dependencies": {
134
+ "include": ["js/my-library.js"],
135
+ "exclude": ["js/app.min.js"],
136
+ "obfuscate": false
137
+ }
138
+ }
139
+ ```
140
+
141
+ `include` marks dependencies that were not detected; `exclude` says a file is yours and should be obfuscated; `obfuscate: true` obfuscates everything.
142
+
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.
144
+
145
+ ## TypeScript
146
+
147
+ Point your HTML at `.ts` files (or at the `.js` name, as TypeScript itself recommends) and build:
148
+
149
+ ```html
150
+ <script type="module" src="js/main.ts"></script>
151
+ ```
152
+
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):
161
+
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.
175
+
176
+ ## Configuration (`builder.config.json`)
177
+
178
+ ```json
179
+ {
180
+ "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" }
197
+ }
198
+ ```
199
+
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.
204
+
205
+ ## API
206
+
207
+ ```js
208
+ import { build, analyze } from 'vanilla-builder';
209
+
210
+ const result = await build({ dir: 'my-site', overrides: { obfuscate: 'high' } });
211
+ console.log(result.report.totals, result.warnings);
212
+ ```
213
+
214
+ ## Development
215
+
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
+ ```
222
+
223
+ ## Known limits
224
+
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.
230
+
231
+ ## License
232
+
233
+ [MIT](LICENSE) © MarioACo