Skip to content

Commit 45e4f82

Browse files
anonriglemire
authored andcommitted
src: move package resolver to c++
Co-authored-by: Daniel Lemire <daniel@lemire.me> PR-URL: #50322 Reviewed-By: Jacob Smith <jacob@frende.me> Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com> Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Geoffrey Booth <webadmin@geoffreybooth.com>
1 parent 0dd53da commit 45e4f82

26 files changed

+798
-347
lines changed

lib/internal/modules/cjs/loader.js

+14-15
Original file line numberDiff line numberDiff line change
@@ -427,7 +427,7 @@ ObjectDefineProperty(Module, '_readPackage', {
427427
* @param {string} originalPath The specifier passed to `require`
428428
*/
429429
function tryPackage(requestPath, exts, isMain, originalPath) {
430-
const pkg = _readPackage(requestPath).main;
430+
const { main: pkg, pjsonPath } = _readPackage(requestPath);
431431

432432
if (!pkg) {
433433
return tryExtensions(path.resolve(requestPath, 'index'), exts, isMain);
@@ -446,14 +446,13 @@ function tryPackage(requestPath, exts, isMain, originalPath) {
446446
'Please verify that the package.json has a valid "main" entry',
447447
);
448448
err.code = 'MODULE_NOT_FOUND';
449-
err.path = path.resolve(requestPath, 'package.json');
449+
err.path = pjsonPath;
450450
err.requestPath = originalPath;
451451
// TODO(BridgeAR): Add the requireStack as well.
452452
throw err;
453453
} else {
454-
const jsonPath = path.resolve(requestPath, 'package.json');
455454
process.emitWarning(
456-
`Invalid 'main' field in '${jsonPath}' of '${pkg}'. ` +
455+
`Invalid 'main' field in '${pjsonPath}' of '${pkg}'. ` +
457456
'Please either fix that or report it to the module author',
458457
'DeprecationWarning',
459458
'DEP0128',
@@ -539,28 +538,28 @@ function trySelfParentPath(parent) {
539538
function trySelf(parentPath, request) {
540539
if (!parentPath) { return false; }
541540

542-
const { data: pkg, path: pkgPath } = packageJsonReader.readPackageScope(parentPath);
543-
if (!pkg || pkg.exports == null || pkg.name === undefined) {
541+
const pkg = packageJsonReader.getNearestParentPackageJSON(parentPath);
542+
if (pkg?.data.exports === undefined || pkg.data.name === undefined) {
544543
return false;
545544
}
546545

547546
let expansion;
548-
if (request === pkg.name) {
547+
if (request === pkg.data.name) {
549548
expansion = '.';
550-
} else if (StringPrototypeStartsWith(request, `${pkg.name}/`)) {
551-
expansion = '.' + StringPrototypeSlice(request, pkg.name.length);
549+
} else if (StringPrototypeStartsWith(request, `${pkg.data.name}/`)) {
550+
expansion = '.' + StringPrototypeSlice(request, pkg.data.name.length);
552551
} else {
553552
return false;
554553
}
555554

556555
try {
557556
const { packageExportsResolve } = require('internal/modules/esm/resolve');
558557
return finalizeEsmResolution(packageExportsResolve(
559-
pathToFileURL(pkgPath + '/package.json'), expansion, pkg,
560-
pathToFileURL(parentPath), getCjsConditions()), parentPath, pkgPath);
558+
pathToFileURL(pkg.path + '/package.json'), expansion, pkg.data,
559+
pathToFileURL(parentPath), getCjsConditions()), parentPath, pkg.path);
561560
} catch (e) {
562561
if (e.code === 'ERR_MODULE_NOT_FOUND') {
563-
throw createEsmNotFoundErr(request, pkgPath + '/package.json');
562+
throw createEsmNotFoundErr(request, pkg.path + '/package.json');
564563
}
565564
throw e;
566565
}
@@ -1099,7 +1098,7 @@ Module._resolveFilename = function(request, parent, isMain, options) {
10991098

11001099
if (request[0] === '#' && (parent?.filename || parent?.id === '<repl>')) {
11011100
const parentPath = parent?.filename ?? process.cwd() + path.sep;
1102-
const pkg = packageJsonReader.readPackageScope(parentPath) || { __proto__: null };
1101+
const pkg = packageJsonReader.getNearestParentPackageJSON(parentPath) || { __proto__: null };
11031102
if (pkg.data?.imports != null) {
11041103
try {
11051104
const { packageImportsResolve } = require('internal/modules/esm/resolve');
@@ -1397,9 +1396,9 @@ Module._extensions['.js'] = function(module, filename) {
13971396
content = fs.readFileSync(filename, 'utf8');
13981397
}
13991398
if (StringPrototypeEndsWith(filename, '.js')) {
1400-
const pkg = packageJsonReader.readPackageScope(filename) || { __proto__: null };
1399+
const pkg = packageJsonReader.getNearestParentPackageJSON(filename);
14011400
// Function require shouldn't be used in ES modules.
1402-
if (pkg.data?.type === 'module') {
1401+
if (pkg?.data.type === 'module') {
14031402
// This is an error path because `require` of a `.js` file in a `"type": "module"` scope is not allowed.
14041403
const parent = moduleParentCache.get(module);
14051404
const parentPath = parent?.filename;

lib/internal/modules/esm/get_format.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ const {
1919
const experimentalNetworkImports =
2020
getOptionValue('--experimental-network-imports');
2121
const { containsModuleSyntax } = internalBinding('contextify');
22-
const { getPackageType } = require('internal/modules/esm/resolve');
22+
const { getPackageType } = require('internal/modules/esm/package_config');
2323
const { fileURLToPath } = require('internal/url');
2424
const { ERR_UNKNOWN_FILE_EXTENSION } = require('internal/errors').codes;
2525

lib/internal/modules/esm/module_job.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -228,7 +228,7 @@ class ModuleJob {
228228
const packageConfig =
229229
StringPrototypeStartsWith(this.module.url, 'file://') &&
230230
RegExpPrototypeExec(/\.js(\?[^#]*)?(#.*)?$/, this.module.url) !== null &&
231-
require('internal/modules/esm/resolve')
231+
require('internal/modules/esm/package_config')
232232
.getPackageScopeConfig(this.module.url);
233233
if (packageConfig.type === 'module') {
234234
e.message +=
+22-47
Original file line numberDiff line numberDiff line change
@@ -1,69 +1,44 @@
11
'use strict';
22

3-
const {
4-
StringPrototypeEndsWith,
5-
} = primordials;
6-
const { URL, fileURLToPath } = require('internal/url');
7-
const packageJsonReader = require('internal/modules/package_json_reader');
3+
const { ArrayIsArray } = primordials;
4+
const modulesBinding = internalBinding('modules');
5+
const { deserializePackageJSON } = require('internal/modules/package_json_reader');
86

9-
/**
10-
* @typedef {object} PackageConfig
11-
* @property {string} pjsonPath - The path to the package.json file.
12-
* @property {boolean} exists - Whether the package.json file exists.
13-
* @property {'none' | 'commonjs' | 'module'} type - The type of the package.
14-
* @property {string} [name] - The name of the package.
15-
* @property {string} [main] - The main entry point of the package.
16-
* @property {PackageTarget} [exports] - The exports configuration of the package.
17-
* @property {Record<string, string | Record<string, string>>} [imports] - The imports configuration of the package.
18-
*/
19-
/**
20-
* @typedef {string | string[] | Record<string, string | Record<string, string>>} PackageTarget
21-
*/
7+
// TODO(@anonrig): Merge this file with internal/esm/package_json_reader.js
228

239
/**
2410
* Returns the package configuration for the given resolved URL.
2511
* @param {URL | string} resolved - The resolved URL.
26-
* @returns {PackageConfig} - The package configuration.
12+
* @returns {import('typings/internalBinding/modules').PackageConfig} - The package configuration.
2713
*/
2814
function getPackageScopeConfig(resolved) {
29-
let packageJSONUrl = new URL('./package.json', resolved);
30-
while (true) {
31-
const packageJSONPath = packageJSONUrl.pathname;
32-
if (StringPrototypeEndsWith(packageJSONPath, 'node_modules/package.json')) {
33-
break;
34-
}
35-
const packageConfig = packageJsonReader.read(fileURLToPath(packageJSONUrl), {
36-
__proto__: null,
37-
specifier: resolved,
38-
isESM: true,
39-
});
40-
if (packageConfig.exists) {
41-
return packageConfig;
42-
}
43-
44-
const lastPackageJSONUrl = packageJSONUrl;
45-
packageJSONUrl = new URL('../package.json', packageJSONUrl);
15+
const result = modulesBinding.getPackageScopeConfig(`${resolved}`);
4616

47-
// Terminates at root where ../package.json equals ../../package.json
48-
// (can't just check "/package.json" for Windows support).
49-
if (packageJSONUrl.pathname === lastPackageJSONUrl.pathname) {
50-
break;
51-
}
17+
if (ArrayIsArray(result)) {
18+
return deserializePackageJSON(`${resolved}`, result, false /* checkIntegrity */);
5219
}
53-
const packageJSONPath = fileURLToPath(packageJSONUrl);
20+
21+
// This means that the response is a string
22+
// and it is the path to the package.json file
5423
return {
5524
__proto__: null,
56-
pjsonPath: packageJSONPath,
25+
pjsonPath: result,
5726
exists: false,
58-
main: undefined,
59-
name: undefined,
6027
type: 'none',
61-
exports: undefined,
62-
imports: undefined,
6328
};
6429
}
6530

31+
/**
32+
* Returns the package type for a given URL.
33+
* @param {URL} url - The URL to get the package type for.
34+
*/
35+
function getPackageType(url) {
36+
// TODO(@anonrig): Write a C++ function that returns only "type".
37+
return getPackageScopeConfig(url).type;
38+
}
39+
6640

6741
module.exports = {
6842
getPackageScopeConfig,
43+
getPackageType,
6944
};

lib/internal/modules/esm/resolve.js

+4-14
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,7 @@ const legacyMainResolveExtensionsIndexes = {
198198
* 4. TRY(pkg_url/index.js, pkg_url/index.json, pkg_url/index.node)
199199
* 5. NOT_FOUND
200200
* @param {URL} packageJSONUrl
201-
* @param {PackageConfig} packageConfig
201+
* @param {import('typings/internalBinding/modules').PackageConfig} packageConfig
202202
* @param {string | URL | undefined} base
203203
* @returns {URL}
204204
*/
@@ -502,7 +502,7 @@ function resolvePackageTarget(packageJSONUrl, target, subpath, packageSubpath,
502502
}
503503
return resolveResult;
504504
}
505-
if (lastException === undefined || lastException === null) {
505+
if (lastException == null) {
506506
return lastException;
507507
}
508508
throw lastException;
@@ -575,7 +575,7 @@ function isConditionalExportsMainSugar(exports, packageJSONUrl, base) {
575575
*/
576576
function packageExportsResolve(
577577
packageJSONUrl, packageSubpath, packageConfig, base, conditions) {
578-
let exports = packageConfig.exports;
578+
let { exports } = packageConfig;
579579
if (isConditionalExportsMainSugar(exports, packageJSONUrl, base)) {
580580
exports = { '.': exports };
581581
}
@@ -740,15 +740,6 @@ function packageImportsResolve(name, base, conditions) {
740740
throw importNotDefined(name, packageJSONUrl, base);
741741
}
742742

743-
/**
744-
* Returns the package type for a given URL.
745-
* @param {URL} url - The URL to get the package type for.
746-
*/
747-
function getPackageType(url) {
748-
const packageConfig = getPackageScopeConfig(url);
749-
return packageConfig.type;
750-
}
751-
752743
/**
753744
* Parse a package name from a specifier.
754745
* @param {string} specifier - The import specifier.
@@ -796,6 +787,7 @@ function parsePackageName(specifier, base) {
796787
* @returns {URL} - The resolved URL.
797788
*/
798789
function packageResolve(specifier, base, conditions) {
790+
// TODO(@anonrig): Move this to a C++ function.
799791
if (BuiltinModule.canBeRequiredWithoutScheme(specifier)) {
800792
return new URL('node:' + specifier);
801793
}
@@ -1179,8 +1171,6 @@ module.exports = {
11791171
decorateErrorWithCommonJSHints,
11801172
defaultResolve,
11811173
encodedSepRegEx,
1182-
getPackageScopeConfig,
1183-
getPackageType,
11841174
packageExportsResolve,
11851175
packageImportsResolve,
11861176
throwIfInvalidParentURL,

0 commit comments

Comments
 (0)