-
-
Notifications
You must be signed in to change notification settings - Fork 12
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
Support for esm.ts using node --loader ts-node/esm
#43
Comments
That sucks. This is a complex feature to add and is on our roadmap for rechoir, but we probably won't have time to work on it for awhile. I'll move this over to the rechoir library, as it needs to be implemented in the loader. |
I just spent hours digging into this, and my general sense is that esm loaders in nodejs are an absolute shitshow right now. They've recently changed their loader API, so a lot of stuff has to shim their internal loader logic, but then I noticed that ts-node had to wholesale copy-paste node internals into their codebase to support their module loader. Check this out: https://github.com/TypeStrong/ts-node/blob/main/dist-raw/README.md Anyway, I dove down that rabbit hole because the most efficient way for rechoir to support esm loaders would be to bootstrap our own loader on startup that proxies through to everyone else's loaders. However, once I discovered that we'd have to copy all the internals from node to do this "right", I decided to hack on a much worse performing solution and came up with something that works: const { spawn } = require('child_process');
const argv = process.argv
async function main() {
try {
require('ts-node/register')
return require('./bar.ts');
} catch (err) {
if (err.code === 'ERR_REQUIRE_ESM') {
try {
return await import('./bar.ts');
console.log(mod);
} catch (err) {
if (err.code === 'ERR_UNKNOWN_FILE_EXTENSION') {
var child = spawn(argv[0], [
'--loader', 'ts-node/esm',
...argv.slice(1)
], { stdio: 'inherit' });
child.on('exit', function (code, signal) {
process.on('exit', function () {
/* istanbul ignore if */
if (signal) {
process.kill(process.pid, signal);
} else {
process.exit(code);
}
});
});
} else {
throw err
}
}
} else {
throw err
}
}
}
main()
.then(mod => console.log(mod))
.catch(err => {
process.nextTick(() => { throw err })
}) Essentially what is happening here is that we are assuming The reason this is pretty insane is that it'll be double registering loaders (for commonjs and esm) for every esm loader you try to use. Additionally, multiple ESM loaders will cause multiple child processes to be spawned (which then reloads all modules from the start). |
I'm not a huge fan of this, but it solves the problem and makes the actual loaders other people's problem. I'll think on it a bit more. |
For reference, I used concepts from gulp-cli, liftoff and https://github.com/lukeed/loadr for the above solution. |
I have the same problem, did you solve it? |
@JusticHentai No, and this will be punted until post-v5 of gulp because nodejs support of loaders is so shitty. |
Just commenting to say that I'm following along with issues like this across the ecosystem, and am always open to discussing ways that tooling and loader authors can shim our way around node's shortcomings. I have this idea that we might devise a pass-through loader which does nothing on its own, but which allows tools such as yours to install hooks at runtime as necessary. Roughly, it would look like this: Node is invoked like this:
If a tool realizes at runtime that it wants to install ts-node's hooks:
Theoretically someone could add |
@cspotcode I believe something like a "multiloader" would make the most sense sense, but your current example only works for dynamic imports. We'd want to be able to prepare the multiloader at launch with flags. Maybe that would be something like: node --loader multiloader --require ts-node/esm` |
That is actually possible today, with, for example: The runtime API I described in my previous comment is not yet implemented, but composing multiple loaders at startup is implemented today. https://github.com/cspotcode/multiloader The end-goal is a future where tools such as yours do not need to attempt to spawn a child process at all. When they decide that a loader is necessary, they can install it at runtime, the same way we do for CJS loaders. EDIT: fixed a typo in the invocation syntax above |
For gulp, I believe we'll always need to respawn because node doesn't have good APIs for allowing us to pass other nodejs flags through, but I understand that having the programmatic API would be useful for other applications. |
Hmm, I'm looking at your code above (#43 (comment)) and assuming that you'd replace the As far as I know, when you do Does gulp do things differently? |
@cspotcode sorry if I was unclear, I'm wasn't talking about the loaders for respawn. We still need to respawn for the usage of |
Ah I see. I think it still might have benefits for this situation:
I think a multiloader solution can limit the number of spawns to, at most, 1. The first time you realize that you need to spawn, you can eagerly register multiloader into the child process. From then on, you know you can install as many CJS and ESM loaders into the process as necessary, without any further respawning. Since the multiloader API is exposed on the process object, you can feature-detect for its presence, avoiding unnecessary respawns when it's available. When it's available, you can even skip |
@cspotcode I've been thinking about this a bunch and I think your original description is ideal. I'd want to specify // Would that have to be async?
process[Symbol.for('multiloader-api')].register('ts-node/esm'); |
Nice, yeah if we want to collaborate on such a runtime API, I think that'd be great. I was toying with adding this to my multiloader thing but I don't remember where I got to. I'll see if I can dig up the code -- maybe it's my work laptop? -- and push it tomorrow. |
This comment was marked as off-topic.
This comment was marked as off-topic.
Any update? @cspotcode can |
I use ts for my gulpfile, but some of the plugins i use (gulp-imagemin specifically), are esm.
So having a way to use both would be useful, as otherwise when one tries to run gulp, they get something like this:
The text was updated successfully, but these errors were encountered: