From f1d3a170430501b4fab1a2d2abb5d77528251c77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Tue, 5 Oct 2021 01:35:55 +0200 Subject: [PATCH] feat: add --compat flag to provide built-in Node modules (#12293) This commit adds "--compat" flag. When the flag is passed a set of mappings for built-in Node modules is injected into the import map. If user doesn't explicitly provide an import map (using "--import-map" flag) then a map is created on the fly. If there are already existing mappings in import map that would clash with built-in Node modules a set of diagnostics is printed to the terminal with suggestions how to proceed. --- Cargo.lock | 4 +- cli/Cargo.toml | 2 +- cli/compat.rs | 60 +++++++++++++++++++ cli/flags.rs | 32 ++++++++++ cli/main.rs | 1 + cli/proc_state.rs | 29 ++++++++- cli/tests/integration/compat_tests.rs | 14 +++++ cli/tests/integration/mod.rs | 2 + .../testdata/compat/existing_import_map.json | 5 ++ .../testdata/compat/existing_import_map.out | 5 ++ cli/tests/testdata/compat/fs_promises.js | 3 + cli/tests/testdata/compat/fs_promises.out | 2 + cli/tests/testdata/compat/test.txt | 1 + cli/tools/standalone.rs | 1 + 14 files changed, 157 insertions(+), 4 deletions(-) create mode 100644 cli/compat.rs create mode 100644 cli/tests/integration/compat_tests.rs create mode 100644 cli/tests/testdata/compat/existing_import_map.json create mode 100644 cli/tests/testdata/compat/existing_import_map.out create mode 100644 cli/tests/testdata/compat/fs_promises.js create mode 100644 cli/tests/testdata/compat/fs_promises.out create mode 100644 cli/tests/testdata/compat/test.txt diff --git a/Cargo.lock b/Cargo.lock index e8284da3c7..dc0deec738 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 cdc3564526..c7cc426fc8 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 0000000000..a3d16538dc --- /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 aaff45388b..dc0e932eff 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 9176aca6f0..52e3f6e955 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 2c61ba51f6..48dc335f0b 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 0000000000..4d93d370d2 --- /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 e3450c8a12..65a8a75161 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 0000000000..db59c0cc24 --- /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 0000000000..0e319b1150 --- /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 0000000000..3f7b4c9350 --- /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 0000000000..368d06776c --- /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 0000000000..422e7b0c0c --- /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 33bc7881ed..fb07254aaf 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,