Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit adda37f

Browse filesBrowse files
JakobJingleheimerRafaelGSS
authored andcommitted
module: add findPackageJSON util
PR-URL: #55412 Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com>
1 parent 2d19614 commit adda37f
Copy full SHA for adda37f

File tree

Expand file treeCollapse file tree

17 files changed

+516
-151
lines changed
Open diff view settings
Filter options
Expand file treeCollapse file tree

17 files changed

+516
-151
lines changed
Open diff view settings
Collapse file

‎doc/api/module.md‎

Copy file name to clipboardExpand all lines: doc/api/module.md
+82Lines changed: 82 additions & 0 deletions
  • Display the source diff
  • Display the rich diff
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,88 @@ added: v22.8.0
217217
* Returns: {string|undefined} Path to the [module compile cache][] directory if it is enabled,
218218
or `undefined` otherwise.
219219
220+
### `module.findPackageJSON(specifier[, base])`
221+
222+
<!-- YAML
223+
added: REPLACEME
224+
-->
225+
226+
> Stability: 1.1 - Active Development
227+
228+
* `specifier` {string|URL} The specifier for the module whose `package.json` to
229+
retrieve. When passing a _bare specifier_, the `package.json` at the root of
230+
the package is returned. When passing a _relative specifier_ or an _absolute specifier_,
231+
the closest parent `package.json` is returned.
232+
* `base` {string|URL} The absolute location (`file:` URL string or FS path) of the
233+
containing module. For CJS, use `__filename` (not `__dirname`!); for ESM, use
234+
`import.meta.url`. You do not need to pass it if `specifier` is an `absolute specifier`.
235+
* Returns: {string|undefined} A path if the `package.json` is found. When `startLocation`
236+
is a package, the package's root `package.json`; when a relative or unresolved, the closest
237+
`package.json` to the `startLocation`.
238+
239+
> **Caveat**: Do not use this to try to determine module format. There are many things effecting
240+
> that determination; the `type` field of package.json is the _least_ definitive (ex file extension
241+
> superceeds it, and a loader hook superceeds that).
242+
243+
```text
244+
/path/to/project
245+
├ packages/
246+
├ bar/
247+
bar.js
248+
package.json // name = '@foo/bar'
249+
└ qux/
250+
├ node_modules/
251+
└ some-package/
252+
package.json // name = 'some-package'
253+
qux.js
254+
package.json // name = '@foo/qux'
255+
main.js
256+
package.json // name = '@foo'
257+
```
258+
259+
```mjs
260+
// /path/to/project/packages/bar/bar.js
261+
import { findPackageJSON } from 'node:module';
262+
263+
findPackageJSON('..', import.meta.url);
264+
// '/path/to/project/package.json'
265+
// Same result when passing an absolute specifier instead:
266+
findPackageJSON(new URL('../', import.meta.url));
267+
findPackageJSON(import.meta.resolve('../'));
268+
269+
findPackageJSON('some-package', import.meta.url);
270+
// '/path/to/project/packages/bar/node_modules/some-package/package.json'
271+
// When passing an absolute specifier, you might get a different result if the
272+
// resolved module is inside a subfolder that has nested `package.json`.
273+
findPackageJSON(import.meta.resolve('some-package'));
274+
// '/path/to/project/packages/bar/node_modules/some-package/some-subfolder/package.json'
275+
276+
findPackageJSON('@foo/qux', import.meta.url);
277+
// '/path/to/project/packages/qux/package.json'
278+
```
279+
280+
```cjs
281+
// /path/to/project/packages/bar/bar.js
282+
const { findPackageJSON } = require('node:module');
283+
const { pathToFileURL } = require('node:url');
284+
const path = require('node:path');
285+
286+
findPackageJSON('..', __filename);
287+
// '/path/to/project/package.json'
288+
// Same result when passing an absolute specifier instead:
289+
findPackageJSON(pathToFileURL(path.join(__dirname, '..')));
290+
291+
findPackageJSON('some-package', __filename);
292+
// '/path/to/project/packages/bar/node_modules/some-package/package.json'
293+
// When passing an absolute specifier, you might get a different result if the
294+
// resolved module is inside a subfolder that has nested `package.json`.
295+
findPackageJSON(pathToFileURL(require.resolve('some-package')));
296+
// '/path/to/project/packages/bar/node_modules/some-package/some-subfolder/package.json'
297+
298+
findPackageJSON('@foo/qux', __filename);
299+
// '/path/to/project/packages/qux/package.json'
300+
```
301+
220302
### `module.isBuiltin(moduleName)`
221303
222304
<!-- YAML
Collapse file

‎lib/internal/modules/cjs/loader.js‎

