-
-
Notifications
You must be signed in to change notification settings - Fork 1.9k
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
Improve ESM exports and enable tree shaking #1009
Comments
Heya! I would love some help with this. I've spent a lot of time trying to get this as compatible and reliable as possible, but would love someone with more knowledge to take a look. The browser field is required, especially in the One thing that complicates this is that I'm not sure if TypeScript has fixed the issues it once had, but at one point I'd found a TS issue where any class with static methods would become un-tree-shakable, which is something I use. If that is fixed, of there is some way to hint to things that it is ok, I'm totally game for adding annotations. I've tried enabling meaningful tree-shaking before, without much luck. Everything is side-effect free. The possible exception is the umbrella package, which, in the browser, adds an I would like to add a Just some thoughts. I've been busy moving and a few other things the last few weeks, I'll look into this more once I get the issue count back under control and have experimented a bit with your annotations you linked to. :) |
This is related to #1011 My current solution is to execute a script to ensure the module field point to content of the browser.esm field but if the browser.esm field is itself an object, the browser field point to the same content : https://github.com/wighawag/purple-warlock/blob/b179a3b7f097bb4eb42036c29ab400dd64588764/fix_ethers_modules.js This works nicely with https://github.com/vitejs/vite I did not check how much tree shaking it does, but I can at least get code splitting. Regarding module vs main vs browser, the current problem is that ethers has non es module on its browser field
this break bundlers that expect es module for tree shaking, etc... Furthermore, modern tooling (like vite gives precedence to es module (the For ethers to work in this case is to
This is currently what I do automatically in my application via the script mentioned above Do you think the solution I propose would break some use case ? |
By the way, I encountered another issue, this time with a webpack project with the browser field (as object) pointing to non-es-module override version (as oposed to browser es-module override version) I have a browser-only library that have the module field pointing to es-module (it does not need a browser field as it is browser-only library) This module has @ethersproject/providers as a peer dependency. The library do not contain @ethersproject/providers as it keep as external and instead expect the application to have it. When it get imported, it get imported as a es-module. In my particular case the import issue that with a trigger of an import of @ethersproject/providers as es-module, which then trigger an import of @ethersproject/web which then as es-module read the browser field (as object) and then import I can fix it by adding the browser.esm override to the browser field too like that :
Note that my script above (that I use in my "vite" project) would also fix that. The story is more complex though as I could not replicate that in a sample project. Still investigating but though worth mentioning as the flow does make sense and the fact that browser.esm contains information important for es-module loading is not a good sign as this field is non-standard and as you mentioned @ricmoo not expected to be used by application. |
As an FYI, I have been overhauling the build scripts the last week or so and plan to address this over the next few weeks. |
This has been largely addressed in 5.0.20. Please try it out. There are still some issues since there are a large collection of bundlers, each doing things slightly different ways, but I've aimed to make things work as best as I can for the widest possible audience; mainly rollup and Webpack (by extension React Native) should see much better build sizes and better compatibility. Anyways, try it out and keep the suggestions coming. :) |
@ricmoo I've been playing around with ethers.js in the past few days and during my tests I noticed this line:
brings with it a chunk thats 98.3kb big. With webpack v5 that supposedly shouldn't happen - any thoughts on why it is happening? Also the test I did was using Next.js v10 - just so you are aware there is a framework with a specific webpack config that may play a part. Even though in my tests it seems as though named exports are imported correctly with webpack v5 and only the required code is imported. The code being imported:
Using ethers v5.0.20 Also worth noting it isn't included the whole ethers library so that is something :) |
Using ethers v5.0.20 I can confirm I do not need to use the script mentioned above, it all works well with code splitting (using vite) |
@csmartinsfct Thanks for the heads up. I will investigate this. :) |
@wighawag Awesome! Glad to hear it! |
@csmartinsfct I've investigated a bit, and ethers is not designed to have folders below the root imported from directly. Your code above is actually pulling the non-ESM version in, but I tried pulling in the ESM version directly, and it doesn't do much better (I thought it might handle it, but tree-shaking is still in its infancy). I may pull the utils out and make it its own sub-modules to help improve these cases, but the better solution for you is probably to add Let me know if that works for you. |
Actually it seems I talked too fast. I guess I had some cached stuff.
|
it seems that for some reason @ethersproject/[email protected] got installed even though |
I will lock all versions in the next release to make sure no current package refers to any pre-esm-module version. I am curious why that would happen, but it makes sense to update the package.json files this way anyways. |
Yea that would work. Thank you :) |
I think this has been addressed, so I'm going to close it. If not though, please re-open. Thanks! :) |
I identified several areas where we could improve the way Ethers gets exported. Please let me know what you think, and if / how I can help with these 🤗
Marking modules as side-effect free
This might be the easiest improvement: assuming all the Ethers packages are side effect free, marking them as such would make it possible for bundlers to eliminate the imports that are not used.
Note: I tried to do it and run
build-all
and it seems to impact something in the build configuration: theBN
export ofbn.js
(which is is CJS) doesn’t seem to get converted properly anymore.Code splitting
The easiest way to eliminate code is to provide exports in ES modules and separate every module in its own file.
__PURE__
annotations can help too, but their support varies a lot from a bundler to another. It makes them adapted as an additional optimization, but they don’t really work as a primary way to enable tree shaking.At the moment, the entire library is bundled in a single file. For the ESM export, setting the
output.preserveModules
totrue
and exporting everything into adist/esm
directory would probably be enough.The package.json
browser
fieldThe
browser
field takes precedence overmodule
andmain
in some bundlers configured to export for browsers. A consequence of it is that it makes it impossible to use the ESM export of Ethers. Is this field needed at all? Browser support for ES modules is excellent now (with the exception of IE 11 which is close to reach end of life), and most people use a bundler nowadays.This field could then be useful to provide browser versions of modules (CJS or ESM) that are meant to be used in Node.js environments (see https://github.com/defunctzombie/package-browser-field-spec#replace-specific-files---advanced), rather than using it as an alternative to
main
andmodule
. It could also provide to browsers the same structure than themodule
export, but minified. It would makeimport … from "//unpkg.com/ethers"
provide an optimized build which could be super nice :)The
ethers
named exportEthers exports the
ethers
object, which contains all the other named exports. Even though a__PURE__
annotation is being used, it might be difficult for some bundlers to respect it and might prevent any code elimination to happen.A solution could be to stop exporting this object, and update the documentation to suggest using
import * as ethers from 'ethers'
instead − while also explaining why individual exports should be preferred. Most bundlers will also acceptimport ethers from 'ethers'
since trying to import a default export from a module not having one is usually converted intoimport * as …
instead.We could also decide to keep the
ethers
export. In that case, a solution would be to have it in a separate file (so that apps not using it could tree shake the library), and let users know in the documentation that it is not optimal.The text was updated successfully, but these errors were encountered: