diff options
author | Bartek IwaĆczuk <biwanczuk@gmail.com> | 2022-08-09 21:06:01 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-08-09 21:06:01 +0200 |
commit | 1f54d877895ea25258a941818f07c6e84d44a7a2 (patch) | |
tree | 1999d04ec926d464c94ed2c5a9fe10fb84f3de24 /ext/node/lib.rs | |
parent | af618e3b8fb11f3947ab5ded9523cdca9cf77ced (diff) |
feat: add ext/node for require support (#15362)
This commit adds "ext/node" extension that implementes CommonJS module system.
In the future this extension might be extended to actually contain implementation of
Node compatibility layer in favor of "deno_std/node".
Currently this functionality is not publicly exposed, it is available via "Deno[Deno.internal].require"
namespace and is meant to be used by other functionality to be landed soon.
This is a minimal first pass, things that still don't work:
support for dynamic imports in CJS
conditional exports
Diffstat (limited to 'ext/node/lib.rs')
-rw-r--r-- | ext/node/lib.rs | 317 |
1 files changed, 317 insertions, 0 deletions
diff --git a/ext/node/lib.rs b/ext/node/lib.rs new file mode 100644 index 000000000..5d6542dc5 --- /dev/null +++ b/ext/node/lib.rs @@ -0,0 +1,317 @@ +// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. + +use deno_core::error::AnyError; +use deno_core::include_js_files; +use deno_core::normalize_path; +use deno_core::op; +use deno_core::Extension; +use std::path::PathBuf; + +pub fn init() -> Extension { + Extension::builder() + .js(include_js_files!( + prefix "deno:ext/node", + "01_require.js", + )) + .ops(vec![ + op_require_init_paths::decl(), + op_require_node_module_paths::decl(), + op_require_proxy_path::decl(), + op_require_is_request_relative::decl(), + op_require_resolve_lookup_paths::decl(), + op_require_try_self_parent_path::decl(), + op_require_try_self::decl(), + op_require_real_path::decl(), + op_require_path_is_absolute::decl(), + op_require_path_dirname::decl(), + op_require_stat::decl(), + op_require_path_resolve::decl(), + op_require_path_basename::decl(), + op_require_read_file::decl(), + ]) + .build() +} + +#[op] +pub fn op_require_init_paths() -> Vec<String> { + let (home_dir, node_path) = if cfg!(windows) { + ( + std::env::var("USERPROFILE").unwrap_or_else(|_| "".into()), + std::env::var("NODE_PATH").unwrap_or_else(|_| "".into()), + ) + } else { + ( + std::env::var("HOME").unwrap_or_else(|_| "".into()), + std::env::var("NODE_PATH").unwrap_or_else(|_| "".into()), + ) + }; + + let mut prefix_dir = std::env::current_exe().unwrap(); + if cfg!(windows) { + prefix_dir = prefix_dir.join("..").join("..") + } else { + prefix_dir = prefix_dir.join("..") + } + + let mut paths = vec![prefix_dir.join("lib").join("node")]; + + if !home_dir.is_empty() { + paths.insert(0, PathBuf::from(&home_dir).join(".node_libraries")); + paths.insert(0, PathBuf::from(&home_dir).join(".nod_modules")); + } + + let mut paths = paths + .into_iter() + .map(|p| p.to_string_lossy().to_string()) + .collect(); + + if !node_path.is_empty() { + let delimiter = if cfg!(windows) { ";" } else { ":" }; + let mut node_paths: Vec<String> = node_path + .split(delimiter) + .filter(|e| !e.is_empty()) + .map(|s| s.to_string()) + .collect(); + node_paths.append(&mut paths); + paths = node_paths; + } + + paths +} + +#[op] +pub fn op_require_node_module_paths(from: String) -> Vec<String> { + // Guarantee that "from" is absolute. + let from = deno_core::resolve_path(&from) + .unwrap() + .to_file_path() + .unwrap(); + + if cfg!(windows) { + // return root node_modules when path is 'D:\\'. + let from_str = from.to_str().unwrap(); + if from_str.len() >= 3 { + let bytes = from_str.as_bytes(); + if bytes[from_str.len() - 1] == b'\\' && bytes[from_str.len() - 2] == b':' + { + let p = from_str.to_owned() + "node_modules"; + return vec![p]; + } + } + } else { + // Return early not only to avoid unnecessary work, but to *avoid* returning + // an array of two items for a root: [ '//node_modules', '/node_modules' ] + if from.to_string_lossy() == "/" { + return vec!["/node_modules".to_string()]; + } + } + + let mut paths = vec![]; + let mut current_path = from.as_path(); + let mut maybe_parent = Some(current_path); + while let Some(parent) = maybe_parent { + if !parent.ends_with("/node_modules") { + paths.push(parent.join("node_modules").to_string_lossy().to_string()); + current_path = parent; + maybe_parent = current_path.parent(); + } + } + + if !cfg!(windows) { + // Append /node_modules to handle root paths. + paths.push("/node_modules".to_string()); + } + + paths +} + +#[op] +fn op_require_proxy_path(filename: String) -> String { + // Allow a directory to be passed as the filename + let trailing_slash = if cfg!(windows) { + filename.ends_with('\\') + } else { + filename.ends_with('/') + }; + + if trailing_slash { + let p = PathBuf::from(filename); + p.join("noop.js").to_string_lossy().to_string() + } else { + filename + } +} + +#[op] +fn op_require_is_request_relative(request: String) -> bool { + if request.starts_with("./") { + return true; + } + + if request.starts_with("../") { + return true; + } + + if cfg!(windows) { + if request.starts_with(".\\") { + return true; + } + + if request.starts_with("..\\") { + return true; + } + } + + false +} + +#[op] +fn op_require_resolve_lookup_paths( + request: String, + maybe_parent_paths: Option<Vec<String>>, + parent_filename: String, +) -> Option<Vec<String>> { + if !request.starts_with('.') + || (request.len() > 1 + && !request.starts_with("..") + && !request.starts_with("./") + && (!cfg!(windows) || !request.starts_with(".\\"))) + { + let module_paths = vec![]; + let mut paths = module_paths; + if let Some(mut parent_paths) = maybe_parent_paths { + if !parent_paths.is_empty() { + paths.append(&mut parent_paths); + } + } + + if !paths.is_empty() { + return Some(paths); + } else { + return None; + } + } + + // In REPL, parent.filename is null. + // if (!parent || !parent.id || !parent.filename) { + // // Make require('./path/to/foo') work - normally the path is taken + // // from realpath(__filename) but in REPL there is no filename + // const mainPaths = ['.']; + + // debug('looking for %j in %j', request, mainPaths); + // return mainPaths; + // } + + let p = PathBuf::from(parent_filename); + Some(vec![p.parent().unwrap().to_string_lossy().to_string()]) +} + +#[op] +fn op_require_path_is_absolute(p: String) -> bool { + PathBuf::from(p).is_absolute() +} + +#[op] +fn op_require_stat(filename: String) -> i32 { + if let Ok(metadata) = std::fs::metadata(&filename) { + if metadata.is_file() { + return 0; + } else { + return 1; + } + } + + -1 +} + +#[op] +fn op_require_real_path(request: String) -> Result<String, AnyError> { + let mut canonicalized_path = PathBuf::from(request).canonicalize()?; + if cfg!(windows) { + canonicalized_path = PathBuf::from( + canonicalized_path + .display() + .to_string() + .trim_start_matches("\\\\?\\"), + ); + } + Ok(canonicalized_path.to_string_lossy().to_string()) +} + +#[op] +fn op_require_path_resolve(parts: Vec<String>) -> String { + assert!(!parts.is_empty()); + let mut p = PathBuf::from(&parts[0]); + if parts.len() > 1 { + for part in &parts[1..] { + p = p.join(part); + } + } + normalize_path(p).to_string_lossy().to_string() +} + +#[op] +fn op_require_path_dirname(request: String) -> String { + let p = PathBuf::from(request); + p.parent().unwrap().to_string_lossy().to_string() +} + +#[op] +fn op_require_path_basename(request: String) -> String { + let p = PathBuf::from(request); + p.file_name().unwrap().to_string_lossy().to_string() +} + +#[op] +fn op_require_try_self_parent_path( + has_parent: bool, + maybe_parent_filename: Option<String>, + maybe_parent_id: Option<String>, +) -> Option<String> { + if !has_parent { + return None; + } + + if let Some(parent_filename) = maybe_parent_filename { + return Some(parent_filename); + } + + if let Some(parent_id) = maybe_parent_id { + if parent_id == "<repl>" || parent_id == "internal/preload" { + if let Ok(cwd) = std::env::current_dir() { + return Some(cwd.to_string_lossy().to_string()); + } + } + } + None +} + +#[op] +fn op_require_try_self( + has_parent: bool, + maybe_parent_filename: Option<String>, + maybe_parent_id: Option<String>, +) -> Option<String> { + if !has_parent { + return None; + } + + if let Some(parent_filename) = maybe_parent_filename { + return Some(parent_filename); + } + + if let Some(parent_id) = maybe_parent_id { + if parent_id == "<repl>" || parent_id == "internal/preload" { + if let Ok(cwd) = std::env::current_dir() { + return Some(cwd.to_string_lossy().to_string()); + } + } + } + None +} + +#[op] +fn op_require_read_file(filename: String) -> Result<String, AnyError> { + let contents = std::fs::read_to_string(filename)?; + Ok(contents) +} |