The bundles carried all thirteen translations at once and a page fetched them a third time as raw source; each page now loads only its own language. Alongside that: JavaScript is minified, the eight stylesheets are served as one file, the QR scanner is fetched after the app is up instead of on every visit, Inter ships once rather than five copies of the same file, and Font Awesome is subset to the 82 icons this app draws instead of all 2468. 1.85 MB across 43 requests becomes under 700 KB across 33. On mobile the page starts drawing in 1.6 s instead of 6.3 s and is usable in 4.5 s instead of 11 s. Pages also carry their text in the HTML now. Everything was drawn by JavaScript into an empty div, so crawlers saw correct metadata around nothing, and twelve of the thirteen language pages had never been shown to anyone. The documentation is published under /docs/ with a new FAQ, and unknown addresses return a real 404. Separately: the localized shells were served with the year-long immutable cache header meant for static assets, which pinned anyone who opened /de/ or /ru/ to that build. The header is fixed and the service worker refreshes what it cached. Claude-Session: https://claude.ai/code/session_014KjzTXxrhzYoDDWChYQ4u2
59 lines
3.2 KiB
JavaScript
59 lines
3.2 KiB
JavaScript
// The QR bundle is the third largest thing this site serves — 142 KB gzipped — and it
|
|
// is needed on exactly two occasions: creating a channel, and opening the scanner.
|
|
// Neither is on the first screen. It used to be a <script type="module"> in <head>, so
|
|
// every first visit paid for it up front. What has to keep holding: it is not in the
|
|
// shells, app-boot still fetches it, esbuild has not quietly inlined it back into the
|
|
// critical bundle, and the scanner can still start if someone opens it early.
|
|
|
|
import assert from 'node:assert/strict';
|
|
import { readFileSync } from 'node:fs';
|
|
import path from 'node:path';
|
|
import { fileURLToPath } from 'node:url';
|
|
|
|
const ROOT = fileURLToPath(new URL('..', import.meta.url));
|
|
const read = (rel) => readFileSync(path.join(ROOT, rel), 'utf8');
|
|
|
|
const site = JSON.parse(read('locales/site.json'));
|
|
const shells = ['index.html', ...site.locales.filter((c) => c !== site.defaultLocale).map((c) => `${c}/index.html`)];
|
|
|
|
for (const shell of shells) {
|
|
const html = read(shell);
|
|
// Comments are allowed to mention it; a <script src> is not.
|
|
const tags = [...html.matchAll(/<script[^>]*src="([^"]*)"/g)].map((m) => m[1]);
|
|
assert.equal(tags.some((src) => src.includes('qr-local')), false,
|
|
`${shell} loads the QR bundle up front — it belongs behind app-boot's idle fetch`);
|
|
assert.equal(tags.some((src) => src.includes('QRScanner')), false,
|
|
`${shell} still loads QRScanner.js, which registers a window.QRScanner nothing reads`);
|
|
}
|
|
|
|
// app-boot must actually fetch it, and only after the app has mounted.
|
|
{
|
|
const boot = read('src/scripts/app-boot.js');
|
|
assert.match(boot, /import\(\s*['"]\/dist\/qr-local\.js['"]\s*\)/,
|
|
'app-boot.js no longer imports the QR bundle — nothing else does either');
|
|
assert.ok(boot.includes('scheduleQrBundle()'), 'app-boot.js never schedules the QR fetch');
|
|
assert.ok(boot.includes('securebit:qr-ready'),
|
|
'app-boot.js must announce the bundle, or a scanner opened early never starts');
|
|
|
|
// The import has to stay a runtime fetch. If esbuild ever resolves and inlines it,
|
|
// the bundle grows by the whole QR library and this change silently undoes itself.
|
|
const bundle = read('dist/app-boot.js');
|
|
assert.ok(bundle.includes('import("/dist/qr-local.js")'),
|
|
'dist/app-boot.js lost the dynamic import — esbuild may have inlined the QR bundle');
|
|
assert.equal(bundle.includes('Html5Qrcode'), false,
|
|
'the QR library ended up inside dist/app-boot.js, which is what deferring it was for');
|
|
}
|
|
|
|
// The scanner starts from an effect guarded on window.Html5Qrcode. With the bundle
|
|
// arriving late that guard can fail on first run, so the effect has to re-run when it
|
|
// lands — otherwise opening the modal early leaves a black viewfinder for good.
|
|
{
|
|
const app = read('src/app.jsx');
|
|
assert.ok(app.includes("window.addEventListener('securebit:qr-ready'"),
|
|
'app.jsx does not listen for the QR bundle, so an early scanner never recovers');
|
|
assert.match(app, /\}, \[showQRScannerModal, qrBundleReady\]\);/,
|
|
'the scanner effect must depend on the QR bundle having arrived');
|
|
}
|
|
|
|
console.log('qr-bundle-deferred.test.mjs: all assertions passed');
|