Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions tko.io/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@
"postinstall": "patch-package",
"prebuild": "bun ./scripts/generate-verified-behaviors.mjs && mkdir -p public/lib && cd ../builds/knockout && bun run build && cp dist/browser.min.js ../../tko.io/public/lib/ko.js && cd ../reference && bun run build && cp dist/browser.min.js ../../tko.io/public/lib/tko.js && cd ../../tko.io && bun ./scripts/bundle-tests.mjs",
"predev": "bun run prebuild",
"dev": "ASTRO_TELEMETRY_DISABLED=1 astro dev",
"build": "ASTRO_TELEMETRY_DISABLED=1 astro build --force",
"preview": "ASTRO_TELEMETRY_DISABLED=1 astro preview",
"check": "ASTRO_TELEMETRY_DISABLED=1 astro check"
"dev": "ASTRO_TELEMETRY_DISABLED=1 bun --bun astro dev",
"build": "ASTRO_TELEMETRY_DISABLED=1 bun --bun astro build --force",
"preview": "ASTRO_TELEMETRY_DISABLED=1 bun --bun astro preview",
"check": "ASTRO_TELEMETRY_DISABLED=1 bun --bun astro check"
},
"keywords": ["tko", "knockout", "documentation"],
"author": "",
Expand Down
5 changes: 3 additions & 2 deletions tko.io/public/tests-frame.html
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,9 @@
runner.on('end', () => parent.postMessage({ type: 'end', slug, stats: runner.stats }, '*'))
parent.postMessage({ type: 'start', slug, total: runner.total }, '*')
} catch (err) {
document.getElementById('err').textContent = 'import failed: ' + (err?.message || err)
parent.postMessage({ type: 'import-error', slug, err: err?.message || String(err) }, '*')
const msg = err?.message || String(err)
document.getElementById('err').textContent = 'import failed: ' + msg
parent.postMessage({ type: 'import-error', slug, err: msg }, '*')
}
}
</script>
Expand Down
21 changes: 20 additions & 1 deletion tko.io/scripts/bundle-tests.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -256,7 +256,14 @@ async function buildSourceBundles({ buildVersion, tsconfig, alias }) {
const specs = await collectSpecs(scope)
if (specs.length === 0) throw new Error('source-bundle scope matched no specs')

// Clean previous chunk-*.js emissions so removed specs / shifted
// dependency graphs don't leak stale hashed chunks into the new
// manifest. Wrappers + per-spec bundles regenerate from fresh
// entry points, so leaving them alone is fine.
await fs.mkdir(sourceOutputDir, { recursive: true })
for await (const name of new Bun.Glob('chunk-*.js').scan({ cwd: sourceOutputDir })) {
await fs.unlink(path.join(sourceOutputDir, name))
}
const { entryPoints, manifest } = await writeSpecWrappers(specs)

await esbuild.build({
Expand All @@ -275,9 +282,21 @@ async function buildSourceBundles({ buildVersion, tsconfig, alias }) {
logLevel: 'warning'
})

// Enumerate the shared chunk files esbuild just emitted. The
// /tests page preloads these via <link rel="modulepreload"> in
// <head> so the browser HTTP cache is warm before iframes race
// to dynamic-import them — otherwise WebKit occasionally reports
// the opaque "Importing a module script failed." error. See
// tko.io/src/pages/tests.astro.
const chunks = []
for await (const name of new Bun.Glob('chunk-*.js').scan({ cwd: sourceOutputDir })) {
chunks.push(name)
}
chunks.sort()

await fs.writeFile(
path.join(sourceOutputDir, 'manifest.json'),
JSON.stringify({ specs: manifest }, null, 2),
JSON.stringify({ specs: manifest, chunks }, null, 2),
'utf8'
)

Expand Down
27 changes: 24 additions & 3 deletions tko.io/src/pages/tests.astro
Original file line number Diff line number Diff line change
@@ -1,4 +1,17 @@
---
import { fileURLToPath } from 'node:url'

// Read the source-mode chunk list at SSR time so the HTML ships
// with <link rel="modulepreload"> for every shared chunk already
// in <head>. The browser starts warming the HTTP cache during
// initial parse — well before any JS runs — which side-steps the
// concurrent-fetch race that makes WebKit occasionally fail spec
// iframes with the opaque "Importing a module script failed."
// error. `prebuild` always runs bundle-tests.mjs before Astro
// builds the site, so manifest.json is guaranteed to exist.
const manifestPath = fileURLToPath(new URL('../../public/tests/source/manifest.json', import.meta.url))
const sourceChunks: string[] = (await Bun.file(manifestPath).json()).chunks ?? []

// TKO Browser Test Runner — written in TKO itself.
//
// Two modes:
Expand Down Expand Up @@ -32,6 +45,8 @@
<title>TKO · Browser Tests</title>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Lobster&display=swap" />
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/mocha@10/mocha.css" />
{sourceChunks.map(name => <link rel="modulepreload" href={`/tests/source/${name}`} />)}
<link rel="preload" as="script" href="/tests/source/setup.js" />
<style>
:root {
--bg: #0b0d11;
Expand Down Expand Up @@ -362,7 +377,7 @@
self.pkg = ko.observable(safePkg)
self.ver = ko.observable(safeVer)
self.grep = ko.observable(qs.get('grep') || '')
// Pool for the parallel (non-focus) phase. Focus-needing
// Pool for the parallel-hidden run. Focus-needing
// specs — flagged at bundle time via `manifest.specs[].needsFocus`
// — always run serially in the visible #workarea so each
// has sole system focus; Chromium only grants
Expand Down Expand Up @@ -520,6 +535,7 @@
const res = await fetch('/tests/source/manifest.json')
const manifest = await res.json()
let specs = manifest.specs

const grep = page.grep().trim()
if (grep) {
// Invalid regex patterns (e.g. bare `(`) throw from the
Expand Down Expand Up @@ -632,7 +648,11 @@
})
}

// Phase 1: parallel hidden specs.
// Parallel-hidden run: non-focus specs, `pool`-at-a-time
// inside the offscreen `hiddenHost`. The <link rel=modulepreload>
// tags in <head> warmed the HTTP cache, so iframes can
// race on dynamic import() without tripping WebKit's
// dependency-tree fetcher.
async function hiddenWorker() {
while (hiddenQueue.length) {
const spec = hiddenQueue.shift()
Expand All @@ -642,7 +662,8 @@
}
await Promise.all(Array.from({ length: page.pool }, hiddenWorker))

// Phase 2: serial focus specs in the visible workarea.
// Serial-focus run: one focus-needing spec at a time in
// the visible #workarea so each has sole system focus.
for (const spec of focusQueue) {
await runOne(spec, { host: workarea, label: workareaLabel, grantFocus: true })
}
Expand Down
Loading