From 817d5c86b8cfced1192e6fb0b833ef7dc77a47cf Mon Sep 17 00:00:00 2001 From: Jan Krems Date: Thu, 18 Jul 2019 21:52:55 -0700 Subject: [PATCH 1/6] module: implement "exports" proposal for CommonJS Refs: https://github.com/jkrems/proposal-pkg-exports/issues/36 Refs: https://github.com/nodejs/node/pull/28568 --- doc/api/errors.md | 7 ++ doc/api/modules.md | 29 ++++++ lib/internal/errors.js | 2 + lib/internal/modules/cjs/loader.js | 96 +++++++++++++++++-- src/module_wrap.cc | 2 +- src/node_errors.h | 1 + src/node_file.cc | 4 +- src/node_options.cc | 1 + test/es-module/test-esm-exports.mjs | 2 +- .../node_modules/pkgexports/package.json | 1 + test/parallel/test-module-package-exports.js | 45 +++++++++ 11 files changed, 179 insertions(+), 11 deletions(-) create mode 100644 test/parallel/test-module-package-exports.js diff --git a/doc/api/errors.md b/doc/api/errors.md index f638f0ba905db6..902f5f5c8ea93a 100644 --- a/doc/api/errors.md +++ b/doc/api/errors.md @@ -1585,6 +1585,13 @@ compiled with ICU support. A given value is out of the accepted range. + +### ERR_PATH_NOT_EXPORTED + +> Stability: 1 - Experimental + +An attempt was made to load a protected path from a package using `exports`. + ### ERR_REQUIRE_ESM diff --git a/doc/api/modules.md b/doc/api/modules.md index 73771f49af6639..71dca3f2b9070f 100644 --- a/doc/api/modules.md +++ b/doc/api/modules.md @@ -202,6 +202,35 @@ NODE_MODULES_PATHS(START) 5. return DIRS ``` +If `--experimental-exports` is enabled, +node allows packages loaded via `LOAD_NODE_MODULES` to explicitly declare +which filepaths to expose and how they should be interpreted. +This expands on the control packages already had using the `main` field. +With this feature enabled, the `LOAD_NODE_MODULES` changes as follows: + +```txt +LOAD_NODE_MODULES(X, START) +1. let DIRS = NODE_MODULES_PATHS(START) +2. for each DIR in DIRS: + a. let FILE_PATH = RESOLVE_BARE_SPECIFIER(DIR, X) + a. LOAD_AS_FILE(FILE_PATH) + b. LOAD_AS_DIRECTORY(FILE_PATH) + +RESOLVE_BARE_SPECIFIER(DIR, X) +1. Try to interpret X as a combination of name and subpath where the name + may have a @scope/ prefix and the subpath begins with a slash (`/`). +2. If X matches this pattern and DIR/name/package.json is a file: + a. Parse DIR/name/package.json, and look for "exports" field. + b. If "exports" is null or undefined, GOTO 3. + c. Find the longest key in "exports" that the subpath starts with. + d. If no such key can be found, throw "not exported". + e. If the key matches the subpath entirely, return DIR/name/${exports[key]}. + f. If either the key or exports[key] do not end with a slash (`/`), + throw "not exported". + g. Return DIR/name/${exports[key]}${subpath.slice(key.length)}. +3. return DIR/X +``` + ## Caching diff --git a/lib/internal/errors.js b/lib/internal/errors.js index c36f34eff40cab..64e5a2773b9aaf 100644 --- a/lib/internal/errors.js +++ b/lib/internal/errors.js @@ -1098,6 +1098,8 @@ E('ERR_OUT_OF_RANGE', msg += ` It must be ${range}. Received ${received}`; return msg; }, RangeError); +E('ERR_PATH_NOT_EXPORTED', + 'Package exports for \'%s\' do not define a \'%s\' subpath', Error); E('ERR_REQUIRE_ESM', 'Must use import to load ES Module: %s', Error); E('ERR_SCRIPT_EXECUTION_INTERRUPTED', 'Script execution was interrupted by `SIGINT`', Error); diff --git a/lib/internal/modules/cjs/loader.js b/lib/internal/modules/cjs/loader.js index 199405b0e24457..46f1c6d76bfcde 100644 --- a/lib/internal/modules/cjs/loader.js +++ b/lib/internal/modules/cjs/loader.js @@ -53,10 +53,12 @@ const { compileFunction } = internalBinding('contextify'); const { ERR_INVALID_ARG_VALUE, ERR_INVALID_OPT_VALUE, + ERR_PATH_NOT_EXPORTED, ERR_REQUIRE_ESM } = require('internal/errors').codes; const { validateString } = require('internal/validators'); const pendingDeprecation = getOptionValue('--pending-deprecation'); +const experimentalExports = getOptionValue('--experimental-exports'); module.exports = { wrapSafe, Module }; @@ -182,12 +184,10 @@ Module._debug = deprecate(debug, 'Module._debug is deprecated.', 'DEP0077'); // Check if the directory is a package.json dir. const packageMainCache = Object.create(null); +// Explicit exports from package.json files +const packageExportsCache = new Map(); -function readPackage(requestPath) { - const entry = packageMainCache[requestPath]; - if (entry) - return entry; - +function readPackageRaw(requestPath) { const jsonPath = path.resolve(requestPath, 'package.json'); const json = internalModuleReadJSON(path.toNamespacedPath(jsonPath)); @@ -201,7 +201,12 @@ function readPackage(requestPath) { } try { - return packageMainCache[requestPath] = JSON.parse(json).main; + const parsed = JSON.parse(json); + packageMainCache[requestPath] = parsed.main; + if (experimentalExports) { + packageExportsCache.set(requestPath, parsed.exports); + } + return parsed; } catch (e) { e.path = jsonPath; e.message = 'Error parsing ' + jsonPath + ': ' + e.message; @@ -209,6 +214,31 @@ function readPackage(requestPath) { } } +function readPackage(requestPath) { + const entry = packageMainCache[requestPath]; + if (entry) + return entry; + + const pkg = readPackageRaw(requestPath); + if (pkg === false) return false; + + return pkg.main; +} + +function readExports(requestPath) { + if (packageExportsCache.has(requestPath)) { + return packageExportsCache.get(requestPath); + } + + const pkg = readPackageRaw(requestPath); + if (!pkg) { + packageExportsCache.set(requestPath, null); + return null; + } + + return pkg.exports; +} + function tryPackage(requestPath, exts, isMain, originalPath) { const pkg = readPackage(requestPath); @@ -297,8 +327,58 @@ function findLongestRegisteredExtension(filename) { return '.js'; } +// This only applies to requests of a specific form: +// 1. name/.* +// 2. @scope/name/.* +const EXPORTS_PATTERN = /^((?:@[^./@\\][^/@\\]*\/)?[^@./\\][^/\\]*)(\/.*)$/; +function resolveExports(nmPath, request, absoluteRequest) { + // The implementation's behavior is meant to mirror resolution in ESM. + if (experimentalExports && !absoluteRequest) { + const [, name, expansion] = + request.match(EXPORTS_PATTERN) || []; + if (!name) { + return path.resolve(nmPath, request); + } + + const basePath = path.resolve(nmPath, name); + const pkgExports = readExports(basePath); + + if (pkgExports != null) { + const mappingKey = `.${expansion}`; + const mapping = pkgExports[mappingKey]; + if (typeof mapping === 'string') { + return path.resolve(basePath, mapping); + } + + let dirMatch = ''; + for (const [candidateKey, candidateValue] of Object.entries(pkgExports)) { + if (candidateKey[candidateKey.length - 1] !== '/') continue; + if (candidateValue[candidateValue.length - 1] !== '/') continue; + if (candidateKey.length > dirMatch.length && + mappingKey.startsWith(candidateKey)) { + dirMatch = candidateKey; + } + } + + if (dirMatch !== '') { + const dirMapping = pkgExports[dirMatch]; + const remainder = mappingKey.slice(dirMatch.length); + const expectedPrefix = path.resolve(basePath, dirMapping); + const resolved = path.resolve(expectedPrefix, remainder); + if (resolved.startsWith(expectedPrefix)) { + return resolved; + } + } + throw new ERR_PATH_NOT_EXPORTED(basePath, mappingKey); + } + } + + return path.resolve(nmPath, request); +} + Module._findPath = function(request, paths, isMain) { - if (path.isAbsolute(request)) { + const absoluteRequest = path.isAbsolute(request); + if (absoluteRequest) { paths = ['']; } else if (!paths || paths.length === 0) { return false; @@ -322,7 +402,7 @@ Module._findPath = function(request, paths, isMain) { // Don't search further if path doesn't exist const curPath = paths[i]; if (curPath && stat(curPath) < 1) continue; - var basePath = path.resolve(curPath, request); + var basePath = resolveExports(curPath, request, absoluteRequest); var filename; var rc = stat(basePath); diff --git a/src/module_wrap.cc b/src/module_wrap.cc index c13ab3b5ed21ae..503ca8a858e2b9 100644 --- a/src/module_wrap.cc +++ b/src/module_wrap.cc @@ -856,7 +856,7 @@ Maybe PackageExportsResolve(Environment* env, std::string msg = "Package exports for '" + URL(".", pjson_url).ToFilePath() + "' do not define a '" + pkg_subpath + "' subpath, imported from " + base.ToFilePath(); - node::THROW_ERR_MODULE_NOT_FOUND(env, msg.c_str()); + node::THROW_ERR_PATH_NOT_EXPORTED(env, msg.c_str()); return Nothing(); } diff --git a/src/node_errors.h b/src/node_errors.h index 939f93a4899f59..c2587d73e67df4 100644 --- a/src/node_errors.h +++ b/src/node_errors.h @@ -53,6 +53,7 @@ void PrintErrorString(const char* format, ...); V(ERR_MISSING_PLATFORM_FOR_WORKER, Error) \ V(ERR_MODULE_NOT_FOUND, Error) \ V(ERR_OUT_OF_RANGE, RangeError) \ + V(ERR_PATH_NOT_EXPORTED, Error) \ V(ERR_SCRIPT_EXECUTION_INTERRUPTED, Error) \ V(ERR_SCRIPT_EXECUTION_TIMEOUT, Error) \ V(ERR_STRING_TOO_LONG, Error) \ diff --git a/src/node_file.cc b/src/node_file.cc index 564c63bad73b23..e11aa9054640a9 100644 --- a/src/node_file.cc +++ b/src/node_file.cc @@ -872,7 +872,9 @@ static void InternalModuleReadJSON(const FunctionCallbackInfo& args) { } const size_t size = offset - start; - if (size == 0 || size == SearchString(&chars[start], size, "\"main\"")) { + if (size == 0 || ( + size == SearchString(&chars[start], size, "\"main\"") && + size == SearchString(&chars[start], size, "\"exports\""))) { return; } else { Local chars_string = diff --git a/src/node_options.cc b/src/node_options.cc index 9da1ed5fb81d67..eaa3e7d049b44b 100644 --- a/src/node_options.cc +++ b/src/node_options.cc @@ -319,6 +319,7 @@ EnvironmentOptionsParser::EnvironmentOptionsParser() { "experimental ES Module support and caching modules", &EnvironmentOptions::experimental_modules, kAllowedInEnvironment); + Implies("--experimental-modules", "--experimental-exports"); AddOption("--experimental-wasm-modules", "experimental ES Module support for webassembly modules", &EnvironmentOptions::experimental_wasm_modules, diff --git a/test/es-module/test-esm-exports.mjs b/test/es-module/test-esm-exports.mjs index 88115026654726..5852e7e7da4a19 100644 --- a/test/es-module/test-esm-exports.mjs +++ b/test/es-module/test-esm-exports.mjs @@ -1,4 +1,4 @@ -// Flags: --experimental-modules --experimental-exports +// Flags: --experimental-modules import { mustCall } from '../common/index.mjs'; import { ok, strictEqual } from 'assert'; diff --git a/test/fixtures/node_modules/pkgexports/package.json b/test/fixtures/node_modules/pkgexports/package.json index 51c596ed8673ab..459041121568d7 100644 --- a/test/fixtures/node_modules/pkgexports/package.json +++ b/test/fixtures/node_modules/pkgexports/package.json @@ -2,6 +2,7 @@ "exports": { ".": "./asdf.js", "./asdf": "./asdf.js", + "./valid-cjs": "./asdf.js", "./sub/": "./" } } diff --git a/test/parallel/test-module-package-exports.js b/test/parallel/test-module-package-exports.js new file mode 100644 index 00000000000000..d1e78aca0d85d4 --- /dev/null +++ b/test/parallel/test-module-package-exports.js @@ -0,0 +1,45 @@ +// Flags: --experimental-exports +'use strict'; + +require('../common'); + +const assert = require('assert'); +const { createRequire } = require('module'); +const path = require('path'); + +const fixtureRequire = + createRequire(path.resolve(__dirname, '../fixtures/imaginary.js')); + +assert.strictEqual(fixtureRequire('pkgexports/valid-cjs'), 'asdf'); + +assert.strictEqual(fixtureRequire('baz/index'), 'eye catcher'); + +assert.strictEqual(fixtureRequire('pkgexports/sub/asdf.js'), 'asdf'); + +assert.throws( + () => fixtureRequire('pkgexports/not-a-known-entry'), + (e) => { + assert.strictEqual(e.code, 'ERR_PATH_NOT_EXPORTED'); + return true; + }); + +assert.throws( + () => fixtureRequire('pkgexports-number/hidden.js'), + (e) => { + assert.strictEqual(e.code, 'ERR_PATH_NOT_EXPORTED'); + return true; + }); + +assert.throws( + () => fixtureRequire('pkgexports/sub/not-a-file.js'), + (e) => { + assert.strictEqual(e.code, 'MODULE_NOT_FOUND'); + return true; + }); + +assert.throws( + () => fixtureRequire('pkgexports/sub/./../asdf.js'), + (e) => { + assert.strictEqual(e.code, 'ERR_PATH_NOT_EXPORTED'); + return true; + }); From 8b29deb7ebddb4752287f58eee71d818b0527c8f Mon Sep 17 00:00:00 2001 From: Jan Krems Date: Tue, 23 Jul 2019 07:58:48 -0700 Subject: [PATCH 2/6] fixup! module: implement "exports" proposal for CommonJS --- lib/internal/modules/cjs/loader.js | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/lib/internal/modules/cjs/loader.js b/lib/internal/modules/cjs/loader.js index 46f1c6d76bfcde..8d8b4b7171dbcc 100644 --- a/lib/internal/modules/cjs/loader.js +++ b/lib/internal/modules/cjs/loader.js @@ -21,7 +21,14 @@ 'use strict'; -const { JSON, Object, Reflect } = primordials; +const { + ArrayPrototype, + JSON, + Object, + Reflect, + SafeMap, + StringPrototype, +} = primordials; const { NativeModule } = require('internal/bootstrap/loaders'); const { pathToFileURL, fileURLToPath, URL } = require('internal/url'); @@ -185,7 +192,7 @@ Module._debug = deprecate(debug, 'Module._debug is deprecated.', 'DEP0077'); // Check if the directory is a package.json dir. const packageMainCache = Object.create(null); // Explicit exports from package.json files -const packageExportsCache = new Map(); +const packageExportsCache = new SafeMap(); function readPackageRaw(requestPath) { const jsonPath = path.resolve(requestPath, 'package.json'); @@ -335,7 +342,7 @@ function resolveExports(nmPath, request, absoluteRequest) { // The implementation's behavior is meant to mirror resolution in ESM. if (experimentalExports && !absoluteRequest) { const [, name, expansion] = - request.match(EXPORTS_PATTERN) || []; + StringPrototype.match(request, EXPORTS_PATTERN) || []; if (!name) { return path.resolve(nmPath, request); } @@ -355,17 +362,17 @@ function resolveExports(nmPath, request, absoluteRequest) { if (candidateKey[candidateKey.length - 1] !== '/') continue; if (candidateValue[candidateValue.length - 1] !== '/') continue; if (candidateKey.length > dirMatch.length && - mappingKey.startsWith(candidateKey)) { + StringPrototype.startsWith(mappingKey, candidateKey)) { dirMatch = candidateKey; } } if (dirMatch !== '') { const dirMapping = pkgExports[dirMatch]; - const remainder = mappingKey.slice(dirMatch.length); + const remainder = ArrayPrototype.slice(mappingKey, dirMatch.length); const expectedPrefix = path.resolve(basePath, dirMapping); const resolved = path.resolve(expectedPrefix, remainder); - if (resolved.startsWith(expectedPrefix)) { + if (StringPrototype.startsWith(resolved, expectedPrefix)) { return resolved; } } From 3316fea299ec21778afdab79ab73bf3a6ec83d80 Mon Sep 17 00:00:00 2001 From: Jan Krems Date: Tue, 23 Jul 2019 09:03:45 -0700 Subject: [PATCH 3/6] fixup! module: implement "exports" proposal for CommonJS --- doc/api/modules.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/doc/api/modules.md b/doc/api/modules.md index 71dca3f2b9070f..abac7eaf91d854 100644 --- a/doc/api/modules.md +++ b/doc/api/modules.md @@ -231,6 +231,10 @@ RESOLVE_BARE_SPECIFIER(DIR, X) 3. return DIR/X ``` +`"exports"` is only honored when loading a package "name" as defined above. Any +`"exports"` values within nested directories and packages must be declared by +the `package.json` responsible for the "name". + ## Caching From b7258f9b069b66b059da89dc8e951cc6a8307bcd Mon Sep 17 00:00:00 2001 From: Jan Krems Date: Tue, 23 Jul 2019 10:04:16 -0700 Subject: [PATCH 4/6] fixup! module: implement "exports" proposal for CommonJS --- lib/internal/modules/cjs/loader.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/internal/modules/cjs/loader.js b/lib/internal/modules/cjs/loader.js index 8d8b4b7171dbcc..6aff1b8176ab49 100644 --- a/lib/internal/modules/cjs/loader.js +++ b/lib/internal/modules/cjs/loader.js @@ -22,7 +22,6 @@ 'use strict'; const { - ArrayPrototype, JSON, Object, Reflect, @@ -369,7 +368,7 @@ function resolveExports(nmPath, request, absoluteRequest) { if (dirMatch !== '') { const dirMapping = pkgExports[dirMatch]; - const remainder = ArrayPrototype.slice(mappingKey, dirMatch.length); + const remainder = StringPrototype.slice(mappingKey, dirMatch.length); const expectedPrefix = path.resolve(basePath, dirMapping); const resolved = path.resolve(expectedPrefix, remainder); if (StringPrototype.startsWith(resolved, expectedPrefix)) { From bd68eaf555786f5638d0c5f852720d378c243acb Mon Sep 17 00:00:00 2001 From: Jan Olaf Krems Date: Tue, 23 Jul 2019 13:23:35 -0700 Subject: [PATCH 5/6] fixup! module: implement "exports" proposal for CommonJS Co-Authored-By: Guy Bedford --- lib/internal/modules/cjs/loader.js | 9 +++++---- test/es-module/test-esm-exports.mjs | 3 ++- test/fixtures/node_modules/pkgexports/package.json | 1 + test/fixtures/node_modules/pkgexports/sp ce.js | 3 +++ test/fixtures/pkgexports.mjs | 1 + test/parallel/test-module-package-exports.js | 2 ++ 6 files changed, 14 insertions(+), 5 deletions(-) create mode 100644 test/fixtures/node_modules/pkgexports/sp ce.js diff --git a/lib/internal/modules/cjs/loader.js b/lib/internal/modules/cjs/loader.js index 6aff1b8176ab49..bdd1dcc6414577 100644 --- a/lib/internal/modules/cjs/loader.js +++ b/lib/internal/modules/cjs/loader.js @@ -348,12 +348,13 @@ function resolveExports(nmPath, request, absoluteRequest) { const basePath = path.resolve(nmPath, name); const pkgExports = readExports(basePath); + const baseURL = `${pathToFileURL(basePath)}/`; if (pkgExports != null) { const mappingKey = `.${expansion}`; const mapping = pkgExports[mappingKey]; if (typeof mapping === 'string') { - return path.resolve(basePath, mapping); + return fileURLToPath(new URL(mapping, baseURL)); } let dirMatch = ''; @@ -369,10 +370,10 @@ function resolveExports(nmPath, request, absoluteRequest) { if (dirMatch !== '') { const dirMapping = pkgExports[dirMatch]; const remainder = StringPrototype.slice(mappingKey, dirMatch.length); - const expectedPrefix = path.resolve(basePath, dirMapping); - const resolved = path.resolve(expectedPrefix, remainder); + const expectedPrefix = new URL(dirMapping, baseURL).href; + const resolved = new URL(remainder, expectedPrefix).href; if (StringPrototype.startsWith(resolved, expectedPrefix)) { - return resolved; + return fileURLToPath(resolved); } } throw new ERR_PATH_NOT_EXPORTED(basePath, mappingKey); diff --git a/test/es-module/test-esm-exports.mjs b/test/es-module/test-esm-exports.mjs index 5852e7e7da4a19..38cd81511fe8ef 100644 --- a/test/es-module/test-esm-exports.mjs +++ b/test/es-module/test-esm-exports.mjs @@ -3,7 +3,7 @@ import { mustCall } from '../common/index.mjs'; import { ok, strictEqual } from 'assert'; -import { asdf, asdf2 } from '../fixtures/pkgexports.mjs'; +import { asdf, asdf2, space } from '../fixtures/pkgexports.mjs'; import { loadMissing, loadFromNumber, @@ -12,6 +12,7 @@ import { strictEqual(asdf, 'asdf'); strictEqual(asdf2, 'asdf'); +strictEqual(space, 'encoded path'); loadMissing().catch(mustCall((err) => { ok(err.message.toString().startsWith('Package exports')); diff --git a/test/fixtures/node_modules/pkgexports/package.json b/test/fixtures/node_modules/pkgexports/package.json index 459041121568d7..b0c8867bb46fd1 100644 --- a/test/fixtures/node_modules/pkgexports/package.json +++ b/test/fixtures/node_modules/pkgexports/package.json @@ -1,6 +1,7 @@ { "exports": { ".": "./asdf.js", + "./space": "./sp%20ce.js", "./asdf": "./asdf.js", "./valid-cjs": "./asdf.js", "./sub/": "./" diff --git a/test/fixtures/node_modules/pkgexports/sp ce.js b/test/fixtures/node_modules/pkgexports/sp ce.js new file mode 100644 index 00000000000000..570237506e4586 --- /dev/null +++ b/test/fixtures/node_modules/pkgexports/sp ce.js @@ -0,0 +1,3 @@ +'use strict'; + +module.exports = 'encoded path'; diff --git a/test/fixtures/pkgexports.mjs b/test/fixtures/pkgexports.mjs index 4d82ba0560ef11..8907ebcb0e253a 100644 --- a/test/fixtures/pkgexports.mjs +++ b/test/fixtures/pkgexports.mjs @@ -1,2 +1,3 @@ export { default as asdf } from 'pkgexports/asdf'; export { default as asdf2 } from 'pkgexports/sub/asdf.js'; +export { default as space } from 'pkgexports/space'; diff --git a/test/parallel/test-module-package-exports.js b/test/parallel/test-module-package-exports.js index d1e78aca0d85d4..a1b9879448c17b 100644 --- a/test/parallel/test-module-package-exports.js +++ b/test/parallel/test-module-package-exports.js @@ -16,6 +16,8 @@ assert.strictEqual(fixtureRequire('baz/index'), 'eye catcher'); assert.strictEqual(fixtureRequire('pkgexports/sub/asdf.js'), 'asdf'); +assert.strictEqual(fixtureRequire('pkgexports/space'), 'encoded path'); + assert.throws( () => fixtureRequire('pkgexports/not-a-known-entry'), (e) => { From 104af348170c79b9addb6ce33c026a2196cbd5f7 Mon Sep 17 00:00:00 2001 From: Jan Krems Date: Tue, 23 Jul 2019 14:43:39 -0700 Subject: [PATCH 6/6] fixup! module: implement "exports" proposal for CommonJS --- lib/internal/modules/cjs/loader.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/internal/modules/cjs/loader.js b/lib/internal/modules/cjs/loader.js index bdd1dcc6414577..c769ce535a8338 100644 --- a/lib/internal/modules/cjs/loader.js +++ b/lib/internal/modules/cjs/loader.js @@ -348,13 +348,12 @@ function resolveExports(nmPath, request, absoluteRequest) { const basePath = path.resolve(nmPath, name); const pkgExports = readExports(basePath); - const baseURL = `${pathToFileURL(basePath)}/`; if (pkgExports != null) { const mappingKey = `.${expansion}`; const mapping = pkgExports[mappingKey]; if (typeof mapping === 'string') { - return fileURLToPath(new URL(mapping, baseURL)); + return fileURLToPath(new URL(mapping, `${pathToFileURL(basePath)}/`)); } let dirMatch = ''; @@ -370,9 +369,10 @@ function resolveExports(nmPath, request, absoluteRequest) { if (dirMatch !== '') { const dirMapping = pkgExports[dirMatch]; const remainder = StringPrototype.slice(mappingKey, dirMatch.length); - const expectedPrefix = new URL(dirMapping, baseURL).href; + const expectedPrefix = + new URL(dirMapping, `${pathToFileURL(basePath)}/`); const resolved = new URL(remainder, expectedPrefix).href; - if (StringPrototype.startsWith(resolved, expectedPrefix)) { + if (StringPrototype.startsWith(resolved, expectedPrefix.href)) { return fileURLToPath(resolved); } }