Import Bytes with immutable backing
Date: 2026-06-24
Area: engine
The TC39 Import Bytes proposal (Stage 2.7) is supported. import x from "./f" with { type: "bytes" } — and the dynamic and import.defer forms — load the resolved file as raw bytes and expose a synthetic module with a single default export: a Uint8Array backed by an immutable ArrayBuffer. Bytes are preserved exactly (no UTF-8 decoding, no newline normalization), the attribute selects the loader regardless of file extension/MIME, and modules are cached by specifier plus type: "bytes". Named imports are rejected because the module declares only default; import.source of a bytes module is rejected because bytes are not a source-phase kind. The content provider gained a raw-bytes accessor (LoadContentBytes) so byte modules never round-trip through the UTF-8/source-line text path.
Import Bytes is enabled by default, not behind a flag. The flag in ADR 0064 exists because the JavaScript ModuleSource provider changes what a previously-working source-phase request resolves to; type: "bytes" is purely additive (it previously threw SyntaxError: Unsupported import attribute type), so no working program changes behavior and gating would only add surface.
For the default export to honor the proposal's "immutable ArrayBuffer" guarantee, the Immutable ArrayBuffers proposal's observable surface was completed as part of this work rather than staged: transferToImmutable already existed, but the immutable getter was missing and several write/detach paths were not enforced. Now the getter reports the state, an immutable buffer cannot be detached (transfer / transferToFixedLength / transferToImmutable throw on it), integer-indexed [[Set]] is a silent no-op (after the observable numeric coercion), integer-indexed [[DefineOwnProperty]] returns false, and DataView setters throw (TypedArray mutating methods and Atomics already threw). The trade-off: shipping bytes over the partial backing would have exported a Uint8Array that was immutable in name only — a DataView over its .buffer, or bytes.buffer.transfer(), could have rewritten or destroyed the bytes. See language.md and built-ins-binary-data.md.
A separate, pre-existing gap was found and left out of scope: the bytecode VM does not reject named imports that name a missing export (it binds undefined), whereas the interpreter throws "has no export named". This affects every module kind, not just bytes, and is tracked for a dedicated fix.