Skip to content

Commit 6443ab9

Browse files
aduh95BridgeAR
authored andcommitted
module: deprecate module.parent
This feature does not work when a module is imported using ECMAScript modules specification, therefore it is deprecated. Fixes: nodejs/modules#469 PR-URL: nodejs#32217 Reviewed-By: Gus Caplan <me@gus.host> Reviewed-By: Bradley Farias <bradley.meck@gmail.com> Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: Tobias Nießen <tniessen@tnie.de> Reviewed-By: Ruben Bridgewater <ruben@bridgewater.de>
1 parent 039cd00 commit 6443ab9

9 files changed

Lines changed: 81 additions & 12 deletions

File tree

doc/api/deprecations.md

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2712,6 +2712,39 @@ The `repl` module exports a `_builtinLibs` property that contains an array with
27122712
native modules. It was incomplete so far and instead it's better to rely upon
27132713
`require('module').builtinModules`.
27142714
2715+
<a id="DEP0143"></a>
2716+
### DEP0143: `module.parent`
2717+
<!-- YAML
2718+
changes:
2719+
- version: REPLACEME
2720+
pr-url: https://github.com/nodejs/node/pull/32217
2721+
description: Documentation-only deprecation.
2722+
-->
2723+
2724+
Type: Documentation-only (supports [`--pending-deprecation`][])
2725+
2726+
A CommonJS module can access the first module that required it using
2727+
`module.parent`. This feature is deprecated because it does not work
2728+
consistently in the presence of ECMAScript modules and because it gives an
2729+
inaccurate representation of the CommonJS module graph.
2730+
2731+
Some modules use it to check if they are the entry point of the current process.
2732+
Instead, it is recommended to compare `require.main` and `module`:
2733+
2734+
```js
2735+
if (require.main === module) {
2736+
// Code section that will run only if current file is the entry point.
2737+
}
2738+
```
2739+
2740+
When looking for the CommonJS modules that have required the current one,
2741+
`require.cache` and `module.children` can be used:
2742+
2743+
```js
2744+
const moduleParents = Object.values(require.cache)
2745+
.filter((m) => m.children.includes(module));
2746+
```
2747+
27152748
[`--pending-deprecation`]: cli.html#cli_pending_deprecation
27162749
[`--throw-deprecation`]: cli.html#cli_throw_deprecation
27172750
[`Buffer.allocUnsafeSlow(size)`]: buffer.html#buffer_class_method_buffer_allocunsafeslow_size

