oxc/crates/oxc_resolver/src/package_json.rs

244 lines
8.5 KiB
Rust

//! package.json definitions
//!
//! Code related to export field are copied from [Parcel's resolver](https://github.com/parcel-bundler/parcel/blob/v2/packages/utils/node-resolver-rs/src/package_json.rs)
use std::{
hash::BuildHasherDefault,
path::{Path, PathBuf},
sync::Arc,
};
use indexmap::IndexMap;
use rustc_hash::FxHasher;
use serde::{Deserialize, Deserializer};
use crate::{path::PathUtil, ResolveError, ResolveOptions};
type FxIndexMap<K, V> = IndexMap<K, V, BuildHasherDefault<FxHasher>>;
#[derive(Debug, Deserialize)]
pub struct PackageJson {
/// Path to `package.json`. Contains the `package.json` filename.
#[serde(skip)]
pub path: PathBuf,
#[serde(skip)]
#[serde(default)]
pub raw_json: Arc<serde_json::Value>,
/// The "name" field defines your package's name.
/// The "name" field can be used in addition to the "exports" field to self-reference a package using its name.
///
/// <https://nodejs.org/api/packages.html#name>
pub name: Option<String>,
/// The "main" field defines the entry point of a package when imported by name via a node_modules lookup. Its value is a path.
/// When a package has an "exports" field, this will take precedence over the "main" field when importing the package by name.
///
/// Values are dynamically added from [ResolveOptions::main_fields].
///
/// <https://nodejs.org/api/packages.html#main>
#[serde(skip)]
pub main_fields: Vec<String>,
/// The "exports" field allows defining the entry points of a package when imported by name loaded either via a node_modules lookup or a self-reference to its own name.
///
/// <https://nodejs.org/api/packages.html#exports>
#[serde(skip)]
pub exports: Vec<ExportsField>,
/// In addition to the "exports" field, there is a package "imports" field to create private mappings that only apply to import specifiers from within the package itself.
///
/// <https://nodejs.org/api/packages.html#subpath-imports>
#[serde(default)]
pub imports: Box<MatchObject>,
/// The "browser" field is provided by a module author as a hint to javascript bundlers or component tools when packaging modules for client side use.
/// Multiple values are configured by [ResolveOptions::alias_fields].
///
/// <https://github.com/defunctzombie/package-browser-field-spec>
#[serde(skip)]
pub browser_fields: Vec<BrowserField>,
}
/// `matchObj` defined in `PACKAGE_IMPORTS_EXPORTS_RESOLVE`
/// This is an IndexMap provided by the `preserve_order` feature.
pub type MatchObject = FxIndexMap<ExportsKey, ExportsField>;
#[derive(Debug, Default, Deserialize)]
#[serde(untagged)]
pub enum ExportsField {
#[default]
None, // For `undefined` or `null` value.
String(String),
Array(Vec<ExportsField>),
Map(MatchObject),
}
#[derive(Debug, Eq, PartialEq, Hash)]
pub enum ExportsKey {
Main,
Pattern(String),
CustomCondition(String),
}
impl From<&str> for ExportsKey {
fn from(key: &str) -> Self {
if key == "." {
Self::Main
} else if key.starts_with("./") {
Self::Pattern(key.trim_start_matches('.').to_string())
} else if key.starts_with('#') {
Self::Pattern(key.to_string())
} else {
Self::CustomCondition(key.to_string())
}
}
}
impl<'de> Deserialize<'de> for ExportsKey {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let s: &'de str = Deserialize::deserialize(deserializer)?;
Ok(Self::from(s))
}
}
#[derive(Debug, Deserialize)]
#[serde(untagged)]
pub enum BrowserField {
String(String),
Map(FxIndexMap<PathBuf, serde_json::Value>),
}
impl PackageJson {
/// # Panics
/// # Errors
pub fn parse(
path: PathBuf,
json: &str,
options: &ResolveOptions,
) -> Result<Self, serde_json::Error> {
let mut package_json_value: serde_json::Value = serde_json::from_str(json)?;
let mut package_json: Self = Self::deserialize(&package_json_value)?;
package_json.main_fields.reserve_exact(options.main_fields.len());
package_json.browser_fields.reserve_exact(options.alias_fields.len());
package_json.exports.reserve_exact(options.exports_fields.len());
if let Some(package_json_value) = package_json_value.as_object_mut() {
// Dynamically create `main_fields`.
for main_field_key in &options.main_fields {
// Using `get` + `clone` instead of remove here
// because `main_fields` may contain `browser`, which is also used in `browser_fields.
if let Some(serde_json::Value::String(value)) =
package_json_value.get(main_field_key)
{
package_json.main_fields.push(value.clone());
}
}
}
// Dynamically create `browser_fields`.
let dir = path.parent().unwrap();
for object_path in &options.alias_fields {
if let Some(browser_field) = Self::get_value_by_path(&package_json_value, object_path) {
let mut browser_field = BrowserField::deserialize(browser_field)?;
// Normalize all relative paths to make browser_field a constant value lookup
if let BrowserField::Map(map) = &mut browser_field {
let relative_paths = map
.keys()
.filter(|path| path.starts_with("."))
.cloned()
.collect::<Vec<_>>();
for relative_path in relative_paths {
if let Some(value) = map.remove(&relative_path) {
let normalized_path = dir.normalize_with(relative_path);
map.insert(normalized_path, value);
}
}
}
package_json.browser_fields.push(browser_field);
}
}
// Dynamically create `exports`.
for object_path in &options.exports_fields {
if let Some(exports) = Self::get_value_by_path(&package_json_value, object_path) {
let exports = ExportsField::deserialize(exports)?;
package_json.exports.push(exports);
}
}
package_json.path = path;
package_json.raw_json = Arc::new(package_json_value);
Ok(package_json)
}
fn get_value_by_path<'a>(
package_json: &'a serde_json::Value,
path: &[String],
) -> Option<&'a serde_json::Value> {
if path.is_empty() {
return None;
}
let mut value = package_json;
for key in path {
if let Some(inner_value) = value.as_object().and_then(|o| o.get(key)) {
value = inner_value;
} else {
return None;
}
}
Some(value)
}
/// Directory to `package.json`
pub fn raw_json(&self) -> &serde_json::Value {
self.raw_json.as_ref()
}
/// Directory to `package.json`
///
/// # Panics
///
/// * When the package.json path is misconfigured.
pub fn directory(&self) -> &Path {
debug_assert!(self.path.file_name().is_some_and(|x| x == "package.json"));
self.path.parent().unwrap()
}
/// Resolve the request string for this package.json by looking at the `browser` field.
///
/// # Errors
///
/// * Returns [ResolveError::Ignored] for `"path": false` in `browser` field.
pub fn resolve_browser_field(
&self,
path: &Path,
request: Option<&str>,
) -> Result<Option<&str>, ResolveError> {
let request = request.map_or(path, |r| Path::new(r));
for browser in &self.browser_fields {
// Only object is valid, all other types are invalid
// https://github.com/webpack/enhanced-resolve/blob/3a28f47788de794d9da4d1702a3a583d8422cd48/lib/AliasFieldPlugin.js#L44-L52
if let BrowserField::Map(field_data) = browser {
if let Some(value) = field_data.get(request) {
return Self::alias_value(path, value);
}
}
}
Ok(None)
}
fn alias_value<'a>(
key: &Path,
value: &'a serde_json::Value,
) -> Result<Option<&'a str>, ResolveError> {
match value {
serde_json::Value::String(value) => Ok(Some(value.as_str())),
serde_json::Value::Bool(b) if !b => Err(ResolveError::Ignored(key.to_path_buf())),
_ => Ok(None),
}
}
}