Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 29 additions & 1 deletion libs/core/modules/map.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1972,7 +1972,7 @@ impl ModuleMap {
.unwrap()
.clone();
match state.phase {
ModuleImportPhase::Defer | ModuleImportPhase::Evaluation => {
ModuleImportPhase::Evaluation => {
// The top-level module from a dynamic import has been instantiated.
// Load is done.
let module_id =
Expand All @@ -1988,6 +1988,34 @@ impl ModuleMap {
state,
)?;
}
ModuleImportPhase::Defer => {
// For defer phase imports, the module is instantiated but NOT evaluated.
// V8 will handle creating the deferred namespace object that triggers
// evaluation on first property access.
// Requires V8 flag --js-defer-import-eval (enabled in runtime/setup.rs).
let module_id =
load.root_module_id.expect("Root module should be loaded");
let result = self.instantiate_module(scope, module_id);
if let Err(exception) = result {
self.dynamic_import_reject(scope, dyn_import_id, exception);
continue;
}
// Resolve with the module namespace without evaluating.
// V8's deferred namespace will trigger evaluation on property access.
let module_handle =
self.get_handle(module_id).expect("ModuleInfo not found");
let module = module_handle.open(scope);
let module_namespace = module.get_module_namespace();
let resolver_handle = self
.dynamic_import_map
.borrow_mut()
.remove(&dyn_import_id)
.expect("Invalid dynamic import id")
.resolver;
let resolver = resolver_handle.open(scope);
resolver.resolve(scope, module_namespace).unwrap();
scope.perform_microtask_checkpoint();
}
ModuleImportPhase::Source => {
let module_reference = load.root_module_reference.as_ref().expect("Root module reference had to have been resolved to get here.");
let key = ModuleSourceKey::from_reference(module_reference);
Expand Down
3 changes: 2 additions & 1 deletion libs/core/runtime/setup.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ fn v8_init(
" --harmony-temporal",
" --js-float16array",
" --js-explicit-resource-management",
" --js-source-phase-imports"
" --js-source-phase-imports",
" --js-defer-import-eval"
);
let snapshot_flags = "--predictable --random-seed=42";
let expose_natives_flags = "--expose_gc --allow_natives_syntax";
Expand Down
6 changes: 6 additions & 0 deletions libs/core_testing/integration/import_defer/deferred.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// Copyright 2018-2026 the Deno authors. MIT license.
console.log("deferred module evaluated");
export const value = 42;
export function add(a, b) {
return a + b;
}
3 changes: 3 additions & 0 deletions libs/core_testing/integration/import_defer/deferred2.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// Copyright 2018-2026 the Deno authors. MIT license.
console.log("deferred2 module evaluated");
export const value = 99;
35 changes: 35 additions & 0 deletions libs/core_testing/integration/import_defer/import_defer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// Copyright 2018-2026 the Deno authors. MIT license.

// Test for TC39 proposal: Deferred Module Evaluation
// https://github.com/tc39/proposal-defer-import-eval
//
// The `import defer` syntax allows loading a module without immediately
// executing it. The module is executed synchronously when any property
// on the namespace is first accessed.
//
console.log("before import defer");

// Static import defer syntax - module is loaded but not executed
import defer * as deferred from "./deferred.js";

console.log("after import defer, before access");

// First property access triggers module evaluation
console.log(`value: ${deferred.value}`);

console.log("after first access");

// Subsequent accesses use the already-evaluated module
console.log(`add: ${deferred.add(1, 2)}`);

console.log("after first access, before dynamic import defer");

// Dynamic import.defer syntax
const deferred2 = await import.defer("./deferred2.js");

console.log("after dynamic import defer, before access");

// First property access triggers evaluation
console.log(`deferred2 value: ${deferred2.value}`);

console.log("done");
11 changes: 11 additions & 0 deletions libs/core_testing/integration/import_defer/import_defer.out
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
before import defer
after import defer, before access
deferred module evaluated
value: 42
after first access
add: 3
after first access, before dynamic import defer
after dynamic import defer, before access
deferred2 module evaluated
deferred2 value: 99
done
1 change: 1 addition & 0 deletions libs/core_testing/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ integration_test!(
dyn_import_op,
dyn_import_no_hang,
dyn_import_pending_tla,
import_defer,
error_async_stack,
error_callsite,
error_non_existent_eval_source,
Expand Down
12 changes: 12 additions & 0 deletions tests/specs/run/import_defer/__test__.jsonc
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"tests": {
"static_defer": {
"args": "run main.js",
"output": "main.out"
},
"dynamic_defer": {
"args": "run dynamic.js",
"output": "dynamic.out"
}
}
}
5 changes: 5 additions & 0 deletions tests/specs/run/import_defer/deferred.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
console.log("deferred module evaluated");
export const value = 42;
export function add(a, b) {
return a + b;
}
14 changes: 14 additions & 0 deletions tests/specs/run/import_defer/dynamic.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
console.log("before dynamic import defer");

const deferred = await import.defer("./deferred.js");

console.log("after dynamic import defer, before access");

// First property access triggers module evaluation
console.log(`value: ${deferred.value}`);

console.log("after first access");

console.log(`add: ${deferred.add(1, 2)}`);

console.log("done");
7 changes: 7 additions & 0 deletions tests/specs/run/import_defer/dynamic.out
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
before dynamic import defer
after dynamic import defer, before access
deferred module evaluated
value: 42
after first access
add: 3
done
13 changes: 13 additions & 0 deletions tests/specs/run/import_defer/main.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import defer * as deferred from "./deferred.js";

console.log("before access");

// First property access triggers module evaluation
console.log(`value: ${deferred.value}`);

console.log("after first access");

// Subsequent accesses use the already-evaluated module
console.log(`add: ${deferred.add(1, 2)}`);

console.log("done");
6 changes: 6 additions & 0 deletions tests/specs/run/import_defer/main.out
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
before access
deferred module evaluated
value: 42
after first access
add: 3
done
Loading