doc/api/modules.md

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -690,7 +690,6 @@ Module {
690690
id: '.',
691691
path: '/absolute/path/to',
692692
exports: {},
693-
parent: null,
694693
filename: '/absolute/path/to/entry.js',
695694
loaded: false,
696695
children: [],
@@ -894,11 +893,17 @@ loading.
894893
### `module.parent`
895894
<!-- YAML
896895
added: v0.1.16
896+
deprecated: REPLACEME
897897
-->
898898

899-
* {module}
899+
> Stability: 0 - Deprecated: Please use [`require.main`][] and
900+
> [`module.children`][] instead.
901+
902+
* {module | null | undefined}
900903

901-
The module that first required this one.
904+
The module that first required this one, or `null` if the current module is the
905+
entry point of the current process, or `undefined` if the module was loaded by
906+
something that is not a CommonJS module (E.G.: REPL or `import`). Read only.
902907

903908
### `module.path`
904909
<!-- YAML
@@ -1122,13 +1127,15 @@ consists of the following keys:
11221127
[`createRequire()`]: #modules_module_createrequire_filename
11231128
[`module` object]: #modules_the_module_object
11241129
[`module.id`]: #modules_module_id
1130+
[`module.children`]: #modules_module_children
11251131
[`path.dirname()`]: path.html#path_path_dirname_path
11261132
[ECMAScript Modules]: esm.html
11271133
[an error]: errors.html#errors_err_require_esm
11281134
[exports shortcut]: #modules_exports_shortcut
11291135
[module resolution]: #modules_all_together
11301136
[module wrapper]: #modules_the_module_wrapper
11311137
[native addons]: addons.html
1138+
[`require.main`]: #modules_require_main
11321139
[source map include directives]: https://sourcemaps.info/spec.html#h.lmz475t4mvbx
11331140
[`--enable-source-maps`]: cli.html#cli_enable_source_maps
11341141
[`NODE_V8_COVERAGE=dir`]: cli.html#cli_node_v8_coverage_dir

lib/internal/modules/cjs/loader.js

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ const {
3939
ReflectSet,
4040
RegExpPrototypeTest,
4141
SafeMap,
42+
SafeWeakMap,
4243
String,
4344
StringPrototypeIndexOf,
4445
StringPrototypeLastIndexOf,
@@ -160,11 +161,12 @@ function updateChildren(parent, child, scan) {
160161
children.push(child);
161162
}
162163

164+
const moduleParentCache = new SafeWeakMap();
163165
function Module(id = '', parent) {
164166
this.id = id;
165167
this.path = path.dirname(id);
166168
this.exports = {};
167-
this.parent = parent;
169+
moduleParentCache.set(this, parent);
168170
updateChildren(parent, this, false);
169171
this.filename = null;
170172
this.loaded = false;
@@ -233,6 +235,18 @@ ObjectDefineProperty(Module, 'wrapper', {
233235
}
234236
});
235237

238+
function getModuleParent() {
239+
return moduleParentCache.get(this);
240+
}
241+
ObjectDefineProperty(Module.prototype, 'parent', {
242+
get: pendingDeprecation ? deprecate(
243+
getModuleParent,
244+
'module.parent is deprecated due to accuracy issues. Please use ' +
245+
'require.main to find program entry point instead.',
246+
'DEP0143'
247+
) : getModuleParent
248+
});
249+
236250
const debug = require('internal/util/debuglog').debuglog('module');
237251
Module._debug = deprecate(debug, 'Module._debug is deprecated.', 'DEP0077');
238252

@@ -1020,7 +1034,7 @@ Module._resolveFilename = function(request, parent, isMain, options) {
10201034
const requireStack = [];
10211035
for (let cursor = parent;
10221036
cursor;
1023-
cursor = cursor.parent) {
1037+
cursor = moduleParentCache.get(cursor)) {
10241038
requireStack.push(cursor.filename || cursor.id);
10251039
}
10261040
let message = `Cannot find module '${request}'`;
@@ -1213,7 +1227,8 @@ Module._extensions['.js'] = function(module, filename) {
12131227
const pkg = readPackageScope(filename);
12141228
// Function require shouldn't be used in ES modules.
12151229
if (pkg && pkg.data && pkg.data.type === 'module') {
1216-
const parentPath = module.parent && module.parent.filename;
1230+
const parent = moduleParentCache.get(module);
1231+
const parentPath = parent && parent.filename;
12171232
const packageJsonPath = path.resolve(pkg.path, 'package.json');
12181233
throw new ERR_REQUIRE_ESM(filename, parentPath, packageJsonPath);
12191234
}

test/common/index.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,12 +59,12 @@ if (process.argv.length === 2 &&
5959
!process.env.NODE_SKIP_FLAG_CHECK &&
6060
isMainThread &&
6161
hasCrypto &&
62-
module.parent &&
62+
require.main &&
6363
require('cluster').isMaster) {
6464
// The copyright notice is relatively big and the flags could come afterwards.
6565
const bytesToRead = 1500;
6666
const buffer = Buffer.allocUnsafe(bytesToRead);
67-
const fd = fs.openSync(module.parent.filename, 'r');
67+
const fd = fs.openSync(require.main.filename, 'r');
6868
const bytesRead = fs.readSync(fd, buffer, 0, bytesToRead);
6969
fs.closeSync(fd);
7070
const source = buffer.toString('utf8', 0, bytesRead);

test/common/require-as.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/* eslint-disable node-core/require-common-first, node-core/required-modules */
22
'use strict';
33

4-
if (module.parent) {
4+
if (require.main !== module) {
55
const { spawnSync } = require('child_process');
66

77
function runModuleAs(filename, flags, spawnOptions, role) {

test/js-native-api/test_instance_data/test.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
const common = require('../../common');
55
const assert = require('assert');
66

7-
if (module.parent) {
7+
if (module !== require.main) {
88
// When required as a module, run the tests.
99
const test_instance_data =
1010
require(`./build/${common.buildType}/test_instance_data`);

test/node-api/test_instance_data/test.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
const common = require('../../common');
55

6-
if (module.parent) {
6+
if (module !== require.main) {
77
// When required as a module, run the tests.
88
const test_instance_data =
99
require(`./build/${common.buildType}/test_instance_data`);

test/parallel/test-cli-eval.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
// USE OR OTHER DEALINGS IN THE SOFTWARE.
2121

2222
'use strict';
23-
if (module.parent) {
23+
if (module !== require.main) {
2424
// Signal we've been loaded as a module.
2525
// The following console.log() is part of the test.
2626
console.log('Loaded as a module, exiting with status code 42.');
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// Flags: --pending-deprecation
2+
3+
'use strict';
4+
const common = require('../common');
5+
const assert = require('assert');
6+
7+
common.expectWarning(
8+
'DeprecationWarning',
9+
'module.parent is deprecated due to accuracy issues. Please use ' +
10+
'require.main to find program entry point instead.',
11+
'DEP0143'
12+
);
13+
14+
assert.strictEqual(module.parent, null);

0 commit comments

Comments
 (0)