Vendor things

This commit is contained in:
John Doty 2024-03-08 11:03:01 -08:00
parent 5deceec006
commit 977e3c17e5
19434 changed files with 10682014 additions and 0 deletions

View file

@ -0,0 +1 @@
{"files":{"Cargo.toml":"2441c5727f8fc7a8ea984d5d1327b5e0257513259789824f65b0e52684dca714","src/lib.rs":"f0574f53ee044742de273fdd148600a26ad6352818e1e13f488f46184ce69d60","src/resolve.rs":"f8991b808a2f4592be9865c49d4851216fe84618e58b97d9a7d4e6c60f299eee","src/resolvers/lru.rs":"4bd4bfdf0da2f9d2b17ab7380992b505f364ff9f6de0d23453454c2ac253b76f","src/resolvers/mod.rs":"e383ac51bfb549be72ce510fee279f073b7375860551946a3a1bc17700fe5016","src/resolvers/node.rs":"227d9bebabe502c607f6a21363bfe63f25c2db801070412b8ede7f4fbc6345c3","src/resolvers/tsc.rs":"bbb665584e0e9f0beca530c29aee60775b05593311c9966e2b720105b9fb7084","tests/basic_import/node_modules/jquery/index.js":"b97dc449b77078dc8b6af5996da434382ae78a551e2268d0e9b7c0dea5dce8ab","tests/basic_import/node_modules/jquery/package.json":"239dfc0bd6b1e2a0859f9683801d41ca46d7f3e6e5ab9cc967d9d22ca14c98af","tests/browser_overwrite/node_modules/jquery/browser.js":"b97dc449b77078dc8b6af5996da434382ae78a551e2268d0e9b7c0dea5dce8ab","tests/browser_overwrite/node_modules/jquery/index.js":"b97dc449b77078dc8b6af5996da434382ae78a551e2268d0e9b7c0dea5dce8ab","tests/browser_overwrite/node_modules/jquery/package.json":"90521fbd10df8533a3af012884e389937a42dd78623b810aef1265ca50ac6fd8","tests/hoisting/node_modules/jquery/index.js":"b97dc449b77078dc8b6af5996da434382ae78a551e2268d0e9b7c0dea5dce8ab","tests/hoisting/node_modules/jquery/package.json":"239dfc0bd6b1e2a0859f9683801d41ca46d7f3e6e5ab9cc967d9d22ca14c98af","tests/hoisting/packages/app/file.js":"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855","tests/hoisting/packages/app/node_modules/example/index.js":"b97dc449b77078dc8b6af5996da434382ae78a551e2268d0e9b7c0dea5dce8ab","tests/hoisting/packages/app/node_modules/example/package.json":"d4463f734c27f09438eac742821dc2ee69e0e24659a8d5f470bdcfdfca2ca4e2","tests/hoisting/packages/app/package.json":"0e4b70448868163c4ce770bc7053e2e688671a444f0cce8e9819027bc713d499","tests/node_resolver.rs":"bb2ea9cf8c0ff97fd7b164c2958c655bcffea58083d79c8dccf2052d7c379352","tests/tsc_resolver.rs":"5503e2cbd58f008730ec4eedfb8d47fa35ef623a901513daa399b4f7dd715735"},"package":"e7d7c322462657ae27ac090a2c89f7e456c94416284a2f5ecf66c43a6a3c19d1"}

View file

@ -0,0 +1,100 @@
# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
#
# When uploading crates to the registry Cargo will automatically
# "normalize" Cargo.toml files for maximal compatibility
# with all versions of Cargo and also rewrite `path` dependencies
# to registry (e.g., crates.io) dependencies.
#
# If you are reading this file be aware that the original Cargo.toml
# will likely look very different (and much more reasonable).
# See Cargo.toml.orig for the original contents.
[package]
edition = "2021"
name = "swc_ecma_loader"
version = "0.44.2"
authors = ["강동윤 <kdy1997.dev@gmail.com>"]
description = "General ecmascript loader used for transforms"
documentation = "https://rustdoc.swc.rs/swc_ecma_loader/"
license = "Apache-2.0"
repository = "https://github.com/swc-project/swc.git"
[package.metadata.docs.rs]
all-features = true
rustdoc-args = [
"--cfg",
"docsrs",
]
[lib]
bench = false
[dependencies.anyhow]
version = "1.0.71"
[dependencies.dashmap]
version = "5.4.0"
optional = true
[dependencies.lru]
version = "0.10.0"
optional = true
[dependencies.once_cell]
version = "1.18.0"
optional = true
[dependencies.parking_lot]
version = "0.12.1"
optional = true
[dependencies.path-clean]
version = "=0.1.0"
optional = true
[dependencies.pathdiff]
version = "0.2.1"
[dependencies.serde]
version = "1"
features = ["derive"]
[dependencies.serde_json]
version = "1.0.64"
optional = true
[dependencies.swc_cached]
version = "0.3.17"
optional = true
[dependencies.swc_common]
version = "0.32.0"
[dependencies.tracing]
version = "0.1.37"
[dev-dependencies.lazy_static]
version = "1.4.0"
[features]
cache = [
"lru",
"parking_lot",
]
default = []
node = [
"normpath",
"serde_json",
"dashmap",
"once_cell",
"path-clean",
]
tsc = [
"dashmap",
"once_cell",
"swc_cached",
]
[target."cfg(windows)".dependencies.normpath]
version = "0.2"
optional = true

