Skip to content

Commit 4ee79ae

Browse files
authored
fix module loader cache (#5278)
Fixes #5270. This makes SimpleModuleLoader cache modules with their import attributes instead of only by path. It also adds a regression test for the json case where the same path is imported once with attributes and later without them. Tests: - cargo test -p boa_engine --test imports -- --nocapture - cargo test -p boa_engine --test module -- --nocapture - cargo clippy -p boa_engine --all-features --all-targets -- -D warnings
1 parent 442b7fd commit 4ee79ae

File tree

3 files changed

+91
-12
lines changed

3 files changed

+91
-12
lines changed

core/engine/src/module/loader/mod.rs

Lines changed: 29 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,23 @@ impl ModuleLoader for MapModuleLoader {
287287
}
288288
}
289289

290+
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
291+
struct ModuleCacheKey {
292+
path: PathBuf,
293+
attributes: Box<[ImportAttribute]>,
294+
}
295+
296+
impl ModuleCacheKey {
297+
fn new(path: PathBuf, attributes: &[ImportAttribute]) -> Self {
298+
let mut attributes = attributes.to_vec();
299+
attributes.sort_unstable_by(|left, right| left.key().cmp(right.key()));
300+
Self {
301+
path,
302+
attributes: attributes.into_boxed_slice(),
303+
}
304+
}
305+
}
306+
290307
/// A simple module loader that loads modules relative to a root path.
291308
///
292309
/// # Note
@@ -297,7 +314,7 @@ impl ModuleLoader for MapModuleLoader {
297314
#[derive(Debug)]
298315
pub struct SimpleModuleLoader {
299316
root: PathBuf,
300-
module_map: GcRefCell<FxHashMap<PathBuf, Module>>,
317+
module_map: GcRefCell<FxHashMap<ModuleCacheKey, Module>>,
301318
}
302319

303320
impl SimpleModuleLoader {
@@ -323,38 +340,39 @@ impl SimpleModuleLoader {
323340
/// Inserts a new module onto the module map.
324341
#[inline]
325342
pub fn insert(&self, path: PathBuf, module: Module) {
326-
self.module_map.borrow_mut().insert(path, module);
343+
self.insert_with_attributes(path, &[], module);
327344
}
328345

329346
/// Inserts a new module onto the module map with the given attributes.
330-
///
331-
/// This is an alias for `insert` in this implementation, as it ignores attributes.
332347
#[inline]
333348
pub fn insert_with_attributes(
334349
&self,
335350
path: PathBuf,
336-
_attributes: &[ImportAttribute],
351+
attributes: &[ImportAttribute],
337352
module: Module,
338353
) {
339-
self.insert(path, module);
354+
self.module_map
355+
.borrow_mut()
356+
.insert(ModuleCacheKey::new(path, attributes), module);
340357
}
341358

342359
/// Gets a module from its original path.
343360
#[inline]
344361
pub fn get(&self, path: &Path) -> Option<Module> {
345-
self.module_map.borrow().get(path).cloned()
362+
self.get_with_attributes(path, &[])
346363
}
347364

348365
/// Gets a module from its original path and attributes.
349-
///
350-
/// This is an alias for `get` in this implementation, as it ignores attributes.
351366
#[inline]
352367
pub fn get_with_attributes(
353368
&self,
354369
path: &Path,
355-
_attributes: &[ImportAttribute],
370+
attributes: &[ImportAttribute],
356371
) -> Option<Module> {
357-
self.get(path)
372+
self.module_map
373+
.borrow()
374+
.get(&ModuleCacheKey::new(path.to_path_buf(), attributes))
375+
.cloned()
358376
}
359377
}
360378

core/engine/tests/assets/data.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{ "ok": true }

core/engine/tests/imports.rs

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use std::rc::Rc;
55

66
use boa_engine::builtins::promise::PromiseState;
77
use boa_engine::module::SimpleModuleLoader;
8-
use boa_engine::{Context, JsValue, Source, js_string};
8+
use boa_engine::{Context, JsString, JsValue, Source, js_string};
99

1010
/// Test that relative imports work with the simple module loader.
1111
#[test]
@@ -45,3 +45,63 @@ fn subdirectories() {
4545
}
4646
}
4747
}
48+
49+
#[test]
50+
fn json_import_attributes_are_part_of_the_cache_key() {
51+
let assets_dir =
52+
PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap()).join("tests/assets");
53+
54+
let loader = Rc::new(SimpleModuleLoader::new(assets_dir).unwrap());
55+
let mut context = Context::builder()
56+
.module_loader(loader.clone())
57+
.build()
58+
.unwrap();
59+
60+
let source = Source::from_bytes(
61+
b"
62+
import json from 'data.json' with { type: 'json' };
63+
export let value = json;
64+
export let p = import('data.json');
65+
",
66+
);
67+
let module = boa_engine::Module::parse(source, None, &mut context).unwrap();
68+
let result = module.load_link_evaluate(&mut context);
69+
70+
context.run_jobs().unwrap();
71+
assert_eq!(
72+
result.state(),
73+
PromiseState::Fulfilled(JsValue::undefined())
74+
);
75+
76+
let value = module
77+
.namespace(&mut context)
78+
.get(js_string!("value"), &mut context)
79+
.unwrap();
80+
assert_eq!(
81+
JsString::from(value.to_json(&mut context).unwrap().unwrap().to_string()),
82+
js_string!(r#"{"ok":true}"#)
83+
);
84+
85+
let p = module
86+
.namespace(&mut context)
87+
.get(js_string!("p"), &mut context)
88+
.unwrap();
89+
let p_obj = p.as_promise().unwrap();
90+
context.run_jobs().unwrap();
91+
92+
match p_obj.state() {
93+
PromiseState::Rejected(e) => {
94+
let error = e
95+
.as_object()
96+
.expect("expected rejection to be an Error object");
97+
let name = error.get(js_string!("name"), &mut context).unwrap();
98+
assert_eq!(name.as_string().unwrap(), js_string!("TypeError"));
99+
let message = error.get(js_string!("message"), &mut context).unwrap();
100+
assert_eq!(
101+
message.as_string().unwrap(),
102+
js_string!("module `data.json` needs an import attribute of type \"json\"")
103+
);
104+
}
105+
state => panic!("expected dynamic import to reject, got {state:?}"),
106+
}
107+
}

0 commit comments

Comments
 (0)