modules icon indicating copy to clipboard operation
modules copied to clipboard

import.meta.main

Open MylesBorins opened this issue 6 years ago • 74 comments

Deno just added this.

Should we?

https://github.com/denoland/deno/pull/1835

MylesBorins avatar Feb 26 '19 22:02 MylesBorins

I'm a bit confused what it does - does it mean "it was the main in package.json"? it was the primary entry point? what about import()?

ljharb avatar Feb 26 '19 22:02 ljharb

it's true if the file was the entrypoint. for node i'd rather it be called something else cuz the term main already has package-scoped meaning.

devsnek avatar Feb 26 '19 22:02 devsnek

Right, but anything that's import()ed is also an entry point, and I'm very unclear on why you care what that value is. Currently you can look at process.argv and __filename to determine that (and import.meta.filename or similar is quite well motivated).

Also yes, main is a terrible name for multiple reasons, not the least of which is that it's a package.json field.

ljharb avatar Feb 26 '19 22:02 ljharb

I really like the import.meta.main concept. Currently in Node.js it is common for CLI tools to do the following check:

if (require.main === module) {
  console.log('This is a CLI tool! Usage: ...');
}

We currently have no version of the above check in ECMAScript modules, so this is a problem that comes up, and there is currently no easy way to do this without it feeling like a hack.

As an already-paved use case, users are familiar with the main terminology in this context already.

The above check then becomes:

if (import.meta.main) {
  console.log('This is a CLI tool! Usage: ...');
}

and the really nice thing about this pattern is that it works in browsers and other environments completely fine without any need for compatibility API layers or compilation.

For these reasons I'm 100% supportive of import.meta.main.

guybedford avatar Feb 27 '19 07:02 guybedford

Doesn’t that use case predate the current community preference, which is to have separate packages for a library and a CLI?

ljharb avatar Feb 27 '19 13:02 ljharb

@ljharb this use case is explicitly documented in https://github.com/nodejs/node/blob/master/doc/api/modules.md#accessing-the-main-module.

guybedford avatar Feb 27 '19 13:02 guybedford

Sure but so is every aspect of the require algorithm :-) the better question is, is it still a common or desired use case to determine if something is the entry point, and how does that change now that dynamic import allows multiple entry points?

ljharb avatar Feb 27 '19 13:02 ljharb

@ljharb yes it is a desired feature. The require.main === module was something we had to explicitly support in the ncc project as users were using it in CLI tools. This came up pretty early in the project and had multiple users asking about it here - https://github.com/zeit/ncc/issues/224. Dynamic import does not affect this, as what we are distinguishing is the CLI entry point, not the module graph entry point.

guybedford avatar Feb 27 '19 13:02 guybedford

The feature seems fine then (it’d only be true in the top-level process entry point); the name, definitely not (but we can bikeshed that).

ljharb avatar Feb 27 '19 13:02 ljharb

Does this absolutely need to be in our first iteration or can we get feedback / focus on other things?

bmeck avatar Feb 27 '19 14:02 bmeck

Thanks for bringing this up @MylesBorins. I (think) we can say this is post-MVP but it's good to have on our radar.

zenparsing avatar Feb 27 '19 18:02 zenparsing

can definitely be post MVP. Just wanted to document what's going on in ecosystem

MylesBorins avatar Feb 27 '19 19:02 MylesBorins

Well, I'm not part of the organization, but as a cli maintainer, this proposal seems so interesting.

Can I start to try implementing this in https://github.com/nodejs/node and possibly a PR now?

shian15810 avatar Aug 01 '19 08:08 shian15810

I'm not a huge fan of the concept of main. imo you should have separate files for bin and lib, which is something other languages do just fine, and installers like npm and yarn already let you specify separate bins.

devsnek avatar Aug 01 '19 14:08 devsnek

@shian15810 thanks for showing an interest in the modules work! Contributions are very much welcome to Node.js core and the module work here.

We do have some consensus issues for this feature due to our not wanting to provide unnecessary features without seeing a strong need for them in the initial modules implementation. import.meta is a very widely used namespace (every module!) with strong backwards compatibility needs, so we do also need to be very cautious about what we put on it.

A PR to Node.js core would certainly drive discussion, and may even sway consensus. Also hearing more about your use case as a CLI maintainer and how this is useful could help us better understand the importance of the feature to you (including for example the points raised by Gus above). Even if the PR sits without approval, we may be able to come back to it in a few months even as well.

guybedford avatar Aug 01 '19 20:08 guybedford

The use case of mine is that both bin and main fields in package.json point to the same file. Using require.main === module, as mentioned in the official docs here, to tell whether the script has been run directly or not, so that to know if the main function should be executed or exported.

As far as I can tell, with a package.json having "type": "commonjs", there are three ways to tell if a file is run directly (aka not being required):

  1. require.main === module or process.mainModule === module
  2. require.main.filename === __filename or process.mainModule.filename === __filename
  3. !module.parent

However, with a package.json having "type": "module", require and module are not available, as well as process.mainModule is undefined.

What's left is just import.meta.url, but the problem is what to compare with to know if it is the main script.

