From 996909ded582ed04065af4710d18b90d16e2024e Mon Sep 17 00:00:00 2001 From: Pierre Le Fevre Date: Thu, 28 May 2026 21:25:47 +0200 Subject: [PATCH] Resolve dynamic import() through the module registry (isu issue 304) Implements sub-step 4 of the document-level ES module work: dynamic `import(specifier)` now evaluates to a Promise for the target module's namespace object, resolved off the same loader that handles static imports. Engine (crates/js): - Parser routes `import(...)` (but not `import.meta`) to a new `__we_dynamic_import` runtime helper. - New `dynamic_import` module: the helper resolves the specifier against the document base URL, returns a pending promise, and queues it; the event loop (`pump`/`run_event_loop`) drains the queue, settling each promise from the shared `__we_modules` registry (resolve with the namespace when present, otherwise reject with a "failed to fetch dynamically imported module" reason). Pending promises are GC-rooted. - `module::dynamic_import_specifiers` best-effort-collects string-literal `import("")` specifiers from a program. Loader (crates/browser): - The module graph builder eagerly fetches and executes statically-discoverable dynamic-import targets as ordinary dependencies so the runtime import resolves from the populated registry. Tests: - Unit tests for promise resolution, the returned promise shape, rejection of unloaded modules, and literal specifier discovery. - e2e page + scenario (`dynamic_import.we`) loading a `data:` module via `import()` and asserting the resolved namespace's constant and a callable export render into the DOM. Known follow-ups (still tracked under #304/#280): import-map bare specifiers, module-relative (vs document-base) specifier resolution, classic-script dynamic-import preloading, and lazy (non-eager) fetching of runtime-computed specifiers. Co-Authored-By: Claude Opus 4.8 --- crates/browser/src/module_loader.rs | 28 ++- crates/e2e/pages/44_dynamic_import.html | 28 +++ crates/e2e/scenarios/dynamic_import.we | 18 ++ crates/js/src/builtins.rs | 4 + crates/js/src/dynamic_import.rs | 206 ++++++++++++++++++ crates/js/src/fetch.rs | 9 + crates/js/src/lib.rs | 1 + crates/js/src/module.rs | 269 +++++++++++++++++++++++- crates/js/src/parser.rs | 23 +- crates/js/src/vm.rs | 6 + 10 files changed, 580 insertions(+), 12 deletions(-) create mode 100644 crates/e2e/pages/44_dynamic_import.html create mode 100644 crates/e2e/scenarios/dynamic_import.we create mode 100644 crates/js/src/dynamic_import.rs diff --git a/crates/browser/src/module_loader.rs b/crates/browser/src/module_loader.rs index 26dbc84..0797736 100644 --- a/crates/browser/src/module_loader.rs +++ b/crates/browser/src/module_loader.rs @@ -13,7 +13,13 @@ //! Specifier resolution currently handles relative (`./`, `../`, `/`) and //! absolute-URL specifiers. Bare specifiers (which require an import map) are //! left unresolved; their imported bindings evaluate to `undefined`. Import -//! maps and dynamic `import()` are tracked as follow-up sub-steps. +//! maps are tracked as a follow-up sub-step. +//! +//! Statically-discoverable dynamic-import targets — `import("")` calls +//! whose specifier is a string literal — are fetched and executed eagerly as +//! ordinary graph dependencies so the runtime `import()` resolves the module +//! namespace from the populated registry (see [`we_js::dynamic_import`]). +//! Runtime-computed specifiers are not preloaded and reject at call time. use crate::loader::{Resource, ResourceLoader, ResourceRequestType}; use std::collections::HashMap; @@ -136,6 +142,26 @@ fn build_graph( } } + // Eagerly fetch and execute statically-discoverable dynamic-import + // targets so a later `import("")` resolves from the populated + // module registry. These are sequenced as ordinary dependencies (they + // run before the importing module) but, unlike static imports, are not + // added to `resolve`: dynamic import reads the registry at call time + // rather than binding names at link time. + for specifier in module::dynamic_import_specifiers(&program) { + if let Some(dep_url) = resolve_specifier(&specifier, &base) { + let dep_key = dep_url.serialize(); + if !deps.contains(&dep_key) { + deps.push(dep_key.clone()); + } + if !graph.contains_key(&dep_key) { + if let Some(dep_source) = fetch_module_source(loader, &dep_url, origin) { + pending.push((dep_key, dep_url, dep_source)); + } + } + } + } + graph.insert( key, ModuleRecord { diff --git a/crates/e2e/pages/44_dynamic_import.html b/crates/e2e/pages/44_dynamic_import.html new file mode 100644 index 0000000..29a6a75 --- /dev/null +++ b/crates/e2e/pages/44_dynamic_import.html @@ -0,0 +1,28 @@ + + + + Dynamic import() (isu issue 304) + + +
pending
+
sum-pending
+ + + + + diff --git a/crates/e2e/scenarios/dynamic_import.we b/crates/e2e/scenarios/dynamic_import.we new file mode 100644 index 0000000..b2902fb --- /dev/null +++ b/crates/e2e/scenarios/dynamic_import.we @@ -0,0 +1,18 @@ +# Regression scenario for isu issue 304: dynamic `import()`. +# A `