mirror of
https://github.com/denoland/deno.git
synced 2025-03-03 17:34:47 -05:00
compat: support compat mode in REPL (#12882)
This commit introduces "ProcState::maybe_resolver" field, which stores a single instance of resolver for the whole lifetime of the process, instead of creating these resolvers for each creation of module graph. As a result, this resolver can be used in fallback case where graph is not constructed (REPL, loading modules using "require") unifying resolution logic.
This commit is contained in:
parent
1a51f2392d
commit
1117d2db39
12 changed files with 112 additions and 41 deletions
|
@ -49,7 +49,7 @@ pub(crate) fn err_module_not_found(
|
||||||
typ: &str,
|
typ: &str,
|
||||||
) -> AnyError {
|
) -> AnyError {
|
||||||
generic_error(format!(
|
generic_error(format!(
|
||||||
"[ERR_MODULE_NOT_FOUND] Cannot find {} '{}' imported from {}",
|
"[ERR_MODULE_NOT_FOUND] Cannot find {} \"{}\" imported from \"{}\"",
|
||||||
typ, path, base
|
typ, path, base
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,10 +24,6 @@ impl NodeEsmResolver {
|
||||||
maybe_import_map_resolver,
|
maybe_import_map_resolver,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn as_resolver(&self) -> &dyn Resolver {
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Resolver for NodeEsmResolver {
|
impl Resolver for NodeEsmResolver {
|
||||||
|
@ -232,12 +228,12 @@ fn finalize_resolution(
|
||||||
};
|
};
|
||||||
if is_dir {
|
if is_dir {
|
||||||
return Err(errors::err_unsupported_dir_import(
|
return Err(errors::err_unsupported_dir_import(
|
||||||
&path.display().to_string(),
|
resolved.as_str(),
|
||||||
&to_file_path_string(base),
|
base.as_str(),
|
||||||
));
|
));
|
||||||
} else if !is_file {
|
} else if !is_file {
|
||||||
return Err(errors::err_module_not_found(
|
return Err(errors::err_module_not_found(
|
||||||
&path.display().to_string(),
|
resolved.as_str(),
|
||||||
base.as_str(),
|
base.as_str(),
|
||||||
"module",
|
"module",
|
||||||
));
|
));
|
||||||
|
|
|
@ -104,6 +104,24 @@ pub(crate) fn load_cjs_module(
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn add_global_require(
|
||||||
|
js_runtime: &mut JsRuntime,
|
||||||
|
main_module: &str,
|
||||||
|
) -> Result<(), AnyError> {
|
||||||
|
let source_code = &format!(
|
||||||
|
r#"(async function setupGlobalRequire(main) {{
|
||||||
|
const Module = await import("{}");
|
||||||
|
const require = Module.createRequire(main);
|
||||||
|
globalThis.require = require;
|
||||||
|
}})('{}');"#,
|
||||||
|
MODULE_URL_STR.as_str(),
|
||||||
|
escape_for_single_quote_string(main_module),
|
||||||
|
);
|
||||||
|
|
||||||
|
js_runtime.execute_script(&located_script_name!(), source_code)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
fn escape_for_single_quote_string(text: &str) -> String {
|
fn escape_for_single_quote_string(text: &str) -> String {
|
||||||
text.replace(r"\", r"\\").replace("'", r"\'")
|
text.replace(r"\", r"\\").replace("'", r"\'")
|
||||||
}
|
}
|
||||||
|
|
|
@ -948,6 +948,7 @@ async fn run_repl(flags: Flags, repl_flags: ReplFlags) -> Result<(), AnyError> {
|
||||||
create_main_worker(&ps, main_module.clone(), permissions, None);
|
create_main_worker(&ps, main_module.clone(), permissions, None);
|
||||||
if flags.compat {
|
if flags.compat {
|
||||||
worker.execute_side_module(&compat::GLOBAL_URL).await?;
|
worker.execute_side_module(&compat::GLOBAL_URL).await?;
|
||||||
|
compat::add_global_require(&mut worker.js_runtime, main_module.as_str())?;
|
||||||
}
|
}
|
||||||
worker.run_event_loop(false).await?;
|
worker.run_event_loop(false).await?;
|
||||||
|
|
||||||
|
|
|
@ -167,6 +167,7 @@ pub struct Inner {
|
||||||
pub broadcast_channel: InMemoryBroadcastChannel,
|
pub broadcast_channel: InMemoryBroadcastChannel,
|
||||||
pub shared_array_buffer_store: SharedArrayBufferStore,
|
pub shared_array_buffer_store: SharedArrayBufferStore,
|
||||||
pub compiled_wasm_module_store: CompiledWasmModuleStore,
|
pub compiled_wasm_module_store: CompiledWasmModuleStore,
|
||||||
|
maybe_resolver: Option<Arc<dyn deno_graph::source::Resolver + Send + Sync>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Deref for ProcState {
|
impl Deref for ProcState {
|
||||||
|
@ -313,6 +314,34 @@ impl ProcState {
|
||||||
.clone()
|
.clone()
|
||||||
.or_else(|| env::var("DENO_UNSTABLE_COVERAGE_DIR").ok());
|
.or_else(|| env::var("DENO_UNSTABLE_COVERAGE_DIR").ok());
|
||||||
|
|
||||||
|
// FIXME(bartlomieju): `NodeEsmResolver` is not aware of JSX resolver
|
||||||
|
// created below
|
||||||
|
let node_resolver = NodeEsmResolver::new(
|
||||||
|
maybe_import_map.clone().map(ImportMapResolver::new),
|
||||||
|
);
|
||||||
|
let maybe_import_map_resolver =
|
||||||
|
maybe_import_map.clone().map(ImportMapResolver::new);
|
||||||
|
let maybe_jsx_resolver = maybe_config_file
|
||||||
|
.as_ref()
|
||||||
|
.map(|cf| {
|
||||||
|
cf.to_maybe_jsx_import_source_module()
|
||||||
|
.map(|im| JsxResolver::new(im, maybe_import_map_resolver.clone()))
|
||||||
|
})
|
||||||
|
.flatten();
|
||||||
|
let maybe_resolver: Option<
|
||||||
|
Arc<dyn deno_graph::source::Resolver + Send + Sync>,
|
||||||
|
> = if flags.compat {
|
||||||
|
Some(Arc::new(node_resolver))
|
||||||
|
} else if let Some(jsx_resolver) = maybe_jsx_resolver {
|
||||||
|
// the JSX resolver offloads to the import map if present, otherwise uses
|
||||||
|
// the default Deno explicit import resolution.
|
||||||
|
Some(Arc::new(jsx_resolver))
|
||||||
|
} else if let Some(import_map_resolver) = maybe_import_map_resolver {
|
||||||
|
Some(Arc::new(import_map_resolver))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
Ok(ProcState(Arc::new(Inner {
|
Ok(ProcState(Arc::new(Inner {
|
||||||
dir,
|
dir,
|
||||||
coverage_dir,
|
coverage_dir,
|
||||||
|
@ -328,6 +357,7 @@ impl ProcState {
|
||||||
broadcast_channel,
|
broadcast_channel,
|
||||||
shared_array_buffer_store,
|
shared_array_buffer_store,
|
||||||
compiled_wasm_module_store,
|
compiled_wasm_module_store,
|
||||||
|
maybe_resolver,
|
||||||
})))
|
})))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -395,30 +425,12 @@ impl ProcState {
|
||||||
);
|
);
|
||||||
let maybe_locker = as_maybe_locker(self.lockfile.clone());
|
let maybe_locker = as_maybe_locker(self.lockfile.clone());
|
||||||
let maybe_imports = self.get_maybe_imports()?;
|
let maybe_imports = self.get_maybe_imports()?;
|
||||||
let node_resolver = NodeEsmResolver::new(
|
let maybe_resolver: Option<&dyn deno_graph::source::Resolver> =
|
||||||
self.maybe_import_map.clone().map(ImportMapResolver::new),
|
if let Some(resolver) = &self.maybe_resolver {
|
||||||
);
|
Some(resolver.as_ref())
|
||||||
let maybe_import_map_resolver =
|
} else {
|
||||||
self.maybe_import_map.clone().map(ImportMapResolver::new);
|
None
|
||||||
let maybe_jsx_resolver = self
|
};
|
||||||
.maybe_config_file
|
|
||||||
.as_ref()
|
|
||||||
.map(|cf| {
|
|
||||||
cf.to_maybe_jsx_import_source_module()
|
|
||||||
.map(|im| JsxResolver::new(im, maybe_import_map_resolver.clone()))
|
|
||||||
})
|
|
||||||
.flatten();
|
|
||||||
let maybe_resolver = if self.flags.compat {
|
|
||||||
Some(node_resolver.as_resolver())
|
|
||||||
} else if maybe_jsx_resolver.is_some() {
|
|
||||||
// the JSX resolver offloads to the import map if present, otherwise uses
|
|
||||||
// the default Deno explicit import resolution.
|
|
||||||
maybe_jsx_resolver.as_ref().map(|jr| jr.as_resolver())
|
|
||||||
} else {
|
|
||||||
maybe_import_map_resolver
|
|
||||||
.as_ref()
|
|
||||||
.map(|im| im.as_resolver())
|
|
||||||
};
|
|
||||||
let graph = create_graph(
|
let graph = create_graph(
|
||||||
roots.clone(),
|
roots.clone(),
|
||||||
is_dynamic,
|
is_dynamic,
|
||||||
|
@ -637,18 +649,26 @@ impl ProcState {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME(bartlomieju): hacky way to provide compatibility with repl
|
// FIXME(bartlomieju): this is a hacky way to provide compatibility with REPL
|
||||||
|
// and `Deno.core.evalContext` API. Ideally we should always have a referrer filled
|
||||||
|
// but sadly that's not the case due to missing APIs in V8.
|
||||||
let referrer = if referrer.is_empty() && self.flags.repl {
|
let referrer = if referrer.is_empty() && self.flags.repl {
|
||||||
deno_core::DUMMY_SPECIFIER
|
deno_core::resolve_url_or_path("./$deno$repl.ts").unwrap()
|
||||||
} else {
|
} else {
|
||||||
referrer
|
deno_core::resolve_url_or_path(referrer).unwrap()
|
||||||
};
|
};
|
||||||
if let Some(import_map) = &self.maybe_import_map {
|
|
||||||
import_map
|
let maybe_resolver: Option<&dyn deno_graph::source::Resolver> =
|
||||||
.resolve(specifier, referrer)
|
if let Some(resolver) = &self.maybe_resolver {
|
||||||
.map_err(|err| err.into())
|
Some(resolver.as_ref())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
if let Some(resolver) = &maybe_resolver {
|
||||||
|
resolver.resolve(specifier, &referrer)
|
||||||
} else {
|
} else {
|
||||||
deno_core::resolve_import(specifier, referrer).map_err(|err| err.into())
|
deno_core::resolve_import(specifier, referrer.as_str())
|
||||||
|
.map_err(|err| err.into())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -46,6 +46,12 @@ itest!(compat_dyn_import_rejects_with_node_compatible_error {
|
||||||
envs: vec![("DENO_NODE_COMPAT_URL".to_string(), std_file_url())],
|
envs: vec![("DENO_NODE_COMPAT_URL".to_string(), std_file_url())],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
itest!(import_esm_from_cjs {
|
||||||
|
args:
|
||||||
|
"run --compat --unstable -A --quiet compat/import_esm_from_cjs/index.js",
|
||||||
|
output_str: Some("function\n"),
|
||||||
|
});
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn globals_in_repl() {
|
fn globals_in_repl() {
|
||||||
let (out, _err) = util::run_and_collect_output_with_args(
|
let (out, _err) = util::run_and_collect_output_with_args(
|
||||||
|
@ -58,6 +64,20 @@ fn globals_in_repl() {
|
||||||
assert!(out.contains("true"));
|
assert!(out.contains("true"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn require_in_repl() {
|
||||||
|
let (out, _err) = util::run_and_collect_output_with_args(
|
||||||
|
true,
|
||||||
|
vec!["repl", "--compat", "--unstable", "--quiet"],
|
||||||
|
Some(vec![
|
||||||
|
"const foo = require('./compat/import_esm_from_cjs/index');",
|
||||||
|
]),
|
||||||
|
None,
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
assert!(out.contains("function"));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn node_compat_url() {
|
fn node_compat_url() {
|
||||||
let (out, err) = util::run_and_collect_output_with_args(
|
let (out, err) = util::run_and_collect_output_with_args(
|
||||||
|
|
|
@ -1,2 +1,2 @@
|
||||||
TypeError: Cannot load module "file:///[WILDCARD]/testdata/compat/foobar.js".
|
TypeError: [ERR_MODULE_NOT_FOUND] Cannot find module "file://[WILDCARD]/testdata/compat/foobar.js" imported from "file://[WILDCARD]/testdata/compat/dyn_import_reject.js"
|
||||||
ERR_MODULE_NOT_FOUND
|
ERR_MODULE_NOT_FOUND
|
||||||
|
|
1
cli/tests/testdata/compat/import_esm_from_cjs/index.js
vendored
Normal file
1
cli/tests/testdata/compat/import_esm_from_cjs/index.js
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
require("pure-cjs");
|
4
cli/tests/testdata/compat/import_esm_from_cjs/node_modules/pure-cjs/index.js
generated
vendored
Normal file
4
cli/tests/testdata/compat/import_esm_from_cjs/node_modules/pure-cjs/index.js
generated
vendored
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
async function run() {
|
||||||
|
const _result = await import('pure-esm');
|
||||||
|
}
|
||||||
|
run()
|
4
cli/tests/testdata/compat/import_esm_from_cjs/node_modules/pure-cjs/package.json
generated
vendored
Normal file
4
cli/tests/testdata/compat/import_esm_from_cjs/node_modules/pure-cjs/package.json
generated
vendored
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
{
|
||||||
|
"name": "pure-cjs",
|
||||||
|
"main": "./index.js"
|
||||||
|
}
|
2
cli/tests/testdata/compat/import_esm_from_cjs/node_modules/pure-esm/index.js
generated
vendored
Normal file
2
cli/tests/testdata/compat/import_esm_from_cjs/node_modules/pure-esm/index.js
generated
vendored
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
import fs from 'node:fs';
|
||||||
|
console.log(typeof fs.chmod);
|
5
cli/tests/testdata/compat/import_esm_from_cjs/node_modules/pure-esm/package.json
generated
vendored
Normal file
5
cli/tests/testdata/compat/import_esm_from_cjs/node_modules/pure-esm/package.json
generated
vendored
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
{
|
||||||
|
"name": "pure-esm",
|
||||||
|
"type": "module",
|
||||||
|
"main": "./index.js"
|
||||||
|
}
|
Loading…
Add table
Reference in a new issue