Skip to content

Commit 281c4f7

Browse files
committed
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: #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 9b7ba87 commit 281c4f7

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
@@ -2695,6 +2695,39 @@ The `repl` module exports a `_builtinLibs` property that contains an array with
26952695
native modules. It was incomplete so far and instead it's better to rely upon
26962696
`require('module').builtinModules`.
26972697
2698+
<a id="DEP0143"></a>
2699+
### DEP0143: `module.parent`
2700+
<!-- YAML
2701+
changes:
2702+
- version: REPLACEME
2703+
pr-url: https://github.com/nodejs/node/pull/32217
2704+
description: Documentation-only deprecation.
2705+
-->
2706+
2707+
Type: Documentation-only (supports [`--pending-deprecation`][])
2708+
2709+
A CommonJS module can access the first module that required it using
2710+
`module.parent`. This feature is deprecated because it does not work
2711+
consistently in the presence of ECMAScript modules and because it gives an
2712+
inaccurate representation of the CommonJS module graph.
2713+
2714+
Some modules use it to check if they are the entry point of the current process.
2715+
Instead, it is recommended to compare `require.main` and `module`:
2716+
2717+
```js
2718+
if (require.main === module) {
2719+
// Code section that will run only if current file is the entry point.
2720+
}
2721+
```
2722+
2723+
When looking for the CommonJS modules that have required the current one,
2724+
`require.cache` and `module.children` can be used:
2725+
2726+
```js
2727+
const moduleParents = Object.values(require.cache)
2728+
.filter((m) => m.children.includes(module));
2729+
```
2730+
26982731
[`--pending-deprecation`]: cli.html#cli_pending_deprecation
26992732
[`--throw-deprecation`]: cli.html#cli_throw_deprecation
27002733
[`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
StringPrototypeMatch,
@@ -159,11 +160,12 @@ function updateChildren(parent, child, scan) {
159160
children.push(child);
160161
}
161162

163+
const moduleParentCache = new SafeWeakMap();
162164
function Module(id = '', parent) {
163165
this.id = id;
164166
this.path = path.dirname(id);
165167
this.exports = {};
166-
this.parent = parent;
168+
moduleParentCache.set(this, parent);
167169
updateChildren(parent, this, false);
168170
this.filename = null;
169171
this.loaded = false;
@@ -232,6 +234,18 @@ ObjectDefineProperty(Module, 'wrapper', {
232234
}
233235
});
234236

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

@@ -1018,7 +1032,7 @@ Module._resolveFilename = function(request, parent, isMain, options) {
10181032
const requireStack = [];
10191033
for (let cursor = parent;
10201034
cursor;
1021-
cursor = cursor.parent) {
1035+
cursor = moduleParentCache.get(cursor)) {
10221036
requireStack.push(cursor.filename || cursor.id);
10231037
}
10241038
let message = `Cannot find module '${request}'`;
@@ -1211,7 +1225,8 @@ Module._extensions['.js'] = function(module, filename) {
12111225
const pkg = readPackageScope(filename);
12121226
// Function require shouldn't be used in ES modules.
12131227
if (pkg && pkg.data && pkg.data.type === 'module') {
1214-
const parentPath = module.parent && module.parent.filename;
1228+
const parent = moduleParentCache.get(module);
1229+
const parentPath = parent && parent.filename;
12151230
const packageJsonPath = path.resolve(pkg.path, 'package.json');
12161231
throw new ERR_REQUIRE_ESM(filename, parentPath, packageJsonPath);
12171232
}

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)