|
In the example on that blog post, you want lodash's "startCase" function. In typical bundling with tree shaking, the developer would download all of lodash and the bundler would identify that only the "startCase" function (and it's deps) are referenced, it would smoosh them all together into one file along with the rest of your code, you put that file on a CDN, and you're done. The client can access everything they need with two requests: index.html, then app.js With import maps, first loadash needs to be sure to split all their exports into individual files (they've done this for a while now as they predate tree shaking, but that's besides the point), then the client requests the website's JS, which tells it to go request https://unpkg.com/lodash-es@4.17.21/startCase.js, which then needs to make requests to https://unpkg.com/lodash-es@4.17.21/_createCompounder.js and https://unpkg.com/lodash-es@4.17.21/upperFirst.js. Then, _createCompounder.js needs to make a request to _arrayReduce.js (thankfully dependency free), deburr.js (depends on a ton of things), and you see where this is going. All said and done, the client ends up making 32 (!!!) separate requests to the lodash CDN for that single function. And yes, this does parallelize somewhat, but its still 8 distinct "depths" of sequential request bottleneck as the browser can't get the dependencies for a file until it gets that file, and there are 8 levels of dependencies for that single function. On my home network this is 250ms just to load a single function, and when I simulate "Slow 3G" it's nearly 20 seconds, just for that one function! It's truly mind-blowing. Also, as lodash doesn't minify their code for some reason, the bulk of the bandwidth is in comments/etc and I download 30kB of useless crap for that single function. When I instead use plain old bundling, the client makes a single request to index.html, which in turn makes a single request to app.js. The bundler does all the work of making app.js include the 10kb it actually needs (in 11ms no less), and even the "Slow 3G" client gets their website in 4 seconds. All in all these "import maps" seem like a massive step back in every way that matters to the client, just to save the developer the 11ms it takes to run esbuild (which comes fully batteries-included, btw. The rollup days of "you get a plugin! you get a plugin!" are over). Edit: this all sidesteps tree shaking as lodash is built to not need it, but you can see how if a library had multiple functions per file and especially if they had differing dependencies, the request chain would look much much worse. |
With that out of the way: yes, the lodash case would be pretty egregious if you imported the whole library in one. And most libraries imported this way will not be totally optimal: you'll probably load some code you don't need. But I think lodash is a pretty dramatic outlier; not only is it gigantic, it's exceptionally modular. Compare it to something like React, which is not small, but is nearly a monolith. The same I assume goes for Vue, etc, as well as other kinds of big libraries like GraphQL clients, third-party SDKs, etc. The percentage of code loaded that didn't need to be is, I would guess, usually much much smaller than it is if you're using a single function from lodash
I would add a couple more things:
- Minification is definitely a loss in the naive case, however, that should be easy for a CDN to implement (I think several already do it). I wouldn't be surprised if Deno/Fresh do this automagically too.
- HTTP/2 is optimized to make lots of parallel requests over a single TCP connection, which could conceivably mean a slightly larger total amount of code might load faster as separate modules than a single large bundle would. Of course like you said "depth" is still a limiting factor.
- For extreme cases, dynamic import() is an option in the native ES module system, and can be used to strategically defer module loading
So I don't think it's all that bad, even though like I said above there are tradeoffs. And I'll be curious to see where the industry goes.
PS: It would be good to have the option to bundle with Deno, though. One thing I would be excited to see, personally, is a Deno-ready bundler. One of the main limitations of using Deno right now, if you've got a front-end, is the lack of front-end tooling. You could install Node separately just for tools... but then that's a whole other system dependency, set of concerns, etc. I'd like to be able to do:
And have that Just Workâ˘. Maybe it uses WASM modules for speed....or maybe this could even be a first-class feature of the `deno` CLI