Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Migrate to package.json/tsconfig.json crates #99

Merged
merged 9 commits into from
Mar 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
43 changes: 40 additions & 3 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -78,11 +78,12 @@ serde_json = { version = "1.0.114", features = [
"preserve_order",
] } # preserve_order: package_json.exports requires order such as `["require", "import", "default"]`
rustc-hash = { version = "1.1.0", default-features = false, features = ["std"] }
indexmap = { version = "2.2.5", features = ["serde"] } # serde for Deserialize from package.json
dunce = "1.0.4" # Normalize Windows paths to the most compatible format, avoiding UNC where possible
once_cell = "1.19.0" # Use `std::sync::OnceLock::get_or_try_init` when it is stable.
thiserror = { version = "1.0.57" }
json-strip-comments = { version = "1.0.2" }
typescript_tsconfig_json = { version = "0.1.1" }
nodejs_package_json = { version = "0.1.1" }

codspeed-criterion-compat = { version = "2.4.0", default-features = false, optional = true }

Expand Down
1 change: 1 addition & 0 deletions napi/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,7 @@ impl ResolverFactory {
.unwrap_or(default.roots),
symlinks: op.symlinks.unwrap_or(default.symlinks),
builtin_modules: op.builtin_modules.unwrap_or(default.builtin_modules),
remove_unused_fields: op.remove_unused_fields.unwrap_or(default.remove_unused_fields),
}
}
}
4 changes: 4 additions & 0 deletions napi/src/options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,10 @@ pub struct NapiResolveOptions {
///
/// Default `false`
pub builtin_modules: Option<bool>,

/// When enabled, will attempt to reduce memory usage by clearing data and fields
/// that are not relevant to the current process, and should not be held in memory.
pub remove_unused_fields: Option<bool>,
}

#[napi]
Expand Down
48 changes: 26 additions & 22 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ use std::{
path::{Component, Path, PathBuf},
sync::Arc,
};
use typescript_tsconfig_json::ExtendsField;

