Skip to content

Commit

Permalink
src: move package resolver to c++
Browse files Browse the repository at this point in the history
  • Loading branch information
anonrig committed Oct 31, 2023
1 parent f001bac commit 7f54836
Show file tree
Hide file tree
Showing 26 changed files with 672 additions and 281 deletions.
8 changes: 4 additions & 4 deletions lib/internal/modules/cjs/loader.js
Original file line number Diff line number Diff line change
Expand Up @@ -539,8 +539,8 @@ function trySelfParentPath(parent) {
function trySelf(parentPath, request) {
if (!parentPath) { return false; }

const { data: pkg, path: pkgPath } = packageJsonReader.readPackageScope(parentPath);
if (!pkg || pkg.exports == null || pkg.name === undefined) {
const { data: pkg, path: pkgPath } = packageJsonReader.getNearestParentPackageJSON(parentPath);
if (pkg?.exports === undefined || pkg.name === undefined) {
return false;
}

Expand Down Expand Up @@ -1099,7 +1099,7 @@ Module._resolveFilename = function(request, parent, isMain, options) {

if (request[0] === '#' && (parent?.filename || parent?.id === '<repl>')) {
const parentPath = parent?.filename ?? process.cwd() + path.sep;
const pkg = packageJsonReader.readPackageScope(parentPath) || { __proto__: null };
const pkg = packageJsonReader.getNearestParentPackageJSON(parentPath) || { __proto__: null };
if (pkg.data?.imports != null) {
try {
const { packageImportsResolve } = require('internal/modules/esm/resolve');
Expand Down Expand Up @@ -1397,7 +1397,7 @@ Module._extensions['.js'] = function(module, filename) {
content = fs.readFileSync(filename, 'utf8');
}
if (StringPrototypeEndsWith(filename, '.js')) {
const pkg = packageJsonReader.readPackageScope(filename) || { __proto__: null };
const pkg = packageJsonReader.getNearestParentPackageJSON(filename) || { __proto__: null };
// Function require shouldn't be used in ES modules.
if (pkg.data?.type === 'module') {
// This is an error path because `require` of a `.js` file in a `"type": "module"` scope is not allowed.
Expand Down
2 changes: 1 addition & 1 deletion lib/internal/modules/esm/get_format.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ const {
const experimentalNetworkImports =
getOptionValue('--experimental-network-imports');
const { containsModuleSyntax } = internalBinding('contextify');
const { getPackageType } = require('internal/modules/esm/resolve');
const { getPackageType } = require('internal/modules/esm/package_config');
const { fileURLToPath } = require('internal/url');
const { ERR_UNKNOWN_FILE_EXTENSION } = require('internal/errors').codes;

Expand Down
2 changes: 1 addition & 1 deletion lib/internal/modules/esm/module_job.js
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,7 @@ class ModuleJob {
const packageConfig =
StringPrototypeStartsWith(this.module.url, 'file://') &&
RegExpPrototypeExec(/\.js(\?[^#]*)?(#.*)?$/, this.module.url) !== null &&
require('internal/modules/esm/resolve')
require('internal/modules/esm/package_config')
.getPackageScopeConfig(this.module.url);
if (packageConfig.type === 'module') {
e.message +=
Expand Down
89 changes: 42 additions & 47 deletions lib/internal/modules/esm/package_config.js
Original file line number Diff line number Diff line change
@@ -1,69 +1,64 @@
'use strict';

const {
StringPrototypeEndsWith,
} = primordials;
const { URL, fileURLToPath } = require('internal/url');
const packageJsonReader = require('internal/modules/package_json_reader');

/**
* @typedef {object} PackageConfig
* @property {string} pjsonPath - The path to the package.json file.
* @property {boolean} exists - Whether the package.json file exists.
* @property {'none' | 'commonjs' | 'module'} type - The type of the package.
* @property {string} [name] - The name of the package.
* @property {string} [main] - The main entry point of the package.
* @property {PackageTarget} [exports] - The exports configuration of the package.
* @property {Record<string, string | Record<string, string>>} [imports] - The imports configuration of the package.
*/
/**
* @typedef {string | string[] | Record<string, string | Record<string, string>>} PackageTarget
*/
const { ArrayIsArray } = primordials;
const { toNamespacedPath } = require('path');
const modulesBinding = internalBinding('modules');

/**
* Returns the package configuration for the given resolved URL.
* @param {URL | string} resolved - The resolved URL.
* @returns {PackageConfig} - The package configuration.
* @returns {import('typings/internalBinding/modules').PackageConfig} - The package configuration.
*/
function getPackageScopeConfig(resolved) {
let packageJSONUrl = new URL('./package.json', resolved);
while (true) {
const packageJSONPath = packageJSONUrl.pathname;
if (StringPrototypeEndsWith(packageJSONPath, 'node_modules/package.json')) {
break;
}
const packageConfig = packageJsonReader.read(fileURLToPath(packageJSONUrl), {
__proto__: null,
specifier: resolved,
isESM: true,
});
if (packageConfig.exists) {
return packageConfig;
}

const lastPackageJSONUrl = packageJSONUrl;
packageJSONUrl = new URL('../package.json', packageJSONUrl);

// Terminates at root where ../package.json equals ../../package.json
// (can't just check "/package.json" for Windows support).
if (packageJSONUrl.pathname === lastPackageJSONUrl.pathname) {
break;
}
}
const packageJSONPath = fileURLToPath(packageJSONUrl);
return {
const packageScopeConfig = modulesBinding.getPackageScopeConfig(toNamespacedPath(`${resolved}`));
const response = {
__proto__: null,
pjsonPath: packageJSONPath,
pjsonPath: undefined,
exists: false,
main: undefined,
name: undefined,
type: 'none',
exports: undefined,
imports: undefined,
};
if (ArrayIsArray(packageScopeConfig)) {
const {
0: name,
1: main,
2: type,
3: imports,
4: exports,
5: _manifest, // eslint-disable-line no-unused-vars
6: filePath,
} = packageScopeConfig;
response.name = name;
response.main = main;
response.type = type;
response.imports = imports;
response.exports = exports;
response.exists = true;
response.pjsonPath = filePath;
} else {
// This means that the response is a string
// and it is the path to the package.json file
response.pjsonPath = packageScopeConfig;
}

return response;
}

/**
* Returns the package type for a given URL.
* @param {URL} url - The URL to get the package type for.
*/
function getPackageType(url) {
// TODO(@anonrig): Write a C++ function that returns only "type".
const packageConfig = getPackageScopeConfig(url);
return packageConfig.type;
}


module.exports = {
getPackageScopeConfig,
getPackageType,
};
27 changes: 14 additions & 13 deletions lib/internal/modules/esm/resolve.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ const {
ArrayIsArray,
ArrayPrototypeJoin,
ArrayPrototypeShift,
JSONParse,
JSONStringify,
ObjectGetOwnPropertyNames,
ObjectPrototypeHasOwnProperty,
Expand Down Expand Up @@ -198,7 +199,7 @@ const legacyMainResolveExtensionsIndexes = {
* 4. TRY(pkg_url/index.js, pkg_url/index.json, pkg_url/index.node)
* 5. NOT_FOUND
* @param {URL} packageJSONUrl
* @param {PackageConfig} packageConfig
* @param {import('typings/internalBinding/modules').PackageConfig} packageConfig
* @param {string | URL | undefined} base
* @returns {URL}
*/
Expand Down Expand Up @@ -576,6 +577,11 @@ function isConditionalExportsMainSugar(exports, packageJSONUrl, base) {
function packageExportsResolve(
packageJSONUrl, packageSubpath, packageConfig, base, conditions) {
let exports = packageConfig.exports;
// JSONParse is required because we don't parse imports and exports
// fields in package.json files.
if (exports[0] === '[' || exports[0] === '{') {
exports = JSONParse(exports);
}
if (isConditionalExportsMainSugar(exports, packageJSONUrl, base)) {
exports = { '.': exports };
}
Expand Down Expand Up @@ -690,8 +696,13 @@ function packageImportsResolve(name, base, conditions) {
const packageConfig = getPackageScopeConfig(base);
if (packageConfig.exists) {
packageJSONUrl = pathToFileURL(packageConfig.pjsonPath);
const imports = packageConfig.imports;
let imports = packageConfig.imports;
if (imports) {
// JSONParse is required because we don't parse imports and exports
// fields in package.json files.
if (imports[0] === '[' || imports[0] === '{') {
imports = JSONParse(imports);
}
if (ObjectPrototypeHasOwnProperty(imports, name) &&
!StringPrototypeIncludes(name, '*')) {
const resolveResult = resolvePackageTarget(
Expand Down Expand Up @@ -740,15 +751,6 @@ function packageImportsResolve(name, base, conditions) {
throw importNotDefined(name, packageJSONUrl, base);
}

/**
* Returns the package type for a given URL.
* @param {URL} url - The URL to get the package type for.
*/
function getPackageType(url) {
const packageConfig = getPackageScopeConfig(url);
return packageConfig.type;
}

/**
* Parse a package name from a specifier.
* @param {string} specifier - The import specifier.
Expand Down Expand Up @@ -796,6 +798,7 @@ function parsePackageName(specifier, base) {
* @returns {URL} - The resolved URL.
*/
function packageResolve(specifier, base, conditions) {
// TODO(@anonrig): Move this to a C++ function.
if (BuiltinModule.canBeRequiredWithoutScheme(specifier)) {
return new URL('node:' + specifier);
}
Expand Down Expand Up @@ -1179,8 +1182,6 @@ module.exports = {
decorateErrorWithCommonJSHints,
defaultResolve,
encodedSepRegEx,
getPackageScopeConfig,
getPackageType,
packageExportsResolve,
packageImportsResolve,
throwIfInvalidParentURL,
Expand Down
Loading

0 comments on commit 7f54836

Please sign in to comment.