In addition, according to this, module.parent in a cjs script will be undefined if the parent is a mjs script. After some testing, require.main and process.mainModule are both undefined too in this context.

In my imagination, if import.meta.main is implemented, I could use it in mjs script, while retaining require.main === module in cjs script. But this still pose a serious problem to a project with mixed cjs and mjs scripts importing each other, as mentioned above.

So yeah, I agree with @devsnek in this case, that is pointing bin and main to separate scripts in the first place, which I now think is the right way to solve this problem.

Also, I agree with @guybedford regarding strong backwards compatibility, even though TC39 states that import.meta object will be extensible.

As a last note, I've read somewhere in this repo stating that in the context of esmodule, there is no concept of main such as in commonjs, correct me if I'm wrong. But I certainly think that there must be some other use cases of determining the main script, just like require.main === module in commonjs. It is good to have import.meta.main as the counterpart in esmodule, though should it be named main is pretty much debatable.

shian15810 avatar Aug 01 '19 23:08 shian15810

Going to abandon this for now, please feel free to reopen

MylesBorins avatar Nov 17 '19 08:11 MylesBorins

I still believe this is an important feature, to be put in the same basket as CJS features not available in ESM like require.resolve.

It's not a common feature, but it is definitely a heavily ingrained one where it is used, and has a small collection of users that absolutely do expect this functionality (mainly CLI developers).

guybedford avatar Nov 17 '19 19:11 guybedford

i am still against the concept of a main module in general.

devsnek avatar Nov 17 '19 19:11 devsnek

@guybedford Which labels are relevant here? Can you add them to make it easier to related to the context please — ie cjs anr/or esm... etc.

SMotaal avatar Nov 17 '19 19:11 SMotaal

@devsnek Can you elaborate more? Main here is the main entrypoint, so it is like window.location where there is a Window context or self.location where there is not (workers... etc.) but that does not apply the same way in CommonJS (ie not URLs).

Aside — I don't think adding a global for ESM only is a good idea personally, and otherwise neutral to import.meta.main.

SMotaal avatar Nov 17 '19 19:11 SMotaal

@guybedford Awesome, that really helps 💯

SMotaal avatar Nov 17 '19 19:11 SMotaal

@SMotaal i dislike the idea of differentiating entrypoints. you can just use a separate file for your bin.

devsnek avatar Nov 17 '19 20:11 devsnek

being an entrypoint doesn't inherently mean it's a cli (for example, cf workers). additionally, some hosts like browsers don't map well to the concept of entrypoints. depending on how you think about it, a webpage may either have several or no entrypoints. Even if you choose to think about a browser having several instead of none, none of the several would be a cli.

devsnek avatar Nov 17 '19 21:11 devsnek

@SMotaal i dislike the idea of differentiating entrypoints. you can just use a separate file for your bin.

@devsnek certainly it is one style, I am not saying your style is wrong, but others also have justifications that cannot be open to judgement because this all comes down to matters of opinion.

As it happens, any given module in a package can be called as a result of at least two paths, a main entrypoint in the package, or an entrypoint for which the package is a direct or indirect dependency… some packages may actually consider this a functional parameter depending on their purpose.

depending on how you think about it

Yes, I think this is the bottom line… And, so no disagreement or disapproval is intended, just wanting us to consider the more diverse landscapes that we may not ourselves be interested in (yet).

SMotaal avatar Nov 17 '19 23:11 SMotaal

To clarify some of the above, the only module that gets import.meta.main set is the module passed into node main.js it is only the module corresponding to process.argv[1]. And no, it doesn't apply to browsers.

This is the Node.js application entry point main, not package entry points, or realm / worker entry points.

An equivalent way to achieve this check would be to do path.resolve(process.argv[1]) === fileURLToPath(import.meta.url).

Perhaps the above explicit check would be enough though.

guybedford avatar Nov 17 '19 23:11 guybedford

@guybedford what i'm trying to say is that because it depends on how you approach it, we shouldn't try to reify a certain pattern. if the condition for your cli is that it is the argument to node, that check seems good to me. i agree it's not the most ergonomic, but i think that comes down to the awkardness of paths and urls.

devsnek avatar Nov 17 '19 23:11 devsnek

@guybedford thanks for the reminder/clarification… so a consideration that comes to mind, who has the authority to know.

And so it being a "readonly" and "private" field on import.meta has very different implications than globals, where it is possible to run into all kinds of costs to try to check for distortions.

Just a thought, no strong feelings on it personally.

SMotaal avatar Nov 17 '19 23:11 SMotaal

The existing Node.js pattern it is replacing is the require.main === module pattern which is documented in the Node.js docs for require - https://nodejs.org/dist/latest-v13.x/docs/api/modules.html#modules_accessing_the_main_module

guybedford avatar Nov 17 '19 23:11 guybedford

the pattern for cjs, sure. i'd argue there is no pattern for esm yet. i'd like to lean on the more mature advancements in the ecosystem like package manager bin entries instead of continuing to copy old python patterns. using separate files also means (as an example) that node-specific cli code is not included if you just load up a library for webpacking.

devsnek avatar Nov 17 '19 23:11 devsnek