-
-
Notifications
You must be signed in to change notification settings - Fork 1.1k
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
🎯 ESM Support for PnP #638
Comments
ESM modules go through a completely different codepath in Node (cjs / esm). The problem is that this codepath is still very new, and although modules have technically been marked stable the way to alter the resolution (aka loaders) is still in heavy discussion (nodejs/modules#351). So we can't support them at the moment (too early), but as soon as the loader spec moves forward with an implementation we'll be ready to experiment with them 🧪 |
Thank you. |
I looked into the API a bit using Node 14. I've written a PoC implementation at (bgotink@34c0a7b) along with a small test repo over at https://github.com/bgotink/berry-esm. The API is still unstable, but I think it's worth checking out to see whether there's any valuable feedback to gain from trying. I'm not sure how volatile we should consider the API, so I'm not considering "should we ship this?" at the moment, only "does it work?" The interesting bits so far:
The boring bits:
Improvements to the PnP API that would make the PoC a lot cleaner:
A node module with code below works fine in the berry root folder and showcases a lot of the basic functionality already. // named exports from fs still work even though we patch fs
import fs, {readFileSync} from 'fs';
// JSON can be imported
import pkg from 'typescript/package';
// CJS package import
import typescript from 'typescript';
// importing a non-patched builtin
import {fileURLToPath} from 'url';
console.log('');
console.log(pkg.name, typescript.version);
// Resolves to a regular file: URL
const typescriptUrl = await import.meta.resolve('typescript');
// So it can get transformed into a path and used in fs APIs or even require (BAD IDEA)
const typescriptPath = fileURLToPath(typescriptUrl);
console.log(`url:`, typescriptUrl);
console.log(`path:`, typescriptPath);
const eslintPackagePath = fileURLToPath(await import.meta.resolve('eslint/package'));
// Just to prove that the fs APIs are patched
console.log('fs.readFileSync', JSON.parse(fs.readFileSync(eslintPackagePath)).version);
console.log('readFileSync', JSON.parse(readFileSync(eslintPackagePath)).version);
|
Nice analysis! You mentioned
That seems acceptable 👍
Calling |
I've kept the module loader conservative in which paths it actually tries to handle itself versus which paths it lets the default loader handle. This requires the different hooks to be able to check whether a URL points to a path that needs to be loaded via the patched filesystem. I'm not sure what's the best path forward here: just load everything via our
As I've stated in my previous comment I've kept the module loader as dumb as possible, it loads everything from the regular |
What is the timeline for enabling ES support? Is there anything one can do to work around this? The issues linked to this suggest not. For now, I will try using berry with the nodeLinker option and see if that lets me use berry, though with it's biggest feature limited lol. The reason I want pnp is essentially what you've labeled 'zero installs', before this I used yarn 1 and the offline mirror mode to make exactly the same .zip folders that berry makes, even in the .yarn folder and everything lol allowing 1 zip per dependency and sane commit changes by just checking the .zips. Built it all with docker images lol. So I'd hoped berry would be able to work in the same way, but since there actually isn't ever a node_modules with this method, I see how it will be more difficult to patch, because the offline-mirror was just how it got stored in git, but once running it was identical to npm/node with a node_modules folder. |
@arcanis @bgotink any news here? @arcanis left the last comment on the related thread nodejs/modules#351 on on Jul 29, 2019, so is there some hope? |
Of course there's hope, and it's even certain it'll eventually happen. It's however not my top priority at the moment since all the software I personally use are still CommonJS, so it'll happen faster if someone can do the legwork 🙂 |
Most of the stuff I use is also CommonJS, I agree. However, once you start to code share between frontend and backend and already bigger libs like Next default to esnext modules it's just cleaner and less headache to have all the code base as one format. Currently, code sharing is a rocket science. But that's not your fault ;) |
or another example which just popped up again in my project: top-level awaits, they are such a life saver and way underrated. Once you get the hang on them you can do insane stuff. Right now I try to setup conditional imports (which are of course async) and then continue depending on the condition. With top-level it would be DRY, without, a lot of But TS requires esnext modules for using top-level await... so yeah |
This comment has been minimized.
This comment has been minimized.
Hey @nnmrts - before commenting in an open source project (any open source project), please remember that the people in front of you aren't your contractors but your peers. Passive aggressiveness like "if you don't do X I won't use Y" isn't more likely to make X happen, because maintainers don't work for you (at least I haven't received the wire yet). That being said, if you need to use ESM now, you just have to downgrade to typical |
I gave this a go and implemented an experimental loader in #2161, if anyone would like to test/try it you can use |
As of commit time, yarn2 cannot support ESM scripts due to difficulties from upstream(node itself). See: yarnpkg/berry#638
As of commit time, yarn2 cannot support ESM scripts due to difficulties from upstream(node itself). See: yarnpkg/berry#638
Adding support for pure ESM packages ([see](https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c)). This is achieved by using proper external type (`commonjs` vs. `module`) in webpack's external config, so that proper code is generated to import ES modules in the server bundle. A few important facts: * Node.js requirement had to be bumped to >= 12.17, in order to use the dynamic `import()` * The `server-main.js` cjs entry becomes an "async module", so it returns a Promise that need to be awaited when using `require()` * Yarn PnP does not support ESM yet (see yarnpkg/berry#638); we'll bundle esm dependencies on the server to workaround this when project is using PnP (the `process.versions.pnp` check) Co-authored-by: Ryan Tsao <ryan.j.tsao@gmail.com>
Hello from 2022, I feel at tad lost among all tickets and doc, am I supposed to do something to have for instance an |
If there are no indicators in your project other than file extensions that ESM support is required you'll need to manually enable it. pnpEnableEsmLoader: true |
Adding However, maybe a dumb question: how do you actually use this loader? I now have a |
Or just use:
Which does add all required parameters for you. |
Perfect thank you!
|
Thanks for the write up @eric-burel! |
Not works when i am trying to use a custom loader. /// file: custom.loader.ts
import { extname } from "node:path";
import { create, createEsmHooks, NodeLoaderHooksAPI2, NodeLoaderHooksFormat } from "ts-node";
interface NodeLoaderHooksAPI2Context {
conditions?: NodeLoaderHooksAPI2.NodeImportConditions;
importAssertions?: NodeLoaderHooksAPI2.NodeImportAssertions;
parentURL: string;
}
interface NodeLoaderHooksAPI2ReturnType {
url: string;
format?: NodeLoaderHooksFormat;
shortCircuit?: boolean;
}
const { resolve: tsnodeResolve } = createEsmHooks(create());
export function resolve(specifier: string, context: NodeLoaderHooksAPI2Context, defaultResolve: NodeLoaderHooksAPI2.ResolveHook): Promise<NodeLoaderHooksAPI2ReturnType> {
switch (extname(specifier)) {
case ".ts" :
case ".tsx" :
return tsnodeResolve(specifier, context, defaultResolve)
default: break;
}
return defaultResolve(specifier, context, defaultResolve);
} $ yarn tsc --target esnext --moduleResolution node ./custom.loader.ts
$ yarn node --loader ./custom.loader.js --no-warnings --experimental-json-modules --experimental-import-meta-resolve ./test.ts
Error [ERR_MODULE_NOT_FOUND]: Cannot find package 'ts-node' imported from xxxxxx
Did you mean to import ts-node-virtual-bba6bca08f/0/cache/ts-node-npm-10.8.0-e60a0a9a4f-1c22dc8dd8.zip/node_modules/ts-node/dist/index.js?
at new NodeError (internal/errors.js:322:7)
at packageResolve (internal/modules/esm/resolve.js:732:9)
at moduleResolve (internal/modules/esm/resolve.js:773:18)
at Loader.defaultResolve [as _resolve] (internal/modules/esm/resolve.js:887:11)
at Loader.resolve (internal/modules/esm/loader.js:89:40)
at Loader.getModuleJob (internal/modules/esm/loader.js:242:28)
at ModuleWrap.<anonymous> (internal/modules/esm/module_job.js:76:40)
at link (internal/modules/esm/module_job.js:75:36) {
code: 'ERR_MODULE_NOT_FOUND'
} |
Edit 2021-04-14 by @arcanis: In case you reach this thread by Twitter, ESM for PnP is on our roadmap. It's tricky due to some primitives currently missing in Node (they're working on this, but as you can see it's still branded as experimental), in the meantime if you need it then the
node-modules
linker remain a perfectly fine solution. In any case, help is appreciated, Twitter rants less.What package is covered by this investigations?```
@softvisio/cli
Describe the goal of the investigation
I am trying to run script, that uses es6 syntax.
But get this error:
I am using node 13 that can load es6 modules directly.
Is it currently possible to use pure es6 modules with yarn 2 or I need to compile them to commonjs before?
The text was updated successfully, but these errors were encountered: