2 ms·
You should almost always use a bundler. ES modules work, yes, but using them naively subjects you to a waterfall (each module file may trigger the loading of ot
by chrismorgan 4y ago
You should almost always use a bundler. ES modules work, yes, but using them naively subjects you to a waterfall (each module file may trigger the loading of others, though you could circumvent this with a sufficiently advanced server that recursively traversed modules in order to collect the whole set and send rel=preload links for each with the initial response), slow loading everywhere and exceedingly slow loading in higher latency, and the downloading and execution of much unnecessary code. For many completely realistic designs that would have been a hundred kilobytes in a one or two files, you’ll end up with it spread over several hundred files, reaching ten or twenty levels deep, so that it downloads and executes multiple megabytes of code, a lot of which is not even used, so that for someone on the other side of the world, what would with proper bundling have taken less than two seconds to load (including initial TLS handshake) will instead take ten or twenty seconds if everything goes smoothly. And I’m not exaggerating in the slightest here.
The situation would certainly be worse in HTTP/1 because of the lack of pipelining and the six connections limit, but HTTP/2 does not make not-bundling in any way reasonable.
- eurasiantiger 4y agoDon’t use external dependencies (:D), don’t write code you don’t use, encapsulate imports in custom elements (this is a joke). Without any runtime boilerplate like module bundling, corejs polyfills and the usual frontend/css-in-js frameworks, all code that gets downloaded is your code, so make it count. 640 kilobytes should be enough for everyone.
- chrismorgan 4y agoFor runtime boilerplate: yeah, don’t use webpack, it does indeed add silly boilerplate (and structures things in a way that almost requires source map tooling to understand!); use Rollup instead, which pretty much writes just what you would have written if you had put everything in one file, with no overhead whatsoever. (If you happen not to be familiar with it, take a look at the examples in https://www.rollupjs.org/repl/ https://www.rollupjs.org/repl/.) Also ditch core-js as it’s almost certainly unnecessary if ES modules are your baseline. But even if you’re not using external dependencies, I maintain: by not bundling, you’re slowing things down, and it’s generally the people on the other side of the world from your servers and the disadvantaged with less-powerful computers and more expensive data that will suffer for your whim.
- eurasiantiger 4y agoFrom what I’ve seen, many built-and-bundled apps have run into security issues in dependencies and have been forced to switch to ESM versions which are highly incompatible with CJS-targeting builds and so require significant retooling just to fix trivial security issues. It’s not npm’s fault for sure, but the ecosystem is fundamentally divergent on this issue and there doesn’t seem to be a universal solution.
- chrismorgan 4y agoI’m confused by your comment, because it doesn’t seem relevant and I don’t understand what you’re saying. My first impression is that you seem to be implying that the security issues in dependencies are in some way linked to the module format (ES/CommonJS), which is not the case. I can’t see why such issues would force a switch in either direction for anyone, given especially that ESM bundlers can generally slurp ECMAScript Modules and CommonJS alike (e.g. Rollup via @rollup/plugin-commonjs) with only rare compatibility issues after at most a little configuration, generally associated with very-badly-written code. Given how much of the npm ecosystem is still on CommonJS modules, there’d be much more outcry if you could’t intermingle the two in a bundler. So if what you had in mind was something like “version x was CommonJS and has a bug, version y is ESM and has fixed the bug”, well, you can probably update to y easily (at least as far as the CommonJS/ESM divide is concerned), and if you were forking or anything, if the bug is clearly identified then patching it onto x is easy too.
- eurasiantiger 4y agoIn practice, there are packages which have moved on to ESM and which no longer maintain their CJS version (at least as actively), making life difficult for maintainers of projects dependent on those CJS packages with published vulnerabilities. A CJS-targeting build can’t slurp up ESM dependencies. Not without dubious hacks, at least. See e.g. https://stackoverflow.com/questions/70545129/compile-a-package-that-depends-on-esm-only-library-into-a-commonjs-package https://stackoverflow.com/questions/70545129/compile-a-packa...
- mattlondon 4y agoThe import statement returns a promise, so you can lazily load modules as required (i.e. don't load everything up front, only load on-demand as users are using your app). You don't need to complicate things - just use vanilla JavaScript features as they were intended. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules#dynamic_module_loading https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guid...
- chrismorgan 4y agoIn practice, you are likely to have to contort your code very significantly to use dynamic imports pervasively. And pervasive usage such as you seem to be describing is emphatically not how dynamic imports were intended to be used, and wouldn’t speed things up much in general anyway (because you still can’t do anything until your dependencies have loaded), and stands a good chance of being slower. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/import https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe... expresses it well: > Use dynamic import only when necessary. The static form is preferable for loading initial dependencies, and can benefit more readily from static analysis tools and tree shaking. (Also, a quibble over your wording: dynamic imports aren’t a statement, but a call, though not a function call.)
- mattlondon 4y agoYou need to develop with this in mind from the start. I am not suggesting refactoring an existing application to use it for every single dependency as yes that would be a pain. Instead import your initial generic dependencies up-front as usual, but then notionally break your application up by CUJ and load the CUJ-specific code dynamically as needed. E.g. for a hypothetical email client you would import the initial "inbox view" code upfront (along with generic common dependencies), but you might not import the code needed for a rich text editor until the user clicks on the "write email" button, saving that network load and code execution for when - or indeed if - the user actually needs it.
- chrismorgan 4y ago