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/CHANGELOG.md +21 -0
- package/README.es.md +531 -154
- package/README.md +530 -155
- package/bin/builder.js +345 -63
- package/package.json +16 -3
- package/src/browsers.js +128 -0
- package/src/budgets.js +77 -0
- package/src/builder.js +367 -103
- package/src/config.js +106 -23
- package/src/deps.js +12 -7
- package/src/files.js +12 -7
- package/src/html.js +10 -3
- package/src/i18n.js +33 -0
- package/src/images.js +61 -0
- package/src/js.js +99 -42
- package/src/jsinfo.js +63 -4
- package/src/obfuscate.js +8 -4
- package/src/optional.js +7 -3
- package/src/output.js +37 -9
- package/src/plugins.js +126 -0
- package/src/pool.js +6 -4
- package/src/report.js +9 -3
- package/src/server.js +8 -4
- package/src/tersercore.js +2 -2
- package/src/verify.js +26 -6
- package/src/worker.js +5 -1
package/src/builder.js
CHANGED
|
@@ -23,6 +23,9 @@ import { detectDependency, dependencyPragma, packageNames } from './deps.js';
|
|
|
23
23
|
import { assertSafeOutDir, writeStaging, swapIntoPlace, discardStaging, writeManifest, MANIFEST } from './output.js';
|
|
24
24
|
import { verifyOutput } from './verify.js';
|
|
25
25
|
import { rewriteTsImports } from './jsimports.js';
|
|
26
|
+
import { IMAGE_EXT, ensureSharp, imageOptions, optimizeImage } from './images.js';
|
|
27
|
+
import { checkBudgets } from './budgets.js';
|
|
28
|
+
import { TRANSFORM_EXT, runHook, runTransform } from './plugins.js';
|
|
26
29
|
import { buildReport } from './report.js';
|
|
27
30
|
import {
|
|
28
31
|
BuildError,
|
|
@@ -41,6 +44,7 @@ import {
|
|
|
41
44
|
minifyJsonText,
|
|
42
45
|
hasOwn,
|
|
43
46
|
} from './util.js';
|
|
47
|
+
import { L } from './i18n.js';
|
|
44
48
|
|
|
45
49
|
export const VERSION = JSON.parse(fs.readFileSync(new URL('../package.json', import.meta.url), 'utf8')).version;
|
|
46
50
|
|
|
@@ -94,6 +98,7 @@ class Context {
|
|
|
94
98
|
this.depCache = new Map();
|
|
95
99
|
this.depMarks = new Map();
|
|
96
100
|
this.tsCode = new Map();
|
|
101
|
+
this.sourceOverrides = new Map();
|
|
97
102
|
this.pkgNames = null;
|
|
98
103
|
this.vendorPackages = [];
|
|
99
104
|
this.chunkDir = cfg.js.chunkDir.replace(/\/+$/, '');
|
|
@@ -129,8 +134,10 @@ class Context {
|
|
|
129
134
|
}
|
|
130
135
|
|
|
131
136
|
readText(rel, { kind = 'text', charset } = {}) {
|
|
132
|
-
// TypeScript: como JS se lee la versión ya transpilada (sin tipos).
|
|
137
|
+
// TypeScript: como JS se lee la versión ya transpilada (sin tipos; ya incluye los cambios de los plugins).
|
|
133
138
|
if (kind === 'js' && this.tsCode.has(rel)) return { text: this.tsCode.get(rel), bom: false, encoding: 'utf8' };
|
|
139
|
+
// Código fuente cambiado por un plugin (transform): es el que se procesa a partir de aquí.
|
|
140
|
+
if (this.sourceOverrides.has(rel)) return { ...this.sourceOverrides.get(rel), encoding: 'utf8' };
|
|
134
141
|
const key = `${rel}\0${kind}`;
|
|
135
142
|
if (this.textCache.has(key)) return this.textCache.get(key);
|
|
136
143
|
const buf = fs.readFileSync(this.abs(rel));
|
|
@@ -141,7 +148,13 @@ class Context {
|
|
|
141
148
|
// latin1 conserva cada byte tal cual: lo que no se edita sale idéntico.
|
|
142
149
|
text = buf.toString('latin1');
|
|
143
150
|
encoding = 'latin1';
|
|
144
|
-
this.warn(
|
|
151
|
+
this.warn(
|
|
152
|
+
L(
|
|
153
|
+
'no es UTF-8; se conserva su codificación original y no se minifica',
|
|
154
|
+
'is not UTF-8; its original encoding is kept and it is not minified',
|
|
155
|
+
),
|
|
156
|
+
rel,
|
|
157
|
+
);
|
|
145
158
|
} else {
|
|
146
159
|
let label = (charset || 'windows-1252').toLowerCase();
|
|
147
160
|
try {
|
|
@@ -151,7 +164,13 @@ class Context {
|
|
|
151
164
|
text = new TextDecoder(label).decode(buf);
|
|
152
165
|
}
|
|
153
166
|
encoding = label;
|
|
154
|
-
this.note(
|
|
167
|
+
this.note(
|
|
168
|
+
L(
|
|
169
|
+
`no es UTF-8; se leyó como ${label} y la salida usa escapes ASCII (no depende de la codificación)`,
|
|
170
|
+
`is not UTF-8; it was read as ${label} and the output uses ASCII escapes (independent of the encoding)`,
|
|
171
|
+
),
|
|
172
|
+
rel,
|
|
173
|
+
);
|
|
155
174
|
}
|
|
156
175
|
}
|
|
157
176
|
const stripped = stripBom(text);
|
|
@@ -172,17 +191,36 @@ class Context {
|
|
|
172
191
|
if (this.brokenKeys.has(key)) return;
|
|
173
192
|
this.brokenKeys.add(key);
|
|
174
193
|
this.broken.push({ from, url, status: r.status, target: r.rel });
|
|
175
|
-
if (r.status === 'ignored')
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
194
|
+
if (r.status === 'ignored')
|
|
195
|
+
this.warn(
|
|
196
|
+
L(
|
|
197
|
+
`referencia a "${url}", pero ese archivo está excluido (ignore). No se publicará.`,
|
|
198
|
+
`references "${url}", but that file is excluded (ignore). It will not be published.`,
|
|
199
|
+
),
|
|
200
|
+
from,
|
|
201
|
+
);
|
|
202
|
+
else if (r.status === 'case')
|
|
203
|
+
this.warn(
|
|
204
|
+
L(
|
|
205
|
+
`"${url}" solo existe como "${r.rel}" (otras mayúsculas): en un servidor Linux daría 404.`,
|
|
206
|
+
`"${url}" only exists as "${r.rel}" (different capitalization): it would be a 404 on a Linux server.`,
|
|
207
|
+
),
|
|
208
|
+
from,
|
|
209
|
+
);
|
|
210
|
+
else if (r.status === 'missing')
|
|
211
|
+
this.warn(
|
|
212
|
+
L(`"${url}" no existe (ya estaba rota en el código fuente).`, `"${url}" does not exist (it was already broken in the source code).`),
|
|
213
|
+
from,
|
|
214
|
+
);
|
|
215
|
+
else if (r.status === 'outside')
|
|
216
|
+
this.note(L(`"${url}" apunta fuera del proyecto; se deja igual.`, `"${url}" points outside the project; left as is.`), from);
|
|
179
217
|
}
|
|
180
218
|
|
|
181
219
|
registerAsset(rel, from) {
|
|
182
220
|
if (!this.assets.has(rel)) this.assets.set(rel, new Set());
|
|
183
221
|
if (from) this.assets.get(rel).add(from);
|
|
184
222
|
const ext = extOf(rel);
|
|
185
|
-
if (JS_EXT.has(ext) || CSS_EXT.has(ext)) this.requireStandalone(rel, `referenciado desde ${from}`);
|
|
223
|
+
if (JS_EXT.has(ext) || CSS_EXT.has(ext)) this.requireStandalone(rel, L(`referenciado desde ${from}`, `referenced from ${from}`));
|
|
186
224
|
}
|
|
187
225
|
|
|
188
226
|
assetOut(rel) {
|
|
@@ -193,7 +231,7 @@ class Context {
|
|
|
193
231
|
urlTarget(rel, from) {
|
|
194
232
|
const ext = extOf(rel);
|
|
195
233
|
if (JS_EXT.has(ext) || CSS_EXT.has(ext)) {
|
|
196
|
-
if (!this.workers.has(rel)) this.requireStandalone(rel, `new URL() en ${from}`);
|
|
234
|
+
if (!this.workers.has(rel)) this.requireStandalone(rel, L(`new URL() en ${from}`, `new URL() in ${from}`));
|
|
197
235
|
} else this.registerAsset(rel, from);
|
|
198
236
|
return jsOutPath(rel);
|
|
199
237
|
}
|
|
@@ -206,7 +244,14 @@ class Context {
|
|
|
206
244
|
registerWorker(rel, info) {
|
|
207
245
|
const prev = this.workers.get(rel);
|
|
208
246
|
if (prev) {
|
|
209
|
-
if (prev.module !== info.module)
|
|
247
|
+
if (prev.module !== info.module)
|
|
248
|
+
this.warn(
|
|
249
|
+
L(
|
|
250
|
+
`se usa como worker clásico y de módulo a la vez; se trata como módulo`,
|
|
251
|
+
`used as both a classic and a module worker; treated as a module`,
|
|
252
|
+
),
|
|
253
|
+
rel,
|
|
254
|
+
);
|
|
210
255
|
prev.module = prev.module || info.module;
|
|
211
256
|
return;
|
|
212
257
|
}
|
|
@@ -217,7 +262,13 @@ class Context {
|
|
|
217
262
|
noteJsInfo(id, info, baseDir) {
|
|
218
263
|
for (const w of info.workers) {
|
|
219
264
|
if (!w.spec) {
|
|
220
|
-
this.warn(
|
|
265
|
+
this.warn(
|
|
266
|
+
L(
|
|
267
|
+
`${w.kind} con una ruta que no es texto fijo; Builder no puede seguirla (el archivo se procesará suelto si existe).`,
|
|
268
|
+
`${w.kind} with a path that is not fixed text; Builder cannot follow it (the file will be processed on its own if it exists).`,
|
|
269
|
+
),
|
|
270
|
+
id,
|
|
271
|
+
);
|
|
221
272
|
continue;
|
|
222
273
|
}
|
|
223
274
|
const c = classifyUrl(w.spec);
|
|
@@ -228,7 +279,7 @@ class Context {
|
|
|
228
279
|
this.reportBroken(id, w.spec, this.resolve(w.spec, baseDir));
|
|
229
280
|
continue;
|
|
230
281
|
}
|
|
231
|
-
if (w.kind === 'importScripts') this.requireStandalone(rel, `importScripts en ${id}`, { classic: true });
|
|
282
|
+
if (w.kind === 'importScripts') this.requireStandalone(rel, L(`importScripts en ${id}`, `importScripts in ${id}`), { classic: true });
|
|
232
283
|
else this.registerWorker(rel, { module: w.module, kind: w.kind, from: id });
|
|
233
284
|
}
|
|
234
285
|
for (const s of info.strings) {
|
|
@@ -261,9 +312,10 @@ class Context {
|
|
|
261
312
|
if (this.depExcluded(rel)) reason = null;
|
|
262
313
|
else if (this.depIncluded(rel)) reason = 'marcado a mano (dependencies.include)';
|
|
263
314
|
else if (mark === 'own' || pragma === 'own') reason = null;
|
|
264
|
-
else if (mark === 'dependency') reason = 'marcado en el HTML (data-builder="dependency")';
|
|
265
|
-
else if (pragma === 'dependency')
|
|
266
|
-
|
|
315
|
+
else if (mark === 'dependency') reason = L('marcado en el HTML (data-builder="dependency")', 'marked in the HTML (data-builder="dependency")');
|
|
316
|
+
else if (pragma === 'dependency')
|
|
317
|
+
reason = L('marcado en el propio archivo (@builder-dependency)', 'marked in the file itself (@builder-dependency)');
|
|
318
|
+
else if (/(^|\/)node_modules\//.test(rel)) reason = L('paquete de npm', 'npm package');
|
|
267
319
|
else if (deps.detect && isJs) reason = detectDependency(rel, text, { packages: this.packageHints() });
|
|
268
320
|
this.depCache.set(rel, reason);
|
|
269
321
|
return reason;
|
|
@@ -301,23 +353,35 @@ class Context {
|
|
|
301
353
|
/** Registra un archivo de salida; nunca sobrescribe en silencio un contenido distinto. */
|
|
302
354
|
emit(outRel, data, meta) {
|
|
303
355
|
const norm = path.posix.normalize(outRel);
|
|
304
|
-
if (norm.startsWith('../') || path.posix.isAbsolute(norm) || norm === MANIFEST)
|
|
356
|
+
if (norm.startsWith('../') || path.posix.isAbsolute(norm) || norm === MANIFEST)
|
|
357
|
+
throw new BuildError(L(`Ruta de salida inválida: ${outRel}`, `Invalid output path: ${outRel}`));
|
|
305
358
|
const buf = Buffer.isBuffer(data) ? data : Buffer.from(data, meta.encoding === 'latin1' ? 'latin1' : 'utf8');
|
|
306
359
|
const prev = this.outputs.get(norm);
|
|
307
360
|
if (prev) {
|
|
308
361
|
const same = prev.copyFrom ? fs.readFileSync(prev.copyFrom).equals(buf) : prev.data.equals(buf);
|
|
309
362
|
if (!same)
|
|
310
|
-
throw new BuildError(
|
|
311
|
-
`
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
363
|
+
throw new BuildError(
|
|
364
|
+
L(`Dos resultados distintos quieren escribirse en "${norm}".`, `Two different results want to be written to "${norm}".`),
|
|
365
|
+
[
|
|
366
|
+
L(`Primero: ${(prev.sources ?? []).join(', ') || prev.action}`, `First: ${(prev.sources ?? []).join(', ') || prev.action}`),
|
|
367
|
+
L(`Después: ${(meta.sources ?? []).join(', ') || meta.action}`, `Second: ${(meta.sources ?? []).join(', ') || meta.action}`),
|
|
368
|
+
L(
|
|
369
|
+
'Suele pasar si el mismo archivo se usa con import maps distintos o si una carpeta del proyecto se llama igual que js.chunkDir.',
|
|
370
|
+
'This usually happens when the same file is used with different import maps, or when a project folder has the same name as js.chunkDir.',
|
|
371
|
+
),
|
|
372
|
+
],
|
|
373
|
+
);
|
|
315
374
|
prev.sources = [...new Set([...(prev.sources ?? []), ...(meta.sources ?? [])])];
|
|
316
375
|
return norm;
|
|
317
376
|
}
|
|
318
377
|
const lower = norm.toLowerCase();
|
|
319
378
|
if (this.outLower.has(lower) && this.outLower.get(lower) !== norm)
|
|
320
|
-
this.warn(
|
|
379
|
+
this.warn(
|
|
380
|
+
L(
|
|
381
|
+
`"${norm}" y "${this.outLower.get(lower)}" solo se diferencian en mayúsculas; en Windows/macOS se pisarían.`,
|
|
382
|
+
`"${norm}" and "${this.outLower.get(lower)}" differ only in capitalization; on Windows/macOS they would overwrite each other.`,
|
|
383
|
+
),
|
|
384
|
+
);
|
|
321
385
|
this.outLower.set(lower, norm);
|
|
322
386
|
this.outputs.set(norm, { ...meta, data: buf });
|
|
323
387
|
return norm;
|
|
@@ -325,6 +389,8 @@ class Context {
|
|
|
325
389
|
|
|
326
390
|
emitCopy(outRel, rel, action = 'copy') {
|
|
327
391
|
const f = this.files.get(rel);
|
|
392
|
+
const override = this.sourceOverrides.get(rel);
|
|
393
|
+
if (override) return this.emit(outRel, (override.bom ? BOM : '') + override.text, { type: typeOfExt(f.ext), action, sources: [rel] });
|
|
328
394
|
const prev = this.outputs.get(outRel);
|
|
329
395
|
if (prev) {
|
|
330
396
|
if (prev.sources?.includes(rel)) return outRel;
|
|
@@ -386,7 +452,7 @@ function loadPages(ctx) {
|
|
|
386
452
|
try {
|
|
387
453
|
model = parseHtml(text);
|
|
388
454
|
} catch (err) {
|
|
389
|
-
ctx.warn(`no se pudo analizar el HTML (${err.message}); se copia tal cual`, rel);
|
|
455
|
+
ctx.warn(L(`no se pudo analizar el HTML (${err.message}); se copia tal cual`, `could not parse the HTML (${err.message}); copied as is`), rel);
|
|
390
456
|
continue;
|
|
391
457
|
}
|
|
392
458
|
const dir = path.posix.dirname(rel) === '.' ? '' : path.posix.dirname(rel);
|
|
@@ -395,7 +461,13 @@ function loadPages(ctx) {
|
|
|
395
461
|
const c = classifyUrl(model.base);
|
|
396
462
|
if (c.type === 'external') {
|
|
397
463
|
page.baseExternal = true;
|
|
398
|
-
ctx.warn(
|
|
464
|
+
ctx.warn(
|
|
465
|
+
L(
|
|
466
|
+
`<base href="${model.base}"> apunta fuera del sitio; sus rutas relativas no se pueden comprobar y se dejan igual.`,
|
|
467
|
+
`<base href="${model.base}"> points outside the site; its relative paths cannot be checked and are left as they are.`,
|
|
468
|
+
),
|
|
469
|
+
rel,
|
|
470
|
+
);
|
|
399
471
|
} else if (c.type === 'root' || c.type === 'relative') {
|
|
400
472
|
const p = c.type === 'root' ? (c.path.startsWith(ctx.base) ? c.path.slice(ctx.base.length) : c.path.slice(1)) : path.posix.join(dir, c.path);
|
|
401
473
|
page.baseDir = path.posix
|
|
@@ -410,6 +482,20 @@ function loadPages(ctx) {
|
|
|
410
482
|
}
|
|
411
483
|
|
|
412
484
|
/** Decide qué páginas se procesan completas (entradas + enlazadas) y cuáles en modo conservador. */
|
|
485
|
+
/** Pasa el código fuente por los plugins con transform (en paralelo entre archivos, en orden entre plugins). */
|
|
486
|
+
async function applyTransforms(ctx) {
|
|
487
|
+
const plugins = ctx.cfg.plugins.filter((p) => typeof p.transform === 'function');
|
|
488
|
+
if (!plugins.length) return;
|
|
489
|
+
const list = [...ctx.files.keys()].filter((rel) => TRANSFORM_EXT.has(extOf(rel)));
|
|
490
|
+
await mapLimit(list, 16, async (rel) => {
|
|
491
|
+
const text = decodeUtf8(fs.readFileSync(ctx.abs(rel)));
|
|
492
|
+
if (text == null) return; // otras codificaciones: se respetan los bytes originales
|
|
493
|
+
const src = stripBom(text);
|
|
494
|
+
const out = await runTransform(plugins, rel, src.text);
|
|
495
|
+
if (out !== src.text) ctx.sourceOverrides.set(rel, { text: out, bom: src.bom });
|
|
496
|
+
});
|
|
497
|
+
}
|
|
498
|
+
|
|
413
499
|
/**
|
|
414
500
|
* Transpila cada archivo TypeScript a JavaScript (solo quita los tipos: no baja el nivel de sintaxis).
|
|
415
501
|
* Usa el tsconfig.json del proyecto si existe. Lo que falle se avisa y sigue el camino normal
|
|
@@ -445,7 +531,7 @@ async function transpileTypeScript(ctx) {
|
|
|
445
531
|
ctx.tsCode.set(rel, rewriteTsImports(r.code, complete));
|
|
446
532
|
} catch (err) {
|
|
447
533
|
const msg = String(err.errors?.[0]?.text ?? err.message).split('\n')[0];
|
|
448
|
-
ctx.warn(`TypeScript no válido (${msg}); se copia sin transpilar`, rel);
|
|
534
|
+
ctx.warn(L(`TypeScript no válido (${msg}); se copia sin transpilar`, `invalid TypeScript (${msg}); copied without transpiling`), rel);
|
|
449
535
|
}
|
|
450
536
|
});
|
|
451
537
|
}
|
|
@@ -461,14 +547,23 @@ function markDependencies(ctx, pages) {
|
|
|
461
547
|
.toLowerCase();
|
|
462
548
|
const value = MARK_VALUES[raw];
|
|
463
549
|
if (!value) {
|
|
464
|
-
ctx.warn(
|
|
550
|
+
ctx.warn(
|
|
551
|
+
L(`data-builder="${raw}" no se reconoce; usa "dependency" o "own"`, `data-builder="${raw}" is not recognized; use "dependency" or "own"`),
|
|
552
|
+
page.rel,
|
|
553
|
+
);
|
|
465
554
|
continue;
|
|
466
555
|
}
|
|
467
556
|
const res = ctx.resolve(s.src.value, page.baseDir);
|
|
468
557
|
if (res.status !== 'ok') continue;
|
|
469
558
|
const prev = ctx.depMarks.get(res.rel);
|
|
470
559
|
if (prev && prev !== value)
|
|
471
|
-
ctx.warn(
|
|
560
|
+
ctx.warn(
|
|
561
|
+
L(
|
|
562
|
+
`"${res.rel}" está marcado como "${prev}" y como "${value}" en distintas páginas; se usa "${prev}"`,
|
|
563
|
+
`"${res.rel}" is marked as "${prev}" and as "${value}" on different pages; using "${prev}"`,
|
|
564
|
+
),
|
|
565
|
+
page.rel,
|
|
566
|
+
);
|
|
472
567
|
else ctx.depMarks.set(res.rel, value);
|
|
473
568
|
}
|
|
474
569
|
}
|
|
@@ -482,7 +577,7 @@ function selectEntries(ctx, pages) {
|
|
|
482
577
|
queue = [...pages.values()].filter((p) => p.model.isDocument).map((p) => p.rel);
|
|
483
578
|
} else {
|
|
484
579
|
const { matched, missing } = matchFiles(ctx.files, cfg.entries);
|
|
485
|
-
if (missing.length) throw new BuildError('Algunas entradas de "entries" no existen:', missing);
|
|
580
|
+
if (missing.length) throw new BuildError(L('Algunas entradas de "entries" no existen:', 'Some "entries" do not exist:'), missing);
|
|
486
581
|
for (const rel of matched) {
|
|
487
582
|
const ext = extOf(rel);
|
|
488
583
|
if (HTML_EXT.has(ext)) queue.push(rel);
|
|
@@ -490,7 +585,11 @@ function selectEntries(ctx, pages) {
|
|
|
490
585
|
else if (JS_EXT.has(ext)) {
|
|
491
586
|
const info = ctx.jsInfo(rel);
|
|
492
587
|
(info.ok && (info.hasModuleSyntax || info.hasImportMeta) ? extraEntries.modules : extraEntries.classic).push(rel);
|
|
493
|
-
} else
|
|
588
|
+
} else
|
|
589
|
+
ctx.warn(
|
|
590
|
+
L(`"${rel}" no es HTML, JS ni CSS; se copiará como recurso`, `"${rel}" is not HTML, JS or CSS; it will be copied as an asset`),
|
|
591
|
+
'entries',
|
|
592
|
+
);
|
|
494
593
|
}
|
|
495
594
|
}
|
|
496
595
|
const full = new Set();
|
|
@@ -549,7 +648,10 @@ function parseImportMap(ctx, page, item) {
|
|
|
549
648
|
const json = JSON.parse(item.inline.code);
|
|
550
649
|
return { json, imports: json.imports ?? {}, scopes: json.scopes, baseDir: page.baseDir };
|
|
551
650
|
} catch (err) {
|
|
552
|
-
ctx.warn(
|
|
651
|
+
ctx.warn(
|
|
652
|
+
L(`el import map no es JSON válido (${err.message}); se ignora`, `the import map is not valid JSON (${err.message}); ignored`),
|
|
653
|
+
page.rel,
|
|
654
|
+
);
|
|
553
655
|
return null;
|
|
554
656
|
}
|
|
555
657
|
}
|
|
@@ -580,16 +682,23 @@ function classicRuns(ctx, page) {
|
|
|
580
682
|
continue;
|
|
581
683
|
}
|
|
582
684
|
const rel = res.rel;
|
|
583
|
-
if (!JS_EXT.has(extOf(rel)))
|
|
685
|
+
if (!JS_EXT.has(extOf(rel)))
|
|
686
|
+
ctx.note(
|
|
687
|
+
L(
|
|
688
|
+
`<script src> apunta a "${rel}" (extensión no habitual); se trata como JavaScript`,
|
|
689
|
+
`<script src> points to "${rel}" (unusual extension); treated as JavaScript`,
|
|
690
|
+
),
|
|
691
|
+
page.rel,
|
|
692
|
+
);
|
|
584
693
|
const info = ctx.jsInfo(rel, false);
|
|
585
694
|
const attrs = s.attrs;
|
|
586
695
|
const reasons = [];
|
|
587
|
-
if (!mergeOn) reasons.push('unión desactivada');
|
|
588
|
-
if (!info.ok) reasons.push('no se pudo analizar');
|
|
589
|
-
if (s.inTemplate) reasons.push('dentro de <template>');
|
|
696
|
+
if (!mergeOn) reasons.push(L('unión desactivada', 'merging disabled'));
|
|
697
|
+
if (!info.ok) reasons.push(L('no se pudo analizar', 'could not be parsed'));
|
|
698
|
+
if (s.inTemplate) reasons.push(L('dentro de <template>', 'inside <template>'));
|
|
590
699
|
if (attrs.has('async')) reasons.push('async');
|
|
591
|
-
if (info.usesCurrentScript) reasons.push('usa document.currentScript');
|
|
592
|
-
if (info.usesDocumentWrite) reasons.push('usa document.write');
|
|
700
|
+
if (info.usesCurrentScript) reasons.push(L('usa document.currentScript', 'uses document.currentScript'));
|
|
701
|
+
if (info.usesDocumentWrite) reasons.push(L('usa document.write', 'uses document.write'));
|
|
593
702
|
const extra = [...attrs.keys()].filter((a) => !MERGE_ATTRS.has(a));
|
|
594
703
|
if (extra.length) reasons.push(`atributos ${extra.join(', ')}`);
|
|
595
704
|
const obf = ctx.shouldObfuscate([rel]);
|
|
@@ -647,10 +756,34 @@ export async function runPipeline(cfg, opts = {}) {
|
|
|
647
756
|
|
|
648
757
|
async function pipeline(ctx, cfg, opts, mode, t0) {
|
|
649
758
|
const log = opts.log ?? (() => {});
|
|
759
|
+
const bt = cfg.browserTargets;
|
|
760
|
+
if (bt?.missing)
|
|
761
|
+
ctx.warn(
|
|
762
|
+
L(
|
|
763
|
+
`hay navegadores objetivo en ${bt.from}, pero falta el paquete "browserslist"; se ignoran (instálalo con: npm install --save-dev browserslist)`,
|
|
764
|
+
`target browsers are set in ${bt.from}, but the "browserslist" package is missing; they are ignored (install it with: npm install --save-dev browserslist)`,
|
|
765
|
+
),
|
|
766
|
+
);
|
|
767
|
+
else if (bt?.targets?.length) {
|
|
768
|
+
log(L(`Navegadores (${bt.from}): ${bt.targets.join(', ')}`, `Browsers (${bt.from}): ${bt.targets.join(', ')}`));
|
|
769
|
+
if (bt.ignored.length)
|
|
770
|
+
ctx.note(
|
|
771
|
+
L(
|
|
772
|
+
`navegadores sin motor equivalente en esbuild (se usan los demás): ${bt.ignored.join(', ')}`,
|
|
773
|
+
`browsers without an equivalent esbuild engine (the others are used): ${bt.ignored.join(', ')}`,
|
|
774
|
+
),
|
|
775
|
+
);
|
|
776
|
+
} else if (bt && !bt.targets?.length)
|
|
777
|
+
ctx.warn(
|
|
778
|
+
L(
|
|
779
|
+
`la consulta de navegadores de ${bt.from} no da ningún navegador conocido; se ignora`,
|
|
780
|
+
`the browsers query in ${bt.from} matches no known browser; ignored`,
|
|
781
|
+
),
|
|
782
|
+
);
|
|
650
783
|
// Tiempo de cada fase (perf.phases): para saber dónde se va el tiempo en proyectos reales.
|
|
651
784
|
const phases = [];
|
|
652
785
|
let phaseStart = Date.now();
|
|
653
|
-
let phaseName = '
|
|
786
|
+
let phaseName = 'setup';
|
|
654
787
|
const mark = (next) => {
|
|
655
788
|
const now = Date.now();
|
|
656
789
|
phases.push([phaseName, now - phaseStart]);
|
|
@@ -659,21 +792,30 @@ async function pipeline(ctx, cfg, opts, mode, t0) {
|
|
|
659
792
|
};
|
|
660
793
|
if (ctx.obfuscatorOptions) await ensureObfuscator(cfg.root);
|
|
661
794
|
if (cfg.js.minifier === 'terser' && cfg.js.minify) await ensureTerser(cfg.root);
|
|
795
|
+
const images = mode === 'build' ? imageOptions(cfg.images) : null;
|
|
796
|
+
const sharp = images ? await ensureSharp(cfg.root) : null;
|
|
662
797
|
|
|
663
|
-
mark('
|
|
798
|
+
mark('pages');
|
|
664
799
|
// 1. Páginas y entradas
|
|
800
|
+
await runHook(cfg.plugins, 'buildStart', { config: cfg, mode });
|
|
801
|
+
await applyTransforms(ctx);
|
|
665
802
|
await transpileTypeScript(ctx);
|
|
666
803
|
const pages = loadPages(ctx);
|
|
667
804
|
markDependencies(ctx, pages);
|
|
668
805
|
const extra = selectEntries(ctx, pages);
|
|
669
806
|
const fullPages = [...pages.values()].filter((p) => p.full);
|
|
670
807
|
const partialPages = [...pages.values()].filter((p) => !p.full);
|
|
671
|
-
log(
|
|
808
|
+
log(
|
|
809
|
+
L(
|
|
810
|
+
`Páginas: ${fullPages.length} completas, ${partialPages.length} en modo conservador`,
|
|
811
|
+
`Pages: ${fullPages.length} complete, ${partialPages.length} in conservative mode`,
|
|
812
|
+
),
|
|
813
|
+
);
|
|
672
814
|
const htmlBlob = [];
|
|
673
815
|
for (const p of fullPages) collectRefs(ctx, p);
|
|
674
816
|
for (const p of partialPages) collectRefs(ctx, p);
|
|
675
817
|
|
|
676
|
-
mark('
|
|
818
|
+
mark('modules');
|
|
677
819
|
// 2. Módulos ES agrupados por import map
|
|
678
820
|
const groups = new Map();
|
|
679
821
|
const groupFor = (page, importMap) => {
|
|
@@ -686,7 +828,7 @@ async function pipeline(ctx, cfg, opts, mode, t0) {
|
|
|
686
828
|
entries: new Set(),
|
|
687
829
|
virtual: new Map(),
|
|
688
830
|
pages: new Set(),
|
|
689
|
-
forcePerFile: importMap?.scopes ? 'el import map usa "scopes"' : null,
|
|
831
|
+
forcePerFile: importMap?.scopes ? L('el import map usa "scopes"', 'the import map uses "scopes"') : null,
|
|
690
832
|
vendorSplit: !!ctx.obfuscatorOptions && !cfg.dependencies.obfuscate,
|
|
691
833
|
});
|
|
692
834
|
return groups.get(key);
|
|
@@ -695,7 +837,11 @@ async function pipeline(ctx, cfg, opts, mode, t0) {
|
|
|
695
837
|
const preloadLinks = [];
|
|
696
838
|
for (const page of fullPages) {
|
|
697
839
|
const map = page.model.importMaps[0] ? parseImportMap(ctx, page, page.model.importMaps[0]) : null;
|
|
698
|
-
if (page.model.importMaps.length > 1)
|
|
840
|
+
if (page.model.importMaps.length > 1)
|
|
841
|
+
ctx.warn(
|
|
842
|
+
L('tiene varios import maps; solo se usa el primero para empaquetar', 'has several import maps; only the first one is used for bundling'),
|
|
843
|
+
page.rel,
|
|
844
|
+
);
|
|
699
845
|
let vIndex = 0;
|
|
700
846
|
for (const s of page.model.scripts) {
|
|
701
847
|
if (s.kind !== 'module' || s.inTemplate) continue;
|
|
@@ -715,7 +861,7 @@ async function pipeline(ctx, cfg, opts, mode, t0) {
|
|
|
715
861
|
const outDir = page.baseDir ? `${page.baseDir}/` : '';
|
|
716
862
|
g.virtual.set(id, {
|
|
717
863
|
code: s.inline.code,
|
|
718
|
-
label: `${page.rel} (módulo inline ${vIndex})`,
|
|
864
|
+
label: L(`${page.rel} (módulo inline ${vIndex})`, `${page.rel} (inline module ${vIndex})`),
|
|
719
865
|
baseDir: page.baseDir,
|
|
720
866
|
out: `${outDir}__vb_inline_${shortHash(id)}`,
|
|
721
867
|
page,
|
|
@@ -743,8 +889,14 @@ async function pipeline(ctx, cfg, opts, mode, t0) {
|
|
|
743
889
|
if (!g.entries.size && !g.virtual.size) continue;
|
|
744
890
|
const res = await buildModuleGroup(ctx, { ...g, hash: g.hash });
|
|
745
891
|
if (res.mode === 'per-file') {
|
|
746
|
-
ctx.note(
|
|
747
|
-
|
|
892
|
+
ctx.note(
|
|
893
|
+
L(
|
|
894
|
+
`módulos sin empaquetar (cada archivo minificado en su sitio): ${res.reason}`,
|
|
895
|
+
`modules not bundled (each file minified in place): ${res.reason}`,
|
|
896
|
+
),
|
|
897
|
+
[...g.pages].join(', ') || 'entries',
|
|
898
|
+
);
|
|
899
|
+
for (const rel of res.inputs) ctx.requireStandalone(rel, L('módulo (modo sin empaquetar)', 'module (unbundled mode)'), { module: true });
|
|
748
900
|
for (const rel of g.entries) entryOut.set(`${g.key}\0${rel}`, jsOutPath(rel));
|
|
749
901
|
for (const [id, v] of g.virtual) {
|
|
750
902
|
const info = analyzeJs(v.code, { module: true });
|
|
@@ -757,7 +909,12 @@ async function pipeline(ctx, cfg, opts, mode, t0) {
|
|
|
757
909
|
}
|
|
758
910
|
continue;
|
|
759
911
|
}
|
|
760
|
-
log(
|
|
912
|
+
log(
|
|
913
|
+
L(
|
|
914
|
+
`Módulos: ${g.entries.size} entradas, ${res.inputs.length} archivos, target ${res.target}`,
|
|
915
|
+
`Modules: ${g.entries.size} entries, ${res.inputs.length} files, target ${res.target}`,
|
|
916
|
+
),
|
|
917
|
+
);
|
|
761
918
|
// Ofuscar/comprimir cada salida en paralelo; registrar después en el orden original (salida determinista).
|
|
762
919
|
const finals = await mapLimit(res.outputs, 64, async (o) => {
|
|
763
920
|
const obfRels = o.virtualId ? [g.virtual.get(o.virtualId).page.rel] : o.isChunk ? o.inputs : [o.entry];
|
|
@@ -775,18 +932,24 @@ async function pipeline(ctx, cfg, opts, mode, t0) {
|
|
|
775
932
|
if (o.virtualId) {
|
|
776
933
|
const v = g.virtual.get(o.virtualId);
|
|
777
934
|
inlineModuleCode.set(o.virtualId, code.replace(/\n$/, ''));
|
|
778
|
-
for (const rel of o.inputs) ctx.cover(rel, 'empaquetado', `${v.page.rel} (módulo inline)`);
|
|
935
|
+
for (const rel of o.inputs) ctx.cover(rel, L('empaquetado', 'bundled'), L(`${v.page.rel} (módulo inline)`, `${v.page.rel} (inline module)`));
|
|
779
936
|
continue;
|
|
780
937
|
}
|
|
781
938
|
const action = o.isVendor ? 'vendor' : o.isChunk ? 'chunk' : 'bundle';
|
|
782
939
|
const outRel = ctx.emitCode(o.outRel, code, map, { type: 'js', module: true, action, sources, before: o.before || sumSizes(ctx, sources) });
|
|
783
940
|
if (o.entry) entryOut.set(`${g.key}\0${o.entry}`, outRel);
|
|
784
|
-
for (const rel of o.inputs)
|
|
941
|
+
for (const rel of o.inputs)
|
|
942
|
+
ctx.cover(
|
|
943
|
+
rel,
|
|
944
|
+
o.isVendor ? L('empaquetado (dependencia, sin ofuscar)', 'bundled (dependency, not obfuscated)') : L('empaquetado', 'bundled'),
|
|
945
|
+
outRel,
|
|
946
|
+
);
|
|
785
947
|
}
|
|
786
948
|
for (const spec of res.vendor) if (!ctx.vendorPackages.includes(spec)) ctx.vendorPackages.push(spec);
|
|
787
949
|
// esbuild puede incrustar un módulo entero (p. ej. constantes) sin que figure en ninguna salida.
|
|
788
950
|
for (const rel of res.consumed) {
|
|
789
|
-
if (!ctx.coverage.get(rel)?.some((s) => s.startsWith('empaquetado'
|
|
951
|
+
if (!ctx.coverage.get(rel)?.some((s) => s.startsWith(L('empaquetado', 'bundled'))))
|
|
952
|
+
ctx.cover(rel, L('empaquetado (incrustado por completo en el paquete)', 'bundled (fully inlined into the bundle)'));
|
|
790
953
|
}
|
|
791
954
|
}
|
|
792
955
|
|
|
@@ -810,15 +973,15 @@ async function pipeline(ctx, cfg, opts, mode, t0) {
|
|
|
810
973
|
const hashed = cfg.hash && !extra.css.includes(rel);
|
|
811
974
|
const r = cssBuilt[ci];
|
|
812
975
|
if (r.error) {
|
|
813
|
-
ctx.warn(`no se pudo procesar el CSS (${r.error}); se copia tal cual`, rel);
|
|
976
|
+
ctx.warn(L(`no se pudo procesar el CSS (${r.error}); se copia tal cual`, `could not process the CSS (${r.error}); copied as is`), rel);
|
|
814
977
|
cssOut.set(rel, ctx.emitCopy(rel, rel, 'copy-fallback'));
|
|
815
|
-
ctx.cover(rel, 'copiado sin cambios (error al procesar)', rel);
|
|
978
|
+
ctx.cover(rel, L('copiado sin cambios (error al procesar)', 'copied unchanged (processing error)'), rel);
|
|
816
979
|
continue;
|
|
817
980
|
}
|
|
818
981
|
const outRel = hashed ? ctx.hashedPath(rel, r.code, '.css') : rel;
|
|
819
982
|
ctx.emitCode(outRel, r.code, r.map, { type: 'css', action: 'bundle', sources: r.inputs, before: sumSizes(ctx, r.inputs) });
|
|
820
983
|
cssOut.set(rel, outRel);
|
|
821
|
-
for (const i of r.inputs) ctx.cover(i, i === rel ? 'procesado' : 'empaquetado', outRel);
|
|
984
|
+
for (const i of r.inputs) ctx.cover(i, i === rel ? L('procesado', 'processed') : L('empaquetado', 'bundled'), outRel);
|
|
822
985
|
}
|
|
823
986
|
|
|
824
987
|
mark('white-space');
|
|
@@ -831,11 +994,14 @@ async function pipeline(ctx, cfg, opts, mode, t0) {
|
|
|
831
994
|
collapse = !pre;
|
|
832
995
|
if (pre)
|
|
833
996
|
ctx.note(
|
|
834
|
-
|
|
997
|
+
L(
|
|
998
|
+
'se usa white-space: pre/pre-wrap/pre-line en el CSS; el HTML no colapsa espacios para no alterar ningún texto (html.collapseWhitespace: true lo fuerza)',
|
|
999
|
+
'white-space: pre/pre-wrap/pre-line is used in the CSS; HTML whitespace is not collapsed so no text changes (html.collapseWhitespace: true forces it)',
|
|
1000
|
+
),
|
|
835
1001
|
);
|
|
836
1002
|
}
|
|
837
1003
|
|
|
838
|
-
mark('scripts
|
|
1004
|
+
mark('classic scripts');
|
|
839
1005
|
// 5. Scripts clásicos por página
|
|
840
1006
|
// Primero se planifican todas las páginas, luego se minifica/ofusca cada grupo distinto en paralelo
|
|
841
1007
|
// y por último se registra todo en el orden original.
|
|
@@ -876,7 +1042,7 @@ async function pipeline(ctx, cfg, opts, mode, t0) {
|
|
|
876
1042
|
}
|
|
877
1043
|
}
|
|
878
1044
|
|
|
879
|
-
mark('
|
|
1045
|
+
mark('html edits');
|
|
880
1046
|
// 6. Edición de cada página: módulos, CSS, inline, preload
|
|
881
1047
|
for (const page of fullPages) {
|
|
882
1048
|
for (const m of moduleRefs.filter((x) => x.page === page)) {
|
|
@@ -902,11 +1068,11 @@ async function pipeline(ctx, cfg, opts, mode, t0) {
|
|
|
902
1068
|
}
|
|
903
1069
|
for (const page of pages.values()) await processInline(ctx, page, collapse);
|
|
904
1070
|
|
|
905
|
-
mark('workers
|
|
1071
|
+
mark('workers and standalone');
|
|
906
1072
|
// 7. Workers, archivos sueltos y rutas mencionadas (hasta que no aparezca nada nuevo)
|
|
907
1073
|
await drainQueues(ctx, cssOut);
|
|
908
1074
|
|
|
909
|
-
mark('
|
|
1075
|
+
mark('unreferenced');
|
|
910
1076
|
// 8. Archivos no referenciados
|
|
911
1077
|
const unref = [];
|
|
912
1078
|
for (const [rel, f] of ctx.files) {
|
|
@@ -916,15 +1082,16 @@ async function pipeline(ctx, cfg, opts, mode, t0) {
|
|
|
916
1082
|
for (const rel of unref) {
|
|
917
1083
|
if (ctx.coverage.has(rel)) continue;
|
|
918
1084
|
const f = ctx.files.get(rel);
|
|
919
|
-
if (cfg.unreferenced === 'process' || f.forced)
|
|
1085
|
+
if (cfg.unreferenced === 'process' || f.forced)
|
|
1086
|
+
ctx.requireStandalone(rel, L('no referenciado (se procesa suelto)', 'unreferenced (processed on its own)'));
|
|
920
1087
|
else if (cfg.unreferenced === 'copy') {
|
|
921
1088
|
ctx.emitCopy(rel, rel);
|
|
922
|
-
ctx.cover(rel, 'no referenciado: copiado', rel);
|
|
923
|
-
} else ctx.cover(rel, 'no referenciado: omitido (unreferenced: skip)');
|
|
1089
|
+
ctx.cover(rel, L('no referenciado: copiado', 'unreferenced: copied'), rel);
|
|
1090
|
+
} else ctx.cover(rel, L('no referenciado: omitido (unreferenced: skip)', 'unreferenced: skipped (unreferenced: skip)'));
|
|
924
1091
|
}
|
|
925
1092
|
await drainQueues(ctx, cssOut);
|
|
926
1093
|
|
|
927
|
-
mark('html
|
|
1094
|
+
mark('final html');
|
|
928
1095
|
// 9. Preloads y HTML final
|
|
929
1096
|
for (const { page, l } of preloadLinks) {
|
|
930
1097
|
const res = ctx.resolve(l.href.value, page.baseDir);
|
|
@@ -935,7 +1102,13 @@ async function pipeline(ctx, cfg, opts, mode, t0) {
|
|
|
935
1102
|
(ctx.hasOwnOutput(res.rel) || ctx.assets.has(res.rel) ? res.rel : null);
|
|
936
1103
|
if (target == null) {
|
|
937
1104
|
page.edits.push({ start: l.start, end: l.end, text: '' });
|
|
938
|
-
ctx.note(
|
|
1105
|
+
ctx.note(
|
|
1106
|
+
L(
|
|
1107
|
+
`se quita <link rel="${l.rels.join(' ')}" href="${l.href.value}">: ese archivo ahora va dentro de un paquete`,
|
|
1108
|
+
`removed <link rel="${l.rels.join(' ')}" href="${l.href.value}">: that file is now inside a bundle`,
|
|
1109
|
+
),
|
|
1110
|
+
page.rel,
|
|
1111
|
+
);
|
|
939
1112
|
} else if (target !== res.rel) {
|
|
940
1113
|
page.edits.push({ start: l.href.start, end: l.href.end, text: attrText('href', urlFor(ctx, page, l.href.value, target)) });
|
|
941
1114
|
}
|
|
@@ -944,30 +1117,35 @@ async function pipeline(ctx, cfg, opts, mode, t0) {
|
|
|
944
1117
|
for (const page of pages.values()) {
|
|
945
1118
|
const referenced = page.full || ctx.pageRefs.has(page.rel) || ctx.mentions.has(page.rel) || ctx.files.get(page.rel).forced;
|
|
946
1119
|
if (!referenced && cfg.unreferenced === 'skip') {
|
|
947
|
-
ctx.cover(page.rel, 'no referenciado: omitido (unreferenced: skip)');
|
|
1120
|
+
ctx.cover(page.rel, L('no referenciado: omitido (unreferenced: skip)', 'unreferenced: skipped (unreferenced: skip)'));
|
|
948
1121
|
continue;
|
|
949
1122
|
}
|
|
950
1123
|
if (!referenced && cfg.unreferenced === 'copy') {
|
|
951
1124
|
ctx.emitCopy(page.rel, page.rel);
|
|
952
|
-
ctx.cover(page.rel, 'no referenciado: copiado', page.rel);
|
|
1125
|
+
ctx.cover(page.rel, L('no referenciado: copiado', 'unreferenced: copied'), page.rel);
|
|
953
1126
|
continue;
|
|
954
1127
|
}
|
|
955
1128
|
await finalizePage(ctx, page, collapse);
|
|
956
1129
|
}
|
|
957
1130
|
|
|
958
|
-
mark('
|
|
1131
|
+
mark('assets');
|
|
959
1132
|
// 10. Recursos: todo lo demás se copia (sin perder nada)
|
|
1133
|
+
const imageJobs = [];
|
|
960
1134
|
for (const [rel, f] of ctx.files) {
|
|
961
1135
|
if (ctx.hasOwnOutput(rel)) {
|
|
962
|
-
if (!ctx.coverage.has(rel)) ctx.cover(rel, 'procesado', rel);
|
|
1136
|
+
if (!ctx.coverage.has(rel)) ctx.cover(rel, L('procesado', 'processed'), rel);
|
|
963
1137
|
continue;
|
|
964
1138
|
}
|
|
965
1139
|
const cov = ctx.coverage.get(rel);
|
|
966
1140
|
// Empaquetar solo absorbe JS y CSS; un JSON importado se publica también por si se pide con fetch().
|
|
967
|
-
if (
|
|
1141
|
+
if (
|
|
1142
|
+
cov &&
|
|
1143
|
+
(JS_EXT.has(f.ext) || CSS_EXT.has(f.ext) || HTML_EXT.has(f.ext) || cov.some((s) => s.startsWith(L('no referenciado', 'unreferenced'))))
|
|
1144
|
+
)
|
|
1145
|
+
continue;
|
|
968
1146
|
const referenced = ctx.assets.has(rel) || ctx.mentions.has(rel);
|
|
969
1147
|
if (cfg.unreferenced === 'skip' && !referenced && !f.forced) {
|
|
970
|
-
ctx.cover(rel, 'no referenciado: omitido (unreferenced: skip)');
|
|
1148
|
+
ctx.cover(rel, L('no referenciado: omitido (unreferenced: skip)', 'unreferenced: skipped (unreferenced: skip)'));
|
|
971
1149
|
continue;
|
|
972
1150
|
}
|
|
973
1151
|
if ((f.ext === '.json' || f.ext === '.webmanifest') && cfg.json.minify) {
|
|
@@ -975,34 +1153,72 @@ async function pipeline(ctx, cfg, opts, mode, t0) {
|
|
|
975
1153
|
const min = raw == null ? null : minifyJsonText(stripBom(raw).text);
|
|
976
1154
|
if (min != null && Buffer.byteLength(min) < f.size) {
|
|
977
1155
|
ctx.emit(rel, min, { type: 'json', action: 'minify', sources: [rel], before: f.size });
|
|
978
|
-
ctx.cover(rel, 'JSON minificado', rel);
|
|
1156
|
+
ctx.cover(rel, L('JSON minificado', 'JSON minified'), rel);
|
|
979
1157
|
continue;
|
|
980
1158
|
}
|
|
981
1159
|
}
|
|
1160
|
+
if (sharp && IMAGE_EXT.has(f.ext)) {
|
|
1161
|
+
imageJobs.push({ rel, f });
|
|
1162
|
+
continue;
|
|
1163
|
+
}
|
|
982
1164
|
ctx.emitCopy(rel, rel);
|
|
983
|
-
ctx.cover(rel, 'copiado', rel);
|
|
984
|
-
}
|
|
1165
|
+
ctx.cover(rel, L('copiado', 'copied'), rel);
|
|
1166
|
+
}
|
|
1167
|
+
// Imágenes: en paralelo (sharp usa sus propios hilos) y con caché por contenido; se registran en orden.
|
|
1168
|
+
const optimized = await mapLimit(imageJobs, 8, async ({ rel, f }) => {
|
|
1169
|
+
const buf = fs.readFileSync(f.abs);
|
|
1170
|
+
const r = await ctx.heavy.memo(`image:${f.ext}:q${images.quality}:sharp${sharp.versions?.sharp ?? '?'}`, buf, async () => {
|
|
1171
|
+
const o = await optimizeImage(sharp, buf, f.ext, images);
|
|
1172
|
+
return o.data ? { b64: o.data.toString('base64') } : { skip: o.skip };
|
|
1173
|
+
});
|
|
1174
|
+
return r.b64 ? { data: Buffer.from(r.b64, 'base64') } : r;
|
|
1175
|
+
});
|
|
1176
|
+
imageJobs.forEach(({ rel, f }, i) => {
|
|
1177
|
+
const o = optimized[i];
|
|
1178
|
+
if (o.data) {
|
|
1179
|
+
ctx.emit(rel, o.data, { type: 'asset', action: 'image', sources: [rel], before: f.size });
|
|
1180
|
+
ctx.cover(rel, L('imagen optimizada', 'image optimized'), rel);
|
|
1181
|
+
} else {
|
|
1182
|
+
ctx.emitCopy(rel, rel);
|
|
1183
|
+
ctx.cover(rel, L(`copiado (${o.skip})`, `copied (${o.skip})`), rel);
|
|
1184
|
+
}
|
|
1185
|
+
});
|
|
985
1186
|
|
|
986
|
-
mark('
|
|
1187
|
+
mark('warnings');
|
|
987
1188
|
// 11. Avisos de rutas mencionadas en el código que ya no existirán
|
|
988
1189
|
for (const [rel, from] of ctx.mentions) {
|
|
989
|
-
if (!ctx.outputs.has(rel) && ctx.coverage.get(rel)?.every((s) => s.startsWith('empaquetado'))) {
|
|
990
|
-
ctx.warn(
|
|
1190
|
+
if (!ctx.outputs.has(rel) && ctx.coverage.get(rel)?.every((s) => s.startsWith(L('empaquetado', 'bundled')))) {
|
|
1191
|
+
ctx.warn(
|
|
1192
|
+
L(
|
|
1193
|
+
`"${rel}" se menciona como texto en ${from} pero ya no existe por separado (va dentro de un paquete).`,
|
|
1194
|
+
`"${rel}" is mentioned as text in ${from} but no longer exists on its own (it is inside a bundle).`,
|
|
1195
|
+
),
|
|
1196
|
+
);
|
|
991
1197
|
}
|
|
992
1198
|
}
|
|
993
1199
|
if (cfg.hash) {
|
|
994
1200
|
for (const [rel, from] of ctx.mentions) {
|
|
995
1201
|
if ((cssOut.get(rel) ?? rel) !== rel)
|
|
996
|
-
ctx.note(
|
|
1202
|
+
ctx.note(
|
|
1203
|
+
L(
|
|
1204
|
+
`"${rel}" se menciona como texto en ${from}: además de la versión con hash se publica en su ruta original.`,
|
|
1205
|
+
`"${rel}" is mentioned as text in ${from}: besides the hashed version, it is published at its original path.`,
|
|
1206
|
+
),
|
|
1207
|
+
);
|
|
997
1208
|
}
|
|
998
1209
|
}
|
|
999
1210
|
|
|
1000
|
-
mark('
|
|
1211
|
+
mark('precompression');
|
|
1001
1212
|
// 12. Precompresión .gz / .br para servidores que sirven archivos comprimidos (nginx gzip_static, etc.)
|
|
1002
1213
|
if (cfg.compress && mode === 'build') await precompress(ctx, cfg.compress === true ? ['gzip', 'brotli'] : [cfg.compress]);
|
|
1003
1214
|
|
|
1004
|
-
mark('
|
|
1215
|
+
mark('size report');
|
|
1005
1216
|
const report = await buildReport(ctx.outputs, ctx.heavy);
|
|
1217
|
+
const budgets = checkBudgets(cfg.budgets, report);
|
|
1218
|
+
for (const p of budgets.unmatched)
|
|
1219
|
+
ctx.warn(L(`el presupuesto "${p}" no coincide con ningún archivo de salida`, `the budget "${p}" does not match any output file`));
|
|
1220
|
+
// En analyze los excesos son avisos; en build detienen la compilación antes de escribir nada.
|
|
1221
|
+
if (mode === 'analyze') for (const v of budgets.violations) ctx.warn(v);
|
|
1006
1222
|
const result = {
|
|
1007
1223
|
version: VERSION,
|
|
1008
1224
|
root: cfg.root,
|
|
@@ -1023,6 +1239,7 @@ async function pipeline(ctx, cfg, opts, mode, t0) {
|
|
|
1023
1239
|
vendor: ctx.vendorPackages,
|
|
1024
1240
|
obfuscated: !!ctx.obfuscatorOptions,
|
|
1025
1241
|
report,
|
|
1242
|
+
budgets,
|
|
1026
1243
|
perf: Object.assign(ctx.heavy.stats, { phases }),
|
|
1027
1244
|
verify: null,
|
|
1028
1245
|
durationMs: 0,
|
|
@@ -1033,8 +1250,16 @@ async function pipeline(ctx, cfg, opts, mode, t0) {
|
|
|
1033
1250
|
return result;
|
|
1034
1251
|
}
|
|
1035
1252
|
|
|
1036
|
-
mark('
|
|
1253
|
+
mark('write and verify');
|
|
1037
1254
|
// 12. Escritura, verificación y publicación
|
|
1255
|
+
if (budgets.violations.length)
|
|
1256
|
+
throw new BuildError(
|
|
1257
|
+
L(
|
|
1258
|
+
`Se superaron ${budgets.violations.length} presupuesto(s) de tamaño; no se tocó ${cfg.outDir}.`,
|
|
1259
|
+
`${budgets.violations.length} size budget(s) exceeded; ${cfg.outDir} was not touched.`,
|
|
1260
|
+
),
|
|
1261
|
+
budgets.violations,
|
|
1262
|
+
);
|
|
1038
1263
|
let staging;
|
|
1039
1264
|
try {
|
|
1040
1265
|
staging = await writeStaging(cfg, ctx.outputs);
|
|
@@ -1042,7 +1267,13 @@ async function pipeline(ctx, cfg, opts, mode, t0) {
|
|
|
1042
1267
|
result.verify = await verifyOutput(staging, ctx.outputs, ctx);
|
|
1043
1268
|
if (result.verify.errors.length) {
|
|
1044
1269
|
if (!opts.keepFailed) await discardStaging(staging);
|
|
1045
|
-
throw new BuildError(
|
|
1270
|
+
throw new BuildError(
|
|
1271
|
+
L(
|
|
1272
|
+
`La verificación encontró ${result.verify.errors.length} problema(s); no se tocó ${cfg.outDir}.`,
|
|
1273
|
+
`Verification found ${result.verify.errors.length} problem(s); ${cfg.outDir} was not touched.`,
|
|
1274
|
+
),
|
|
1275
|
+
result.verify.errors,
|
|
1276
|
+
);
|
|
1046
1277
|
}
|
|
1047
1278
|
}
|
|
1048
1279
|
await writeManifest(staging, {
|
|
@@ -1052,6 +1283,7 @@ async function pipeline(ctx, cfg, opts, mode, t0) {
|
|
|
1052
1283
|
files: [...ctx.outputs.keys()].sort(),
|
|
1053
1284
|
});
|
|
1054
1285
|
await swapIntoPlace(cfg, staging);
|
|
1286
|
+
await runHook(cfg.plugins, 'buildEnd', { result, outDir: cfg.outDir });
|
|
1055
1287
|
mark('fin');
|
|
1056
1288
|
} catch (err) {
|
|
1057
1289
|
if (staging && !opts.keepFailed) await discardStaging(staging);
|
|
@@ -1095,13 +1327,13 @@ function markModuleImport(ctx, spec, baseDir, from, importMap) {
|
|
|
1095
1327
|
.map((k) => importMap.imports[k] + spec.slice(k.length))[0];
|
|
1096
1328
|
if (target) {
|
|
1097
1329
|
const r = ctx.resolve(target, importMap.baseDir);
|
|
1098
|
-
if (r.status === 'ok') ctx.requireStandalone(r.rel, `importado desde ${from}`, { module: true });
|
|
1330
|
+
if (r.status === 'ok') ctx.requireStandalone(r.rel, L(`importado desde ${from}`, `imported from ${from}`), { module: true });
|
|
1099
1331
|
}
|
|
1100
1332
|
}
|
|
1101
1333
|
return;
|
|
1102
1334
|
}
|
|
1103
1335
|
const r = ctx.resolve(spec, baseDir);
|
|
1104
|
-
if (r.status === 'ok') ctx.requireStandalone(r.rel, `importado desde ${from}`, { module: true });
|
|
1336
|
+
if (r.status === 'ok') ctx.requireStandalone(r.rel, L(`importado desde ${from}`, `imported from ${from}`), { module: true });
|
|
1105
1337
|
else ctx.reportBroken(from, spec, r);
|
|
1106
1338
|
}
|
|
1107
1339
|
|
|
@@ -1132,9 +1364,12 @@ function commitClassicRun(ctx, run, page, comp) {
|
|
|
1132
1364
|
const items = run.items;
|
|
1133
1365
|
const first = items[0];
|
|
1134
1366
|
if (comp.copy) {
|
|
1135
|
-
ctx.warn(
|
|
1367
|
+
ctx.warn(
|
|
1368
|
+
L(`no se pudo analizar (${first.info.error}); se copia sin cambios`, `could not be parsed (${first.info.error}); copied unchanged`),
|
|
1369
|
+
first.rel,
|
|
1370
|
+
);
|
|
1136
1371
|
const outRel = ctx.emitCopy(first.rel, first.rel, 'copy-fallback');
|
|
1137
|
-
ctx.cover(first.rel, 'copiado sin cambios (no se pudo analizar)', outRel);
|
|
1372
|
+
ctx.cover(first.rel, L('copiado sin cambios (no se pudo analizar)', 'copied unchanged (could not be parsed)'), outRel);
|
|
1138
1373
|
return { merged: false, outs: [{ outRel, changed: false }] };
|
|
1139
1374
|
}
|
|
1140
1375
|
{
|
|
@@ -1150,20 +1385,25 @@ function commitClassicRun(ctx, run, page, comp) {
|
|
|
1150
1385
|
items.map((c) => c.rel),
|
|
1151
1386
|
),
|
|
1152
1387
|
});
|
|
1153
|
-
for (const c of items) ctx.cover(c.rel, 'unido', outRel);
|
|
1388
|
+
for (const c of items) ctx.cover(c.rel, L('unido', 'merged'), outRel);
|
|
1154
1389
|
return { merged: true, outRel };
|
|
1155
1390
|
}
|
|
1156
|
-
if (comp.mergeError)
|
|
1391
|
+
if (comp.mergeError)
|
|
1392
|
+
ctx.warn(
|
|
1393
|
+
L(`no se pudieron unir (${comp.mergeError}); se procesan por separado`, `could not be merged (${comp.mergeError}); processed separately`),
|
|
1394
|
+
page.rel,
|
|
1395
|
+
);
|
|
1157
1396
|
}
|
|
1158
1397
|
// Un solo archivo, o una unión fallida: cada uno se procesa en su sitio.
|
|
1159
1398
|
const outs = [];
|
|
1160
1399
|
for (const [i, c] of items.entries()) {
|
|
1161
1400
|
const r = comp.separate[i];
|
|
1162
1401
|
for (const n of r.notes) ctx.note(n, c.rel);
|
|
1163
|
-
if (c.reasons.length && c.reasons[0] !== 'unión desactivada'
|
|
1402
|
+
if (c.reasons.length && c.reasons[0] !== L('unión desactivada', 'merging disabled'))
|
|
1403
|
+
ctx.note(L(`no se une con otros scripts: ${c.reasons.join(', ')}`, `not merged with other scripts: ${c.reasons.join(', ')}`), c.rel);
|
|
1164
1404
|
const outRel = ctx.cfg.hash ? ctx.hashedPath(c.rel, r.code, '.js') : jsOutPath(c.rel);
|
|
1165
1405
|
ctx.emitCode(outRel, r.code, r.map, { type: 'js', action: 'minify', sources: [c.rel], before: ctx.files.get(c.rel).size });
|
|
1166
|
-
ctx.cover(c.rel, 'procesado', outRel);
|
|
1406
|
+
ctx.cover(c.rel, L('procesado', 'processed'), outRel);
|
|
1167
1407
|
outs.push({ outRel, changed: true });
|
|
1168
1408
|
}
|
|
1169
1409
|
return { merged: false, outs };
|
|
@@ -1176,7 +1416,7 @@ async function processInline(ctx, page) {
|
|
|
1176
1416
|
if (s.src) {
|
|
1177
1417
|
if (!page.full) {
|
|
1178
1418
|
const res = ctx.resolve(s.src.value, page.baseDir);
|
|
1179
|
-
if (res.status === 'ok') ctx.requireStandalone(res.rel, `script en ${page.rel}`, { module: s.kind === 'module' });
|
|
1419
|
+
if (res.status === 'ok') ctx.requireStandalone(res.rel, L(`script en ${page.rel}`, `script in ${page.rel}`), { module: s.kind === 'module' });
|
|
1180
1420
|
else if (res.status !== 'skip') ctx.reportBroken(page.rel, s.src.value, res);
|
|
1181
1421
|
}
|
|
1182
1422
|
continue;
|
|
@@ -1185,7 +1425,10 @@ async function processInline(ctx, page) {
|
|
|
1185
1425
|
if (s.kind === 'classic') {
|
|
1186
1426
|
const info = analyzeJs(s.inline.code, { module: false });
|
|
1187
1427
|
if (!info.ok) {
|
|
1188
|
-
ctx.note(
|
|
1428
|
+
ctx.note(
|
|
1429
|
+
L(`un <script> inline no es JS estándar (¿plantilla?); se deja igual`, `an inline <script> is not standard JS (a template?); left as is`),
|
|
1430
|
+
page.rel,
|
|
1431
|
+
);
|
|
1189
1432
|
continue;
|
|
1190
1433
|
}
|
|
1191
1434
|
ctx.noteJsInfo(page.rel, info, page.baseDir);
|
|
@@ -1215,7 +1458,7 @@ async function processInline(ctx, page) {
|
|
|
1215
1458
|
for (const l of page.model.links) {
|
|
1216
1459
|
if (l.type !== 'stylesheet') continue;
|
|
1217
1460
|
const res = ctx.resolve(l.href.value, page.baseDir);
|
|
1218
|
-
if (res.status === 'ok') ctx.requireStandalone(res.rel, `hoja de estilos en ${page.rel}`);
|
|
1461
|
+
if (res.status === 'ok') ctx.requireStandalone(res.rel, L(`hoja de estilos en ${page.rel}`, `stylesheet in ${page.rel}`));
|
|
1219
1462
|
else if (res.status !== 'skip') ctx.reportBroken(page.rel, l.href.value, res);
|
|
1220
1463
|
}
|
|
1221
1464
|
}
|
|
@@ -1227,7 +1470,13 @@ async function processInline(ctx, page) {
|
|
|
1227
1470
|
outRel: page.baseDir ? `${page.baseDir}/__page.html` : '__page.html',
|
|
1228
1471
|
});
|
|
1229
1472
|
if (r.error) {
|
|
1230
|
-
ctx.note(
|
|
1473
|
+
ctx.note(
|
|
1474
|
+
L(
|
|
1475
|
+
`un <style> no se pudo minificar (${r.error.split('\n')[0]}); se deja igual`,
|
|
1476
|
+
`a <style> could not be minified (${r.error.split('\n')[0]}); left as is`,
|
|
1477
|
+
),
|
|
1478
|
+
page.rel,
|
|
1479
|
+
);
|
|
1231
1480
|
continue;
|
|
1232
1481
|
}
|
|
1233
1482
|
if (r.code !== st.code) page.edits.push({ start: st.start, end: st.end, text: escapeInlineStyle(r.code) });
|
|
@@ -1264,7 +1513,7 @@ async function finalizePage(ctx, page, collapse) {
|
|
|
1264
1513
|
baseDir: page.baseDir,
|
|
1265
1514
|
baseExternal: page.baseExternal,
|
|
1266
1515
|
});
|
|
1267
|
-
ctx.cover(page.rel, page.full ? 'procesado' : 'procesado (modo conservador)', page.rel);
|
|
1516
|
+
ctx.cover(page.rel, page.full ? L('procesado', 'processed') : L('procesado (modo conservador)', 'processed (conservative mode)'), page.rel);
|
|
1268
1517
|
}
|
|
1269
1518
|
|
|
1270
1519
|
/** Procesa workers y archivos que deben existir sueltos en su ruta original, hasta agotar la cola. */
|
|
@@ -1275,7 +1524,7 @@ async function drainQueues(ctx, cssOut) {
|
|
|
1275
1524
|
for (const [rel] of ctx.mentions) {
|
|
1276
1525
|
const ext = extOf(rel);
|
|
1277
1526
|
if ((JS_EXT.has(ext) || CSS_EXT.has(ext)) && !ctx.hasOwnOutput(rel) && !ctx.standalone.has(rel))
|
|
1278
|
-
ctx.requireStandalone(rel, `mencionado como texto en ${ctx.mentions.get(rel)}`);
|
|
1527
|
+
ctx.requireStandalone(rel, L(`mencionado como texto en ${ctx.mentions.get(rel)}`, `mentioned as text in ${ctx.mentions.get(rel)}`));
|
|
1279
1528
|
else if (!JS_EXT.has(ext) && !CSS_EXT.has(ext) && !HTML_EXT.has(ext)) ctx.registerAsset(rel, ctx.mentions.get(rel));
|
|
1280
1529
|
}
|
|
1281
1530
|
for (const [rel, w] of ctx.workers) {
|
|
@@ -1286,7 +1535,13 @@ async function drainQueues(ctx, cssOut) {
|
|
|
1286
1535
|
if (w.module) {
|
|
1287
1536
|
const r = await buildModuleWorker(ctx, rel);
|
|
1288
1537
|
if (r.error) {
|
|
1289
|
-
ctx.warn(
|
|
1538
|
+
ctx.warn(
|
|
1539
|
+
L(
|
|
1540
|
+
`no se pudo empaquetar el worker (${r.error.split('\n')[0]}); se procesa suelto`,
|
|
1541
|
+
`could not bundle the worker (${r.error.split('\n')[0]}); processed on its own`,
|
|
1542
|
+
),
|
|
1543
|
+
rel,
|
|
1544
|
+
);
|
|
1290
1545
|
ctx.requireStandalone(rel, 'worker (respaldo)', { module: true });
|
|
1291
1546
|
continue;
|
|
1292
1547
|
}
|
|
@@ -1304,8 +1559,9 @@ async function drainQueues(ctx, cssOut) {
|
|
|
1304
1559
|
sources,
|
|
1305
1560
|
before: sumSizes(ctx, sources),
|
|
1306
1561
|
});
|
|
1307
|
-
for (const s of sources)
|
|
1308
|
-
|
|
1562
|
+
for (const s of sources)
|
|
1563
|
+
ctx.cover(s, s === rel ? L('procesado (worker)', 'processed (worker)') : L('empaquetado', 'bundled'), jsOutPath(rel));
|
|
1564
|
+
} else ctx.requireStandalone(rel, L(`worker clásico (${w.kind})`, `classic worker (${w.kind})`), { classic: true });
|
|
1309
1565
|
}
|
|
1310
1566
|
// Análisis en orden, trabajo pesado en paralelo, registro otra vez en orden.
|
|
1311
1567
|
const jobs = [];
|
|
@@ -1321,7 +1577,9 @@ async function drainQueues(ctx, cssOut) {
|
|
|
1321
1577
|
jobs.forEach((j, i) => j.commit(results[i]));
|
|
1322
1578
|
if (!progressed) return;
|
|
1323
1579
|
}
|
|
1324
|
-
throw new BuildError(
|
|
1580
|
+
throw new BuildError(
|
|
1581
|
+
L('Demasiadas rondas al resolver dependencias (¿ciclo inesperado?).', 'Too many rounds resolving dependencies (unexpected cycle?).'),
|
|
1582
|
+
);
|
|
1325
1583
|
}
|
|
1326
1584
|
|
|
1327
1585
|
/**
|
|
@@ -1337,13 +1595,19 @@ function planStandalone(ctx, rel, s, cssOut) {
|
|
|
1337
1595
|
run: () => buildCss(ctx, { entry: rel, outRel: rel }),
|
|
1338
1596
|
commit: (r) => {
|
|
1339
1597
|
if (r.error) {
|
|
1340
|
-
ctx.warn(
|
|
1598
|
+
ctx.warn(
|
|
1599
|
+
L(
|
|
1600
|
+
`no se pudo procesar el CSS (${r.error.split('\n')[0]}); se copia tal cual`,
|
|
1601
|
+
`could not process the CSS (${r.error.split('\n')[0]}); copied as is`,
|
|
1602
|
+
),
|
|
1603
|
+
rel,
|
|
1604
|
+
);
|
|
1341
1605
|
ctx.emitCopy(rel, rel, 'copy-fallback');
|
|
1342
|
-
ctx.cover(rel, 'copiado sin cambios (error al procesar)', rel);
|
|
1606
|
+
ctx.cover(rel, L('copiado sin cambios (error al procesar)', 'copied unchanged (processing error)'), rel);
|
|
1343
1607
|
return;
|
|
1344
1608
|
}
|
|
1345
1609
|
ctx.emitCode(rel, r.code, r.map, { type: 'css', action: 'standalone', sources: [rel], before: ctx.files.get(rel).size });
|
|
1346
|
-
for (const i of r.inputs) ctx.cover(i, i === rel ? 'procesado (suelto)' : 'empaquetado', rel);
|
|
1610
|
+
for (const i of r.inputs) ctx.cover(i, i === rel ? L('procesado (suelto)', 'processed (standalone)') : L('empaquetado', 'bundled'), rel);
|
|
1347
1611
|
if (!cssOut.has(rel)) cssOut.set(rel, rel);
|
|
1348
1612
|
},
|
|
1349
1613
|
};
|
|
@@ -1356,9 +1620,9 @@ function planStandalone(ctx, rel, s, cssOut) {
|
|
|
1356
1620
|
const isModule = !s.classic && (s.module || (auto.ok && (auto.hasModuleSyntax || auto.hasImportMeta)) || (!auto.ok && auto.isModule));
|
|
1357
1621
|
const info = isModule ? ctx.jsInfo(rel, true) : ctx.jsInfo(rel, false);
|
|
1358
1622
|
if (!info.ok) {
|
|
1359
|
-
ctx.warn(`no se pudo analizar (${info.error}); se copia sin cambios`, rel);
|
|
1623
|
+
ctx.warn(L(`no se pudo analizar (${info.error}); se copia sin cambios`, `could not be parsed (${info.error}); copied unchanged`), rel);
|
|
1360
1624
|
ctx.emitCopy(rel, rel, 'copy-fallback');
|
|
1361
|
-
ctx.cover(rel, 'copiado sin cambios (no se pudo analizar)', rel);
|
|
1625
|
+
ctx.cover(rel, L('copiado sin cambios (no se pudo analizar)', 'copied unchanged (could not be parsed)'), rel);
|
|
1362
1626
|
return null;
|
|
1363
1627
|
}
|
|
1364
1628
|
ctx.noteJsInfo(rel, info, dir);
|
|
@@ -1379,7 +1643,7 @@ function planStandalone(ctx, rel, s, cssOut) {
|
|
|
1379
1643
|
for (const n of r.notes) ctx.note(n, rel);
|
|
1380
1644
|
const out = jsOutPath(rel);
|
|
1381
1645
|
ctx.emitCode(out, r.code, r.map, { type: 'js', module: isModule, action: 'standalone', sources: [rel], before: ctx.files.get(rel).size });
|
|
1382
|
-
ctx.cover(rel, `procesado suelto (${s.reason})`, out);
|
|
1646
|
+
ctx.cover(rel, L(`procesado suelto (${s.reason})`, `processed standalone (${s.reason})`), out);
|
|
1383
1647
|
},
|
|
1384
1648
|
};
|
|
1385
1649
|
}
|