Copy file name to clipboardExpand all lines: lib/internal/modules/cjs/loader.js
+11-11Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -602,11 +602,11 @@ function trySelf(parentPath, request) {
602602
try {
603603
const { packageExportsResolve } = require('internal/modules/esm/resolve');
604604
return finalizeEsmResolution(packageExportsResolve(
605-
pathToFileURL(pkg.path + '/package.json'), expansion, pkg.data,
605+
pathToFileURL(pkg.path), expansion, pkg.data,
606606
pathToFileURL(parentPath), getCjsConditions()), parentPath, pkg.path);
607607
} catch (e) {
608608
if (e.code === 'ERR_MODULE_NOT_FOUND') {
609-
throw createEsmNotFoundErr(request, pkg.path + '/package.json');
609+
throw createEsmNotFoundErr(request, pkg.path);
610610
}
611611
throw e;
612612
}
@@ -1201,14 +1201,15 @@ Module._resolveFilename = function(request, parent, isMain, options) {
12011201

12021202
if (request[0] === '#' && (parent?.filename || parent?.id === '<repl>')) {
12031203
const parentPath = parent?.filename ?? process.cwd() + path.sep;
1204-
const pkg = packageJsonReader.getNearestParentPackageJSON(parentPath) || { __proto__: null };
1205-
if (pkg.data?.imports != null) {
1204+
const pkg = packageJsonReader.getNearestParentPackageJSON(parentPath);
1205+
if (pkg?.data.imports != null) {
12061206
try {
12071207
const { packageImportsResolve } = require('internal/modules/esm/resolve');
12081208
return finalizeEsmResolution(
1209-
packageImportsResolve(request, pathToFileURL(parentPath),
1210-
getCjsConditions()), parentPath,
1211-
pkg.path);
1209+
packageImportsResolve(request, pathToFileURL(parentPath), getCjsConditions()),
1210+
parentPath,
1211+
pkg.path,
1212+
);
12121213
} catch (e) {
12131214
if (e.code === 'ERR_MODULE_NOT_FOUND') {
12141215
throw createEsmNotFoundErr(request);
@@ -1268,8 +1269,7 @@ function finalizeEsmResolution(resolved, parentPath, pkgPath) {
12681269
if (actual) {
12691270
return actual;
12701271
}
1271-
const err = createEsmNotFoundErr(filename,
1272-
path.resolve(pkgPath, 'package.json'));
1272+
const err = createEsmNotFoundErr(filename, pkgPath);
12731273
throw err;
12741274
}
12751275

@@ -1623,7 +1623,7 @@ function loadTS(module, filename) {
16231623

16241624
const parent = module[kModuleParent];
16251625
const parentPath = parent?.filename;
1626-
const packageJsonPath = path.resolve(pkg.path, 'package.json');
1626+
const packageJsonPath = pkg.path;
16271627
const usesEsm = containsModuleSyntax(content, filename);
16281628
const err = new ERR_REQUIRE_ESM(filename, usesEsm, parentPath,
16291629
packageJsonPath);
@@ -1682,7 +1682,7 @@ Module._extensions['.js'] = function(module, filename) {
16821682
// This is an error path because `require` of a `.js` file in a `"type": "module"` scope is not allowed.
16831683
const parent = module[kModuleParent];
16841684
const parentPath = parent?.filename;
1685-
const packageJsonPath = path.resolve(pkg.path, 'package.json');
1685+
const packageJsonPath = pkg.path;
16861686
const usesEsm = containsModuleSyntax(content, filename);
16871687
const err = new ERR_REQUIRE_ESM(filename, usesEsm, parentPath,
16881688
packageJsonPath);
Collapse file

‎lib/internal/modules/esm/resolve.js‎

Copy file name to clipboardExpand all lines: lib/internal/modules/esm/resolve.js
+16-87Lines changed: 16 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -356,7 +356,6 @@ function invalidPackageTarget(
356356

357357
const invalidSegmentRegEx = /(^|\\|\/)((\.|%2e)(\.|%2e)?|(n|%6e|%4e)(o|%6f|%4f)(d|%64|%44)(e|%65|%45)(_|%5f)(m|%6d|%4d)(o|%6f|%4f)(d|%64|%44)(u|%75|%55)(l|%6c|%4c)(e|%65|%45)(s|%73|%53))?(\\|\/|$)/i;
358358
const deprecatedInvalidSegmentRegEx = /(^|\\|\/)((\.|%2e)(\.|%2e)?|(n|%6e|%4e)(o|%6f|%4f)(d|%64|%44)(e|%65|%45)(_|%5f)(m|%6d|%4d)(o|%6f|%4f)(d|%64|%44)(u|%75|%55)(l|%6c|%4c)(e|%65|%45)(s|%73|%53))(\\|\/|$)/i;
359-
const invalidPackageNameRegEx = /^\.|%|\\/;
360359
const patternRegEx = /\*/g;
361360

362361
/**
@@ -752,44 +751,6 @@ function packageImportsResolve(name, base, conditions) {
752751
throw importNotDefined(name, packageJSONUrl, base);
753752
}
754753

755-
/**
756-
* Parse a package name from a specifier.
757-
* @param {string} specifier - The import specifier.
758-
* @param {string | URL | undefined} base - The parent URL.
759-
*/
760-
function parsePackageName(specifier, base) {
761-
let separatorIndex = StringPrototypeIndexOf(specifier, '/');
762-
let validPackageName = true;
763-
let isScoped = false;
764-
if (specifier[0] === '@') {
765-
isScoped = true;
766-
if (separatorIndex === -1 || specifier.length === 0) {
767-
validPackageName = false;
768-
} else {
769-
separatorIndex = StringPrototypeIndexOf(
770-
specifier, '/', separatorIndex + 1);
771-
}
772-
}
773-
774-
const packageName = separatorIndex === -1 ?
775-
specifier : StringPrototypeSlice(specifier, 0, separatorIndex);
776-
777-
// Package name cannot have leading . and cannot have percent-encoding or
778-
// \\ separators.
779-
if (RegExpPrototypeExec(invalidPackageNameRegEx, packageName) !== null) {
780-
validPackageName = false;
781-
}
782-
783-
if (!validPackageName) {
784-
throw new ERR_INVALID_MODULE_SPECIFIER(
785-
specifier, 'is not a valid package name', fileURLToPath(base));
786-
}
787-
788-
const packageSubpath = '.' + (separatorIndex === -1 ? '' :
789-
StringPrototypeSlice(specifier, separatorIndex));
790-
791-
return { packageName, packageSubpath, isScoped };
792-
}
793754

794755
/**
795756
* Resolves a package specifier to a URL.
@@ -804,57 +765,24 @@ function packageResolve(specifier, base, conditions) {
804765
return new URL('node:' + specifier);
805766
}
806767

807-
const { packageName, packageSubpath, isScoped } =
808-
parsePackageName(specifier, base);
768+
const { packageJSONUrl, packageJSONPath, packageSubpath } = packageJsonReader.getPackageJSONURL(specifier, base);
809769

810-
// ResolveSelf
811-
const packageConfig = packageJsonReader.getPackageScopeConfig(base);
812-
if (packageConfig.exists) {
813-
if (packageConfig.exports != null && packageConfig.name === packageName) {
814-
const packageJSONUrl = pathToFileURL(packageConfig.pjsonPath);
815-
return packageExportsResolve(
816-
packageJSONUrl, packageSubpath, packageConfig, base, conditions);
817-
}
818-
}
770+
const packageConfig = packageJsonReader.read(packageJSONPath, { __proto__: null, specifier, base, isESM: true });
819771

820-
let packageJSONUrl =
821-
new URL('./node_modules/' + packageName + '/package.json', base);
822-
let packageJSONPath = fileURLToPath(packageJSONUrl);
823-
let lastPath;
824-
do {
825-
const stat = internalFsBinding.internalModuleStat(
826-
internalFsBinding,
827-
StringPrototypeSlice(packageJSONPath, 0, packageJSONPath.length - 13),
772+
// Package match.
773+
if (packageConfig.exports != null) {
774+
return packageExportsResolve(
775+
packageJSONUrl, packageSubpath, packageConfig, base, conditions);
776+
}
777+
if (packageSubpath === '.') {
778+
return legacyMainResolve(
779+
packageJSONUrl,
780+
packageConfig,
781+
base,
828782
);
829-
// Check for !stat.isDirectory()
830-
if (stat !== 1) {
831-
lastPath = packageJSONPath;
832-
packageJSONUrl = new URL((isScoped ?
833-
'../../../../node_modules/' : '../../../node_modules/') +
834-
packageName + '/package.json', packageJSONUrl);
835-
packageJSONPath = fileURLToPath(packageJSONUrl);
836-
continue;
837-
}
838-
839-
// Package match.
840-
const packageConfig = packageJsonReader.read(packageJSONPath, { __proto__: null, specifier, base, isESM: true });
841-
if (packageConfig.exports != null) {
842-
return packageExportsResolve(
843-
packageJSONUrl, packageSubpath, packageConfig, base, conditions);
844-
}
845-
if (packageSubpath === '.') {
846-
return legacyMainResolve(
847-
packageJSONUrl,
848-
packageConfig,
849-
base,
850-
);
851-
}
852-
853-
return new URL(packageSubpath, packageJSONUrl);
854-
// Cross-platform root check.
855-
} while (packageJSONPath.length !== lastPath.length);
783+
}
856784

857-
throw new ERR_MODULE_NOT_FOUND(packageName, fileURLToPath(base), null);
785+
return new URL(packageSubpath, packageJSONUrl);
858786
}
859787

860788
/**
@@ -1105,10 +1033,11 @@ module.exports = {
11051033
decorateErrorWithCommonJSHints,
11061034
defaultResolve,
11071035
encodedSepRegEx,
1036+
legacyMainResolve,
11081037
packageExportsResolve,
11091038
packageImportsResolve,
1039+
packageResolve,
11101040
throwIfInvalidParentURL,
1111-
legacyMainResolve,
11121041
};
11131042

11141043
// cycle

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.