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 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
- - **Ligero**: 57 KB propios; la instalación base son 8 paquetes (el grueso es el binario de esbuild). El ofuscador y terser se instalan solo si los usas.
13
- - **Node 16 o superior** (probado en 16, 18, 20, 22 y 24).
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
- | `--json` | Resultado en JSON para CI (código de salida 1 si algo falla). |
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
- - Se detectan como dependencia los archivos en `vendor/`, `node_modules/`, `third-party/`…, los nombres de librerías conocidas (`jquery-3.7.1.min.js`, `bootstrap.bundle.js`…), los `.min.js`/`.umd.js`, el código ya minificado y las cabeceras de licencia con versión (`/*! Foo v1.2.0 */`).
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 corregirlo a mano:
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
- - **Lightweight**: 57 KB of its own code; the base install is 8 packages (most of it is the esbuild binary). The obfuscator and terser are only installed if you use them.
13
- - **Node 16 or newer** (tested on 16, 18, 20, 22 and 24).
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
- | `--json` | JSON output for CI (exit code 1 if anything fails). |
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/`…, well-known library names (`jquery-3.7.1.min.js`, `bootstrap.bundle.js`…), `.min.js`/`.umd.js` files, already-minified code and license headers with a version (`/*! Foo v1.2.0 */`) are detected as dependencies.
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
- To correct it by hand:
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.1.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",