Refactor module resolving (#2493)

Adds ModuleSpecifier, which wraps a URL. This is now passed around instead of
specifier and resolver strings.
This commit is contained in:
Bartek Iwańczuk 2019-06-12 21:00:08 +02:00 committed by Ryan Dahl
parent 2a5138a516
commit b3c4307d02
8 changed files with 518 additions and 542 deletions

View File

@ -247,8 +247,8 @@ mod tests {
fn test_compile_sync() {
tokio_util::init(|| {
let specifier = "./tests/002_hello.ts";
use crate::worker;
let module_name = worker::root_specifier_to_url(specifier)
use crate::module_specifier::ModuleSpecifier;
let module_name = ModuleSpecifier::resolve_root(specifier)
.unwrap()
.to_string();
@ -294,8 +294,8 @@ mod tests {
#[test]
fn test_bundle_async() {
let specifier = "./tests/002_hello.ts";
use crate::worker;
let module_name = worker::root_specifier_to_url(specifier)
use crate::module_specifier::ModuleSpecifier;
let module_name = ModuleSpecifier::resolve_root(specifier)
.unwrap()
.to_string();

View File

@ -502,8 +502,8 @@ pub enum DenoSubcommand {
}
fn get_default_bundle_filename(source_file: &str) -> String {
use crate::worker::root_specifier_to_url;
let url = root_specifier_to_url(source_file).unwrap();
use crate::module_specifier::ModuleSpecifier;
let url = ModuleSpecifier::resolve_root(source_file).unwrap().to_url();
let path_segments = url.path_segments().unwrap();
let last = path_segments.last().unwrap();
String::from(last.trim_end_matches(".ts").trim_end_matches(".js"))

View File

@ -1,3 +1,4 @@
use crate::module_specifier::ModuleSpecifier;
use indexmap::IndexMap;
use serde_json::Map;
use serde_json::Value;
@ -22,7 +23,7 @@ impl ImportMapError {
// can't resolve URL with other schemes (eg. data:, about:, blob:)
const SUPPORTED_FETCH_SCHEMES: [&str; 3] = ["http", "https", "file"];
type SpecifierMap = IndexMap<String, Vec<String>>;
type SpecifierMap = IndexMap<String, Vec<ModuleSpecifier>>;
type ScopesMap = IndexMap<String, SpecifierMap>;
#[derive(Debug)]
@ -157,8 +158,8 @@ impl ImportMap {
specifier_key: &str,
base_url: &str,
potential_addresses: Vec<String>,
) -> Vec<String> {
let mut normalized_addresses: Vec<String> = vec![];
) -> Vec<ModuleSpecifier> {
let mut normalized_addresses: Vec<ModuleSpecifier> = vec![];
for potential_address in potential_addresses {
let url =
@ -177,7 +178,9 @@ impl ImportMap {
continue;
}
normalized_addresses.push(url_string);
let normalized_address = ModuleSpecifier::resolve(&url_string, ".")
.expect("Address should be valid module specifier");
normalized_addresses.push(normalized_address);
}
normalized_addresses
@ -311,7 +314,7 @@ impl ImportMap {
scopes: &ScopesMap,
normalized_specifier: &str,
referrer: &str,
) -> Result<Option<String>, ImportMapError> {
) -> Result<Option<ModuleSpecifier>, ImportMapError> {
// exact-match
if let Some(scope_imports) = scopes.get(referrer) {
if let Ok(scope_match) =
@ -347,7 +350,7 @@ impl ImportMap {
pub fn resolve_imports_match(
imports: &SpecifierMap,
normalized_specifier: &str,
) -> Result<Option<String>, ImportMapError> {
) -> Result<Option<ModuleSpecifier>, ImportMapError> {
// exact-match
if let Some(address_vec) = imports.get(normalized_specifier) {
if address_vec.is_empty() {
@ -361,7 +364,7 @@ impl ImportMap {
"Specifier {:?} was mapped to {:?}.",
normalized_specifier, address
);
return Ok(Some(address.to_string()));
return Ok(Some(address.clone()));
} else {
return Err(ImportMapError::new(
"Multi-address mappings are not yet supported",
@ -383,12 +386,10 @@ impl ImportMap {
let address = address_vec.first().unwrap();
let after_prefix = &normalized_specifier[specifier_key.len()..];
if let Ok(base_url) = Url::parse(address) {
if let Ok(url) = base_url.join(after_prefix) {
let resolved_url = url.to_string();
debug!("Specifier {:?} was mapped to {:?} (via prefix specifier key {:?}).", normalized_specifier, resolved_url, address);
return Ok(Some(resolved_url));
}
let base_url = address.to_url();
if let Ok(url) = base_url.join(after_prefix) {
debug!("Specifier {:?} was mapped to {:?} (via prefix specifier key {:?}).", normalized_specifier, url, address);
return Ok(Some(ModuleSpecifier::from(url)));
}
unreachable!();
@ -420,7 +421,7 @@ impl ImportMap {
&self,
specifier: &str,
referrer: &str,
) -> Result<Option<String>, ImportMapError> {
) -> Result<Option<ModuleSpecifier>, ImportMapError> {
let resolved_url: Option<Url> =
ImportMap::try_url_like_specifier(specifier, referrer);
let normalized_specifier = match &resolved_url {
@ -449,7 +450,7 @@ impl ImportMap {
// no match in import map but we got resolvable URL
if let Some(resolved_url) = resolved_url {
return Ok(Some(resolved_url.to_string()));
return Ok(Some(ModuleSpecifier::from(resolved_url)));
}
Err(ImportMapError::new(&format!(
@ -1250,43 +1251,52 @@ mod tests {
}
}
fn assert_resolve(
result: Result<Option<ModuleSpecifier>, ImportMapError>,
expected_url: &str,
) {
let maybe_url = result
.unwrap_or_else(|err| panic!("ImportMap::resolve failed: {:?}", err));
let resolved_url =
maybe_url.unwrap_or_else(|| panic!("Unexpected None resolved URL"));
assert_eq!(resolved_url, expected_url.to_string());
}
#[test]
fn resolve_unmapped_relative_specifiers() {
let referrer_url = "https://example.com/js/script.ts";
let import_map = get_empty_import_map();
// Should resolve ./ specifiers as URLs.
assert_eq!(
import_map.resolve("./foo", referrer_url).unwrap(),
Some("https://example.com/js/foo".to_string())
assert_resolve(
import_map.resolve("./foo", referrer_url),
"https://example.com/js/foo",
);
assert_eq!(
import_map.resolve("./foo/bar", referrer_url).unwrap(),
Some("https://example.com/js/foo/bar".to_string())
assert_resolve(
import_map.resolve("./foo/bar", referrer_url),
"https://example.com/js/foo/bar",
);
assert_eq!(
import_map.resolve("./foo/../bar", referrer_url).unwrap(),
Some("https://example.com/js/bar".to_string())
assert_resolve(
import_map.resolve("./foo/../bar", referrer_url),
"https://example.com/js/bar",
);
assert_eq!(
import_map.resolve("./foo/../../bar", referrer_url).unwrap(),
Some("https://example.com/bar".to_string())
assert_resolve(
import_map.resolve("./foo/../../bar", referrer_url),
"https://example.com/bar",
);
// Should resolve ../ specifiers as URLs.
assert_eq!(
import_map.resolve("../foo", referrer_url).unwrap(),
Some("https://example.com/foo".to_string())
assert_resolve(
import_map.resolve("../foo", referrer_url),
"https://example.com/foo",
);
assert_eq!(
import_map.resolve("../foo/bar", referrer_url).unwrap(),
Some("https://example.com/foo/bar".to_string())
assert_resolve(
import_map.resolve("../foo/bar", referrer_url),
"https://example.com/foo/bar",
);
assert_eq!(
import_map
.resolve("../../../foo/bar", referrer_url)
.unwrap(),
Some("https://example.com/foo/bar".to_string())
assert_resolve(
import_map.resolve("../../../foo/bar", referrer_url),
"https://example.com/foo/bar",
);
}
@ -1296,47 +1306,39 @@ mod tests {
let import_map = get_empty_import_map();
// Should resolve / specifiers as URLs.
assert_eq!(
import_map.resolve("/foo", referrer_url).unwrap(),
Some("https://example.com/foo".to_string())
assert_resolve(
import_map.resolve("/foo", referrer_url),
"https://example.com/foo",
);
assert_eq!(
import_map.resolve("/foo/bar", referrer_url).unwrap(),
Some("https://example.com/foo/bar".to_string())
assert_resolve(
import_map.resolve("/foo/bar", referrer_url),
"https://example.com/foo/bar",
);
assert_eq!(
import_map.resolve("../../foo/bar", referrer_url).unwrap(),
Some("https://example.com/foo/bar".to_string())
assert_resolve(
import_map.resolve("../../foo/bar", referrer_url),
"https://example.com/foo/bar",
);
assert_eq!(
import_map.resolve("/../foo/../bar", referrer_url).unwrap(),
Some("https://example.com/bar".to_string())
assert_resolve(
import_map.resolve("/../foo/../bar", referrer_url),
"https://example.com/bar",
);
// Should parse absolute fetch-scheme URLs.
assert_eq!(
import_map
.resolve("https://example.net", referrer_url)
.unwrap(),
Some("https://example.net/".to_string())
assert_resolve(
import_map.resolve("https://example.net", referrer_url),
"https://example.net/",
);
assert_eq!(
import_map
.resolve("https://ex%41mple.com/", referrer_url)
.unwrap(),
Some("https://example.com/".to_string())
assert_resolve(
import_map.resolve("https://ex%41mple.com/", referrer_url),
"https://example.com/",
);
assert_eq!(
import_map
.resolve("https:example.org", referrer_url)
.unwrap(),
Some("https://example.org/".to_string())
assert_resolve(
import_map.resolve("https:example.org", referrer_url),
"https://example.org/",
);
assert_eq!(
import_map
.resolve("https://///example.com///", referrer_url)
.unwrap(),
Some("https://example.com///".to_string())
assert_resolve(
import_map.resolve("https://///example.com///", referrer_url),
"https://example.com///",
);
}
@ -1414,39 +1416,37 @@ mod tests {
let import_map = ImportMap::from_json(base_url, json_map).unwrap();
// Should work for package main modules.
assert_eq!(
import_map.resolve("moment", referrer_url).unwrap(),
Some("https://example.com/deps/moment/src/moment.js".to_string())
assert_resolve(
import_map.resolve("moment", referrer_url),
"https://example.com/deps/moment/src/moment.js",
);
assert_eq!(
import_map.resolve("lodash-dot", referrer_url).unwrap(),
Some("https://example.com/app/deps/lodash-es/lodash.js".to_string())
assert_resolve(
import_map.resolve("lodash-dot", referrer_url),
"https://example.com/app/deps/lodash-es/lodash.js",
);
assert_eq!(
import_map.resolve("lodash-dotdot", referrer_url).unwrap(),
Some("https://example.com/deps/lodash-es/lodash.js".to_string())
assert_resolve(
import_map.resolve("lodash-dotdot", referrer_url),
"https://example.com/deps/lodash-es/lodash.js",
);
// Should work for package submodules.
assert_eq!(
import_map.resolve("moment/foo", referrer_url).unwrap(),
Some("https://example.com/deps/moment/src/foo".to_string())
assert_resolve(
import_map.resolve("moment/foo", referrer_url),
"https://example.com/deps/moment/src/foo",
);
assert_eq!(
import_map.resolve("lodash-dot/foo", referrer_url).unwrap(),
Some("https://example.com/app/deps/lodash-es/foo".to_string())
assert_resolve(
import_map.resolve("lodash-dot/foo", referrer_url),
"https://example.com/app/deps/lodash-es/foo",
);
assert_eq!(
import_map
.resolve("lodash-dotdot/foo", referrer_url)
.unwrap(),
Some("https://example.com/deps/lodash-es/foo".to_string())
assert_resolve(
import_map.resolve("lodash-dotdot/foo", referrer_url),
"https://example.com/deps/lodash-es/foo",
);
// Should work for package names that end in a slash.
assert_eq!(
import_map.resolve("moment/", referrer_url).unwrap(),
Some("https://example.com/deps/moment/src/".to_string())
assert_resolve(
import_map.resolve("moment/", referrer_url),
"https://example.com/deps/moment/src/",
);
// Should fail for package modules that are not declared.
@ -1476,33 +1476,31 @@ mod tests {
let import_map = ImportMap::from_json(base_url, json_map).unwrap();
// Should work for explicitly-mapped specifiers that happen to have a slash.
assert_eq!(
import_map
.resolve("package/withslash", referrer_url)
.unwrap(),
Some("https://example.com/deps/package-with-slash/index.mjs".to_string())
assert_resolve(
import_map.resolve("package/withslash", referrer_url),
"https://example.com/deps/package-with-slash/index.mjs",
);
// Should work when the specifier has punctuation.
assert_eq!(
import_map.resolve(".", referrer_url).unwrap(),
Some("https://example.com/lib/dot.mjs".to_string())
assert_resolve(
import_map.resolve(".", referrer_url),
"https://example.com/lib/dot.mjs",
);
assert_eq!(
import_map.resolve("..", referrer_url).unwrap(),
Some("https://example.com/lib/dotdot.mjs".to_string())
assert_resolve(
import_map.resolve("..", referrer_url),
"https://example.com/lib/dotdot.mjs",
);
assert_eq!(
import_map.resolve("..\\\\", referrer_url).unwrap(),
Some("https://example.com/lib/dotdotbackslash.mjs".to_string())
assert_resolve(
import_map.resolve("..\\\\", referrer_url),
"https://example.com/lib/dotdotbackslash.mjs",
);
assert_eq!(
import_map.resolve("%2E", referrer_url).unwrap(),
Some("https://example.com/lib/percent2e.mjs".to_string())
assert_resolve(
import_map.resolve("%2E", referrer_url),
"https://example.com/lib/percent2e.mjs",
);
assert_eq!(
import_map.resolve("%2F", referrer_url).unwrap(),
Some("https://example.com/lib/percent2f.mjs".to_string())
assert_resolve(
import_map.resolve("%2F", referrer_url),
"https://example.com/lib/percent2f.mjs",
);
// Should fail for attempting to get a submodule of something not declared with a trailing slash.
@ -1537,45 +1535,35 @@ mod tests {
let import_map = ImportMap::from_json(base_url, json_map).unwrap();
// Should remap to other URLs.
assert_eq!(
import_map
.resolve("https://example.com/lib/foo.mjs", referrer_url)
.unwrap(),
Some("https://example.com/app/more/bar.mjs".to_string())
assert_resolve(
import_map.resolve("https://example.com/lib/foo.mjs", referrer_url),
"https://example.com/app/more/bar.mjs",
);
assert_eq!(
import_map
.resolve("https://///example.com/lib/foo.mjs", referrer_url)
.unwrap(),
Some("https://example.com/app/more/bar.mjs".to_string())
assert_resolve(
import_map.resolve("https://///example.com/lib/foo.mjs", referrer_url),
"https://example.com/app/more/bar.mjs",
);
assert_eq!(
import_map.resolve("/lib/foo.mjs", referrer_url).unwrap(),
Some("https://example.com/app/more/bar.mjs".to_string())
assert_resolve(
import_map.resolve("/lib/foo.mjs", referrer_url),
"https://example.com/app/more/bar.mjs",
);
assert_eq!(
assert_resolve(
import_map
.resolve("https://example.com/app/dotrelative/foo.mjs", referrer_url)
.unwrap(),
Some("https://example.com/lib/dot.mjs".to_string())
.resolve("https://example.com/app/dotrelative/foo.mjs", referrer_url),
"https://example.com/lib/dot.mjs",
);
assert_eq!(
import_map
.resolve("../app/dotrelative/foo.mjs", referrer_url)
.unwrap(),
Some("https://example.com/lib/dot.mjs".to_string())
assert_resolve(
import_map.resolve("../app/dotrelative/foo.mjs", referrer_url),
"https://example.com/lib/dot.mjs",
);
assert_eq!(
assert_resolve(
import_map
.resolve("https://example.com/dotdotrelative/foo.mjs", referrer_url)
.unwrap(),
Some("https://example.com/lib/dotdot.mjs".to_string())
.resolve("https://example.com/dotdotrelative/foo.mjs", referrer_url),
"https://example.com/lib/dotdot.mjs",
);
assert_eq!(
import_map
.resolve("../dotdotrelative/foo.mjs", referrer_url)
.unwrap(),
Some("https://example.com/lib/dotdot.mjs".to_string())
assert_resolve(
import_map.resolve("../dotdotrelative/foo.mjs", referrer_url),
"https://example.com/lib/dotdot.mjs",
);
// Should fail for URLs that remap to empty arrays.
@ -1603,55 +1591,47 @@ mod tests {
);
// Should remap URLs that are just composed from / and ..
assert_eq!(
import_map
.resolve("https://example.com/", referrer_url)
.unwrap(),
Some("https://example.com/lib/slash-only/".to_string())
assert_resolve(
import_map.resolve("https://example.com/", referrer_url),
"https://example.com/lib/slash-only/",
);
assert_eq!(
import_map.resolve("/", referrer_url).unwrap(),
Some("https://example.com/lib/slash-only/".to_string())
assert_resolve(
import_map.resolve("/", referrer_url),
"https://example.com/lib/slash-only/",
);
assert_eq!(
import_map.resolve("../", referrer_url).unwrap(),
Some("https://example.com/lib/slash-only/".to_string())
assert_resolve(
import_map.resolve("../", referrer_url),
"https://example.com/lib/slash-only/",
);
assert_eq!(
import_map
.resolve("https://example.com/app/", referrer_url)
.unwrap(),
Some("https://example.com/lib/dotslash-only/".to_string())
assert_resolve(
import_map.resolve("https://example.com/app/", referrer_url),
"https://example.com/lib/dotslash-only/",
);
assert_eq!(
import_map.resolve("/app/", referrer_url).unwrap(),
Some("https://example.com/lib/dotslash-only/".to_string())
assert_resolve(
import_map.resolve("/app/", referrer_url),
"https://example.com/lib/dotslash-only/",
);
assert_eq!(
import_map.resolve("../app/", referrer_url).unwrap(),
Some("https://example.com/lib/dotslash-only/".to_string())
assert_resolve(
import_map.resolve("../app/", referrer_url),
"https://example.com/lib/dotslash-only/",
);
// Should remap URLs that are prefix-matched by keys with trailing slashes.
assert_eq!(
import_map.resolve("/test/foo.mjs", referrer_url).unwrap(),
Some("https://example.com/lib/url-trailing-slash/foo.mjs".to_string())
assert_resolve(
import_map.resolve("/test/foo.mjs", referrer_url),
"https://example.com/lib/url-trailing-slash/foo.mjs",
);
assert_eq!(
import_map
.resolve("https://example.com/app/test/foo.mjs", referrer_url)
.unwrap(),
Some(
"https://example.com/lib/url-trailing-slash-dot/foo.mjs".to_string()
)
assert_resolve(
import_map.resolve("https://example.com/app/test/foo.mjs", referrer_url),
"https://example.com/lib/url-trailing-slash-dot/foo.mjs",
);
// Should use the last entry's address when URL-like specifiers parse to the same absolute URL.
//
// NOTE: this works properly because of "preserve_order" feature flag to "serde_json" crate
assert_eq!(
import_map.resolve("/test", referrer_url).unwrap(),
Some("https://example.com/lib/test2.mjs".to_string())
assert_resolve(
import_map.resolve("/test", referrer_url),
"https://example.com/lib/test2.mjs",
);
}
@ -1672,25 +1652,25 @@ mod tests {
}"#;
let import_map = ImportMap::from_json(base_url, json_map).unwrap();
assert_eq!(
import_map.resolve("a", referrer_url).unwrap(),
Some("https://example.com/1".to_string())
assert_resolve(
import_map.resolve("a", referrer_url),
"https://example.com/1",
);
assert_eq!(
import_map.resolve("a/", referrer_url).unwrap(),
Some("https://example.com/2/".to_string())
assert_resolve(
import_map.resolve("a/", referrer_url),
"https://example.com/2/",
);
assert_eq!(
import_map.resolve("a/b", referrer_url).unwrap(),
Some("https://example.com/3".to_string())
assert_resolve(
import_map.resolve("a/b", referrer_url),
"https://example.com/3",
);
assert_eq!(
import_map.resolve("a/b/", referrer_url).unwrap(),
Some("https://example.com/4/".to_string())
assert_resolve(
import_map.resolve("a/b/", referrer_url),
"https://example.com/4/",
);
assert_eq!(
import_map.resolve("a/b/c", referrer_url).unwrap(),
Some("https://example.com/4/c".to_string())
assert_resolve(
import_map.resolve("a/b/c", referrer_url),
"https://example.com/4/c",
);
}
@ -1709,17 +1689,17 @@ mod tests {
assert!(import_map.resolve("a", referrer_url).is_err());
assert!(import_map.resolve("a/", referrer_url).is_err());
assert!(import_map.resolve("a/x", referrer_url).is_err());
assert_eq!(
import_map.resolve("a/b", referrer_url).unwrap(),
Some("https://example.com/3".to_string())
assert_resolve(
import_map.resolve("a/b", referrer_url),
"https://example.com/3",
);
assert_eq!(
import_map.resolve("a/b/", referrer_url).unwrap(),
Some("https://example.com/4/".to_string())
assert_resolve(
import_map.resolve("a/b/", referrer_url),
"https://example.com/4/",
);
assert_eq!(
import_map.resolve("a/b/c", referrer_url).unwrap(),
Some("https://example.com/4/c".to_string())
assert_resolve(
import_map.resolve("a/b/c", referrer_url),
"https://example.com/4/c",
);
assert!(import_map.resolve("a/x/c", referrer_url).is_err());
}
@ -1766,25 +1746,21 @@ mod tests {
let js_in_dir = "https://example.com/js/app.mjs";
let with_js_prefix = "https://example.com/jsiscool";
assert_eq!(
import_map.resolve("moment", js_non_dir).unwrap(),
Some("https://example.com/only-triggered-by-exact/moment".to_string())
assert_resolve(
import_map.resolve("moment", js_non_dir),
"https://example.com/only-triggered-by-exact/moment",
);
assert_eq!(
import_map.resolve("moment/foo", js_non_dir).unwrap(),
Some(
"https://example.com/only-triggered-by-exact/moment/foo".to_string()
)
assert_resolve(
import_map.resolve("moment/foo", js_non_dir),
"https://example.com/only-triggered-by-exact/moment/foo",
);
assert_eq!(
import_map.resolve("moment", js_in_dir).unwrap(),
Some("https://example.com/triggered-by-any-subpath/moment".to_string())
assert_resolve(
import_map.resolve("moment", js_in_dir),
"https://example.com/triggered-by-any-subpath/moment",
);
assert_eq!(
import_map.resolve("moment/foo", js_in_dir).unwrap(),
Some(
"https://example.com/triggered-by-any-subpath/moment/foo".to_string()
)
assert_resolve(
import_map.resolve("moment/foo", js_in_dir),
"https://example.com/triggered-by-any-subpath/moment/foo",
);
assert!(import_map.resolve("moment", with_js_prefix).is_err());
assert!(import_map.resolve("moment/foo", with_js_prefix).is_err());
@ -1809,15 +1785,13 @@ mod tests {
let js_in_dir = "https://example.com/js/app.mjs";
let with_js_prefix = "https://example.com/jsiscool";
assert_eq!(
import_map.resolve("moment", js_non_dir).unwrap(),
Some("https://example.com/only-triggered-by-exact/moment".to_string())
assert_resolve(
import_map.resolve("moment", js_non_dir),
"https://example.com/only-triggered-by-exact/moment",
);
assert_eq!(
import_map.resolve("moment/foo", js_non_dir).unwrap(),
Some(
"https://example.com/only-triggered-by-exact/moment/foo".to_string()
)
assert_resolve(
import_map.resolve("moment/foo", js_non_dir),
"https://example.com/only-triggered-by-exact/moment/foo",
);
assert!(import_map.resolve("moment", js_in_dir).is_err());
assert!(import_map.resolve("moment/foo", js_in_dir).is_err());
@ -1846,15 +1820,13 @@ mod tests {
assert!(import_map.resolve("moment", js_non_dir).is_err());
assert!(import_map.resolve("moment/foo", js_non_dir).is_err());
assert_eq!(
import_map.resolve("moment", js_in_dir).unwrap(),
Some("https://example.com/triggered-by-any-subpath/moment".to_string())
assert_resolve(
import_map.resolve("moment", js_in_dir),
"https://example.com/triggered-by-any-subpath/moment",
);
assert_eq!(
import_map.resolve("moment/foo", js_in_dir).unwrap(),
Some(
"https://example.com/triggered-by-any-subpath/moment/foo".to_string()
)
assert_resolve(
import_map.resolve("moment/foo", js_in_dir),
"https://example.com/triggered-by-any-subpath/moment/foo",
);
assert!(import_map.resolve("moment", with_js_prefix).is_err());
assert!(import_map.resolve("moment/foo", with_js_prefix).is_err());
@ -1893,75 +1865,61 @@ mod tests {
let top_level = "https://example.com/app.mjs";
// Should resolve scoped.
assert_eq!(
import_map.resolve("lodash-dot", js_in_dir).unwrap(),
Some(
"https://example.com/app/node_modules_2/lodash-es/lodash.js"
.to_string()
)
assert_resolve(
import_map.resolve("lodash-dot", js_in_dir),
"https://example.com/app/node_modules_2/lodash-es/lodash.js",
);
assert_eq!(
import_map.resolve("lodash-dotdot", js_in_dir).unwrap(),
Some(
"https://example.com/node_modules_2/lodash-es/lodash.js".to_string()
)
assert_resolve(
import_map.resolve("lodash-dotdot", js_in_dir),
"https://example.com/node_modules_2/lodash-es/lodash.js",
);
assert_eq!(
import_map.resolve("lodash-dot/foo", js_in_dir).unwrap(),
Some("https://example.com/app/node_modules_2/lodash-es/foo".to_string())
assert_resolve(
import_map.resolve("lodash-dot/foo", js_in_dir),
"https://example.com/app/node_modules_2/lodash-es/foo",
);
assert_eq!(
import_map.resolve("lodash-dotdot/foo", js_in_dir).unwrap(),
Some("https://example.com/node_modules_2/lodash-es/foo".to_string())
assert_resolve(
import_map.resolve("lodash-dotdot/foo", js_in_dir),
"https://example.com/node_modules_2/lodash-es/foo",
);
// Should apply best scope match.
assert_eq!(
import_map.resolve("moment", top_level).unwrap(),
Some(
"https://example.com/node_modules_3/moment/src/moment.js".to_string()
)
assert_resolve(
import_map.resolve("moment", top_level),
"https://example.com/node_modules_3/moment/src/moment.js",
);
assert_eq!(
import_map.resolve("moment", js_in_dir).unwrap(),
Some(
"https://example.com/node_modules_3/moment/src/moment.js".to_string()
)
assert_resolve(
import_map.resolve("moment", js_in_dir),
"https://example.com/node_modules_3/moment/src/moment.js",
);
assert_eq!(
import_map.resolve("vue", js_in_dir).unwrap(),
Some(
"https://example.com/node_modules_3/vue/dist/vue.runtime.esm.js"
.to_string()
)
assert_resolve(
import_map.resolve("vue", js_in_dir),
"https://example.com/node_modules_3/vue/dist/vue.runtime.esm.js",
);
// Should fallback to "imports".
assert_eq!(
import_map.resolve("moment/foo", top_level).unwrap(),
Some("https://example.com/node_modules/moment/src/foo".to_string())
assert_resolve(
import_map.resolve("moment/foo", top_level),
"https://example.com/node_modules/moment/src/foo",
);
assert_eq!(
import_map.resolve("moment/foo", js_in_dir).unwrap(),
Some("https://example.com/node_modules/moment/src/foo".to_string())
assert_resolve(
import_map.resolve("moment/foo", js_in_dir),
"https://example.com/node_modules/moment/src/foo",
);
assert_eq!(
import_map.resolve("lodash-dot", top_level).unwrap(),
Some(
"https://example.com/app/node_modules/lodash-es/lodash.js".to_string()
)
assert_resolve(
import_map.resolve("lodash-dot", top_level),
"https://example.com/app/node_modules/lodash-es/lodash.js",
);
assert_eq!(
import_map.resolve("lodash-dotdot", top_level).unwrap(),
Some("https://example.com/node_modules/lodash-es/lodash.js".to_string())
assert_resolve(
import_map.resolve("lodash-dotdot", top_level),
"https://example.com/node_modules/lodash-es/lodash.js",
);
assert_eq!(
import_map.resolve("lodash-dot/foo", top_level).unwrap(),
Some("https://example.com/app/node_modules/lodash-es/foo".to_string())
assert_resolve(
import_map.resolve("lodash-dot/foo", top_level),
"https://example.com/app/node_modules/lodash-es/foo",
);
assert_eq!(
import_map.resolve("lodash-dotdot/foo", top_level).unwrap(),
Some("https://example.com/node_modules/lodash-es/foo".to_string())
assert_resolve(
import_map.resolve("lodash-dotdot/foo", top_level),
"https://example.com/node_modules/lodash-es/foo",
);
// Should still fail for package-like specifiers that are not declared.
@ -1996,45 +1954,45 @@ mod tests {
let scope_3_url = "https://example.com/scope2/scope3/foo.mjs";
// Should fall back to "imports" when none match.
assert_eq!(
import_map.resolve("a", scope_1_url).unwrap(),
Some("https://example.com/a-1.mjs".to_string())
assert_resolve(
import_map.resolve("a", scope_1_url),
"https://example.com/a-1.mjs",
);
assert_eq!(
import_map.resolve("b", scope_1_url).unwrap(),
Some("https://example.com/b-1.mjs".to_string())
assert_resolve(
import_map.resolve("b", scope_1_url),
"https://example.com/b-1.mjs",
);
assert_eq!(
import_map.resolve("c", scope_1_url).unwrap(),
Some("https://example.com/c-1.mjs".to_string())
assert_resolve(
import_map.resolve("c", scope_1_url),
"https://example.com/c-1.mjs",
);
// Should use a direct scope override.
assert_eq!(
import_map.resolve("a", scope_2_url).unwrap(),
Some("https://example.com/a-2.mjs".to_string())
assert_resolve(
import_map.resolve("a", scope_2_url),
"https://example.com/a-2.mjs",
);
assert_eq!(
import_map.resolve("b", scope_2_url).unwrap(),
Some("https://example.com/b-1.mjs".to_string())
assert_resolve(
import_map.resolve("b", scope_2_url),
"https://example.com/b-1.mjs",
);
assert_eq!(
import_map.resolve("c", scope_2_url).unwrap(),
Some("https://example.com/c-1.mjs".to_string())
assert_resolve(
import_map.resolve("c", scope_2_url),
"https://example.com/c-1.mjs",
);
// Should use an indirect scope override.
assert_eq!(
import_map.resolve("a", scope_3_url).unwrap(),
Some("https://example.com/a-2.mjs".to_string())
assert_resolve(
import_map.resolve("a", scope_3_url),
"https://example.com/a-2.mjs",
);
assert_eq!(
import_map.resolve("b", scope_3_url).unwrap(),
Some("https://example.com/b-3.mjs".to_string())
assert_resolve(
import_map.resolve("b", scope_3_url),
"https://example.com/b-3.mjs",
);
assert_eq!(
import_map.resolve("c", scope_3_url).unwrap(),
Some("https://example.com/c-1.mjs".to_string())
assert_resolve(
import_map.resolve("c", scope_3_url),
"https://example.com/c-1.mjs",
);
}
@ -2066,37 +2024,37 @@ mod tests {
let in_dir_above_map = "https://example.com/foo.mjs";
// Should resolve an empty string scope using the import map URL.
assert_eq!(
import_map.resolve("a", base_url).unwrap(),
Some("https://example.com/a-empty-string.mjs".to_string())
assert_resolve(
import_map.resolve("a", base_url),
"https://example.com/a-empty-string.mjs",
);
assert_eq!(
import_map.resolve("a", in_same_dir_as_map).unwrap(),
Some("https://example.com/a-1.mjs".to_string())
assert_resolve(
import_map.resolve("a", in_same_dir_as_map),
"https://example.com/a-1.mjs",
);
// Should resolve a ./ scope using the import map URL's directory.
assert_eq!(
import_map.resolve("b", base_url).unwrap(),
Some("https://example.com/b-dot-slash.mjs".to_string())
assert_resolve(
import_map.resolve("b", base_url),
"https://example.com/b-dot-slash.mjs",
);
assert_eq!(
import_map.resolve("b", in_same_dir_as_map).unwrap(),
Some("https://example.com/b-dot-slash.mjs".to_string())
assert_resolve(
import_map.resolve("b", in_same_dir_as_map),
"https://example.com/b-dot-slash.mjs",
);
// Should resolve a ../ scope using the import map URL's directory.
assert_eq!(
import_map.resolve("c", base_url).unwrap(),
Some("https://example.com/c-dot-dot-slash.mjs".to_string())
assert_resolve(
import_map.resolve("c", base_url),
"https://example.com/c-dot-dot-slash.mjs",
);
assert_eq!(
import_map.resolve("c", in_same_dir_as_map).unwrap(),
Some("https://example.com/c-dot-dot-slash.mjs".to_string())
assert_resolve(
import_map.resolve("c", in_same_dir_as_map),
"https://example.com/c-dot-dot-slash.mjs",
);
assert_eq!(
import_map.resolve("c", in_dir_above_map).unwrap(),
Some("https://example.com/c-dot-dot-slash.mjs".to_string())
assert_resolve(
import_map.resolve("c", in_dir_above_map),
"https://example.com/c-dot-dot-slash.mjs",
);
}
@ -2121,13 +2079,13 @@ mod tests {
}"#;
let import_map = ImportMap::from_json(base_url, json_map).unwrap();
assert_eq!(
import_map.resolve("std:blank", base_url).unwrap(),
Some("https://example.com/app/blank.mjs".to_string())
assert_resolve(
import_map.resolve("std:blank", base_url),
"https://example.com/app/blank.mjs",
);
assert_eq!(
import_map.resolve("std:none", base_url).unwrap(),
Some("https://example.com/app/none.mjs".to_string())
assert_resolve(
import_map.resolve("std:none", base_url),
"https://example.com/app/none.mjs",
);
}
}

View File

@ -27,6 +27,7 @@ mod http_body;
mod http_util;
mod import_map;
pub mod js_errors;
mod module_specifier;
pub mod msg;
pub mod msg_util;
pub mod ops;
@ -45,9 +46,9 @@ pub mod worker;
use crate::compiler::bundle_async;
use crate::errors::RustOrJsError;
use crate::module_specifier::ModuleSpecifier;
use crate::progress::Progress;
use crate::state::ThreadSafeState;
use crate::worker::root_specifier_to_url;
use crate::worker::Worker;
use deno::v8_set_flags;
use flags::DenoFlags;
@ -98,51 +99,53 @@ where
pub fn print_file_info(
worker: Worker,
url: &str,
module_specifier: &ModuleSpecifier,
) -> impl Future<Item = Worker, Error = ()> {
state::fetch_module_meta_data_and_maybe_compile_async(&worker.state, url, ".")
.and_then(move |out| {
println!("{} {}", ansi::bold("local:".to_string()), &(out.filename));
state::fetch_module_meta_data_and_maybe_compile_async(
&worker.state,
module_specifier,
).and_then(move |out| {
println!("{} {}", ansi::bold("local:".to_string()), &(out.filename));
println!(
"{} {}",
ansi::bold("type:".to_string()),
msg::enum_name_media_type(out.media_type)
);
if out.maybe_output_code_filename.is_some() {
println!(
"{} {}",
ansi::bold("type:".to_string()),
msg::enum_name_media_type(out.media_type)
ansi::bold("compiled:".to_string()),
out.maybe_output_code_filename.as_ref().unwrap(),
);
}
if out.maybe_output_code_filename.is_some() {
println!(
"{} {}",
ansi::bold("compiled:".to_string()),
out.maybe_output_code_filename.as_ref().unwrap(),
);
}
if out.maybe_source_map_filename.is_some() {
println!(
"{} {}",
ansi::bold("map:".to_string()),
out.maybe_source_map_filename.as_ref().unwrap()
);
}
if out.maybe_source_map_filename.is_some() {
println!(
"{} {}",
ansi::bold("map:".to_string()),
out.maybe_source_map_filename.as_ref().unwrap()
);
}
if let Some(deps) =
worker.state.modules.lock().unwrap().deps(&out.module_name)
{
println!("{}{}", ansi::bold("deps:\n".to_string()), deps.name);
if let Some(ref depsdeps) = deps.deps {
for d in depsdeps {
println!("{}", d);
}
if let Some(deps) =
worker.state.modules.lock().unwrap().deps(&out.module_name)
{
println!("{}{}", ansi::bold("deps:\n".to_string()), deps.name);
if let Some(ref depsdeps) = deps.deps {
for d in depsdeps {
println!("{}", d);
}
} else {
println!(
"{} cannot retrieve full dependency graph",
ansi::bold("deps:".to_string()),
);
}
Ok(worker)
}).map_err(|err| println!("{}", err))
} else {
println!(
"{} cannot retrieve full dependency graph",
ansi::bold("deps:".to_string()),
);
}
Ok(worker)
}).map_err(|err| println!("{}", err))
}
fn create_worker_and_state(
@ -193,10 +196,8 @@ fn fetch_or_info_command(
js_check(worker.execute("denoMain()"));
debug!("main_module {}", main_module);
let main_url = root_specifier_to_url(&main_module).unwrap();
worker
.execute_mod_async(&main_url, true)
.execute_mod_async(&main_module, true)
.map_err(print_err_and_exit)
.and_then(move |()| {
if print_info {
@ -269,11 +270,10 @@ fn bundle_command(flags: DenoFlags, argv: Vec<String>) {
let (mut _worker, state) = create_worker_and_state(flags, argv);
let main_module = state.main_module().unwrap();
let main_url = root_specifier_to_url(&main_module).unwrap();
assert!(state.argv.len() >= 3);
let out_file = state.argv[2].clone();
debug!(">>>>> bundle_async START");
let bundle_future = bundle_async(state, main_url.to_string(), out_file)
let bundle_future = bundle_async(state, main_module.to_string(), out_file)
.map_err(|e| {
debug!("diagnostics returned, exiting!");
eprintln!("\n{}", e.to_string());
@ -313,10 +313,8 @@ fn run_script(flags: DenoFlags, argv: Vec<String>) {
js_check(worker.execute("denoMain()"));
debug!("main_module {}", main_module);
let main_url = root_specifier_to_url(&main_module).unwrap();
worker
.execute_mod_async(&main_url, false)
.execute_mod_async(&main_module, false)
.and_then(move |()| {
worker.then(|result| {
js_check(result);

81
cli/module_specifier.rs Normal file
View File

@ -0,0 +1,81 @@
use std::fmt;
use url::Url;
#[derive(Debug, Clone, PartialEq)]
/// Resolved module specifier
pub struct ModuleSpecifier(Url);
impl ModuleSpecifier {
pub fn to_url(&self) -> Url {
self.0.clone()
}
/// Resolves module using this algorithm:
/// https://html.spec.whatwg.org/multipage/webappapis.html#resolve-a-module-specifier
pub fn resolve(
specifier: &str,
base: &str,
) -> Result<ModuleSpecifier, url::ParseError> {
// 1. Apply the URL parser to specifier. If the result is not failure, return
// the result.
// let specifier = parse_local_or_remote(specifier)?.to_string();
if let Ok(url) = Url::parse(specifier) {
return Ok(ModuleSpecifier(url));
}
// 2. If specifier does not start with the character U+002F SOLIDUS (/), the
// two-character sequence U+002E FULL STOP, U+002F SOLIDUS (./), or the
// three-character sequence U+002E FULL STOP, U+002E FULL STOP, U+002F
// SOLIDUS (../), return failure.
if !specifier.starts_with('/')
&& !specifier.starts_with("./")
&& !specifier.starts_with("../")
{
// TODO This is (probably) not the correct error to return here.
// TODO: This error is very not-user-friendly
return Err(url::ParseError::RelativeUrlWithCannotBeABaseBase);
}
// 3. Return the result of applying the URL parser to specifier with base URL
// as the base URL.
let base_url = Url::parse(base)?;
let u = base_url.join(&specifier)?;
Ok(ModuleSpecifier(u))
}
/// Takes a string representing a path or URL to a module, but of the type
/// passed through the command-line interface for the main module. This is
/// slightly different than specifiers used in import statements: "foo.js" for
/// example is allowed here, whereas in import statements a leading "./" is
/// required ("./foo.js"). This function is aware of the current working
/// directory and returns an absolute URL.
pub fn resolve_root(
root_specifier: &str,
) -> Result<ModuleSpecifier, url::ParseError> {
if let Ok(url) = Url::parse(root_specifier) {
Ok(ModuleSpecifier(url))
} else {
let cwd = std::env::current_dir().unwrap();
let base = Url::from_directory_path(cwd).unwrap();
let url = base.join(root_specifier)?;
Ok(ModuleSpecifier(url))
}
}
}
impl From<Url> for ModuleSpecifier {
fn from(url: Url) -> Self {
ModuleSpecifier(url)
}
}
impl fmt::Display for ModuleSpecifier {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.0.fmt(f)
}
}
impl PartialEq<String> for ModuleSpecifier {
fn eq(&self, other: &String) -> bool {
&self.to_string() == other
}
}

View File

@ -10,6 +10,7 @@ use crate::fs as deno_fs;
use crate::http_util;
use crate::js_errors::apply_source_map;
use crate::js_errors::JSErrorColor;
use crate::module_specifier::ModuleSpecifier;
use crate::msg;
use crate::msg_util;
use crate::rand;
@ -24,12 +25,10 @@ use crate::state::ThreadSafeState;
use crate::tokio_util;
use crate::tokio_write;
use crate::version;
use crate::worker::root_specifier_to_url;
use crate::worker::Worker;
use deno::js_check;
use deno::Buf;
use deno::JSError;
//use deno::Loader;
use deno::Op;
use deno::PinnedBuf;
use flatbuffers::FlatBufferBuilder;
@ -341,7 +340,9 @@ fn op_start(
let deno_version = version::DENO;
let deno_version_off = builder.create_string(deno_version);
let main_module = state.main_module().map(|m| builder.create_string(&m));
let main_module = state
.main_module()
.map(|m| builder.create_string(&m.to_string()));
let xeval_delim = state
.flags
@ -507,7 +508,7 @@ fn op_fetch_module_meta_data(
Some(import_map) => {
match import_map.resolve(specifier, referrer) {
Ok(result) => match result {
Some(url) => url.clone(),
Some(module_specifier) => module_specifier.to_string(),
None => specifier.to_string(),
},
Err(err) => panic!("error resolving using import map: {:?}", err), // TODO: this should be coerced to DenoError
@ -2082,11 +2083,11 @@ fn op_create_worker(
js_check(worker.execute("denoMain()"));
js_check(worker.execute("workerMain()"));
let op = root_specifier_to_url(specifier)
.and_then(|specifier_url| {
let op = ModuleSpecifier::resolve_root(specifier)
.and_then(|module_specifier| {
Ok(
worker
.execute_mod_async(&specifier_url, false)
.execute_mod_async(&module_specifier, false)
.and_then(move |()| {
let mut workers_tl = parent_state.workers.lock().unwrap();
workers_tl.insert(rid, worker.shared());

View File

@ -7,13 +7,13 @@ use crate::errors::DenoResult;
use crate::flags;
use crate::global_timer::GlobalTimer;
use crate::import_map::ImportMap;
use crate::module_specifier::ModuleSpecifier;
use crate::msg;
use crate::ops;
use crate::permissions::DenoPermissions;
use crate::progress::Progress;
use crate::resources;
use crate::resources::ResourceId;
use crate::worker::resolve_module_spec;
use crate::worker::Worker;
use deno::Buf;
use deno::Loader;
@ -60,7 +60,7 @@ pub struct ThreadSafeState(Arc<State>);
#[cfg_attr(feature = "cargo-clippy", allow(stutter))]
pub struct State {
pub modules: Arc<Mutex<deno::Modules>>,
pub main_module: Option<String>,
pub main_module: Option<ModuleSpecifier>,
pub dir: deno_dir::DenoDir,
pub argv: Vec<String>,
pub permissions: DenoPermissions,
@ -113,44 +113,40 @@ impl ThreadSafeState {
pub fn fetch_module_meta_data_and_maybe_compile_async(
state: &ThreadSafeState,
specifier: &str,
referrer: &str,
module_specifier: &ModuleSpecifier,
) -> impl Future<Item = ModuleMetaData, Error = DenoError> {
let state_ = state.clone();
let specifier = specifier.to_string();
let referrer = referrer.to_string();
let is_root = referrer == ".";
let use_cache =
!state_.flags.reload || state_.has_compiled(&module_specifier.to_string());
let no_fetch = state_.flags.no_fetch;
let f =
futures::future::result(state.resolve(&specifier, &referrer, is_root));
f.and_then(move |module_id| {
let use_cache = !state_.flags.reload || state_.has_compiled(&module_id);
let no_fetch = state_.flags.no_fetch;
state_
.dir
.fetch_module_meta_data_async(&specifier, &referrer, use_cache, no_fetch)
.and_then(move |out| {
if out.media_type == msg::MediaType::TypeScript
&& !out.has_output_code_and_source_map()
{
debug!(">>>>> compile_sync START");
Either::A(
compile_async(state_.clone(), &out)
.map_err(|e| {
debug!("compiler error exiting!");
eprintln!("\n{}", e.to_string());
std::process::exit(1);
}).and_then(move |out| {
debug!(">>>>> compile_sync END");
Ok(out)
}),
)
} else {
Either::B(futures::future::ok(out))
}
})
})
state_
.dir
.fetch_module_meta_data_async(
&module_specifier.to_string(),
".",
use_cache,
no_fetch,
).and_then(move |out| {
if out.media_type == msg::MediaType::TypeScript
&& !out.has_output_code_and_source_map()
{
debug!(">>>>> compile_sync START");
Either::A(
compile_async(state_.clone(), &out)
.map_err(|e| {
debug!("compiler error exiting!");
eprintln!("\n{}", e.to_string());
std::process::exit(1);
}).and_then(move |out| {
debug!(">>>>> compile_sync END");
Ok(out)
}),
)
} else {
Either::B(futures::future::ok(out))
}
})
}
impl Loader for ThreadSafeState {
@ -164,28 +160,25 @@ impl Loader for ThreadSafeState {
) -> Result<String, Self::Error> {
if !is_root {
if let Some(import_map) = &self.import_map {
match import_map.resolve(specifier, referrer) {
Ok(result) => {
if result.is_some() {
return Ok(result.unwrap());
}
}
Err(err) => {
// TODO(bartlomieju): this should be coerced to DenoError
panic!("error resolving using import map: {:?}", err);
}
let result = import_map.resolve(specifier, referrer)?;
if result.is_some() {
return Ok(result.unwrap().to_string());
}
}
}
resolve_module_spec(specifier, referrer).map_err(DenoError::from)
let module_specifier =
ModuleSpecifier::resolve(specifier, referrer).map_err(DenoError::from)?;
Ok(module_specifier.to_string())
}
/// Given an absolute url, load its source code.
fn load(&self, url: &str) -> Box<deno::SourceCodeInfoFuture<Self::Error>> {
self.metrics.resolve_count.fetch_add(1, Ordering::SeqCst);
let module_specifier = ModuleSpecifier::resolve_root(url)
.expect("should already been properly resolved");
Box::new(
fetch_module_meta_data_and_maybe_compile_async(self, url, ".")
fetch_module_meta_data_and_maybe_compile_async(self, &module_specifier)
.map_err(|err| {
eprintln!("{}", err);
err
@ -256,18 +249,15 @@ impl ThreadSafeState {
let dir =
deno_dir::DenoDir::new(custom_root, &config, progress.clone()).unwrap();
let main_module: Option<String> = if argv_rest.len() <= 1 {
let main_module: Option<ModuleSpecifier> = if argv_rest.len() <= 1 {
None
} else {
let specifier = argv_rest[1].clone();
let referrer = ".";
// TODO: does this really have to be resolved by DenoDir?
// Maybe we can call `resolve_module_spec`
match dir.resolve_module_url(&specifier, referrer) {
Ok(url) => Some(url.to_string()),
let root_specifier = argv_rest[1].clone();
match ModuleSpecifier::resolve_root(&root_specifier) {
Ok(specifier) => Some(specifier),
Err(e) => {
debug!("Potentially swallowed error {}", e);
None
// TODO: handle unresolvable specifier
panic!("Unable to resolve root specifier: {:?}", e);
}
}
};
@ -275,11 +265,11 @@ impl ThreadSafeState {
let mut import_map = None;
if let Some(file_name) = &flags.import_map_path {
let base_url = match &main_module {
Some(url) => url,
Some(module_specifier) => module_specifier.clone(),
None => unreachable!(),
};
match ImportMap::load(base_url, file_name) {
match ImportMap::load(&base_url.to_string(), file_name) {
Ok(map) => import_map = Some(map),
Err(err) => {
println!("{:?}", err);
@ -319,9 +309,9 @@ impl ThreadSafeState {
}
/// Read main module from argv
pub fn main_module(&self) -> Option<String> {
pub fn main_module(&self) -> Option<ModuleSpecifier> {
match &self.main_module {
Some(url) => Some(url.to_string()),
Some(module_specifier) => Some(module_specifier.clone()),
None => None,
}
}

View File

@ -2,6 +2,7 @@
use crate::errors::DenoError;
use crate::errors::RustOrJsError;
use crate::js_errors;
use crate::module_specifier::ModuleSpecifier;
use crate::state::ThreadSafeState;
use crate::tokio_util;
use deno;
@ -11,7 +12,6 @@ use futures::Async;
use futures::Future;
use std::sync::Arc;
use std::sync::Mutex;
use url::Url;
/// Wraps deno::Isolate to provide source maps, ops for the CLI, and
/// high-level module loading
@ -57,7 +57,7 @@ impl Worker {
/// Executes the provided JavaScript module.
pub fn execute_mod_async(
&mut self,
js_url: &Url,
module_specifier: &ModuleSpecifier,
is_prefetch: bool,
) -> impl Future<Item = (), Error = RustOrJsError> {
let worker = self.clone();
@ -65,8 +65,12 @@ impl Worker {
let loader = self.state.clone();
let isolate = self.isolate.clone();
let modules = self.state.modules.clone();
let recursive_load =
deno::RecursiveLoad::new(js_url.as_str(), loader, isolate, modules);
let recursive_load = deno::RecursiveLoad::new(
&module_specifier.to_string(),
loader,
isolate,
modules,
);
recursive_load
.and_then(move |id| -> Result<(), deno::JSErrorOr<DenoError>> {
worker.state.progress.done();
@ -96,10 +100,10 @@ impl Worker {
/// Executes the provided JavaScript module.
pub fn execute_mod(
&mut self,
js_url: &Url,
module_specifier: &ModuleSpecifier,
is_prefetch: bool,
) -> Result<(), RustOrJsError> {
tokio_util::block_on(self.execute_mod_async(js_url, is_prefetch))
tokio_util::block_on(self.execute_mod_async(module_specifier, is_prefetch))
}
/// Applies source map to the error.
@ -108,58 +112,6 @@ impl Worker {
}
}
// https://html.spec.whatwg.org/multipage/webappapis.html#resolve-a-module-specifier
// TODO(ry) Add tests.
// TODO(ry) Move this to core?
pub fn resolve_module_spec(
specifier: &str,
base: &str,
) -> Result<String, url::ParseError> {
// 1. Apply the URL parser to specifier. If the result is not failure, return
// the result.
// let specifier = parse_local_or_remote(specifier)?.to_string();
if let Ok(specifier_url) = Url::parse(specifier) {
return Ok(specifier_url.to_string());
}
// 2. If specifier does not start with the character U+002F SOLIDUS (/), the
// two-character sequence U+002E FULL STOP, U+002F SOLIDUS (./), or the
// three-character sequence U+002E FULL STOP, U+002E FULL STOP, U+002F
// SOLIDUS (../), return failure.
if !specifier.starts_with('/')
&& !specifier.starts_with("./")
&& !specifier.starts_with("../")
{
// TODO(ry) This is (probably) not the correct error to return here.
return Err(url::ParseError::RelativeUrlWithCannotBeABaseBase);
}
// 3. Return the result of applying the URL parser to specifier with base URL
// as the base URL.
let base_url = Url::parse(base)?;
let u = base_url.join(&specifier)?;
Ok(u.to_string())
}
/// Takes a string representing a path or URL to a module, but of the type
/// passed through the command-line interface for the main module. This is
/// slightly different than specifiers used in import statements: "foo.js" for
/// example is allowed here, whereas in import statements a leading "./" is
/// required ("./foo.js"). This function is aware of the current working
/// directory and returns an absolute URL.
pub fn root_specifier_to_url(
root_specifier: &str,
) -> Result<Url, url::ParseError> {
let maybe_url = Url::parse(root_specifier);
if let Ok(url) = maybe_url {
Ok(url)
} else {
let cwd = std::env::current_dir().unwrap();
let base = Url::from_directory_path(cwd).unwrap();
base.join(root_specifier)
}
}
impl Future for Worker {
type Item = ();
type Error = JSError;
@ -186,12 +138,9 @@ mod tests {
#[test]
fn execute_mod_esm_imports_a() {
let filename = std::env::current_dir()
.unwrap()
.join("tests/esm_imports_a.js");
let js_url = Url::from_file_path(filename).unwrap();
let argv = vec![String::from("./deno"), js_url.to_string()];
let module_specifier =
ModuleSpecifier::resolve_root("tests/esm_imports_a.js").unwrap();
let argv = vec![String::from("./deno"), module_specifier.to_string()];
let state = ThreadSafeState::new(
flags::DenoFlags::default(),
argv,
@ -202,7 +151,7 @@ mod tests {
tokio_util::run(lazy(move || {
let mut worker =
Worker::new("TEST".to_string(), StartupData::None, state);
let result = worker.execute_mod(&js_url, false);
let result = worker.execute_mod(&module_specifier, false);
if let Err(err) = result {
eprintln!("execute_mod err {:?}", err);
}
@ -217,10 +166,9 @@ mod tests {
#[test]
fn execute_mod_circular() {
let filename = std::env::current_dir().unwrap().join("tests/circular1.js");
let js_url = Url::from_file_path(filename).unwrap();
let argv = vec![String::from("./deno"), js_url.to_string()];
let module_specifier =
ModuleSpecifier::resolve_root("tests/circular1.js").unwrap();
let argv = vec![String::from("./deno"), module_specifier.to_string()];
let state = ThreadSafeState::new(
flags::DenoFlags::default(),
argv,
@ -231,7 +179,7 @@ mod tests {
tokio_util::run(lazy(move || {
let mut worker =
Worker::new("TEST".to_string(), StartupData::None, state);
let result = worker.execute_mod(&js_url, false);
let result = worker.execute_mod(&module_specifier, false);
if let Err(err) = result {
eprintln!("execute_mod err {:?}", err);
}
@ -246,11 +194,9 @@ mod tests {
#[test]
fn execute_006_url_imports() {
let filename = std::env::current_dir()
.unwrap()
.join("tests/006_url_imports.ts");
let js_url = Url::from_file_path(filename).unwrap();
let argv = vec![String::from("deno"), js_url.to_string()];
let module_specifier =
ModuleSpecifier::resolve_root("tests/006_url_imports.ts").unwrap();
let argv = vec![String::from("deno"), module_specifier.to_string()];
let mut flags = flags::DenoFlags::default();
flags.reload = true;
let state =
@ -263,7 +209,7 @@ mod tests {
state,
);
js_check(worker.execute("denoMain()"));
let result = worker.execute_mod(&js_url, false);
let result = worker.execute_mod(&module_specifier, false);
if let Err(err) = result {
eprintln!("execute_mod err {:?}", err);
}
@ -378,8 +324,9 @@ mod tests {
tokio_util::init(|| {
// "foo" is not a vailid module specifier so this should return an error.
let mut worker = create_test_worker();
let js_url = root_specifier_to_url("does-not-exist").unwrap();
let result = worker.execute_mod_async(&js_url, false).wait();
let module_specifier =
ModuleSpecifier::resolve_root("does-not-exist").unwrap();
let result = worker.execute_mod_async(&module_specifier, false).wait();
assert!(result.is_err());
})
}
@ -390,8 +337,9 @@ mod tests {
// This assumes cwd is project root (an assumption made throughout the
// tests).
let mut worker = create_test_worker();
let js_url = root_specifier_to_url("./tests/002_hello.ts").unwrap();
let result = worker.execute_mod_async(&js_url, false).wait();
let module_specifier =
ModuleSpecifier::resolve_root("./tests/002_hello.ts").unwrap();
let result = worker.execute_mod_async(&module_specifier, false).wait();
assert!(result.is_ok());
})
}