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.
This commit is contained in:
Bartek Iwańczuk 2021-10-05 01:35:55 +02:00 committed by GitHub
parent 64a7187238
commit f1d3a17043
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 157 additions and 4 deletions

4
Cargo.lock generated
View File

@ -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",

View File

@ -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"

60
cli/compat.rs Normal file
View File

@ -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<String, String> {
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
}

View File

@ -222,6 +222,9 @@ pub struct Flags {
pub log_level: Option<Level>,
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()
}
);
}
}

View File

@ -3,6 +3,7 @@
mod ast;
mod auth_tokens;
mod checksum;
mod compat;
mod config_file;
mod deno_dir;
mod diagnostics;

View File

@ -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<ImportMap> =
let mut maybe_import_map: Option<ImportMap> =
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()))

View File

@ -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,
});

View File

@ -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"]

View File

@ -0,0 +1,5 @@
{
"imports": {
"fs/promises": "./non_existent_file.js"
}
}

View File

@ -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]

View File

@ -0,0 +1,3 @@
import fs from "fs/promises";
const data = await fs.readFile("compat/test.txt", "utf-8");
console.log(data);

View File

@ -0,0 +1,2 @@
[WILDCARD]
This is some example text that will be read using compatiblity mode.

1
cli/tests/testdata/compat/test.txt vendored Normal file
View File

@ -0,0 +1 @@
This is some example text that will be read using compatiblity mode.

View File

@ -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,