4 ms·
So much of this is transparently untrue that I’m surprised to see it upvoted this much on HN. > Which then makes people believe that treeshaking is not possibl
by afavour 3y ago
So much of this is transparently untrue that I’m surprised to see it upvoted this much on HN.
> Which then makes people believe that treeshaking is not possible with CommonJS modules. Well, it is - Rollup just chose not to support it.
It is possible with CommonJS but it’s enormously brittle and unintuitive. I can write:
module.exports = { blah: 1 }
module.exports.blah = 1
let name = “blah”;
module.exports[name] = 1
And countless other possibilities. Then on the other side:
const { blah } = require(“module”)
const blah = require(“module”).blah;
const mod = require(“blah”);
mod.blah;
let key = value ? “blah” : “something else”;
mod[blah];
Footguns all over the place that could easily ruin your treeshaking and result in unnecessarily large bundles.
> And then people go "well you can statically analyze it better!", apparently not realizing that ESM doesn't actually change any of the JS semantics
Also plainly untrue. Similar to the above, I can do:
let name = “module”;
const mod = require(name);
With even a slight bit of added complication in that logic static analysis becomes enormously difficult. ESM does not let you do any of that.
> loading a full dependency tree over the network would be unreasonably and unavoidably slow
Allow me to introduce you to HTTP2. It’s also fast to load dependencies because ESM is statically analysable in a way CommonJS isn’t, so you don’t actually have to evaluate the JS. No, it won’t be as fast as one request but the ability to just write a file and immediately upload it is tremendously liberating, IMO. For small projects Webpack, Vite or whatever is tremendous overkill.
> There are no actual advantages to it. At all.
Igorance is bliss, I guess.
- TheAceOfHearts 3y agoAren't there also some limitations with tree shaking when using ES Modules as well? How are polyfills done in a pure ESM world? You still need a build step to get value out of tree shaking, no? It's been too many years since I seriously looked at the problem, so my memory is fuzzy at best. But I do remember coming away thinking that it's a lot less of a magical or useful feature than I'd originally expected it to be.
- afavour 3y agoYou do need a build step to actually perform the tree shaking, yes. > How are polyfills done in a pure ESM world? Maybe you could give an example? In my experience you’d just import “polyfill-module”; At the top of the file. But I suspect you have something specific in mind.
- stilist 3y agoThese days polyfills are seen as a bad practice, and instead people recommend ‘ponyfills’ that export the relevant functions but don’t monkeypatch.
- WorldMaker 3y ago> Aren't there also some limitations with tree shaking when using ES Modules as well? You can still `await import(someVariable)` and your tree-shaking will be limited at that point without a lot more manual configuration. But import syntax makes that an edge case, not the base case. > You still need a build step to get value out of tree shaking, no? ESM was also designed so that browsers can tree-shake at runtime. (Whether they will or not is an implementation detail based on whatever optimizations the browser's engine decides to make, of course.) Imports to modules that are never referenced by URL are never loaded, for one obvious part, that's the most basic form of tree-shaking in general, the browser never has a full view of your file system tree. Beyond that, the module objects that `await import()` most directly returns (but are used to back all imports and are also represented as `something` in `import * as something from 'module'`) are designed to be "immutable" proxies that the Browsers can store as weak-references until needed and can in turn store the exports of that module themselves as weak-references. If the reference is never dereferenced it stands possible to be garbage collected like anything else. The browser might not have parsed anything beyond the top-level of a module and the name of the export, delaying paying any attention at all to the definition itself of that export until dereferenced. (It delays even parsing in some optimization cases; any JIT compilation is obviously delayed further until after parsing.) An export that was never parsed/compiled and eventually garbage collected away as the app ran is a very close equivalent to tree-shaking from an optimization standpoint (of nearly all but raw bandwidth/memory usage). The garbage collection churn isn't nearly as optimized as build-time treeshaking can be, but most engines have heavily optimized garbage collectors and there are worse garbage collection churn in the average application than that. Relying on the browser to do most of your tree-shaking for you at runtime is maybe not the best idea, but ESM was certainly designed that browsers could optimize it as much as possible. There are still runtime benefits/optimizations to expect from ESM that CommonJS was never designed to support.
- msoad 3y ago`import()` enters the chat and breaks all your static analysis
- afavour 3y agoWhich is barely ever used. ESM lets you very easily distinguish between static and dynamic imports: another advantage.
- msoad 3y agoHow a top level require statement with a string (which is the most common) not the same then? If we're ignoring edge cases, then why even bother with a new syntax?
- afavour 3y ago> If we're ignoring edge cases ESM specifically doesn't ignore the edge case, it makes a special syntax for it! The point is that require() is surrounded by footguns. It's a magic function with untold side effects. That's bad. If an inexperienced developer sees this: const mod = require('module'); they might think it's fine to change it to: const moduleName = 'module'; const mod = require(moduleName); because why not? Or why not move it from the top level? There's nothing obvious about require() that would inform you you're about to cause a giant side effect. Indeed, on Node you wouldn't be. But you would be on a client-side build. This is the problem with non-standard implementations. Switching from a top-level import to using import() has a drastic effect on your code: import() is async, requires you to wait on a promise to resolve, etc. etc. It's obvious that making the switch alters the structure of your code and the way it loads.
- msoad 3y agoI am responding to your comment above: I've worked in this space and I've heard all of the argument > [require] is a magic function with untold side effects Sure, how about making it standard with a new directive? "use require" or something like that. Now require is special. You can't redefine it etc