import.meta.main
arslivinski opened this issue · 27 comments
What is the problem this feature will solve?
We need an easy way to know if the current file was the one executed by Node.
What is the feature you are proposing to solve the problem?
Following of #49440
Deno and Bun have it, Node should have it too.
What alternatives have you considered?
No response
I'm fine with the feature, but calling it "main" is very confusing, since anything that's import()ed is an entry point, and "main" already exists.
(Also, I'd hope that node would add things not solely because some alternatives have it, but because it's independently motivated)
@ljharb I get your point of not doing something just because others did it. But at the same time, I see that by taking so long to take a decision on this, that others decided first and now the ecossystem is using import.meta.main. It's a ship that already sailed.
Also, I didn't mention entrypoint, but if the current file was the one executed by Node, like by running node someFile.js.
@ljharb you say "main" already exists, can you explain more specifically?
IMO it makes sense to name this main. Not only does this improve inter-operability between the 'big' JavaScript runtimes, but there is significant precedent, really too much to ignore.
- Python:
if __name__ == "__main__": foo() - C/C++:
int main() { ... } - Rust:
fn main() { ... } - Java (preview 24):
void main() { ... } - Kotlin:
fun main() { ... } - C#:
static void Main(string[] args) { ... } - Go:
func main() { ... }
In all of these cases main refers to a special name/value which says 'here is where the magic happens'.
@Lordfirespeed i mean the "main" field of package.json, which is the entrypoint of a package when exports is not present.
@Lordfirespeed i mean the "main" field of package.json, which is the entrypoint of a package when
exportsis not present.
Ah, yes, thankyou for reminding me.
To me, this corroborates that naming this feature main makes sense - the desired behaviour is that import.meta.main evaluates true in the main file (of the package.json variety), and false otherwise, in the case that one runs e.g. npm start.
You don't agree? 😅
"main" in package.json means "an entry point", and every dynamic-imported file is an entry point - but #57226 (comment) confirms that that's not the intended semantic, which means "main" is objectively the wrong name for it.
If it's intended to be true in every graph root - iow, anything ran directly by node AND anything import()ed - then "main" would be an appropriate name - but only if that's the case.
"main" in package.json means "an entry point"
My interpretation of it is that it defines the primary entry point to your package - npm seems to agree with this ref
If [and only if] it's intended to be true in every graph root [...] then "main" would be an appropriate name
I see what you're getting at, I commend your technical correctness
Call it an abuse of notation for the sake of adhering to popular convention, then.
The most comparable case here is Python, the next most popular interpreted language; by your reasoning, Python's __name__ == "__main__" is a misnomer.
I think the popular opinion will be that familiarity trumps correctness, as unsatisfying as that may be.
Node’s most important consumer isn’t a polyglot, though - most JS devs have only ever learned JS. Prior art is important, but that doesn’t mean we should be constrained by what other languages do - and since node has the main field, it has a conflation all the others don’t - so it should also have a solution that all the others don’t.
most JS devs have only ever learned JS
Here's the raw data for the JetBrains State of Developer Ecosystem Survey 2024.
The survey had 23,262 respondents.
Respondents were asked: What are your primary programming languages?
The survey data suggests that 65.3% of 'primary' JavaScript developers consider (at least one of) Java, PHP, Python or C# to be one of their primary programming languages.
Excluding PHP from the above list (it doesn't follow the main paradigm), we are still left with a 53.8% majority.
I am happy to accept arguments that the data may be skewed due to collection methods / circumstances, though.
since node has the main field, it has a conflation all the others don’t
I'm still strongly of the opinion that the main field corroborates with import.meta.main, and would not be a conflation
Whatever else main might mean is not super important here. The definition of import.meta.main is the same that Deno defines here: https://docs.deno.com/runtime/reference/deno_namespace_apis/#import.meta.main
Also Bun. https://bun.sh/docs/api/import-meta
If people didn’t have problems with require.main I don’t see how import.meta.main is going to be a problematic name? I see it less so for aligning with what other runtimes do (it would be an unfortunate situation if it becomes “whoever gets to this name first gets to dictate the API, no matter the API makes sense or not in other runtimes”), but more so for bringing import.meta on par with what require offers (we already have import.meta.resolve for bringing it on par with require.resolve, for example).
Lots of us had problems with require.main, it's just that there wasn't any point in trying to change it - also, import() didn't exist.
I don't mind the functionality at all, I just think "main" is a very bad name for it.
Do you have specific examples of people having problems with the name of require.main in the past?
Also I am not sure how import() changes anything here - many think of import() as "asynchronous require()". If one has been using require.main, it would be unlikely for them to think of the module doing import() as "main" because that is not what require.main is - instead they are called "parent" (or what people hoped require.parent to be even though that's not reliably implementable). It seems pretty subjective to call the module doing import() "main" or even "entry point" - in Node.js code base internally, "entry point" is interchangeable with "main" and only refers to the module being run as the starting point of the application, not any module initiating import(). Outside of internals, require.main and process.mainModule have already been well known for a long time.
I mean, no technical problems - a name's a name - but it's caused confusion, and the broader ecosystem started preferring having separate foo and foo-cli packages rather than having a dual-purpose file.
I still think that it's an antipattern to have a dual-purpose file, but if there's use cases then so be it - I just hope we can pick a name that's unambiguous instead of cargoculting require.main, or other non-node runtimes.
I personally would be fine with import.meta.isEntryPoint / import.meta.isMain (personally I find it to be better than import.meta.main being a boolean, which isn't very intuitive about its type and diverges from what people expected from require.main - an object). But then if other runtimes have taken import.meta.main it seems worse to invent a new one and have users check both in their code. That's probably a flaw in the WinterTC/WinterCG import.meta registry process (whoever gets to it first gets to dictate the API) but that's a different topic.
Yeah, I totally agree that import.meta.main is not the best name, however:
But then if other runtimes have taken
import.meta.mainit seems worse to invent a new one and have users check both in their code.
This, to me, it's the most important part!
That's probably a flaw in the WinterTC/WinterCG
import.metaregistry process (whoever gets to it first gets to dictate the API) but that's a different topic.
Yeah, ppl should use some sort of namespacing when trying new things. Otherwise we are going to have the SmooshGate and globalThis again. We must learn with previous mistakes.
Let's please not make interop worse by coming up with a different-name-that-is-not-main-but-means-the-same-thing. We already have that problem with import.meta.dirname / import.meta.dir and import.meta.filename / import.meta.file.
From the description for the WinterTC import.meta registry:
Because import.meta is a kind of global namespace, and because implementations
are permitted to put whatever they like there, there are potential interoperability and
code portability challenges introduced for developers if runtimes and frameworks fail
to coordinate their use of the namespace. Therefore, the WinterCG has established
this registry to serve as a coordination point.
WinterCG compliant runtimes should never use an import.meta property that conflicts
in definition with any of the properties included in this registry.
Likewise, implementations should try their best to avoid adding new import.meta
properties in their implementations, or to this registry, that conflict or overlap with other
properties in this registry. Given that such conflict/overlap may not be always avoidable,
registrations here should take care to describe when/where/how such properties differ
from others they conflict/overlap with.
Yes, I do happen to agree with @ljharb that main likely isn't the best choices of names for this and I've had to explain a few too many times what the difference between the main entry point of the app vs. the main entry point of a module, but sadly import.meta.main is already in use in production systems and it would simply be counter to interop and cross-runtime compat to introduce an alternative name that means the same thing.
In the future, my hope is that runtimes and frameworks will actually leverage WinterTC as a coordination point before introducing new import.meta properties into common use.
That's probably a flaw in the WinterTC/WinterCG import.meta registry process (whoever gets to it first gets to dictate the API) but that's a different topic.
It's an artifact of the way TC-39 defined import.meta. Per the TC-39 spec It is entirely up to implementations with no guidelines at all. Runtimes started adding things to import.meta before WinterCG/WinterTC existed. The registry just reflects this reality and hopefully serves as a coordination point moving forward... and the fact that it was started when Winter was still just a Community Group with no actual ability to publish a normative standard, the registry is about the best we were able to do. Now that Winter is a full technical committee we can likely publish a normative spec imposing more of a formal process to introducing new properties but there's nothing to stop runtimes and frameworks from simply ignoring that and still doing whatever TC-39 allows them to do.
This idea of "whoever gets to it first" is a feature of literally every registry system. Want to register a particular mime type? Want to register a new standard HTTP header? Just be the first to do so at IANA.
So could we agree with import.meta.main? There is some sort of ritual to approve things in Node? How do we proceed from here?
Someone needs to send a pull request to implement it. If no one blocks the PR, then one just deals with other kinds of code reviews, get the CI green, etc. and get it landed. If a collaborator blocks, and no middle ground can be reached, it can be escalated to a vote in the TSC. But most of the time it's expected that things just get solved on GitHub without escalation.
You can find the details about how to make a change in https://github.com/nodejs/node/blob/main/doc/contributing/pull-requests.md
FWIW #32223
+1 just hit this myself. A few unsolicited thoughts:
So could we agree with
import.meta.main? There is some sort of ritual to approve things in Node? How do we proceed from here?
Based on my outsider read of this thread there appears to be consensus, albeit with caveats.
That's probably a flaw in the WinterTC/WinterCG import.meta registry process (whoever gets to it first gets to dictate the API) but that's a different topic.
Strong agree here @joyeecheung 💯 I would urge early investment in WinterTC related processes as it will simply continue to happen.
It's an artifact of the way TC-39 defined import.meta. Per the TC-39 spec It is entirely up to implementations with no guidelines at all. Runtimes started adding things to import.meta before WinterCG/WinterTC existed.
@jasnell makes sense. Are there any existing procedures for when something from – say – ECMA-402 impacts ECMA-262 (or vice-versa)? Cross TC coordination seems like it'll be more important than ever.
Both of those standards are handled by TC39, so those conflicts are generally discovered during the proposal process.
Two things.
- Yes, let's get
import.meta.mainimplemented here with the established semantics. - Yes, we can absolutely ask WinterTC/TC-55 to pick this up and coordinate with TC-39 on it.
@ljharb of course, but as we know those processes evolve over time as the complexity of the work & the organization grow; that's all I was getting at
Coincidentally I was reminiscing over a bit of the history behind the staging process with the Zarr spec folks yesterday
As TC-55 ramps up I'm glad that sort of evolution will be top-of-mind 🤝💯
In the future, my hope is that runtimes and frameworks will actually leverage WinterTC as a coordination point before introducing new
import.metaproperties into common use.
Node is unarguably the dominant non-browser JS runtime. If 5+ years ago, Node (as in the team behind it) expressed the concerns that have been raised here, and proposed a better alternative, I'm sure that the whole JS ecosystem would have followed suit.
However Node simply refused to support this feature, and because of this, it didn't do much to participate to the conversation about this property. To me it doesn't seem fair to criticize the rest of the ecosystem for a decision that you (Node) decided not to be involved with.
Regardless of what the standardization process is, the standard doesn't matter much if most of the community decides to follow certain practices. This should be glaring, when one remembers the times of the old Internet Explorer, or even by looking at all the non-standard extensions of the many JS VMs, browser APIs, runtimes, languages that transpile to JS etc.
The best solution I see for the future, is for Node to be more open to listening to the requests of the userbase, and to participate more actively to the conversations about JS. You have the strength to steer the whole ecosystem to the solution you prefer, but only if you acknowledge the issues.
By the way, almost four years ago I left a comment to the original import.meta.main issue, and I'd like to apologize for it. I still believe in its core meaning: that this feature has been ignored for years due to opinionated preferences of members of the Node team; this frustrated me and it still does. However my tone was out of line.
I think there is a misunderstanding about the governance structure of Node.js here:
If 5+ years ago, Node (as in the team behind it) expressed the concerns that have been raised here, and proposed a better alternative, I'm sure that the whole JS ecosystem would have followed suit.
Node.js has a contribution-based collaborator base. It consists of 100+ of contributors, mostly volunteers, with various activity level based on their personal bandwith. Contributors come and go. The people chiming in 5 years ago on the matter can be a completely different group from the people chiming in now. The person blocking a PR years ago might no longer be a collaborator anymore due to inactivity.
However Node simply refused to support this feature, and because of this, it didn't do much to participate to the conversation about this property.
I wouldn't say it's refused, but more like stagnated. It's typically how things happen when something gets pushback from at least 1 of those 100+ collaborators, and nobody is motivated enough to or know how to navigate through the situation. Then it gets stalled. That happens quite a lot in Node.js. Sometimes things get lucky and some people are interested in picking something stalled up again, and push it through the consensus seeking process (I did some of that myself, so I know very well that something getting blocked years ago by a few individuals - who may or may not be active now - doesn't mean it's refused, just means it needs more work to go foward). Most of the times they just get stalled due to lack of volunteers. Which had been happening to #32223 and now there's another volunteer looking into it in #57804
The best solution I see for the future, is for Node to be more open to listening to the requests of the userbase, and to participate more actively to the conversations about JS. You have the strength to steer the whole ecosystem to the solution you prefer, but only if you acknowledge the issues.
While that may be ideal, bear in mind that Node.js is a volunteer-based project, and no single individual speak for the project. The solution suggested implies to have some workforce to actively drive the proposal in Node.js, seek consensus from other collaborators, bringing it back to other venue, and most importantly, do not give up when 1 out of the 100+ collaborators push back, and instead find compromises or call for a TSC vote to avoid a standstill. That's a lot of work that would be hard to count on volunteers to take on. Making "you" or "Node.js" the culprit for inactivity is not really motivating volunteers to step up to do it, in my experience. (That's why Node.js has a extremely flaky CI for 7+ years to even keep the development process afloat, for example, due to lack of stable volunteer efforts to maintain it). People typically step up when they are personally interested enough or they get paid by some company to do it, not because people they don't know from the Internet tell them that they must do this work (for free).
Also as you can see both in this thread and in past threads, there are many collaborators acknowledging that it would be better to have it than not, while there are also some collaborators speaking against it. I don't think it's correct to generalize the stance as "Node.js does not acknowledge it", a more accurate description would be "a few out of the 100+ collaborators in Node.js support it, a few out of the 100+ collaborators in Node.js opposed it, nobody volunteered to drive for a compromise or call for a vote, so it remained indecisive for 5 years". In the meantime, other projects controlled by a company and maintained by their employees can be more decisive when they want to push something in their runtime, because they do not have to seek consensus from the 100+ collaborators who are not bound by contract to the same company. This would be difficult to change, and many would still prefer that Node.js does not get controlled by a single company. If you want to see more work to be done in Node.js for this, the most effective way is to either start doing that work yourself, or get companies to pay for this work, than calling other volunteers to do it.