module-stage.js (4170B)
1 import { readList, readText, text } from './codecs.js'; 2 import { compilerDiagnostic, SOURCE_LIMIT } from './compiler.js'; 3 import { fetchBytes, decodeBundle } from './bundle.js'; 4 import { packageIndex } from './packages.js'; 5 import { localModuleInputs, validateLocalModule } from './local-modules.js'; 6 import { fetchModule } from './module-resolver.js'; 7 export function stageModule(graph, library, source) { 8 const call = (name, argument) => { 9 const f = library.get('source_module.' + name); 10 if (f === undefined) 11 throw new Error('Missing native module compiler helper'); 12 return graph.apply(f, [argument]); 13 }; 14 const value = (result, diagnostic = false) => { 15 const [tag, payload] = graph.pair(result), flag = graph.observe(tag).node; 16 if (flag.kind === 'leaf') 17 throw new Error(diagnostic ? compilerDiagnostic(graph, payload) : readText(graph, graph.pair(payload)[0], 512)); 18 if (flag.kind !== 'stem' || graph.observe(flag.child).node.kind !== 'leaf') 19 throw new Error('Invalid native module result'); 20 return graph.pair(payload)[0]; 21 }; 22 const [edges, rest] = graph.pair(value(call('sourceModuleHeaderStage', text(graph, source, SOURCE_LIMIT)), true)); 23 if (readList(graph, edges, SOURCE_LIMIT).length) 24 throw new Error('Browser modules require manifest hashes; workspace and build-only sources are unavailable'); 25 const [uses, tail] = graph.pair(rest), [exports, body] = graph.pair(tail); 26 const shapes = (rows, helper) => readList(graph, rows, SOURCE_LIMIT).map(row => { 27 const [a, b] = graph.pair(value(call(helper, row))); 28 return [readText(graph, a, 128), readText(graph, b, 128)]; 29 }); 30 const imports = shapes(uses, 'sourceModuleUseShape'), selected = shapes(exports, 'sourceModuleExportShape'); 31 return { body: readText(graph, body, SOURCE_LIMIT), imports, exports: selected, native: !!(imports.length || selected.length) }; 32 } 33 export async function moduleDependencies(base, stage, local = []) { 34 const refs = [...new Set([...stage.imports.map(i => i[0]), ...stage.exports.filter(e => e[0] === 'from').map(e => e[1])])]; 35 const result = new Map(); 36 if (!refs.length) 37 return result; 38 const available = new Map(localModuleInputs(local).map(entry => [entry.manifest, entry])); 39 let index; 40 for (const ref of refs) { 41 const entry = available.get(ref); 42 if (entry) { 43 const checked = await validateLocalModule(entry.bytes); 44 if (checked.manifest !== ref) 45 throw new Error('Local module manifest mismatch'); 46 result.set(ref, decodeBundle(checked.bytes)); 47 } 48 else { 49 index ??= packageIndex(JSON.parse(new TextDecoder('utf-8', { fatal: true }).decode(await fetchBytes(new URL('packages.json', base), 2_097_152)))); 50 result.set(ref, await fetchModule(base, ref, index)); 51 } 52 } 53 return result; 54 } 55 // Projection of already native-parsed export requests onto backend-owned trees. 56 // Keep executable trees as backend-owned handles during interface selection. 57 export function selectModuleExports(stage, locals, modules) { 58 const selected = new Map(); 59 const add = (name, tree) => { 60 if (selected.has(name)) 61 throw new Error('duplicate export'); 62 if (selected.size >= 4096) 63 throw new Error('Module export limit exceeded'); 64 selected.set(name, tree); 65 }; 66 for (const [name, ref] of stage.exports) { 67 if (name === 'from') { 68 const values = modules.get(ref); 69 if (!values) 70 throw new Error('Missing re-export dependency'); 71 for (const [key, value] of values) 72 add(key, value); 73 } 74 else if (name === '*') { 75 for (const [key, value] of locals) 76 add(key, value); 77 } 78 else { 79 if (!locals.has(name)) 80 throw new Error('undeclared local export'); 81 add(name, locals.get(name)); 82 } 83 } 84 if (!selected.size) 85 throw new Error('module requires an export'); 86 return selected; 87 }