Repository navigation
feat(unstable): support TC39 import defer proposal - #32360
Merged
Merged
Conversation
Adds support for the TC39 "Deferred Module Evaluation" proposal (https://github.com/tc39/proposal-defer-import-eval, Stage 3). Changes: - Enable V8 `--js-defer-import-eval` flag for parser/runtime support - Separate `ModuleImportPhase::Defer` handling from `Evaluation` phase: deferred modules are instantiated but not evaluated, letting V8 create a deferred namespace that triggers evaluation on first property access - Add integration test validating deferred evaluation semantics Ported from denoland/deno_core#1280 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2 tasks done
devsnek
approved these changes
Mar 3, 2026
Member
Author
|
Waiting on v8/v8@f2016e1 in V8 14.7. |
This was referenced Apr 19, 2026
Merged
Upgrade rusty_v8 to 147.3.0 which includes the Module::EvaluateForImportDefer and GetModuleNamespace with phase APIs needed for dynamic import.defer() support. Fix merge conflict in map.rs: restore separate Defer arm in dynamic import handling (must NOT call dynamic_import_module_evaluate since defer imports should not be evaluated until namespace access). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Point to local deno_graph (with ImportDefer variants) and deno_doc. Add ImportDefer handling to unfurl.rs. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Use the new V8 Module::evaluate_for_import_defer API for dynamic import.defer() to properly gather async transitive dependencies before resolving. Get the deferred namespace via get_module_namespace_with_phase (kDefer) so V8 creates a namespace that triggers evaluation on first property access. Handles three promise states: - Fulfilled: all async deps ready, resolve immediately with deferred ns - Rejected: propagate error to dynamic import reject - Pending: store for later resolution via pending_dyn_mod_evaluations Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Filter TS18060 diagnostic ("Deferred imports are only supported when
module is esnext or preserve") since Deno handles module resolution
natively and supports import defer.
- Add test for running import defer from TypeScript files.
- Add test for type-checking import defer TypeScript files.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace local path overrides with published crate versions that include import defer support. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…o feat/import-defer-eval
1 task done
Jarred-Sumner
pushed a commit
to oven-sh/bun
that referenced
this pull request
May 19, 2026
Implements the static form of the Stage 3 [Deferred Module Evaluation](https://github.com/tc39/proposal-defer-import-eval) proposal: ```js import defer * as ns from "./mod.js"; // mod.js is fetched and linked now, but NOT evaluated. console.log("before"); console.log(ns.value); // <-- mod.js evaluates here, synchronously ``` JavaScriptCore already carries the full runtime for this (landed upstream in [WebKit/WebKit#64053](WebKit/WebKit#64053) and present in Bun's pinned WebKit `3167a44fb9`). This PR threads the `ModulePhase::Defer` flag through Bun's own parser → printer → `JSModuleRecord` bridge so JSC actually receives it, and flips on `useImportDefer`. > Supersedes #30015, which was written before the WebKit bump and gated everything behind `#ifdef JSC_HAS_IMPORT_DEFER` pending a separate `oven-sh/WebKit` patch. That indirection is no longer needed. This PR is also narrower in scope — it intentionally does **not** implement dynamic `import.defer()`. ### What changed **Parser / AST** — `src/js_parser/parse/parse_stmt.rs`, `src/ast/{s,import_record}.rs` - `defer` is a contextual phase keyword only when immediately followed by `*`. In every other position (`import defer from "x"`, `import defer, {a} from "x"`, `import { defer } from "x"`) it remains an ordinary identifier. - New `S::Import.phase_defer` and `ImportRecord::Flags::PHASE_DEFER`. **Printer / ModuleInfo** — `src/js_printer/lib.rs` - Emits `import defer` so JSC's own parser sees it during bytecode generation. - New `RecordKind::ImportInfoNamespaceDefer`; `requested_modules` now dedups by `(specifier, phase)` — matching JSC's `ModuleAnalyzer::appendRequestedModule` — and serialises a parallel `u8` phase array. Transpiler-cache version bumped (20 → 21) for the format change. - `finalize()`'s local→indirect export rewrite intentionally ignores deferred namespace imports so `import defer * as ns; export { ns }` stays a **local** export, matching `ModuleAnalyzer::exportVariable`. **JSC bridge** — `src/bundler/analyze_transpiled_module.rs`, `src/bundler_jsc/analyze_jsc.rs`, `src/jsc/bindings/BunAnalyzeTranspiledModule.cpp` - `appendRequestedModule(..., ModulePhase::Defer)` and `addImportEntry({ Namespace, phase: Defer, ... })` for deferred requests. - `dumpRecordInfo` includes phase so the `BUN_DEBUG` fallback-parse diff stays in agreement. **Runtime** — `src/jsc/bindings/ZigGlobalObject.cpp` - `JSC::Options::useImportDefer() = true`. **Unrelated build fix** — `src/jsc/bindings/wtf-bindings.cpp` - `wtf/Int128.h` dropped its transitive `<cassert>` in the latest WebKit bump, which was the only thing declaring `assert()` for `uv__tty_make_raw()` in that unified-build unit. Added the include directly. ### Tests (`test/js/bun/resolve/import-defer.test.ts`, 16 cases) - Evaluation deferred until first non-symbol property access; no re-eval on subsequent access - `ns[Symbol.toStringTag] === "Deferred Module"` without triggering evaluation - `in` / `Object.keys` trigger evaluation - Throwing module re-throws the same error on every access - Same specifier imported at both Evaluation and Defer phase - Transitive TLA dep evaluated eagerly (`GatherAsynchronousTransitiveDependencies`) so on-access eval stays synchronous - `import defer * as ns; export { ns }` exports the deferred namespace as a local binding - `import defer * as d from "./x.json" with { type: "json" }` - Works in `.ts` - `defer` still a valid identifier (`import defer from`, `import defer, {x} from`, `import { defer } from`) - `Bun.Transpiler` round-trips `import defer` - `import defer { x } from` / `import defer x from` are syntax errors ### Out of scope - Dynamic `import.defer(...)` (left for a follow-up; JSC's upstream PR also excluded it). - Bundler-side lazy-wrapping: when `bun build` inlines a deferred dependency into the same chunk the defer semantics are lost (module body runs at chunk load). External imports (`--external`) correctly preserve `import defer` in the output. ### References - Proposal: https://github.com/tc39/proposal-defer-import-eval - Spec: https://tc39.es/proposal-defer-import-eval/ - WebKit runtime: WebKit/WebKit#64053 - Deno: denoland/deno#32360 --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
liooil
pushed a commit
to liooil/poly
that referenced
this pull request
Aug 7, 2026
Implements the static form of the Stage 3 [Deferred Module Evaluation](https://github.com/tc39/proposal-defer-import-eval) proposal: ```js import defer * as ns from "./mod.js"; // mod.js is fetched and linked now, but NOT evaluated. console.log("before"); console.log(ns.value); // <-- mod.js evaluates here, synchronously ``` JavaScriptCore already carries the full runtime for this (landed upstream in [WebKit/WebKit#64053](WebKit/WebKit#64053) and present in Bun's pinned WebKit `3167a44fb9`). This PR threads the `ModulePhase::Defer` flag through Bun's own parser → printer → `JSModuleRecord` bridge so JSC actually receives it, and flips on `useImportDefer`. > Supersedes #30015, which was written before the WebKit bump and gated everything behind `#ifdef JSC_HAS_IMPORT_DEFER` pending a separate `oven-sh/WebKit` patch. That indirection is no longer needed. This PR is also narrower in scope — it intentionally does **not** implement dynamic `import.defer()`. ### What changed **Parser / AST** — `src/js_parser/parse/parse_stmt.rs`, `src/ast/{s,import_record}.rs` - `defer` is a contextual phase keyword only when immediately followed by `*`. In every other position (`import defer from "x"`, `import defer, {a} from "x"`, `import { defer } from "x"`) it remains an ordinary identifier. - New `S::Import.phase_defer` and `ImportRecord::Flags::PHASE_DEFER`. **Printer / ModuleInfo** — `src/js_printer/lib.rs` - Emits `import defer` so JSC's own parser sees it during bytecode generation. - New `RecordKind::ImportInfoNamespaceDefer`; `requested_modules` now dedups by `(specifier, phase)` — matching JSC's `ModuleAnalyzer::appendRequestedModule` — and serialises a parallel `u8` phase array. Transpiler-cache version bumped (20 → 21) for the format change. - `finalize()`'s local→indirect export rewrite intentionally ignores deferred namespace imports so `import defer * as ns; export { ns }` stays a **local** export, matching `ModuleAnalyzer::exportVariable`. **JSC bridge** — `src/bundler/analyze_transpiled_module.rs`, `src/bundler_jsc/analyze_jsc.rs`, `src/jsc/bindings/BunAnalyzeTranspiledModule.cpp` - `appendRequestedModule(..., ModulePhase::Defer)` and `addImportEntry({ Namespace, phase: Defer, ... })` for deferred requests. - `dumpRecordInfo` includes phase so the `BUN_DEBUG` fallback-parse diff stays in agreement. **Runtime** — `src/jsc/bindings/ZigGlobalObject.cpp` - `JSC::Options::useImportDefer() = true`. **Unrelated build fix** — `src/jsc/bindings/wtf-bindings.cpp` - `wtf/Int128.h` dropped its transitive `<cassert>` in the latest WebKit bump, which was the only thing declaring `assert()` for `uv__tty_make_raw()` in that unified-build unit. Added the include directly. ### Tests (`test/js/bun/resolve/import-defer.test.ts`, 16 cases) - Evaluation deferred until first non-symbol property access; no re-eval on subsequent access - `ns[Symbol.toStringTag] === "Deferred Module"` without triggering evaluation - `in` / `Object.keys` trigger evaluation - Throwing module re-throws the same error on every access - Same specifier imported at both Evaluation and Defer phase - Transitive TLA dep evaluated eagerly (`GatherAsynchronousTransitiveDependencies`) so on-access eval stays synchronous - `import defer * as ns; export { ns }` exports the deferred namespace as a local binding - `import defer * as d from "./x.json" with { type: "json" }` - Works in `.ts` - `defer` still a valid identifier (`import defer from`, `import defer, {x} from`, `import { defer } from`) - `Bun.Transpiler` round-trips `import defer` - `import defer { x } from` / `import defer x from` are syntax errors ### Out of scope - Dynamic `import.defer(...)` (left for a follow-up; JSC's upstream PR also excluded it). - Bundler-side lazy-wrapping: when `bun build` inlines a deferred dependency into the same chunk the defer semantics are lost (module body runs at chunk load). External imports (`--external`) correctly preserve `import defer` in the output. ### References - Proposal: https://github.com/tc39/proposal-defer-import-eval - Spec: https://tc39.es/proposal-defer-import-eval/ - WebKit runtime: WebKit/WebKit#64053 - Deno: denoland/deno#32360 --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This commit adds an experimental support for
import defer ...syntax,for https://github.com/tc39/proposal-defer-import-eval proposal.
Closes #30053