feat: support node built-in module imports (#17264)

Co-authored-by: David Sherret <dsherret@gmail.com>
This commit is contained in:
Bartek Iwańczuk 2023-01-24 15:05:54 +01:00 committed by GitHub
parent cadeaae045
commit fc2e00152b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
32 changed files with 925 additions and 445 deletions

View file

@ -17,6 +17,7 @@ mod ts {
use deno_core::error::AnyError;
use deno_core::op;
use deno_core::OpState;
use deno_runtime::deno_node::SUPPORTED_BUILTIN_NODE_MODULES;
use regex::Regex;
use serde::Deserialize;
use serde_json::json;
@ -164,10 +165,16 @@ mod ts {
#[op]
fn op_build_info(state: &mut OpState) -> Value {
let build_specifier = "asset:///bootstrap.ts";
let node_built_in_module_names = SUPPORTED_BUILTIN_NODE_MODULES
.iter()
.map(|s| s.name)
.collect::<Vec<&str>>();
let build_libs = state.borrow::<Vec<&str>>();
json!({
"buildSpecifier": build_specifier,
"libs": build_libs,
"nodeBuiltInModuleNames": node_built_in_module_names,
})
}

23
cli/cache/mod.rs vendored
View file

@ -65,7 +65,7 @@ impl FetchCacher {
impl Loader for FetchCacher {
fn get_cache_info(&self, specifier: &ModuleSpecifier) -> Option<CacheInfo> {
if specifier.scheme() == "npm" {
if matches!(specifier.scheme(), "npm" | "node") {
return None;
}
@ -101,7 +101,26 @@ impl Loader for FetchCacher {
));
}
let specifier = specifier.clone();
let specifier =
if let Some(module_name) = specifier.as_str().strip_prefix("node:") {
if module_name == "module" {
// the source code for "node:module" is built-in rather than
// being from deno_std like the other modules
return Box::pin(futures::future::ready(Ok(Some(
deno_graph::source::LoadResponse::External {
specifier: specifier.clone(),
},
))));
}
match crate::node::resolve_builtin_node_module(module_name) {
Ok(specifier) => specifier,
Err(err) => return Box::pin(futures::future::ready(Err(err))),
}
} else {
specifier.clone()
};
let permissions = if is_dynamic {
self.dynamic_permissions.clone()
} else {

View file

@ -8,7 +8,7 @@ use crate::cache;
use crate::cache::TypeCheckCache;
use crate::colors;
use crate::errors::get_error_class_name;
use crate::npm::resolve_npm_package_reqs;
use crate::npm::resolve_graph_npm_info;
use crate::npm::NpmPackageReference;
use crate::npm::NpmPackageReq;
use crate::proc_state::ProcState;
@ -25,6 +25,7 @@ use deno_graph::GraphImport;
use deno_graph::MediaType;
use deno_graph::ModuleGraph;
use deno_graph::ModuleGraphError;
use deno_graph::ModuleKind;
use deno_graph::Range;
use deno_graph::Resolved;
use deno_runtime::permissions::PermissionsContainer;
@ -54,7 +55,10 @@ pub enum ModuleEntry {
#[derive(Debug, Default)]
pub struct GraphData {
modules: HashMap<ModuleSpecifier, ModuleEntry>,
/// Specifiers that are built-in or external.
external_specifiers: HashSet<ModuleSpecifier>,
npm_packages: Vec<NpmPackageReq>,
has_node_builtin_specifier: bool,
/// Map of first known referrer locations for each module. Used to enhance
/// error messages.
referrer_map: HashMap<ModuleSpecifier, Box<Range>>,
@ -83,13 +87,12 @@ impl GraphData {
let mut has_npm_specifier_in_graph = false;
for (specifier, result) in graph.specifiers() {
if NpmPackageReference::from_specifier(specifier).is_ok() {
has_npm_specifier_in_graph = true;
if self.modules.contains_key(specifier) {
continue;
}
if self.modules.contains_key(specifier) {
continue;
if !self.has_node_builtin_specifier && specifier.scheme() == "node" {
self.has_node_builtin_specifier = true;
}
if let Some(found) = graph.redirects.get(specifier) {
@ -97,8 +100,19 @@ impl GraphData {
self.modules.insert(specifier.clone(), module_entry);
continue;
}
match result {
Ok((_, _, media_type)) => {
Ok((_, module_kind, media_type)) => {
if module_kind == ModuleKind::External {
if !has_npm_specifier_in_graph
&& NpmPackageReference::from_specifier(specifier).is_ok()
{
has_npm_specifier_in_graph = true;
}
self.external_specifiers.insert(specifier.clone());
continue; // ignore npm and node specifiers
}
let module = graph.get(specifier).unwrap();
let code = match &module.maybe_source {
Some(source) => source.clone(),
@ -147,7 +161,9 @@ impl GraphData {
}
if has_npm_specifier_in_graph {
self.npm_packages.extend(resolve_npm_package_reqs(graph));
self
.npm_packages
.extend(resolve_graph_npm_info(graph).package_reqs);
}
}
@ -157,6 +173,11 @@ impl GraphData {
self.modules.iter()
}
/// Gets if the graph had a "node:" specifier.
pub fn has_node_builtin_specifier(&self) -> bool {
self.has_node_builtin_specifier
}
/// Gets the npm package requirements from all the encountered graphs
/// in the order that they should be resolved.
pub fn npm_package_reqs(&self) -> &Vec<NpmPackageReq> {
@ -195,13 +216,14 @@ impl GraphData {
}
}
while let Some(specifier) = visiting.pop_front() {
if NpmPackageReference::from_specifier(specifier).is_ok() {
continue; // skip analyzing npm specifiers
}
let (specifier, entry) = match self.modules.get_key_value(specifier) {
Some(pair) => pair,
None => return None,
None => {
if self.external_specifiers.contains(specifier) {
continue;
}
return None;
}
};
result.insert(specifier, entry);
match entry {
@ -281,6 +303,8 @@ impl GraphData {
}
Some(Self {
modules,
external_specifiers: self.external_specifiers.clone(),
has_node_builtin_specifier: self.has_node_builtin_specifier,
npm_packages: self.npm_packages.clone(),
referrer_map,
graph_imports: self.graph_imports.to_vec(),
@ -547,6 +571,14 @@ pub async fn create_graph_and_maybe_check(
}
if ps.options.type_check_mode() != TypeCheckMode::None {
// node built-in specifiers use the @types/node package to determine
// types, so inject that now after the lockfile has been written
if graph_data.has_node_builtin_specifier() {
ps.npm_resolver
.inject_synthetic_types_node_package()
.await?;
}
let ts_config_result =
ps.options.resolve_ts_config_for_emit(TsConfigType::Check {
lib: ps.options.ts_type_lib_window(),

View file

@ -68,7 +68,7 @@ impl CacheMetadata {
&self,
specifier: &ModuleSpecifier,
) -> Option<Arc<HashMap<MetadataKey, String>>> {
if specifier.scheme() == "file" || specifier.scheme() == "npm" {
if matches!(specifier.scheme(), "file" | "npm" | "node") {
return None;
}
let version = self

View file

@ -13,6 +13,7 @@ use super::tsc;
use super::tsc::TsServer;
use crate::args::LintOptions;
use crate::node;
use crate::npm::NpmPackageReference;
use crate::tools::lint::get_configured_rules;
@ -614,6 +615,8 @@ pub enum DenoDiagnostic {
},
/// An error occurred when resolving the specifier string.
ResolutionError(deno_graph::ResolutionError),
/// Invalid `node:` specifier.
InvalidNodeSpecifier(ModuleSpecifier),
}
impl DenoDiagnostic {
@ -641,6 +644,7 @@ impl DenoDiagnostic {
},
ResolutionError::ResolverError { .. } => "resolver-error",
},
Self::InvalidNodeSpecifier(_) => "resolver-error",
}
}
@ -791,6 +795,7 @@ impl DenoDiagnostic {
Self::NoLocal(specifier) => (lsp::DiagnosticSeverity::ERROR, format!("Unable to load a local module: \"{}\".\n Please check the file path.", specifier), None),
Self::Redirect { from, to} => (lsp::DiagnosticSeverity::INFORMATION, format!("The import of \"{}\" was redirected to \"{}\".", from, to), Some(json!({ "specifier": from, "redirect": to }))),
Self::ResolutionError(err) => (lsp::DiagnosticSeverity::ERROR, err.to_string(), None),
Self::InvalidNodeSpecifier(specifier) => (lsp::DiagnosticSeverity::ERROR, format!("Unknown Node built-in module: {}", specifier.path()), None),
};
lsp::Diagnostic {
range: *range,
@ -872,6 +877,30 @@ fn diagnose_resolved(
);
}
}
} else if let Some(module_name) = specifier.as_str().strip_prefix("node:")
{
if node::resolve_builtin_node_module(module_name).is_err() {
diagnostics.push(
DenoDiagnostic::InvalidNodeSpecifier(specifier.clone())
.to_lsp_diagnostic(&range),
);
} else if let Some(npm_resolver) = &snapshot.maybe_npm_resolver {
// check that a @types/node package exists in the resolver
let types_node_ref =
NpmPackageReference::from_str("npm:@types/node").unwrap();
if npm_resolver
.resolve_package_folder_from_deno_module(&types_node_ref.req)
.is_err()
{
diagnostics.push(
DenoDiagnostic::NoCacheNpm(
types_node_ref,
ModuleSpecifier::parse("npm:@types/node").unwrap(),
)
.to_lsp_diagnostic(&range),
);
}
}
} else {
// When the document is not available, it means that it cannot be found
// in the cache or locally on the disk, so we want to issue a diagnostic

View file

@ -770,6 +770,9 @@ pub struct Documents {
maybe_resolver: Option<CliResolver>,
/// The npm package requirements.
npm_reqs: Arc<HashSet<NpmPackageReq>>,
/// Gets if any document had a node: specifier such that a @types/node package
/// should be injected.
has_injected_types_node_package: bool,
/// Resolves a specifier to its final redirected to specifier.
specifier_resolver: Arc<SpecifierResolver>,
}
@ -785,6 +788,7 @@ impl Documents {
imports: Default::default(),
maybe_resolver: None,
npm_reqs: Default::default(),
has_injected_types_node_package: false,
specifier_resolver: Arc::new(SpecifierResolver::new(location)),
}
}
@ -925,6 +929,12 @@ impl Documents {
(*self.npm_reqs).clone()
}
/// Returns if a @types/node package was injected into the npm
/// resolver based on the state of the documents.
pub fn has_injected_types_node_package(&self) -> bool {
self.has_injected_types_node_package
}
/// Return a document for the specifier.
pub fn get(&self, original_specifier: &ModuleSpecifier) -> Option<Document> {
let specifier = self.specifier_resolver.resolve(original_specifier)?;
@ -985,11 +995,15 @@ impl Documents {
/// tsc when type checking.
pub fn resolve(
&self,
specifiers: &[String],
referrer: &ModuleSpecifier,
specifiers: Vec<String>,
referrer_doc: &AssetOrDocument,
maybe_npm_resolver: Option<&NpmPackageResolver>,
) -> Option<Vec<Option<(ModuleSpecifier, MediaType)>>> {
let dependencies = self.get(referrer)?.0.dependencies.clone();
) -> Vec<Option<(ModuleSpecifier, MediaType)>> {
let referrer = referrer_doc.specifier();
let dependencies = match referrer_doc {
AssetOrDocument::Asset(_) => None,
AssetOrDocument::Document(doc) => Some(doc.0.dependencies.clone()),
};
let mut results = Vec::new();
for specifier in specifiers {
if let Some(npm_resolver) = maybe_npm_resolver {
@ -997,7 +1011,7 @@ impl Documents {
// we're in an npm package, so use node resolution
results.push(Some(NodeResolution::into_specifier_and_media_type(
node::node_resolve(
specifier,
&specifier,
referrer,
NodeResolutionMode::Types,
npm_resolver,
@ -1009,15 +1023,28 @@ impl Documents {
continue;
}
}
// handle npm:<package> urls
if let Some(module_name) = specifier.strip_prefix("node:") {
if crate::node::resolve_builtin_node_module(module_name).is_ok() {
// return itself for node: specifiers because during type checking
// we resolve to the ambient modules in the @types/node package
// rather than deno_std/node
results.push(Some((
ModuleSpecifier::parse(&specifier).unwrap(),
MediaType::Dts,
)));
continue;
}
}
if specifier.starts_with("asset:") {
if let Ok(specifier) = ModuleSpecifier::parse(specifier) {
if let Ok(specifier) = ModuleSpecifier::parse(&specifier) {
let media_type = MediaType::from(&specifier);
results.push(Some((specifier, media_type)));
} else {
results.push(None);
}
} else if let Some(dep) = dependencies.deps.get(specifier) {
} else if let Some(dep) =
dependencies.as_ref().and_then(|d| d.deps.get(&specifier))
{
if let Resolved::Ok { specifier, .. } = &dep.maybe_type {
results.push(self.resolve_dependency(specifier, maybe_npm_resolver));
} else if let Resolved::Ok { specifier, .. } = &dep.maybe_code {
@ -1026,12 +1053,12 @@ impl Documents {
results.push(None);
}
} else if let Some(Resolved::Ok { specifier, .. }) =
self.resolve_imports_dependency(specifier)
self.resolve_imports_dependency(&specifier)
{
// clone here to avoid double borrow of self
let specifier = specifier.clone();
results.push(self.resolve_dependency(&specifier, maybe_npm_resolver));
} else if let Ok(npm_ref) = NpmPackageReference::from_str(specifier) {
} else if let Ok(npm_ref) = NpmPackageReference::from_str(&specifier) {
results.push(maybe_npm_resolver.map(|npm_resolver| {
NodeResolution::into_specifier_and_media_type(
node_resolve_npm_reference(
@ -1048,7 +1075,7 @@ impl Documents {
results.push(None);
}
}
Some(results)
results
}
/// Update the location of the on disk cache for the document store.
@ -1125,6 +1152,7 @@ impl Documents {
analyzed_specifiers: HashSet<ModuleSpecifier>,
pending_specifiers: VecDeque<ModuleSpecifier>,
npm_reqs: HashSet<NpmPackageReq>,
has_node_builtin_specifier: bool,
}
impl DocAnalyzer {
@ -1148,7 +1176,11 @@ impl Documents {
fn analyze_doc(&mut self, specifier: &ModuleSpecifier, doc: &Document) {
self.analyzed_specifiers.insert(specifier.clone());
for dependency in doc.dependencies().values() {
for (name, dependency) in doc.dependencies() {
if !self.has_node_builtin_specifier && name.starts_with("node:") {
self.has_node_builtin_specifier = true;
}
if let Some(dep) = dependency.get_code() {
self.add(dep, specifier);
}
@ -1185,8 +1217,19 @@ impl Documents {
}
}
let mut npm_reqs = doc_analyzer.npm_reqs;
// Ensure a @types/node package exists when any module uses a node: specifier.
// Unlike on the command line, here we just add @types/node to the npm package
// requirements since this won't end up in the lockfile.
self.has_injected_types_node_package = doc_analyzer
.has_node_builtin_specifier
&& !npm_reqs.iter().any(|r| r.name == "@types/node");
if self.has_injected_types_node_package {
npm_reqs.insert(NpmPackageReq::from_str("@types/node").unwrap());
}
self.dependents_map = Arc::new(doc_analyzer.dependents_map);
self.npm_reqs = Arc::new(doc_analyzer.npm_reqs);
self.npm_reqs = Arc::new(npm_reqs);
self.dirty = false;
file_system_docs.dirty = false;
}

View file

@ -216,7 +216,7 @@ fn new_assets_map() -> Arc<Mutex<AssetsMap>> {
let asset = AssetDocument::new(specifier.clone(), v);
(specifier, asset)
})
.collect();
.collect::<AssetsMap>();
Arc::new(Mutex::new(assets))
}
@ -2728,28 +2728,29 @@ fn op_resolve(
let state = state.borrow_mut::<State>();
let mark = state.performance.mark("op_resolve", Some(&args));
let referrer = state.normalize_specifier(&args.base)?;
let result = if let Some(resolved) = state.state_snapshot.documents.resolve(
&args.specifiers,
&referrer,
state.state_snapshot.maybe_npm_resolver.as_ref(),
) {
Ok(
resolved
.into_iter()
.map(|o| {
o.map(|(s, mt)| (s.to_string(), mt.as_ts_extension().to_string()))
})
.collect(),
)
} else {
Err(custom_error(
let result = match state.get_asset_or_document(&referrer) {
Some(referrer_doc) => {
let resolved = state.state_snapshot.documents.resolve(
args.specifiers,
&referrer_doc,
state.state_snapshot.maybe_npm_resolver.as_ref(),
);
Ok(
resolved
.into_iter()
.map(|o| {
o.map(|(s, mt)| (s.to_string(), mt.as_ts_extension().to_string()))
})
.collect(),
)
}
None => Err(custom_error(
"NotFound",
format!(
"Error resolving. Referring specifier \"{}\" was not found.",
args.base
),
))
)),
};
state.performance.measure(mark);
@ -2764,15 +2765,20 @@ fn op_respond(state: &mut OpState, args: Response) -> bool {
}
#[op]
fn op_script_names(state: &mut OpState) -> Vec<ModuleSpecifier> {
fn op_script_names(state: &mut OpState) -> Vec<String> {
let state = state.borrow_mut::<State>();
state
.state_snapshot
.documents
.documents(true, true)
.into_iter()
.map(|d| d.specifier().clone())
.collect()
let documents = &state.state_snapshot.documents;
let open_docs = documents.documents(true, true);
let mut result = Vec::with_capacity(open_docs.len() + 1);
if documents.has_injected_types_node_package() {
// ensure this is first so it resolves the node types first
result.push("asset:///node_types.d.ts".to_string());
}
result.extend(open_docs.into_iter().map(|d| d.specifier().to_string()));
result
}
#[derive(Debug, Deserialize, Serialize)]

View file

@ -25,6 +25,7 @@ use deno_runtime::deno_node::package_imports_resolve;
use deno_runtime::deno_node::package_resolve;
use deno_runtime::deno_node::path_to_declaration_path;
use deno_runtime::deno_node::NodeModuleKind;
use deno_runtime::deno_node::NodeModulePolyfill;
use deno_runtime::deno_node::NodePermissions;
use deno_runtime::deno_node::NodeResolutionMode;
use deno_runtime::deno_node::PackageJson;
@ -32,6 +33,7 @@ use deno_runtime::deno_node::PathClean;
use deno_runtime::deno_node::RequireNpmResolver;
use deno_runtime::deno_node::DEFAULT_CONDITIONS;
use deno_runtime::deno_node::NODE_GLOBAL_THIS_NAME;
use deno_runtime::deno_node::SUPPORTED_BUILTIN_NODE_MODULES;
use deno_runtime::permissions::PermissionsContainer;
use once_cell::sync::Lazy;
use regex::Regex;
@ -106,200 +108,6 @@ impl NodeResolution {
}
}
struct NodeModulePolyfill {
/// Name of the module like "assert" or "timers/promises"
name: &'static str,
/// Specifier relative to the root of `deno_std` repo, like "node/asser.ts"
specifier: &'static str,
}
static SUPPORTED_MODULES: &[NodeModulePolyfill] = &[
NodeModulePolyfill {
name: "assert",
specifier: "node/assert.ts",
},
NodeModulePolyfill {
name: "assert/strict",
specifier: "node/assert/strict.ts",
},
NodeModulePolyfill {
name: "async_hooks",
specifier: "node/async_hooks.ts",
},
NodeModulePolyfill {
name: "buffer",
specifier: "node/buffer.ts",
},
NodeModulePolyfill {
name: "child_process",
specifier: "node/child_process.ts",
},
NodeModulePolyfill {
name: "cluster",
specifier: "node/cluster.ts",
},
NodeModulePolyfill {
name: "console",
specifier: "node/console.ts",
},
NodeModulePolyfill {
name: "constants",
specifier: "node/constants.ts",
},
NodeModulePolyfill {
name: "crypto",
specifier: "node/crypto.ts",
},
NodeModulePolyfill {
name: "dgram",
specifier: "node/dgram.ts",
},
NodeModulePolyfill {
name: "dns",
specifier: "node/dns.ts",
},
NodeModulePolyfill {
name: "dns/promises",
specifier: "node/dns/promises.ts",
},
NodeModulePolyfill {
name: "domain",
specifier: "node/domain.ts",
},
NodeModulePolyfill {
name: "events",
specifier: "node/events.ts",
},
NodeModulePolyfill {
name: "fs",
specifier: "node/fs.ts",
},
NodeModulePolyfill {
name: "fs/promises",
specifier: "node/fs/promises.ts",
},
NodeModulePolyfill {
name: "http",
specifier: "node/http.ts",
},
NodeModulePolyfill {
name: "https",
specifier: "node/https.ts",
},
NodeModulePolyfill {
name: "module",
// NOTE(bartlomieju): `module` is special, because we don't want to use
// `deno_std/node/module.ts`, but instead use a special shim that we
// provide in `ext/node`.
specifier: "[USE `deno_node::MODULE_ES_SHIM` to get this module]",
},
NodeModulePolyfill {
name: "net",
specifier: "node/net.ts",
},
NodeModulePolyfill {
name: "os",
specifier: "node/os.ts",
},
NodeModulePolyfill {
name: "path",
specifier: "node/path.ts",
},
NodeModulePolyfill {
name: "path/posix",
specifier: "node/path/posix.ts",
},
NodeModulePolyfill {
name: "path/win32",
specifier: "node/path/win32.ts",
},
NodeModulePolyfill {
name: "perf_hooks",
specifier: "node/perf_hooks.ts",
},
NodeModulePolyfill {
name: "process",
specifier: "node/process.ts",
},
NodeModulePolyfill {
name: "querystring",
specifier: "node/querystring.ts",
},
NodeModulePolyfill {
name: "readline",
specifier: "node/readline.ts",
},
NodeModulePolyfill {
name: "stream",
specifier: "node/stream.ts",
},
NodeModulePolyfill {
name: "stream/consumers",
specifier: "node/stream/consumers.mjs",
},
NodeModulePolyfill {
name: "stream/promises",
specifier: "node/stream/promises.mjs",
},
NodeModulePolyfill {
name: "stream/web",
specifier: "node/stream/web.ts",
},
NodeModulePolyfill {
name: "string_decoder",
specifier: "node/string_decoder.ts",
},
NodeModulePolyfill {
name: "sys",
specifier: "node/sys.ts",
},
NodeModulePolyfill {
name: "timers",
specifier: "node/timers.ts",
},
NodeModulePolyfill {
name: "timers/promises",
specifier: "node/timers/promises.ts",
},
NodeModulePolyfill {
name: "tls",
specifier: "node/tls.ts",
},
NodeModulePolyfill {
name: "tty",
specifier: "node/tty.ts",
},
NodeModulePolyfill {
name: "url",
specifier: "node/url.ts",
},
NodeModulePolyfill {
name: "util",
specifier: "node/util.ts",
},
NodeModulePolyfill {
name: "util/types",
specifier: "node/util/types.ts",
},
NodeModulePolyfill {
name: "v8",
specifier: "node/v8.ts",
},
NodeModulePolyfill {
name: "vm",
specifier: "node/vm.ts",
},
NodeModulePolyfill {
name: "worker_threads",
specifier: "node/worker_threads.ts",
},
NodeModulePolyfill {
name: "zlib",
specifier: "node/zlib.ts",
},
];
static NODE_COMPAT_URL: Lazy<Url> = Lazy::new(|| {
if let Ok(url_str) = std::env::var("DENO_NODE_COMPAT_URL") {
let url = Url::parse(&url_str).expect(
@ -315,7 +123,9 @@ pub static MODULE_ALL_URL: Lazy<Url> =
Lazy::new(|| NODE_COMPAT_URL.join("node/module_all.ts").unwrap());
fn find_builtin_node_module(specifier: &str) -> Option<&NodeModulePolyfill> {
SUPPORTED_MODULES.iter().find(|m| m.name == specifier)
SUPPORTED_BUILTIN_NODE_MODULES
.iter()
.find(|m| m.name == specifier)
}
fn is_builtin_node_module(specifier: &str) -> bool {
@ -336,7 +146,7 @@ pub fn resolve_builtin_node_module(specifier: &str) -> Result<Url, AnyError> {
}
Err(generic_error(format!(
"Unknown built-in Node module: {}",
"Unknown built-in \"node:\" module: {}",
specifier
)))
}

View file

@ -14,7 +14,7 @@ pub use cache::NpmCache;
pub use registry::NpmPackageVersionDistInfo;
pub use registry::NpmRegistryApi;
pub use registry::RealNpmRegistryApi;
pub use resolution::resolve_npm_package_reqs;
pub use resolution::resolve_graph_npm_info;
pub use resolution::NpmPackageId;
pub use resolution::NpmPackageReference;
pub use resolution::NpmPackageReq;

View file

@ -1081,7 +1081,7 @@ fn tag_to_version_info<'a>(
// explicit version.
if tag == "latest" && info.name == "@types/node" {
return get_resolved_package_version_and_info(
&NpmVersionReq::parse("18.0.0 - 18.8.2").unwrap(),
&NpmVersionReq::parse("18.0.0 - 18.11.18").unwrap(),
info,
parent,
);

View file

@ -28,7 +28,7 @@ mod specifier;
use graph::Graph;
pub use snapshot::NpmResolutionSnapshot;
pub use specifier::resolve_npm_package_reqs;
pub use specifier::resolve_graph_npm_info;
pub use specifier::NpmPackageReference;
pub use specifier::NpmPackageReq;

View file

@ -168,8 +168,15 @@ impl NpmVersionMatcher for NpmPackageReq {
}
}
/// Resolves the npm package requirements from the graph attempting. The order
/// returned is the order they should be resolved in.
pub struct GraphNpmInfo {
/// The order of these package requirements is the order they
/// should be resolved in.
pub package_reqs: Vec<NpmPackageReq>,
/// Gets if the graph had a built-in node specifier (ex. `node:fs`).
pub has_node_builtin_specifier: bool,
}
/// Resolves npm specific information from the graph.
///
/// This function will analyze the module graph for parent-most folder
/// specifiers of all modules, then group npm specifiers together as found in
@ -211,7 +218,7 @@ impl NpmVersionMatcher for NpmPackageReq {
///
/// Then it would resolve the npm specifiers in each of those groups according
/// to that tree going by tree depth.
pub fn resolve_npm_package_reqs(graph: &ModuleGraph) -> Vec<NpmPackageReq> {
pub fn resolve_graph_npm_info(graph: &ModuleGraph) -> GraphNpmInfo {
fn collect_specifiers<'a>(
graph: &'a ModuleGraph,
module: &'a deno_graph::Module,
@ -248,6 +255,7 @@ pub fn resolve_npm_package_reqs(graph: &ModuleGraph) -> Vec<NpmPackageReq> {
graph: &ModuleGraph,
specifier_graph: &mut SpecifierTree,
seen: &mut HashSet<ModuleSpecifier>,
has_node_builtin_specifier: &mut bool,
) {
if !seen.insert(module.specifier.clone()) {
return; // already visited
@ -267,12 +275,22 @@ pub fn resolve_npm_package_reqs(graph: &ModuleGraph) -> Vec<NpmPackageReq> {
.dependencies
.insert(get_folder_path_specifier(specifier));
}
if !*has_node_builtin_specifier && specifier.scheme() == "node" {
*has_node_builtin_specifier = true;
}
}
// now visit all the dependencies
for specifier in &specifiers {
if let Some(module) = graph.get(specifier) {
analyze_module(module, graph, specifier_graph, seen);
analyze_module(
module,
graph,
specifier_graph,
seen,
has_node_builtin_specifier,
);
}
}
}
@ -284,9 +302,16 @@ pub fn resolve_npm_package_reqs(graph: &ModuleGraph) -> Vec<NpmPackageReq> {
.collect::<Vec<_>>();
let mut seen = HashSet::new();
let mut specifier_graph = SpecifierTree::default();
let mut has_node_builtin_specifier = false;
for root in &root_specifiers {
if let Some(module) = graph.get(root) {
analyze_module(module, graph, &mut specifier_graph, &mut seen);
analyze_module(
module,
graph,
&mut specifier_graph,
&mut seen,
&mut has_node_builtin_specifier,
);
}
}
@ -324,7 +349,10 @@ pub fn resolve_npm_package_reqs(graph: &ModuleGraph) -> Vec<NpmPackageReq> {
}
}
result
GraphNpmInfo {
has_node_builtin_specifier,
package_reqs: result,
}
}
fn get_folder_path_specifier(specifier: &ModuleSpecifier) -> ModuleSpecifier {
@ -979,7 +1007,8 @@ mod tests {
},
)
.await;
let reqs = resolve_npm_package_reqs(&graph)
let reqs = resolve_graph_npm_info(&graph)
.package_reqs
.into_iter()
.map(|r| r.to_string())
.collect::<Vec<_>>();

View file

@ -95,59 +95,51 @@ impl NpmPackageResolver {
no_npm: bool,
local_node_modules_path: Option<PathBuf>,
) -> Self {
Self::new_with_maybe_snapshot(
Self::new_inner(cache, api, no_npm, local_node_modules_path, None, None)
}
pub async fn new_with_maybe_lockfile(
cache: NpmCache,
api: RealNpmRegistryApi,
no_npm: bool,
local_node_modules_path: Option<PathBuf>,
maybe_lockfile: Option<Arc<Mutex<Lockfile>>>,
) -> Result<Self, AnyError> {
let maybe_snapshot = if let Some(lockfile) = &maybe_lockfile {
if lockfile.lock().overwrite {
None
} else {
Some(
NpmResolutionSnapshot::from_lockfile(lockfile.clone(), &api)
.await
.with_context(|| {
format!(
"failed reading lockfile '{}'",
lockfile.lock().filename.display()
)
})?,
)
}
} else {
None
};
Ok(Self::new_inner(
cache,
api,
no_npm,
local_node_modules_path,
None,
)
maybe_snapshot,
maybe_lockfile,
))
}
/// This function will replace current resolver with a new one built from a
/// snapshot created out of the lockfile.
pub async fn add_lockfile_and_maybe_regenerate_snapshot(
&mut self,
lockfile: Arc<Mutex<Lockfile>>,
) -> Result<(), AnyError> {
self.maybe_lockfile = Some(lockfile.clone());
if lockfile.lock().overwrite {
return Ok(());
}
let snapshot =
NpmResolutionSnapshot::from_lockfile(lockfile.clone(), &self.api)
.await
.with_context(|| {
format!(
"failed reading lockfile '{}'",
lockfile.lock().filename.display()
)
})?;
if let Some(node_modules_folder) = &self.local_node_modules_path {
self.inner = Arc::new(LocalNpmPackageResolver::new(
self.cache.clone(),
self.api.clone(),
node_modules_folder.clone(),
Some(snapshot),
));
} else {
self.inner = Arc::new(GlobalNpmPackageResolver::new(
self.cache.clone(),
self.api.clone(),
Some(snapshot),
));
}
Ok(())
}
fn new_with_maybe_snapshot(
fn new_inner(
cache: NpmCache,
api: RealNpmRegistryApi,
no_npm: bool,
local_node_modules_path: Option<PathBuf>,
initial_snapshot: Option<NpmResolutionSnapshot>,
maybe_lockfile: Option<Arc<Mutex<Lockfile>>>,
) -> Self {
let process_npm_state = NpmProcessState::take();
let local_node_modules_path = local_node_modules_path.or_else(|| {
@ -177,7 +169,7 @@ impl NpmPackageResolver {
local_node_modules_path,
api,
cache,
maybe_lockfile: None,
maybe_lockfile,
}
}
@ -320,12 +312,13 @@ impl NpmPackageResolver {
/// Gets a new resolver with a new snapshotted state.
pub fn snapshotted(&self) -> Self {
Self::new_with_maybe_snapshot(
Self::new_inner(
self.cache.clone(),
self.api.clone(),
self.no_npm,
self.local_node_modules_path.clone(),
Some(self.snapshot()),
None,
)
}
@ -336,6 +329,19 @@ impl NpmPackageResolver {
pub fn lock(&self, lockfile: &mut Lockfile) -> Result<(), AnyError> {
self.inner.lock(lockfile)
}
pub async fn inject_synthetic_types_node_package(
&self,
) -> Result<(), AnyError> {
// add and ensure this isn't added to the lockfile
self
.inner
.add_package_reqs(vec![NpmPackageReq::from_str("@types/node").unwrap()])
.await?;
self.inner.cache_packages().await?;
Ok(())
}
}
impl RequireNpmResolver for NpmPackageResolver {

View file

@ -23,7 +23,7 @@ use crate::graph_util::ModuleEntry;
use crate::http_util::HttpClient;
use crate::node;
use crate::node::NodeResolution;
use crate::npm::resolve_npm_package_reqs;
use crate::npm::resolve_graph_npm_info;
use crate::npm::NpmCache;
use crate::npm::NpmPackageReference;
use crate::npm::NpmPackageResolver;
@ -261,20 +261,16 @@ impl ProcState {
http_client.clone(),
progress_bar.clone(),
);
let maybe_lockfile = lockfile.as_ref().cloned();
let mut npm_resolver = NpmPackageResolver::new(
let npm_resolver = NpmPackageResolver::new_with_maybe_lockfile(
npm_cache.clone(),
api,
cli_options.no_npm(),
cli_options
.resolve_local_node_modules_folder()
.with_context(|| "Resolving local node_modules folder.")?,
);
if let Some(lockfile) = maybe_lockfile {
npm_resolver
.add_lockfile_and_maybe_regenerate_snapshot(lockfile)
.await?;
}
lockfile.as_ref().cloned(),
)
.await?;
let node_analysis_cache =
NodeAnalysisCache::new(Some(dir.node_analysis_db_file_path()));
@ -424,7 +420,7 @@ impl ProcState {
graph_data.entries().map(|(s, _)| s).cloned().collect()
};
let npm_package_reqs = {
let (npm_package_reqs, has_node_builtin_specifier) = {
let mut graph_data = self.graph_data.write();
graph_data.add_graph(&graph);
let check_js = self.options.check_js();
@ -435,7 +431,10 @@ impl ProcState {
check_js,
)
.unwrap()?;
graph_data.npm_package_reqs().clone()
(
graph_data.npm_package_reqs().clone(),
graph_data.has_node_builtin_specifier(),
)
};
if !npm_package_reqs.is_empty() {
@ -443,6 +442,15 @@ impl ProcState {
self.prepare_node_std_graph().await?;
}
if has_node_builtin_specifier
&& self.options.type_check_mode() != TypeCheckMode::None
{
self
.npm_resolver
.inject_synthetic_types_node_package()
.await?;
}
drop(_pb_clear_guard);
// type check if necessary
@ -614,6 +622,11 @@ impl ProcState {
}
}
// Built-in Node modules
if let Some(module_name) = specifier.strip_prefix("node:") {
return node::resolve_builtin_node_module(module_name);
}
// 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.
@ -732,9 +745,20 @@ impl ProcState {
.await;
// add the found npm package requirements to the npm resolver and cache them
let npm_package_reqs = resolve_npm_package_reqs(&graph);
if !npm_package_reqs.is_empty() {
self.npm_resolver.add_package_reqs(npm_package_reqs).await?;
let graph_npm_info = resolve_graph_npm_info(&graph);
if !graph_npm_info.package_reqs.is_empty() {
self
.npm_resolver
.add_package_reqs(graph_npm_info.package_reqs)
.await?;
}
if graph_npm_info.has_node_builtin_specifier
&& self.options.type_check_mode() != TypeCheckMode::None
{
self
.npm_resolver
.inject_synthetic_types_node_package()
.await?;
}
Ok(graph)

View file

@ -64,6 +64,18 @@ itest!(check_static_response_json {
exit_code: 0,
});
itest!(check_node_builtin_modules_ts {
args: "check --quiet check/node_builtin_modules/mod.ts",
output: "check/node_builtin_modules/mod.ts.out",
exit_code: 1,
});
itest!(check_node_builtin_modules_js {
args: "check --quiet check/node_builtin_modules/mod.js",
output: "check/node_builtin_modules/mod.js.out",
exit_code: 1,
});
itest!(check_no_error_truncation {
args: "check --quiet check/no_error_truncation/main.ts --config check/no_error_truncation/deno.json",
output: "check/no_error_truncation/main.out",

View file

@ -4407,6 +4407,190 @@ fn lsp_npm_specifier_unopened_file() {
}
}
#[test]
fn lsp_completions_node_specifier() {
let _g = http_server();
let mut client = init("initialize_params.json");
let diagnostics = CollectedDiagnostics(did_open(
&mut client,
json!({
"textDocument": {
"uri": "file:///a/file.ts",
"languageId": "typescript",
"version": 1,
"text": "import fs from 'node:non-existent';\n\n",
}
}),
));
let non_existent_diagnostics = diagnostics
.with_file_and_source("file:///a/file.ts", "deno")
.diagnostics
.into_iter()
.filter(|d| {
d.code == Some(lsp::NumberOrString::String("resolver-error".to_string()))
})
.collect::<Vec<_>>();
assert_eq!(
json!(non_existent_diagnostics),
json!([
{
"range": {
"start": {
"line": 0,
"character": 15
},
"end": {
"line": 0,
"character": 34
}
},
"severity": 1,
"code": "resolver-error",
"source": "deno",
"message": "Unknown Node built-in module: non-existent"
}
])
);
// update to have node:fs import
client
.write_notification(
"textDocument/didChange",
json!({
"textDocument": {
"uri": "file:///a/file.ts",
"version": 2
},
"contentChanges": [
{
"range": {
"start": {
"line": 0,
"character": 16
},
"end": {
"line": 0,
"character": 33
}
},
"text": "node:fs"
}
]
}),
)
.unwrap();
let diagnostics = read_diagnostics(&mut client);
let cache_diagnostics = diagnostics
.with_file_and_source("file:///a/file.ts", "deno")
.diagnostics
.into_iter()
.filter(|d| {
d.code == Some(lsp::NumberOrString::String("no-cache-npm".to_string()))
})
.collect::<Vec<_>>();
assert_eq!(
json!(cache_diagnostics),
json!([
{
"range": {
"start": {
"line": 0,
"character": 15
},
"end": {
"line": 0,
"character": 24
}
},
"data": {
"specifier": "npm:@types/node",
},
"severity": 1,
"code": "no-cache-npm",
"source": "deno",
"message": "Uncached or missing npm package: \"@types/node\"."
}
])
);
let (maybe_res, maybe_err) = client
.write_request::<_, _, Value>(
"deno/cache",
json!({
"referrer": {
"uri": "file:///a/file.ts",
},
"uris": [
{
"uri": "npm:@types/node",
}
]
}),
)
.unwrap();
assert!(maybe_err.is_none());
assert!(maybe_res.is_some());
client
.write_notification(
"textDocument/didChange",
json!({
"textDocument": {
"uri": "file:///a/file.ts",
"version": 2
},
"contentChanges": [
{
"range": {
"start": {
"line": 2,
"character": 0
},
"end": {
"line": 2,
"character": 0
}
},
"text": "fs."
}
]
}),
)
.unwrap();
read_diagnostics(&mut client);
let (maybe_res, maybe_err) = client
.write_request(
"textDocument/completion",
json!({
"textDocument": {
"uri": "file:///a/file.ts"
},
"position": {
"line": 2,
"character": 3
},
"context": {
"triggerKind": 2,
"triggerCharacter": "."
}
}),
)
.unwrap();
assert!(maybe_err.is_none());
if let Some(lsp::CompletionResponse::List(list)) = maybe_res {
assert!(!list.is_incomplete);
assert!(list.items.iter().any(|i| i.label == "writeFile"));
assert!(list.items.iter().any(|i| i.label == "writeFileSync"));
} else {
panic!("unexpected response");
}
shutdown(&mut client);
}
#[test]
fn lsp_completions_registry() {
let _g = http_server();

View file

@ -3743,3 +3743,23 @@ fn stdio_streams_are_locked_in_permission_prompt() {
assert_eq!(output, expected_output);
});
}
itest!(run_node_builtin_modules_ts {
args: "run --quiet run/node_builtin_modules/mod.ts",
output: "run/node_builtin_modules/mod.ts.out",
envs: vec![(
"DENO_NODE_COMPAT_URL".to_string(),
test_util::std_file_url()
)],
exit_code: 0,
});
itest!(run_node_builtin_modules_js {
args: "run --quiet run/node_builtin_modules/mod.js",
output: "run/node_builtin_modules/mod.js.out",
envs: vec![(
"DENO_NODE_COMPAT_URL".to_string(),
test_util::std_file_url()
)],
exit_code: 0,
});

View file

@ -0,0 +1,3 @@
// @ts-check
import fs from "node:fs";
const _data = fs.readFileSync("./node_builtin.js", 123);

View file

@ -0,0 +1,5 @@
error: TS2769 [ERROR]: No overload matches this call.
[WILDCARD]
const _data = fs.readFileSync("./node_builtin.js", 123);
~~~
at file:///[WILDCARD]/node_builtin_modules/mod.js:3:52

View file

@ -0,0 +1,9 @@
import fs from "node:fs";
const _data = fs.readFileSync("./node_builtin.js", 123);
// check node:module specifically because for deno check it should
// resolve to the @types/node package, but at runtime it uses a different
// builtin object than deno_std
import { builtinModules } from "node:module";
// should error about being string[]
const _testString: number[] = builtinModules;

View file

@ -0,0 +1,13 @@
error: TS2769 [ERROR]: No overload matches this call.
[WILDCARD]
const _data = fs.readFileSync("./node_builtin.js", 123);
~~~
at file:///[WILDCARD]/node_builtin_modules/mod.ts:2:52
TS2322 [ERROR]: Type 'string[]' is not assignable to type 'number[]'.
Type 'string' is not assignable to type 'number'.
const _testString: number[] = builtinModules;
~~~~~~~~~~~
at file:///[WILDCARD]/node_builtin_modules/mod.ts:9:7
Found 2 errors.

View file

@ -0,0 +1,2 @@
import process from "node:process";
console.log(process.version);

View file

@ -0,0 +1 @@
v[WILDCARD].[WILDCARD].[WILDCARD]

View file

@ -0,0 +1,2 @@
import process from "node:process";
console.log(process.version);

View file

@ -0,0 +1 @@
v[WILDCARD].[WILDCARD].[WILDCARD]

View file

@ -232,10 +232,16 @@ fn get_tsc_roots(
graph_data: &GraphData,
check_js: bool,
) -> Vec<(ModuleSpecifier, MediaType)> {
graph_data
.entries()
.into_iter()
.filter_map(|(specifier, module_entry)| match module_entry {
let mut result = Vec::new();
if graph_data.has_node_builtin_specifier() {
// inject a specifier that will resolve node types
result.push((
ModuleSpecifier::parse("asset:///node_types.d.ts").unwrap(),
MediaType::Dts,
));
}
result.extend(graph_data.entries().into_iter().filter_map(
|(specifier, module_entry)| match module_entry {
ModuleEntry::Module {
media_type, code, ..
} => match media_type {
@ -252,8 +258,9 @@ fn get_tsc_roots(
_ => None,
},
_ => None,
})
.collect()
},
));
result
}
/// Matches the `@ts-check` pragma.

View file

@ -91389,10 +91389,15 @@ var ts;
var deno;
(function (deno) {
var isNodeSourceFile = function () { return false; };
var nodeBuiltInModuleNames = new ts.Set();
function setIsNodeSourceFileCallback(callback) {
isNodeSourceFile = callback;
}
deno.setIsNodeSourceFileCallback = setIsNodeSourceFileCallback;
function setNodeBuiltInModuleNames(names) {
nodeBuiltInModuleNames = new ts.Set(names);
}
deno.setNodeBuiltInModuleNames = setNodeBuiltInModuleNames;
// When upgrading:
// 1. Inspect all usages of "globals" and "globalThisSymbol" in checker.ts
// - Beware that `globalThisType` might refer to the global `this` type
@ -91452,8 +91457,16 @@ var ts;
function getGlobalsForName(id) {
// Node ambient modules are only accessible in the node code,
// so put them on the node globals
if (ambientModuleSymbolRegex.test(id))
if (ambientModuleSymbolRegex.test(id)) {
if (id.startsWith('"node:')) {
// check if it's a node specifier that we support
var name = id.slice(6, -1);
if (nodeBuiltInModuleNames.has(name)) {
return globals;
}
}
return nodeGlobals;
}
return nodeOnlyGlobalNames.has(id) ? nodeGlobals : globals;
}
function mergeGlobalSymbolTable(node, source, unidirectional) {

View file

@ -855,25 +855,7 @@ delete Object.prototype.__proto__;
...program.getOptionsDiagnostics(),
...program.getGlobalDiagnostics(),
...program.getSemanticDiagnostics(),
].filter((diagnostic) => {
if (IGNORED_DIAGNOSTICS.includes(diagnostic.code)) {
return false;
} else if (
diagnostic.code === 1259 &&
typeof diagnostic.messageText === "string" &&
diagnostic.messageText.startsWith(
"Module '\"deno:///missing_dependency.d.ts\"' can only be default-imported using the 'allowSyntheticDefaultImports' flag",
)
) {
// For now, ignore diagnostics like:
// > TS1259 [ERROR]: Module '"deno:///missing_dependency.d.ts"' can only be default-imported using the 'allowSyntheticDefaultImports' flag
// This diagnostic has surfaced due to supporting node cjs imports because this module does `export =`.
// See discussion in https://github.com/microsoft/TypeScript/pull/51136
return false;
} else {
return true;
}
});
].filter((diagnostic) => !IGNORED_DIAGNOSTICS.includes(diagnostic.code));
// emit the tsbuildinfo file
// @ts-ignore: emitBuildInfo is not exposed (https://github.com/microsoft/TypeScript/issues/49871)
@ -1273,9 +1255,11 @@ delete Object.prototype.__proto__;
// A build time only op that provides some setup information that is used to
// ensure the snapshot is setup properly.
/** @type {{ buildSpecifier: string; libs: string[] }} */
/** @type {{ buildSpecifier: string; libs: string[]; nodeBuiltInModuleNames: string[] }} */
const { buildSpecifier, libs, nodeBuiltInModuleNames } = ops.op_build_info();
ts.deno.setNodeBuiltInModuleNames(nodeBuiltInModuleNames);
const { buildSpecifier, libs } = ops.op_build_info();
for (const lib of libs) {
const specifier = `lib.${lib}.d.ts`;
// we are using internal APIs here to "inject" our custom libraries into

View file

@ -30,6 +30,7 @@ declare global {
function setIsNodeSourceFileCallback(
callback: (sourceFile: SourceFile) => boolean,
);
function setNodeBuiltInModuleNames(names: string[]);
}
}

View file

@ -299,9 +299,11 @@ impl Diagnostic {
fn fmt_related_information(&self, f: &mut fmt::Formatter) -> fmt::Result {
if let Some(related_information) = self.related_information.as_ref() {
write!(f, "\n\n")?;
for info in related_information {
info.fmt_stack(f, 4)?;
if !related_information.is_empty() {
write!(f, "\n\n")?;
for info in related_information {
info.fmt_stack(f, 4)?;
}
}
}

View file

@ -165,6 +165,12 @@ pub static LAZILY_LOADED_STATIC_ASSETS: Lazy<
"lib.webworker.iterable.d.ts",
inc!("lib.webworker.iterable.d.ts"),
),
(
// Special file that can be used to inject the @types/node package.
// This is used for `node:` specifiers.
"node_types.d.ts",
"/// <reference types=\"npm:@types/node\" />\n",
),
])
.iter()
.cloned()
@ -599,117 +605,133 @@ fn op_resolve(
"Error converting a string module specifier for \"op_resolve\".",
)?
};
for specifier in &args.specifiers {
for specifier in args.specifiers {
if let Some(module_name) = specifier.strip_prefix("node:") {
if crate::node::resolve_builtin_node_module(module_name).is_ok() {
// return itself for node: specifiers because during type checking
// we resolve to the ambient modules in the @types/node package
// rather than deno_std/node
resolved.push((specifier, MediaType::Dts.to_string()));
continue;
}
}
if specifier.starts_with("asset:///") {
resolved.push((
specifier.clone(),
MediaType::from(specifier).as_ts_extension().to_string(),
));
} else {
let graph_data = state.graph_data.read();
let resolved_dep = match graph_data.get_dependencies(&referrer) {
Some(dependencies) => dependencies.get(specifier).map(|d| {
if matches!(d.maybe_type, Resolved::Ok { .. }) {
&d.maybe_type
} else {
&d.maybe_code
}
}),
None => None,
};
let maybe_result = match resolved_dep {
Some(Resolved::Ok { specifier, .. }) => {
let specifier = graph_data.follow_redirect(specifier);
match graph_data.get(&specifier) {
Some(ModuleEntry::Module {
media_type,
maybe_types,
..
}) => match maybe_types {
Some(Resolved::Ok { specifier, .. }) => {
let types = graph_data.follow_redirect(specifier);
match graph_data.get(&types) {
Some(ModuleEntry::Module { media_type, .. }) => {
Some((types, *media_type))
}
_ => None,
let media_type =
MediaType::from(&specifier).as_ts_extension().to_string();
resolved.push((specifier, media_type));
continue;
}
let graph_data = state.graph_data.read();
let resolved_dep = match graph_data.get_dependencies(&referrer) {
Some(dependencies) => dependencies.get(&specifier).map(|d| {
if matches!(d.maybe_type, Resolved::Ok { .. }) {
&d.maybe_type
} else {
&d.maybe_code
}
}),
None => None,
};
let maybe_result = match resolved_dep {
Some(Resolved::Ok { specifier, .. }) => {
let specifier = graph_data.follow_redirect(specifier);
match graph_data.get(&specifier) {
Some(ModuleEntry::Module {
media_type,
maybe_types,
..
}) => match maybe_types {
Some(Resolved::Ok { specifier, .. }) => {
let types = graph_data.follow_redirect(specifier);
match graph_data.get(&types) {
Some(ModuleEntry::Module { media_type, .. }) => {
Some((types, *media_type))
}
_ => None,
}
_ => Some((specifier, *media_type)),
},
_ => {
// handle npm:<package> urls
if let Ok(npm_ref) =
NpmPackageReference::from_specifier(&specifier)
{
if let Some(npm_resolver) = &state.maybe_npm_resolver {
Some(resolve_npm_package_reference_types(
&npm_ref,
npm_resolver,
)?)
} else {
None
}
}
_ => Some((specifier, *media_type)),
},
_ => {
// handle npm:<package> urls
if let Ok(npm_ref) = NpmPackageReference::from_specifier(&specifier)
{
if let Some(npm_resolver) = &state.maybe_npm_resolver {
Some(resolve_npm_package_reference_types(
&npm_ref,
npm_resolver,
)?)
} else {
None
}
}
}
}
_ => {
state.maybe_npm_resolver.as_ref().and_then(|npm_resolver| {
if npm_resolver.in_npm_package(&referrer) {
// we're in an npm package, so use node resolution
Some(NodeResolution::into_specifier_and_media_type(
node::node_resolve(
specifier,
&referrer,
NodeResolutionMode::Types,
npm_resolver,
&mut PermissionsContainer::allow_all(),
)
.ok()
.flatten(),
))
} else {
None
}
})
}
}
};
let result = match maybe_result {
Some((specifier, media_type)) => {
let specifier_str = match specifier.scheme() {
"data" | "blob" => {
let specifier_str = hash_url(&specifier, media_type);
}
_ => {
if let Some(npm_resolver) = state.maybe_npm_resolver.as_ref() {
if npm_resolver.in_npm_package(&referrer) {
// we're in an npm package, so use node resolution
Some(NodeResolution::into_specifier_and_media_type(
node::node_resolve(
&specifier,
&referrer,
NodeResolutionMode::Types,
npm_resolver,
&mut PermissionsContainer::allow_all(),
)
.ok()
.flatten(),
))
} else if let Ok(npm_ref) = NpmPackageReference::from_str(&specifier)
{
// this could occur when resolving npm:@types/node when it is
// injected and not part of the graph
Some(resolve_npm_package_reference_types(&npm_ref, npm_resolver)?)
} else {
None
}
} else {
None
}
}
};
let result = match maybe_result {
Some((specifier, media_type)) => {
let specifier_str = match specifier.scheme() {
"data" | "blob" => {
let specifier_str = hash_url(&specifier, media_type);
state
.remapped_specifiers
.insert(specifier_str.clone(), specifier);
specifier_str
}
_ => {
if let Some(specifier_str) =
maybe_remap_specifier(&specifier, media_type)
{
state
.remapped_specifiers
.insert(specifier_str.clone(), specifier);
specifier_str
} else {
specifier.to_string()
}
_ => {
if let Some(specifier_str) =
maybe_remap_specifier(&specifier, media_type)
{
state
.remapped_specifiers
.insert(specifier_str.clone(), specifier);
specifier_str
} else {
specifier.to_string()
}
}
};
(specifier_str, media_type.as_ts_extension().into())
}
None => (
"deno:///missing_dependency.d.ts".to_string(),
".d.ts".to_string(),
),
};
log::debug!("Resolved {} to {:?}", specifier, result);
resolved.push(result);
}
}
};
(specifier_str, media_type.as_ts_extension().into())
}
None => (
"deno:///missing_dependency.d.ts".to_string(),
".d.ts".to_string(),
),
};
log::debug!("Resolved {} to {:?}", specifier, result);
resolved.push(result);
}
Ok(resolved)

View file

@ -675,3 +675,197 @@ fn op_require_break_on_next_statement(state: &mut OpState) {
.borrow_mut()
.wait_for_session_and_break_on_next_statement()
}
pub struct NodeModulePolyfill {
/// Name of the module like "assert" or "timers/promises"
pub name: &'static str,
/// Specifier relative to the root of `deno_std` repo, like "node/assert.ts"
pub specifier: &'static str,
}
pub static SUPPORTED_BUILTIN_NODE_MODULES: &[NodeModulePolyfill] = &[
NodeModulePolyfill {
name: "assert",
specifier: "node/assert.ts",
},
NodeModulePolyfill {
name: "assert/strict",
specifier: "node/assert/strict.ts",
},
NodeModulePolyfill {
name: "async_hooks",
specifier: "node/async_hooks.ts",
},
NodeModulePolyfill {
name: "buffer",
specifier: "node/buffer.ts",
},
NodeModulePolyfill {
name: "child_process",
specifier: "node/child_process.ts",
},
NodeModulePolyfill {
name: "cluster",
specifier: "node/cluster.ts",
},
NodeModulePolyfill {
name: "console",
specifier: "node/console.ts",
},
NodeModulePolyfill {
name: "constants",
specifier: "node/constants.ts",
},
NodeModulePolyfill {
name: "crypto",
specifier: "node/crypto.ts",
},
NodeModulePolyfill {
name: "dgram",
specifier: "node/dgram.ts",
},
NodeModulePolyfill {
name: "dns",
specifier: "node/dns.ts",
},
NodeModulePolyfill {
name: "dns/promises",
specifier: "node/dns/promises.ts",
},
NodeModulePolyfill {
name: "domain",
specifier: "node/domain.ts",
},
NodeModulePolyfill {
name: "events",
specifier: "node/events.ts",
},
NodeModulePolyfill {
name: "fs",
specifier: "node/fs.ts",
},
NodeModulePolyfill {
name: "fs/promises",
specifier: "node/fs/promises.ts",
},
NodeModulePolyfill {
name: "http",
specifier: "node/http.ts",
},
NodeModulePolyfill {
name: "https",
specifier: "node/https.ts",
},
NodeModulePolyfill {
name: "module",
// NOTE(bartlomieju): `module` is special, because we don't want to use
// `deno_std/node/module.ts`, but instead use a special shim that we
// provide in `ext/node`.
specifier: "[USE `deno_node::MODULE_ES_SHIM` to get this module]",
},
NodeModulePolyfill {
name: "net",
specifier: "node/net.ts",
},
NodeModulePolyfill {
name: "os",
specifier: "node/os.ts",
},
NodeModulePolyfill {
name: "path",
specifier: "node/path.ts",
},
NodeModulePolyfill {
name: "path/posix",
specifier: "node/path/posix.ts",
},
NodeModulePolyfill {
name: "path/win32",
specifier: "node/path/win32.ts",
},
NodeModulePolyfill {
name: "perf_hooks",
specifier: "node/perf_hooks.ts",
},
NodeModulePolyfill {
name: "process",
specifier: "node/process.ts",
},
NodeModulePolyfill {
name: "querystring",
specifier: "node/querystring.ts",
},
NodeModulePolyfill {
name: "readline",
specifier: "node/readline.ts",
},
NodeModulePolyfill {
name: "stream",
specifier: "node/stream.ts",
},
NodeModulePolyfill {
name: "stream/consumers",
specifier: "node/stream/consumers.mjs",
},
NodeModulePolyfill {
name: "stream/promises",
specifier: "node/stream/promises.mjs",
},
NodeModulePolyfill {
name: "stream/web",
specifier: "node/stream/web.ts",
},
NodeModulePolyfill {
name: "string_decoder",
specifier: "node/string_decoder.ts",
},
NodeModulePolyfill {
name: "sys",
specifier: "node/sys.ts",
},
NodeModulePolyfill {
name: "timers",
specifier: "node/timers.ts",
},
NodeModulePolyfill {
name: "timers/promises",
specifier: "node/timers/promises.ts",
},
NodeModulePolyfill {
name: "tls",
specifier: "node/tls.ts",
},
NodeModulePolyfill {
name: "tty",
specifier: "node/tty.ts",
},
NodeModulePolyfill {
name: "url",
specifier: "node/url.ts",
},
NodeModulePolyfill {
name: "util",
specifier: "node/util.ts",
},
NodeModulePolyfill {
name: "util/types",
specifier: "node/util/types.ts",
},
NodeModulePolyfill {
name: "v8",
specifier: "node/v8.ts",
},
NodeModulePolyfill {
name: "vm",
specifier: "node/vm.ts",
},
NodeModulePolyfill {
name: "worker_threads",
specifier: "node/worker_threads.ts",
},
NodeModulePolyfill {
name: "zlib",
specifier: "node/zlib.ts",
},
];