esm: move package config helpers

PR-URL: https://github.com/nodejs/node/pull/43967
Reviewed-By: Jacob Smith <jacob@frende.me>
Reviewed-By: Michaël Zasso <targos@protonmail.com>
Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com>
This commit is contained in:
Geoffrey Booth 2022-07-25 21:08:18 -07:00 committed by GitHub
parent da2764bda3
commit 7ef40322b7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 153 additions and 109 deletions

View File

@ -0,0 +1,142 @@
'use strict';
const {
JSONParse,
SafeMap,
StringPrototypeEndsWith,
} = primordials;
const { URL, fileURLToPath } = require('internal/url');
const {
ERR_INVALID_PACKAGE_CONFIG,
} = require('internal/errors').codes;
const packageJsonReader = require('internal/modules/package_json_reader');
/**
* @typedef {string | string[] | Record<string, unknown>} Exports
* @typedef {'module' | 'commonjs'} PackageType
* @typedef {{
* pjsonPath: string,
* exports?: ExportConfig,
* name?: string,
* main?: string,
* type?: PackageType,
* }} PackageConfig
*/
/** @type {Map<string, PackageConfig>} */
const packageJSONCache = new SafeMap();
/**
* @param {string} path
* @param {string} specifier
* @param {string | URL | undefined} base
* @returns {PackageConfig}
*/
function getPackageConfig(path, specifier, base) {
const existing = packageJSONCache.get(path);
if (existing !== undefined) {
return existing;
}
const source = packageJsonReader.read(path).string;
if (source === undefined) {
const packageConfig = {
pjsonPath: path,
exists: false,
main: undefined,
name: undefined,
type: 'none',
exports: undefined,
imports: undefined,
};
packageJSONCache.set(path, packageConfig);
return packageConfig;
}
let packageJSON;
try {
packageJSON = JSONParse(source);
} catch (error) {
throw new ERR_INVALID_PACKAGE_CONFIG(
path,
(base ? `"${specifier}" from ` : '') + fileURLToPath(base || specifier),
error.message
);
}
let { imports, main, name, type } = packageJSON;
const { exports } = packageJSON;
if (typeof imports !== 'object' || imports === null) {
imports = undefined;
}
if (typeof main !== 'string') {
main = undefined;
}
if (typeof name !== 'string') {
name = undefined;
}
// Ignore unknown types for forwards compatibility
if (type !== 'module' && type !== 'commonjs') {
type = 'none';
}
const packageConfig = {
pjsonPath: path,
exists: true,
main,
name,
type,
exports,
imports,
};
packageJSONCache.set(path, packageConfig);
return packageConfig;
}
/**
* @param {URL | string} resolved
* @returns {PackageConfig}
*/
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 = getPackageConfig(fileURLToPath(packageJSONUrl), resolved);
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);
const packageConfig = {
pjsonPath: packageJSONPath,
exists: false,
main: undefined,
name: undefined,
type: 'none',
exports: undefined,
imports: undefined,
};
packageJSONCache.set(packageJSONPath, packageConfig);
return packageConfig;
}
module.exports = {
getPackageConfig,
getPackageScopeConfig,
};

View File

