Skip to content

Commit

Permalink
feat(compat): integrate import map and classic resolutions in ESM res…
Browse files Browse the repository at this point in the history
…olution (denoland#12549)

This commit integrates import map and "classic" resolutions in
the "--compat" mode when using ES modules; in effect
"http:", "https:" and "blob:" imports now work in compat mode.

The algorithm works as follows:

1. If there's an import map, try to resolve using it and if succeeded
return the specifier
2. Try to resolve using "Node ESM resolution", and if succeeded return
the specifier
3. Fall back to regular ESM resolution
  • Loading branch information
bartlomieju committed Oct 28, 2021
1 parent a065604 commit f77c570
Show file tree
Hide file tree
Showing 7 changed files with 70 additions and 8 deletions.
48 changes: 44 additions & 4 deletions cli/compat/esm_resolver.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.

use super::errors;
use crate::resolver::ImportMapResolver;
use deno_core::error::generic_error;
use deno_core::error::AnyError;
use deno_core::serde_json;
Expand All @@ -13,21 +14,60 @@ use regex::Regex;
use std::path::PathBuf;

#[derive(Debug, Default)]
pub struct NodeEsmResolver;
pub(crate) struct NodeEsmResolver<'a> {
maybe_import_map_resolver: Option<ImportMapResolver<'a>>,
}

impl<'a> NodeEsmResolver<'a> {
pub fn new(maybe_import_map_resolver: Option<ImportMapResolver<'a>>) -> Self {
Self {
maybe_import_map_resolver,
}
}

impl NodeEsmResolver {
pub fn as_resolver(&self) -> &dyn Resolver {
self
}
}

impl Resolver for NodeEsmResolver {
impl Resolver for NodeEsmResolver<'_> {
fn resolve(
&self,
specifier: &str,
referrer: &ModuleSpecifier,
) -> Result<ModuleSpecifier, AnyError> {
node_resolve(specifier, referrer.as_str(), &std::env::current_dir()?)
// First try to resolve using import map, ignoring any errors
if !specifier.starts_with("node:") {
if let Some(import_map_resolver) = &self.maybe_import_map_resolver {
if let Ok(specifier) = import_map_resolver.resolve(specifier, referrer)
{
return Ok(specifier);
}
}
}

let node_resolution =
node_resolve(specifier, referrer.as_str(), &std::env::current_dir()?);

match node_resolution {
Ok(specifier) => {
// If node resolution succeeded, return the specifier
Ok(specifier)
}
Err(err) => {
// If node resolution failed, check if it's because of unsupported
// URL scheme, and if so try to resolve using regular resolution algorithm
if err
.to_string()
.starts_with("[ERR_UNSUPPORTED_ESM_URL_SCHEME]")
{
return deno_core::resolve_import(specifier, referrer.as_str())
.map_err(|err| err.into());
}

Err(err)
}
}
}
}

Expand Down
6 changes: 3 additions & 3 deletions cli/compat/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use deno_core::located_script_name;
use deno_core::url::Url;
use deno_core::JsRuntime;

pub use esm_resolver::NodeEsmResolver;
pub(crate) use esm_resolver::NodeEsmResolver;

// TODO(bartlomieju): this needs to be bumped manually for
// each release, a better mechanism is preferable, but it's a quick and dirty
Expand Down Expand Up @@ -86,7 +86,7 @@ fn try_resolve_builtin_module(specifier: &str) -> Option<Url> {
}
}

pub async fn check_if_should_use_esm_loader(
pub(crate) async fn check_if_should_use_esm_loader(
js_runtime: &mut JsRuntime,
main_module: &str,
) -> Result<bool, AnyError> {
Expand All @@ -113,7 +113,7 @@ pub async fn check_if_should_use_esm_loader(
Ok(use_esm_loader)
}

pub fn load_cjs_module(
pub(crate) fn load_cjs_module(
js_runtime: &mut JsRuntime,
main_module: &str,
) -> Result<(), AnyError> {
Expand Down
4 changes: 3 additions & 1 deletion cli/proc_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -298,7 +298,9 @@ impl ProcState {
);
let maybe_locker = as_maybe_locker(self.lockfile.clone());
let maybe_imports = self.get_maybe_imports();
let node_resolver = NodeEsmResolver;
let node_resolver = NodeEsmResolver::new(
self.maybe_import_map.as_ref().map(ImportMapResolver::new),
);
let import_map_resolver =
self.maybe_import_map.as_ref().map(ImportMapResolver::new);
let maybe_resolver = if self.flags.compat {
Expand Down
5 changes: 5 additions & 0 deletions cli/tests/integration/compat_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ itest!(node_prefix_fs_promises {
output: "compat/fs_promises.out",
});

itest!(compat_with_import_map_and_https_imports {
args: "run --quiet --compat --unstable -A --import-map=compat/import_map.json compat/import_map_https_imports.mjs",
output: "compat/import_map_https_imports.out",
});

#[test]
fn globals_in_repl() {
let (out, _err) = util::run_and_collect_output_with_args(
Expand Down
5 changes: 5 additions & 0 deletions cli/tests/testdata/compat/import_map.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"imports": {
"std/": "https://deno.land/[email protected]/"
}
}
7 changes: 7 additions & 0 deletions cli/tests/testdata/compat/import_map_https_imports.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { sortBy } from "std/collections/sort_by.ts";
import { findSingle } from "https://deno.land/[email protected]/collections/find_single.ts";
import os from "node:os";

console.log(sortBy([2, 3, 1], (it) => it));
console.log(findSingle([2, 3, 1], (it) => it == 2));
console.log("arch", os.arch());
3 changes: 3 additions & 0 deletions cli/tests/testdata/compat/import_map_https_imports.out
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[ 1, 2, 3 ]
2
arch [WILDCARD]

0 comments on commit f77c570

Please sign in to comment.