|
1 | 1 | --- |
| 2 | +import { fileURLToPath } from 'node:url' |
| 3 | +
|
| 4 | +// Read the source-mode chunk list at SSR time so the HTML ships |
| 5 | +// with <link rel="modulepreload"> for every shared chunk already |
| 6 | +// in <head>. The browser starts warming the HTTP cache during |
| 7 | +// initial parse — well before any JS runs — which side-steps the |
| 8 | +// concurrent-fetch race that makes WebKit occasionally fail spec |
| 9 | +// iframes with the opaque "Importing a module script failed." |
| 10 | +// error. `prebuild` always runs bundle-tests.mjs before Astro |
| 11 | +// builds the site, so manifest.json is guaranteed to exist; the |
| 12 | +// catch is only for the very first `bun run dev` invocation. |
| 13 | +// Scripts (and Astro under `bun --bun`) run on Bun, so use |
| 14 | +// Bun.file rather than node:fs. |
| 15 | +let sourceChunks: string[] = [] |
| 16 | +try { |
| 17 | + const manifestPath = fileURLToPath(new URL('../../public/tests/source/manifest.json', import.meta.url)) |
| 18 | + const manifest = await Bun.file(manifestPath).json() |
| 19 | + sourceChunks = manifest.chunks ?? [] |
| 20 | +} catch { |
| 21 | + // bundle hasn't run yet — runSourceMode's runtime fallback |
| 22 | + // still injects missing <link> tags client-side. |
| 23 | +} |
| 24 | +
|
2 | 25 | // TKO Browser Test Runner — written in TKO itself. |
3 | 26 | // |
4 | 27 | // Two modes: |
|
32 | 55 | <title>TKO · Browser Tests</title> |
33 | 56 | <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Lobster&display=swap" /> |
34 | 57 | <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/mocha@10/mocha.css" /> |
| 58 | + {sourceChunks.map(name => <link rel="modulepreload" href={`/tests/source/${name}`} />)} |
| 59 | + <link rel="preload" as="script" href="/tests/source/setup.js" /> |
35 | 60 | <style> |
36 | 61 | :root { |
37 | 62 | --bg: #0b0d11; |
|
520 | 545 | const res = await fetch('/tests/source/manifest.json') |
521 | 546 | const manifest = await res.json() |
522 | 547 | let specs = manifest.specs |
| 548 | + |
| 549 | + // Chunks are normally preloaded via <link rel="modulepreload"> |
| 550 | + // rendered in the page's <head> at SSR time (see frontmatter). |
| 551 | + // If the rendered page predates the current bundle (e.g. |
| 552 | + // Astro SSR'd before bundle-tests.mjs ran, or manifest has |
| 553 | + // new chunks), inject any missing <link>s now so the browser |
| 554 | + // warms the HTTP cache before iframes race on it. |
| 555 | + const head = document.head |
| 556 | + const existing = new Set( |
| 557 | + [...head.querySelectorAll('link[rel=modulepreload]')].map(l => l.getAttribute('href')) |
| 558 | + ) |
| 559 | + const missing = (manifest.chunks || []) |
| 560 | + .map(name => `/tests/source/${name}`) |
| 561 | + .filter(href => !existing.has(href)) |
| 562 | + await Promise.all(missing.map(href => new Promise(resolve => { |
| 563 | + const link = document.createElement('link') |
| 564 | + link.rel = 'modulepreload' |
| 565 | + link.href = href |
| 566 | + link.addEventListener('load', resolve, { once: true }) |
| 567 | + link.addEventListener('error', resolve, { once: true }) |
| 568 | + head.appendChild(link) |
| 569 | + }))) |
523 | 570 | const grep = page.grep().trim() |
524 | 571 | if (grep) { |
525 | 572 | // Invalid regex patterns (e.g. bare `(`) throw from the |
|
632 | 679 | }) |
633 | 680 | } |
634 | 681 |
|
635 | | - // Phase 1: parallel hidden specs. |
| 682 | + // Phase 1: parallel hidden specs. The chunk prefetch |
| 683 | + // above warmed the HTTP cache, so iframes can safely race |
| 684 | + // on dynamic import() without tripping WebKit's |
| 685 | + // dependency-tree fetcher. |
636 | 686 | async function hiddenWorker() { |
637 | 687 | while (hiddenQueue.length) { |
638 | 688 | const spec = hiddenQueue.shift() |
|
0 commit comments