Repository navigation
import(cjs) messes with module.parent #469
Description
Activity
Thanks for bringing this up, I don't think we were tracking this yet! Your options sound spot-on.
Make module.parent truthy when a CJS is imported (although I have no idea what it could look like).
Making
module.parentcould be tricky if any package actually tries to check it - it would have to be something that itself isn't a valid CJS module. So we'd have to select a value very carefully or risk breaking other uses ofmodule.parentif those exist.Packages using require.main===module are fine, so that's what library authors should be using to test this, right?
Yeah, I think to me
require.main===moduleis the "canonical" check. That's also what the docs suggest using: https://nodejs.org/api/modules.html#modules_accessing_the_main_module. So for maintainers, the suggested path would be to migrate to that pattern instead.Disregard this issue, as users can use module.createRequire to workaround the issue.
I think it'll depend on how widespread this issue is. Can you share which package (or packages) this happened with? Capturing a list here may make it easier for us to determine the impact.
One use case for
module.parent, fwiw: https://github.com/hypercloud/import-dir/blob/afb1ef9ae1849e6ce8b535f69d412e38dda3115b/index.js#L14. Not a highly used module, but stillReacted by Derek Lewis and Jakob Rosenberg@SimenB not all ESM have a valid file path associated with them (
data:in particular for now), and/or there may have multiple ESM with the same file path but different URLs (using URL fragments). I'm not sure we could preserve that through populatingmodule.parentfrom an ESM base.Reacted by Jan Olaf Martin, ExE Boss and Jordan HarbandFor reference, I have seen is-port-available and pdf-parse use a similar pattern. Not highly used modules either, but there are certainly others.
Reacted by ExE BossReacted by Jan Olaf Martin and Derek LewisI think we should deprecate
module.parent.Reacted by Advaiya LadReacted by Derek Lewis and Jakob RosenbergFolks, I'm
very muchopposed to deprecatingmodule.parent.In fact, I'm in need of this feature to be functional for ES modules too.
The intended use-case is to be able to visualize module graphs. To be able to traverse these graphs, I'd need this parent-children relationship metadata. This also relates to APMs.
Additionally, I do not think something so important should be deprecated merely to discourage a pattern.
Reacted by Sam El-Borai and Kirill DyakovReacted by ExE Boss and Advaiya Lad@DerekNonGeneric parent does not give an accurate traversal of a graph anyway, because modules can actually have multiple parents. you should start at the entrypoint and traverse down.
Reacted by ExE Boss and Ruben BridgewaterI'm sorry, can you clarify? I'm aware CJS and ES modules use different algorithms, but
module.parentshows undefined in my.mjsgraphs anyways, so I was referring to CJS graphs. I'd appreciate a link to spec docs if possible.@DerekNonGeneric like
require('x.js')ina.jsandrequire('x.js')inb.js. Botha.jsandb.jsare parents ofx.js, but only the one that ran first would be themodule.parent.Reacted by ExE BossThat's perfectly fine! I'm interested in visualizing the graph as it exists during execution (not conceptually).
@DerekNonGeneric ... during execution both a.js and b.js required x.js. a cjs module graph would show a directed edge from a.js to x.js and a directed edge from b.js to x.js.
Reacted by ExE BossI'm unclear why this information would be useful to a stack trace, but unavailable to userland. I'd have to do more research, but this seems like valuable information is being lost.
@DerekNonGeneric the graph structure is preserved (in a more accurate manner) via
module.children.Reacted by ExE Boss, Tobias Nießen and Ruben Bridgewater@devsnek, thank you. I don't feel the ends justify the means here. It seems like the correct way to go about this would be to deprecate the module that's using the discouraged pattern, not deprecate a runtime feature because people are using it incorrectly.
21 remaining items
@aduh95, your PR is recommending to use …
if (require.main === module) { }
… as opposed to …
if (!module.parent) { }
These may appear to achieve the same desired effect, but that's not the case in
every circumstance. The following points explain why I'm still not +1 and might
be worth considering.- Because
requirecan be “hijacked” (and often is), none of its behavior can
be trusted with any degree of certainty. A good example is
node -r esm. - Because
moduleis a “free variable” in the global CommonJS scope, it never
gets loaded withrequire(), which may provide more certainty that its
contents are trustworthy (sincerequire()never had the opportunity to
tamper w/ it).
Therefore, if I'm not mistaken,
module.parentis currently the only credible
way to determine whether or not the current module is the root node of the
module graph.
Ultimately, I'm not attached to the outcome of the deprecation PR since it's
just documentation deprecation (for now). However, if the docs deprecation were
to eventually become runtime deprecation, it seems to me like it would come at
the expense of module graph observability, which doesn't seem like the intention.Reacted by Jacob Chapman- Because
Therefore, if I'm not mistaken, module.parent is currently the only credible
way to determine whether or not the current module is the root node of the
module graph.It's not any more or less credible than every other option. There's no reliability difference (afaik) between
require.mainandmodule.parent. Both can be influenced by code running before the module because CommonJS is highly hackable.module.parentis very easy to change by just usingModule._load:$ cat /tmp/my.cjs console.log(module.parent ? "has parent" : "no parent") $ node /tmp/my.cjs no parent $ node -e "require('/tmp/my.cjs')" has parent $ node -e "require('module')._load('/tmp/my.cjs', null, true)" no parent
You can even modify
require.cache['/realpath/to/my.cjs'].parentafter the fact and make the parent appear and disappear at runtime.it seems to me like it would come at the expense of module graph observability
I don't agree since
require.cachenever modeled a full or reliable module graph. And if we ever bring a module graph to ESM in general, it would likely not be in the CommonJS cache. Sorequire.cachewill become less and less complete one way or another as people start using ESM features.Reacted by Derek Lewis, Jordan Harband, Ruben Bridgewater and ExE BossTherefore, if I'm not mistaken, module.parent is currently the only credible
way to determine whether or not the current module is the root node of the
module graph.I believe
process.mainModuleachieves the same purpose as well. It's been doc deprecated on v14.0.0 but it's still an alternative.Because
requirecan be “hijacked” (and often is), none of its behavior can
be trustedThat doesn't seem like a problem we would want to fix, I guess people that do "hijack"
requiredo it for their own reasons. If you want certainty, you shouldn't use code that "hijacks" it, wouldn't you agree?Anyway,
module.parenthas other issues that have been discussed in this thread already, I'm not sure further discussion can be constructive at this point.Reacted by ExE Boss- added a commit that references this issue
on May 24, 2020 - added a commit that references this issue
on Jun 9, 2020 - added 2 commits that reference this issue
on Jul 15, 2020 - added a commit that references this issue
on Aug 18, 2020 - added a commit that references this issue
on Aug 18, 2020 - added a commit that references this issue
on Dec 31, 2025
Some packages use a check on
module.parentto know if they are launched from CLI or required by another module:This works fine if you use
require(I tried from both CJS and ES modules):However, when importing from ESM, that doesn't work:
Same for dynamic imports:
I can see three ways to tackle this issue, but none is very satisfying to me:
module.parenttruthy when a CJS is imported (although I have no idea what it could look like).module.parent? Packages usingrequire.main===moduleare fine, so that's what library authors should be using to test this, right?module.createRequireto workaround the issue.