mirror of
https://github.com/lexmount/moli.git
synced 2026-10-26 08:00:52 +00:00
202 lines
7.3 KiB
Rust
202 lines
7.3 KiB
Rust
//! Import map registration and module specifier resolution.
|
|
//!
|
|
//! This crate stores and merges import maps, tracks already-resolved module
|
|
//! specifiers, and resolves module specifiers against document/module base URLs
|
|
//! without depending on the V8 module runtime.
|
|
|
|
mod helpers;
|
|
mod import_map;
|
|
mod registry;
|
|
mod types;
|
|
|
|
pub use helpers::resolve_url_like_module_specifier;
|
|
pub use types::ImportMapRegistryState;
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use url::Url;
|
|
|
|
use crate::ImportMapRegistryState;
|
|
|
|
#[test]
|
|
fn registry_unifies_registration_and_resolution() {
|
|
let base_url = Url::parse("https://example.test/app/index.html").unwrap();
|
|
let mut registry = ImportMapRegistryState::default();
|
|
registry
|
|
.register_import_map(r#"{"imports":{"fixture":"/mapped.mjs"}}"#, &base_url)
|
|
.expect("initial import map should register");
|
|
|
|
let resolved = registry
|
|
.resolve_module_specifier("fixture", &base_url)
|
|
.expect("mapped specifier should resolve");
|
|
assert_eq!(resolved.as_str(), "https://example.test/mapped.mjs");
|
|
}
|
|
|
|
#[test]
|
|
fn later_import_maps_add_unresolved_specifiers_after_module_resolution_starts() {
|
|
let base_url = Url::parse("https://example.test/app/index.html").unwrap();
|
|
let mut registry = ImportMapRegistryState::default();
|
|
let first = registry
|
|
.resolve_module_specifier("./already-resolved.mjs", &base_url)
|
|
.expect("module resolution should start before the later import map");
|
|
assert_eq!(
|
|
first.as_str(),
|
|
"https://example.test/app/already-resolved.mjs"
|
|
);
|
|
|
|
registry
|
|
.register_import_map(r#"{"imports":{"late":"/late.mjs"}}"#, &base_url)
|
|
.expect("later import map should merge");
|
|
let late = registry
|
|
.resolve_module_specifier("late", &base_url)
|
|
.expect("a previously unresolved specifier should use the later import map");
|
|
assert_eq!(late.as_str(), "https://example.test/late.mjs");
|
|
}
|
|
|
|
#[test]
|
|
fn resolved_modules_are_not_remapped_by_late_import_maps() {
|
|
let base_url = Url::parse("https://example.test/app/index.html").unwrap();
|
|
let mut registry = ImportMapRegistryState::default();
|
|
|
|
let first = registry
|
|
.resolve_module_specifier("./mod.mjs", &base_url)
|
|
.expect("relative module should resolve without import map");
|
|
assert_eq!(first.as_str(), "https://example.test/app/mod.mjs");
|
|
|
|
registry
|
|
.register_import_map(
|
|
r#"{"imports":{"./mod.mjs":"/replacement.mjs","new":"/new.mjs"}}"#,
|
|
&base_url,
|
|
)
|
|
.expect("later import map should merge");
|
|
|
|
let second = registry
|
|
.resolve_module_specifier("./mod.mjs", &base_url)
|
|
.expect("previously resolved module should keep original resolution");
|
|
assert_eq!(second.as_str(), "https://example.test/app/mod.mjs");
|
|
assert_eq!(
|
|
registry
|
|
.resolve_module_specifier("new", &base_url)
|
|
.expect("late import map should add unresolved specifiers")
|
|
.as_str(),
|
|
"https://example.test/new.mjs"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn registry_uses_import_map_crate_for_scopes_prefixes_and_null_entries() {
|
|
let base_url = Url::parse("https://example.test/app/index.html").unwrap();
|
|
let scoped_referrer = Url::parse("https://example.test/scope/main.mjs").unwrap();
|
|
let other_referrer = Url::parse("https://example.test/other/main.mjs").unwrap();
|
|
let mut registry = ImportMapRegistryState::default();
|
|
|
|
registry
|
|
.register_import_map(
|
|
r#"{
|
|
"imports": {
|
|
"blocked": null,
|
|
"pkg/": "/packages/pkg/"
|
|
},
|
|
"scopes": {
|
|
"/scope/": {
|
|
"dep": "/scope/dep.mjs"
|
|
}
|
|
}
|
|
}"#,
|
|
&base_url,
|
|
)
|
|
.expect("import map should register");
|
|
|
|
let prefixed = registry
|
|
.resolve_module_specifier("pkg/component.mjs", &base_url)
|
|
.expect("package prefix should resolve");
|
|
assert_eq!(
|
|
prefixed.as_str(),
|
|
"https://example.test/packages/pkg/component.mjs"
|
|
);
|
|
|
|
let scoped = registry
|
|
.resolve_module_specifier("dep", &scoped_referrer)
|
|
.expect("scoped bare specifier should resolve");
|
|
assert_eq!(scoped.as_str(), "https://example.test/scope/dep.mjs");
|
|
|
|
assert!(
|
|
registry
|
|
.resolve_module_specifier("dep", &other_referrer)
|
|
.is_err()
|
|
);
|
|
assert!(
|
|
registry
|
|
.resolve_module_specifier("blocked", &base_url)
|
|
.expect_err("null import map entries should block resolution")
|
|
.contains("Blocked by null entry")
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn registry_resolves_import_map_integrity_by_normalized_url() {
|
|
let base_url = Url::parse("https://example.test/app/index.html").unwrap();
|
|
let mut registry = ImportMapRegistryState::default();
|
|
|
|
registry
|
|
.register_import_map(
|
|
r#"{
|
|
"imports": { "entry": "/entry.mjs" },
|
|
"integrity": {
|
|
"/entry.mjs": "sha384-entry",
|
|
"./dep.mjs": "sha384-dep",
|
|
"bare": "sha384-ignored",
|
|
"/not-string.mjs": 7
|
|
}
|
|
}"#,
|
|
&base_url,
|
|
)
|
|
.expect("import map with integrity should register");
|
|
|
|
assert_eq!(
|
|
registry
|
|
.resolve_module_integrity(&Url::parse("https://example.test/entry.mjs").unwrap())
|
|
.as_deref(),
|
|
Some("sha384-entry")
|
|
);
|
|
assert_eq!(
|
|
registry
|
|
.resolve_module_integrity(&Url::parse("https://example.test/app/dep.mjs").unwrap())
|
|
.as_deref(),
|
|
Some("sha384-dep")
|
|
);
|
|
assert!(
|
|
registry
|
|
.resolve_module_integrity(&Url::parse("https://example.test/app/bare").unwrap())
|
|
.is_none()
|
|
);
|
|
assert!(
|
|
registry
|
|
.resolve_module_integrity(
|
|
&Url::parse("https://example.test/not-string.mjs").unwrap()
|
|
)
|
|
.is_none()
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn registry_keeps_first_import_map_integrity_rule_on_conflict() {
|
|
let base_url = Url::parse("https://example.test/app/index.html").unwrap();
|
|
let mut registry = ImportMapRegistryState::default();
|
|
|
|
registry
|
|
.register_import_map(r#"{"integrity":{"/entry.mjs":"sha384-first"}}"#, &base_url)
|
|
.expect("first integrity rule should register");
|
|
registry
|
|
.register_import_map(r#"{"integrity":{"/entry.mjs":"sha384-second"}}"#, &base_url)
|
|
.expect("conflicting integrity rule should not fail registration");
|
|
|
|
assert_eq!(
|
|
registry
|
|
.resolve_module_integrity(&Url::parse("https://example.test/entry.mjs").unwrap())
|
|
.as_deref(),
|
|
Some("sha384-first")
|
|
);
|
|
}
|
|
}
|