vanilla-builder 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/pool.js CHANGED
@@ -11,6 +11,7 @@ import { ensureObfuscator, obfuscateSafe } from './obfuscate.js';
11
11
  import { importOptional } from './optional.js';
12
12
  import { terserMinify } from './tersercore.js';
13
13
  import { jsImports } from './jsimports.js';
14
+ import { L, getLang } from './i18n.js';
14
15
 
15
16
  const WORKER_URL = new URL('./worker.js', import.meta.url);
16
17
  const OWN_VERSION = JSON.parse(fs.readFileSync(new URL('../package.json', import.meta.url), 'utf8')).version;
@@ -84,7 +85,8 @@ export function createHeavy({ root, workers = autoWorkers(), cacheDir = null })
84
85
 
85
86
  function cacheKey(kind, payload) {
86
87
  const engine = kind === 'obfuscate' ? 'javascript-obfuscator' : kind === 'terser' ? 'terser' : 'acorn';
87
- const meta = JSON.stringify([CACHE_FORMAT, OWN_VERSION, kind, versionOf(engine), payload.opts, plain(payload.opts.globals)]);
88
+ // El idioma entra en la clave: los motivos de rechazo ("no se ofuscó: …") se guardan ya traducidos.
89
+ const meta = JSON.stringify([CACHE_FORMAT, OWN_VERSION, kind, versionOf(engine), getLang(), payload.opts, plain(payload.opts.globals)]);
88
90
  return crypto.createHash('sha256').update(meta).update('\0').update(payload.code).digest('hex');
89
91
  }
90
92
 
@@ -175,12 +177,12 @@ export function createHeavy({ root, workers = autoWorkers(), cacheDir = null })
175
177
  await ensureObfuscator(root);
176
178
  return obfuscateSafe(payload.code, payload.opts);
177
179
  }
178
- inProcessTerser ??= await importOptional('terser', root, 'para la compresión máxima');
180
+ inProcessTerser ??= await importOptional('terser', root, L('para la compresión máxima', 'for maximum compression'));
179
181
  return terserMinify(inProcessTerser, payload.code, payload.opts);
180
182
  }
181
183
 
