Browse documentation

Virtual Module Configuration

Virtual modules let a host supply ECMAScript modules without writing them to the ambient filesystem. Use them for host-provided application dependencies; use global injection only when application code must consume an implicit global binding.

Virtual modules use the ordinary module loader. Static and dynamic imports, import attributes, Import Bytes, source and defer phases, relative imports, import.meta, module caching, the interpreter, and the bytecode VM all follow the same paths as filesystem modules where the module kind and enabled flags support them.

Definition Schema

A module map is an object whose keys are module addresses and whose values are descriptors. Every descriptor has content and may have type:

{
"host:settings": { "type": "json", "content": { "version": 3 } },
"host:database": { "content": "export const connect = () => 'ok';" },
"host:asset": { "type": "bytes", "content": "AQID" }
}
TypeContent
javascriptECMAScript module source; this is the default
typescriptAlias for javascript
jsonAny JSON value
textUTF-8 text exposed as the default export
bytesBase64 decoded and exposed as an immutable byte array

Descriptor structure and base64 are validated while configuration is applied. JavaScript and TypeScript source is parsed and linked only when the module is loaded.

CLI and Config Files

Use --module for an inline definition:

./build/GocciaScriptLoader app.mjs \
--module='host:config=export const answer = 42;'
./build/GocciaScriptLoader app.mjs \
--module='host:data={"type":"json","content":{"answer":42}}'

Use repeatable --modules options for bulk manifests:

./build/GocciaScriptLoader app.mjs \
--modules=modules.json \
--modules=generated-modules.ts

Bulk manifests may be JSON, JSON5, TOML, YAML, JavaScript, or TypeScript. JavaScript and TypeScript manifests must default-export the module map. A manifest is evaluated before its definitions are registered, so it cannot import modules that it defines itself.

Project config may contain a module map directly:

{
"modules": {
"host:config": { "content": "export const answer = 42;" }
}
}

The singular module config key accepts one inline definition or an array of definitions. The modules key also accepts one manifest path or an array of paths. Paths and relative definition addresses are resolved from the config or manifest that declares them, including declarations inherited through extends.

Configuration precedence is CLI, then per-file config, then root config. Within one level, a later definition may replace an earlier definition until the module is first loaded.

The shared CLI hosts expose these options. GocciaSandboxRunner applies them after installing its runtime modules, so attempts to shadow fs or goccia are configuration errors.

Resolution and Collisions

Configured names are canonical module addresses. Relative imports inside a virtual JavaScript module resolve from that module's address. import.meta.url returns the canonical address, and import.meta.resolve() uses the same loader resolution seam.

Virtual definitions take precedence over import maps and filesystem modules. When both a virtual definition and filesystem resolution match, the loader emits a warning and uses the virtual definition. A collision with a registered host module or goccia: runtime module is a configuration error.

ShadowRealm children inherit the definition set but create separate module records, values, evaluation state, and caches.

Embedding API

Register one definition directly:

Engine.InjectModule('host:database',
'export const connect = () => "connected";');

Register a module map from serialized configuration:

Engine.InjectModulesFromJSON(
'{"host:settings":{"type":"json","content":{"version":3}}}');

InjectModule accepts optional content type and base-address arguments. InjectModulesFromJSON, InjectModulesFromJSON5, InjectModulesFromTOML, and InjectModulesFromYAML accept an optional base address. Strict JSON is part of the core engine; the other formats require their matching runtime extensions. InjectModulesFromModule loads a JavaScript or TypeScript default-exported map.

Programmatically registered host modules use RegisterHostModule or RegisterHostModuleProvider. The historical RegisterGlobalModule names remain supported as compatibility aliases without warnings.

See ADR 0094 for the architectural decision that keeps virtual modules inside the ordinary loader.