| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
Sorry, something went wrong.
|
Review requested:
|
Sorry, something went wrong.
This used to exist, as --entry-type. It was removed in favor of --input-type, because of a cascade of UX problems. Please review that history, and if you think you have a way to avoid all the problems cited against --entry-type, then we can consider this.
I think this is possible already? node --eval 'import("./2.mjs?key=value#hash")'This is as short as it is because dynamic import() is allowed in CommonJS, allowing us to skip --input-type=module. So I guess the ask is to make this even shorter and more obvious. But before getting to that, what is the use case for this? Why is this common enough to justify a new CLI flag? If this is an edge case, then the existing solution is arguably good enough. |
Sorry, something went wrong.
If 2.mjs or any of its dependencies is using top-level await, it would lead to surprising behavior and hidden errors. I wouldn't recommend doing this. node --input-type=module -e 'import "./2.mjs?key=value#hash"' is a much better alternative – but does not address the linked issues. |
Sorry, something went wrong.
What surprising behavior? What hidden errors? Let's please not spread FUD.
What linked issues? I read #49204 but I don’t really see a use case; just “I want to be able to launch Node with a URL” but not why the user wants to do that. I don’t think we should be adding complexity for the sake of adding features, only for adding use cases. What’s the use case for launching Node with a URL? |
Sorry, something went wrong.
AFAICT the linked issue objected --entry-type's behaviour specifically. This option:
If there are problems that are applicable to this version rather than to --entry-type implementation, please point on these specific problems so we can see in which direction it might be improved.
Exactly, this is possible with import, import(), and --import. In fact, this is necessary: we can't use path string in import until we at least encodeURI it. Supporting search and hash here allows to do parameterized imports such as const hmac = await import('./crypto/myHMAC.mjs?algo=SHA3-768'), import { db } from './flexbb/mydb.mjs#customdbprefix_', etc. But it's import, not direct load. The question is, if we have this for import, why we don't have this for main entry point? const params = new URL(import.meta.url).searchParams;
if (!params.get('algo'))
throw new Error('algo must be supplied!');
if (import.meta.main) {
console.info(`Oops, this should be imported!\nUsage info: ...`);
process.exit(-14);
}
// [implementation]or become #!/usr/bin/env node
const params = new URL(import.meta.url).searchParams;
// TODO: allow algo to be provided as command line argument
if (!params.get('algo'))
throw new Error('algo must be supplied!');
// [implementation]
if (import.meta.main) {
const autogeneratedKey = makeNewKey();
const hash = myHMAC(process.stdin, autogeneratedKey);
console.log({ autogeneratedKey, hash });
}Assuming import.meta.main is supported, its code is reachable and it could be reached directly as node-49295 --module https://example.com/myHMAC.mjs?algo=123 (or as deno run https://example.com/myHMAC.mjs?algo=123). So, how do I at least test this part with Node.js, without writing a huge loader hook to somehow set import.meta.main to true? Also, how do I load a any module over https: or data: protocol as main module? |
Sorry, something went wrong.
See the --entry-type “cons” listed in nodejs/modules#300 (comment):
I would think that your proposal shares these issues? With regard to the last one, yes the query params stuff is a use case but that points toward a different goal, of having Node treat the entry as a URL rather than a path string, which is separate from forcing it to be treated as ESM. There are other ways to solve the “treat as URL” request, such as the options mentioned above, or we can propose new ways. Something to consider is that Node already supports Wasm files, and someone might want the equivalent “interpret the entry as a URL” option for a Wasm entry. So --module isn’t really the right name if that’s what this flag is doing; it could be --entry-url, for example, to opt into parsing the entry the same way that --import parses its value. Another option is to just allow running node with no entry point, like node --import entry.js?foo=bar. Currently this runs the imported script and then opens the REPL, but we could perhaps change that. |
Sorry, something went wrong.
Also, if users want to misuse it and bypass type resolution, they will do that anyway. They will find a way like cat file.js | node --input-type=module, node --import "data:text/javascript,$(cat file.js)", etc. But in case of --module, we can at least print warning or hint; while tampering with file contents is impossible to detect.
This is argument against --entry-type misuse. --module is not intended for usage with CJS-first projects, and overriding type for them won't be useful. However,
Agreed. The reasons why it's a combined option are:
IMHO this is still more intuitive and more convenient that resolution algorithm that handles URL specifier differently depending on its protocol. Of course, if I'm missing something and there are more reasons to apply path-oriented resolution here, it's possible to rework it into --entry-url.
Right, this approach is applicable for Wasm entry points, too. The choices I see here are:
This probably wouldn't work. There can be multiple --imports, it's misleading to say "import" for main entry point, and opening REPL with --import ./temporal.mjs --import poteto --import https://example.com/map-emplace-polyfill.js is valid usecase. After checking for related issues, I'm linking #46009 as one that would be fixed by this PR, let me know if this is incorrect. |
Sorry, something went wrong.
I don’t see how any of these points address the issue. In a folder with no package.json, if you run node --module a.js, and a.js imports CommonJS file b.js, which then calls require('a.js'), does Node error on a.js being an ES module that can’t be required? Or does Node try to evaluate a.js as a CommonJS module? Yes this is a contrived example but it’s illustrating the point that you have two files in the same folder with the same file extension being treated differently; the only way this application could work is with the knowledge that a.js needs to be run with --module, which feels a lot like a footgun. This potential source of confusion was why were leaning away from --entry-type toward --package-type, a flag that would set the current package scope to be either CommonJS or ESM (so node --package-type=module entry.js would more or less be equivalent to echo '{"type": "module"}' > package.json; node entry.js). Ultimately we didn’t end up shipping that either because there weren’t really any use cases for such a flag (if I remember correctly). I think there’s an argument to be made that it would be nice to launch Node with an entry point that’s a URL. I just don’t know if that argument is compelling enough to justify creating a new flag, when you can achieve the same via node --input-type=module --eval 'import <url>'. The idea of using query params as a way to pass input into a module feels very hacky to me; like a workaround for just importing a function from the module and running it. |
Sorry, something went wrong.
These points mostly address the issue being an issue. What is practical benefit of having same type for same extension in same directory? I can see two factors:
Both are not working by default because 2.js can be a symlink pointing at file in any scope, or become symlink at some point, and it will be resolved using that unknown scope. On top of that,
Moreover, this "rule" contradicts with the idea of having /usr/bin/ populated with a mix of .js, .mjs and extensionless files that all behave differently because they all are symlinks to scoped files installed by package manager. Tl;dr I think that for URL-based resolution (that is native for module and not native for commonjs) the very concept of "being in same directory" doesn't work as good as it did with cjs-only ecosystem, and this rule shouldn't be enforced here.
Simple: main entry must be evaluated as ESM and imports must be evaluated as usual. If there is a chance that it may happen in real life, we can probably detect this and print a warning. This usage doesn't seem to be intentional to me, unless for some reason they want to do some weird trick like (() => {
try { 09; } catch { return; }
/* let's use sloppy mode magic! */
})();
If someone designs an application that relies on the fact that a.js must be evaluated as ESM and then via circular dependency again as CJS on purpose, they don't even need a footgun. They probably already did this using ln -s a.js a.mjs with --preserve-symlinks or using data:text/javascript,$(cat a.js) or with a dozen of similar workarounds.
The alternatives to proposed --module behaviour are exactly this: to either do it in --entry-type style that will override type for "file" (or, to be more precise, with the module with URL specifier; IMHO mixing it with CJS-style concept of file on filesystem is the actual source of confusion here) whenever it's imported again and be ambiguous with type specified in package.json if any; or to be like --package-type (which wouldn't work well as we must specify scope which is applicable only to file: modules). The proposed option doesn't share these issues because it does not mess with any imports at all, it's only applied to main entry point. Also it wouldn't endorse developers to design "packages that work only with this option", as all imports in package will continue working the same, and importing the package as dependency will also work the same.
It's not the same as it's not the main entry point.
This concern might go offtopic but the idea of using querystring to parametrize a module in JavaScript feels even more natural than process.argv and process.env to me, since it works the same in browsers and other runtimes (and in Node.js itself, but only for imports and not for main entry point for some reason). "Just importing a function" doesn't always work. Let's say, when we import myHMAC.mjs?algo=sha9, it performs a process of initialization, e.g. collecting enough entropy for autogenerating salt, loads certificate chains from CA for signing functions, pre-allocates buffers, etc. |
Sorry, something went wrong.
You’re essentially arguing that what many people in that earlier thread found confusing isn’t actually confusing. It’s not a “rule” that we decided upon back then; it was a question of “if we allow this override, will users be confused by how it behaves?” and the consensus was that users would be confused; and the benefit of the override didn’t outweigh the cost of the bad UX. I think that that still applies today. It would be nice if Node’s entries were always treated as URLs, the way they are for --import, but we just can’t make that change because of backward compatibility. As for network imports and so on, Node cares about file extensions; our version of a network server that decides what Content-Type MIME to use is to read the file extension.
Imports have no way of knowing whether they’re the main entry point. We don’t support import.meta.main. The bar to adding new flags should be high, as it’s both bad UX the more we add and it adds downstream complexity, like in this flag’s case how this special “treat as module override” is supposed to carry through into the resolve and load module customization hooks. This proposed flag seems messy in that it’s trying to solve two possibly unrelated use cases, that haven’t been common asks; and there are existing solutions to achieve equivalent or very similar results. I would suggest that you take a step back and focus on one use case or the other, and open an issue asking for ideas to achieve it, with some that you’ve considered. Building new features is a search for consensus, both that the problem deserves to be solved and the use case supported, and also agreement that your proposed solution is the best one. This PR seems like a jump straight to the end of the process, which can sometimes work if there are lots of people already in agreement on all of the decisions that you made on the way (both that the use case deserves support and that this is the best solution) but based on the prior art around --entry-type and the lack of others commenting on this thread, I don’t think that that consensus is here at the moment. |
Sorry, something went wrong.
Please point it out where many people found confusing about the behaviour proposed in this PR. What was discussed in that earlier thread is --entry-type, not this.
This is correct and this is why I propose a command line option instead of changing how node's first positional argument is interpreted.
This is wrong. Node.js's import only cares about Content-Type header, and there is no separate MIME types for ESM and CJS.
We don't support it yet. In fact, I think it would make sense to ship this option together with the introduction of import.meta.main.
Please point out which parts of proposed implementation are confusing or too complex; perhaps we can solve the specific issues. For resolve hook, it works like this:
For load hook, it works like this:
So basically, hook gets .format === 'module' hint, gets full URL as specifier, can mutate the result and the outcome will be the same as with regular load. Unless I missed something, it doesn't add complexity and doesn't lower capabilities of hooks.
There are two issues linked: #46009 and #49204. Wouldn't opening a new one be a duplicate?
This PR is mainly to discuss this particular solution and its implementation, and to see exactly how complex it is, is it feasible, are there any unobvious nuances (right now I see that it needs adjustments in how process.argv is built, but otherwise it looks stable), or is there a better approach code-wise. I'll look into possibility of making --entry-url alternative since it looks less controversial, although for now I'm
Agreed. Of course there is no rush, I understand that consensus, enough of time and enough amount of explicit approvals is required; as well as possibility of new alternate solutions, or bugs that can't be fixed with this approach. If you feel like the part of discussion that is about issue itself and alternate solutions should be continued in any of the linked issues or a new one, I wouldn't mind to move. Or maybe you would like to suggest something to get more feedback? Either way, thanks for discussion so far, I hope it will serve as good ground for everyone else to agree, counter, or propose a new suggestion. :) |
Sorry, something went wrong.
There was a problem hiding this comment.
I’ll just make this explicit: we shouldn’t have a single CLI flag that does both force the Node entry point to be treated as an ES module and force it to be interpreted as an URL string. The first part was already proposed and rejected in --entry-type and I don’t find the arguments listed here persuasive in light of the previous discussion.
With regard to forcing the entry to be parsed as a URL string, I’m not opposed per se but I’m unconvinced that we need such a feature. I’m not sure that such an ability is worth adding a new flag for. The discussion in #46009 was more one of surprise that Node didn’t support URL strings, not a suggestion that it begin to do so (since apparently it can’t be done without adding a new flag). No one on that thread suggested adding a new flag to support such a feature.
Sorry, something went wrong.
The benefit is not to help humans disambiguate the type of file – for most JS files, any JS dev can very easily guess if it's CJS or ESM at a glance without looking at the file extension or the related package.json – it's for the computer: by having a set of well defined and stable rule to tell if a file is CJS or ESM, all the tools of the ecosystem that follow the same rules will agree on how such file should be parsed. The goal is not to have a implication "same extension + same directory => same type", it's to have a deterministic way to tell if such or such file should be parsed as ESM or CJS.
It's possible to write code that can be parsed as either ESM or CJS, and output different things depending how it's parsed. // a.js
import('./b.js');
console.log(`Hello from ${this===undefined ? 'ESM' : 'CJS'}`);// b.js
require('./a.js');$ node ./a.js
Hello from CJS
$ node --module ./a.js
Hello from ESM
Hello from CJSIf we introduce a way for someone to parse twice the same file, given enough time someone is eventually going to fall in that trap by accident, and it's probably going to be very confusing and hard to even understand what's happening.
CJS is simply not supported with network imports and data: URLs – because there is no MIME type for CJS I believe, and CJS-over-the-network does not seem like a good idea anyway. |
Sorry, something went wrong.
|
As a user, I rather like this option & how it gets Node into such a WinterCG mode, makes it behave more like how modules behave everywhere else on the planet. It seems like a big win. It also resolves #34049 which is one of the longest saddest issues around, which has had more issues merged into it than is to believed. At a start we have #32316, #33223, #37512, #37848, #41522. And address https://github.com/orgs/nodejs/discussions/37857 . It seems like the opinion is turning against this idea. I'd beg to please let us do something. It keeps seemingly like every option has some kind of problem, and that lets us excuse getting no where. Or we stand on precedent. @GeoffreyBooth:
But to quote a user in 34049,
The resolution in nodejs/modules#300 created a disaster. A huge trail of making it impossible to create esm escripts in node.js. Users have been begging for redress for years. The 5 issues I listed above & long story of 34049 itself are a testament to how clearly & obviously help is wanted. If not this PR... one thing that's still super unclear to me is why --input-type is restricted to stdin/eval. Quoting @GeoffreyBooth in nodejs/modules#300 (in two different comments):
The result of 300 created an impossibility to create esm scripts. It sure seems straightforward & obvious to users to expect --input-type to just work, but it doesn't. This PR seems like an even better option that also enables a more cross-platform like behavior from Node. I don't have a strong opinion what solution there should be, but I'd really like to see some resolve to let scripts be ESM, and not need special extensions to do it: very few people write extensions on their binaries, and it should not have to be. Please, I beg that we make progress in this arena; it feels like the call keeps being heard then everyone decides, no, it was decided a long time ago & we're sticking to that. But 300 created a disaster. |
Sorry, something went wrong.
There are multiple use cases being condensed here:
There’s probably not a single solution that works for all of these. There are many problems with --entry-type that --module type shares; the most problematic from my point of view is that in our experience, users expect the “force as ESM” aspect to continue to imported files, which it wouldn’t. But that’s not the only issue, they’ve all been rehashed above. The solution for extensionless script files, like that would get a shebang line, that seems most promising is to ship a second binary like node-esm or something so that you could do a shebang like #!/bin/node-esm. This other binary could have ESM-first behaviors such as interpreting the entry point as a URL and interpreting extensionless files as ESM. This would allow us to essentially flip the defaults without breaking changes. This solution still wouldn’t provide the “force ESM” feature but I’m not sure that that feature has value per se; it seems to be requested more as a means to an end to do things like run extensionless ESM scripts or launch URLs. |
Sorry, something went wrong.
|
I agree that having a separate binary would be a good option. I'm affiliated & partial but #37512 seemed promising.
It just makes me very sad that imagined concerns about users misusing what seems to so many here like a straightforward simple ask that would help improve a half decade old solution is the primary objection. Slap an experimental prefix on the flag & let it sit for two years & find out. #NodeFwd, then now and always. You're right that there are multiple intertangled of concerns, and your breakdown is great. I realize there's a balance in trying to not create more problems as we go, trying to create consistency & clarity, which requires extreme dilligence. And I'm so happy there are so many sharp knowing minds able to break these problems down. My impatience & sadness over trying to create standalone ESM scripts has been growing for a long time however, and I want the impulse to make happen to have a voice, which I don't see in so many PRs. |
Sorry, something went wrong.
|
Did i read that right? The bar of adding new binary is lower than adding new command line option?
|
Sorry, something went wrong.
no poll, survey, or community involvement, was used to reach such consensus, IIRC, and this issue and PRs keep piling up since about ever. the energy spent to re-hydrate and re-enforce discussions born around the "must use .mjs extension" era, still something mostly nobody cares about in the real world, or in other runtimes (Deno, Bun, gjs who decided to break everything until they move to ESM for GNOME 45), is amazing ... are you all folks really convinced this topic is over because .mjs was introduced years ago? It's quite entertaining to see same effort over and over again, but alternatives are becoming more popular and current (frankly stubborn and dystopian) position around this topic never has been beneficial for anyone involved in this project, or actual users demanding ESM opt-in default for years (which dare I say it's the standard ECMAScript suggestion, btw). Other runtimes got this solved, the main project around JS in console can't move forward (ever, apparently) about this topic ... I know I am adding nothing to this issue, but the fact it keeps coming up with actual PRs that "just works" and get rejected is something to follow/study/entertain current state of JS around teams/projects. |
Sorry, something went wrong.
No. A binary is the only solution for the extensionless shell script use case, because many platforms don’t allow flags in shebang lines. We’ve never added a new binary before, so it might very well be a nonstarter more broadly. We would need to find some way to create it such that it doesn’t double the size of the Node.js download, for starters. I’m sure people would raise other concerns. The tone on this thread is feeling more argumentative than productive. The solution proposed in this PR has been proposed before and rejected before, so I suggest you move on. Issues can be used for discussing use cases and ideas for solutions. |
Sorry, something went wrong.
you don't need a binary at all if there is a flag ... you need a regular bash / sh script, executable wherever node lands, that starts node with that flag on ... this argument about double binary needed, but no flag wanted, doesn't really look like a smart argument from the very smart people behind this project, sorry. |
Sorry, something went wrong.
|
@WebReflection can you please phrase your argument in a way that doesn't sound insulting?
That's already possible, a dedicated flag would simplify said bash script, but if you're fine making your own executable nothing's stopping you to make it work already. But I think most folks asking for this would actually want to not have a bash script. |
Sorry, something went wrong.
Then what, exactly, is the proposed alternative? I guess there is consensus on the fact that this PR addresses two, partially related, issues. This fact comes to be the main blocking reason. Your suggestion is to extract the "entry-url" part of it, which is reasonable and feasible on its own. But then what do we do with the "entry-module" part? Do we reintroduce --entry-type, do we ship node-esm, do we replace --entry-url with --entry-module, or do we do something else?
Which shebang-supporting platforms don't allow flags in 2023?
This is PR thread and this is about specific solution, so questions I'd like to see addressed here are:
I think it would be more productive to see how we can fix the issue and what is the best solution and then implement the best solution; rather than blocking all possible choices just because each of them has its own cons. |
Sorry, something went wrong.
Yes, an --entry-url flag that opted into treating the CLI entry point argument as an URL string rather than a path string would at least not cause breaking changes and avoid UX issues, since the regular resolution algorithm would apply. I’m not sure it wouldn’t have its own issues, but it’s at least something that hasn’t been proposed before and doesn’t have any obvious problems that I’m aware of.
By “the entry-module part” I assume you mean what --entry-type did? I don’t see any way to support that that makes sense, for the reasons listed above and in the linked issues. But it’s also a means to an end, not a use case in itself. If you want to force an arbitrary file to be treated as ESM, you can use module customization hooks to do so. I don’t think Node needs to provide a built-in low-level API for this, especially considering all the problems that would result if we did so. It would be nice to have a built-in way to run extensionless ESM scripts, and there are several old issues debating various approaches for this. The binary solution came from one of them; to my recollection it’s the idea that seemed the most feasible, though it’s already possible today if you’re willing to add some BASH code. Search for “shebang ESM” in this repo and you’ll see the various old issues, such as #32316. |
Sorry, something went wrong.
|
@GeoffreyBooth beside the fact I kinda like the "go big or go home" you suggested, you mentioned me so I'd like to clarify:
it's not me eventually being correct, it's explained in the official gnu documentation but I haven't tested it across all possible OS or WSL although I expect these having an env greater than 8.30 or equal to it. if anyone is willing to give it a shot, my example should just run node and show its version, then exit instead of hanging out foever or throw errors. #!/usr/bin/env -S node -vas complete test: TEST_NODE_ESM='/tmp/node-esm'
echo '#!/usr/bin/env -S node -v' > $TEST_NODE_ESM
chmod +x $TEST_NODE_ESM
$TEST_NODE_ESM
rm $TEST_NODE_ESM |
Sorry, something went wrong.
As I already wrote in #34049 (comment), busybox env (used by default e.g. on Alpine Linux) doesn’t support -S. The same for OpenBSD env and NetBSD env. This is a non-standard GNU option, i.e. not defined in POSIX or similar. |
Sorry, something went wrong.
Thanks. Yeah I suspected there was still some strong reason why a flag wouldn’t work all on its own. However I think we can have both: the binary is only required for the shebang use case, whereas the flag covers a whole bunch of others. I would suggest starting with the flag as that’s relatively straightforward and not novel like creating a new binary, and once the flag ships we can investigate the binary option. |
Sorry, something went wrong.
|
@jirutka apologies for not reading that comment out of a different issue but thanks for sharing that ... like I've said I just tested locally and based my assumption out of official gnu documentation (9.3 there) but I wonder at which version busybox or other mentioned OS are, when it comes to env, and if there's hope they'll ever update to the current gnu (standard?) behavior. |
Sorry, something went wrong.
#34049 is directly related to this PR (and it’s linked in its description).
The latest version, they simply don’t use GNU implementation of env.
What, why should they? GNU coreutils (the source of GNU env) is just one of the multiple implementations of basic unix utilities, it’s not a standard that everyone should follow. The standard here is POSIX. |
Sorry, something went wrong.
|
@jirutka I haven't read all comments linked to this ... my bad.
for the same reason every other OS solved this issue, in a way or another, or never had it to start with ... are you saying those OSs decided explicitly to never be able to have multiple arguments @ shebang line/definition? edit all I could find is a standard from 5 years ago that suggests env only has a -i flag although examples look like it would support multiple arguments there, if I'm not mistaken ... so, if that's the case, it's a matter of creating a different first line of the file to use the flag accordingly with the target which is still easier at both build time and distribution (I suppose). If instead these OSs decided this issue should never be solved I rise my hands but still agree solving with a flag first would be already a win. If those OSs are used mostly in Docker and Serverless though, I suppose they can also use a {"type":"module"} at the root of their node execution if desired/needed, solving this issue in a way or another. |
Sorry, something went wrong.
By every other you mean GNU(/Linux) and OpenBSD…?
They probably didn’t decide that, they just probably didn’t feel the need to add another option and diverge from the standard. I’m not saying this option isn’t useful and others shouldn’t implement it, just stating the fact that it’s not as universally supported as some people in this PR think it is. And I definitely didn’t mean it as an argument against adding --module <path> option. Tbh, I got a bit lost in this PR, so not sure who is proposing which variant. |
Sorry, something went wrong.
|
@WebReflection and @jirutka can you please take this discussion of shebang stuff to #49407 or a new issue? This PR is about a --module flag. |
Sorry, something went wrong.
Sigh, Alpine Linux might be often used in containers, but OpenBSD and NetBSD definitely not.
Exactly, and the --module flag is gonna be used in shebang… It’s tightly related, not a separate issue. |
Sorry, something went wrong.
By not getting approval do you mean absence of explicit approval, verbal disapproval, or explicit block on the PR?
This sounds great and I'd absolutely like to see the end goal of flipping defaults in semver-major release to be reached.
In any previous issues I couldn't find the reason why we don't want extensionless Wasm anymore. Can you elaborate?
Please elaborate this as well.
Can we generalize this to any files with no explicit type, including those that do not belong to any existing package.json? Sure, there is this question. |
Sorry, something went wrong.
Block. There are many footguns created by overriding just the entry point and not the larger scope. This has been explained numerous times above and in linked issues. I would consider what was earlier proposed as --package-type, which essentially overrides the "type" field of the nearest parent package.json (or of the default, if there is no package.json) but it has some of the same footguns and others might block it.
It’s not that we don’t want it, it’s just that it’s unclear how to achieve it. Right now, extensionless files are parsed as CommonJS. Early on we had them controlled by the "type" field, so that in a "type": "module" package scope they would be parsed as ESM, but we had to remove that ability because of compatibility problems with bin scripts. So if we want to change how extensionless files are parsed in ESM under this new flag, we’d need to find a solution that somehow permits bin scripts to continue to work. Breaking all of them will hinder adoption of this new mode, and discourage the team from ever making it the default. Once we find a solution for how Node should know to interpret extensionless files as ESM, we would need to find another solution for permitting extensionless Wasm. One idea that was proposed was that if an extensionless entry point file failed to parse as a string, Node would fall back to trying to detect if it had magic number (the first few bytes defining the type of the file, like how PNG starts with the letters PNG and Wasm starts with \0ASM). There are also out-of-band solutions, like "type": "wasm" was once proposed but I dislike that idea. We should figure this out from the start, because it's tricky to add later.
I’m not sure we need --entry-url at all, regardless of whatever else lands. It feels like extra complexity for an extremely niche use case that can be achieved via other means. All I mean is that the entry point string would be interpreted in the same way the value of --import is interpreted. So you could have a Node entry point that’s a data: URL, etc.
I don’t know what you mean. Generalize what to any files with no explicit type? Currently every package.json file defines a “package scope,” the folder that the package.json is in as well as any subfolders that don’t themselves contain a package.json which would start a new scope. For any package scope where the controlling package.json lacks a "type" field, Node behaves as if there was "type": "commonjs" for backward compatibility. This is the default that we would flip: the lack of "type" would be interpreted instead as "type": "module", meaning that any packages relying on the other default would need an explicit "type": "commonjs" to restore the previous behavior.
Since this mode is opt-in via flag it wouldn’t break anything yet, which gives the ecosystem time to react and update to this potentially becoming the new default. The npm registry already inserts a few extra metadata fields to every installed package’s package.json; it might be feasible to ask them to insert "type": "commonjs" to packages that lack a "type" field, or updated package managers themselves could do so; in the short term it would be pretty trivial to write a CLI tool that crawls all your subfolders to find package.json files lacking "type" and add "type": "commonjs". Shipping the flag first and announcing the intention to make this the default eventually puts the ecosystem on notice to make these fairly minor updates to prepare for the change. |
Sorry, something went wrong.
|
Another proposal along these lines would be one of the previous hopes that we could switch the default operating mode of Node.js at some point in future to an ESM first behaviour. Effectively Node.js today defaults to treating extensionless entry points as CommonJS and to treating packages without a "type" field as CommonJS. These defaults if switched to the corresponding ESM versions would imply not needing a "type" field for modules as .js files and also being able to execute extensionless ESM entry points. If we had a flag like this - node --default-type=module or similar`, that could also help test out such a path to some long term future where such a switch might be possible. |
Sorry, something went wrong.
Is there consensus that in that "new defaults" mode by default extensionless file is ES module?
Why don't we support extensionless files as ESM right now without flags? How about adding it before flipping the defaults so the adaptation can go smoother for the ecosystem? Also, what about unknown non-empty extensions? Should there be any difference in running myfile or myfile.exe or myfile.jabbascript?
Errm, this is self-contradictory. Why support for an extremely niche use case should become default behaviour. 😄
All of this sound good to me. Two more points to clarify:
We should give developers a choice. All users should have awareness, and end users should have easier way to avoid breakage. jq '. += if (has("type") | not) then { type: "commonjs" } end'I'd raise a question "but how to undo it?" but it's also feasible. Also, we have to keep developers informed that some kind of action is required from them, and to keep users informed that they are using a package that wasn't updated and might eventually stop working. I'd suggest the following:
All of this is applicable only to "unflagged" semver-major step and should have prior discussion and consensus with npm and packaging system maintainers and community in general, of course. These last questions are about how do we deal with phase 2 after flipping defaults. It's important to have general outline, but now I'm going back to the phase 1 which is optional flag that doesn't break the ecosystem. After considering all of the above, I agree that this default flipping plan is feasible and on the long run more desirable than what was proposed in this PR originally. I'm glad to see actionable way to proceed, and hope we can move forward with it. Hence, I plan to:
The discussion would be also simplified, as we won't have to have URLs, module types and shebangs tangled together. :) Does this plan sound good? |
Sorry, something went wrong.
That doesn't sound ideal, and would probably have security implications. According to https://webassembly.github.io/spec/core/binary/modules.html#binary-module, WASM modules must start with a magic string (0x00 0x61 0x73 0x6D), in all likelyhood that's what we should use to decide if a file is ES or WASM.
Historically, node would parse anything as CJS unless for .json, .node. When introducing "type":"module", it was decided we should take the opportunity to restrict what extension was allowed in case we want to add support for it later – e.g. do we really want to parse file.ts as ESM? Wouldn't that close the door to support TS out-of-the-box in a later version without being a breaking change? Regarding the whole plan, I'm not sure where I stand. It seems to me that the only POSIX compliant way forward would be to have a separate binary name, and if we introduce a non-POSIX way that happens to work for some wide-spread OSs, it will very likely start to be used on popular npm packages (either because their authors are not aware or they don't care), putting our users in an awkward situation. The status quo (either symlink your extensionless executable to a .mjs file, or use CJS) is portable, and when it errors out it produces relatively easy to understand error message; for most folks, it's an OK tradeoff. |
Sorry, something went wrong.
|
Just did a quick check but I don't see any comments on all the existing
code spawning child_process here. Moving from error condition to a valid
one worries me far less than swapping a default from one valid condition to
another without a clear way to migrate code like things spawning processes
(both from node and other envs).
…On Fri, Sep 1, 2023, 5:51 AM Antoine du Hamel ***@***.***> wrote:
5. We should add fallback to parsing entry point as Wasm if loading as ESM
fails
That doesn't sound ideal, and would probably have security implications.
According to
https://webassembly.github.io/spec/core/binary/modules.html#binary-module,
WASM modules must start with a magic string (0x00 0x61 0x73 0x6D), in all
likelyhood that's what we should use to decide if a file is ES or WASM.
Also, what about unknown non-empty extensions? Should there be any
difference in running myfile or myfile.exe or myfile.jabbascript?
Historically, node would parse anything as CJS unless for .json, .node.
When introducing "type":"module", it was decided we should take the
opportunity to restrict what extension was allowed in case we want to add
support for it later – e.g. do we really want to parse file.ts as ESM?
Wouldn't that close the door to support TS out-of-the-box in a later
version?
------------------------------
Regarding the whole plan, I'm not sure where I stand. It seems to me that
the only POSIX compliant way forward would be to have a separate binary
name, and if we introduce a non-POSIX way that happens to work for some
wide-spread OSs, it will very likely start to be used on popular npm
packages (either because their authors are not aware or they don't care),
putting our users in an awkward situation. The status quo (either symlink
your extensionless executable to a .mjs file, or use CJS) is portable,
and when it errors out it produces relatively easy to understand error
message; for most folks, it's an OK tradeoff.
—
Reply to this email directly, view it on GitHub
<#49295 (comment)>, or
unsubscribe
<https://github.com/notifications/unsubscribe-auth/AABZJI7ITJNZW65MQHZFGF3XYG4Z3ANCNFSM6AAAAAA33IPFKQ>
.
You are receiving this because you are on a team that was mentioned.Message
ID: ***@***.***>
|
Sorry, something went wrong.
Tbh as someone who is using fgrep every day (history | fgrep fgrep | wc -l shows 49 out of 502) in interactive shell but keeps grep -F in scripts, I'd say that sometimes POSIX compliance or most-Unix-way can be counterintuitive. Especially when we also have usecases like node as entry point in Docker. But why can't we just have both options available, command line flag and named binary?
Maybe it would be worth adding to
tsc-agenda
Issues and PRs to discuss during the meetings of the TSC.
to decide, when we have both choices implemented and if there's no critical flaw in them? |
Sorry, something went wrong.
There isn’t consensus, but this very well might become one. I think this is one of the main sticking points to figure out, and deserves its own issue. My recollection of #31415 was that it was done because we wanted to preserve an option for extensionless Wasm in the future, and we didn’t like the "type": "wasm" approach that had landed without much review (and was reverted). The most obvious options that I can think of for supporting extensionless ESM in a new mode are:
Maybe there are other approaches. This should probably get its own discussion, because if we can’t reach a consensus on this aspect, there’s not much point in creating the new mode since one of its primary use cases is to enable extensionless ESM scripts.
I think it’s much simpler: we just add a flag which flips as many defaults as possible to ESM. Once that ships, we can possibly add the binary as an alternate way of running Node in this mode. And at some point, in a semver-major change we make the new mode the default and provide a way to opt into the CommonJS-default mode. We should design things such that it’s not awkward to flip back. I don’t think we need a collection of new flags for various smaller pieces of this. I wouldn’t block such efforts, but I feel like it would be harder to get consensus on --entry-url and --extensionless-type and so on than it would to just ship a single flag that does it all. In general we want to limit the number of CLI flags; I know there are already a huge number but that’s all the more reason for it to not get even further out of hand.
Yes, this was my intent. The current algorithm searches up the current volume to the root, and if it never finds a package.json, it acts as if it had found a package.json at the root with "type": "commonjs". The new mode would just flip that, so the imaginary default package.json would act like "type": "module".
I don’t think so. Changing how typeless package scopes are interpreted and discouraging typeless packages go hand-in-hand. There’s no point in having ./node_modules/some-commonjs-dependency being simply broken; a tool should just go in there and add the missing "type": "commonjs". There are countless dependencies that people use every day that aren’t actively maintained anymore; we don’t want to rely on maintainers to need to go back and publish new versions of packages they haven’t touched in years just to add "type": "commonjs". This kind of disruption doesn’t benefit our users; our goal here is just to make ESM easier to use in Node for end users, not to stamp out CommonJS code from the npm registry. We don’t need to work out the question of how to patch CommonJS dependencies just yet. Whether it’s something at the npm registry level, something at the package manager level or handled by end users directly via a shell command or some script like npx add-missing-package-types, there are many solutions that can work and some of them don’t require acquiescence by other parties. I would build the new mode and ship it as a flag first, so that these other teams can see it with their own eyes and use it, and then they’ll probably be motivated without our prodding to build solutions that they can test against Node in this mode.
I wouldn’t bother doing --entry-url as a standalone PR. There’s a good chance it gets blocked, and on its own it enables very little. I would just do one PR that includes everything, unless that’s too big to handle in one piece (in which case we can use a build flag to hide things until they’re all in place, or land them on main but mark them as “don’t backport” to prevent releasing until they’re all ready). Before we start, though, I think we should reach consensus on the open questions. I’ll open two issues:
There are also questions about how to migrate the ecosystem (adding "type": "commonjs", etc.) and whether/when Node should make this new mode the default, but I would postpone those discussions until we ship the flag. It’ll be easier to reach consensus on those topics once people can see what the new mode looks like. The other thing we need is a list of concrete use cases, and not solutions presented as use cases. As in, “I want to be able to launch Node with an URL entry point” isn’t a use case; why do you want to do that? Whereas something like “I want to write what are typically called shell scripts in ESM JavaScript, where I can have a single file anywhere on my disk that uses ESM syntax and it doesn’t need a nearby package.json file or a symlink or wrapper file in order to run as ESM” is a specific use case where it’s not possible today and the new mode enables it. Ideally this new mode would satisfy most if not all of the outstanding “can’t do it in ESM” use cases in the various issues you’ve referenced. |
Sorry, something went wrong.
@jirutka Do you mind giving me some examples of where flags in shebang lines do and don’t work? With the most popular/prominent platforms included. I’m trying to write documentation along the lines of:
In particular, we should include macOS and Windows (Windows Subsystem for Linux?) in this sentence, whichever list they fall into, as those two probably account for the majority of our users. For Linux, at least Debian and Alpine, as those are the two used in the official Docker images. |
Sorry, something went wrong.
|
I'm pretty sure dash and sh don't support them, as I had to account for that in nvm. |
Sorry, something went wrong.
|
@LiviaMedeiros Is there a reason to keep this open? I think #49869 covers everything except the URL stuff, and #49975 is in progress for that. |
Sorry, something went wrong.
| Back | FazBrowse Home | New Git URL |
This adds an option to directly load ESM entry point by absolute or relative URL.
Allows:
Doesn't allow:
I.e. most importantly, it allows to
Without breaking changes to how node path/to/file.js works.
Might be related to: #34049
Fixes: #46009
Fixes: #49204
I'm not sure if this is the most correct way to implement it (or if a better alternative is planned), hence draft.
cc @nodejs/modules