pub use crate::{
builtins::NODEJS_BUILTINS,
Expand All @@ -61,11 +62,11 @@ use crate::{
cache::{Cache, CachedPath},
context::ResolveContext as Ctx,
file_system::FileSystemOs,
package_json::{ExportsField, ExportsKey, MatchObject},
path::{PathUtil, SLASH_START},
specifier::Specifier,
tsconfig::{ProjectReference, TsConfig},
};
use nodejs_package_json::{ImportExportField, ImportExportKey, ImportExportMap};

type ResolveResult = Result<Option<CachedPath>, ResolveError>;

Expand Down Expand Up @@ -752,6 +753,7 @@ impl<Fs: FileSystem + Default> ResolverGeneric<Fs> {
if !package_json.exports.is_empty() {
// 4. If the SCOPE/package.json "name" is not the first segment of X, return.
if let Some(subpath) = package_json
.data
.name
.as_ref()
.and_then(|package_name| Self::strip_package_name(specifier, package_name))
Expand Down Expand Up @@ -1000,7 +1002,8 @@ impl<Fs: FileSystem + Default> ResolverGeneric<Fs> {
tracing::trace!(tsconfig = ?tsconfig, "load_tsconfig");

// Extend tsconfig
if let Some(tsconfig_extend_specifier) = &tsconfig.extends {
// TODO multiple extends
if let Some(ExtendsField::Single(tsconfig_extend_specifier)) = &tsconfig.data.extends {
Boshen marked this conversation as resolved.
Show resolved Hide resolved
let extended_tsconfig_path = match tsconfig_extend_specifier.as_bytes().first() {
None => {
return Err(ResolveError::Specifier(SpecifierError::Empty(
Expand Down Expand Up @@ -1131,17 +1134,18 @@ impl<Fs: FileSystem + Default> ResolverGeneric<Fs> {
&self,
package_url: &Path,
subpath: &str,
exports: &ExportsField,
exports: &ImportExportField,
conditions: &[String],
ctx: &mut Ctx,
) -> ResolveResult {
// 1. If exports is an Object with both a key starting with "." and a key not starting with ".", throw an Invalid Package Configuration error.
if let ExportsField::Map(map) = exports {
if let ImportExportField::Map(map) = exports {
let mut has_dot = false;
let mut without_dot = false;
for key in map.keys() {
has_dot = has_dot || matches!(key, ExportsKey::Main | ExportsKey::Pattern(_));
without_dot = without_dot || matches!(key, ExportsKey::CustomCondition(_));
has_dot =
has_dot || matches!(key, ImportExportKey::Main | ImportExportKey::Pattern(_));
without_dot = without_dot || matches!(key, ImportExportKey::CustomCondition(_));
if has_dot && without_dot {
return Err(ResolveError::InvalidPackageConfig(
package_url.join("package.json"),
Expand All @@ -1165,18 +1169,18 @@ impl<Fs: FileSystem + Default> ResolverGeneric<Fs> {
}
// 1. Let mainExport be undefined.
let main_export = match exports {
ExportsField::None => None,
ImportExportField::None => None,
// 2. If exports is a String or Array, or an Object containing no keys starting with ".", then
ExportsField::String(_) | ExportsField::Array(_) => {
ImportExportField::String(_) | ImportExportField::Array(_) => {
// 1. Set mainExport to exports.
Some(exports)
}
// 3. Otherwise if exports is an Object containing a "." property, then
ExportsField::Map(map) => {
ImportExportField::Map(map) => {
// 1. Set mainExport to exports["."].
map.get(&ExportsKey::Main).map_or_else(
map.get(&ImportExportKey::Main).map_or_else(
|| {
if map.keys().any(|key| matches!(key, ExportsKey::Pattern(_))) {
if map.keys().any(|key| matches!(key, ImportExportKey::Pattern(_))) {
None
} else {
Some(exports)
Expand Down Expand Up @@ -1205,7 +1209,7 @@ impl<Fs: FileSystem + Default> ResolverGeneric<Fs> {
}
}
// 3. Otherwise, if exports is an Object and all keys of exports start with ".", then
if let ExportsField::Map(exports) = exports {
if let ImportExportField::Map(exports) = exports {
// 1. Let matchKey be the string "./" concatenated with subpath.
// Note: `package_imports_exports_resolve` does not require the leading dot.
let match_key = &subpath;
Expand Down Expand Up @@ -1273,7 +1277,7 @@ impl<Fs: FileSystem + Default> ResolverGeneric<Fs> {
fn package_imports_exports_resolve(
&self,
match_key: &str,
match_obj: &MatchObject,
match_obj: &ImportExportMap,
package_url: &Path,
is_imports: bool,
conditions: &[String],
Expand All @@ -1287,7 +1291,7 @@ impl<Fs: FileSystem + Default> ResolverGeneric<Fs> {
// 1. If matchKey is a key of matchObj and does not contain "*", then
if !match_key.contains('*') {
// 1. Let target be the value of matchObj[matchKey].
if let Some(target) = match_obj.get(&ExportsKey::Pattern(match_key.to_string())) {
if let Some(target) = match_obj.get(&ImportExportKey::Pattern(match_key.to_string())) {
// 2. Return the result of PACKAGE_TARGET_RESOLVE(packageURL, target, null, isImports, conditions).
return self.package_target_resolve(
package_url,
Expand All @@ -1307,7 +1311,7 @@ impl<Fs: FileSystem + Default> ResolverGeneric<Fs> {
// 2. Let expansionKeys be the list of keys of matchObj containing only a single "*", sorted by the sorting function PATTERN_KEY_COMPARE which orders in descending order of specificity.
// 3. For each key expansionKey in expansionKeys, do
for (expansion_key, target) in match_obj {
if let ExportsKey::Pattern(expansion_key) = expansion_key {
if let ImportExportKey::Pattern(expansion_key) = expansion_key {
// 1. Let patternBase be the substring of expansionKey up to but excluding the first "*" character.
if let Some((pattern_base, pattern_trailer)) = expansion_key.split_once('*') {
// 2. If matchKey starts with but is not equal to patternBase, then
Expand Down Expand Up @@ -1360,7 +1364,7 @@ impl<Fs: FileSystem + Default> ResolverGeneric<Fs> {
&self,
package_url: &Path,
target_key: &str,
target: &ExportsField,
target: &ImportExportField,
pattern_match: Option<&str>,
is_imports: bool,
conditions: &[String],
Expand Down Expand Up @@ -1393,9 +1397,9 @@ impl<Fs: FileSystem + Default> ResolverGeneric<Fs> {
}

match target {
ExportsField::None => {}
ImportExportField::None => {}
// 1. If target is a String, then
ExportsField::String(target) => {
ImportExportField::String(target) => {
// 1. If target does not start with "./", then
if !target.starts_with("./") {
// 1. If isImports is false, or if target starts with "../" or "/", or if target is a valid URL, then
Expand Down Expand Up @@ -1436,14 +1440,14 @@ impl<Fs: FileSystem + Default> ResolverGeneric<Fs> {
return Ok(Some(value));
}
// 2. Otherwise, if target is a non-null Object, then
ExportsField::Map(target) => {
ImportExportField::Map(target) => {
// 1. If exports contains any index property keys, as defined in ECMA-262 6.1.7 Array Index, throw an Invalid Package Configuration error.
// 2. For each property p of target, in object insertion order as,
for (i, (key, target_value)) in target.iter().enumerate() {
// https://nodejs.org/api/packages.html#conditional-exports
// "default" - the generic fallback that always matches. Can be a CommonJS or ES module file. This condition should always come last.
// Note: node.js does not throw this but enhanced-resolve does.
let is_default = matches!(key, ExportsKey::CustomCondition(condition) if condition == "default");
let is_default = matches!(key, ImportExportKey::CustomCondition(condition) if condition == "default");
if i < target.len() - 1 && is_default {
return Err(ResolveError::InvalidPackageConfigDefault(
package_url.join("package.json"),
Expand All @@ -1452,7 +1456,7 @@ impl<Fs: FileSystem + Default> ResolverGeneric<Fs> {

// 1. If p equals "default" or conditions contains an entry for p, then
if is_default
|| matches!(key, ExportsKey::CustomCondition(condition) if conditions.contains(condition))
|| matches!(key, ImportExportKey::CustomCondition(condition) if conditions.contains(condition))
{
// 1. Let targetValue be the value of the p property in target.
// 2. Let resolved be the result of PACKAGE_TARGET_RESOLVE( packageURL, targetValue, patternMatch, isImports, conditions).
Expand All @@ -1476,7 +1480,7 @@ impl<Fs: FileSystem + Default> ResolverGeneric<Fs> {
return Ok(None);
}
// 3. Otherwise, if target is an Array, then
ExportsField::Array(targets) => {
ImportExportField::Array(targets) => {
// 1. If _target.length is zero, return null.
if targets.is_empty() {
// Note: return PackagePathNotExported has the same effect as return because there are no matches.
Expand Down
5 changes: 5 additions & 0 deletions src/options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,10 @@ pub struct ResolveOptions {
///
/// Default `false`
pub builtin_modules: bool,

/// When enabled, will attempt to reduce memory usage by clearing data and fields
/// that are not relevant to the current process, and should not be held in memory.
pub remove_unused_fields: bool,
}

impl ResolveOptions {
Expand Down Expand Up @@ -373,6 +377,7 @@ impl Default for ResolveOptions {
roots: vec![],
symlinks: true,
builtin_modules: false,
remove_unused_fields: true,
}
}
}
Expand Down