182
184
  function spawn() {
183
- const w = new Worker(WORKER_URL, { workerData: { root } });
185
+ const w = new Worker(WORKER_URL, { workerData: { root, lang: getLang() } });
184
186
  w.on('message', ({ id, result, error }) => {
185
187
  const p = pending.get(id);
186
188
  pending.delete(id);
@@ -283,7 +285,7 @@ export function createHeavy({ root, workers = autoWorkers(), cacheDir = null })
283
285
  const key = cacheDir
284
286
  ? crypto
285
287
  .createHash('sha256')
286
- .update(JSON.stringify([CACHE_FORMAT, kind]))
288
+ .update(JSON.stringify([CACHE_FORMAT, OWN_VERSION, getLang(), kind]))
287
289
  .update('\0')
288
290
  .update(keyData)
289
291
  .digest('hex')
package/src/report.js CHANGED
@@ -1,6 +1,7 @@
1
1
  // Informe de tamaños (original, final, gzip y brotli) por archivo y totales.
2
2
  import { formatBytes } from './util.js';
3
3
  import { gzipOf, brotliOf, mapLimit } from './compress.js';
4
+ import { L } from './i18n.js';
4
5
 
5
6
  const COMPRESSIBLE = new Set(['html', 'css', 'js', 'json', 'svg', 'text']);
6
7
 
@@ -51,15 +52,20 @@ export function renderReport(report, C, { verbose = false } = {}) {
51
52
  const lines = [];
52
53
  const shown = report.rows.filter((r) => verbose || r.action !== 'copy');
53
54
  const hidden = report.rows.filter((r) => !verbose && r.action === 'copy');
54
- const hiddenLabel = `+ ${hidden.length} recursos copiados sin cambios`;
55
+ const hiddenLabel = L(`+ ${hidden.length} recursos copiados sin cambios`, `+ ${hidden.length} assets copied unchanged`);
55
56
  const w = Math.min(60, Math.max(20, ...shown.map((r) => r.file.length), hidden.length ? hiddenLabel.length : 0));
56
57
  const pad = (s, n) => (s.length > n ? `…${s.slice(-(n - 1))}` : s.padEnd(n));
57
58
  const num = (n) => (n == null ? '—' : formatBytes(n)).padStart(10);
58
59
  lines.push(
59
- C.gray(`${'Archivo'.padEnd(w)} ${'Antes'.padStart(10)} ${'Después'.padStart(10)} ${'gzip'.padStart(10)} ${'brotli'.padStart(10)} Ahorro`),
60
+ C.gray(
61
+ L(
62
+ `${'Archivo'.padEnd(w)} ${'Antes'.padStart(10)} ${'Después'.padStart(10)} ${'gzip'.padStart(10)} ${'brotli'.padStart(10)} Ahorro`,
63
+ `${'File'.padEnd(w)} ${'Before'.padStart(10)} ${'After'.padStart(10)} ${'gzip'.padStart(10)} ${'brotli'.padStart(10)} Saved`,
64
+ ),
65
+ ),
60
66
  );
61
67
  for (const r of shown) {
62
- const src = r.sources && r.sources.length > 1 ? C.gray(` (${r.sources.length} archivos)`) : '';
68
+ const src = r.sources && r.sources.length > 1 ? C.gray(L(` (${r.sources.length} archivos)`, ` (${r.sources.length} files)`)) : '';
63
69
  lines.push(`${pad(r.file, w)} ${num(r.before)} ${num(r.size)} ${num(r.gzip)} ${num(r.brotli)} ${pct(r.before, r.size).padStart(6)}${src}`);
64
70
  }
65
71
  if (hidden.length) {
package/src/server.js CHANGED
@@ -2,6 +2,7 @@
2
2
  import fs from 'node:fs';
3
3
  import http from 'node:http';
4
4
  import path from 'node:path';
5
+ import { L } from './i18n.js';
5
6
 
6
7
  const MIME = {
7
8
  '.html': 'text/html; charset=utf-8',
@@ -42,7 +43,8 @@ const MIME = {
42
43
 
43
44
  const RELOAD_SCRIPT = `<script>(()=>{const s=new EventSource('/__builder/events');let box;
44
45
  s.addEventListener('reload',()=>location.reload());
45
- s.addEventListener('error-build',e=>{box??=document.body.appendChild(Object.assign(document.createElement('pre'),{style:'position:fixed;inset:auto 0 0 0;max-height:50vh;overflow:auto;margin:0;padding:12px;background:#300;color:#fdd;font:12px/1.4 monospace;z-index:2147483647;white-space:pre-wrap'}));box.textContent='Builder: error al compilar\\n\\n'+JSON.parse(e.data)});
46
+ s.addEventListener('css',()=>{for(const l of document.querySelectorAll('link[rel~="stylesheet"][href]')){const u=new URL(l.href,location.href);if(u.origin!==location.origin)continue;u.searchParams.set('vb',Date.now());const n=l.cloneNode();n.href=u.href;n.onload=n.onerror=()=>l.remove();l.after(n)}});
47
+ s.addEventListener('error-build',e=>{box??=document.body.appendChild(Object.assign(document.createElement('pre'),{style:'position:fixed;inset:auto 0 0 0;max-height:50vh;overflow:auto;margin:0;padding:12px;background:#300;color:#fdd;font:12px/1.4 monospace;z-index:2147483647;white-space:pre-wrap'}));box.textContent=JSON.parse(e.data)});
46
48
  s.addEventListener('ok',()=>{box?.remove();box=null});})();</script>`;
47
49
 
48
50
  /**
@@ -56,7 +58,7 @@ export function startServer({ dir, port = 5173, host = '127.0.0.1', liveReload =
56
58
  try {
57
59
  pathname = decodeURIComponent(new URL(req.url, 'http://x').pathname);
58
60
  } catch {
59
- res.writeHead(400).end('URL inválida');
61
+ res.writeHead(400).end(L('URL inválida', 'Invalid URL'));
60
62
  return;
61
63
  }
62
64
  if (liveReload && pathname === '/__builder/events') {
@@ -84,7 +86,7 @@ export function startServer({ dir, port = 5173, host = '127.0.0.1', liveReload =
84
86
  }
85
87
  fs.readFile(file, (err, data) => {
86
88
  if (err) {
87
- res.writeHead(404, { 'Content-Type': 'text/plain; charset=utf-8' }).end(`No encontrado: ${pathname}`);
89
+ res.writeHead(404, { 'Content-Type': 'text/plain; charset=utf-8' }).end(L(`No encontrado: ${pathname}`, `Not found: ${pathname}`));
88
90
  return;
89
91
  }
90
92
  const ext = path.extname(file).toLowerCase();
@@ -111,7 +113,9 @@ export function startServer({ dir, port = 5173, host = '127.0.0.1', liveReload =
111
113
  url: `http://${host === '0.0.0.0' ? 'localhost' : host}:${addr.port}/`,
112
114
  port: addr.port,
113
115
  reload: () => send('reload'),
114
- buildError: (msg) => send('error-build', msg),
116
+ // Solo cambió CSS: se recargan las hojas de estilos sin perder el estado de la página.
117
+ reloadCss: () => send('css'),
118
+ buildError: (msg) => send('error-build', `${L('Builder: error al compilar', 'Builder: build failed')}\n\n${msg}`),
115
119
  buildOk: () => send('ok'),
116
120
  close: () =>
117
121
  new Promise((r) => {
package/src/tersercore.js CHANGED
@@ -5,7 +5,7 @@ import { compareGlobals, moduleShape } from './jsinfo.js';
5
5
  * Comprime con terser y solo acepta el resultado si es más pequeño y conserva las globales (scripts)
6
6
  * o las exportaciones/importaciones (módulos). Devuelve el código original si no compensa.
7
7
  */
8
- export async function terserMinify(terser, code, { isModule, level, globals, keepNames = false }) {
8
+ export async function terserMinify(terser, code, { isModule, level, globals, allowPrefix = null, keepNames = false }) {
9
9
  const ecma = Math.min(2020, Math.max(5, level ?? 2020));
10
10
  let out;
11
11
  try {
@@ -28,6 +28,6 @@ export async function terserMinify(terser, code, { isModule, level, globals, kee
28
28
  const a = moduleShape(code);
29
29
  const b = moduleShape(out);
30
30
  if (!a || !b || a.exports !== b.exports || a.imports !== b.imports) return code;
31
- } else if (!globals || !compareGlobals(globals, out).ok) return code;
31
+ } else if (!globals || !compareGlobals(globals, out, allowPrefix).ok) return code;
32
32
  return out;
33
33
  }
package/src/verify.js CHANGED
@@ -5,6 +5,7 @@ import { jsImports } from './jsimports.js';
5
5
  import { mapLimit } from './compress.js';
6
6
  import { parseHtml } from './html.js';
7
7
  import { classifyUrl, parseSrcset } from './urls.js';
8
+ import { L } from './i18n.js';
8
9
 
9
10
  function unescapeCss(s) {
10
11
  return s.replace(/\\([0-9a-f]{1,6}[ \t\n]?|[\s\S])/gi, (_, e) => (/^[0-9a-f]/i.test(e) ? String.fromCodePoint(parseInt(e, 16)) : e));
@@ -82,7 +83,12 @@ export async function verifyOutput(staging, outputs, ctx) {
82
83
  const candidates = rel === '' || rel.endsWith('/') ? [`${rel}index.html`] : page ? [rel, `${rel}/index.html`, `${rel}.html`] : [rel];
83
84
  if (candidates.some(exists)) continue;
84
85
  const ci = candidates.map((c) => lower.get(c.toLowerCase())).find(Boolean);
85
- errors.push(`${fromRel}: la referencia "${u}" no existe en la salida${ci ? ` (existe "${ci}" con otras mayúsculas)` : ''}.`);
86
+ errors.push(
87
+ L(
88
+ `${fromRel}: la referencia "${u}" no existe en la salida${ci ? ` (existe "${ci}" con otras mayúsculas)` : ''}.`,
89
+ `${fromRel}: the reference "${u}" does not exist in the output${ci ? ` ("${ci}" exists with different capitalization)` : ''}.`,
90
+ ),
91
+ );
86
92
  }
87
93
  };
88
94
 
@@ -98,11 +104,12 @@ export async function verifyOutput(staging, outputs, ctx) {
98
104
  try {
99
105
  st = fs.statSync(dest);
100
106
  } catch {
101
- errors.push(`${rel}: no se escribió en disco.`);
107
+ errors.push(L(`${rel}: no se escribió en disco.`, `${rel}: was not written to disk.`));
102
108
  continue;
103
109
  }
104
110
  const expected = o.copyFrom ? o.size : Buffer.byteLength(o.data);
105
- if (st.size !== expected) errors.push(`${rel}: tamaño en disco ${st.size} ≠ esperado ${expected}.`);
111
+ if (st.size !== expected)
112
+ errors.push(L(`${rel}: tamaño en disco ${st.size} ≠ esperado ${expected}.`, `${rel}: size on disk ${st.size} ≠ expected ${expected}.`));
106
113
  if (o.copyFrom) continue;
107
114
  const dir = path.posix.dirname(rel) === '.' ? '' : path.posix.dirname(rel);
108
115
  if (o.type === 'html') {
@@ -115,7 +122,13 @@ export async function verifyOutput(staging, outputs, ctx) {
115
122
  if (s.src) check(rel, s.src.value, baseDir);
116
123
  if (s.inline && s.kind === 'module') {
117
124
  const r = jsImports(s.inline.code, true);
118
- if (r.error) errors.push(`${rel}: un <script type="module"> inline no es JS válido (${r.error}).`);
125
+ if (r.error)
126
+ errors.push(
127
+ L(
128
+ `${rel}: un <script type="module"> inline no es JS válido (${r.error}).`,
129
+ `${rel}: an inline <script type="module"> is not valid JS (${r.error}).`,
130
+ ),
131
+ );
119
132
  else for (const spec of r.imports) if (/^\.{0,2}\//.test(spec)) check(rel, spec, baseDir);
120
133
  }
121
134
  }
@@ -127,12 +140,19 @@ export async function verifyOutput(staging, outputs, ctx) {
127
140
  } else if (o.type === 'js') {
128
141
  stats.js++;
129
142
  const r = parsed.get(rel);
130
- if (r.error) errors.push(`${rel}: el JavaScript generado no es válido (${r.error}).`);
143
+ if (r.error)
144
+ errors.push(L(`${rel}: el JavaScript generado no es válido (${r.error}).`, `${rel}: the generated JavaScript is not valid (${r.error}).`));
131
145
  else for (const spec of r.imports) if (/^\.{0,2}\//.test(spec)) check(rel, spec, dir);
132
146
  }
133
147
  }
134
148
 
135
149
  const missing = [...ctx.files.keys()].filter((rel) => !ctx.coverage.has(rel));
136
- for (const rel of missing) errors.push(`${rel}: Builder no registró qué hizo con este archivo (no se publicaría).`);
150
+ for (const rel of missing)
151
+ errors.push(
152
+ L(
153
+ `${rel}: Builder no registró qué hizo con este archivo (no se publicaría).`,
154
+ `${rel}: Builder did not record what it did with this file (it would not be published).`,
155
+ ),
156
+ );
137
157
  return { errors, warnings, stats };
138
158
  }
package/src/worker.js CHANGED
@@ -4,9 +4,13 @@ import { ensureObfuscator, obfuscateSafe } from './obfuscate.js';
4
4
  import { importOptional } from './optional.js';
5
5
  import { terserMinify } from './tersercore.js';
6
6
  import { jsImports } from './jsimports.js';
7
+ import { L, setLang } from './i18n.js';
7
8
 
8
9
  let terser = null;
9
10
 
11
+ // Mismo idioma que el proceso principal (puede venir de --lang).
12
+ setLang(workerData.lang);
13
+
10
14
  parentPort.on('message', async ({ id, kind, payload }) => {
11
15
  try {
12
16
  let result;
@@ -14,7 +18,7 @@ parentPort.on('message', async ({ id, kind, payload }) => {
14
18
  await ensureObfuscator(workerData.root);
15
19
  result = obfuscateSafe(payload.code, payload.opts);
16
20
  } else if (kind === 'terser') {
17
- terser ??= await importOptional('terser', workerData.root, 'para la compresión máxima');
21
+ terser ??= await importOptional('terser', workerData.root, L('para la compresión máxima', 'for maximum compression'));
18
22
  result = await terserMinify(terser, payload.code, payload.opts);
19
23
  } else if (kind === 'imports') {
20
24
  result = jsImports(payload.code, payload.opts.isModule);