vanilla-builder 0.1.0 → 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 +20 -0
- package/README.es.md +76 -7
- package/README.md +76 -7
- package/bin/builder.js +106 -2
- package/package.json +7 -3
- package/src/builder.js +272 -89
- package/src/compress.js +44 -0
- package/src/config.js +197 -190
- package/src/deps.js +60 -1
- package/src/files.js +7 -0
- package/src/js.js +26 -34
- package/src/jsimports.js +54 -0
- package/src/jsinfo.js +19 -3
- package/src/pool.js +341 -0
- package/src/report.js +14 -19
- package/src/tersercore.js +33 -0
- package/src/urls.js +3 -0
- package/src/util.js +5 -1
- package/src/verify.js +13 -23
- package/src/worker.js +26 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,25 @@
|
|
|
2
2
|
|
|
3
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
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
|
+
|
|
5
24
|
## [0.1.0] - 2026-11-08
|
|
6
25
|
|
|
7
26
|
First public release.
|
|
@@ -18,4 +37,5 @@ First public release.
|
|
|
18
37
|
- Projects opened through a symlink or junction (e.g. macOS `/tmp`) now build; the root is resolved to its real path.
|
|
19
38
|
- MIT license, CI on Node 16–24 (Linux, Windows, macOS) and browser equivalence tests in Chrome.
|
|
20
39
|
|
|
40
|
+
[0.2.0]: https://github.com/MarioACo/vanilla-builder/releases/tag/v0.2.0
|
|
21
41
|
[0.1.0]: https://github.com/MarioACo/vanilla-builder/releases/tag/v0.1.0
|
package/README.es.md
CHANGED
|
@@ -9,8 +9,11 @@ Detecta solo las dependencias de cada página, verifica la salida antes de publi
|
|
|
9
9
|
|
|
10
10
|
- **Cero configuración**: apúntalo a una carpeta con `.html` y listo.
|
|
11
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
|
-
- **
|
|
13
|
-
- **
|
|
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).
|
|
14
17
|
|
|
15
18
|
## Instalación
|
|
16
19
|
|
|
@@ -29,6 +32,7 @@ npm install --save-dev terser # solo si quieres la compresión
|
|
|
29
32
|
builder build mi-sitio # compila en mi-sitio/dist
|
|
30
33
|
builder build mi-sitio --obfuscate # además ofusca tu JavaScript (nivel medium)
|
|
31
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
|
|
32
36
|
builder init mi-sitio # crea builder.config.json con lo detectado
|
|
33
37
|
builder dev mi-sitio # compila, vigila cambios y sirve dist/ con recarga automática
|
|
34
38
|
builder serve mi-sitio # sirve dist/ para probarlo
|
|
@@ -48,7 +52,9 @@ Opciones frecuentes de `build` (todas tienen equivalente en la configuración):
|
|
|
48
52
|
| `--target es2018` | Transpila a un nivel concreto (por defecto: el del propio código). |
|
|
49
53
|
| `--drop-console` | Quita `console.*` y `debugger`. |
|
|
50
54
|
| `--entry`, `--include`, `--ignore` | Entradas explícitas, archivos forzados y excluidos (repetibles). |
|
|
51
|
-
| `--
|
|
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. |
|
|
52
58
|
|
|
53
59
|
## Qué detecta solo
|
|
54
60
|
|
|
@@ -83,13 +89,41 @@ La batería de pruebas abre cada sitio de ejemplo **original y compilado en Chro
|
|
|
83
89
|
|
|
84
90
|
## Dependencias de terceros y ofuscación
|
|
85
91
|
|
|
86
|
-
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:
|
|
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:
|
|
87
93
|
|
|
88
|
-
-
|
|
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.
|
|
89
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).
|
|
90
|
-
- `builder analyze` lista qué detectó y por qué.
|
|
91
99
|
|
|
92
|
-
Para
|
|
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:
|
|
93
127
|
|
|
94
128
|
```json
|
|
95
129
|
{
|
|
@@ -106,6 +140,37 @@ Para corregirlo a mano:
|
|
|
106
140
|
|
|
107
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.
|
|
108
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
|
+
|
|
109
174
|
## Configuración (`builder.config.json`)
|
|
110
175
|
|
|
111
176
|
```json
|
|
@@ -120,6 +185,8 @@ Niveles: `low` (casi sin coste), `medium` (recomendado), `high` y `max` (además
|
|
|
120
185
|
"hash": false,
|
|
121
186
|
"sourcemap": false,
|
|
122
187
|
"compress": false,
|
|
188
|
+
"cache": true,
|
|
189
|
+
"workers": "auto",
|
|
123
190
|
"base": "/",
|
|
124
191
|
"sri": "auto",
|
|
125
192
|
"html": { "minify": true, "removeComments": true, "collapseWhitespace": "auto" },
|
|
@@ -146,6 +213,7 @@ console.log(result.report.totals, result.warnings);
|
|
|
146
213
|
|
|
147
214
|
```bash
|
|
148
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é
|
|
149
217
|
npm run test:browser # equivalencia real en Chromium (busca Chrome/Edge o usa BUILDER_CHROMIUM)
|
|
150
218
|
BUILDER_NODE=/ruta/node16 npm run test:browser # compila con otro Node y verifica en el navegador
|
|
151
219
|
```
|
|
@@ -156,6 +224,7 @@ BUILDER_NODE=/ruta/node16 npm run test:browser # compila con otro Node y verif
|
|
|
156
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.
|
|
157
225
|
- Colapsar espacios en HTML cambia `textContent` (no lo que se ve). Para conservarlo byte a byte: `"html": { "collapseWhitespace": false }`.
|
|
158
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`.
|
|
159
228
|
|
|
160
229
|
## Licencia
|
|
161
230
|
|
package/README.md
CHANGED
|
@@ -9,8 +9,11 @@ It follows the real dependencies of every page, verifies the output before publi
|
|
|
9
9
|
|
|
10
10
|
- **Zero config**: point it at a folder with `.html` files and you are done.
|
|
11
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
|
-
- **
|
|
13
|
-
- **
|
|
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).
|
|
14
17
|
|
|
15
18
|
## Install
|
|
16
19
|
|
|
@@ -29,6 +32,7 @@ npm install --save-dev terser # only if you want maximum compr
|
|
|
29
32
|
npx builder build my-site # builds into my-site/dist
|
|
30
33
|
npx builder build my-site --obfuscate # also obfuscates your JavaScript (medium level)
|
|
31
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
|
|
32
36
|
npx builder init my-site # creates builder.config.json from what it detected
|
|
33
37
|
npx builder dev my-site # builds, watches and serves dist/ with live reload
|
|
34
38
|
npx builder serve my-site # serves dist/ to try it out
|
|
@@ -48,7 +52,9 @@ Common `build` options (all of them have a config equivalent):
|
|
|
48
52
|
| `--target es2018` | Transpile to a specific level (default: whatever your own code already uses). |
|
|
49
53
|
| `--drop-console` | Removes `console.*` and `debugger`. |
|
|
50
54
|
| `--entry`, `--include`, `--ignore` | Explicit entries, forced files and exclusions (repeatable). |
|
|
51
|
-
| `--
|
|
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. |
|
|
52
58
|
|
|
53
59
|
Run `npx builder help` for the full list.
|
|
54
60
|
|
|
@@ -85,13 +91,41 @@ The test suite opens every example site **original and built in Chromium** and r
|
|
|
85
91
|
|
|
86
92
|
## Third-party dependencies and obfuscation
|
|
87
93
|
|
|
88
|
-
Obfuscating jQuery, Bootstrap or an npm package protects nothing of yours, makes them slower and may violate their license. So when obfuscating:
|
|
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:
|
|
89
95
|
|
|
90
|
-
- Files in `vendor/`, `node_modules/`, `third-party
|
|
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.
|
|
91
100
|
- npm packages imported by your module code are split into `assets/vendor/` (not obfuscated, with their shared state intact).
|
|
92
|
-
- `builder analyze` lists what it detected and why.
|
|
93
101
|
|
|
94
|
-
|
|
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:
|
|
95
129
|
|
|
96
130
|
```json
|
|
97
131
|
{
|
|
@@ -108,6 +142,37 @@ To correct it by hand:
|
|
|
108
142
|
|
|
109
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.
|
|
110
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
|
+
|
|
111
176
|
## Configuration (`builder.config.json`)
|
|
112
177
|
|
|
113
178
|
```json
|
|
@@ -122,6 +187,8 @@ Levels: `low` (almost free), `medium` (recommended), `high` and `max` (also defe
|
|
|
122
187
|
"hash": false,
|
|
123
188
|
"sourcemap": false,
|
|
124
189
|
"compress": false,
|
|
190
|
+
"cache": true,
|
|
191
|
+
"workers": "auto",
|
|
125
192
|
"base": "/",
|
|
126
193
|
"sri": "auto",
|
|
127
194
|
"html": { "minify": true, "removeComments": true, "collapseWhitespace": "auto" },
|
|
@@ -148,6 +215,7 @@ console.log(result.report.totals, result.warnings);
|
|
|
148
215
|
|
|
149
216
|
```bash
|
|
150
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
|
|
151
219
|
npm run test:browser # real equivalence in Chromium (finds Chrome/Edge or uses BUILDER_CHROMIUM)
|
|
152
220
|
BUILDER_NODE=/path/to/node16 npm run test:browser # build with another Node and verify in the browser
|
|
153
221
|
```
|
|
@@ -158,6 +226,7 @@ BUILDER_NODE=/path/to/node16 npm run test:browser # build with another Node an
|
|
|
158
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.
|
|
159
227
|
- Collapsing HTML whitespace changes `textContent` (not what you see). To keep it byte for byte: `"html": { "collapseWhitespace": false }`.
|
|
160
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.
|
|
161
230
|
|
|
162
231
|
## License
|
|
163
232
|
|
package/bin/builder.js
CHANGED
|
@@ -8,7 +8,8 @@ import { runPipeline, VERSION } from '../src/builder.js';
|
|
|
8
8
|
import { renderReport } from '../src/report.js';
|
|
9
9
|
import { startServer } from '../src/server.js';
|
|
10
10
|
import { watchTree } from '../src/watch.js';
|
|
11
|
-
import { BuildError, formatBytes, relFrom, isInside } from '../src/util.js';
|
|
11
|
+
import { BuildError, formatBytes, relFrom, isInside, JS_EXT, extOf, toPosix } from '../src/util.js';
|
|
12
|
+
import { globMatcher } from '../src/files.js';
|
|
12
13
|
|
|
13
14
|
const tty = process.stdout.isTTY && !process.env.NO_COLOR;
|
|
14
15
|
const color = (code) => (text) => (tty ? `\x1b[${code}m${text}\x1b[0m` : String(text));
|
|
@@ -20,9 +21,16 @@ Uso:
|
|
|
20
21
|
builder build [carpeta] [opciones] Compila el proyecto en dist/ (por defecto)
|
|
21
22
|
builder analyze [carpeta] [--json] Muestra dependencias detectadas y la salida prevista, sin escribir
|
|
22
23
|
builder init [carpeta] [--force] Crea builder.config.json con lo detectado
|
|
24
|
+
builder deps [carpeta] Lista qué JS son librerías de terceros (no se ofuscan) y cuáles son tuyos
|
|
25
|
+
builder deps add|own|reset <patrón…> Marca librerías (add), tu código (own) o vuelve a lo automático (reset)
|
|
23
26
|
builder dev [carpeta] [--port N] Compila, vigila cambios y sirve dist/ con recarga automática
|
|
24
27
|
builder serve [carpeta] [--port N] Sirve la carpeta de salida para probarla
|
|
25
28
|
|
|
29
|
+
Marcar dependencias sin configuración:
|
|
30
|
+
En el HTML: <script src="js/lib.js" data-builder="dependency"></script> (o "own")
|
|
31
|
+
En el archivo: /* @builder-dependency */ al principio (o @builder-own)
|
|
32
|
+
Con deps: builder deps add "js/libs/**" (en otra carpeta: --dir <carpeta>)
|
|
33
|
+
|
|
26
34
|
Opciones de build:
|
|
27
35
|
--out <carpeta> Carpeta de salida (por defecto dist)
|
|
28
36
|
--config <archivo> Archivo de configuración (por defecto builder.config.json)
|
|
@@ -44,6 +52,8 @@ Opciones de build:
|
|
|
44
52
|
--no-merge No une scripts clásicos consecutivos
|
|
45
53
|
--unreferenced <modo> Archivos no referenciados: process | copy | skip (por defecto process)
|
|
46
54
|
--base <ruta> Ruta base del sitio para URLs absolutas (por defecto /)
|
|
55
|
+
--workers <n|auto> Hilos para ofuscar/comprimir en paralelo (0 = sin hilos; por defecto auto)
|
|
56
|
+
--no-cache No usa la caché de compilaciones anteriores
|
|
47
57
|
--no-verify Omite la verificación final (no recomendado)
|
|
48
58
|
--force Permite vaciar una carpeta de salida que no creó Builder
|
|
49
59
|
--keep-failed Conserva la carpeta temporal si la verificación falla
|
|
@@ -68,6 +78,8 @@ function parseArgs(argv) {
|
|
|
68
78
|
'dep',
|
|
69
79
|
'not-dep',
|
|
70
80
|
'minifier',
|
|
81
|
+
'workers',
|
|
82
|
+
'dir',
|
|
71
83
|
]);
|
|
72
84
|
const optionalValue = { obfuscate: ['low', 'medium', 'high', 'max'], sourcemap: ['linked', 'hidden', 'inline'], compress: ['gzip', 'brotli'] };
|
|
73
85
|
for (let i = 0; i < argv.length; i++) {
|
|
@@ -104,6 +116,12 @@ function overridesFrom(args) {
|
|
|
104
116
|
if (args.base) o.base = args.base;
|
|
105
117
|
if (args.verify === false) o.verify = false;
|
|
106
118
|
if (args.compress !== undefined) o.compress = args.compress;
|
|
119
|
+
if (args.cache === false) o.cache = false;
|
|
120
|
+
if (args.workers !== undefined) {
|
|
121
|
+
const n = Number(args.workers);
|
|
122
|
+
if (args.workers !== 'auto' && !(Number.isInteger(n) && n >= 0)) throw new BuildError('--workers debe ser "auto" o un número (0 = sin hilos).');
|
|
123
|
+
o.workers = args.workers === 'auto' ? 'auto' : n;
|
|
124
|
+
}
|
|
107
125
|
const deps = {};
|
|
108
126
|
if (args.dep.length) deps.include = args.dep;
|
|
109
127
|
if (args['not-dep'].length) deps.exclude = args['not-dep'];
|
|
@@ -151,6 +169,7 @@ function jsonResult(result) {
|
|
|
151
169
|
warnings: result.warnings,
|
|
152
170
|
notes: result.notes,
|
|
153
171
|
verify: result.verify && { errors: result.verify.errors, stats: result.verify.stats },
|
|
172
|
+
perf: result.perf,
|
|
154
173
|
};
|
|
155
174
|
}
|
|
156
175
|
|
|
@@ -167,6 +186,18 @@ function printSummary(result, args) {
|
|
|
167
186
|
}
|
|
168
187
|
console.log(`${C.green('✔')} Cobertura: ${result.coverage.size}/${result.coverage.size} archivos de origen con destino conocido`);
|
|
169
188
|
if (r.compressed) console.log(`${C.green('✔')} ${r.compressed} archivos precomprimidos (.gz/.br)`);
|
|
189
|
+
const p = result.perf;
|
|
190
|
+
if (p?.tasks) {
|
|
191
|
+
const plural = (n, one, many) => `${n} ${n === 1 ? one : many}`;
|
|
192
|
+
const where = p.threads ? `en el hilo principal y ${plural(p.threads, 'hilo', 'hilos')} más` : 'en el hilo principal';
|
|
193
|
+
const all = p.cacheHits === p.tasks;
|
|
194
|
+
const detail = all
|
|
195
|
+
? p.tasks === 1
|
|
196
|
+
? 'reutilizado de la caché'
|
|
197
|
+
: 'todos reutilizados de la caché'
|
|
198
|
+
: `${where}${p.cacheHits ? `, ${p.cacheHits} de la caché` : ''}`;
|
|
199
|
+
console.log(`${C.green('✔')} Ofuscación/terser: ${plural(p.tasks, 'archivo', 'archivos')}, ${detail}`);
|
|
200
|
+
}
|
|
170
201
|
if (result.obfuscated) {
|
|
171
202
|
const deps = [
|
|
172
203
|
...result.dependencies.map((d) => `${d.rel} — ${d.reason}`),
|
|
@@ -272,6 +303,79 @@ async function cmdInit(dir, args) {
|
|
|
272
303
|
console.log(C.gray(' En "dependencies.include" marcas a mano librerías; en "dependencies.exclude" corriges una detección.'));
|
|
273
304
|
}
|
|
274
305
|
|
|
306
|
+
/** builder deps [carpeta] — lista; builder deps add|own|reset <patrón…> — edita builder.config.json. */
|
|
307
|
+
async function cmdDeps(_dir, args) {
|
|
308
|
+
const sub = args._[1];
|
|
309
|
+
if (['add', 'own', 'reset'].includes(sub)) return editDeps(sub, args._.slice(2), args);
|
|
310
|
+
const dir = args.dir ?? sub ?? '.';
|
|
311
|
+
const cfg = await loadConfig({ dir, config: args.config, overrides: overridesFrom(args) });
|
|
312
|
+
const result = await runPipeline(cfg, { mode: 'analyze' });
|
|
313
|
+
const deps = new Map(result.dependencies.map((d) => [d.rel, d.reason]));
|
|
314
|
+
const own = [...result.coverage.keys()].filter((rel) => JS_EXT.has(extOf(rel)) && !deps.has(rel)).sort();
|
|
315
|
+
if (args.json) {
|
|
316
|
+
console.log(JSON.stringify({ dependencies: result.dependencies, own }, null, 2));
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
319
|
+
console.log(`${C.bold(`Builder ${VERSION} — dependencias`)} ${cfg.root}`);
|
|
320
|
+
printList(
|
|
321
|
+
'Librerías de terceros (no se ofuscan)',
|
|
322
|
+
[...deps].map(([rel, why]) => `${rel} ${C.gray(`— ${why}`)}`),
|
|
323
|
+
C.cyan,
|
|
324
|
+
200,
|
|
325
|
+
);
|
|
326
|
+
printList('Tu código (se ofusca con --obfuscate)', own, C.green, 200);
|
|
327
|
+
if (!deps.size && !own.length) console.log('\nNo hay archivos JavaScript en el proyecto.');
|
|
328
|
+
console.log(C.gray('\n¿Algo mal clasificado?'));
|
|
329
|
+
console.log(C.gray(' builder deps add <patrón> es una librería: no se ofusca'));
|
|
330
|
+
console.log(C.gray(' builder deps own <patrón> es tu código: sí se ofusca'));
|
|
331
|
+
console.log(C.gray(' builder deps reset <patrón> vuelve a la detección automática'));
|
|
332
|
+
console.log(C.gray(' También: data-builder="dependency" en el <script>, o /* @builder-dependency */ al inicio del archivo.'));
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
async function editDeps(sub, rawPatterns, args) {
|
|
336
|
+
if (!rawPatterns.length) throw new BuildError(`Indica uno o más archivos o patrones: builder deps ${sub} "js/vendor/**"`);
|
|
337
|
+
const projectDir = path.resolve(args.dir ?? '.');
|
|
338
|
+
const file = args.config ? path.resolve(args.config) : (findConfigFile(projectDir) ?? path.join(projectDir, 'builder.config.json'));
|
|
339
|
+
if (!file.endsWith('.json'))
|
|
340
|
+
throw new BuildError(`La configuración está en ${path.basename(file)} (JavaScript); añade los patrones a mano en "dependencies".`);
|
|
341
|
+
const config = fs.existsSync(file) ? JSON.parse(fs.readFileSync(file, 'utf8').replace(/^/, '')) : {};
|
|
342
|
+
if (config.dependencies === false || config.dependencies === true || config.dependencies == null) config.dependencies = {};
|
|
343
|
+
const d = config.dependencies;
|
|
344
|
+
const include = new Set(d.include ?? []);
|
|
345
|
+
const exclude = new Set(d.exclude ?? []);
|
|
346
|
+
const patterns = rawPatterns.map((p) => toPosix(p).replace(/^\.\//, ''));
|
|
347
|
+
for (const p of patterns) {
|
|
348
|
+
if (sub === 'add') {
|
|
349
|
+
include.add(p);
|
|
350
|
+
exclude.delete(p);
|
|
351
|
+
} else if (sub === 'own') {
|
|
352
|
+
exclude.add(p);
|
|
353
|
+
include.delete(p);
|
|
354
|
+
} else {
|
|
355
|
+
include.delete(p);
|
|
356
|
+
exclude.delete(p);
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
d.include = [...include];
|
|
360
|
+
d.exclude = [...exclude];
|
|
361
|
+
// Se valida antes de escribir: nunca deja un builder.config.json roto.
|
|
362
|
+
const cfg = await loadConfig({ dir: projectDir, config: args.config, overrides: { dependencies: { include: d.include, exclude: d.exclude } } });
|
|
363
|
+
const existed = fs.existsSync(file);
|
|
364
|
+
fs.writeFileSync(file, `${JSON.stringify(config, null, 2)}\n`);
|
|
365
|
+
const result = await runPipeline(cfg, { mode: 'analyze' });
|
|
366
|
+
const deps = new Map(result.dependencies.map((x) => [x.rel, x.reason]));
|
|
367
|
+
const jsFiles = [...result.coverage.keys()].filter((rel) => JS_EXT.has(extOf(rel)));
|
|
368
|
+
console.log(`${C.green('✔')} ${existed ? 'Actualizado' : 'Creado'} ${file}`);
|
|
369
|
+
for (const p of patterns) {
|
|
370
|
+
const hits = jsFiles.filter(globMatcher([p]));
|
|
371
|
+
if (!hits.length) {
|
|
372
|
+
console.log(C.yellow(` ! "${p}" no coincide con ningún archivo JavaScript del proyecto (queda guardado igualmente)`));
|
|
373
|
+
continue;
|
|
374
|
+
}
|
|
375
|
+
for (const rel of hits) console.log(` ${rel} ${deps.has(rel) ? C.cyan(`librería — ${deps.get(rel)}`) : C.green('tu código — se ofusca')}`);
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
|
|
275
379
|
async function cmdServe(dir, args) {
|
|
276
380
|
const cfg = await loadConfig({ dir, config: args.config, overrides: overridesFrom(args) });
|
|
277
381
|
const target = fs.existsSync(cfg.outDir) ? cfg.outDir : null;
|
|
@@ -349,7 +453,7 @@ async function main() {
|
|
|
349
453
|
if (args.version || args.v) return console.log(VERSION);
|
|
350
454
|
const [command = 'help', dir = '.'] = args._;
|
|
351
455
|
if (args.help || args.h || command === 'help') return console.log(HELP);
|
|
352
|
-
const commands = { build: cmdBuild, analyze: cmdAnalyze, init: cmdInit, serve: cmdServe, dev: cmdDev };
|
|
456
|
+
const commands = { build: cmdBuild, analyze: cmdAnalyze, init: cmdInit, serve: cmdServe, dev: cmdDev, deps: cmdDeps };
|
|
353
457
|
if (!commands[command]) throw new BuildError(`Comando desconocido "${command}". Usa "builder help".`);
|
|
354
458
|
await commands[command](dir, args);
|
|
355
459
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vanilla-builder",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Zero-config bundler, minifier and obfuscator for vanilla HTML, CSS and JavaScript sites. Detects every dependency, verifies the output and never loses a file.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.js",
|
|
@@ -24,7 +24,8 @@
|
|
|
24
24
|
"test:browser": "node --test test/browser.test.js",
|
|
25
25
|
"test:node16": "node test/unit.test.js && node test/build.test.js",
|
|
26
26
|
"check": "node --test test/unit.test.js test/build.test.js test/browser.test.js",
|
|
27
|
-
"prepublishOnly": "npm test"
|
|
27
|
+
"prepublishOnly": "npm test",
|
|
28
|
+
"bench": "node bench/run.mjs"
|
|
28
29
|
},
|
|
29
30
|
"keywords": [
|
|
30
31
|
"bundler",
|
|
@@ -36,7 +37,10 @@
|
|
|
36
37
|
"css",
|
|
37
38
|
"javascript",
|
|
38
39
|
"build",
|
|
39
|
-
"esbuild"
|
|
40
|
+
"esbuild",
|
|
41
|
+
"typescript",
|
|
42
|
+
"obfuscation",
|
|
43
|
+
"minifier"
|
|
40
44
|
],
|
|
41
45
|
"author": "MarioACo",
|
|
42
46
|
"license": "MIT",
|