3 ms·
Can anyone recommend a good resource discussing the interplay between highly optimized js bundles and code caching? i.e. it seems to be me that one would benef
by jgalt212 3y ago
Can anyone recommend a good resource discussing the interplay between highly optimized js bundles and code caching? i.e. it seems to be me that one would benefit by serving the same js blob on most / all pages even if it includes unused code because then you pretty much always utilize the code cache.
- rimunroe 3y agoMaybe I’m misunderstanding your question but why would you want a single bundle vs multiple bundles? If you have a single blob you have to invalidate the whole thing when it updates. If you split your code out into multiple bundles you can invalidate only the bundles which contain changes. As far as I know this has been standard practice for the last decade at least.
- iainmerrick 3y agoI think it’s a good question, and the answer isn’t quite as straightforward as you suggest. The advantage of bundling is that you can do cross-module minification and dead-strip unused code. The extreme version of “multiple bundles” would be to minify each JS module individually, but don’t bundle them. That would clearly miss a lot of size optimisations. (And as I understand it, this is how Deno’s new package manager is meant to work, which makes me a bit suspicious.) The opposite extreme of one bundle for the entire app is great for optimisation, but as you say, then you have to invalidate the whole thing if anything changes. One bundle per route is tempting but then common code gets duplicated. Vite’s default behavior (via Rollup) is to make one bundle per common module. That works pretty well but I’ve found the number of output bundles can explode in surprising ways. In a recent project I manually split the code into chunks and loaded them via dynamic import() and that worked pretty well -- good balance of size optimisation, caching and manual control.
- jgalt212 3y agoThis is probably some big hairy statistical optimization problem. 100 bundles across 1,000 routes. How to you pack up the 100 bundles into n (n<100) larger bundles such that average time to interactive is smallest. Of course, you get different answers depending on first time vs returning site visitors.
- rimunroe 3y agoThanks for the thorough reply! I confess I wasn't thinking about a particular build tool. My recent experience has been with Vite, where I took a similar approach to what you describe, but haven't had to dig deep into bundle performance because that's not a bottleneck for our application. The last time I did deeper work on the subject was years ago with Webpack. I thought Webpack at least did dead-code elimination before splitting things into chunks. If I'm reading this random GitHub issue[1] right (and the asker is also right), Webpack does partially behave as I expected, but the pre-chunking optimization pass occurs before things like constant expression evaluation. [1] https://github.com/webpack/webpack/issues/16672 https://github.com/webpack/webpack/issues/16672
- jgalt212 3y agoWe have a single bundle of JavaScript with all our helper functions, then a page specific bundle that uses some of the helper functions. The same helper function bundle is delivered to all pages--not a different tree-shaken version delivered to each page that only has the functions used by that page. One helper function bundle is bigger, but since every page gets it, this bundle is code cached.
- chuckadams 3y agoBundling everything together and letting it be cached is how most single-page apps do it, yes. If the code isn't used by anything at all, it gets removed from the bundle entirely: that process is called "tree shaking". Modern frameworks like Nuxt also tend to do something called "code splitting": for example, I have a Nuxt app that contains, among others, pages like /vehicles, /drivers, and /fuel. Instead of creating a single giant js bundle, Nuxt creates several, including a file for code that's used by just the /vehicles page, a separate chunk for code that's only for the /drivers page, and so on, along with common chunks that are used by all of them. Script tags are then generated for each of these three pages so that when you hit one of the pages, you only get enough of the code for that page, and the rest is loaded when you navigate to another page. It's still a single-page app, but with multiple entry points, each one optimized to initially load only what that entry point needs, the rest being dynamically imported when needed. Each bundle's filename contains a hash of its content, so they can be cached forever (that does have consequences when upgrading though: a typical SPA has to do some some version checking and self-updating on its own, as the browser will otherwise be eager to serve stale code) In dev though, one usually doesn't even bundle, and runs the app on a dev server that dynamically compiles and serves individual modules. When I initially load the app off the dev server, my browser makes close to a thousand requests for all those modules, but pipelining and caching make it all quite zippy regardless. Bundling is still faster and uses less bandwidth though, so for production one typically does still build a bundle (or several code-split ones). There's gory details at https://webpack.js.org/guides/code-splitting/ https://webpack.js.org/guides/code-splitting/ (for webpack; other bundlers like Rollup work similarly) but frameworks like Next/Nuxt do it automatically as part of the build process.