Embed snippets preload the Mapbox engine, so a partner page fetches it alongside the bundle
The card asked for Vue to be bundled inline to make maps faster. It
already is: vite.config.js externalises only jspdf, and every IIFE
carries its Vue runtime. The measurement (internal/measure-map-load.mjs,
cold, 10 Mbps / 60 ms, served built tree) found one serial runtime
fetch before first paint, on partner pages only. A page that pastes the
embed snippet has no leaf page to fetch mapbox-gl.js (~480 KB gz), so the
bundle requested it after it had downloaded, run and mounted. Leaf pages
already load it in parallel.
Map profiles' script snippets now start with a preload for the engine,
at the URL read from useMapInstance.js ENGINES.mapbox.js so the two can
never differ. It has no crossorigin attribute because
ensureEngineScript injects a plain <script>, and a mismatched preload is
not reused. Non-map profiles get no preload. A comment tells a partner
who switches to provider "maplibre" to delete the line.
Partner embed, before -> after (2 cold runs):
doxa-simple-map engine start 1.06 -> 0.20 s, done 2.26 -> 0.95 s,
style start 2.73 -> 1.94 s
regions-mfe engine start 2.67 -> 0.11 s, done 3.25 -> 1.13 s
(style start unchanged: the composite mounts its map late)
gospel resources engine start 0.81 -> 0.27 s
One engine request per run: the preload is reused, never a double
download. The time until tiles are loaded is dominated by tiles, data
and drawing, and moved within noise.
Held, per dx-shepherd: no shared Vue, no inlined mapbox-gl, and lighter
basemaps are for the Driver to decide.
This commit is contained in:
parent
11a4d06935
commit
4a6886d2c9
2 changed files with 31 additions and 2 deletions
|
|
@ -161,6 +161,19 @@ const DEFINE_RE = /customElements\.define\(\s*['"]([^'"]+)['"]/g // registered
|
||||||
// manifest lists exactly what the dev manifest lists (a blank template-map-a/b in the built studio was
|
// manifest lists exactly what the dev manifest lists (a blank template-map-a/b in the built studio was
|
||||||
// the only difference, found 2026-09-25). Not gatedProfiles: that list is governance (private data).
|
// the only difference, found 2026-09-25). Not gatedProfiles: that list is governance (private data).
|
||||||
const SCAFFOLD_BUNDLES = ['template-bundle']
|
const SCAFFOLD_BUNDLES = ['template-bundle']
|
||||||
|
// ★ THE MAPBOX ENGINE URL, read from the ONE place that loads it (useMapInstance.js ENGINES.mapbox.js),
|
||||||
|
// so the embed snippet's preload can never point at a different version than the one the bundle
|
||||||
|
// requests — a mismatched preload is not used, and the engine would download twice.
|
||||||
|
const MAPBOX_GL_JS = (() => {
|
||||||
|
try {
|
||||||
|
const src = readFileSync(resolve(__dirname, '..', 'library', 'composables', 'useMapInstance.js'), 'utf8')
|
||||||
|
const m = src.match(/mapbox:\s*\{[^}]*?js:\s*'([^']+mapbox-gl\.js)'/)
|
||||||
|
if (m) return m[1]
|
||||||
|
} catch { /* fall through */ }
|
||||||
|
console.warn('[generate-tree] could not read the Mapbox engine URL from useMapInstance.js; embed snippets get no preload')
|
||||||
|
return ''
|
||||||
|
})()
|
||||||
|
|
||||||
const isBundleDir = (name) => !name.startsWith('_') && !name.startsWith('.') && !DEST_CONFIG.gatedProfiles.includes(name) && !SCAFFOLD_BUNDLES.includes(name)
|
const isBundleDir = (name) => !name.startsWith('_') && !name.startsWith('.') && !DEST_CONFIG.gatedProfiles.includes(name) && !SCAFFOLD_BUNDLES.includes(name)
|
||||||
|
|
||||||
// ★ CUSTOM-ELEMENT NAME RULE (HTML spec): lowercase, must contain a hyphen, no spaces.
|
// ★ CUSTOM-ELEMENT NAME RULE (HTML spec): lowercase, must contain a hyphen, no spaces.
|
||||||
|
|
@ -1204,7 +1217,20 @@ function scanBundles() {
|
||||||
isMap,
|
isMap,
|
||||||
iframeUrl,
|
iframeUrl,
|
||||||
scriptUrl,
|
scriptUrl,
|
||||||
embed: { iframe: iframeEmbed, script: scriptEmbed },
|
// ★ ENGINE PRELOAD, map snippets only (measured 2026-09-25, cold, 10 Mbps). A partner page that
|
||||||
|
// pastes this snippet has no leaf page to fetch mapbox-gl.js, so the bundle requested it only
|
||||||
|
// after it had downloaded, run and mounted: doxa-simple-map's engine started at 1.06 s, right
|
||||||
|
// after its bundle (0.99 s); regions-mfe's at 2.67 s. A preload lets the ~480 KB engine download
|
||||||
|
// alongside the bundle. NO crossorigin attribute, on purpose: ensureEngineScript injects a plain
|
||||||
|
// <script src>, and a preload is only reused when its request mode matches — with crossorigin it
|
||||||
|
// would download twice. Mapbox only: a non-map profile gets none, and a partner switching to
|
||||||
|
// provider:"maplibre" is told to drop the line, since that engine must make zero Mapbox requests.
|
||||||
|
embed: {
|
||||||
|
iframe: iframeEmbed,
|
||||||
|
script: isMap && MAPBOX_GL_JS
|
||||||
|
? `<!-- Mapbox engine, fetched early; remove this line if you set "provider":"maplibre" -->\n<link rel="preload" as="script" href="${MAPBOX_GL_JS}">\n` + scriptEmbed
|
||||||
|
: scriptEmbed,
|
||||||
|
},
|
||||||
...(instances.length ? { instances } : {}),
|
...(instances.length ? { instances } : {}),
|
||||||
// Extra profile-config keys from <profile>.meta.json `config` (e.g. pinHint:false).
|
// Extra profile-config keys from <profile>.meta.json `config` (e.g. pinHint:false).
|
||||||
// GENERATOR-INTERNAL: stripped before the manifest is written (see writeManifestAndHtml)
|
// GENERATOR-INTERNAL: stripped before the manifest is written (see writeManifestAndHtml)
|
||||||
|
|
|
||||||
|
|
@ -87,7 +87,10 @@ for (const ref of ONLY) {
|
||||||
const first = (k) => all.filter((x) => x.kind === k).sort((a, c) => a.start - c.start)[0]
|
const first = (k) => all.filter((x) => x.kind === k).sort((a, c) => a.start - c.start)[0]
|
||||||
const bundleReq = all.find((x) => x.kind === 'bundle' && x.url.includes(`/${bundle}/${bundle}.js`)) || first('bundle')
|
const bundleReq = all.find((x) => x.kind === 'bundle' && x.url.includes(`/${bundle}/${bundle}.js`)) || first('bundle')
|
||||||
const eng = first('engine'), sty = first('style'), dat = first('data')
|
const eng = first('engine'), sty = first('style'), dat = first('data')
|
||||||
|
// A preload the browser cannot reuse (request mode mismatch) shows up as a SECOND engine request.
|
||||||
|
const engineRequests = all.filter((x) => x.kind === 'engine').length
|
||||||
runs.push({
|
runs.push({
|
||||||
|
engineRequests,
|
||||||
loaded, styled, bundleEnd: bundleReq && bundleReq.end, engineStart: eng && eng.start, engineEnd: eng && eng.end,
|
loaded, styled, bundleEnd: bundleReq && bundleReq.end, engineStart: eng && eng.start, engineEnd: eng && eng.end,
|
||||||
styleStart: sty && sty.start, dataStart: dat && dat.start,
|
styleStart: sty && sty.start, dataStart: dat && dat.start,
|
||||||
bytes: { bundle: 0 },
|
bytes: { bundle: 0 },
|
||||||
|
|
@ -95,7 +98,7 @@ for (const ref of ONLY) {
|
||||||
await ctx.close()
|
await ctx.close()
|
||||||
}
|
}
|
||||||
const med = (k) => { const v = runs.map((x) => x[k]).filter((x) => x != null).sort((a, c) => a - c); return v.length ? v[Math.floor(v.length / 2)] : null }
|
const med = (k) => { const v = runs.map((x) => x[k]).filter((x) => x != null).sort((a, c) => a - c); return v.length ? v[Math.floor(v.length / 2)] : null }
|
||||||
console.log(' measured ' + ref + ': ' + JSON.stringify(runs.map((x) => x.loaded)))
|
console.log(' measured ' + ref + ': ' + JSON.stringify(runs.map((x) => x.loaded)) + ' · engine requests per run ' + JSON.stringify(runs.map((x) => x.engineRequests)))
|
||||||
rows.push({ ref, styled: med('styled'), loaded: med('loaded'), bundleEnd: med('bundleEnd'), engineStart: med('engineStart'), engineEnd: med('engineEnd'), styleStart: med('styleStart'), dataStart: med('dataStart'), runs: runs.map((x) => x.loaded) })
|
rows.push({ ref, styled: med('styled'), loaded: med('loaded'), bundleEnd: med('bundleEnd'), engineStart: med('engineStart'), engineEnd: med('engineEnd'), styleStart: med('styleStart'), dataStart: med('dataStart'), runs: runs.map((x) => x.loaded) })
|
||||||
}
|
}
|
||||||
await browser.close(); srv.kill(); host.close()
|
await browser.close(); srv.kill(); host.close()
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue