feat(resolver): check for node.js core modules (#794)

This commit is contained in:
Boshen 2023-08-25 22:33:10 +08:00 committed by GitHub
parent 1bc1418ee6
commit 3d8ee2567f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 191 additions and 9 deletions

View file

@ -31,7 +31,7 @@ fn data() -> Vec<(PathBuf, &'static str)> {
(cwd.join("test/fixtures"), "m1/a.js?query#fragment"),
// extensions
(cwd.join("test/fixtures/extensions"), "./foo"),
(cwd.join("test/fixtures/extensions/module"), "module"),
(cwd.join("test/fixtures/extensions/module"), "module/"),
// browserField
(cwd.join("test/fixtures/browser-module"), "./lib/replaced"),
(cwd.join("test/fixtures/browser-module/lib"), "./replaced"),

View file

@ -0,0 +1,69 @@
// <https://nodejs.org/api/modules.html#core-modules>
// node -p "[...require('module').builtinModules].map(b => JSON.stringify(b)).join(',\n')"
pub const BUILTINS: &[&str] = &[
"_http_agent",
"_http_client",
"_http_common",
"_http_incoming",
"_http_outgoing",
"_http_server",
"_stream_duplex",
"_stream_passthrough",
"_stream_readable",
"_stream_transform",
"_stream_wrap",
"_stream_writable",
"_tls_common",
"_tls_wrap",
"assert",
"assert/strict",
"async_hooks",
"buffer",
"child_process",
"cluster",
"console",
"constants",
"crypto",
"dgram",
"diagnostics_channel",
"dns",
"dns/promises",
"domain",
"events",
"fs",
"fs/promises",
"http",
"http2",
"https",
"inspector",
"module",
"net",
"os",
"path",
"path/posix",
"path/win32",
"perf_hooks",
"process",
"punycode",
"querystring",
"readline",
"repl",
"stream",
"stream/consumers",
"stream/promises",
"stream/web",
"string_decoder",
"sys",
"timers",
"timers/promises",
"tls",
"trace_events",
"tty",
"url",
"util",
"util/types",
"v8",
"vm",
"worker_threads",
"zlib",
];

View file

@ -19,6 +19,12 @@ pub enum ResolveError {
/// Path not found
NotFound(PathBuf),
/// Node.js builtin modules
///
/// This is an error due to not being a Node.js runtime.
/// The `alias` option can be used to resolve a builtin module to a polyfill.
Builtin(String),
/// All of the aliased extension are not found
ExtensionAlias,

View file

@ -15,6 +15,7 @@
//! [ECMAScript Module Resolution Algorithm]: https://nodejs.org/api/esm.html#resolution-algorithm-specification
//! [parcel-resolver]: https://github.com/parcel-bundler/parcel/blob/v2/packages/utils/node-resolver-rs
mod builtins;
mod cache;
mod error;
mod file_system;
@ -41,6 +42,7 @@ use std::{
};
use crate::{
builtins::BUILTINS,
cache::{Cache, CachedPath},
file_system::FileSystemOs,
package_json::{ExportsField, ExportsKey, MatchObject},
@ -190,8 +192,11 @@ impl<Fs: FileSystem> ResolverGeneric<Fs> {
}
/// require(X) from module at path Y
///
/// X: specifier
/// Y: path
///
/// <https://nodejs.org/api/modules.html#all-together>
fn require(
&self,
cached_path: &CachedPath,
@ -218,9 +223,6 @@ impl<Fs: FileSystem> ResolverGeneric<Fs> {
}
match specifier.as_bytes()[0] {
// 1. If X is a core module,
// a. return the core module
// b. STOP
// 2. If X begins with '/'
// a. set Y to be the file system root
b'/' => self.require_absolute(cached_path, specifier, ctx),
@ -228,13 +230,28 @@ impl<Fs: FileSystem> ResolverGeneric<Fs> {
b'.' => self.require_relative(cached_path, specifier, ctx),
// 4. If X begins with '#'
b'#' => self.require_hash(cached_path, specifier, ctx),
// (ESM) 5. Otherwise,
// Note: specifier is now a bare specifier.
// Set resolved the result of PACKAGE_RESOLVE(specifier, parentURL).
_ => self.require_bare(cached_path, specifier, ctx),
_ => {
// 1. If X is a core module,
// a. return the core module
// b. STOP
self.require_core(specifier)?;
// (ESM) 5. Otherwise,
// Note: specifier is now a bare specifier.
// Set resolved the result of PACKAGE_RESOLVE(specifier, parentURL).
self.require_bare(cached_path, specifier, ctx)
}
}
}
#[allow(clippy::unused_self)]
fn require_core(&self, specifier: &str) -> Result<(), ResolveError> {
if specifier.starts_with("node:") || BUILTINS.binary_search(&specifier).is_ok() {
return Err(ResolveError::Builtin(specifier.to_string()));
}
Ok(())
}
fn require_absolute(
&self,
cached_path: &CachedPath,

View file

@ -0,0 +1,88 @@
use crate::{ResolveError, Resolver};
use std::path::Path;
#[test]
fn builtins() {
let resolver = Resolver::default();
let f = Path::new("/");
#[rustfmt::skip]
let pass = [
"_http_agent",
"_http_client",
"_http_common",
"_http_incoming",
"_http_outgoing",
"_http_server",
"_stream_duplex",
"_stream_passthrough",
"_stream_readable",
"_stream_transform",
"_stream_wrap",
"_stream_writable",
"_tls_common",
"_tls_wrap",
"assert",
"assert/strict",
"async_hooks",
"buffer",
"child_process",
"cluster",
"console",
"constants",
"crypto",
"dgram",
"diagnostics_channel",
"dns",
"dns/promises",
"domain",
"events",
"fs",
"fs/promises",
"http",
"http2",
"https",
"inspector",
"module",
"net",
"os",
"path",
"path/posix",
"path/win32",
"perf_hooks",
"process",
"punycode",
"querystring",
"readline",
"repl",
"stream",
"stream/consumers",
"stream/promises",
"stream/web",
"string_decoder",
"sys",
"timers",
"timers/promises",
"tls",
"trace_events",
"tty",
"url",
"util",
"util/types",
"v8",
"vm",
"worker_threads",
"zlib",
];
for request in pass {
let resolved_path = resolver.resolve(f, request).map(|r| r.full_path());
assert_eq!(resolved_path, Err(ResolveError::Builtin(request.to_string())), "{request}");
}
for request in pass {
let request = format!("node:{request}");
let resolved_path = resolver.resolve(f, &request).map(|r| r.full_path());
assert_eq!(resolved_path, Err(ResolveError::Builtin(request.to_string())), "{request}");
}
}

View file

@ -16,7 +16,8 @@ fn extensions() {
("should resolve according to order of provided extensions", "./foo", "foo.ts"),
("should resolve according to order of provided extensions (dir index)", "./dir", "dir/index.ts"),
("should resolve according to main field in module root", ".", "index.js"),
("should resolve single file module before directory", "module", "node_modules/module.js"),
// This is a core module
// ("should resolve single file module before directory", "module", "node_modules/module.js"),
("should resolve trailing slash directory before single file", "module/", "node_modules/module/index.ts"),
];

View file

@ -1,5 +1,6 @@
mod alias;
mod browser_field;
mod builtins;
mod exports_field;
mod extension_alias;
mod extensions;