@ -58,9 +58,16 @@ const {
ERR_NETWORK_IMPORT_DISALLOWED,
ERR_UNSUPPORTED_ESM_URL_SCHEME,
} = require('internal/errors').codes;
const { Module: CJSModule } = require('internal/modules/cjs/loader');
const { Module: CJSModule } = require('internal/modules/cjs/loader');
const packageJsonReader = require('internal/modules/package_json_reader');
const { getPackageConfig, getPackageScopeConfig } = require('internal/modules/esm/package_config');
/**
* @typedef {import('internal/modules/esm/package_config.js').PackageConfig} PackageConfig
*/
const userConditions = getOptionValue('--conditions');
const noAddons = getOptionValue('--no-addons');
const addonConditions = noAddons ? [] : ['node-addons'];
@ -74,18 +81,6 @@ const DEFAULT_CONDITIONS = ObjectFreeze([
const DEFAULT_CONDITIONS_SET = new SafeSet(DEFAULT_CONDITIONS);
/**
* @typedef {string | string[] | Record<string, unknown>} Exports
* @typedef {'module' | 'commonjs'} PackageType
* @typedef {{
* pjsonPath: string,
* exports?: ExportConfig,
* name?: string,
* main?: string,
* type?: PackageType,
* }} PackageConfig
*/
const emittedPackageWarnings = new SafeSet();
function emitTrailingSlashPatternDeprecation(match, pjsonUrl, base) {
@ -154,7 +149,6 @@ function getConditionsSet(conditions) {
}
const realpathCache = new SafeMap();
const packageJSONCache = new SafeMap(); /* string -> PackageConfig */
/**
* @param {string | URL} path
@ -163,99 +157,6 @@ const packageJSONCache = new SafeMap(); /* string -> PackageConfig */
const tryStatSync =
(path) => statSync(path, { throwIfNoEntry: false }) ?? new Stats();
/**
* @param {string} path
* @param {string} specifier
* @param {string | URL | undefined} base
* @returns {PackageConfig}
*/
function getPackageConfig(path, specifier, base) {
const existing = packageJSONCache.get(path);
if (existing !== undefined) {
return existing;
}
const source = packageJsonReader.read(path).string;
if (source === undefined) {
const packageConfig = {
pjsonPath: path,
exists: false,
main: undefined,
name: undefined,
type: 'none',
exports: undefined,
imports: undefined,
};
packageJSONCache.set(path, packageConfig);
return packageConfig;
}
let packageJSON;
try {
packageJSON = JSONParse(source);
} catch (error) {
throw new ERR_INVALID_PACKAGE_CONFIG(
path,
(base ? `"${specifier}" from ` : '') + fileURLToPath(base || specifier),
error.message
);
}
let { imports, main, name, type } = packageJSON;
const { exports } = packageJSON;
if (typeof imports !== 'object' || imports === null) imports = undefined;
if (typeof main !== 'string') main = undefined;
if (typeof name !== 'string') name = undefined;
// Ignore unknown types for forwards compatibility
if (type !== 'module' && type !== 'commonjs') type = 'none';
const packageConfig = {
pjsonPath: path,
exists: true,
main,
name,
type,
exports,
imports,
};
packageJSONCache.set(path, packageConfig);
return packageConfig;
}
/**
* @param {URL | string} resolved
* @returns {PackageConfig}
*/
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 = getPackageConfig(fileURLToPath(packageJSONUrl),
resolved);
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);
const packageConfig = {
pjsonPath: packageJSONPath,
exists: false,
main: undefined,
name: undefined,
type: 'none',
exports: undefined,
imports: undefined,
};
packageJSONCache.set(packageJSONPath, packageConfig);
return packageConfig;
}
/**
* @param {string | URL} url
* @returns {boolean}
@ -609,7 +510,7 @@ function resolvePackageTarget(packageJSONUrl, target, subpath, packageSubpath,
/**
*
* @param {Exports} exports
* @param {import('internal/modules/esm/package_config.js').Exports} exports
* @param {URL} packageJSONUrl
* @param {string | URL | undefined} base
* @returns {boolean}
@ -799,7 +700,7 @@ function packageImportsResolve(name, base, conditions) {
/**
* @param {URL} url
* @returns {PackageType}
* @returns {import('internal/modules/esm/package_config.js').PackageType}
*/
function getPackageType(url) {
const packageConfig = getPackageScopeConfig(url);

View File

@ -84,6 +84,7 @@ const expectedModules = new Set([
'NativeModule internal/modules/esm/loader',
'NativeModule internal/modules/esm/module_job',
'NativeModule internal/modules/esm/module_map',
'NativeModule internal/modules/esm/package_config',
'NativeModule internal/modules/esm/resolve',
'NativeModule internal/modules/esm/translators',
'NativeModule internal/modules/package_json_reader',