diff --git a/packages/core/core/src/Dependency.js b/packages/core/core/src/Dependency.js index 85f6fad0778..35dd2bc591d 100644 --- a/packages/core/core/src/Dependency.js +++ b/packages/core/core/src/Dependency.js @@ -10,7 +10,12 @@ import type { } from '@parcel/types'; import type {Dependency, Environment, Target} from './types'; import {hashString} from '@parcel/hash'; -import {SpecifierType, Priority, BundleBehavior} from './types'; +import { + SpecifierType, + Priority, + BundleBehavior, + ExportsCondition, +} from './types'; import {toInternalSourceLocation} from './utils'; import {toProjectPath} from './projectPath'; @@ -28,6 +33,7 @@ type DependencyOpts = {| isOptional?: boolean, loc?: SourceLocation, env: Environment, + packageConditions?: Array, meta?: Meta, resolveFrom?: FilePath, range?: SemverRange, @@ -53,15 +59,13 @@ export function createDependency( (opts.pipeline ?? '') + opts.specifierType + (opts.bundleBehavior ?? '') + - (opts.priority ?? 'sync'), + (opts.priority ?? 'sync') + + (opts.packageConditions ? JSON.stringify(opts.packageConditions) : ''), ); - return { - ...opts, - resolveFrom: toProjectPath(projectRoot, opts.resolveFrom), - sourcePath: toProjectPath(projectRoot, opts.sourcePath), + let dep: Dependency = { id, - loc: toInternalSourceLocation(projectRoot, opts.loc), + specifier: opts.specifier, specifierType: SpecifierType[opts.specifierType], priority: Priority[opts.priority ?? 'sync'], needsStableName: opts.needsStableName ?? false, @@ -70,7 +74,13 @@ export function createDependency( : null, isEntry: opts.isEntry ?? false, isOptional: opts.isOptional ?? false, + loc: toInternalSourceLocation(projectRoot, opts.loc), + env: opts.env, meta: opts.meta || {}, + target: opts.target, + sourceAssetId: opts.sourceAssetId, + sourcePath: toProjectPath(projectRoot, opts.sourcePath), + resolveFrom: toProjectPath(projectRoot, opts.resolveFrom), range: opts.range, symbols: opts.symbols && @@ -85,7 +95,14 @@ export function createDependency( }, ]), ), + pipeline: opts.pipeline, }; + + if (opts.packageConditions) { + convertConditions(opts.packageConditions, dep); + } + + return dep; } export function mergeDependencies(a: Dependency, b: Dependency): void { @@ -101,3 +118,25 @@ export function mergeDependencies(a: Dependency, b: Dependency): void { if (isEntry) a.isEntry = true; if (!isOptional) a.isOptional = false; } + +function convertConditions(conditions: Array, dep: Dependency) { + // Store common package conditions as bit flags to reduce size. + // Custom conditions are stored as strings. + let packageConditions = 0; + let customConditions = []; + for (let condition of conditions) { + if (ExportsCondition[condition]) { + packageConditions |= ExportsCondition[condition]; + } else { + customConditions.push(condition); + } + } + + if (packageConditions) { + dep.packageConditions = packageConditions; + } + + if (customConditions.length) { + dep.customPackageConditions = customConditions; + } +} diff --git a/packages/core/core/src/Transformation.js b/packages/core/core/src/Transformation.js index 4880b509e65..2ceebe2bd56 100644 --- a/packages/core/core/src/Transformation.js +++ b/packages/core/core/src/Transformation.js @@ -6,6 +6,7 @@ import type { Transformer, TransformerResult, PackageName, + ResolveOptions, SemverRange, } from '@parcel/types'; import type {WorkerApi} from '@parcel/workers'; @@ -734,12 +735,17 @@ export default class Transformation { ): Promise<$ReadOnlyArray> { const logger = new PluginLogger({origin: transformerName}); - const resolve = async (from: FilePath, to: string): Promise => { + const resolve = async ( + from: FilePath, + to: string, + options?: ResolveOptions, + ): Promise => { let result = await pipeline.resolverRunner.resolve( createDependency(this.options.projectRoot, { env: asset.value.env, specifier: to, - specifierType: 'esm', // ??? + specifierType: options?.specifierType || 'esm', + packageConditions: options?.packageConditions, sourcePath: from, }), ); diff --git a/packages/core/core/src/public/Dependency.js b/packages/core/core/src/public/Dependency.js index bf22a85f5a2..d145768f5fb 100644 --- a/packages/core/core/src/public/Dependency.js +++ b/packages/core/core/src/public/Dependency.js @@ -17,7 +17,11 @@ import nullthrows from 'nullthrows'; import Environment from './Environment'; import Target from './Target'; import {MutableDependencySymbols} from './Symbols'; -import {SpecifierType as SpecifierTypeMap, Priority} from '../types'; +import { + SpecifierType as SpecifierTypeMap, + Priority, + ExportsCondition, +} from '../types'; import {fromProjectPath} from '../projectPath'; import {fromInternalSourceLocation} from '../utils'; @@ -101,6 +105,23 @@ export default class Dependency implements IDependency { return new Environment(this.#dep.env, this.#options); } + get packageConditions(): ?Array { + // Merge custom conditions with conditions stored as bitflags. + // Order is not important because exports conditions are resolved + // in the order they are declared in the package.json. + let conditions = this.#dep.customPackageConditions; + if (this.#dep.packageConditions) { + conditions = conditions ? [...conditions] : []; + for (let key in ExportsCondition) { + if (this.#dep.packageConditions & ExportsCondition[key]) { + conditions.push(key); + } + } + } + + return conditions; + } + get meta(): Meta { return this.#dep.meta; } diff --git a/packages/core/core/src/types.js b/packages/core/core/src/types.js index 64b43c26c5b..ad4636dee47 100644 --- a/packages/core/core/src/types.js +++ b/packages/core/core/src/types.js @@ -113,6 +113,17 @@ export const Priority = { lazy: 2, }; +// Must match package_json.rs in node-resolver-rs. +export const ExportsCondition = { + import: 1 << 0, + require: 1 << 1, + module: 1 << 2, + style: 1 << 12, + sass: 1 << 13, + less: 1 << 14, + stylus: 1 << 15, +}; + export type Dependency = {| id: string, specifier: DependencySpecifier, @@ -124,6 +135,8 @@ export type Dependency = {| isOptional: boolean, loc: ?InternalSourceLocation, env: Environment, + packageConditions?: number, + customPackageConditions?: Array, meta: Meta, resolverMeta?: ?Meta, target: ?Target, diff --git a/packages/core/integration-tests/test/css.js b/packages/core/integration-tests/test/css.js index f266ff461bd..af5905a9ae2 100644 --- a/packages/core/integration-tests/test/css.js +++ b/packages/core/integration-tests/test/css.js @@ -459,6 +459,19 @@ describe('css', () => { ]); }); + it('should support the style package exports condition', async () => { + let b = await bundle( + path.join(__dirname, '/integration/css-exports/index.css'), + ); + + assertBundles(b, [ + { + name: 'index.css', + assets: ['index.css', 'foo.css'], + }, + ]); + }); + it('should support external CSS imports', async () => { let b = await bundle( path.join(__dirname, '/integration/css-external/a.css'), diff --git a/packages/core/integration-tests/test/integration/css-exports/index.css b/packages/core/integration-tests/test/integration/css-exports/index.css new file mode 100644 index 00000000000..ac7a5b06e8f --- /dev/null +++ b/packages/core/integration-tests/test/integration/css-exports/index.css @@ -0,0 +1 @@ +@import 'npm:foo'; diff --git a/packages/core/integration-tests/test/integration/css-exports/node_modules/foo/foo.css b/packages/core/integration-tests/test/integration/css-exports/node_modules/foo/foo.css new file mode 100644 index 00000000000..dc1b2983f0b --- /dev/null +++ b/packages/core/integration-tests/test/integration/css-exports/node_modules/foo/foo.css @@ -0,0 +1,3 @@ +.foo { + background-color: red; +} \ No newline at end of file diff --git a/packages/core/integration-tests/test/integration/css-exports/node_modules/foo/index.js b/packages/core/integration-tests/test/integration/css-exports/node_modules/foo/index.js new file mode 100644 index 00000000000..e69de29bb2d diff --git a/packages/core/integration-tests/test/integration/css-exports/node_modules/foo/package.json b/packages/core/integration-tests/test/integration/css-exports/node_modules/foo/package.json new file mode 100644 index 00000000000..c2c296507a0 --- /dev/null +++ b/packages/core/integration-tests/test/integration/css-exports/node_modules/foo/package.json @@ -0,0 +1,7 @@ +{ + "name": "foo", + "exports": { + "style": "./foo.css", + "default": "./foo.js" + } +} \ No newline at end of file diff --git a/packages/core/integration-tests/test/integration/less-exports/index.less b/packages/core/integration-tests/test/integration/less-exports/index.less new file mode 100644 index 00000000000..cc0c043e482 --- /dev/null +++ b/packages/core/integration-tests/test/integration/less-exports/index.less @@ -0,0 +1 @@ +@import "foo"; \ No newline at end of file diff --git a/packages/core/integration-tests/test/integration/less-exports/node_modules/foo/a.js b/packages/core/integration-tests/test/integration/less-exports/node_modules/foo/a.js new file mode 100644 index 00000000000..e69de29bb2d diff --git a/packages/core/integration-tests/test/integration/less-exports/node_modules/foo/a.less b/packages/core/integration-tests/test/integration/less-exports/node_modules/foo/a.less new file mode 100644 index 00000000000..b3a5ee845ea --- /dev/null +++ b/packages/core/integration-tests/test/integration/less-exports/node_modules/foo/a.less @@ -0,0 +1,3 @@ +.a { + color: red; +} \ No newline at end of file diff --git a/packages/core/integration-tests/test/integration/less-exports/node_modules/foo/package.json b/packages/core/integration-tests/test/integration/less-exports/node_modules/foo/package.json new file mode 100644 index 00000000000..7c4d10e676a --- /dev/null +++ b/packages/core/integration-tests/test/integration/less-exports/node_modules/foo/package.json @@ -0,0 +1,6 @@ +{ + "exports": { + "import": "./a.js", + "less": "./a.less" + } +} \ No newline at end of file diff --git a/packages/core/integration-tests/test/integration/sass-exports/index.sass b/packages/core/integration-tests/test/integration/sass-exports/index.sass new file mode 100644 index 00000000000..6bf81459690 --- /dev/null +++ b/packages/core/integration-tests/test/integration/sass-exports/index.sass @@ -0,0 +1 @@ +@import "library" \ No newline at end of file diff --git a/packages/core/integration-tests/test/integration/sass-exports/node_modules/library/lib.js b/packages/core/integration-tests/test/integration/sass-exports/node_modules/library/lib.js new file mode 100644 index 00000000000..e69de29bb2d diff --git a/packages/core/integration-tests/test/integration/sass-exports/node_modules/library/package.json b/packages/core/integration-tests/test/integration/sass-exports/node_modules/library/package.json new file mode 100644 index 00000000000..52fc0c40382 --- /dev/null +++ b/packages/core/integration-tests/test/integration/sass-exports/node_modules/library/package.json @@ -0,0 +1,7 @@ +{ + "private": true, + "exports": { + "sass": "./style.sass", + "default": "./lib.js" + } +} \ No newline at end of file diff --git a/packages/core/integration-tests/test/integration/sass-exports/node_modules/library/style.sass b/packages/core/integration-tests/test/integration/sass-exports/node_modules/library/style.sass new file mode 100644 index 00000000000..0cf7f09d6e6 --- /dev/null +++ b/packages/core/integration-tests/test/integration/sass-exports/node_modules/library/style.sass @@ -0,0 +1,2 @@ +.external + color: red \ No newline at end of file diff --git a/packages/core/integration-tests/test/integration/sass-exports/package.json b/packages/core/integration-tests/test/integration/sass-exports/package.json new file mode 100644 index 00000000000..83a4a452279 --- /dev/null +++ b/packages/core/integration-tests/test/integration/sass-exports/package.json @@ -0,0 +1,3 @@ +{ + "private": true +} \ No newline at end of file diff --git a/packages/core/integration-tests/test/integration/stylus-exports/index.styl b/packages/core/integration-tests/test/integration/stylus-exports/index.styl new file mode 100644 index 00000000000..db574d51035 --- /dev/null +++ b/packages/core/integration-tests/test/integration/stylus-exports/index.styl @@ -0,0 +1 @@ +@import "foo"; diff --git a/packages/core/integration-tests/test/integration/stylus-exports/node_modules/foo/a.js b/packages/core/integration-tests/test/integration/stylus-exports/node_modules/foo/a.js new file mode 100644 index 00000000000..e69de29bb2d diff --git a/packages/core/integration-tests/test/integration/stylus-exports/node_modules/foo/a.styl b/packages/core/integration-tests/test/integration/stylus-exports/node_modules/foo/a.styl new file mode 100644 index 00000000000..2f6263f57a9 --- /dev/null +++ b/packages/core/integration-tests/test/integration/stylus-exports/node_modules/foo/a.styl @@ -0,0 +1,3 @@ +.a + color: red + \ No newline at end of file diff --git a/packages/core/integration-tests/test/integration/stylus-exports/node_modules/foo/package.json b/packages/core/integration-tests/test/integration/stylus-exports/node_modules/foo/package.json new file mode 100644 index 00000000000..726446b1cd9 --- /dev/null +++ b/packages/core/integration-tests/test/integration/stylus-exports/node_modules/foo/package.json @@ -0,0 +1,6 @@ +{ + "exports": { + "stylus": "./a.styl", + "default": "./a.js" + } +} \ No newline at end of file diff --git a/packages/core/integration-tests/test/less.js b/packages/core/integration-tests/test/less.js index a75a20fa1ed..409e7f2cc56 100644 --- a/packages/core/integration-tests/test/less.js +++ b/packages/core/integration-tests/test/less.js @@ -285,4 +285,11 @@ describe('less', function () { ), ); }); + + it('should support the less package exports condition', async function () { + await bundle(path.join(__dirname, '/integration/less-exports/index.less')); + + let css = await outputFS.readFile(path.join(distDir, 'index.css'), 'utf8'); + assert(css.includes('.a')); + }); }); diff --git a/packages/core/integration-tests/test/sass.js b/packages/core/integration-tests/test/sass.js index 6d6b0426207..1af3d682add 100644 --- a/packages/core/integration-tests/test/sass.js +++ b/packages/core/integration-tests/test/sass.js @@ -269,4 +269,20 @@ describe('sass', function () { let css = await outputFS.readFile(path.join(distDir, 'index.css'), 'utf8'); assert(css.includes('.included')); }); + + it('should support package.json exports', async function () { + let b = await bundle( + path.join(__dirname, '/integration/sass-exports/index.sass'), + ); + + assertBundles(b, [ + { + name: 'index.css', + assets: ['index.sass'], + }, + ]); + + let css = await outputFS.readFile(path.join(distDir, 'index.css'), 'utf8'); + assert(css.includes('.external')); + }); }); diff --git a/packages/core/integration-tests/test/stylus.js b/packages/core/integration-tests/test/stylus.js index 872e10c93b5..af1455cb85d 100644 --- a/packages/core/integration-tests/test/stylus.js +++ b/packages/core/integration-tests/test/stylus.js @@ -172,4 +172,13 @@ describe('stylus', function () { assert(css.includes('.foo')); assert(css.includes('.bar')); }); + + it('should support the stylus package exports condition', async function () { + await bundle( + path.join(__dirname, '/integration/stylus-exports/index.styl'), + ); + + let css = await outputFS.readFile(path.join(distDir, 'index.css'), 'utf8'); + assert(css.includes('.a')); + }); }); diff --git a/packages/core/types/index.js b/packages/core/types/index.js index d46081d3d0d..81ba604e905 100644 --- a/packages/core/types/index.js +++ b/packages/core/types/index.js @@ -517,6 +517,13 @@ export type DependencyOptions = {| +loc?: SourceLocation, /** The environment of the dependency. */ +env?: EnvironmentOptions, + /** + * A list of custom conditions to use when resolving package.json "exports" and "imports". + * This is combined with the conditions from the environment. However, it overrides the + * default "import" and "require" conditions inferred from the specifierType. To include those + * in addition to custom conditions, explicitly add them to this list. + */ + +packageConditions?: Array, /** Plugin-specific metadata for the dependency. */ +meta?: Meta, /** The pipeline defined in .parcelrc that the dependency should be processed with. */ @@ -589,6 +596,13 @@ export interface Dependency { +loc: ?SourceLocation; /** The environment of the dependency. */ +env: Environment; + /** + * A list of custom conditions to use when resolving package.json "exports" and "imports". + * This is combined with the conditions from the environment. However, it overrides the + * default "import" and "require" conditions inferred from the specifierType. To include those + * in addition to custom conditions, explicitly add them to this list. + */ + +packageConditions: ?Array; /** Plugin-specific metadata for the dependency. */ +meta: Meta; /** If this is an entry, this is the target that is associated with that entry. */ @@ -959,7 +973,27 @@ export interface PluginLogger { /** * @section transformer */ -export type ResolveFn = (from: FilePath, to: string) => Promise; +export type ResolveOptions = {| + /** + * How the specifier should be interpreted. + * - esm: An ES module specifier. It is parsed as a URL, but bare specifiers are treated as node_modules. + * - commonjs: A CommonJS specifier. It is not parsed as a URL. + * - url: A URL that works as in a browser. Bare specifiers are treated as relative URLs. + * - custom: A custom specifier. Must be handled by a custom resolver plugin. + */ + +specifierType?: SpecifierType, + /** A list of custom conditions to use when resolving package.json "exports" and "imports". */ + +packageConditions?: Array, +|}; + +/** + * @section transformer + */ +export type ResolveFn = ( + from: FilePath, + to: string, + options?: ResolveOptions, +) => Promise; /** * @section validator diff --git a/packages/resolvers/default/src/DefaultResolver.js b/packages/resolvers/default/src/DefaultResolver.js index 9581142abc3..8468d543f11 100644 --- a/packages/resolvers/default/src/DefaultResolver.js +++ b/packages/resolvers/default/src/DefaultResolver.js @@ -31,6 +31,7 @@ export default (new Resolver({ env: dependency.env, sourcePath: dependency.sourcePath, loc: dependency.loc, + packageConditions: dependency.packageConditions, }); }, }): Resolver); diff --git a/packages/transformers/css/src/CSSTransformer.js b/packages/transformers/css/src/CSSTransformer.js index c305e9790bd..d0baaf51608 100644 --- a/packages/transformers/css/src/CSSTransformer.js +++ b/packages/transformers/css/src/CSSTransformer.js @@ -156,6 +156,7 @@ export default (new Transformer({ specifier: dep.url, specifierType: 'url', loc, + packageConditions: ['style'], meta: { // For the glob resolver to distinguish between `@import` and other URL dependencies. isCSSImport: true, @@ -225,6 +226,7 @@ export default (new Transformer({ asset.addDependency({ specifier: ref.specifier, specifierType: 'esm', + packageConditions: ['style'], }); } s += '${' + `${d}[${JSON.stringify(ref.name)}]` + '}'; @@ -268,6 +270,7 @@ export default (new Transformer({ asset.addDependency({ specifier: reference.specifier, specifierType: 'esm', + packageConditions: ['style'], symbols: new Map([ [reference.name, {local: symbol, isWeak: false, loc: null}], ]), diff --git a/packages/transformers/less/src/LessTransformer.js b/packages/transformers/less/src/LessTransformer.js index d6607790404..d5fc868be11 100644 --- a/packages/transformers/less/src/LessTransformer.js +++ b/packages/transformers/less/src/LessTransformer.js @@ -152,7 +152,9 @@ function resolvePathPlugin({asset, resolve}) { } if (!contents) { - filePath = await resolve(asset.filePath, filename); + filePath = await resolve(asset.filePath, filename, { + packageConditions: ['less', 'style'], + }); contents = await asset.fs.readFile(filePath, 'utf8'); } diff --git a/packages/transformers/sass/src/SassTransformer.js b/packages/transformers/sass/src/SassTransformer.js index 9253c3f7049..595e4b62ae9 100644 --- a/packages/transformers/sass/src/SassTransformer.js +++ b/packages/transformers/sass/src/SassTransformer.js @@ -163,7 +163,9 @@ function resolvePathImporter({asset, resolve, includePaths, options}) { u = u.slice(1); } try { - const filePath = await resolve(prev, u); + const filePath = await resolve(prev, u, { + packageConditions: ['sass', 'style'], + }); if (filePath) { const contents = await asset.fs.readFile(filePath, 'utf8'); return {filePath, contents}; diff --git a/packages/transformers/stylus/src/StylusTransformer.js b/packages/transformers/stylus/src/StylusTransformer.js index d1d36bfaf50..50aabee87a4 100644 --- a/packages/transformers/stylus/src/StylusTransformer.js +++ b/packages/transformers/stylus/src/StylusTransformer.js @@ -101,13 +101,21 @@ function attemptResolve(importedPath, filepath, asset, resolve, deps) { resolve( filepath, './' + path.relative(path.dirname(filepath), entry), + { + packageConditions: ['stylus', 'style'], + }, ), ), ), ), ); } else { - deps.set(importedPath, resolve(filepath, importedPath)); + deps.set( + importedPath, + resolve(filepath, importedPath, { + packageConditions: ['stylus', 'style'], + }), + ); } } diff --git a/packages/utils/node-resolver-core/src/Wrapper.js b/packages/utils/node-resolver-core/src/Wrapper.js index 36185d06ba1..c95f8808794 100644 --- a/packages/utils/node-resolver-core/src/Wrapper.js +++ b/packages/utils/node-resolver-core/src/Wrapper.js @@ -56,6 +56,7 @@ type ResolveOptions = {| env: Environment, sourcePath?: ?FilePath, loc?: ?SourceLocation, + packageConditions?: ?Array, |}; export default class NodeResolver { diff --git a/packages/utils/node-resolver-core/src/lib.rs b/packages/utils/node-resolver-core/src/lib.rs index ab010cfbf98..bbf6c02e74a 100644 --- a/packages/utils/node-resolver-core/src/lib.rs +++ b/packages/utils/node-resolver-core/src/lib.rs @@ -171,6 +171,7 @@ pub struct ResolveOptions { pub filename: String, pub specifier_type: String, pub parent: String, + pub package_conditions: Option>, } #[napi(object)] @@ -267,7 +268,7 @@ impl Resolver { #[napi] pub fn resolve(&self, options: ResolveOptions, env: Env) -> Result { - let mut res = self.resolver.resolve( + let mut res = self.resolver.resolve_with_options( &options.filename, Path::new(&options.parent), match options.specifier_type.as_ref() { @@ -281,6 +282,11 @@ impl Resolver { )) } }, + if let Some(conditions) = options.package_conditions { + get_resolve_options(conditions) + } else { + Default::default() + }, ); let side_effects = if let Ok((Resolution::Path(p), _)) = &res.result { @@ -350,3 +356,20 @@ fn convert_invalidations( .collect(); (invalidate_on_file_change, invalidate_on_file_create) } + +fn get_resolve_options(mut custom_conditions: Vec) -> parcel_resolver::ResolveOptions { + let mut conditions = ExportsCondition::empty(); + custom_conditions.retain(|condition| { + if let Ok(cond) = ExportsCondition::try_from(condition.as_ref()) { + conditions |= cond; + false + } else { + true + } + }); + + parcel_resolver::ResolveOptions { + conditions, + custom_conditions, + } +} diff --git a/packages/utils/node-resolver-core/test/fixture/tsconfig/extends-extension/base-tsconfig.json b/packages/utils/node-resolver-core/test/fixture/tsconfig/extends-extension/base-tsconfig.json new file mode 100644 index 00000000000..2923ba1d0f3 --- /dev/null +++ b/packages/utils/node-resolver-core/test/fixture/tsconfig/extends-extension/base-tsconfig.json @@ -0,0 +1,7 @@ +{ + "compilerOptions": { + "paths": { + "foo": ["foo.js"] + } + } +} diff --git a/packages/utils/node-resolver-core/test/fixture/tsconfig/extends-extension/foo.js b/packages/utils/node-resolver-core/test/fixture/tsconfig/extends-extension/foo.js new file mode 100644 index 00000000000..e69de29bb2d diff --git a/packages/utils/node-resolver-core/test/fixture/tsconfig/extends-extension/tsconfig.json b/packages/utils/node-resolver-core/test/fixture/tsconfig/extends-extension/tsconfig.json new file mode 100644 index 00000000000..0ae4c6a3faf --- /dev/null +++ b/packages/utils/node-resolver-core/test/fixture/tsconfig/extends-extension/tsconfig.json @@ -0,0 +1,3 @@ +{ + "extends": "./base-tsconfig" +} diff --git a/packages/utils/node-resolver-rs/src/lib.rs b/packages/utils/node-resolver-rs/src/lib.rs index 446235e7e48..e3ef5956871 100644 --- a/packages/utils/node-resolver-rs/src/lib.rs +++ b/packages/utils/node-resolver-rs/src/lib.rs @@ -90,6 +90,12 @@ pub struct Resolver<'a, Fs> { cache: CacheCow<'a, Fs>, } +#[derive(Default)] +pub struct ResolveOptions { + pub conditions: ExportsCondition, + pub custom_conditions: Vec, +} + #[derive(Debug, PartialEq, Eq, Clone, serde::Serialize)] #[serde(tag = "type", content = "value")] pub enum Resolution { @@ -158,6 +164,16 @@ impl<'a, Fs: FileSystem> Resolver<'a, Fs> { specifier: &'s str, from: &Path, specifier_type: SpecifierType, + ) -> ResolveResult { + self.resolve_with_options(specifier, from, specifier_type, Default::default()) + } + + pub fn resolve_with_options<'s>( + &self, + specifier: &'s str, + from: &Path, + specifier_type: SpecifierType, + options: ResolveOptions, ) -> ResolveResult { let invalidations = Invalidations::default(); let (specifier, query) = match Specifier::parse(specifier, specifier_type, self.flags) { @@ -169,7 +185,13 @@ impl<'a, Fs: FileSystem> Resolver<'a, Fs> { } } }; - let request = ResolveRequest::new(self, &specifier, specifier_type, from, &invalidations); + let mut request = ResolveRequest::new(self, &specifier, specifier_type, from, &invalidations); + if !options.conditions.is_empty() || !options.custom_conditions.is_empty() { + // If custom conditions are defined, these override the default conditions inferred from the specifier type. + request.conditions = self.conditions | options.conditions; + request.custom_conditions = options.custom_conditions.as_slice(); + } + let result = match request.resolve() { Ok(r) => Ok((r, query.map(|q| q.to_owned()))), Err(r) => Err(r), @@ -251,6 +273,7 @@ struct ResolveRequest<'a, Fs> { root_package: OnceCell>>, invalidations: &'a Invalidations, conditions: ExportsCondition, + custom_conditions: &'a [String], priority_extension: Option<&'a str>, } @@ -319,6 +342,7 @@ impl<'a, Fs: FileSystem> ResolveRequest<'a, Fs> { root_package: OnceCell::new(), invalidations, conditions, + custom_conditions: &[], priority_extension, } } @@ -345,6 +369,8 @@ impl<'a, Fs: FileSystem> ResolveRequest<'a, Fs> { self.invalidations, ); req.priority_extension = self.priority_extension; + req.conditions = self.conditions; + req.custom_conditions = self.custom_conditions; let resolved = req.resolve()?; Ok(Some(resolved)) } @@ -406,7 +432,7 @@ impl<'a, Fs: FileSystem> ResolveRequest<'a, Fs> { let package = self.find_package(&self.from.parent().unwrap())?; if let Some(package) = package { let res = package - .resolve_package_imports(&hash, self.conditions) + .resolve_package_imports(&hash, self.conditions, self.custom_conditions) .map_err(|e| ResolverError::PackageJsonError { module: package.name.to_owned(), path: package.path.clone(), @@ -599,7 +625,7 @@ impl<'a, Fs: FileSystem> ResolveRequest<'a, Fs> { // Otherwise, fall back to classic CJS resolution. if self.resolver.flags.contains(Flags::EXPORTS) && package.has_exports() { let path = package - .resolve_package_exports(subpath, self.conditions) + .resolve_package_exports(subpath, self.conditions, self.custom_conditions) .map_err(|e| ResolverError::PackageJsonError { module: package.name.to_owned(), path: package.path.clone(), @@ -995,7 +1021,24 @@ impl<'a, Fs: FileSystem> ResolveRequest<'a, Fs> { absolute_path.push("tsconfig.json"); } - if !self.resolver.cache.fs.is_file(&absolute_path) { + let mut exists = self.resolver.cache.fs.is_file(&absolute_path); + + // If the file doesn't exist, and doesn't end with `.json`, try appending the extension. + if !exists { + let try_extension = match absolute_path.extension() { + None => true, + Some(ext) => ext != "json", + }; + + if try_extension { + let mut os_str = absolute_path.into_os_string(); + os_str.push(".json"); + absolute_path = PathBuf::from(os_str); + exists = self.resolver.cache.fs.is_file(&absolute_path) + } + } + + if !exists { return Err(ResolverError::TsConfigExtendsNotFound { tsconfig: tsconfig.compiler_options.path.clone(), error: Box::new(ResolverError::FileNotFound { @@ -2124,6 +2167,18 @@ mod tests { .0, Resolution::Path(root().join("node_modules/tsconfig-exports/foo.js")) ); + assert_eq!( + test_resolver() + .resolve( + "foo", + &root().join("tsconfig/extends-extension/index.js"), + SpecifierType::Esm + ) + .result + .unwrap() + .0, + Resolution::Path(root().join("tsconfig/extends-extension/foo.js")) + ); assert_eq!( test_resolver() .resolve( diff --git a/packages/utils/node-resolver-rs/src/package_json.rs b/packages/utils/node-resolver-rs/src/package_json.rs index e984377ad66..5f3dd30a6eb 100644 --- a/packages/utils/node-resolver-rs/src/package_json.rs +++ b/packages/utils/node-resolver-rs/src/package_json.rs @@ -132,6 +132,16 @@ bitflags! { const PRODUCTION = 1 << 9; const TYPES = 1 << 10; const DEFAULT = 1 << 11; + const STYLE = 1 << 12; + const SASS = 1 << 13; + const LESS = 1 << 14; + const STYLUS = 1 << 15; + } +} + +impl Default for ExportsCondition { + fn default() -> Self { + ExportsCondition::empty() } } @@ -151,6 +161,10 @@ impl TryFrom<&str> for ExportsCondition { "production" => ExportsCondition::PRODUCTION, "types" => ExportsCondition::TYPES, "default" => ExportsCondition::DEFAULT, + "style" => ExportsCondition::STYLE, + "sass" => ExportsCondition::SASS, + "less" => ExportsCondition::LESS, + "stylus" => ExportsCondition::STYLUS, _ => return Err(()), }) } @@ -254,6 +268,7 @@ impl<'a> PackageJson<'a> { &self, subpath: &'a str, conditions: ExportsCondition, + custom_conditions: &[String], ) -> Result { // 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) = &self.exports { @@ -288,14 +303,20 @@ impl<'a> PackageJson<'a> { } if main_export != &ExportsField::None { - match self.resolve_package_target(main_export, "", false, conditions)? { + match self.resolve_package_target(main_export, "", false, conditions, custom_conditions)? { ExportsResolution::Path(path) => return Ok(path), ExportsResolution::None | ExportsResolution::Package(..) => {} } } } else if let ExportsField::Map(exports) = &self.exports { // All exports must start with "." at this point. - match self.resolve_package_imports_exports(subpath, &exports, false, conditions)? { + match self.resolve_package_imports_exports( + subpath, + &exports, + false, + conditions, + custom_conditions, + )? { ExportsResolution::Path(path) => return Ok(path), ExportsResolution::None | ExportsResolution::Package(..) => {} } @@ -308,12 +329,19 @@ impl<'a> PackageJson<'a> { &self, specifier: &'a str, conditions: ExportsCondition, + custom_conditions: &[String], ) -> Result, PackageJsonError> { if specifier == "#" || specifier.starts_with("#/") { return Err(PackageJsonError::InvalidSpecifier); } - match self.resolve_package_imports_exports(specifier, &self.imports, true, conditions)? { + match self.resolve_package_imports_exports( + specifier, + &self.imports, + true, + conditions, + custom_conditions, + )? { ExportsResolution::None => {} res => return Ok(res), } @@ -327,6 +355,7 @@ impl<'a> PackageJson<'a> { pattern_match: &str, is_imports: bool, conditions: ExportsCondition, + custom_conditions: &[String], ) -> Result, PackageJsonError> { match target { ExportsField::String(target) => { @@ -372,12 +401,23 @@ impl<'a> PackageJson<'a> { ExportsField::Map(target) => { // We must iterate in object insertion order. for (key, value) in target { - if let ExportsKey::Condition(key) = key { - if *key == ExportsCondition::DEFAULT || conditions.contains(*key) { - match self.resolve_package_target(value, pattern_match, is_imports, conditions)? { - ExportsResolution::None => continue, - res => return Ok(res), - } + let matches = match key { + ExportsKey::Condition(key) => { + *key == ExportsCondition::DEFAULT || conditions.contains(*key) + } + ExportsKey::CustomCondition(key) => custom_conditions.iter().any(|k| k == key), + _ => false, + }; + if matches { + match self.resolve_package_target( + value, + pattern_match, + is_imports, + conditions, + custom_conditions, + )? { + ExportsResolution::None => continue, + res => return Ok(res), } } } @@ -388,7 +428,13 @@ impl<'a> PackageJson<'a> { } for item in target { - match self.resolve_package_target(item, pattern_match, is_imports, conditions) { + match self.resolve_package_target( + item, + pattern_match, + is_imports, + conditions, + custom_conditions, + ) { Err(_) | Ok(ExportsResolution::None) => continue, Ok(res) => return Ok(res), } @@ -406,11 +452,12 @@ impl<'a> PackageJson<'a> { match_obj: &'a IndexMap, ExportsField<'a>>, is_imports: bool, conditions: ExportsCondition, + custom_conditions: &[String], ) -> Result, PackageJsonError> { let pattern = ExportsKey::Pattern(match_key); if let Some(target) = match_obj.get(&pattern) { if !match_key.contains('*') { - return self.resolve_package_target(target, "", is_imports, conditions); + return self.resolve_package_target(target, "", is_imports, conditions, custom_conditions); } } @@ -438,6 +485,7 @@ impl<'a> PackageJson<'a> { best_match, is_imports, conditions, + custom_conditions, ); } @@ -795,7 +843,7 @@ mod tests { assert_eq!( pkg - .resolve_package_exports("", ExportsCondition::empty()) + .resolve_package_exports("", ExportsCondition::empty(), &[]) .unwrap(), PathBuf::from("/foo/exports.js") ); @@ -816,12 +864,12 @@ mod tests { assert_eq!( pkg - .resolve_package_exports("", ExportsCondition::empty()) + .resolve_package_exports("", ExportsCondition::empty(), &[]) .unwrap(), PathBuf::from("/foo/exports.js") ); assert!(matches!( - pkg.resolve_package_exports(".", ExportsCondition::empty()), + pkg.resolve_package_exports(".", ExportsCondition::empty(), &[]), Err(PackageJsonError::PackagePathNotExported) )); // assert_eq!(pkg.resolve_package_exports("foobar", &[]).unwrap(), PathBuf::from("/foo/exports.js")); @@ -843,22 +891,26 @@ mod tests { assert_eq!( pkg - .resolve_package_exports("", ExportsCondition::IMPORT | ExportsCondition::REQUIRE) + .resolve_package_exports( + "", + ExportsCondition::IMPORT | ExportsCondition::REQUIRE, + &[] + ) .unwrap(), PathBuf::from("/foo/import.js") ); assert_eq!( pkg - .resolve_package_exports("", ExportsCondition::REQUIRE) + .resolve_package_exports("", ExportsCondition::REQUIRE, &[]) .unwrap(), PathBuf::from("/foo/require.js") ); assert!(matches!( - pkg.resolve_package_exports("", ExportsCondition::empty()), + pkg.resolve_package_exports("", ExportsCondition::empty(), &[]), Err(PackageJsonError::PackagePathNotExported) )); assert!(matches!( - pkg.resolve_package_exports("", ExportsCondition::NODE), + pkg.resolve_package_exports("", ExportsCondition::NODE, &[]), Err(PackageJsonError::PackagePathNotExported) )); } @@ -879,19 +931,19 @@ mod tests { assert_eq!( pkg - .resolve_package_exports("foo", ExportsCondition::empty()) + .resolve_package_exports("foo", ExportsCondition::empty(), &[]) .unwrap(), PathBuf::from("/foo/exports.js") ); assert_eq!( pkg - .resolve_package_exports(".invisible", ExportsCondition::empty()) + .resolve_package_exports(".invisible", ExportsCondition::empty(), &[]) .unwrap(), PathBuf::from("/foo/.invisible.js") ); assert_eq!( pkg - .resolve_package_exports("file", ExportsCondition::empty()) + .resolve_package_exports("file", ExportsCondition::empty(), &[]) .unwrap(), PathBuf::from("/foo/file.js") ); @@ -913,22 +965,26 @@ mod tests { assert_eq!( pkg - .resolve_package_exports("foo", ExportsCondition::IMPORT | ExportsCondition::REQUIRE) + .resolve_package_exports( + "foo", + ExportsCondition::IMPORT | ExportsCondition::REQUIRE, + &[] + ) .unwrap(), PathBuf::from("/foo/import.js") ); assert_eq!( pkg - .resolve_package_exports("foo", ExportsCondition::REQUIRE) + .resolve_package_exports("foo", ExportsCondition::REQUIRE, &[]) .unwrap(), PathBuf::from("/foo/require.js") ); assert!(matches!( - pkg.resolve_package_exports("foo", ExportsCondition::empty()), + pkg.resolve_package_exports("foo", ExportsCondition::empty(), &[]), Err(PackageJsonError::PackagePathNotExported) )); assert!(matches!( - pkg.resolve_package_exports("foo", ExportsCondition::NODE), + pkg.resolve_package_exports("foo", ExportsCondition::NODE, &[]), Err(PackageJsonError::PackagePathNotExported) )); } @@ -950,31 +1006,56 @@ mod tests { assert_eq!( pkg - .resolve_package_exports("", ExportsCondition::NODE | ExportsCondition::IMPORT) + .resolve_package_exports("", ExportsCondition::NODE | ExportsCondition::IMPORT, &[]) .unwrap(), PathBuf::from("/foo/import.js") ); assert_eq!( pkg - .resolve_package_exports("", ExportsCondition::NODE | ExportsCondition::REQUIRE) + .resolve_package_exports("", ExportsCondition::NODE | ExportsCondition::REQUIRE, &[]) .unwrap(), PathBuf::from("/foo/require.js") ); assert_eq!( pkg - .resolve_package_exports("", ExportsCondition::IMPORT) + .resolve_package_exports("", ExportsCondition::IMPORT, &[]) + .unwrap(), + PathBuf::from("/foo/default.js") + ); + assert_eq!( + pkg + .resolve_package_exports("", ExportsCondition::empty(), &[]) .unwrap(), PathBuf::from("/foo/default.js") ); assert_eq!( pkg - .resolve_package_exports("", ExportsCondition::empty()) + .resolve_package_exports("", ExportsCondition::NODE, &[]) .unwrap(), PathBuf::from("/foo/default.js") ); + } + + #[test] + fn custom_conditions() { + let pkg = PackageJson { + path: "/foo/package.json".into(), + name: "foobar", + exports: ExportsField::Map(indexmap! { + "custom".into() => ExportsField::String("./custom.js"), + "default".into() => ExportsField::String("./default.js") + }), + ..PackageJson::default() + }; + assert_eq!( + pkg + .resolve_package_exports("", ExportsCondition::NODE, &["custom".into()]) + .unwrap(), + PathBuf::from("/foo/custom.js") + ); assert_eq!( pkg - .resolve_package_exports("", ExportsCondition::NODE) + .resolve_package_exports("", ExportsCondition::NODE, &[]) .unwrap(), PathBuf::from("/foo/default.js") ); @@ -1002,19 +1083,31 @@ mod tests { assert_eq!( pkg - .resolve_package_exports("lite", ExportsCondition::NODE | ExportsCondition::IMPORT) + .resolve_package_exports( + "lite", + ExportsCondition::NODE | ExportsCondition::IMPORT, + &[] + ) .unwrap(), PathBuf::from("/foo/node_import.js") ); assert_eq!( pkg - .resolve_package_exports("lite", ExportsCondition::NODE | ExportsCondition::REQUIRE) + .resolve_package_exports( + "lite", + ExportsCondition::NODE | ExportsCondition::REQUIRE, + &[] + ) .unwrap(), PathBuf::from("/foo/node_require.js") ); assert_eq!( pkg - .resolve_package_exports("lite", ExportsCondition::BROWSER | ExportsCondition::IMPORT) + .resolve_package_exports( + "lite", + ExportsCondition::BROWSER | ExportsCondition::IMPORT, + &[] + ) .unwrap(), PathBuf::from("/foo/browser_import.js") ); @@ -1022,13 +1115,14 @@ mod tests { pkg .resolve_package_exports( "lite", - ExportsCondition::BROWSER | ExportsCondition::REQUIRE + ExportsCondition::BROWSER | ExportsCondition::REQUIRE, + &[] ) .unwrap(), PathBuf::from("/foo/browser_require.js") ); assert!(matches!( - pkg.resolve_package_exports("lite", ExportsCondition::empty()), + pkg.resolve_package_exports("lite", ExportsCondition::empty(), &[]), Err(PackageJsonError::PackagePathNotExported) )); } @@ -1049,37 +1143,37 @@ mod tests { assert_eq!( pkg - .resolve_package_exports("hello", ExportsCondition::empty()) + .resolve_package_exports("hello", ExportsCondition::empty(), &[]) .unwrap(), PathBuf::from("/foo/cheese/hello.mjs") ); assert_eq!( pkg - .resolve_package_exports("hello/world", ExportsCondition::empty()) + .resolve_package_exports("hello/world", ExportsCondition::empty(), &[]) .unwrap(), PathBuf::from("/foo/cheese/hello/world.mjs") ); assert_eq!( pkg - .resolve_package_exports("hello.js", ExportsCondition::empty()) + .resolve_package_exports("hello.js", ExportsCondition::empty(), &[]) .unwrap(), PathBuf::from("/foo/cheese/hello.js.mjs") ); assert_eq!( pkg - .resolve_package_exports("pizza/test", ExportsCondition::empty()) + .resolve_package_exports("pizza/test", ExportsCondition::empty(), &[]) .unwrap(), PathBuf::from("/foo/pizza/test.mjs") ); assert_eq!( pkg - .resolve_package_exports("burritos/test", ExportsCondition::empty()) + .resolve_package_exports("burritos/test", ExportsCondition::empty(), &[]) .unwrap(), PathBuf::from("/foo/burritos/test/test.mjs") ); assert_eq!( pkg - .resolve_package_exports("literal", ExportsCondition::empty()) + .resolve_package_exports("literal", ExportsCondition::empty(), &[]) .unwrap(), PathBuf::from("/foo/literal/*.js") ); @@ -1096,16 +1190,16 @@ mod tests { }; assert_eq!( pkg - .resolve_package_exports("file", ExportsCondition::empty()) + .resolve_package_exports("file", ExportsCondition::empty(), &[]) .unwrap(), PathBuf::from("/foo/file.js") ); assert!(matches!( - pkg.resolve_package_exports("file.js", ExportsCondition::empty()), + pkg.resolve_package_exports("file.js", ExportsCondition::empty(), &[]), Err(PackageJsonError::PackagePathNotExported) )); assert!(matches!( - pkg.resolve_package_exports("internal/file", ExportsCondition::empty()), + pkg.resolve_package_exports("internal/file", ExportsCondition::empty(), &[]), Err(PackageJsonError::PackagePathNotExported) )); } @@ -1124,20 +1218,21 @@ mod tests { assert_eq!( pkg - .resolve_package_exports("features/foo.js", ExportsCondition::empty()) + .resolve_package_exports("features/foo.js", ExportsCondition::empty(), &[]) .unwrap(), PathBuf::from("/foo/src/features/foo.js") ); assert_eq!( pkg - .resolve_package_exports("features/foo/bar.js", ExportsCondition::empty()) + .resolve_package_exports("features/foo/bar.js", ExportsCondition::empty(), &[]) .unwrap(), PathBuf::from("/foo/src/features/foo/bar.js") ); assert!(matches!( pkg.resolve_package_exports( "features/private-internal/foo.js", - ExportsCondition::empty() + ExportsCondition::empty(), + &[] ), Err(PackageJsonError::PackagePathNotExported) ),); @@ -1167,7 +1262,8 @@ mod tests { pkg .resolve_package_exports( "utils/index.js", - ExportsCondition::BROWSER | ExportsCondition::WORKLET + ExportsCondition::BROWSER | ExportsCondition::WORKLET, + &[] ) .unwrap(), PathBuf::from("/foo/index.js") @@ -1176,29 +1272,30 @@ mod tests { pkg .resolve_package_exports( "utils/index.js", - ExportsCondition::BROWSER | ExportsCondition::NODE + ExportsCondition::BROWSER | ExportsCondition::NODE, + &[] ) .unwrap(), PathBuf::from("/foo/node/index.js") ); assert_eq!( pkg - .resolve_package_exports("test/index.js", ExportsCondition::empty()) + .resolve_package_exports("test/index.js", ExportsCondition::empty(), &[]) .unwrap(), PathBuf::from("/foo/bar/index.js") ); assert_eq!( pkg - .resolve_package_exports("file", ExportsCondition::empty()) + .resolve_package_exports("file", ExportsCondition::empty(), &[]) .unwrap(), PathBuf::from("/foo/file.js") ); assert!(matches!( - pkg.resolve_package_exports("utils/index.js", ExportsCondition::BROWSER), + pkg.resolve_package_exports("utils/index.js", ExportsCondition::BROWSER, &[]), Err(PackageJsonError::PackagePathNotExported) )); assert!(matches!( - pkg.resolve_package_exports("dir/file.js", ExportsCondition::BROWSER), + pkg.resolve_package_exports("dir/file.js", ExportsCondition::BROWSER, &[]), Err(PackageJsonError::PackagePathNotExported) )); @@ -1216,13 +1313,13 @@ mod tests { assert_eq!( pkg - .resolve_package_exports("", ExportsCondition::empty()) + .resolve_package_exports("", ExportsCondition::empty(), &[]) .unwrap(), PathBuf::from("/foo/b.js") ); assert_eq!( pkg - .resolve_package_exports("", ExportsCondition::NODE) + .resolve_package_exports("", ExportsCondition::NODE, &[]) .unwrap(), PathBuf::from("/foo/a.js") ); @@ -1247,35 +1344,35 @@ mod tests { }; assert!(matches!( - pkg.resolve_package_exports("invalid", ExportsCondition::empty()), + pkg.resolve_package_exports("invalid", ExportsCondition::empty(), &[]), Err(PackageJsonError::InvalidPackageTarget) )); assert!(matches!( - pkg.resolve_package_exports("absolute", ExportsCondition::empty()), + pkg.resolve_package_exports("absolute", ExportsCondition::empty(), &[]), Err(PackageJsonError::InvalidPackageTarget) )); assert!(matches!( - pkg.resolve_package_exports("package", ExportsCondition::empty()), + pkg.resolve_package_exports("package", ExportsCondition::empty(), &[]), Err(PackageJsonError::InvalidPackageTarget) )); assert!(matches!( - pkg.resolve_package_exports("utils/index", ExportsCondition::empty()), + pkg.resolve_package_exports("utils/index", ExportsCondition::empty(), &[]), Err(PackageJsonError::InvalidPackageTarget) )); assert!(matches!( - pkg.resolve_package_exports("dist/foo", ExportsCondition::empty()), + pkg.resolve_package_exports("dist/foo", ExportsCondition::empty(), &[]), Err(PackageJsonError::InvalidPackageTarget) )); assert!(matches!( - pkg.resolve_package_exports("modules/foo", ExportsCondition::empty()), + pkg.resolve_package_exports("modules/foo", ExportsCondition::empty(), &[]), Err(PackageJsonError::InvalidPackageTarget) )); assert!(matches!( - pkg.resolve_package_exports("a/b", ExportsCondition::empty()), + pkg.resolve_package_exports("a/b", ExportsCondition::empty(), &[]), Err(PackageJsonError::PackagePathNotExported) )); assert!(matches!( - pkg.resolve_package_exports("a/*", ExportsCondition::empty()), + pkg.resolve_package_exports("a/*", ExportsCondition::empty(), &[]), Err(PackageJsonError::PackagePathNotExported) )); @@ -1290,11 +1387,11 @@ mod tests { }; assert!(matches!( - pkg.resolve_package_exports("", ExportsCondition::NODE), + pkg.resolve_package_exports("", ExportsCondition::NODE, &[]), Err(PackageJsonError::InvalidPackageTarget) )); assert!(matches!( - pkg.resolve_package_exports("", ExportsCondition::NODE), + pkg.resolve_package_exports("", ExportsCondition::NODE, &[]), Err(PackageJsonError::InvalidPackageTarget) )); } @@ -1314,19 +1411,19 @@ mod tests { assert_eq!( pkg - .resolve_package_imports("foo", ExportsCondition::empty()) + .resolve_package_imports("foo", ExportsCondition::empty(), &[]) .unwrap(), ExportsResolution::Path(PathBuf::from("/foo/foo.mjs")) ); assert_eq!( pkg - .resolve_package_imports("internal/foo", ExportsCondition::empty()) + .resolve_package_imports("internal/foo", ExportsCondition::empty(), &[]) .unwrap(), ExportsResolution::Path(PathBuf::from("/foo/src/internal/foo.mjs")) ); assert_eq!( pkg - .resolve_package_imports("bar", ExportsCondition::empty()) + .resolve_package_imports("bar", ExportsCondition::empty(), &[]) .unwrap(), ExportsResolution::Package("bar".into()) ); @@ -1347,13 +1444,13 @@ mod tests { }; assert_eq!( pkg - .resolve_package_imports("entry/foo", ExportsCondition::NODE) + .resolve_package_imports("entry/foo", ExportsCondition::NODE, &[]) .unwrap(), ExportsResolution::Path(PathBuf::from("/foo/node/foo.js")) ); assert_eq!( pkg - .resolve_package_imports("entry/foo", ExportsCondition::BROWSER) + .resolve_package_imports("entry/foo", ExportsCondition::BROWSER, &[]) .unwrap(), ExportsResolution::Path(PathBuf::from("/foo/browser/foo.js")) ); @@ -1361,7 +1458,8 @@ mod tests { pkg .resolve_package_imports( "entry/foo", - ExportsCondition::NODE | ExportsCondition::BROWSER + ExportsCondition::NODE | ExportsCondition::BROWSER, + &[] ) .unwrap(), ExportsResolution::Path(PathBuf::from("/foo/node/foo.js")) diff --git a/packages/utils/node-resolver-rs/src/specifier.rs b/packages/utils/node-resolver-rs/src/specifier.rs index d19c5ad3f50..3e4df4ea9af 100644 --- a/packages/utils/node-resolver-rs/src/specifier.rs +++ b/packages/utils/node-resolver-rs/src/specifier.rs @@ -90,19 +90,21 @@ impl<'a> Specifier<'a> { // Bare specifier. match specifier_type { SpecifierType::Url | SpecifierType::Esm => { - if BUILTINS.contains(&specifier.as_ref()) { - return Ok((Specifier::Builtin(Cow::Borrowed(specifier)), None)); - } - // Check if there is a scheme first. if let Ok((scheme, rest)) = parse_scheme(specifier) { let (path, rest) = parse_path(rest); let (query, _) = parse_query(rest); match scheme.as_ref() { - "npm" if flags.contains(Flags::NPM_SCHEME) => ( - parse_package(percent_decode_str(path).decode_utf8_lossy())?, - query, - ), + "npm" if flags.contains(Flags::NPM_SCHEME) => { + if BUILTINS.contains(&path.as_ref()) { + return Ok((Specifier::Builtin(Cow::Borrowed(path)), None)); + } + + ( + parse_package(percent_decode_str(path).decode_utf8_lossy())?, + query, + ) + } "node" => { // Node does not URL decode or support query params here. // See https://github.com/nodejs/node/issues/39710. @@ -127,6 +129,10 @@ impl<'a> Specifier<'a> { // otherwise treat this as a relative path. let (path, rest) = parse_path(specifier); if specifier_type == SpecifierType::Esm { + if BUILTINS.contains(&path.as_ref()) { + return Ok((Specifier::Builtin(Cow::Borrowed(path)), None)); + } + let (query, _) = parse_query(rest); ( parse_package(percent_decode_str(path).decode_utf8_lossy())?,