diff --git a/Cargo.lock b/Cargo.lock index e8284da3c780a7..dc0deec738ef20 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1720,9 +1720,9 @@ checksum = "cb56e1aa765b4b4f3aadfab769793b7087bb03a4ea4920644a6d238e2df5b9ed" [[package]] name = "import_map" -version = "0.3.0" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae79a7af71e92e4038963b64884106d28d508da6bf3c7f36294efcb3bb3b003d" +checksum = "d315210af92bcde7a84672d5554fc2b4268c4d40dc9c930ae1d1ed765a8f6381" dependencies = [ "indexmap", "log", diff --git a/cli/Cargo.toml b/cli/Cargo.toml index cdc3564526eac4..c7cc426fc843a2 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -59,7 +59,7 @@ encoding_rs = "0.8.28" env_logger = "0.8.4" fancy-regex = "0.7.1" http = "0.2.4" -import_map = "0.3.0" +import_map = "0.3.3" jsonc-parser = { version = "0.17.0", features = ["serde"] } lazy_static = "1.4.0" libc = "0.2.101" diff --git a/cli/compat.rs b/cli/compat.rs new file mode 100644 index 00000000000000..a3d16538dc0ca4 --- /dev/null +++ b/cli/compat.rs @@ -0,0 +1,60 @@ +// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. + +use std::collections::HashMap; + +static SUPPORTED_MODULES: &[&str] = &[ + "assert", + "assert/strict", + "async_hooks", + "buffer", + "child_process", + "cluster", + "console", + "constants", + "crypto", + "dgram", + "dns", + "domain", + "events", + "fs", + "fs/promises", + "http", + "https", + "module", + "net", + "os", + "path", + "path/posix", + "path/win32", + "perf_hooks", + "process", + "querystring", + "readline", + "stream", + "stream/promises", + "stream/web", + "string_decoder", + "sys", + "timers", + "timers/promises", + "tls", + "tty", + "url", + "util", + "util/types", + "v8", + "vm", + "zlib", +]; + +pub fn get_mapped_node_builtins() -> HashMap { + let mut mappings = HashMap::new(); + + for module in SUPPORTED_MODULES { + // TODO(bartlomieju): this is unversioned, and should be fixed to use latest stable? + let module_url = format!("https://deno.land/std/node/{}.ts", module); + mappings.insert(module.to_string(), module_url); + } + + mappings +} diff --git a/cli/flags.rs b/cli/flags.rs index aaff45388ba866..dc0e932eff46f8 100644 --- a/cli/flags.rs +++ b/cli/flags.rs @@ -222,6 +222,9 @@ pub struct Flags { pub log_level: Option, pub no_check: bool, pub no_remote: bool, + /// If true, a list of Node built-in modules will be injected into + /// the import map. + pub compat: bool, pub prompt: bool, pub reload: bool, pub repl: bool, @@ -1490,6 +1493,7 @@ fn runtime_args<'a, 'b>( .arg(v8_flags_arg()) .arg(seed_arg()) .arg(enable_testing_features_arg()) + .arg(compat_arg()) } fn inspect_args<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> { @@ -1619,6 +1623,12 @@ fn seed_arg<'a, 'b>() -> Arg<'a, 'b> { }) } +fn compat_arg<'a, 'b>() -> Arg<'a, 'b> { + Arg::with_name("compat") + .long("compat") + .help("Node compatibility mode. Currently only enables built-in node modules like 'fs'.") +} + fn watch_arg<'a, 'b>() -> Arg<'a, 'b> { Arg::with_name("watch") .long("watch") @@ -2228,6 +2238,7 @@ fn runtime_args_parse( location_arg_parse(flags, matches); v8_flags_arg_parse(flags, matches); seed_arg_parse(flags, matches); + compat_arg_parse(flags, matches); inspect_arg_parse(flags, matches); enable_testing_features_arg_parse(flags, matches); } @@ -2313,6 +2324,12 @@ fn seed_arg_parse(flags: &mut Flags, matches: &ArgMatches) { } } +fn compat_arg_parse(flags: &mut Flags, matches: &ArgMatches) { + if matches.is_present("compat") { + flags.compat = true; + } +} + fn no_check_arg_parse(flags: &mut Flags, matches: &clap::ArgMatches) { if matches.is_present("no-check") { flags.no_check = true; @@ -4431,4 +4448,19 @@ mod tests { .to_string() .contains("Expected protocol \"http\" or \"https\"")); } + + #[test] + fn compat() { + let r = flags_from_vec(svec!["deno", "run", "--compat", "foo.js"]); + assert_eq!( + r.unwrap(), + Flags { + subcommand: DenoSubcommand::Run(RunFlags { + script: "foo.js".to_string(), + }), + compat: true, + ..Flags::default() + } + ); + } } diff --git a/cli/main.rs b/cli/main.rs index 9176aca6f0b0bb..52e3f6e955fcdd 100644 --- a/cli/main.rs +++ b/cli/main.rs @@ -3,6 +3,7 @@ mod ast; mod auth_tokens; mod checksum; +mod compat; mod config_file; mod deno_dir; mod diagnostics; diff --git a/cli/proc_state.rs b/cli/proc_state.rs index 2c61ba51f60b0e..48dc335f0bf00d 100644 --- a/cli/proc_state.rs +++ b/cli/proc_state.rs @@ -36,6 +36,7 @@ use deno_tls::rustls_native_certs::load_native_certs; use deno_tls::webpki_roots::TLS_SERVER_ROOTS; use import_map::ImportMap; use log::debug; +use log::info; use log::warn; use std::collections::HashMap; use std::collections::HashSet; @@ -182,7 +183,7 @@ impl ProcState { None }; - let maybe_import_map: Option = + let mut maybe_import_map: Option = match flags.import_map_path.as_ref() { None => None, Some(import_map_url) => { @@ -204,6 +205,32 @@ impl ProcState { } }; + if flags.compat { + let mut import_map = match maybe_import_map { + Some(import_map) => import_map, + None => { + // INFO: we're creating an empty import map, with its specifier pointing + // to `CWD/node_import_map.json` to make sure the map still works as expected. + let import_map_specifier = + std::env::current_dir()?.join("node_import_map.json"); + ImportMap::from_json(import_map_specifier.to_str().unwrap(), "{}") + .unwrap() + } + }; + let node_builtins = crate::compat::get_mapped_node_builtins(); + let diagnostics = import_map.update_imports(node_builtins)?; + + if !diagnostics.is_empty() { + info!("Some Node built-ins were not added to the import map:"); + for diagnostic in diagnostics { + info!(" - {}", diagnostic); + } + info!("If you want to use Node built-ins provided by Deno remove listed specifiers from \"imports\" mapping in the import map file."); + } + + maybe_import_map = Some(import_map); + } + let maybe_inspect_host = flags.inspect.or(flags.inspect_brk); let maybe_inspector_server = maybe_inspect_host.map(|host| { Arc::new(InspectorServer::new(host, version::get_user_agent())) diff --git a/cli/tests/integration/compat_tests.rs b/cli/tests/integration/compat_tests.rs new file mode 100644 index 00000000000000..4d93d370d25abc --- /dev/null +++ b/cli/tests/integration/compat_tests.rs @@ -0,0 +1,14 @@ +// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. + +use crate::itest; + +itest!(fs_promises { + args: "run --compat --unstable -A compat/fs_promises.js", + output: "compat/fs_promises.out", +}); + +itest!(existing_import_map { + args: "run --compat --import-map compat/existing_import_map.json compat/fs_promises.js", + output: "compat/existing_import_map.out", + exit_code: 1, +}); diff --git a/cli/tests/integration/mod.rs b/cli/tests/integration/mod.rs index e3450c8a1245b8..65a8a7516135e0 100644 --- a/cli/tests/integration/mod.rs +++ b/cli/tests/integration/mod.rs @@ -54,6 +54,8 @@ macro_rules! itest_flaky( mod bundle; #[path = "cache_tests.rs"] mod cache; +#[path = "compat_tests.rs"] +mod compat; #[path = "compile_tests.rs"] mod compile; #[path = "coverage_tests.rs"] diff --git a/cli/tests/testdata/compat/existing_import_map.json b/cli/tests/testdata/compat/existing_import_map.json new file mode 100644 index 00000000000000..db59c0cc2400d7 --- /dev/null +++ b/cli/tests/testdata/compat/existing_import_map.json @@ -0,0 +1,5 @@ +{ + "imports": { + "fs/promises": "./non_existent_file.js" + } +} diff --git a/cli/tests/testdata/compat/existing_import_map.out b/cli/tests/testdata/compat/existing_import_map.out new file mode 100644 index 00000000000000..0e319b11509f50 --- /dev/null +++ b/cli/tests/testdata/compat/existing_import_map.out @@ -0,0 +1,5 @@ +[WILDCARD] +Some Node built-ins were not added to the import map: + - "fs/promises" already exists and is mapped to "[WILDCARD]non_existent_file.js" +If you want to use Node built-ins provided by Deno remove listed specifiers from "imports" mapping in the import map file. +error: Cannot resolve module [WILDCARD] \ No newline at end of file diff --git a/cli/tests/testdata/compat/fs_promises.js b/cli/tests/testdata/compat/fs_promises.js new file mode 100644 index 00000000000000..3f7b4c93505eb2 --- /dev/null +++ b/cli/tests/testdata/compat/fs_promises.js @@ -0,0 +1,3 @@ +import fs from "fs/promises"; +const data = await fs.readFile("compat/test.txt", "utf-8"); +console.log(data); diff --git a/cli/tests/testdata/compat/fs_promises.out b/cli/tests/testdata/compat/fs_promises.out new file mode 100644 index 00000000000000..368d06776cf2a0 --- /dev/null +++ b/cli/tests/testdata/compat/fs_promises.out @@ -0,0 +1,2 @@ +[WILDCARD] +This is some example text that will be read using compatiblity mode. diff --git a/cli/tests/testdata/compat/test.txt b/cli/tests/testdata/compat/test.txt new file mode 100644 index 00000000000000..422e7b0c0c152a --- /dev/null +++ b/cli/tests/testdata/compat/test.txt @@ -0,0 +1 @@ +This is some example text that will be read using compatiblity mode. \ No newline at end of file diff --git a/cli/tools/standalone.rs b/cli/tools/standalone.rs index 33bc7881ed588f..fb07254aaf8b12 100644 --- a/cli/tools/standalone.rs +++ b/cli/tools/standalone.rs @@ -227,6 +227,7 @@ pub fn compile_to_runtime_flags( lock: None, log_level: flags.log_level, no_check: false, + compat: flags.compat, unsafely_ignore_certificate_errors: flags .unsafely_ignore_certificate_errors, no_remote: false,