Skip to content

Commit

Permalink
esm: move package config helpers
Browse files Browse the repository at this point in the history
PR-URL: #43967
Reviewed-By: Jacob Smith <jacob@frende.me>
Reviewed-By: Michaël Zasso <targos@protonmail.com>
Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com>
  • Loading branch information
GeoffreyBooth authored and ruyadorno committed Aug 22, 2022
1 parent 8c2d19b commit e072c3a
Show file tree
Hide file tree
Showing 3 changed files with 153 additions and 109 deletions.
142 changes: 142 additions & 0 deletions lib/internal/modules/esm/package_config.js
@@ -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,
};
119 changes: 10 additions & 109 deletions lib/internal/modules/esm/resolve.js
Expand Up @@ -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'];
Expand All @@ -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) {
Expand Down Expand Up @@ -154,7 +149,6 @@ function getConditionsSet(conditions) {
}

const realpathCache = new SafeMap();
const packageJSONCache = new SafeMap(); /* string -> PackageConfig */

/**
* @param {string | URL} path
Expand All @@ -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}
Expand Down Expand Up @@ -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}
Expand Down Expand Up @@ -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);
Expand Down
1 change: 1 addition & 0 deletions test/parallel/test-bootstrap-modules.js
Expand Up @@ -85,6 +85,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',
Expand Down

0 comments on commit e072c3a

Please sign in to comment.