-
Notifications
You must be signed in to change notification settings - Fork 30k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Feature: ESM in executable files #49444
Comments
There are a variety of solutions we should probably evaluate and see if they are or are not feasible that I can think of. Right now we can point executables without extensions to other files using symlinks. I'm not curious about what file extension maps to what format, but I am curious about if that disambiguation and/or indirection method is infeasible. I'd be curious about places that cannot support that behavior. Most installations put the In addition the executable could stay CJS and I also am concerned about hashbang being unusable not just on linux shells but on windows. I agree it is a design space that we can look into but I am not sure how feasible it is. I do not think loaders can solve this problem using a hashbang either because they currently are not runtime configurable, nor am I convinced they should become runtime configurable due to this single issue. If we were to use a loader configuration it would need to be done outside of the file using CLI/ENV/package.json/etc. That makes me think we might want to look at indirection so that we can get that out of band data somehow. I do have scaling concerns for having multiple executables because things like BinaryAST / WASM / WebPackage are other goals I'd like to support in the future and they would explode the number of executables that I do not think wrapping processes are a way to solve this problem either. Notably, Windows does not have an |
Forgot to add, that even though Loaders are not configurable, they could be done in a per package manner for things that are not installed using |
Not sure I follow, but I've used Linux in pretty much every dev/production env I know and written dunno how many binary files based on the hashbang
NodeJS ignores hashbang too since ever because it's been a valid use case for long time so it shouldn't probably be under discussion the ability to create nodejs executable based on ESM, but I hope I've misunderstood your reply. |
@WebReflection that is executing the |
then I misunderstood, and indeed since it doesn't work in the only env I've used it (Linux) I guess we would need a better way. I don't have ideas for now but I'll keep thinking about it. |
If we want to avoid passing parameters in the shebang, we could create an excutable wrapper that passes them ( But from my point of view that would be confusing for people used to run node. |
This is one of the benefits of the package.json "mode" proposal in that it can allow this scenario to be handled clearly (when executing the "bin", the package.json is loaded and used as source-of-truth of the format). @xtuc's suggestion is an interesting alternative here too, although agreed being weary of adding to confusion. |
Don’t forget the case where the extensionless file is outside the package folder, e.g. |
node by default uses the extension of the resolved file, not the symlink e.g. |
FWIW @xtuc suggestion is exact equivalent of my #!/usr/bin/env bash
node --module $1 This is a pragmatic solution to the shebang gotcha but it feels future hostile needing that in order to have an executable based on modern code so, unfortunately, we might need a better idea than just an indirection 😢 |
@WebReflection whats wrong with the symlink indirection that most things do today? |
@bmeck if I understand correctly that would require a I just think everything is possible today with |
@WebReflection it would rely on some file that it points to being unambiguous, yea. Not necessarily using |
I think this is where all of these are breaking down. Perhaps, the way that things are done today doesn't scale well, even at 2 possibilities we are seeing problems unless we define a different mechanism than today. Wait for BinaryAST and WASM entrypoints and we have 4 possibilities. |
@bmeck I don't think anything else different from JS would have the same issue for the simple reason I don't think WASM would allow a shebang on top, right? Anyway, I forgot I have some hackery to start GJS so that this would be my solution: #!/usr/bin/env bash
Function=Function//; node -m "$0" "$@"; exit
console.log('ESM'); Explanation
All it's missing now, is this mechanism to bypass the file extension and enable the |
I think you are over focusing on a hashbang based solution. Hashbang doesn't work for a variety of cases or at all on some deployment targets like windows. I'd be wary of things that can vary per shell and aren't even in some environments. |
windows has numerous way to bring in bash though, but yeah, I'm focusing on developers, those that actually use NodeJS the most, and on Servers, those by stats dominated by Linux. Focusing on windows without Linux subsystem or chocolate bash, aka focusing on non developers, looks like not so important. Windows has many ways to ship binaries, including ways compatible, or based, on shebangs. npm would also solve that case via |
even better, you could use my technique both via npm and as a stand alone file/application, so I don't see any reason to ignore it as use case/solution. |
Is this feature documented? can this be closed? |
Yes, it’s in the features list in the README: https://github.com/nodejs/modules#existing-nodejs-utility-features |
Closing based on above comment. please re-open (or ask me to) if this was a mistake |
I don't think this should've been closed until there's a reliable way to do this cross platform without relying on bash hacks. FWIW, the unambiguous grammar proposal would've easily solved this. :( |
FWIW, I think that's the only way to solve this (without bash hackery) |
Or the shebang suggestion, like starting your file with I had suggested elsewhere that unambiguous grammar could be treated as a fallback, like if the mode wasn’t explicitly defined via something in The minimal kernel currently in progress inherits the assumption from Even if that could be designed, though, relying on unambiguous grammar for determining module mode has its own issues as listed succinctly in nodejs/modules#150 (comment). Part of me still would love for someone to write up a proposal making the case for author-driven disambiguation via unambiguous grammar; @bmeck did a lot of work on unambiguous grammar years ago, but basically walked away from it as he didn’t think it could be made to work. But if someone wants to give it a shot, I’d love to see another attempt 😄even if we come to the same conclusion. If nothing else, it would help persuade the mob that every attempt was made to try to come up with a more developer-friendly UX than whatever we end up shipping with. Or if a workable unambiguous syntax algorithm could be designed, well, that would be very interesting. We’d still have the other issues to address, perhaps by shipping unambiguous grammar along with metadata flags like file extension and |
Unfortunately the issue there is not just Windows, but Linux too, where that wouldn't work, you need my suggested hacky bootstrap. But executables with shebangs are extremely common in Linux (servers), so anything that wouldn't work in there makes little sense, in terms of wasted time, IMO. |
@GeoffreyBooth it’s not really worth another attempt - it would require TC39 to change the spec, and a number of members of the committee have expressed legitimate opposition such that (unfortunately) it basically can never happen. |
We thought the same thing about named exports, but they appear to have moved on that, haven’t they? Again I don’t see how a proposal could resolve all or most of the issues raised in nodejs/modules#150 (comment), so like you I’m not optimistic that an unambiguous grammar solution is possible. But I would still love to see someone take a crack at persuading me (and perhaps the TC39) otherwise. All it takes is a gist explaining in pseudocode what the proposed unambiguous grammar algorithm would be (perhaps based on @bmeck’s prior work) and how it would be future-proof, and how to address the other issues raised by nodejs/modules#150 (comment). |
@GeoffreyBooth i don't think you have an accurate understanding; it's more that the committee has always wanted to help node, but previous attempts weren't tenable. The recent attempt, however, was tenable. As it relates to unambiguous parsing, the committee as a whole actively does not want unambiguous parsing, no matter the solution - many members explicitly want it to be ambiguous, and as such, it's not worth any attempt, because conceptually it's doomed from the start (as dynamic named exports were not). |
@GeoffreyBooth the context hasn’t changed; mjs and mimes were the plan during the entire unambiguous grammar process with TC39 - it wasn’t as long ago as you think. Certainly node can decide to require unambiguous grammar - but we’d be doing that by restricting ourselves to a subset of JavaScript programs, which doesn’t seem like a good idea for node dot JS to be doing. As it relates to TC39, there’s really no further debate to be had. If you want to debate doing our own thing, we can, but i don’t see how that’s legitimate or productive. |
@devsnek Apologies for not actually checking that an executable .mjs script would just work. With all the trouble I had for extensionless scripts I'd assumed that at some time I must have also tried it with the extension, but I guess I never had (even though my solution was to have the extension and then make a sh launcher script!) In my defence the docs don't say that it's possible or that this exception to needing |
Pardon if I'm misunderstanding the documentation, but can't a module-based extensionless script work as ESM by putting itself in a package's Is this a new development since this issue was last commented on, or is that was meant when people mentioned NPM being able to handle it above? |
Oh, the problem is that that only applies if What's the roadmap for when ESM will be enabled without a flag? Sometime in 2020? |
This works now, and you’re right the ✦ cd /tmp && mkdir test && cd test
✦ echo '{"type": "module"}' > package.json
✦ echo '#!/usr/bin/env node\nimport { version } from "process";\nconsole.log(version);' \
> executable
✦ chmod +x ./executable
✦ export NODE_OPTIONS='--experimental-modules'
✦ ./executable
(node:54196) ExperimentalWarning: The ESM module loader is experimental.
v12.6.0 You can use clever shell scripting to avoid the need for The plan is to unflag when Node 12 goes LTS in October 2019. |
With unflagged modules in 15.5 I'm still getting the error: node:internal/process/esm_loader:74
internalBinding('errors').triggerUncaughtException(
^
TypeError [ERR_UNKNOWN_FILE_EXTENSION]: Unknown file extension "" package.json: "type": "module",
"module": "index.js",
"bin": "simple-ddos", |
You also don’t ever need type module; you can name your file with .mjs instead. I’m not sure extensionless files work with ESM at all, though, unless they’re the node entry point and you pass the input-type flag. |
@DerekNonGeneric thanks for the tip but it drops the same error :/ @ljharb I tried to do #!/usr/bin/env node --input-type="module" UPDATEthe solution I came up with is to create a shell file that launches the js script, e.g. this
script's contents: #!/usr/bin/env sh
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
node $DIR/cli.js "$@" |
that's half a solution, but also, imagine you had to launch executable files like I really hope soon there will be a way to have an ESM only executable files, that also force-load everything as ESM without needing a |
@WebReflection i totally agree there should be some way to make it work without an extension - but i think it’s very important that “type module” doesn’t get thrown around and misunderstood as a solution to anything beyond the one tiny thing it does :-) |
@ljharb it's not so tiny though ... and until there is a |
It has nothing to do with extensionless files, though, which is the only relevant part of this thread. |
it does! the day that works one can write:
and call it a day edit |
Right, the goal is avoiding a wrapper like that. type module is utterly unrelated - your wrapper could point to an mjs file just as easily. |
Being able to signal a short cli script to use esm over cjs is important. For small, general purpose, bash-like, copy-paste-able, executable scripts like, for example:
I'd like to name this script 'hello-world' instead of 'hello-world.mjs` for it to run without error. |
@mshiltonj why? there is precisely zero about that script that requires it to be ESM, except the import - which could be |
I'm a long term user of Node.js (since I do however DISAGREE with your comment @ljharb. By not supporting ESM module syntax in extensionless shebang executables you are not only forcing programmers to use the CommonJS If the problem was with a single file it would really be (as you point out) "a few users of a niche use case", but by making every dependency use CommonJS you are dooming extensionless bin files as a whole which are part of a lot of major Node.js libraries. Just to pick a few examples: Quoting NPM:
https://docs.npmjs.com/cli/v8/configuring-npm/package-json#bin |
No? You can, in a CJS bin, async import any ESM module. |
Extensionless bin files I mean. If you add the extension |
Sure. But in CJS, you can always use |
Without
Using
Using On the other hand, it seems like @bmeck is still dedicating brain time to this (#42301). |
@sdesalas type module only determines whether your own .js files are ESM or not (it has no effect on things in node_modules). You can still use .mjs for ESM (and should). |
@sdesalas CommonJS doesn't have top-level await, so instead of this ext.mjs content: #!/usr/bin/env node
const {default: axios} = await import('axios');
console.log(axios); You should write something like this in a noext file: #!/usr/bin/env node
(async () => {
const {default: axios} = await import('axios');
console.log(axios);
})(); if you have these excusable files in the same folder where node_modules/axios is installed, you won't have any issue. |
@WebReflection I used an async IIFE for the await part. The problem was not with axios on the extensionless shebanged file, but on the dependency chain from its required modules. I'll write up a working example when i have a mo. |
@sdesalas the dependency chain shouldn't have any impact - altho CJS can't require ESM, it can dynamically import it, so any module format can be accessed from any module format, just not always synchronously. |
Hey all, I am new to this thread, but I am in the process of upgrading from Node 12 to Node 14 and thought it would be good to convert everything to ESM. However I am running into a bunch of problems, it seems like ESM is not fully baked. One of our major problems is getting our .bin executables to run with ESM. I have tried using .mjs I have tried type: "module" but it always seems to have some problem. Is this just not possible currently? Is the current guideline to only use CommonJS in executables? If it is possible is there a fully baked example to refer to? For example, in the package.json do I need to specify the .mjs extension in the key? The examples in the doc do not specify.
|
Added a build script to run the TypeScript compiler, rather than running it myself. Looking into some other setups to help automate the library setup a bit more. Looking into adding a `rm -rf ./dist` (`npx rimraf ./dist`) call before `npx tsc`, since sometimes some ghost files are there after removing old files that are no longer in the codebase (they are still in `./dist` though). But, thinking about it, I also don't want to accidentally remove files that are meant to be there, so I probably won't add it. I will have to remember to reset the `./dist` folder myself, if I removed a file from `./src`. https://stackoverflow.com/questions/45082648/npm-package-json-os-specific-script https://github.com/isaacs/rimraf While looking into that, I also found an article about making your npm package into a shell script. I realized that could be a great thing for NBTify to have too! Hadn't even thought about that before. Being able to simply install NBTify globally to your machine, then running it as a command, `nbtify`, and it can manipulate NBT files directly on your file system. Then you wouldn't even need to write a JavaScript script to work with NBTify! I'm gonna go make an issue over on GitHub for that! A really cool thing to look into next. #25 https://blog.deepgram.com/npx-script/ https://2ality.com/2022/07/nodejs-esm-shell-scripts.html#node.js-esm-modules-as-standalone-shell-scripts-on-unix https://github.com/nodejs/modules/issues/152 Not too familiar with all of the different ways to add scripts to your npm package, so I'm looking to find out what most libraries do, since it doesn't always seem to be the same thing (`npm run dev` vs `npm start`). https://docs.npmjs.com/cli/v9/using-npm/scripts https://stackoverflow.com/questions/69400243/whats-the-difference-between-npm-run-dev-and-npm-run-start-in-next-js https://stackoverflow.com/questions/51358235/difference-between-npm-start-and-npm-run-start Relating to the previous thing, it sounds like some TypeScript libraries will run their dev server using `tsc` for the type checking, then instead use `esbuild` for the build process, since it tends to be much quicker, and it doesn't perform any type checking. I think it's because you don't need to worry about type errors during the build process, but instead during the dev process, when you're actively working on the project. I do like that crossover to make things a bit lighter to build for production. evanw/esbuild#1519
Coming from this comment: nodejs/modules#151 (comment)
It is now possible to create executable, extension-less, files:
But it's not possible to enable ESM as parsing goal.
Even if there are OS incapable to parse the whole shebang up to the
-m
flag (or whatever flag will land in nodejs), there is no way to even define an executable that would like to parse the source file without any extension.Example
Given the following
esm
file, reachable through/usr/local/bin
or similar OS folder:And given the following executable:
It should be possible to have extensions-less files parsable as ESM.
Update
There is a solution to the single file problem that would still require
--module
hook to bootstrap the file as ESM parse goal.The text was updated successfully, but these errors were encountered: