Skip to content

Commit ec7bf32

Browse files
GeoffreyBoothjuanarbol
authored andcommitted
esm: move package config helpers
PR-URL: #43967 Reviewed-By: Jacob Smith <[email protected]> Reviewed-By: Michaël Zasso <[email protected]> Reviewed-By: Antoine du Hamel <[email protected]>
1 parent ec1645b commit ec7bf32

File tree

3 files changed

+153
-109
lines changed

3 files changed

+153
-109
lines changed
+142
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
'use strict';
2+
3+
const {
4+
JSONParse,
5+
SafeMap,
6+
StringPrototypeEndsWith,
7+
} = primordials;
8+
const { URL, fileURLToPath } = require('internal/url');
9+
const {
10+
ERR_INVALID_PACKAGE_CONFIG,
11+
} = require('internal/errors').codes;
12+
13+
const packageJsonReader = require('internal/modules/package_json_reader');
14+
15+
16+
/**
17+
* @typedef {string | string[] | Record<string, unknown>} Exports
18+
* @typedef {'module' | 'commonjs'} PackageType
19+
* @typedef {{
20+
* pjsonPath: string,
21+
* exports?: ExportConfig,
22+
* name?: string,
23+
* main?: string,
24+
* type?: PackageType,
25+
* }} PackageConfig
26+
*/
27+
28+
/** @type {Map<string, PackageConfig>} */
29+
const packageJSONCache = new SafeMap();
30+
31+
32+
/**
33+
* @param {string} path
34+
* @param {string} specifier
35+
* @param {string | URL | undefined} base
36+
* @returns {PackageConfig}
37+
*/
38+
function getPackageConfig(path, specifier, base) {
39+
const existing = packageJSONCache.get(path);
40+
if (existing !== undefined) {
41+
return existing;
42+
}
43+
const source = packageJsonReader.read(path).string;
44+
if (source === undefined) {
45+
const packageConfig = {
46+
pjsonPath: path,
47+
exists: false,
48+
main: undefined,
49+
name: undefined,
50+
type: 'none',
51+
exports: undefined,
52+
imports: undefined,
53+
};
54+
packageJSONCache.set(path, packageConfig);
55+
return packageConfig;
56+
}
57+
58+
let packageJSON;
59+
try {
60+
packageJSON = JSONParse(source);
61+
} catch (error) {
62+
throw new ERR_INVALID_PACKAGE_CONFIG(
63+
path,
64+
(base ? `"${specifier}" from ` : '') + fileURLToPath(base || specifier),
65+
error.message
66+
);
67+
}
68+
69+
let { imports, main, name, type } = packageJSON;
70+
const { exports } = packageJSON;
71+
if (typeof imports !== 'object' || imports === null) {
72+
imports = undefined;
73+
}
74+
if (typeof main !== 'string') {
75+
main = undefined;
76+
}
77+
if (typeof name !== 'string') {
78+
name = undefined;
79+
}
80+
// Ignore unknown types for forwards compatibility
81+
if (type !== 'module' && type !== 'commonjs') {
82+
type = 'none';
83+
}
84+
85+
const packageConfig = {
86+
pjsonPath: path,
87+
exists: true,
88+
main,
89+
name,
90+
type,
91+
exports,
92+
imports,
93+
};
94+
packageJSONCache.set(path, packageConfig);
95+
return packageConfig;
96+
}
97+
98+
99+
/**
100+
* @param {URL | string} resolved
101+
* @returns {PackageConfig}
102+
*/
103+
function getPackageScopeConfig(resolved) {
104+
let packageJSONUrl = new URL('./package.json', resolved);
105+
while (true) {
106+
const packageJSONPath = packageJSONUrl.pathname;
107+
if (StringPrototypeEndsWith(packageJSONPath, 'node_modules/package.json')) {
108+
break;
109+
}
110+
const packageConfig = getPackageConfig(fileURLToPath(packageJSONUrl), resolved);
111+
if (packageConfig.exists) {
112+
return packageConfig;
113+
}
114+
115+
const lastPackageJSONUrl = packageJSONUrl;
116+
packageJSONUrl = new URL('../package.json', packageJSONUrl);
117+
118+
// Terminates at root where ../package.json equals ../../package.json
119+
// (can't just check "/package.json" for Windows support).
120+
if (packageJSONUrl.pathname === lastPackageJSONUrl.pathname) {
121+
break;
122+
}
123+
}
124+
const packageJSONPath = fileURLToPath(packageJSONUrl);
125+
const packageConfig = {
126+
pjsonPath: packageJSONPath,
127+
exists: false,
128+
main: undefined,
129+
name: undefined,
130+
type: 'none',
131+
exports: undefined,
132+
imports: undefined,
133+
};
134+
packageJSONCache.set(packageJSONPath, packageConfig);
135+
return packageConfig;
136+
}
137+
138+
139+
module.exports = {
140+
getPackageConfig,
141+
getPackageScopeConfig,
142+
};

