oxc_resolver/
package_json.rs

1//! package.json definitions
2//!
3//! 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)
4use std::{
5    fmt,
6    path::{Path, PathBuf},
7};
8
9use simd_json::{BorrowedValue, prelude::*};
10
11use crate::{ResolveError, path::PathUtil};
12
13// Use simd_json's Object type which handles the hasher correctly based on features
14type BorrowedObject<'a> = simd_json::value::borrowed::Object<'a>;
15
16#[derive(Clone, Copy, Debug, Eq, PartialEq)]
17pub enum PackageType {
18    CommonJs,
19    Module,
20}
21
22impl PackageType {
23    fn from_str(s: &str) -> Option<Self> {
24        match s {
25            "commonjs" => Some(Self::CommonJs),
26            "module" => Some(Self::Module),
27            _ => None,
28        }
29    }
30}
31
32impl fmt::Display for PackageType {
33    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
34        match self {
35            Self::CommonJs => f.write_str("commonjs"),
36            Self::Module => f.write_str("module"),
37        }
38    }
39}
40
41#[derive(Clone, Copy, Debug, Eq, PartialEq)]
42pub enum ImportsExportsKind {
43    String,
44    Array,
45    Map,
46    Invalid,
47}
48
49#[derive(Clone, Debug, Eq, PartialEq)]
50pub enum SideEffects<'a> {
51    Bool(bool),
52    String(&'a str),
53    Array(Vec<&'a str>),
54}
55
56self_cell::self_cell! {
57    struct PackageJsonCell {
58        owner: Vec<u8>,
59
60        #[covariant]
61        dependent: BorrowedValue,
62    }
63}
64
65/// Serde implementation for the deserialized `package.json`.
66///
67/// This implementation is used by the [crate::Cache] and enabled through the
68/// `fs_cache` feature.
69pub struct PackageJson {
70    /// Path to `package.json`. Contains the `package.json` filename.
71    pub path: PathBuf,
72
73    /// Realpath to `package.json`. Contains the `package.json` filename.
74    pub realpath: PathBuf,
75
76    cell: PackageJsonCell,
77}
78
79impl fmt::Debug for PackageJson {
80    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
81        f.debug_struct("PackageJson")
82            .field("path", &self.path)
83            .field("realpath", &self.realpath)
84            .field("name", &self.name())
85            .field("type", &self.r#type())
86            .finish_non_exhaustive()
87    }
88}
89
90impl PackageJson {
91    /// Returns the path where the `package.json` was found.
92    ///
93    /// Contains the `package.json` filename.
94    ///
95    /// This does not need to be the path where the file is stored on disk.
96    /// See [Self::realpath()].
97    #[must_use]
98    pub fn path(&self) -> &Path {
99        &self.path
100    }
101
102    /// Returns the path where the `package.json` file was stored on disk.
103    ///
104    /// Contains the `package.json` filename.
105    ///
106    /// This is the canonicalized version of [Self::path()], where all symbolic
107    /// links are resolved.
108    #[must_use]
109    pub fn realpath(&self) -> &Path {
110        &self.realpath
111    }
112
113    /// Directory to `package.json`.
114    ///
115    /// # Panics
116    ///
117    /// * When the `package.json` path is misconfigured.
118    #[must_use]
119    pub fn directory(&self) -> &Path {
120        debug_assert!(self.realpath.file_name().is_some_and(|x| x == "package.json"));
121        self.realpath.parent().unwrap()
122    }
123
124    /// Name of the package.
125    ///
126    /// The "name" field can be used together with the "exports" field to
127    /// self-reference a package using its name.
128    ///
129    /// <https://nodejs.org/api/packages.html#name>
130    #[must_use]
131    pub fn name(&self) -> Option<&str> {
132        self.cell
133            .borrow_dependent()
134            .as_object()
135            .and_then(|obj| obj.get("name"))
136            .and_then(|v| v.as_str())
137    }
138
139    /// Version of the package.
140    ///
141    /// <https://nodejs.org/api/packages.html#name>
142    #[must_use]
143    pub fn version(&self) -> Option<&str> {
144        self.cell
145            .borrow_dependent()
146            .as_object()
147            .and_then(|obj| obj.get("version"))
148            .and_then(|v| v.as_str())
149    }
150
151    /// Returns the package type, if one is configured in the `package.json`.
152    ///
153    /// <https://nodejs.org/api/packages.html#type>
154    #[must_use]
155    pub fn r#type(&self) -> Option<PackageType> {
156        self.cell
157            .borrow_dependent()
158            .as_object()
159            .and_then(|obj| obj.get("type"))
160            .and_then(|v| v.as_str())
161            .and_then(PackageType::from_str)
162    }
163
164    /// The "sideEffects" field.
165    ///
166    /// <https://webpack.js.org/guides/tree-shaking>
167    #[must_use]
168    pub fn side_effects(&self) -> Option<SideEffects<'_>> {
169        self.cell.borrow_dependent().as_object().and_then(|obj| obj.get("sideEffects")).and_then(
170            |value| match value {
171                BorrowedValue::Static(simd_json::StaticNode::Bool(b)) => {
172                    Some(SideEffects::Bool(*b))
173                }
174                BorrowedValue::String(s) => Some(SideEffects::String(s.as_ref())),
175                BorrowedValue::Array(arr) => {
176                    let strings: Vec<&str> = arr.iter().filter_map(|v| v.as_str()).collect();
177                    Some(SideEffects::Array(strings))
178                }
179                _ => None,
180            },
181        )
182    }
183
184    /// The "exports" field allows defining the entry points of a package.
185    ///
186    /// <https://nodejs.org/api/packages.html#exports>
187    #[must_use]
188    pub fn exports(&self) -> Option<ImportsExportsEntry<'_>> {
189        self.cell
190            .borrow_dependent()
191            .as_object()
192            .and_then(|obj| obj.get("exports"))
193            .map(ImportsExportsEntry)
194    }
195
196    /// The "main" field defines the entry point of a package when imported by
197    /// name via a node_modules lookup. Its value should be a path.
198    ///
199    /// When a package has an "exports" field, this will take precedence over
200    /// the "main" field when importing the package by name.
201    ///
202    /// Values are dynamically retrieved from [crate::ResolveOptions::main_fields].
203    ///
204    /// <https://nodejs.org/api/packages.html#main>
205    pub(crate) fn main_fields<'a>(
206        &'a self,
207        main_fields: &'a [String],
208    ) -> impl Iterator<Item = &'a str> + 'a {
209        let json_value = self.cell.borrow_dependent();
210        let json_object = json_value.as_object();
211
212        main_fields
213            .iter()
214            .filter_map(move |main_field| json_object.and_then(|obj| obj.get(main_field.as_str())))
215            .filter_map(|v| v.as_str())
216    }
217
218    /// The "exports" field allows defining the entry points of a package when
219    /// imported by name loaded either via a node_modules lookup or a
220    /// self-reference to its own name.
221    ///
222    /// <https://nodejs.org/api/packages.html#exports>
223    pub(crate) fn exports_fields<'a>(
224        &'a self,
225        exports_fields: &'a [Vec<String>],
226    ) -> impl Iterator<Item = ImportsExportsEntry<'a>> + 'a {
227        let json_value = self.cell.borrow_dependent();
228
229        exports_fields
230            .iter()
231            .filter_map(move |object_path| {
232                json_value
233                    .as_object()
234                    .and_then(|json_object| Self::get_value_by_path(json_object, object_path))
235            })
236            .map(ImportsExportsEntry)
237    }
238
239    /// In addition to the "exports" field, there is a package "imports" field
240    /// to create private mappings that only apply to import specifiers from
241    /// within the package itself.
242    ///
243    /// <https://nodejs.org/api/packages.html#subpath-imports>
244    pub(crate) fn imports_fields<'a>(
245        &'a self,
246        imports_fields: &'a [Vec<String>],
247    ) -> impl Iterator<Item = ImportsExportsMap<'a>> + 'a {
248        let json_value = self.cell.borrow_dependent();
249
250        imports_fields
251            .iter()
252            .filter_map(move |object_path| {
253                json_value
254                    .as_object()
255                    .and_then(|json_object| Self::get_value_by_path(json_object, object_path))
256                    .and_then(|v| v.as_object())
257            })
258            .map(ImportsExportsMap)
259    }
260
261    /// Resolves the request string for this `package.json` by looking at the
262    /// "browser" field.
263    ///
264    /// <https://github.com/defunctzombie/package-browser-field-spec>
265    pub(crate) fn resolve_browser_field<'a>(
266        &'a self,
267        path: &Path,
268        request: Option<&str>,
269        alias_fields: &'a [Vec<String>],
270    ) -> Result<Option<&'a str>, ResolveError> {
271        for object in self.browser_fields(alias_fields) {
272            if let Some(request) = request {
273                // Find matching key in object
274                if let Some(value) = object.get(request) {
275                    return Self::alias_value(path, value);
276                }
277            } else {
278                let dir = self.path.parent().unwrap();
279                for (key, value) in object {
280                    let joined = dir.normalize_with(key.as_ref());
281                    if joined == path {
282                        return Self::alias_value(path, value);
283                    }
284                }
285            }
286        }
287        Ok(None)
288    }
289
290    /// Parse a package.json file from JSON string
291    ///
292    /// # Panics
293    /// # Errors
294    pub fn parse(path: PathBuf, realpath: PathBuf, json: String) -> Result<Self, simd_json::Error> {
295        // Strip BOM in place by replacing with spaces (no reallocation)
296        let mut json_bytes = json.into_bytes();
297        if json_bytes.starts_with(b"\xEF\xBB\xBF") {
298            json_bytes[0] = b' ';
299            json_bytes[1] = b' ';
300            json_bytes[2] = b' ';
301        }
302
303        // Create the self-cell with the JSON bytes and parsed BorrowedValue
304        let cell = PackageJsonCell::try_new(json_bytes.clone(), |bytes| {
305            // We need a mutable slice from our owned data
306            // SAFETY: We're creating a mutable reference to the owned data.
307            // The self_cell ensures this reference is valid for the lifetime of the cell.
308            let slice =
309                unsafe { std::slice::from_raw_parts_mut(bytes.as_ptr().cast_mut(), bytes.len()) };
310            simd_json::to_borrowed_value(slice)
311        })?;
312
313        Ok(Self { path, realpath, cell })
314    }
315
316    fn get_value_by_path<'a>(
317        fields: &'a BorrowedObject<'a>,
318        path: &[String],
319    ) -> Option<&'a BorrowedValue<'a>> {
320        if path.is_empty() {
321            return None;
322        }
323        let mut value = fields.get(path[0].as_str())?;
324
325        for key in path.iter().skip(1) {
326            if let Some(obj) = value.as_object() {
327                value = obj.get(key.as_str())?;
328            } else {
329                return None;
330            }
331        }
332        Some(value)
333    }
334
335    /// 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.
336    /// Multiple values are configured by [ResolveOptions::alias_fields].
337    ///
338    /// <https://github.com/defunctzombie/package-browser-field-spec>
339    pub(crate) fn browser_fields<'a>(
340        &'a self,
341        alias_fields: &'a [Vec<String>],
342    ) -> impl Iterator<Item = &'a BorrowedObject<'a>> + 'a {
343        let json_value = self.cell.borrow_dependent();
344
345        alias_fields.iter().filter_map(move |object_path| {
346            json_value
347                .as_object()
348                .and_then(|json_object| Self::get_value_by_path(json_object, object_path))
349                // Only object is valid, all other types are invalid
350                // https://github.com/webpack/enhanced-resolve/blob/3a28f47788de794d9da4d1702a3a583d8422cd48/lib/AliasFieldPlugin.js#L44-L52
351                .and_then(|value| value.as_object())
352        })
353    }
354
355    pub(crate) fn alias_value<'a>(
356        key: &Path,
357        value: &'a BorrowedValue<'a>,
358    ) -> Result<Option<&'a str>, ResolveError> {
359        match value {
360            BorrowedValue::String(s) => Ok(Some(s.as_ref())),
361            BorrowedValue::Static(simd_json::StaticNode::Bool(false)) => {
362                Err(ResolveError::Ignored(key.to_path_buf()))
363            }
364            _ => Ok(None),
365        }
366    }
367}
368
369#[derive(Clone)]
370pub struct ImportsExportsEntry<'a>(pub(crate) &'a BorrowedValue<'a>);
371
372impl<'a> ImportsExportsEntry<'a> {
373    #[must_use]
374    pub fn kind(&self) -> ImportsExportsKind {
375        match self.0 {
376            BorrowedValue::String(_) => ImportsExportsKind::String,
377            BorrowedValue::Array(_) => ImportsExportsKind::Array,
378            BorrowedValue::Object(_) => ImportsExportsKind::Map,
379            BorrowedValue::Static(_) => ImportsExportsKind::Invalid,
380        }
381    }
382
383    #[must_use]
384    pub fn as_string(&self) -> Option<&'a str> {
385        match self.0 {
386            BorrowedValue::String(s) => Some(s.as_ref()),
387            _ => None,
388        }
389    }
390
391    #[must_use]
392    pub fn as_array(&self) -> Option<ImportsExportsArray<'a>> {
393        match self.0 {
394            BorrowedValue::Array(arr) => Some(ImportsExportsArray(arr)),
395            _ => None,
396        }
397    }
398
399    #[must_use]
400    pub fn as_map(&self) -> Option<ImportsExportsMap<'a>> {
401        match self.0 {
402            BorrowedValue::Object(obj) => Some(ImportsExportsMap(obj)),
403            _ => None,
404        }
405    }
406}
407
408#[derive(Clone)]
409pub struct ImportsExportsArray<'a>(&'a [BorrowedValue<'a>]);
410
411impl<'a> ImportsExportsArray<'a> {
412    #[must_use]
413    pub fn is_empty(&self) -> bool {
414        self.len() == 0
415    }
416
417    #[must_use]
418    pub fn len(&self) -> usize {
419        self.0.len()
420    }
421
422    pub fn iter(&self) -> impl Iterator<Item = ImportsExportsEntry<'a>> {
423        ImportsExportsArrayIter { slice: self.0, index: 0 }
424    }
425}
426
427struct ImportsExportsArrayIter<'a> {
428    slice: &'a [BorrowedValue<'a>],
429    index: usize,
430}
431
432impl<'a> Iterator for ImportsExportsArrayIter<'a> {
433    type Item = ImportsExportsEntry<'a>;
434
435    fn next(&mut self) -> Option<Self::Item> {
436        self.slice.get(self.index).map(|value| {
437            self.index += 1;
438            ImportsExportsEntry(value)
439        })
440    }
441}
442
443#[derive(Clone)]
444pub struct ImportsExportsMap<'a>(pub(crate) &'a BorrowedObject<'a>);
445
446impl<'a> ImportsExportsMap<'a> {
447    pub fn get(&self, key: &str) -> Option<ImportsExportsEntry<'a>> {
448        self.0.get(key).map(ImportsExportsEntry)
449    }
450
451    pub fn keys(&self) -> impl Iterator<Item = &'a str> {
452        self.0.keys().map(std::convert::AsRef::as_ref)
453    }
454
455    pub fn iter(&self) -> impl Iterator<Item = (&'a str, ImportsExportsEntry<'a>)> {
456        self.0.iter().map(|(k, v)| (k.as_ref(), ImportsExportsEntry(v)))
457    }
458}