From 4539ede450fa1b914b6a4c78738782e4427e1b58 Mon Sep 17 00:00:00 2001 From: xc2 Date: Sun, 17 Dec 2023 23:09:18 +0800 Subject: [PATCH] chore(napi): align `*Fields` in UserResolveOptions with enhanced-resolve --- napi/index.d.ts | 8 ++--- napi/package.json | 2 +- napi/src/lib.rs | 19 ++++++++---- napi/src/options.rs | 27 ++++++++++++++--- napi/tests/options.test.mjs | 59 +++++++++++++++++++++++++++++++++++++ 5 files changed, 101 insertions(+), 14 deletions(-) create mode 100644 napi/tests/options.test.mjs diff --git a/napi/index.d.ts b/napi/index.d.ts index 497e588a..60080686 100644 --- a/napi/index.d.ts +++ b/napi/index.d.ts @@ -33,7 +33,7 @@ export interface NapiResolveOptions { * * Default `[]` */ - aliasFields?: Array> + 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. @@ -65,7 +65,7 @@ export interface NapiResolveOptions { * * Default `[["exports"]]`. */ - exportsFields?: Array> + exportsFields?: (string | string[])[] /** * An object which maps extension to extension aliases. * @@ -99,7 +99,7 @@ export interface NapiResolveOptions { * * Default `["main"]`. */ - mainFields?: Array + mainFields?: string | string[] /** * The filename to be used while resolving directories. * @@ -111,7 +111,7 @@ export interface NapiResolveOptions { * * Default `["node_modules"]` */ - modules?: Array + modules?: string | string[] /** * Resolve to a context instead of a file. * diff --git a/napi/package.json b/napi/package.json index 2633b765..5cc881a3 100644 --- a/napi/package.json +++ b/napi/package.json @@ -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" diff --git a/napi/src/lib.rs b/napi/src/lib.rs index e3354183..ee472d17 100644 --- a/napi/src/lib.rs +++ b/napi/src/lib.rs @@ -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; @@ -48,14 +48,20 @@ impl ResolverFactory { .collect::>() }) .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::>()) + .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::>()) + .unwrap_or(default_options.exports_fields), extension_alias: op .extension_alias .map(|extension_alias| extension_alias.into_iter().collect::>()) @@ -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), diff --git a/napi/src/options.rs b/napi/src/options.rs index 49641083..65a09ca8 100644 --- a/napi/src/options.rs +++ b/napi/src/options.rs @@ -23,6 +23,7 @@ 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>>>, /// A list of alias fields in description files. @@ -30,7 +31,8 @@ pub struct NapiResolveOptions { /// Can be a path to json object such as `["path", "to", "exports"]`. /// /// Default `[]` - pub alias_fields: Option>>, + #[napi(ts_type = "(string | string[])[]")] + pub alias_fields: Option>, /// 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. @@ -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>>, + #[napi(ts_type = "(string | string[])[]")] + pub exports_fields: Option>, /// An object which maps extension to extension aliases. /// @@ -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>>>, /// 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). @@ -87,7 +91,9 @@ pub struct NapiResolveOptions { /// A list of main fields in description files /// /// Default `["main"]`. - pub main_fields: Option>, + // FIXME (string | string[] | { name: string | string[]; forceRelative: boolean })[] + #[napi(ts_type = "string | string[]")] + pub main_fields: Option, /// The filename to be used while resolving directories. /// @@ -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>, + #[napi(ts_type = "string | string[]")] + pub modules: Option, /// Resolve to a context instead of a file. /// @@ -234,3 +241,15 @@ impl Into for TsconfigOptions { } } } + +type StrOrStrListType = Either>; +pub struct StrOrStrList(pub StrOrStrListType); + +impl Into> for StrOrStrList { + fn into(self) -> Vec { + match self { + StrOrStrList(Either::A(s)) => Vec::from([s]), + StrOrStrList(Either::B(a)) => a, + } + } +} diff --git a/napi/tests/options.test.mjs b/napi/tests/options.test.mjs new file mode 100644 index 00000000..4a93efff --- /dev/null +++ b/napi/tests/options.test.mjs @@ -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"])); + }); +});