@moku-labs/web 2.4.11 → 2.4.12
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/dist/index.cjs +100 -7
- package/dist/index.mjs +100 -7
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -2299,7 +2299,8 @@ const headPlugin = createPlugin$1("head", {
|
|
|
2299
2299
|
/**
|
|
2300
2300
|
* @file build phase 1 — bundle. Runs `Bun.build` for CSS and JS separately into
|
|
2301
2301
|
* outDir (honoring `config.minify`) with content-hashed output naming; caches the
|
|
2302
|
-
* fingerprinted asset paths for the pages phase
|
|
2302
|
+
* fingerprinted asset paths for the pages phase, the chunks the JS entry imports
|
|
2303
|
+
* statically (for `<link rel="modulepreload">`), and the complete output list for
|
|
2303
2304
|
* the cache-headers phase.
|
|
2304
2305
|
*/
|
|
2305
2306
|
/** Conventional CSS entry candidates (project-relative). */
|
|
@@ -2358,10 +2359,11 @@ const CSS_EXTERNAL_FONT_GLOBS = [
|
|
|
2358
2359
|
* @param options.naming.asset - Naming template for additional emitted assets.
|
|
2359
2360
|
* @param options.external - Import/url() globs left unresolved in the output.
|
|
2360
2361
|
* @param options.define - Global names replaced with constant expressions.
|
|
2362
|
+
* @param options.metafile - Whether to return the build metafile.
|
|
2361
2363
|
* @returns The structural build result.
|
|
2362
2364
|
* @example
|
|
2363
2365
|
* ```ts
|
|
2364
|
-
* await defaultRunner({ entrypoints: ["a.css"], outdir: "dist", minify: true, splitting: true, target: "browser", naming: FINGERPRINT_NAMING, external: [], define: {} });
|
|
2366
|
+
* await defaultRunner({ entrypoints: ["a.css"], outdir: "dist", minify: true, splitting: true, target: "browser", naming: FINGERPRINT_NAMING, external: [], define: {}, metafile: false });
|
|
2365
2367
|
* ```
|
|
2366
2368
|
*/
|
|
2367
2369
|
async function defaultRunner(options) {
|
|
@@ -2456,6 +2458,73 @@ function normalizeAssetPath(absolutePath, outDir) {
|
|
|
2456
2458
|
return node_path$1.default.relative(node_path$1.default.resolve(outDir), node_path$1.default.resolve(absolutePath)).split(node_path$1.default.sep).join("/");
|
|
2457
2459
|
}
|
|
2458
2460
|
/**
|
|
2461
|
+
* Parse the runner's metafile into the structural view (object or JSON string).
|
|
2462
|
+
*
|
|
2463
|
+
* @param metafile - The metafile as the runner returned it (possibly absent).
|
|
2464
|
+
* @returns The parsed metafile, or `undefined` when absent or unreadable.
|
|
2465
|
+
* @example
|
|
2466
|
+
* ```ts
|
|
2467
|
+
* parseMetafile('{"outputs":{}}'); // { outputs: {} }
|
|
2468
|
+
* ```
|
|
2469
|
+
*/
|
|
2470
|
+
function parseMetafile(metafile) {
|
|
2471
|
+
if (typeof metafile !== "string") return metafile;
|
|
2472
|
+
try {
|
|
2473
|
+
return JSON.parse(metafile);
|
|
2474
|
+
} catch {
|
|
2475
|
+
return;
|
|
2476
|
+
}
|
|
2477
|
+
}
|
|
2478
|
+
/**
|
|
2479
|
+
* Normalize an outdir-relative output path into one graph key ("./a.js" and "a.js"
|
|
2480
|
+
* are the same file; Windows separators become "/").
|
|
2481
|
+
*
|
|
2482
|
+
* @param file - An outdir-relative output path.
|
|
2483
|
+
* @returns The normalized POSIX key.
|
|
2484
|
+
* @example
|
|
2485
|
+
* ```ts
|
|
2486
|
+
* toKey("./chunk-1.js"); // "chunk-1.js"
|
|
2487
|
+
* ```
|
|
2488
|
+
*/
|
|
2489
|
+
function toKey(file) {
|
|
2490
|
+
return node_path$1.default.posix.normalize(file.split(node_path$1.default.sep).join("/"));
|
|
2491
|
+
}
|
|
2492
|
+
/**
|
|
2493
|
+
* Collect the chunks the JS entry points import STATICALLY, transitively, from the
|
|
2494
|
+
* metafile import graph. Only `import-statement` edges are followed: a chunk reached
|
|
2495
|
+
* only through a dynamic `import()` stays lazy and is never preloaded. These are the
|
|
2496
|
+
* files the browser would otherwise discover only after the entry finished
|
|
2497
|
+
* downloading (a request waterfall); the pages phase emits one
|
|
2498
|
+
* `<link rel="modulepreload">` per path so they load in parallel with the entry.
|
|
2499
|
+
* The walk starts from the `entry-point` artifacts, not from the metafile: Bun also
|
|
2500
|
+
* tags each lazy chunk with an `entryPoint`, and a lazy chunk may import the entry.
|
|
2501
|
+
*
|
|
2502
|
+
* @param result - The JS pass result (artifacts + metafile; no metafile → no preloads).
|
|
2503
|
+
* @param outdir - The bundler output directory the metafile paths are relative to.
|
|
2504
|
+
* @param outDir - The publish root the web paths are made relative to.
|
|
2505
|
+
* @returns The publish-root-relative paths of the static chunks (entries excluded).
|
|
2506
|
+
* @example
|
|
2507
|
+
* ```ts
|
|
2508
|
+
* staticEntryChunks(result, "dist/assets", "dist"); // ["assets/chunk-9f8e.js"]
|
|
2509
|
+
* ```
|
|
2510
|
+
*/
|
|
2511
|
+
function staticEntryChunks(result, outdir, outDir) {
|
|
2512
|
+
const metafile = parseMetafile(result.metafile);
|
|
2513
|
+
if (!metafile) return [];
|
|
2514
|
+
const graph = new Map(Object.entries(metafile.outputs).map(([file, output]) => [toKey(file), (output.imports ?? []).filter((edge) => edge.kind === "import-statement").map((edge) => toKey(edge.path))]));
|
|
2515
|
+
const entries = result.outputs.filter((output) => output.kind === "entry-point" && output.path.endsWith(".js")).map((output) => toKey(node_path$1.default.relative(node_path$1.default.resolve(outdir), node_path$1.default.resolve(output.path))));
|
|
2516
|
+
const seen = new Set(entries);
|
|
2517
|
+
const queue = [...entries];
|
|
2518
|
+
const chunks = [];
|
|
2519
|
+
for (let file = queue.shift(); file !== void 0; file = queue.shift()) for (const target of graph.get(file) ?? []) {
|
|
2520
|
+
if (seen.has(target)) continue;
|
|
2521
|
+
seen.add(target);
|
|
2522
|
+
queue.push(target);
|
|
2523
|
+
chunks.push(normalizeAssetPath(node_path$1.default.join(outdir, target), outDir));
|
|
2524
|
+
}
|
|
2525
|
+
return chunks;
|
|
2526
|
+
}
|
|
2527
|
+
/**
|
|
2459
2528
|
* Run one bundler pass for a single asset kind and record the hashed output
|
|
2460
2529
|
* paths under `state.buildCache` keyed by the original entry basename. Lazy
|
|
2461
2530
|
* split chunks are emitted to disk but excluded from the recorded manifest
|
|
@@ -2484,7 +2553,8 @@ async function runOne(ctx, runner, kind, entrypoints, outDir, outdir, minify, de
|
|
|
2484
2553
|
target: "browser",
|
|
2485
2554
|
naming: FINGERPRINT_NAMING,
|
|
2486
2555
|
external: kind === "css" ? [...CSS_EXTERNAL_FONT_GLOBS] : [],
|
|
2487
|
-
define
|
|
2556
|
+
define,
|
|
2557
|
+
metafile: kind === "js"
|
|
2488
2558
|
});
|
|
2489
2559
|
if (!result.success) throw new Error(`[web] build.bundle ${kind} build failed`);
|
|
2490
2560
|
const hashed = {};
|
|
@@ -2494,6 +2564,7 @@ async function runOne(ctx, runner, kind, entrypoints, outDir, outdir, minify, de
|
|
|
2494
2564
|
}
|
|
2495
2565
|
ctx.state.buildCache.set(kind, hashed);
|
|
2496
2566
|
ctx.state.buildCache.set(`${kind}:outputs`, result.outputs.map((output) => normalizeAssetPath(output.path, outDir)));
|
|
2567
|
+
if (kind === "js") ctx.state.buildCache.set("js:preload", staticEntryChunks(result, outdir, outDir));
|
|
2497
2568
|
ctx.log.debug("build:bundle", {
|
|
2498
2569
|
kind,
|
|
2499
2570
|
count: result.outputs.length
|
|
@@ -2570,6 +2641,22 @@ function readBundleOutputs(ctx, kind) {
|
|
|
2570
2641
|
return Array.isArray(entry) ? entry : [];
|
|
2571
2642
|
}
|
|
2572
2643
|
/**
|
|
2644
|
+
* Read the chunks the JS entry imports statically (publish-root-relative web paths),
|
|
2645
|
+
* recorded by the bundle phase under `js:preload`. Dynamic-import-only chunks are
|
|
2646
|
+
* never in this list.
|
|
2647
|
+
*
|
|
2648
|
+
* @param ctx - Plugin context (provides `state`).
|
|
2649
|
+
* @returns The static chunk paths, or an empty array when absent.
|
|
2650
|
+
* @example
|
|
2651
|
+
* ```ts
|
|
2652
|
+
* readPreloads(ctx); // ["assets/chunk-9f8e.js"]
|
|
2653
|
+
* ```
|
|
2654
|
+
*/
|
|
2655
|
+
function readPreloads(ctx) {
|
|
2656
|
+
const entry = ctx.state.buildCache.get("js:preload");
|
|
2657
|
+
return Array.isArray(entry) ? entry : [];
|
|
2658
|
+
}
|
|
2659
|
+
/**
|
|
2573
2660
|
* Render the stylesheet `<link>` tags for the fingerprinted CSS manifest.
|
|
2574
2661
|
*
|
|
2575
2662
|
* @param ctx - Plugin context (provides `state`).
|
|
@@ -2583,17 +2670,23 @@ function buildCssTags(ctx) {
|
|
|
2583
2670
|
return Object.values(readManifest(ctx, "css")).map((href) => `<link rel="stylesheet" href="/${href}">`).join("");
|
|
2584
2671
|
}
|
|
2585
2672
|
/**
|
|
2586
|
-
* Render the module `<script>` tags for the fingerprinted JS manifest
|
|
2673
|
+
* Render the module `<script>` tags for the fingerprinted JS manifest, followed by
|
|
2674
|
+
* one `<link rel="modulepreload">` per chunk the entry imports statically. Without
|
|
2675
|
+
* the preloads the browser finds those chunks only after the entry has downloaded
|
|
2676
|
+
* and parsed, a request waterfall that delays hydration.
|
|
2587
2677
|
*
|
|
2588
2678
|
* @param ctx - Plugin context (provides `state`).
|
|
2589
|
-
* @returns The concatenated `<script type="module">` tags (possibly `""`).
|
|
2679
|
+
* @returns The concatenated `<script type="module">` and modulepreload tags (possibly `""`).
|
|
2590
2680
|
* @example
|
|
2591
2681
|
* ```ts
|
|
2592
|
-
* buildJsTags(ctx);
|
|
2682
|
+
* buildJsTags(ctx);
|
|
2683
|
+
* // '<script type="module" src="/assets/spa-abc123.js"><\/script><link rel="modulepreload" href="/assets/chunk-9f8e.js">'
|
|
2593
2684
|
* ```
|
|
2594
2685
|
*/
|
|
2595
2686
|
function buildJsTags(ctx) {
|
|
2596
|
-
|
|
2687
|
+
const scripts = Object.values(readManifest(ctx, "js")).map((src) => `<script type="module" src="/${src}"><\/script>`).join("");
|
|
2688
|
+
if (scripts === "") return "";
|
|
2689
|
+
return scripts + readPreloads(ctx).map((href) => `<link rel="modulepreload" href="/${href}">`).join("");
|
|
2597
2690
|
}
|
|
2598
2691
|
/**
|
|
2599
2692
|
* Build the asset tag block from the fingerprinted manifests — both kinds by
|
package/dist/index.mjs
CHANGED
|
@@ -2255,7 +2255,8 @@ const headPlugin = createPlugin$1("head", {
|
|
|
2255
2255
|
/**
|
|
2256
2256
|
* @file build phase 1 — bundle. Runs `Bun.build` for CSS and JS separately into
|
|
2257
2257
|
* outDir (honoring `config.minify`) with content-hashed output naming; caches the
|
|
2258
|
-
* fingerprinted asset paths for the pages phase
|
|
2258
|
+
* fingerprinted asset paths for the pages phase, the chunks the JS entry imports
|
|
2259
|
+
* statically (for `<link rel="modulepreload">`), and the complete output list for
|
|
2259
2260
|
* the cache-headers phase.
|
|
2260
2261
|
*/
|
|
2261
2262
|
/** Conventional CSS entry candidates (project-relative). */
|
|
@@ -2314,10 +2315,11 @@ const CSS_EXTERNAL_FONT_GLOBS = [
|
|
|
2314
2315
|
* @param options.naming.asset - Naming template for additional emitted assets.
|
|
2315
2316
|
* @param options.external - Import/url() globs left unresolved in the output.
|
|
2316
2317
|
* @param options.define - Global names replaced with constant expressions.
|
|
2318
|
+
* @param options.metafile - Whether to return the build metafile.
|
|
2317
2319
|
* @returns The structural build result.
|
|
2318
2320
|
* @example
|
|
2319
2321
|
* ```ts
|
|
2320
|
-
* await defaultRunner({ entrypoints: ["a.css"], outdir: "dist", minify: true, splitting: true, target: "browser", naming: FINGERPRINT_NAMING, external: [], define: {} });
|
|
2322
|
+
* await defaultRunner({ entrypoints: ["a.css"], outdir: "dist", minify: true, splitting: true, target: "browser", naming: FINGERPRINT_NAMING, external: [], define: {}, metafile: false });
|
|
2321
2323
|
* ```
|
|
2322
2324
|
*/
|
|
2323
2325
|
async function defaultRunner(options) {
|
|
@@ -2412,6 +2414,73 @@ function normalizeAssetPath(absolutePath, outDir) {
|
|
|
2412
2414
|
return path.relative(path.resolve(outDir), path.resolve(absolutePath)).split(path.sep).join("/");
|
|
2413
2415
|
}
|
|
2414
2416
|
/**
|
|
2417
|
+
* Parse the runner's metafile into the structural view (object or JSON string).
|
|
2418
|
+
*
|
|
2419
|
+
* @param metafile - The metafile as the runner returned it (possibly absent).
|
|
2420
|
+
* @returns The parsed metafile, or `undefined` when absent or unreadable.
|
|
2421
|
+
* @example
|
|
2422
|
+
* ```ts
|
|
2423
|
+
* parseMetafile('{"outputs":{}}'); // { outputs: {} }
|
|
2424
|
+
* ```
|
|
2425
|
+
*/
|
|
2426
|
+
function parseMetafile(metafile) {
|
|
2427
|
+
if (typeof metafile !== "string") return metafile;
|
|
2428
|
+
try {
|
|
2429
|
+
return JSON.parse(metafile);
|
|
2430
|
+
} catch {
|
|
2431
|
+
return;
|
|
2432
|
+
}
|
|
2433
|
+
}
|
|
2434
|
+
/**
|
|
2435
|
+
* Normalize an outdir-relative output path into one graph key ("./a.js" and "a.js"
|
|
2436
|
+
* are the same file; Windows separators become "/").
|
|
2437
|
+
*
|
|
2438
|
+
* @param file - An outdir-relative output path.
|
|
2439
|
+
* @returns The normalized POSIX key.
|
|
2440
|
+
* @example
|
|
2441
|
+
* ```ts
|
|
2442
|
+
* toKey("./chunk-1.js"); // "chunk-1.js"
|
|
2443
|
+
* ```
|
|
2444
|
+
*/
|
|
2445
|
+
function toKey(file) {
|
|
2446
|
+
return path.posix.normalize(file.split(path.sep).join("/"));
|
|
2447
|
+
}
|
|
2448
|
+
/**
|
|
2449
|
+
* Collect the chunks the JS entry points import STATICALLY, transitively, from the
|
|
2450
|
+
* metafile import graph. Only `import-statement` edges are followed: a chunk reached
|
|
2451
|
+
* only through a dynamic `import()` stays lazy and is never preloaded. These are the
|
|
2452
|
+
* files the browser would otherwise discover only after the entry finished
|
|
2453
|
+
* downloading (a request waterfall); the pages phase emits one
|
|
2454
|
+
* `<link rel="modulepreload">` per path so they load in parallel with the entry.
|
|
2455
|
+
* The walk starts from the `entry-point` artifacts, not from the metafile: Bun also
|
|
2456
|
+
* tags each lazy chunk with an `entryPoint`, and a lazy chunk may import the entry.
|
|
2457
|
+
*
|
|
2458
|
+
* @param result - The JS pass result (artifacts + metafile; no metafile → no preloads).
|
|
2459
|
+
* @param outdir - The bundler output directory the metafile paths are relative to.
|
|
2460
|
+
* @param outDir - The publish root the web paths are made relative to.
|
|
2461
|
+
* @returns The publish-root-relative paths of the static chunks (entries excluded).
|
|
2462
|
+
* @example
|
|
2463
|
+
* ```ts
|
|
2464
|
+
* staticEntryChunks(result, "dist/assets", "dist"); // ["assets/chunk-9f8e.js"]
|
|
2465
|
+
* ```
|
|
2466
|
+
*/
|
|
2467
|
+
function staticEntryChunks(result, outdir, outDir) {
|
|
2468
|
+
const metafile = parseMetafile(result.metafile);
|
|
2469
|
+
if (!metafile) return [];
|
|
2470
|
+
const graph = new Map(Object.entries(metafile.outputs).map(([file, output]) => [toKey(file), (output.imports ?? []).filter((edge) => edge.kind === "import-statement").map((edge) => toKey(edge.path))]));
|
|
2471
|
+
const entries = result.outputs.filter((output) => output.kind === "entry-point" && output.path.endsWith(".js")).map((output) => toKey(path.relative(path.resolve(outdir), path.resolve(output.path))));
|
|
2472
|
+
const seen = new Set(entries);
|
|
2473
|
+
const queue = [...entries];
|
|
2474
|
+
const chunks = [];
|
|
2475
|
+
for (let file = queue.shift(); file !== void 0; file = queue.shift()) for (const target of graph.get(file) ?? []) {
|
|
2476
|
+
if (seen.has(target)) continue;
|
|
2477
|
+
seen.add(target);
|
|
2478
|
+
queue.push(target);
|
|
2479
|
+
chunks.push(normalizeAssetPath(path.join(outdir, target), outDir));
|
|
2480
|
+
}
|
|
2481
|
+
return chunks;
|
|
2482
|
+
}
|
|
2483
|
+
/**
|
|
2415
2484
|
* Run one bundler pass for a single asset kind and record the hashed output
|
|
2416
2485
|
* paths under `state.buildCache` keyed by the original entry basename. Lazy
|
|
2417
2486
|
* split chunks are emitted to disk but excluded from the recorded manifest
|
|
@@ -2440,7 +2509,8 @@ async function runOne(ctx, runner, kind, entrypoints, outDir, outdir, minify, de
|
|
|
2440
2509
|
target: "browser",
|
|
2441
2510
|
naming: FINGERPRINT_NAMING,
|
|
2442
2511
|
external: kind === "css" ? [...CSS_EXTERNAL_FONT_GLOBS] : [],
|
|
2443
|
-
define
|
|
2512
|
+
define,
|
|
2513
|
+
metafile: kind === "js"
|
|
2444
2514
|
});
|
|
2445
2515
|
if (!result.success) throw new Error(`[web] build.bundle ${kind} build failed`);
|
|
2446
2516
|
const hashed = {};
|
|
@@ -2450,6 +2520,7 @@ async function runOne(ctx, runner, kind, entrypoints, outDir, outdir, minify, de
|
|
|
2450
2520
|
}
|
|
2451
2521
|
ctx.state.buildCache.set(kind, hashed);
|
|
2452
2522
|
ctx.state.buildCache.set(`${kind}:outputs`, result.outputs.map((output) => normalizeAssetPath(output.path, outDir)));
|
|
2523
|
+
if (kind === "js") ctx.state.buildCache.set("js:preload", staticEntryChunks(result, outdir, outDir));
|
|
2453
2524
|
ctx.log.debug("build:bundle", {
|
|
2454
2525
|
kind,
|
|
2455
2526
|
count: result.outputs.length
|
|
@@ -2526,6 +2597,22 @@ function readBundleOutputs(ctx, kind) {
|
|
|
2526
2597
|
return Array.isArray(entry) ? entry : [];
|
|
2527
2598
|
}
|
|
2528
2599
|
/**
|
|
2600
|
+
* Read the chunks the JS entry imports statically (publish-root-relative web paths),
|
|
2601
|
+
* recorded by the bundle phase under `js:preload`. Dynamic-import-only chunks are
|
|
2602
|
+
* never in this list.
|
|
2603
|
+
*
|
|
2604
|
+
* @param ctx - Plugin context (provides `state`).
|
|
2605
|
+
* @returns The static chunk paths, or an empty array when absent.
|
|
2606
|
+
* @example
|
|
2607
|
+
* ```ts
|
|
2608
|
+
* readPreloads(ctx); // ["assets/chunk-9f8e.js"]
|
|
2609
|
+
* ```
|
|
2610
|
+
*/
|
|
2611
|
+
function readPreloads(ctx) {
|
|
2612
|
+
const entry = ctx.state.buildCache.get("js:preload");
|
|
2613
|
+
return Array.isArray(entry) ? entry : [];
|
|
2614
|
+
}
|
|
2615
|
+
/**
|
|
2529
2616
|
* Render the stylesheet `<link>` tags for the fingerprinted CSS manifest.
|
|
2530
2617
|
*
|
|
2531
2618
|
* @param ctx - Plugin context (provides `state`).
|
|
@@ -2539,17 +2626,23 @@ function buildCssTags(ctx) {
|
|
|
2539
2626
|
return Object.values(readManifest(ctx, "css")).map((href) => `<link rel="stylesheet" href="/${href}">`).join("");
|
|
2540
2627
|
}
|
|
2541
2628
|
/**
|
|
2542
|
-
* Render the module `<script>` tags for the fingerprinted JS manifest
|
|
2629
|
+
* Render the module `<script>` tags for the fingerprinted JS manifest, followed by
|
|
2630
|
+
* one `<link rel="modulepreload">` per chunk the entry imports statically. Without
|
|
2631
|
+
* the preloads the browser finds those chunks only after the entry has downloaded
|
|
2632
|
+
* and parsed, a request waterfall that delays hydration.
|
|
2543
2633
|
*
|
|
2544
2634
|
* @param ctx - Plugin context (provides `state`).
|
|
2545
|
-
* @returns The concatenated `<script type="module">` tags (possibly `""`).
|
|
2635
|
+
* @returns The concatenated `<script type="module">` and modulepreload tags (possibly `""`).
|
|
2546
2636
|
* @example
|
|
2547
2637
|
* ```ts
|
|
2548
|
-
* buildJsTags(ctx);
|
|
2638
|
+
* buildJsTags(ctx);
|
|
2639
|
+
* // '<script type="module" src="/assets/spa-abc123.js"><\/script><link rel="modulepreload" href="/assets/chunk-9f8e.js">'
|
|
2549
2640
|
* ```
|
|
2550
2641
|
*/
|
|
2551
2642
|
function buildJsTags(ctx) {
|
|
2552
|
-
|
|
2643
|
+
const scripts = Object.values(readManifest(ctx, "js")).map((src) => `<script type="module" src="/${src}"><\/script>`).join("");
|
|
2644
|
+
if (scripts === "") return "";
|
|
2645
|
+
return scripts + readPreloads(ctx).map((href) => `<link rel="modulepreload" href="/${href}">`).join("");
|
|
2553
2646
|
}
|
|
2554
2647
|
/**
|
|
2555
2648
|
* Build the asset tag block from the fingerprinted manifests — both kinds by
|
package/package.json
CHANGED