lib/internal/modules/esm/resolve.js

+10-109
Original file line numberDiff line numberDiff line change
@@ -59,9 +59,16 @@ const {
5959
ERR_NETWORK_IMPORT_DISALLOWED,
6060
ERR_UNSUPPORTED_ESM_URL_SCHEME,
6161
} = require('internal/errors').codes;
62-
const { Module: CJSModule } = require('internal/modules/cjs/loader');
6362

63+
const { Module: CJSModule } = require('internal/modules/cjs/loader');
6464
const packageJsonReader = require('internal/modules/package_json_reader');
65+
const { getPackageConfig, getPackageScopeConfig } = require('internal/modules/esm/package_config');
66+
67+
/**
68+
* @typedef {import('internal/modules/esm/package_config.js').PackageConfig} PackageConfig
69+
*/
70+
71+
6572
const userConditions = getOptionValue('--conditions');
6673
const noAddons = getOptionValue('--no-addons');
6774
const addonConditions = noAddons ? [] : ['node-addons'];
@@ -75,18 +82,6 @@ const DEFAULT_CONDITIONS = ObjectFreeze([
7582

7683
const DEFAULT_CONDITIONS_SET = new SafeSet(DEFAULT_CONDITIONS);
7784

78-
/**
79-
* @typedef {string | string[] | Record<string, unknown>} Exports
80-
* @typedef {'module' | 'commonjs'} PackageType
81-
* @typedef {{
82-
* pjsonPath: string,
83-
* exports?: ExportConfig,
84-
* name?: string,
85-
* main?: string,
86-
* type?: PackageType,
87-
* }} PackageConfig
88-
*/
89-
9085
const emittedPackageWarnings = new SafeSet();
9186

9287
/**
@@ -179,7 +174,6 @@ function getConditionsSet(conditions) {
179174
}
180175

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

184178
/**
185179
* @param {string | URL} path
@@ -188,99 +182,6 @@ const packageJSONCache = new SafeMap(); /* string -> PackageConfig */
188182
const tryStatSync =
189183
(path) => statSync(path, { throwIfNoEntry: false }) ?? new Stats();
190184

191-
/**
192-
* @param {string} path
193-
* @param {string} specifier
194-
* @param {string | URL | undefined} base
195-
* @returns {PackageConfig}
196-
*/
197-
function getPackageConfig(path, specifier, base) {
198-
const existing = packageJSONCache.get(path);
199-
if (existing !== undefined) {
200-
return existing;
201-
}
202-
const source = packageJsonReader.read(path).string;
203-
if (source === undefined) {
204-
const packageConfig = {
205-
pjsonPath: path,
206-
exists: false,
207-
main: undefined,
208-
name: undefined,
209-
type: 'none',
210-
exports: undefined,
211-
imports: undefined,
212-
};
213-
packageJSONCache.set(path, packageConfig);
214-
return packageConfig;
215-
}
216-
217-
let packageJSON;
218-
try {
219-
packageJSON = JSONParse(source);
220-
} catch (error) {
221-
throw new ERR_INVALID_PACKAGE_CONFIG(
222-
path,
223-
(base ? `"${specifier}" from ` : '') + fileURLToPath(base || specifier),
224-
error.message
225-
);
226-
}
227-
228-
let { imports, main, name, type } = packageJSON;
229-
const { exports } = packageJSON;
230-
if (typeof imports !== 'object' || imports === null) imports = undefined;
231-
if (typeof main !== 'string') main = undefined;
232-
if (typeof name !== 'string') name = undefined;
233-
// Ignore unknown types for forwards compatibility
234-
if (type !== 'module' && type !== 'commonjs') type = 'none';
235-
236-
const packageConfig = {
237-
pjsonPath: path,
238-
exists: true,
239-
main,
240-
name,
241-
type,
242-
exports,
243-
imports,
244-
};
245-
packageJSONCache.set(path, packageConfig);
246-
return packageConfig;
247-
}
248-
249-
/**
250-
* @param {URL | string} resolved
251-
* @returns {PackageConfig}
252-
*/
253-
function getPackageScopeConfig(resolved) {
254-
let packageJSONUrl = new URL('./package.json', resolved);
255-
while (true) {
256-
const packageJSONPath = packageJSONUrl.pathname;
257-
if (StringPrototypeEndsWith(packageJSONPath, 'node_modules/package.json'))
258-
break;
259-
const packageConfig = getPackageConfig(fileURLToPath(packageJSONUrl),
260-
resolved);
261-
if (packageConfig.exists) return packageConfig;
262-
263-
const lastPackageJSONUrl = packageJSONUrl;
264-
packageJSONUrl = new URL('../package.json', packageJSONUrl);
265-
266-
// Terminates at root where ../package.json equals ../../package.json
267-
// (can't just check "/package.json" for Windows support).
268-
if (packageJSONUrl.pathname === lastPackageJSONUrl.pathname) break;
269-
}
270-
const packageJSONPath = fileURLToPath(packageJSONUrl);
271-
const packageConfig = {
272-
pjsonPath: packageJSONPath,
273-
exists: false,
274-
main: undefined,
275-
name: undefined,
276-
type: 'none',
277-
exports: undefined,
278-
imports: undefined,
279-
};
280-
packageJSONCache.set(packageJSONPath, packageConfig);
281-
return packageConfig;
282-
}
283-
284185
/**
285186
* @param {string | URL} url
286187
* @returns {boolean}
@@ -634,7 +535,7 @@ function resolvePackageTarget(packageJSONUrl, target, subpath, packageSubpath,
634535

635536
/**
636537
*
637-
* @param {Exports} exports
538+
* @param {import('internal/modules/esm/package_config.js').Exports} exports
638539
* @param {URL} packageJSONUrl
639540
* @param {string | URL | undefined} base
640541
* @returns {boolean}
@@ -838,7 +739,7 @@ function packageImportsResolve(name, base, conditions) {
838739

839740
/**
840741
* @param {URL} url
841-
* @returns {PackageType}
742+
* @returns {import('internal/modules/esm/package_config.js').PackageType}
842743
*/
843744
function getPackageType(url) {
844745
const packageConfig = getPackageScopeConfig(url);

test/parallel/test-bootstrap-modules.js

+1
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ const expectedModules = new Set([
8484
'NativeModule internal/modules/esm/loader',
8585
'NativeModule internal/modules/esm/module_job',
8686
'NativeModule internal/modules/esm/module_map',
87+
'NativeModule internal/modules/esm/package_config',
8788
'NativeModule internal/modules/esm/resolve',
8889
'NativeModule internal/modules/esm/translators',
8990
'NativeModule internal/modules/package_json_reader',

0 commit comments

Comments
 (0)