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

chore(napi): align *Fields user options with enhanced-resolve #35

Merged
merged 1 commit into from
Dec 18, 2023
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions napi/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export interface NapiResolveOptions {
*
* Default `[]`
*/
aliasFields?: Array<Array<string>>
aliasFields?: (string | string[])[]
/**
* Condition names for exports field which defines entry points of a package.
* The key order in the exports field is significant. During condition matching, earlier entries have higher priority and take precedence over later entries.
Expand Down Expand Up @@ -65,7 +65,7 @@ export interface NapiResolveOptions {
*
* Default `[["exports"]]`.
*/
exportsFields?: Array<Array<string>>
exportsFields?: (string | string[])[]
/**
* An object which maps extension to extension aliases.
*
Expand Down Expand Up @@ -99,7 +99,7 @@ export interface NapiResolveOptions {
*
* Default `["main"]`.
*/
mainFields?: Array<string>
mainFields?: string | string[]
/**
* The filename to be used while resolving directories.
*
Expand All @@ -111,7 +111,7 @@ export interface NapiResolveOptions {
*
* Default `["node_modules"]`
*/
modules?: Array<string>
modules?: string | string[]
/**
* Resolve to a context instead of a file.
*
Expand Down
2 changes: 1 addition & 1 deletion napi/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"scripts": {
"build": "napi build --platform --release",
"build:debug": "napi build --platform ",
"test": "node test.mjs"
"test": "node --test"
},
"devDependencies": {
"@napi-rs/cli": "^2.15.2"
Expand Down
19 changes: 14 additions & 5 deletions napi/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use std::path::{Path, PathBuf};
use napi_derive::napi;
use oxc_resolver::{ResolveOptions, Resolver};

use self::options::NapiResolveOptions;
use self::options::{NapiResolveOptions, StrOrStrList};

mod options;

Expand Down Expand Up @@ -48,14 +48,20 @@ impl ResolverFactory {
.collect::<Vec<_>>()
})
.unwrap_or(default_options.alias),
alias_fields: op.alias_fields.unwrap_or(default_options.alias_fields),
alias_fields: op
.alias_fields
.map(|o| o.into_iter().map(|x| StrOrStrList(x).into()).collect::<Vec<_>>())
.unwrap_or(default_options.alias_fields),
condition_names: op.condition_names.unwrap_or(default_options.condition_names),
description_files: op.description_files.unwrap_or(default_options.description_files),
enforce_extension: op
.enforce_extension
.map(|enforce_extension| enforce_extension.into())
.unwrap_or(default_options.enforce_extension),
exports_fields: op.exports_fields.unwrap_or(default_options.exports_fields),
exports_fields: op
.exports_fields
.map(|o| o.into_iter().map(|x| StrOrStrList(x).into()).collect::<Vec<_>>())
.unwrap_or(default_options.exports_fields),
extension_alias: op
.extension_alias
.map(|extension_alias| extension_alias.into_iter().collect::<Vec<_>>())
Expand All @@ -80,9 +86,12 @@ impl ResolverFactory {
})
.unwrap_or(default_options.fallback),
fully_specified: op.fully_specified.unwrap_or(default_options.fully_specified),
main_fields: op.main_fields.unwrap_or(default_options.main_fields),
main_fields: op
.main_fields
.map(|o| StrOrStrList(o).into())
.unwrap_or(default_options.main_fields),
main_files: op.main_files.unwrap_or(default_options.main_files),
modules: op.modules.unwrap_or(default_options.modules),
modules: op.modules.map(|o| StrOrStrList(o).into()).unwrap_or(default_options.modules),
resolve_to_context: op.resolve_to_context.unwrap_or(default_options.resolve_to_context),
prefer_relative: op.prefer_relative.unwrap_or(default_options.prefer_relative),
prefer_absolute: op.prefer_absolute.unwrap_or(default_options.prefer_absolute),
Expand Down
27 changes: 23 additions & 4 deletions napi/src/options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,16 @@ pub struct NapiResolveOptions {
/// Create aliases to import or require certain modules more easily.
/// A trailing $ can also be added to the given object's keys to signify an exact match.
///
// FIXME can be array
pub alias: Option<HashMap<String, Vec<Option<String>>>>,

/// A list of alias fields in description files.
/// Specify a field, such as `browser`, to be parsed according to [this specification](https://github.com/defunctzombie/package-browser-field-spec).
/// Can be a path to json object such as `["path", "to", "exports"]`.
///
/// Default `[]`
pub alias_fields: Option<Vec<Vec<String>>>,
#[napi(ts_type = "(string | string[])[]")]
pub alias_fields: Option<Vec<StrOrStrListType>>,

/// Condition names for exports field which defines entry points of a package.
/// The key order in the exports field is significant. During condition matching, earlier entries have higher priority and take precedence over later entries.
Expand Down Expand Up @@ -58,7 +60,8 @@ pub struct NapiResolveOptions {
/// Can be a path to json object such as `["path", "to", "exports"]`.
///
/// Default `[["exports"]]`.
pub exports_fields: Option<Vec<Vec<String>>>,
#[napi(ts_type = "(string | string[])[]")]
pub exports_fields: Option<Vec<StrOrStrListType>>,

/// An object which maps extension to extension aliases.
///
Expand All @@ -75,6 +78,7 @@ pub struct NapiResolveOptions {
/// Redirect module requests when normal resolving fails.
///
/// Default `[]`
// FIXME can be array - same as alias
pub fallback: Option<HashMap<String, Vec<Option<String>>>>,

/// Request passed to resolve is already fully specified and extensions or main files are not resolved for it (they are still resolved for internal requests).
Expand All @@ -87,7 +91,9 @@ pub struct NapiResolveOptions {
/// A list of main fields in description files
///
/// Default `["main"]`.
pub main_fields: Option<Vec<String>>,
// FIXME (string | string[] | { name: string | string[]; forceRelative: boolean })[]
#[napi(ts_type = "string | string[]")]
pub main_fields: Option<StrOrStrListType>,

/// The filename to be used while resolving directories.
///
Expand All @@ -97,7 +103,8 @@ pub struct NapiResolveOptions {
/// A list of directories to resolve modules from, can be absolute path or folder name.
///
/// Default `["node_modules"]`
pub modules: Option<Vec<String>>,
#[napi(ts_type = "string | string[]")]
pub modules: Option<StrOrStrListType>,

/// Resolve to a context instead of a file.
///
Expand Down Expand Up @@ -234,3 +241,15 @@ impl Into<oxc_resolver::TsconfigOptions> for TsconfigOptions {
}
}
}

type StrOrStrListType = Either<String, Vec<String>>;
pub struct StrOrStrList(pub StrOrStrListType);

impl Into<Vec<String>> for StrOrStrList {
fn into(self) -> Vec<String> {
match self {
StrOrStrList(Either::A(s)) => Vec::from([s]),
StrOrStrList(Either::B(a)) => a,
}
}
}
59 changes: 59 additions & 0 deletions napi/tests/options.test.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { describe, it } from "node:test";
import { ResolverFactory } from "../index.js";
import * as assert from "node:assert";
import * as path from "node:path";

const fixtureDir = new URL(
"../../fixtures/enhanced_resolve/test/fixtures",
import.meta.url
).pathname;

describe("option", () => {
describe("aliasFields", () => {
it("should allow field string ", () => {
const resolver = new ResolverFactory({ aliasFields: ["browser"] });
assert.match(
resolver.sync(fixtureDir, "./browser-module/lib/replaced.js").path,
/browser-module\/lib\/browser\.js$/
);
});
it("should allow json path array", () => {
const resolver = new ResolverFactory({
aliasFields: [["innerBrowser1", "field", "browser"]],
});

assert.match(
resolver.sync(fixtureDir, "./browser-module/lib/main1.js").path,
/browser-module\/lib\/main\.js$/
);
});
});

describe("exportsFields", () => {
const createTest = (exportsFields) => {
const resolver = new ResolverFactory({ exportsFields });
assert.match(
resolver.sync(
path.resolve(fixtureDir, "./exports-field3"),
"exports-field"
).path,
/\/exports-field\/src\/index\.js$/
);
};
it("should allow string as field item", createTest(["broken"]));
it("should allow json path array as field item", createTest([["broken"]]));
});

describe("mainFields", () => {
const createTest = (mainFields) => {
const resolver = new ResolverFactory({ mainFields });
assert.match(
resolver.sync(fixtureDir, "../..").path,
/\/lib\/index\.js$/
);
};
it("should use `'main'` as default", createTest(undefined));
it("should allow field string", createTest("main"));
it("should allow field array", createTest(["main"]));
});
});