View file

@ -0,0 +1,93 @@
#![cfg_attr(docsrs, feature(doc_cfg))]
use serde::{Deserialize, Serialize};
pub mod resolve;
pub mod resolvers;
/// List of built in packages for latest stable node with LTS (node@16).
///
/// Run `node -p "require('module').builtinModules"`
pub const NODE_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",
"readline/promises",
"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",
];
/// Target runtime environment.
#[derive(Debug, Copy, Clone, Serialize, Deserialize, Eq, PartialEq, Hash)]
pub enum TargetEnv {
#[serde(rename = "browser")]
Browser,
#[serde(rename = "node")]
Node,
}
impl Default for TargetEnv {
fn default() -> Self {
TargetEnv::Browser
}
}

View file

@ -0,0 +1,28 @@
use std::sync::Arc;
use anyhow::Error;
use swc_common::{
sync::{Send, Sync},
FileName,
};
pub trait Resolve: Send + Sync {
fn resolve(&self, base: &FileName, module_specifier: &str) -> Result<FileName, Error>;
}
macro_rules! impl_ref {
($R:ident, $T:ty) => {
impl<$R> Resolve for $T
where
R: ?Sized + Resolve,
{
fn resolve(&self, base: &FileName, src: &str) -> Result<FileName, Error> {
(**self).resolve(base, src)
}
}
};
}
impl_ref!(R, &'_ R);
impl_ref!(R, Box<R>);
impl_ref!(R, Arc<R>);

View file

@ -0,0 +1,63 @@
use std::num::NonZeroUsize;
use anyhow::Error;
use lru::LruCache;
use parking_lot::Mutex;
use swc_common::FileName;
use crate::resolve::Resolve;
#[derive(Debug)]
pub struct CachingResolver<R>
where
R: Resolve,
{
cache: Mutex<LruCache<(FileName, String), FileName>>,
inner: R,
}
impl<R> Default for CachingResolver<R>
where
R: Resolve + Default,
{
fn default() -> Self {
Self::new(40, Default::default())
}
}
impl<R> CachingResolver<R>
where
R: Resolve,
{
pub fn new(cap: usize, inner: R) -> Self {
Self {
cache: Mutex::new(LruCache::new(
NonZeroUsize::try_from(cap).expect("cap == 0"),
)),
inner,
}
}
}
impl<R> Resolve for CachingResolver<R>
where
R: Resolve,
{
fn resolve(&self, base: &FileName, src: &str) -> Result<FileName, Error> {
{
let mut lock = self.cache.lock();
//
if let Some(v) = lock.get(&(base.clone(), src.to_string())) {
return Ok(v.clone());
}
}
let resolved = self.inner.resolve(base, src)?;
{
let mut lock = self.cache.lock();
lock.put((base.clone(), src.to_string()), resolved.clone());
}
Ok(resolved)
}
}

View file

@ -0,0 +1,9 @@
#[cfg(feature = "lru")]
#[cfg_attr(docsrs, doc(cfg(feature = "lru")))]
pub mod lru;
#[cfg(feature = "node")]
#[cfg_attr(docsrs, doc(cfg(feature = "node")))]
pub mod node;
#[cfg(feature = "tsc")]
#[cfg_attr(docsrs, doc(cfg(feature = "tsc")))]
pub mod tsc;

View file

@ -0,0 +1,529 @@
//! Faster version of node-resolve.
//!
//! See: https://github.com/goto-bus-stop/node-resolve
use std::{
env::current_dir,
fs::File,
io::BufReader,
path::{Component, Path, PathBuf},
};
use anyhow::{bail, Context, Error};
use dashmap::DashMap;
#[cfg(windows)]
use normpath::BasePath;
use once_cell::sync::Lazy;
use path_clean::PathClean;
use pathdiff::diff_paths;
use serde::Deserialize;
use swc_common::{
collections::{AHashMap, AHashSet, ARandomState},
FileName,
};
use tracing::{debug, trace, Level};
use crate::{resolve::Resolve, TargetEnv, NODE_BUILTINS};
static PACKAGE: &str = "package.json";
/// Map of cached `browser` fields from deserialized package.json
/// used to determine if we need to map to alternative paths when
/// bundling for the browser runtime environment. The key is the
/// directory containing the package.json file which is important
/// to ensure we only apply these `browser` rules to modules in
/// the owning package.
static BROWSER_CACHE: Lazy<DashMap<PathBuf, BrowserCache, ARandomState>> =
Lazy::new(Default::default);
#[derive(Debug, Default)]
struct BrowserCache {
rewrites: AHashMap<PathBuf, PathBuf>,
ignores: AHashSet<PathBuf>,
module_rewrites: AHashMap<String, PathBuf>,
module_ignores: AHashSet<String>,
}
/// Helper to find the nearest `package.json` file to get
/// the base directory for a package.
fn find_package_root(path: &Path) -> Option<PathBuf> {
let mut parent = path.parent();
while let Some(p) = parent {
let pkg = p.join(PACKAGE);
if pkg.is_file() {
return Some(p.to_path_buf());
}
parent = p.parent();
}
None
}
pub fn to_absolute_path(path: &Path) -> Result<PathBuf, Error> {
let absolute_path = if path.is_absolute() {
path.to_path_buf()
} else {
current_dir()?.join(path)
}
.clean();
Ok(absolute_path)
}
pub(crate) fn is_core_module(s: &str) -> bool {
NODE_BUILTINS.contains(&s)
}
#[derive(Deserialize)]
struct PackageJson {
#[serde(default)]
main: Option<String>,
#[serde(default)]
browser: Option<Browser>,
#[serde(default)]
module: Option<String>,
}
#[derive(Deserialize)]
#[serde(untagged)]
enum Browser {
Str(String),
Obj(AHashMap<String, StringOrBool>),
}
#[derive(Deserialize, Clone)]
#[serde(untagged)]
enum StringOrBool {
Str(String),
Bool(bool),
}
#[derive(Debug, Default)]
pub struct NodeModulesResolver {
target_env: TargetEnv,
alias: AHashMap<String, String>,
// if true do not resolve symlink
preserve_symlinks: bool,
ignore_node_modules: bool,
}
static EXTENSIONS: &[&str] = &["ts", "tsx", "js", "jsx", "json", "node"];
impl NodeModulesResolver {
/// Create a node modules resolver for the target runtime environment.
pub fn new(
target_env: TargetEnv,
alias: AHashMap<String, String>,
preserve_symlinks: bool,
) -> Self {
Self {
target_env,
alias,
preserve_symlinks,
ignore_node_modules: false,
}
}
/// Create a node modules resolver which does not care about `node_modules`
pub fn without_node_modules(
target_env: TargetEnv,
alias: AHashMap<String, String>,
preserve_symlinks: bool,
) -> Self {
Self {
target_env,
alias,
preserve_symlinks,
ignore_node_modules: true,
}
}
fn wrap(&self, path: Option<PathBuf>) -> Result<FileName, Error> {
if let Some(path) = path {
if self.preserve_symlinks {
return Ok(FileName::Real(path.clean()));
} else {
return Ok(FileName::Real(path.canonicalize()?));
}
}
bail!("index not found")
}
/// Resolve a path as a file. If `path` refers to a file, it is returned;
/// otherwise the `path` + each extension is tried.
fn resolve_as_file(&self, path: &Path) -> Result<Option<PathBuf>, Error> {
let _tracing = if cfg!(debug_assertions) {
Some(
tracing::span!(
Level::ERROR,
"resolve_as_file",
path = tracing::field::display(path.display())
)
.entered(),
)
} else {
None
};
if cfg!(debug_assertions) {
trace!("resolve_as_file({})", path.display());
}
let try_exact = path.extension().is_some();
if try_exact {
if path.is_file() {
return Ok(Some(path.to_path_buf()));
}
} else {
// We try `.js` first.
let mut path = path.to_path_buf();
path.set_extension("js");
if path.is_file() {
return Ok(Some(path));
}
}
// Try exact file after checking .js, for performance
if !try_exact && path.is_file() {
return Ok(Some(path.to_path_buf()));
}
if let Some(name) = path.file_name() {
let mut ext_path = path.to_path_buf();
let name = name.to_string_lossy();
for ext in EXTENSIONS {
ext_path.set_file_name(format!("{}.{}", name, ext));
if ext_path.is_file() {
return Ok(Some(ext_path));
}
}
// TypeScript-specific behavior: if the extension is ".js" or ".jsx",
// try replacing it with ".ts" or ".tsx".
ext_path.set_file_name(name.into_owned());
let old_ext = path.extension().and_then(|ext| ext.to_str());
if let Some(old_ext) = old_ext {
let extensions: &[&str] = match old_ext {
// Note that the official compiler code always tries ".ts" before
// ".tsx" even if the original extension was ".jsx".
"js" => &["ts", "tsx"],
"jsx" => &["ts", "tsx"],
"mjs" => &["mts"],
"cjs" => &["cts"],
_ => &[],
};
for ext in extensions {
ext_path.set_extension(ext);
if ext_path.is_file() {
return Ok(Some(ext_path));
}
}
}
}
bail!("file not found: {}", path.display())
}
/// Resolve a path as a directory, using the "main" key from a package.json
/// file if it exists, or resolving to the index.EXT file if it exists.
fn resolve_as_directory(
&self,
path: &Path,
allow_package_entry: bool,
) -> Result<Option<PathBuf>, Error> {
let _tracing = if cfg!(debug_assertions) {
Some(
tracing::span!(
Level::ERROR,
"resolve_as_directory",
path = tracing::field::display(path.display())
)
.entered(),
)
} else {
None
};
if cfg!(debug_assertions) {
trace!("resolve_as_directory({})", path.display());
}
let pkg_path = path.join(PACKAGE);
if allow_package_entry && pkg_path.is_file() {
if let Some(main) = self.resolve_package_entry(path, &pkg_path)? {
return Ok(Some(main));
}
}
// Try to resolve to an index file.
for ext in EXTENSIONS {
let ext_path = path.join(format!("index.{}", ext));
if ext_path.is_file() {
return Ok(Some(ext_path));
}
}
Ok(None)
}
/// Resolve using the package.json "main" or "browser" keys.
fn resolve_package_entry(
&self,
pkg_dir: &Path,
pkg_path: &Path,
) -> Result<Option<PathBuf>, Error> {
let _tracing = if cfg!(debug_assertions) {
Some(
tracing::span!(
Level::ERROR,
"resolve_package_entry",
pkg_dir = tracing::field::display(pkg_dir.display()),
pkg_path = tracing::field::display(pkg_path.display()),
)
.entered(),
)
} else {
None
};
let file = File::open(pkg_path)?;
let reader = BufReader::new(file);
let pkg: PackageJson = serde_json::from_reader(reader)
.context(format!("failed to deserialize {}", pkg_path.display()))?;
let main_fields = match self.target_env {
TargetEnv::Node => {
vec![pkg.module.as_ref(), pkg.main.as_ref()]
}
TargetEnv::Browser => {
if let Some(browser) = &pkg.browser {
match browser {
Browser::Str(path) => {
vec![Some(path), pkg.module.as_ref(), pkg.main.as_ref()]
}
Browser::Obj(map) => {
let mut bucket = BrowserCache::default();
for (k, v) in map {
let target_key = Path::new(k);
let mut components = target_key.components();
// Relative file paths are sources for this package
let source = if let Some(Component::CurDir) = components.next() {
let path = pkg_dir.join(k);
if let Ok(file) = self
.resolve_as_file(&path)
.or_else(|_| self.resolve_as_directory(&path, false))
{
file.map(|file| file.clean())
} else {
None
}
} else {
None
};
match v {
StringOrBool::Str(dest) => {
let path = pkg_dir.join(dest);
let file = self
.resolve_as_file(&path)
.or_else(|_| self.resolve_as_directory(&path, false))?;
if let Some(file) = file {
let target = file.clean();
let target = target
.strip_prefix(current_dir().unwrap_or_default())
.map(|target| target.to_path_buf())
.unwrap_or(target);
if let Some(source) = source {
bucket.rewrites.insert(source, target);
} else {
bucket.module_rewrites.insert(k.clone(), target);
}
}
}
StringOrBool::Bool(flag) => {
// If somebody set boolean `true` which is an
// invalid value we will just ignore it
if !flag {
if let Some(source) = source {
bucket.ignores.insert(source);
} else {
bucket.module_ignores.insert(k.clone());
}
}
}
}
}
BROWSER_CACHE.insert(pkg_dir.to_path_buf(), bucket);
vec![pkg.module.as_ref(), pkg.main.as_ref()]
}
}
} else {
vec![pkg.module.as_ref(), pkg.main.as_ref()]
}
}
};
if let Some(Some(target)) = main_fields.iter().find(|x| x.is_some()) {
let path = pkg_dir.join(target);
return self
.resolve_as_file(&path)
.or_else(|_| self.resolve_as_directory(&path, false));
}
Ok(None)
}
/// Resolve by walking up node_modules folders.
fn resolve_node_modules(
&self,
base_dir: &Path,
target: &str,
) -> Result<Option<PathBuf>, Error> {
if self.ignore_node_modules {
return Ok(None);
}
let absolute_path = to_absolute_path(base_dir)?;
let mut path = Some(&*absolute_path);
while let Some(dir) = path {
let node_modules = dir.join("node_modules");
if node_modules.is_dir() {
let path = node_modules.join(target);
if let Some(result) = self
.resolve_as_file(&path)
.ok()
.or_else(|| self.resolve_as_directory(&path, true).ok())
.flatten()
{
return Ok(Some(result));
}
}
path = dir.parent();
}
Ok(None)
}
}
impl Resolve for NodeModulesResolver {
fn resolve(&self, base: &FileName, target: &str) -> Result<FileName, Error> {
debug!(
"Resolving {} from {:#?} for {:#?}",
target, base, self.target_env
);
let base = match base {
FileName::Real(v) => v,
_ => bail!("node-resolver supports only files"),
};
let base_dir = if base.is_file() {
let cwd = &Path::new(".");
base.parent().unwrap_or(cwd)
} else {
base
};
// Handle module references for the `browser` package config
// before we map aliases.
if let TargetEnv::Browser = self.target_env {
if let Some(pkg_base) = find_package_root(base) {
if let Some(item) = BROWSER_CACHE.get(&pkg_base) {
let value = item.value();
if value.module_ignores.contains(target) {
return Ok(FileName::Custom(target.into()));
}
if let Some(rewrite) = value.module_rewrites.get(target) {
return self.wrap(Some(rewrite.to_path_buf()));
}
}
}
}
// Handle builtin modules for nodejs
if let TargetEnv::Node = self.target_env {
if target.starts_with("node:") {
return Ok(FileName::Custom(target.into()));
}
if is_core_module(target) {
return Ok(FileName::Custom(format!("node:{}", target)));
}
}
// Aliases allow browser shims to be renamed so we can
// map `stream` to `stream-browserify` for example
let target = if let Some(alias) = self.alias.get(target) {
&alias[..]
} else {
target
};
let target_path = Path::new(target);
let file_name = {
if target_path.is_absolute() {
let path = PathBuf::from(target_path);
self.resolve_as_file(&path)
.or_else(|_| self.resolve_as_directory(&path, true))
.and_then(|p| self.wrap(p))
} else {
let mut components = target_path.components();
if let Some(Component::CurDir | Component::ParentDir) = components.next() {
#[cfg(windows)]
let path = {
let base_dir = BasePath::new(base_dir).unwrap();
base_dir
.join(target.replace('/', "\\"))
.normalize_virtually()
.unwrap()
.into_path_buf()
};
#[cfg(not(windows))]
let path = base_dir.join(target);
self.resolve_as_file(&path)
.or_else(|_| self.resolve_as_directory(&path, true))
.and_then(|p| self.wrap(p))
} else {
self.resolve_node_modules(base_dir, target)
.and_then(|path| {
let file_path = path.context("failed to get the node_modules path");
let current_directory = current_dir()?;
let relative_path = diff_paths(file_path?, current_directory);
self.wrap(relative_path)
})
}
}
}
.and_then(|v| {
// Handle path references for the `browser` package config
if let TargetEnv::Browser = self.target_env {
if let FileName::Real(path) = &v {
if let Some(pkg_base) = find_package_root(path) {
let pkg_base = to_absolute_path(&pkg_base).unwrap();
if let Some(item) = BROWSER_CACHE.get(&pkg_base) {
let value = item.value();
let path = to_absolute_path(path).unwrap();
if value.ignores.contains(&path) {
return Ok(FileName::Custom(path.display().to_string()));
}
if let Some(rewrite) = value.rewrites.get(&path) {
return self.wrap(Some(rewrite.to_path_buf()));
}
}
}
}
}
Ok(v)
});
file_name
}
}

View file

@ -0,0 +1,292 @@
use std::path::{Component, Path, PathBuf};
use anyhow::{bail, Context, Error};
use swc_common::FileName;
use tracing::{debug, info, trace, warn, Level};
use crate::resolve::Resolve;
#[derive(Debug)]
enum Pattern {
Wildcard {
prefix: String,
},
/// No wildcard.
Exact(String),
}
/// Support for `paths` of `tsconfig.json`.
///
/// See https://www.typescriptlang.org/docs/handbook/module-resolution.html#path-mapping
#[derive(Debug)]
pub struct TsConfigResolver<R>
where
R: Resolve,
{
inner: R,
base_url: PathBuf,
base_url_filename: FileName,
paths: Vec<(Pattern, Vec<String>)>,
}
impl<R> TsConfigResolver<R>
where
R: Resolve,
{
///
/// # Parameters
///
/// ## base_url
///
/// See https://www.typescriptlang.org/tsconfig#baseUrl
///
/// The typescript documentation says `This must be specified if "paths"
/// is.`.
///
/// ## `paths`
///
/// Pass `paths` map from `tsconfig.json`.
///
/// See https://www.typescriptlang.org/tsconfig#paths
///
/// Note that this is not a hashmap because value is not used as a hash map.
pub fn new(inner: R, base_url: PathBuf, paths: Vec<(String, Vec<String>)>) -> Self {
if cfg!(debug_assertions) {
info!(
base_url = tracing::field::display(base_url.display()),
"jsc.paths"
);
}
let paths = paths
.into_iter()
.map(|(from, to)| {
assert!(
!to.is_empty(),
"value of `paths.{}` should not be an empty array",
from,
);
let pos = from.as_bytes().iter().position(|&c| c == b'*');
let pat = if from.contains('*') {
if from.as_bytes().iter().rposition(|&c| c == b'*') != pos {
panic!("`paths.{}` should have only one wildcard", from)
}
Pattern::Wildcard {
prefix: from[..pos.unwrap()].to_string(),
}
} else {
assert_eq!(
to.len(),
1,
"value of `paths.{}` should be an array with one element because the src \
path does not contains * (wildcard)",
from,
);
Pattern::Exact(from)
};
(pat, to)
})
.collect();
Self {
inner,
base_url_filename: FileName::Real(base_url.clone()),
base_url,
paths,
}
}
fn invoke_inner_resolver(
&self,
base: &FileName,
module_specifier: &str,
) -> Result<FileName, Error> {
let res = self.inner.resolve(base, module_specifier).with_context(|| {
format!(
"failed to resolve `{module_specifier}` from `{base}` using inner \
resolver\nbase_url={}",
self.base_url_filename
)
});
match res {
Ok(resolved) => {
info!(
"Resolved `{}` as `{}` from `{}`",
module_specifier, resolved, base
);
if let FileName::Real(target) = &resolved {
// If node_modules is in path, we should return module specifier.
if target.components().any(|c| {
if let Component::Normal(v) = c {
v == "node_modules"
} else {
false
}
}) {
return Ok(FileName::Real(module_specifier.into()));
}
}
Ok(resolved)
}
Err(err) => {
warn!("{:?}", err);
Err(err)
}
}
}
}
impl<R> Resolve for TsConfigResolver<R>
where
R: Resolve,
{
fn resolve(&self, base: &FileName, module_specifier: &str) -> Result<FileName, Error> {
let _tracing = if cfg!(debug_assertions) {
Some(
tracing::span!(
Level::ERROR,
"TsConfigResolver::resolve",
base_url = tracing::field::display(self.base_url.display()),
base = tracing::field::display(base),
src = tracing::field::display(module_specifier),
)
.entered(),
)
} else {
None
};
if module_specifier.starts_with('.')
&& (module_specifier == ".."
|| module_specifier.starts_with("./")
|| module_specifier.starts_with("../"))
{
return self
.invoke_inner_resolver(base, module_specifier)
.context("not processed by tsc resolver because it's relative import");
}
if let FileName::Real(v) = base {
if v.components().any(|c| match c {
Component::Normal(v) => v == "node_modules",
_ => false,
}) {
return self.invoke_inner_resolver(base, module_specifier).context(
"not processed by tsc resolver because base module is in node_modules",
);
}
}
info!("Checking `jsc.paths`");
// https://www.typescriptlang.org/docs/handbook/module-resolution.html#path-mapping
for (from, to) in &self.paths {
match from {
Pattern::Wildcard { prefix } => {
debug!("Checking `{}` in `jsc.paths`", prefix);
let extra = module_specifier.strip_prefix(prefix);
let extra = match extra {
Some(v) => v,
None => {
if cfg!(debug_assertions) {
trace!("skip because src doesn't start with prefix");
}
continue;
}
};
if cfg!(debug_assertions) {
debug!("Extra: `{}`", extra);
}
let mut errors = vec![];
for target in to {
let mut replaced = target.replace('*', extra);
let _tracing = if cfg!(debug_assertions) {
Some(
tracing::span!(
Level::ERROR,
"TsConfigResolver::resolve::jsc.paths",
replaced = tracing::field::display(&replaced),
)
.entered(),
)
} else {
None
};
let relative = format!("./{}", replaced);
let res = self
.invoke_inner_resolver(base, module_specifier)
.or_else(|_| {
self.invoke_inner_resolver(&self.base_url_filename, &relative)
})
.or_else(|_| {
self.invoke_inner_resolver(&self.base_url_filename, &replaced)
});
errors.push(match res {
Ok(resolved) => return Ok(resolved),
Err(err) => err,
});
if cfg!(target_os = "windows") {
replaced = replaced.replace('/', "\\");
}
if to.len() == 1 {
info!(
"Using `{}` for `{}` because the length of the jsc.paths entry is \
1",
replaced, module_specifier
);
return Ok(FileName::Real(replaced.into()));
}
}
bail!(
"`{}` matched `{}` (from tsconfig.paths) but failed to resolve:\n{:?}",
module_specifier,
prefix,
errors
)
}
Pattern::Exact(from) => {
// Should be exactly matched
if module_specifier != from {
continue;
}
let tp = Path::new(&to[0]);
if tp.is_absolute() {
return Ok(FileName::Real(tp.into()));
}
if let Ok(res) = self.resolve(&self.base_url_filename, &format!("./{}", &to[0]))
{
return Ok(res);
}
return Ok(FileName::Real(self.base_url.join(&to[0])));
}
}
}
if let Ok(v) = self.invoke_inner_resolver(&self.base_url_filename, module_specifier) {
return Ok(v);
}
self.invoke_inner_resolver(base, module_specifier)
}
}

View file

@ -0,0 +1 @@
console.log("hello world!");

View file

@ -0,0 +1,4 @@
{
"name": "jquery",
"main": "index.js"
}

View file

@ -0,0 +1 @@
console.log("hello world!");

View file

@ -0,0 +1 @@
console.log("hello world!");

View file

@ -0,0 +1,7 @@
{
"name": "jquery",
"main": "./index.js",
"browser": {
"./index.js": "./browser.js"
}
}

View file

@ -0,0 +1 @@
console.log("hello world!");

View file

@ -0,0 +1,4 @@
{
"name": "jquery",
"main": "index.js"
}

View file

@ -0,0 +1 @@
console.log("hello world!");

View file

@ -0,0 +1,4 @@
{
"name": "example",
"main": "index.js"
}

View file

@ -0,0 +1,4 @@
{
"name": "app",
"main": "file.js"
}

View file

@ -0,0 +1,107 @@
#![deny(warnings)]
#![cfg(feature = "node")]
use std::{
env::{current_dir, set_current_dir},
path::PathBuf,
sync::{Arc, Mutex},
};
use lazy_static::lazy_static;
use swc_common::FileName;
extern crate swc_ecma_loader;
use swc_ecma_loader::{resolve::Resolve, resolvers::node::NodeModulesResolver, TargetEnv};
lazy_static! {
static ref UPDATE_DIR_MUTEX: Arc<Mutex<()>> = Arc::new(Mutex::new(()));
}
fn inside_directory(dir: &str, callback: fn()) {
let arc = Arc::clone(&UPDATE_DIR_MUTEX);
let _lock = arc.lock().expect("could not lock");
let initial_current_dir = current_dir().unwrap();
let new_current_dir = format!("{}{}", initial_current_dir.display(), dir);
set_current_dir(new_current_dir).unwrap();
callback();
set_current_dir(initial_current_dir).unwrap();
}
#[test]
fn basic_import() {
inside_directory("/tests/basic_import", || {
// Given
let node_resolver = NodeModulesResolver::new(TargetEnv::Node, Default::default(), true);
// When
let resolved = node_resolver
.resolve(&FileName::Real(PathBuf::from("jquery")), "jquery")
.expect("should resolve");
// Expect
assert_eq!(
resolved,
FileName::Real(PathBuf::from("node_modules/jquery/index.js"))
);
});
}
#[test]
fn hoisting() {
inside_directory("/tests/hoisting/packages/app", || {
// Given
let node_resolver = NodeModulesResolver::new(TargetEnv::Node, Default::default(), true);
// When
let resolved = node_resolver
.resolve(&FileName::Real(PathBuf::from("jquery")), "jquery")
.expect("should resolve");
// Expect
assert_eq!(
resolved,
FileName::Real(PathBuf::from("../../node_modules/jquery/index.js"))
);
});
}
#[test]
fn builtin_modules() {
let node_resolver = NodeModulesResolver::new(TargetEnv::Node, Default::default(), true);
// When
let resolved = node_resolver
.resolve(&FileName::Real(PathBuf::from("index.js")), "path")
.expect("should resolve");
// Expect
assert_eq!(resolved, FileName::Custom("node:path".to_string()));
// When
let resolved = node_resolver
.resolve(&FileName::Real(PathBuf::from("index.js")), "node:path")
.expect("should resolve");
// Expect
assert_eq!(resolved, FileName::Custom("node:path".to_string()));
}
#[test]
fn browser_overwrite() {
inside_directory("/tests/browser_overwrite", || {
// Given
let node_resolver = NodeModulesResolver::new(TargetEnv::Browser, Default::default(), true);
// When
let resolved = node_resolver
.resolve(&FileName::Real(PathBuf::from("jquery")), "jquery")
.expect("should resolve");
// Expect
assert_eq!(
resolved,
FileName::Real(PathBuf::from("node_modules/jquery/browser.js"))
);
});
}

View file

@ -0,0 +1,89 @@
#![cfg(feature = "tsc")]
use std::collections::HashMap;
use anyhow::{anyhow, Error};
use swc_common::{collections::AHashMap, FileName};
use swc_ecma_loader::{resolve::Resolve, resolvers::tsc::TsConfigResolver};
#[test]
fn base_dir_exact() {}
#[test]
fn base_dir_wildcard() {}
#[test]
fn exact() {
let mut map = HashMap::default();
map.insert("jquery".to_string(), "fail".to_string());
map.insert(
"./node_modules/jquery/dist/jquery".to_string(),
"success".to_string(),
);
let r = TsConfigResolver::new(
TestResolver(map),
".".into(),
vec![(
"jquery".into(),
vec!["node_modules/jquery/dist/jquery".into()],
)],
);
{
let resolved = r
.resolve(&FileName::Anon, "jquery")
.expect("should resolve");
assert_eq!(resolved, FileName::Custom("success".into()));
}
{
r.resolve(&FileName::Anon, "unrelated")
.expect_err("should not touch error");
}
}
#[test]
fn pattern_1() {
let mut map = HashMap::default();
map.insert("./folder1/file1".to_string(), "success-1".to_string());
map.insert("./folder1/file2".to_string(), "success-2".to_string());
map.insert(
"./generated/folder2/file3".to_string(),
"success-3".to_string(),
);
let r = TsConfigResolver::new(
TestResolver(map),
".".into(),
vec![("*".into(), vec!["*".into(), "generated/*".into()])],
);
{
let resolved = r
.resolve(&FileName::Anon, "folder1/file2")
.expect("should resolve");
assert_eq!(resolved, FileName::Custom("success-2".into()));
}
{
let resolved = r
.resolve(&FileName::Anon, "folder2/file3")
.expect("should resolve");
assert_eq!(resolved, FileName::Custom("success-3".into()));
}
}
struct TestResolver(AHashMap<String, String>);
impl Resolve for TestResolver {
fn resolve(&self, _: &FileName, src: &str) -> Result<FileName, Error> {
self.0
.get(src)
.cloned()
.map(FileName::Custom)
.ok_or_else(|| anyhow!("failed to resolve `{}`", src))
}
}