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"), (cwd.join("test/fixtures"), "m1/a.js?query#fragment"),
// extensions // extensions
(cwd.join("test/fixtures/extensions"), "./foo"), (cwd.join("test/fixtures/extensions"), "./foo"),
(cwd.join("test/fixtures/extensions/module"), "module"), (cwd.join("test/fixtures/extensions/module"), "module/"),
// browserField // browserField
(cwd.join("test/fixtures/browser-module"), "./lib/replaced"), (cwd.join("test/fixtures/browser-module"), "./lib/replaced"),
(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 /// Path not found
NotFound(PathBuf), 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 /// All of the aliased extension are not found
ExtensionAlias, ExtensionAlias,

View file

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

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", "./foo", "foo.ts"),
("should resolve according to order of provided extensions (dir index)", "./dir", "dir/index.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 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"), ("should resolve trailing slash directory before single file", "module/", "node_modules/module/index.ts"),
]; ];

View file

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