(rootSrc, exports, lockfile, ctx)
| 43 | } |
| 44 | |
| 45 | export async function bfsPrefetch(rootSrc, exports, lockfile, ctx) { |
| 46 | const { fetchedSources, knownMissing, importsMap, mainThreadSpecs } = ctx; |
| 47 | const visited = new Set(); |
| 48 | const queue = []; |
| 49 | // Module specs that never registered; thrown together at the end so the user sees a clear cause. |
| 50 | const failures = []; |
| 51 | // Bare-name -> target spec. Seeded from importsMap (defaults + user); physical packages.json merge in as discovered. |
| 52 | const table = { ...(importsMap || {}) }; |
| 53 | // Bare names scanned before a manifest declared them; retried after each manifest merge. |
| 54 | const pendingBare = new Map(); // name -> importer dirs, for relative targets |
| 55 | |
| 56 | const writeBytes = (bytes) => { |
| 57 | const ptr = exports.wasm_alloc(Math.max(1, bytes.length)); |
| 58 | new Uint8Array(exports.memory.buffer, ptr, bytes.length).set(bytes); |
| 59 | return ptr; |
| 60 | }; |
| 61 | const enqueueManifestSibling = (forSpec) => { |
| 62 | const m = dirOf(forSpec) + 'packages.json'; |
| 63 | if (!knownMissing.has(m)) queue.push(m); |
| 64 | }; |
| 65 | |
| 66 | /* A scanned import contributes at most one fetch target: quoted is direct, bare resolves via the table. */ |
| 67 | const enqueueImport = (imp, dir) => { |
| 68 | if (!imp.bare) { queue.push(joinRel(dir, imp.spec)); return; } |
| 69 | const target = table[imp.spec]; |
| 70 | if (target !== undefined) queue.push(joinRel(dir, target)); |
| 71 | else { const ds = pendingBare.get(imp.spec); ds ? ds.push(dir) : pendingBare.set(imp.spec, [dir]); } // a later manifest may declare it |
| 72 | }; |
| 73 | const retryPending = () => { |
| 74 | for (const [name, dirs] of [...pendingBare]) { |
| 75 | const target = table[name]; |
| 76 | if (target !== undefined) { for (const dir of dirs) queue.push(joinRel(dir, target)); pendingBare.delete(name); } |
| 77 | } |
| 78 | }; |
| 79 | |
| 80 | // Synthetic root packages.json so the COMPILER resolves bare names at parse time the same way. |
| 81 | if (Object.keys(table).length > 0) { |
| 82 | fetchedSources.set('packages.json', TE.encode(JSON.stringify({ imports: table }))); |
| 83 | knownMissing.delete('packages.json'); |
| 84 | } |
| 85 | |
| 86 | for (const imp of scanImports(rootSrc, exports)) enqueueImport(imp, ''); |
| 87 | if (!knownMissing.has('packages.json')) queue.push('packages.json'); |
| 88 | |
| 89 | while (queue.length) { |
| 90 | const spec = queue.shift(); |
| 91 | if (visited.has(spec)) continue; |
| 92 | visited.add(spec); |
| 93 | |
| 94 | // Eager host (programmatic object) already registered before prefetch; nothing to fetch. |
| 95 | if (mainThreadSpecs && mainThreadSpecs.has(spec)) continue; |
| 96 | |
| 97 | // Lazy host: ask the page to load the ESM, then register its exports as `mt:<name>` stubs. |
| 98 | if (spec.startsWith('mt:')) { |
| 99 | const name = spec.slice(3); |
| 100 | let exportNames; |
| 101 | try { exportNames = await ctx.loadHost(name); } |
| 102 | catch (e) { failures.push(`host '${name}' failed to load: ${e?.message ?? e}`); continue; } |
no test coverage detected