Browse Source

Moar links

master
David Larlet 1 year ago
parent
commit
aae331b01e
No known key found for this signature in database

+ 646
- 0
cache/2020/be8e81e9337d81e7a31a5cc1f4d38435/index.html View File

@@ -0,0 +1,646 @@
<!doctype html><!-- This is a valid HTML5 document. -->
<!-- Screen readers, SEO, extensions and so on. -->
<html lang="fr">
<!-- Has to be within the first 1024 bytes, hence before the <title>
See: https://www.w3.org/TR/2012/CR-html5-20121217/document-metadata.html#charset -->
<meta charset="utf-8">
<!-- Why no `X-UA-Compatible` meta: https://stackoverflow.com/a/6771584 -->
<!-- The viewport meta is quite crowded and we are responsible for that.
See: https://codepen.io/tigt/post/meta-viewport-for-2015 -->
<meta name="viewport" content="width=device-width,initial-scale=1">
<!-- Required to make a valid HTML5 document. -->
<title>The Cost of Javascript Frameworks (archive) — David Larlet</title>
<!-- Generated from https://realfavicongenerator.net/ such a mess. -->
<link rel="apple-touch-icon" sizes="180x180" href="/static/david/icons2/apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="32x32" href="/static/david/icons2/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="/static/david/icons2/favicon-16x16.png">
<link rel="manifest" href="/static/david/icons2/site.webmanifest">
<link rel="mask-icon" href="/static/david/icons2/safari-pinned-tab.svg" color="#07486c">
<link rel="shortcut icon" href="/static/david/icons2/favicon.ico">
<meta name="msapplication-TileColor" content="#f0f0ea">
<meta name="msapplication-config" content="/static/david/icons2/browserconfig.xml">
<meta name="theme-color" content="#f0f0ea">
<!-- Documented, feel free to shoot an email. -->
<link rel="stylesheet" href="/static/david/css/style_2020-04-25.css">
<!-- See https://www.zachleat.com/web/comprehensive-webfonts/ for the trade-off. -->
<link rel="preload" href="/static/david/css/fonts/triplicate_t4_poly_regular.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: light), (prefers-color-scheme: no-preference)" crossorigin>
<link rel="preload" href="/static/david/css/fonts/triplicate_t4_poly_bold.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: light), (prefers-color-scheme: no-preference)" crossorigin>
<link rel="preload" href="/static/david/css/fonts/triplicate_t4_poly_italic.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: light), (prefers-color-scheme: no-preference)" crossorigin>
<link rel="preload" href="/static/david/css/fonts/triplicate_t3_regular.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: dark)" crossorigin>
<link rel="preload" href="/static/david/css/fonts/triplicate_t3_bold.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: dark)" crossorigin>
<link rel="preload" href="/static/david/css/fonts/triplicate_t3_italic.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: dark)" crossorigin>

<meta name="robots" content="noindex, nofollow">
<meta content="origin-when-cross-origin" name="referrer">
<!-- Canonical URL for SEO purposes -->
<link rel="canonical" href="https://timkadlec.com/remembers/2020-04-21-the-cost-of-javascript-frameworks/">

<body class="remarkdown h1-underline h2-underline h3-underline hr-center ul-star pre-tick">

<article>
<h1>The Cost of Javascript Frameworks</h1>
<h2><a href="https://timkadlec.com/remembers/2020-04-21-the-cost-of-javascript-frameworks/">Source originale du contenu</a></h2>
<p>There is no faster (pun intended) way to slow down a site than to use a bunch of JavaScript.</p>

<p>The thing about JavaScript is you end up paying a performance tax no less than four times:</p>

<ol>
<li>The cost of downloading the file on the network</li>
<li>The cost of parsing and compiling the uncompressed file once downloaded</li>
<li>The cost of executing the JavaScript</li>
<li>The memory cost</li>
</ol>

<p>The <a href="https://v8.dev/blog/cost-of-javascript-2019">combination is very expensive</a>.</p>

<p>And we are shipping an increasingly high amount. We’re making the core functionality of our sites increasingly dependant on JavaScript as organizations move towards sites driven by frameworks like React, Vue.js, and friends.</p>

<p>I see a lot of very heavy sites using them, but then, my perspective is very biased as the companies that I work with work with me precisely <em>because</em> they are facing performance challenges. I was curious just how common the situation is and just <em>how</em> much of a penalty we’re paying when we make these frameworks the default starting point.</p>

<p>Thanks to <a href="https://httparchive.org">HTTP Archive</a>, we can figure that out.</p>

<h2 id="the-data">The data</h2>

<p>In total, HTTP Archive tracks 4,308,655 desktop URLs, and 5,484,239 mobile URLs. Among the many data points HTTP Archive reports for those URLs is a list of the detected technologies for a given site. That means we can pick out the thousands of sites that use various frameworks and see how much code they’re shipping, and what that costs the CPU.</p>

<p>I ran all the queries against March of 2020, the most recent run at the time.</p>

<p>I decided to compare the aggregate HTTP Archive data for all sites recorded against sites with React, Vue.js, and Angular detected.</p>

<p>For fun, I also added jQuery—it’s still massively popular, and it also represents a bit of a different approach to building with JavaScript than the single-page application (<abbr title="Single-Page Application">SPA</abbr>) approach provided by React, Vue.js and Angular.</p>

<table cellspacing="3" class="plain">
<caption>URLs in HTTP Archive with specific frameworks detected</caption>
<thead>
<tr>
<th>Framework</th>
<th class="num">Mobile URLs</th>
<th class="num">Desktop URLs</th>
</tr>
</thead>
<tbody>
<tr>
<td data-title="Framework">jQuery</td>
<td data-title="Mobile URLs" class="num">4,615,474</td>
<td data-title="Desktop URLs" class="num">3,714,643</td>
</tr>
<tr>
<td data-title="Framework">React</td>
<td data-title="Mobile URLs" class="num">489,827</td>
<td data-title="Desktop URLs" class="num">241,023</td>
</tr>
<tr>
<td data-title="Framework">Vue.js</td>
<td data-title="Mobile URLs" class="num">85,649</td>
<td data-title="Desktop URLs" class="num">43,691</td>
</tr>
<tr>
<td data-title="Framework">Angular</td>
<td data-title="Mobile URLs" class="num">19,423</td>
<td data-title="Desktop URLs" class="num">18,088</td>
</tr>
</tbody>
</table>

<h2 id="hopes-and-dreams">Hopes and dreams</h2>

<p>Before we dig in, here’s what I would hope.</p>

<p>In an ideal world, I believe a framework should go beyond developer experience value and provide concrete value for the people using our sites. Performance is just one part of that—accessibility and security both come to mind as well—but it’s an essential part.</p>

<p>So in an ideal world, a framework makes it easier to perform well by either providing a better starting point or providing constraints and characteristics that make it hard to build something that doesn’t perform well.</p>

<p>The best of frameworks would do both: provide a better starting point and help to restrict how out of hands things can get.</p>

<p>Looking at the median for our data isn’t going to tell us that, and <a href="https://timkadlec.com/remembers/2018-06-07-prioritizing-the-long-tail-of-performance/">in fact leaves a ton of information out</a>. Instead, for each stat, I pulled the following percentiles: the 10th, 25th, 50th (the median), 75th, and 90th.</p>

<p>The 10th and 90th percentiles are particularly interesting to me. The 10th percentile represents the best of class (or at least, reasonably close to the best of class) for a given framework. In other words, only 10% of all sites using a given framework reach that mark or better. The 90th percentile, on the other hand, is the opposite of the spectrum—it shows us how bad things can get. The 90th percentile represents the long-tail—that last 10% of sites with the highest number of bytes or largest amount of main thread time.</p>

<h2 id="javascript-bytes">JavaScript Bytes</h2>

<p>For the starting point, it makes sense to look at the amount of JavaScript passed over the network.</p>

<table cellspacing="3" class="plain">
<caption>JavaScript Bytes Served to Mobile Devices, by Percentile</caption>
<thead>
<tr>
<th/>
<th class="num">10th</th>
<th class="num">25th</th>
<th class="num">50th</th>
<th class="num">75th</th>
<th class="num">90th</th>
</tr>
</thead>
<tbody>
<tr>
<td data-title="Dataset">All Sites</td>
<td data-title="Bytes Served, 10th Percentile" class="num">93.4kb</td>
<td data-title="Bytes Served, 25th Percentile" class="num">196.6kb</td>
<td data-title="Bytes Served, 50th Percentile" class="num">413.5kb</td>
<td data-title="Bytes Served, 75th Percentile" class="num">746.8kb</td>
<td data-title="Bytes Served, 90th Percentile" class="num">1,201.6kb</td>
</tr>
<tr>
<td data-title="Dataset">Sites with jQuery</td>
<td data-title="Bytes Served, 10th Percentile" class="num">110.3kb</td>
<td data-title="Bytes Served, 25th Percentile" class="num">219.8kb</td>
<td data-title="Bytes Served, 50th Percentile" class="num">430.4kb</td>
<td data-title="Bytes Served, 75th Percentile" class="num">748.6kb</td>
<td data-title="Bytes Served, 90th Percentile" class="num">1,162.3kb</td>
</tr>
<tr>
<td data-title="Dataset">Sites with Vue.js</td>
<td data-title="Bytes Served, 10th Percentile" class="num">244.7kb</td>
<td data-title="Bytes Served, 25th Percentile" class="num">409.3kb</td>
<td data-title="Bytes Served, 50th Percentile" class="num">692.1kb</td>
<td data-title="Bytes Served, 75th Percentile" class="num">1,065.5kb</td>
<td data-title="Bytes Served, 90th Percentile" class="num">1,570.7kb</td>
</tr>
<tr>
<td data-title="Dataset">Sites with Angular</td>
<td data-title="Bytes Served, 10th Percentile" class="num">445.1kb</td>
<td data-title="Bytes Served, 25th Percentile" class="num">675.6kb</td>
<td data-title="Bytes Served, 50th Percentile" class="num">1,066.4kb</td>
<td data-title="Bytes Served, 75th Percentile" class="num">1,761.5kb</td>
<td data-title="Bytes Served, 90th Percentile" class="num">2,893.2kb</td>
</tr>
<tr>
<td data-title="Dataset">Sites with React</td>
<td data-title="Bytes Served, 10th Percentile" class="num">345.8kb</td>
<td data-title="Bytes Served, 25th Percentile" class="num">441.6kb</td>
<td data-title="Bytes Served, 50th Percentile" class="num">690.3kb</td>
<td data-title="Bytes Served, 75th Percentile" class="num">1,238.5kb</td>
<td data-title="Bytes Served, 90th Percentile" class="num">1,893.6kb</td>
</tr>
</tbody>
</table>

<p><picture class="diagram">
<source media="(min-width: 650px)" srcset="/images/cost-of-frameworks-bytes-mobile.png">
<img src="/images/cost-of-frameworks-bytes-mobile-sm.png" alt="Boxplot charts showing the amount of JavaScript bytes served to mobile devices for sites with various frameworks. Also presented by the preceding table"/>
</source></picture></p>

<table cellspacing="3" class="plain">
<caption>JavaScript Bytes Served to Desktop Devices, by Percentile</caption>
<thead>
<tr>
<th/>
<th class="num">10th</th>
<th class="num">25th</th>
<th class="num">50th</th>
<th class="num">75th</th>
<th class="num">90th</th>
</tr>
</thead>
<tbody>
<tr>
<td data-title="Dataset">All Sites</td>
<td data-title="Bytes Served, 10th Percentile" class="num">105.5kb</td>
<td data-title="Bytes Served, 25th Percentile" class="num">226.6kb</td>
<td data-title="Bytes Served, 50th Percentile" class="num">450.4kb</td>
<td data-title="Bytes Served, 75th Percentile" class="num">808.8kb</td>
<td data-title="Bytes Served, 90th Percentile" class="num">1,267.3kb</td>
</tr>
<tr>
<td data-title="Dataset">Sites with jQuery</td>
<td data-title="Bytes Served, 10th Percentile" class="num">121.7kb</td>
<td data-title="Bytes Served, 25th Percentile" class="num">242.2kb</td>
<td data-title="Bytes Served, 50th Percentile" class="num">458.3kb</td>
<td data-title="Bytes Served, 75th Percentile" class="num">803.4kb</td>
<td data-title="Bytes Served, 90th Percentile" class="num">1,235.3kb</td>
</tr>
<tr>
<td data-title="Dataset">Sites with Vue.js</td>
<td data-title="Bytes Served, 10th Percentile" class="num">248.0kb</td>
<td data-title="Bytes Served, 25th Percentile" class="num">420.1kb</td>
<td data-title="Bytes Served, 50th Percentile" class="num">718.0kb</td>
<td data-title="Bytes Served, 75th Percentile" class="num">1,122.5kb</td>
<td data-title="Bytes Served, 90th Percentile" class="num">1,643.1kb</td>
</tr>
<tr>
<td data-title="Dataset">Sites with Angular</td>
<td data-title="Bytes Served, 10th Percentile" class="num">468.8kb</td>
<td data-title="Bytes Served, 25th Percentile" class="num">716.9kb</td>
<td data-title="Bytes Served, 50th Percentile" class="num">1,144.2kb</td>
<td data-title="Bytes Served, 75th Percentile" class="num">1,930.0kb</td>
<td data-title="Bytes Served, 90th Percentile" class="num">3,283.1kb</td>
</tr>
<tr>
<td data-title="Dataset">Sites with React</td>
<td data-title="Bytes Served, 10th Percentile" class="num">308.6kb</td>
<td data-title="Bytes Served, 25th Percentile" class="num">469.0kb</td>
<td data-title="Bytes Served, 50th Percentile" class="num">841.9kb</td>
<td data-title="Bytes Served, 75th Percentile" class="num">1,472.2kb</td>
<td data-title="Bytes Served, 90th Percentile" class="num">2,197.8kb</td>
</tr>
</tbody>
</table>

<p><picture class="diagram">
<source media="(min-width: 650px)" srcset="/images/cost-of-frameworks-bytes-desktop.png">
<img src="/images/cost-of-frameworks-bytes-desktop-sm.png" alt="Boxplot charts showing the amount of JavaScript bytes served to desktop devices for sites with various frameworks. Also presented by the preceding table"/>
</source></picture></p>

<p>For the sheer payload size, the 10th percentile turns out pretty much as you would expect: if one of these frameworks are in use, there’s more JavaScript even in the most ideal of situations. That’s not surprising—you can’t add a JavaScript framework as a default starting point and expect to ship less JavaScript out of the box.</p>

<p>What <em>is</em> notable is that some frameworks correlate to better starting points than others. Sites with jQuery are the best of the bunch, starting with about 15% more JavaScript on desktop devices and about 18% more on mobile. (There’s admittedly a little bit of bias here. jQuery is found on a lot of sites, so naturally, it’s going to have a tighter relationship to the overall numbers than others. Still, that doesn’t change the way the raw numbers appear for each framework.)</p>

<p>While even a 15-18% increase is notable, comparing that to the opposite end of the spectrum makes the jQuery tax feel very low. Sites with Angular ship 344% more JavaScript on desktop at the 10th percentile, and 377% more on mobile. Sites with React, the next heaviest, ship 193% more JavaScript on desktop and 270% more on mobile devices.</p>

<p>I mentioned earlier that even if the starting point is a little off, I would hope that a framework could still provide value by limiting the upper bound in some way.</p>

<p>Interestingly, jQuery driven sites follow this pattern. While they’re a bit heftier (15-18%) at the 10th percentile, they’re slightly smaller than the aggregate at the 90th percentile—about 3% on both desktop and mobile. Neither of those numbers is super significant, but at least sites with jQuery don’t seem to have a dramatically worse long-tail in terms of JavaScript bytes shipped.</p>

<p>The same can’t be said of the other frameworks.</p>

<p>Just as with the 10th percentile, Angular and React driven sites tend to distance themselves from others at the 90th percentile, and not in a very flattering way.</p>

<p>At the 90th percentile, Angular sites ship 141% more bytes on mobile and 159% more bytes on desktop. Sites with React ship 73% more bytes on desktop and 58% more on mobile. With a 90th percentile weight of 2,197.8kb, React sites ship 322.9kb more bytes of JavaScript to mobile users than Vue.js, the next closest. The desktop gap between Angular and React and the rest of the crowd is even higher—React-driven sites ship 554.7kb more JavaScript than Vue.js-driven sites.</p>

<h2 id="javascript-main-thread-time">JavaScript Main Thread Time</h2>

<p>It’s clear from the data that sites with these frameworks in place tend to pay a large penalty in terms of bytes. But of course, that’s just one part of the equation.</p>

<p>Once that JavaScript arrives, it has to get to work. Any work that occurs on the main thread of the browser is particularly troubling. The main thread is responsible for handling user input, during style calculation, layout and painting. If we’re clogging it up with a lot of JavaScript work, the main thread has no chance to do those things in a timely manner, leading to lag and jank.</p>

<p>HTTP Archive records V8 main thread time, so we can query to see just how much time that main thread is working on all that JavaScript.</p>

<table cellspacing="3" class="plain">
<caption>Scripting related CPU time (in milliseconds) for mobile devices, in percentiles</caption>
<thead>
<tr>
<th/>
<th class="num">10th</th>
<th class="num">25th</th>
<th class="num">50th</th>
<th class="num">75th</th>
<th class="num">90th</th>
</tr>
</thead>
<tbody>
<tr>
<td data-title="Dataset">All Sites</td>
<td data-title="Main thread time, 10th Percentile" class="num">356.4ms</td>
<td data-title="Main thread time, 25th Percentile" class="num">959.7ms</td>
<td data-title="Main thread time, 50th Percentile" class="num">2,372.1ms</td>
<td data-title="Main thread time, 75th Percentile" class="num">5,367.3ms</td>
<td data-title="Main thread time, 90th Percentile" class="num">10,485.8ms</td>
</tr>
<tr>
<td data-title="Dataset">Sites with jQuery</td>
<td data-title="Main thread time, 10th Percentile" class="num">575.3ms</td>
<td data-title="Main thread time, 25th Percentile" class="num">1,147.4ms</td>
<td data-title="Main thread time, 50th Percentile" class="num">2,555.9ms</td>
<td data-title="Main thread time, 75th Percentile" class="num">5,511.0ms</td>
<td data-title="Main thread time, 90th Percentile" class="num">10,349.4ms</td>
</tr>
<tr>
<td data-title="Dataset">Sites with Vue.js</td>
<td data-title="Main thread time" class="num">1,130.0ms</td>
<td data-title="Main thread time" class="num">2,087.9ms</td>
<td data-title="Main thread time" class="num">4,100.4ms</td>
<td data-title="Main thread time" class="num">7,676.1ms</td>
<td data-title="Main thread time" class="num">12,849.4ms</td>
</tr>
<tr>
<td data-title="Dataset">Sites with Angular</td>
<td data-title="Main thread time, 10th Percentile" class="num">1,471.3ms</td>
<td data-title="Main thread time, 25th Percentile" class="num">2,380.1ms</td>
<td data-title="Main thread time, 50th Percentile" class="num">4,118.6ms</td>
<td data-title="Main thread time, 75th Percentile" class="num">7,450.8ms</td>
<td data-title="Main thread time, 90th Percentile" class="num">13,296.4ms</td>
</tr>
<tr>
<td data-title="Dataset">Sites with React</td>
<td data-title="Main thread time, 10th Percentile" class="num">2,700.1ms</td>
<td data-title="Main thread time, 25th Percentile" class="num">5,090.3ms</td>
<td data-title="Main thread time, 50th Percentile" class="num">9,287.6ms</td>
<td data-title="Main thread time, 75th Percentile" class="num">14,509.6ms</td>
<td data-title="Main thread time, 90th Percentile" class="num">20,813.3ms</td>
</tr>
</tbody>
</table>

<p><picture class="diagram">
<source media="(min-width: 650px)" srcset="/images/cost-of-frameworks-cpu-mobile.png">
<img src="/images/cost-of-frameworks-cpu-mobile-sm.png" alt="Boxplot charts showing the amount of JavaScript cpu time for mobile tests for sites with various frameworks. Also presented by the preceding table"/>
</source></picture></p>

<table cellspacing="3" class="plain">
<caption>Scripting related CPU time (in milliseconds) for desktop devices, in percentiles</caption>
<thead>
<tr>
<th/>
<th data-title="Main thread time, 10th Percentile" class="num">10th</th>
<th data-title="Main thread time, 25th Percentile" class="num">25th</th>
<th data-title="Main thread time, 50th Percentile" class="num">50th</th>
<th data-title="Main thread time, 75th Percentile" class="num">75th</th>
<th data-title="Main thread time, 90th Percentile" class="num">90th</th>
</tr>
</thead>
<tbody>
<tr>
<td data-title="Dataset">All Sites</td>
<td data-title="Main thread time" class="num">146.0ms</td>
<td data-title="Main thread time" class="num">351.8ms</td>
<td data-title="Main thread time" class="num">831.0ms</td>
<td data-title="Main thread time" class="num">1,739.8ms</td>
<td data-title="Main thread time" class="num">3,236.8ms</td>
</tr>
<tr>
<td data-title="Dataset">Sites with jQuery</td>
<td data-title="Main thread time, 10th Percentile" class="num">199.6ms</td>
<td data-title="Main thread time, 25th Percentile" class="num">399.2ms</td>
<td data-title="Main thread time, 50th Percentile" class="num">877.5ms</td>
<td data-title="Main thread time, 75th Percentile" class="num">1,779.9ms</td>
<td data-title="Main thread time, 90th Percentile" class="num">3,215.5ms</td>
</tr>
<tr>
<td data-title="Dataset">Sites with Vue.js</td>
<td data-title="Main thread time, 10th Percentile" class="num">350.4ms</td>
<td data-title="Main thread time, 25th Percentile" class="num">650.8ms</td>
<td data-title="Main thread time, 50th Percentile" class="num">1,280.7ms</td>
<td data-title="Main thread time, 75th Percentile" class="num">2,388.5ms</td>
<td data-title="Main thread time, 90th Percentile" class="num">4,010.8ms</td>
</tr>
<tr>
<td data-title="Dataset">Sites with Angular</td>
<td data-title="Main thread time, 10th Percentile" class="num">482.2ms</td>
<td data-title="Main thread time, 25th Percentile" class="num">777.9ms</td>
<td data-title="Main thread time, 50th Percentile" class="num">1,365.5ms</td>
<td data-title="Main thread time, 75th Percentile" class="num">2,400.6ms</td>
<td data-title="Main thread time, 90th Percentile" class="num">4,171.8ms</td>
</tr>
<tr>
<td data-title="Dataset">Sites with React</td>
<td data-title="Main thread time, 10th Percentile" class="num">508.0ms</td>
<td data-title="Main thread time, 25th Percentile" class="num">1,045.6ms</td>
<td data-title="Main thread time, 50th Percentile" class="num">2,121.1ms</td>
<td data-title="Main thread time, 75th Percentile" class="num">4,235.1ms</td>
<td data-title="Main thread time, 90th Percentile" class="num">7,444.3ms</td>
</tr>
</tbody>
</table>

<p><picture class="diagram">
<source media="(min-width: 650px)" srcset="/images/cost-of-frameworks-cpu-desktop.png">
<img src="/images/cost-of-frameworks-cpu-desktop-sm.png" alt="Boxplot charts showing the amount of JavaScript cpu time for desktop tests for sites with various frameworks. Also presented by the preceding table"/>
</source></picture></p>

<p>There are some very familiar themes here.</p>

<p>First, sites with jQuery detected spend <em>much</em> less time on JavaScript work on the main thread than the other three analyzed. At the 10th percentile, there’s a 61% increase in JavaScript main thread work being done on mobile devices and 37% more on desktop. At the 90th percentile, jQuery sites are gain pretty darn close to the aggregate, spending 1.3% <em>less</em> time on the main thread for mobile devices and ..7% less time on desktop machines.</p>

<p>The opposite end—the frameworks that correlate to the most time spent on the main thread—is once again made up of Angular and React. The only difference is that while Angular sites shipped more JavaScript than React sites, they actually spend less time on the CPU—<em>much</em> less time.</p>

<p>At the 10th percentile, Angular sites spend 230% more time on the CPU for JavaScript related work on desktop devices, and 313% more on mobile devices. React sites bring up the tail end, spending 248% more time on desktop devices and 658% more time on mobile devices. No, 658% is not a typo. At the 10th percentile, sites with React spend 2.7s on the main thread dealing with all the JavaScript sent down.</p>

<p>Compared to those big numbers, the situation at the 90th percentile at least looks a little better. The main thread of Angular sites spends 29% more time on JavaScript for desktop devices and 27% more time on mobile devices. React sites spend 130% more time on desktop and 98% more time on mobile devices.</p>

<p>Those percentages look much better than at the 10th percentile, but keep in mind that the bulk numbers are pretty scary: that’s 20.8s of main thread work for sites built with React at the 90th percentile on mobile devices. (What, exactly, is happening during that time is a topic for a follow-up post, I think.)</p>

<p>There’s one potential gotcha (thanks <a href="https://jeremy.codes/">Jeremy</a> for making sure I double-checked the stats from this angle)—many sites will pull in multiple libraries. In particular, I see a lot of sites pulling jQuery in alongside React or Vue.js as they’re migrating to that architecture. So, I re-ran the queries, only this time I only included URLs that included only React, jQuery, Angular or Vue.js <em>not some combination of them</em>.</p>

<table cellspacing="3" class="plain">
<caption>Scripting related CPU time (in milliseconds) for mobile devices where only one of the frameworks is detected, in percentiles</caption>
<thead>
<tr>
<th/>
<th class="num">10th</th>
<th class="num">25th</th>
<th class="num">50th</th>
<th class="num">75th</th>
<th class="num">90th</th>
</tr>
</thead>
<tbody>
<tr>
<td data-title="Dataset">Sites with only jQuery</td>
<td data-title="Main thread time, 10th Percentile" class="num">542.9ms</td>
<td data-title="Main thread time, 25th Percentile" class="num">1,062.2ms</td>
<td data-title="Main thread time, 50th Percentile" class="num">2,297.4ms</td>
<td data-title="Main thread time, 75th Percentile" class="num">4,769.7ms</td>
<td data-title="Main thread time, 90th Percentile" class="num">8,718.2ms</td>
</tr>
<tr>
<td data-title="Dataset">Sites with only Vue.js</td>
<td data-title="Main thread time, 10th Percentile" class="num">944.0ms</td>
<td data-title="Main thread time, 25th Percentile" class="num">1,716.3ms</td>
<td data-title="Main thread time, 50th Percentile" class="num">3,194.7ms</td>
<td data-title="Main thread time, 75th Percentile" class="num">5,959.6ms</td>
<td data-title="Main thread time, 90th Percentile" class="num">9,843.8ms</td>
</tr>
<tr>
<td data-title="Dataset">Sites with Angular</td>
<td data-title="Main thread time, 10th Percentile" class="num">1,328.9ms</td>
<td data-title="Main thread time, 25th Percentile" class="num">2,151.9ms</td>
<td data-title="Main thread time, 50th Percentile" class="num">3,695.3ms</td>
<td data-title="Main thread time, 75th Percentile" class="num">6,629.3ms</td>
<td data-title="Main thread time, 90th Percentile" class="num">11,607.7ms</td>
</tr>
<tr>
<td data-title="Dataset">Sites with React</td>
<td data-title="Main thread time, 10th Percentile" class="num">2,443.2ms</td>
<td data-title="Main thread time, 25th Percentile" class="num">4,620.5ms</td>
<td data-title="Main thread time, 50th Percentile" class="num">10,061.4ms</td>
<td data-title="Main thread time, 75th Percentile" class="num">17,074.3ms</td>
<td data-title="Main thread time, 90th Percentile" class="num">24,956.3ms</td>
</tr>
</tbody>
</table>

<p><picture class="diagram">
<source media="(min-width: 650px)" srcset="/images/cost-of-frameworks-cpu-mobile-only.png">
<img src="/images/cost-of-frameworks-cpu-mobile-only-sm.png" alt="Boxplot charts showing the amount of JavaScript cpu time for mobiles tests for sites with only one framework detected. Also presented by the preceding table"/>
</source></picture></p>

<p>First, the unsurprising bit: when only one framework is used, performance improves far more often than not. The numbers for every framework look better at the 10th and 25th percentile. That makes sense. A site that is built well with one framework <em>should</em> perform better than a site that is built well with two or more.</p>

<p>In fact, the numbers for every framework look better at each percentile with one curious exception.
What surprised me, and the reason I ended up including this data, is that at the 50th percentile and beyond, sites using React perform <em>worse</em> when React is the only framework in use.</p>

<p>It’s a bit odd, but here’s my best guess.</p>

<p>If you have React and jQuery running alongside each other, you’re more likely to be in the midst of a migration to React, or a mixed codebase. Since we have already seen that sites with jQuery spend less time on the main thread than sites with React, it makes sense that having some functionality still driven by jQuery would bring the numbers down a bit.</p>

<p>As you move away from jQuery and focus more on React exclusively, though, that changes. If the site is built really well and you’re using React sparingly, you’re fine. But for the average site, more work inside of React means the main thread receives an increasing amount of strain.</p>

<h2 id="the-mobile-desktop-gap">The mobile/desktop gap</h2>

<p>Another angle that’s worth looking at is just how large the gap is between that mobile experience and the desktop experience. Looking at it from a bytes perspective, nothing too scary jumps out. Sure, I’d love to see fewer bytes passed along, but neither mobile nor desktop devices receive significantly more bytes than the other.</p>

<p>But once you look at the processing time, the gap is significant.</p>

<table cellspacing="3" class="plain">
<caption>Percentage increase in main thread scripting work on mobile devices compared to desktop devices, by percentiles</caption>
<thead>
<tr>
<th/>
<th class="num">10th</th>
<th class="num">25th</th>
<th class="num">50th</th>
<th class="num">75th</th>
<th class="num">90th</th>
</tr>
</thead>
<tbody>
<tr>
<td data-title="Dataset">All Sites</td>
<td data-title="Change from Desktop to Mobile, 10th percentile" class="num">144.1%</td>
<td data-title="Change from Desktop to Mobile, 25th percentile" class="num">172.8%</td>
<td data-title="Change from Desktop to Mobile, 50th percentile" class="num">185.5%</td>
<td data-title="Change from Desktop to Mobile, 75th percentile" class="num">208.5%</td>
<td data-title="Change from Desktop to Mobile, 90th percentile" class="num">224.0%</td>
</tr>
<tr>
<td data-title="Dataset">Sites with jQuery</td>
<td data-title="Change from Desktop to Mobile, 10th percentile" class="num">188.2%</td>
<td data-title="Change from Desktop to Mobile, 25th percentile" class="num">187.4%</td>
<td data-title="Change from Desktop to Mobile, 50th percentile" class="num">191.3%</td>
<td data-title="Change from Desktop to Mobile, 75th percentile" class="num">209.6%</td>
<td data-title="Change from Desktop to Mobile, 90th percentile" class="num">221.9%</td>
</tr>
<tr>
<td data-title="Dataset">Sites with Vue.js</td>
<td data-title="Change from Desktop to Mobile, 10th percentile" class="num">222.5%</td>
<td data-title="Change from Desktop to Mobile, 25th percentile" class="num">220.8%</td>
<td data-title="Change from Desktop to Mobile, 50th percentile" class="num">220.2%</td>
<td data-title="Change from Desktop to Mobile, 75th percentile" class="num">221.4%</td>
<td data-title="Change from Desktop to Mobile, 90th percentile" class="num">220.4%</td>
</tr>
<tr>
<td data-title="Dataset">Sites with Angular</td>
<td data-title="Change from Desktop to Mobile, 10th percentile" class="num">205.1%</td>
<td data-title="Change from Desktop to Mobile, 25th percentile" class="num">206.0%</td>
<td data-title="Change from Desktop to Mobile, 50th percentile" class="num">201.6%</td>
<td data-title="Change from Desktop to Mobile, 75th percentile" class="num">210.4%</td>
<td data-title="Change from Desktop to Mobile, 90th percentile" class="num">218.7%</td>
</tr>
<tr>
<td data-title="Dataset">Sites with React</td>
<td data-title="Change from Desktop to Mobile, 10th percentile" class="num">431.5%</td>
<td data-title="Change from Desktop to Mobile, 25th percentile" class="num">386.8%</td>
<td data-title="Change from Desktop to Mobile, 50th percentile" class="num">337.9%</td>
<td data-title="Change from Desktop to Mobile, 75th percentile" class="num">242.6%</td>
<td data-title="Change from Desktop to Mobile, 90th percentile" class="num">179.6%</td>
</tr>
</tbody>
</table>

<p>While some variance is expected between a phone and a laptop, seeing numbers this high tells me that the current crop of frameworks isn’t doing enough to prioritize less powerful devices and help to close that gap. Even at the 10th percentile, React sites spend 431.5% more time on the main thread on mobile devices as they do on desktop devices. jQuery has the lowest gap of all frameworks, but even there, it equates to 188.2% more time on mobile devices. When we make the CPU work harder—and increasingly we are—folks with less powerful devices end up holding the bill.</p>

<h2 id="the-big-picture">The big picture</h2>

<p>Good frameworks should provide a better starting point on the essentials (security, accessibility, performance) or have built-in constraints that make it harder to ship something that violates those.</p>

<p>That doesn’t appear to be happening with performance (<a href="https://webaim.org/projects/million/#frameworks">nor with accessibility</a>, apparently).</p>

<p>It’s worth noting that because sites with React or Angular spend more time on the CPU than others, that isn’t necessarily the same as saying React is more expensive on the CPU than Vue.js. In fact, it says very little about the performance of the core frameworks in play and much more about the approach to development these frameworks may encourage (whether intentionally or not) through documentation, ecosystem, and general coding practices.</p>

<p>It’s also worth noting what we don’t have here: data about how much time the device spends on this JavaScript for subsequent views. The argument for the <abbr title="Single-Page Application">SPA</abbr> architecture is that, once the <abbr title="Single-Page Application">SPA</abbr> is in place, you theoretically get faster subsequent page loads. My own experience tells me that’s far from a given, but we don’t have concrete data here to make that case in either direction.</p>

<p>What is clear: right now, if you’re using a framework to build your site, you’re making a trade-off in terms of initial performance—even in the best of scenarios.</p>

<p><em>Some</em> trade-off may be acceptable in the right situations, but it’s important that we make that exchange consciously.</p>

<p>There’s reason for optimism. I’m encouraged by how closely the folks at Chrome have been working with some of these frameworks to help improve their performance.</p>

<p>But I’m also pragmatic. New architectures tend to create performance problems just as often as they solve them, and it takes time to right the ship. Just as <a href="https://timkadlec.com/remembers/2019-04-18-new-network-fallacies/">we shouldn’t expect new networks to solve all our performance woes</a>, we shouldn’t expect that the next version of our favorite framework is going to solve them all either.</p>

<p>If you are going to use one of these frameworks, then you have to take extra steps to make sure you don’t negatively impact performance in the meantime. Here are a few great starting considerations:</p>

<ul>
<li>Do a sanity check: do you <em>really</em> need to use it? Vanilla JavaScript can do <em>a lot</em> today.</li>
<li>Is there a lighter alternative (Preact, Svelte, etc.) that gets you 90% of the way there?</li>
<li>If you’re going with a framework, does anything exist that provides better, more opinionated defaults (ex: Nuxt.js instead of Vue.js, Next.js instead of React, etc.)?</li>
<li>What’s your <a href="https://timkadlec.com/remembers/2019-03-07-performance-budgets-that-stick/">performance budget going to be</a> for your JavaScript?</li>
<li>What friction can you introduce into the workflow that makes it harder to <a href="https://timkadlec.com/remembers/2020-03-18-building-with-friction/">add any more JavaScript than absolutely necessary</a>?</li>
<li>If you’re using a framework for the developer ergonomics, do you <a href="https://www.gatsbyjs.org/packages/gatsby-plugin-no-javascript/">need to ship it down to the client</a>, or can you handle that all on the server?</li>
</ul>

<p>These are generally good things to consider regardless of your technology choice, but they’re particularly important if you’re starting with a performance deficit from the beginning.</p>

<p>For example, we could use all sites that didn't have any of the mentioned frameworks (jQuery, React, Vue.js, Angular) detected.</p>

<p> Here are the numbers for the JavaScript main thread work on mobile devices for those URLs:

</p>

<table cellspacing="3" class="plain">
<caption>Scripting related CPU time (in milliseconds) for mobile devices, in percentiles</caption>
<thead>
<tr>
<th/>
<th class="num">10th</th>
<th class="num">25th</th>
<th class="num">50th</th>
<th class="num">75th</th>
<th class="num">90th</th>
</tr>
</thead>
<tbody>
<tr>
<td data-title="Dataset">All Sites</td>
<td data-title="Main thread time, 10th Percentile" class="num">6.3ms</td>
<td data-title="Main thread time, 25th Percentile" class="num">75.3ms</td>
<td data-title="Main thread time, 50th Percentile" class="num">382.2ms</td>
<td data-title="Main thread time, 75th Percentile" class="num">2,316.6ms</td>
<td data-title="Main thread time, 90th Percentile" class="num">5,504.7ms</td>
</tr>
</tbody>
</table>

<p>Going even further, we could use all sites with no JavaScript framework or library detected at all. Here's what the main thread time looks like for those:</p>

<table cellspacing="3" class="plain">
<caption>Scripting related CPU time (in milliseconds) for mobile devices, in percentiles</caption>
<thead>
<tr>
<th/>
<th class="num">10th</th>
<th class="num">25th</th>
<th class="num">50th</th>
<th class="num">75th</th>
<th class="num">90th</th>
</tr>
</thead>
<tbody>
<tr>
<td data-title="Dataset">All Sites</td>
<td data-title="Main thread time, 10th Percentile" class="num">3.6ms</td>
<td data-title="Main thread time, 25th Percentile" class="num">29.9ms</td>
<td data-title="Main thread time, 50th Percentile" class="num">193.2ms</td>
<td data-title="Main thread time, 75th Percentile" class="num">1,399.6ms</td>
<td data-title="Main thread time, 90th Percentile" class="num">3,714.5ms</td>
</tr>
</tbody>
</table>

<p>With either baseline, the data would have looked less favorable and more dramatic. Ultimately, I ended up using the aggregate data as the baseline because:</p>

<ul>
<li>It's what is used broadly in the community whenever this stuff is discussed.</li>
<li>It avoids derailing the conversation with debates about whether or not sites without frameworks are complex enough to be an accurate comparison. You can build large, complex sites without using a framework—I've worked with companies who have—but it's an argument that would distract from the otherwise pretty clear conclusions from the data.</li>
</ul>

<p>Still, as a few folks pointed out to me when I was showing them the data, it's interesting to see these alternate baselines as they do make it very clear how much these tools are messing with the averages.</p>

</div>
</article>


<hr>

<footer>
<p>
<a href="/david/" title="Aller à l’accueil">🏠</a> •
<a href="/david/log/" title="Accès au flux RSS">🤖</a> •
<a href="http://larlet.com" title="Go to my English profile" data-instant>🇨🇦</a> •
<a href="mailto:david%40larlet.fr" title="Envoyer un courriel">📮</a> •
<abbr title="Hébergeur : Alwaysdata, 62 rue Tiquetonne 75002 Paris, +33184162340">🧚</abbr>
</p>
</footer>
<script src="/static/david/js/instantpage-3.0.0.min.js" type="module" defer></script>
</body>
</html>

+ 587
- 0
cache/2020/be8e81e9337d81e7a31a5cc1f4d38435/index.md View File

@@ -0,0 +1,587 @@
title: The Cost of Javascript Frameworks
url: https://timkadlec.com/remembers/2020-04-21-the-cost-of-javascript-frameworks/
hash_url: be8e81e9337d81e7a31a5cc1f4d38435

<p>There is no faster (pun intended) way to slow down a site than to use a bunch of JavaScript.</p>

<p>The thing about JavaScript is you end up paying a performance tax no less than four times:</p>

<ol>
<li>The cost of downloading the file on the network</li>
<li>The cost of parsing and compiling the uncompressed file once downloaded</li>
<li>The cost of executing the JavaScript</li>
<li>The memory cost</li>
</ol>

<p>The <a href="https://v8.dev/blog/cost-of-javascript-2019">combination is very expensive</a>.</p>

<p>And we are shipping an increasingly high amount. We’re making the core functionality of our sites increasingly dependant on JavaScript as organizations move towards sites driven by frameworks like React, Vue.js, and friends.</p>

<p>I see a lot of very heavy sites using them, but then, my perspective is very biased as the companies that I work with work with me precisely <em>because</em> they are facing performance challenges. I was curious just how common the situation is and just <em>how</em> much of a penalty we’re paying when we make these frameworks the default starting point.</p>

<p>Thanks to <a href="https://httparchive.org">HTTP Archive</a>, we can figure that out.</p>

<h2 id="the-data">The data</h2>

<p>In total, HTTP Archive tracks 4,308,655 desktop URLs, and 5,484,239 mobile URLs. Among the many data points HTTP Archive reports for those URLs is a list of the detected technologies for a given site. That means we can pick out the thousands of sites that use various frameworks and see how much code they’re shipping, and what that costs the CPU.</p>

<p>I ran all the queries against March of 2020, the most recent run at the time.</p>

<p>I decided to compare the aggregate HTTP Archive data for all sites recorded against sites with React, Vue.js, and Angular detected.</p>

<p>For fun, I also added jQuery—it’s still massively popular, and it also represents a bit of a different approach to building with JavaScript than the single-page application (<abbr title="Single-Page Application">SPA</abbr>) approach provided by React, Vue.js and Angular.</p>

<table cellspacing="3" class="plain">
<caption>URLs in HTTP Archive with specific frameworks detected</caption>
<thead>
<tr>
<th>Framework</th>
<th class="num">Mobile URLs</th>
<th class="num">Desktop URLs</th>
</tr>
</thead>
<tbody>
<tr>
<td data-title="Framework">jQuery</td>
<td data-title="Mobile URLs" class="num">4,615,474</td>
<td data-title="Desktop URLs" class="num">3,714,643</td>
</tr>
<tr>
<td data-title="Framework">React</td>
<td data-title="Mobile URLs" class="num">489,827</td>
<td data-title="Desktop URLs" class="num">241,023</td>
</tr>
<tr>
<td data-title="Framework">Vue.js</td>
<td data-title="Mobile URLs" class="num">85,649</td>
<td data-title="Desktop URLs" class="num">43,691</td>
</tr>
<tr>
<td data-title="Framework">Angular</td>
<td data-title="Mobile URLs" class="num">19,423</td>
<td data-title="Desktop URLs" class="num">18,088</td>
</tr>
</tbody>
</table>

<h2 id="hopes-and-dreams">Hopes and dreams</h2>

<p>Before we dig in, here’s what I would hope.</p>

<p>In an ideal world, I believe a framework should go beyond developer experience value and provide concrete value for the people using our sites. Performance is just one part of that—accessibility and security both come to mind as well—but it’s an essential part.</p>

<p>So in an ideal world, a framework makes it easier to perform well by either providing a better starting point or providing constraints and characteristics that make it hard to build something that doesn’t perform well.</p>

<p>The best of frameworks would do both: provide a better starting point and help to restrict how out of hands things can get.</p>

<p>Looking at the median for our data isn’t going to tell us that, and <a href="https://timkadlec.com/remembers/2018-06-07-prioritizing-the-long-tail-of-performance/">in fact leaves a ton of information out</a>. Instead, for each stat, I pulled the following percentiles: the 10th, 25th, 50th (the median), 75th, and 90th.</p>

<p>The 10th and 90th percentiles are particularly interesting to me. The 10th percentile represents the best of class (or at least, reasonably close to the best of class) for a given framework. In other words, only 10% of all sites using a given framework reach that mark or better. The 90th percentile, on the other hand, is the opposite of the spectrum—it shows us how bad things can get. The 90th percentile represents the long-tail—that last 10% of sites with the highest number of bytes or largest amount of main thread time.</p>

<h2 id="javascript-bytes">JavaScript Bytes</h2>

<p>For the starting point, it makes sense to look at the amount of JavaScript passed over the network.</p>

<table cellspacing="3" class="plain">
<caption>JavaScript Bytes Served to Mobile Devices, by Percentile</caption>
<thead>
<tr>
<th/>
<th class="num">10th</th>
<th class="num">25th</th>
<th class="num">50th</th>
<th class="num">75th</th>
<th class="num">90th</th>
</tr>
</thead>
<tbody>
<tr>
<td data-title="Dataset">All Sites</td>
<td data-title="Bytes Served, 10th Percentile" class="num">93.4kb</td>
<td data-title="Bytes Served, 25th Percentile" class="num">196.6kb</td>
<td data-title="Bytes Served, 50th Percentile" class="num">413.5kb</td>
<td data-title="Bytes Served, 75th Percentile" class="num">746.8kb</td>
<td data-title="Bytes Served, 90th Percentile" class="num">1,201.6kb</td>
</tr>
<tr>
<td data-title="Dataset">Sites with jQuery</td>
<td data-title="Bytes Served, 10th Percentile" class="num">110.3kb</td>
<td data-title="Bytes Served, 25th Percentile" class="num">219.8kb</td>
<td data-title="Bytes Served, 50th Percentile" class="num">430.4kb</td>
<td data-title="Bytes Served, 75th Percentile" class="num">748.6kb</td>
<td data-title="Bytes Served, 90th Percentile" class="num">1,162.3kb</td>
</tr>
<tr>
<td data-title="Dataset">Sites with Vue.js</td>
<td data-title="Bytes Served, 10th Percentile" class="num">244.7kb</td>
<td data-title="Bytes Served, 25th Percentile" class="num">409.3kb</td>
<td data-title="Bytes Served, 50th Percentile" class="num">692.1kb</td>
<td data-title="Bytes Served, 75th Percentile" class="num">1,065.5kb</td>
<td data-title="Bytes Served, 90th Percentile" class="num">1,570.7kb</td>
</tr>
<tr>
<td data-title="Dataset">Sites with Angular</td>
<td data-title="Bytes Served, 10th Percentile" class="num">445.1kb</td>
<td data-title="Bytes Served, 25th Percentile" class="num">675.6kb</td>
<td data-title="Bytes Served, 50th Percentile" class="num">1,066.4kb</td>
<td data-title="Bytes Served, 75th Percentile" class="num">1,761.5kb</td>
<td data-title="Bytes Served, 90th Percentile" class="num">2,893.2kb</td>
</tr>
<tr>
<td data-title="Dataset">Sites with React</td>
<td data-title="Bytes Served, 10th Percentile" class="num">345.8kb</td>
<td data-title="Bytes Served, 25th Percentile" class="num">441.6kb</td>
<td data-title="Bytes Served, 50th Percentile" class="num">690.3kb</td>
<td data-title="Bytes Served, 75th Percentile" class="num">1,238.5kb</td>
<td data-title="Bytes Served, 90th Percentile" class="num">1,893.6kb</td>
</tr>
</tbody>
</table>

<p><picture class="diagram">
<source media="(min-width: 650px)" srcset="/images/cost-of-frameworks-bytes-mobile.png">
<img src="/images/cost-of-frameworks-bytes-mobile-sm.png" alt="Boxplot charts showing the amount of JavaScript bytes served to mobile devices for sites with various frameworks. Also presented by the preceding table"/>
</source></picture></p>

<table cellspacing="3" class="plain">
<caption>JavaScript Bytes Served to Desktop Devices, by Percentile</caption>
<thead>
<tr>
<th/>
<th class="num">10th</th>
<th class="num">25th</th>
<th class="num">50th</th>
<th class="num">75th</th>
<th class="num">90th</th>
</tr>
</thead>
<tbody>
<tr>
<td data-title="Dataset">All Sites</td>
<td data-title="Bytes Served, 10th Percentile" class="num">105.5kb</td>
<td data-title="Bytes Served, 25th Percentile" class="num">226.6kb</td>
<td data-title="Bytes Served, 50th Percentile" class="num">450.4kb</td>
<td data-title="Bytes Served, 75th Percentile" class="num">808.8kb</td>
<td data-title="Bytes Served, 90th Percentile" class="num">1,267.3kb</td>
</tr>
<tr>
<td data-title="Dataset">Sites with jQuery</td>
<td data-title="Bytes Served, 10th Percentile" class="num">121.7kb</td>
<td data-title="Bytes Served, 25th Percentile" class="num">242.2kb</td>
<td data-title="Bytes Served, 50th Percentile" class="num">458.3kb</td>
<td data-title="Bytes Served, 75th Percentile" class="num">803.4kb</td>
<td data-title="Bytes Served, 90th Percentile" class="num">1,235.3kb</td>
</tr>
<tr>
<td data-title="Dataset">Sites with Vue.js</td>
<td data-title="Bytes Served, 10th Percentile" class="num">248.0kb</td>
<td data-title="Bytes Served, 25th Percentile" class="num">420.1kb</td>
<td data-title="Bytes Served, 50th Percentile" class="num">718.0kb</td>
<td data-title="Bytes Served, 75th Percentile" class="num">1,122.5kb</td>
<td data-title="Bytes Served, 90th Percentile" class="num">1,643.1kb</td>
</tr>
<tr>
<td data-title="Dataset">Sites with Angular</td>
<td data-title="Bytes Served, 10th Percentile" class="num">468.8kb</td>
<td data-title="Bytes Served, 25th Percentile" class="num">716.9kb</td>
<td data-title="Bytes Served, 50th Percentile" class="num">1,144.2kb</td>
<td data-title="Bytes Served, 75th Percentile" class="num">1,930.0kb</td>
<td data-title="Bytes Served, 90th Percentile" class="num">3,283.1kb</td>
</tr>
<tr>
<td data-title="Dataset">Sites with React</td>
<td data-title="Bytes Served, 10th Percentile" class="num">308.6kb</td>
<td data-title="Bytes Served, 25th Percentile" class="num">469.0kb</td>
<td data-title="Bytes Served, 50th Percentile" class="num">841.9kb</td>
<td data-title="Bytes Served, 75th Percentile" class="num">1,472.2kb</td>
<td data-title="Bytes Served, 90th Percentile" class="num">2,197.8kb</td>
</tr>
</tbody>
</table>

<p><picture class="diagram">
<source media="(min-width: 650px)" srcset="/images/cost-of-frameworks-bytes-desktop.png">
<img src="/images/cost-of-frameworks-bytes-desktop-sm.png" alt="Boxplot charts showing the amount of JavaScript bytes served to desktop devices for sites with various frameworks. Also presented by the preceding table"/>
</source></picture></p>

<p>For the sheer payload size, the 10th percentile turns out pretty much as you would expect: if one of these frameworks are in use, there’s more JavaScript even in the most ideal of situations. That’s not surprising—you can’t add a JavaScript framework as a default starting point and expect to ship less JavaScript out of the box.</p>

<p>What <em>is</em> notable is that some frameworks correlate to better starting points than others. Sites with jQuery are the best of the bunch, starting with about 15% more JavaScript on desktop devices and about 18% more on mobile. (There’s admittedly a little bit of bias here. jQuery is found on a lot of sites, so naturally, it’s going to have a tighter relationship to the overall numbers than others. Still, that doesn’t change the way the raw numbers appear for each framework.)</p>

<p>While even a 15-18% increase is notable, comparing that to the opposite end of the spectrum makes the jQuery tax feel very low. Sites with Angular ship 344% more JavaScript on desktop at the 10th percentile, and 377% more on mobile. Sites with React, the next heaviest, ship 193% more JavaScript on desktop and 270% more on mobile devices.</p>

<p>I mentioned earlier that even if the starting point is a little off, I would hope that a framework could still provide value by limiting the upper bound in some way.</p>

<p>Interestingly, jQuery driven sites follow this pattern. While they’re a bit heftier (15-18%) at the 10th percentile, they’re slightly smaller than the aggregate at the 90th percentile—about 3% on both desktop and mobile. Neither of those numbers is super significant, but at least sites with jQuery don’t seem to have a dramatically worse long-tail in terms of JavaScript bytes shipped.</p>

<p>The same can’t be said of the other frameworks.</p>

<p>Just as with the 10th percentile, Angular and React driven sites tend to distance themselves from others at the 90th percentile, and not in a very flattering way.</p>

<p>At the 90th percentile, Angular sites ship 141% more bytes on mobile and 159% more bytes on desktop. Sites with React ship 73% more bytes on desktop and 58% more on mobile. With a 90th percentile weight of 2,197.8kb, React sites ship 322.9kb more bytes of JavaScript to mobile users than Vue.js, the next closest. The desktop gap between Angular and React and the rest of the crowd is even higher—React-driven sites ship 554.7kb more JavaScript than Vue.js-driven sites.</p>

<h2 id="javascript-main-thread-time">JavaScript Main Thread Time</h2>

<p>It’s clear from the data that sites with these frameworks in place tend to pay a large penalty in terms of bytes. But of course, that’s just one part of the equation.</p>

<p>Once that JavaScript arrives, it has to get to work. Any work that occurs on the main thread of the browser is particularly troubling. The main thread is responsible for handling user input, during style calculation, layout and painting. If we’re clogging it up with a lot of JavaScript work, the main thread has no chance to do those things in a timely manner, leading to lag and jank.</p>

<p>HTTP Archive records V8 main thread time, so we can query to see just how much time that main thread is working on all that JavaScript.</p>

<table cellspacing="3" class="plain">
<caption>Scripting related CPU time (in milliseconds) for mobile devices, in percentiles</caption>
<thead>
<tr>
<th/>
<th class="num">10th</th>
<th class="num">25th</th>
<th class="num">50th</th>
<th class="num">75th</th>
<th class="num">90th</th>
</tr>
</thead>
<tbody>
<tr>
<td data-title="Dataset">All Sites</td>
<td data-title="Main thread time, 10th Percentile" class="num">356.4ms</td>
<td data-title="Main thread time, 25th Percentile" class="num">959.7ms</td>
<td data-title="Main thread time, 50th Percentile" class="num">2,372.1ms</td>
<td data-title="Main thread time, 75th Percentile" class="num">5,367.3ms</td>
<td data-title="Main thread time, 90th Percentile" class="num">10,485.8ms</td>
</tr>
<tr>
<td data-title="Dataset">Sites with jQuery</td>
<td data-title="Main thread time, 10th Percentile" class="num">575.3ms</td>
<td data-title="Main thread time, 25th Percentile" class="num">1,147.4ms</td>
<td data-title="Main thread time, 50th Percentile" class="num">2,555.9ms</td>
<td data-title="Main thread time, 75th Percentile" class="num">5,511.0ms</td>
<td data-title="Main thread time, 90th Percentile" class="num">10,349.4ms</td>
</tr>
<tr>
<td data-title="Dataset">Sites with Vue.js</td>
<td data-title="Main thread time" class="num">1,130.0ms</td>
<td data-title="Main thread time" class="num">2,087.9ms</td>
<td data-title="Main thread time" class="num">4,100.4ms</td>
<td data-title="Main thread time" class="num">7,676.1ms</td>
<td data-title="Main thread time" class="num">12,849.4ms</td>
</tr>
<tr>
<td data-title="Dataset">Sites with Angular</td>
<td data-title="Main thread time, 10th Percentile" class="num">1,471.3ms</td>
<td data-title="Main thread time, 25th Percentile" class="num">2,380.1ms</td>
<td data-title="Main thread time, 50th Percentile" class="num">4,118.6ms</td>
<td data-title="Main thread time, 75th Percentile" class="num">7,450.8ms</td>
<td data-title="Main thread time, 90th Percentile" class="num">13,296.4ms</td>
</tr>
<tr>
<td data-title="Dataset">Sites with React</td>
<td data-title="Main thread time, 10th Percentile" class="num">2,700.1ms</td>
<td data-title="Main thread time, 25th Percentile" class="num">5,090.3ms</td>
<td data-title="Main thread time, 50th Percentile" class="num">9,287.6ms</td>
<td data-title="Main thread time, 75th Percentile" class="num">14,509.6ms</td>
<td data-title="Main thread time, 90th Percentile" class="num">20,813.3ms</td>
</tr>
</tbody>
</table>

<p><picture class="diagram">
<source media="(min-width: 650px)" srcset="/images/cost-of-frameworks-cpu-mobile.png">
<img src="/images/cost-of-frameworks-cpu-mobile-sm.png" alt="Boxplot charts showing the amount of JavaScript cpu time for mobile tests for sites with various frameworks. Also presented by the preceding table"/>
</source></picture></p>

<table cellspacing="3" class="plain">
<caption>Scripting related CPU time (in milliseconds) for desktop devices, in percentiles</caption>
<thead>
<tr>
<th/>
<th data-title="Main thread time, 10th Percentile" class="num">10th</th>
<th data-title="Main thread time, 25th Percentile" class="num">25th</th>
<th data-title="Main thread time, 50th Percentile" class="num">50th</th>
<th data-title="Main thread time, 75th Percentile" class="num">75th</th>
<th data-title="Main thread time, 90th Percentile" class="num">90th</th>
</tr>
</thead>
<tbody>
<tr>
<td data-title="Dataset">All Sites</td>
<td data-title="Main thread time" class="num">146.0ms</td>
<td data-title="Main thread time" class="num">351.8ms</td>
<td data-title="Main thread time" class="num">831.0ms</td>
<td data-title="Main thread time" class="num">1,739.8ms</td>
<td data-title="Main thread time" class="num">3,236.8ms</td>
</tr>
<tr>
<td data-title="Dataset">Sites with jQuery</td>
<td data-title="Main thread time, 10th Percentile" class="num">199.6ms</td>
<td data-title="Main thread time, 25th Percentile" class="num">399.2ms</td>
<td data-title="Main thread time, 50th Percentile" class="num">877.5ms</td>
<td data-title="Main thread time, 75th Percentile" class="num">1,779.9ms</td>
<td data-title="Main thread time, 90th Percentile" class="num">3,215.5ms</td>
</tr>
<tr>
<td data-title="Dataset">Sites with Vue.js</td>
<td data-title="Main thread time, 10th Percentile" class="num">350.4ms</td>
<td data-title="Main thread time, 25th Percentile" class="num">650.8ms</td>
<td data-title="Main thread time, 50th Percentile" class="num">1,280.7ms</td>
<td data-title="Main thread time, 75th Percentile" class="num">2,388.5ms</td>
<td data-title="Main thread time, 90th Percentile" class="num">4,010.8ms</td>
</tr>
<tr>
<td data-title="Dataset">Sites with Angular</td>
<td data-title="Main thread time, 10th Percentile" class="num">482.2ms</td>
<td data-title="Main thread time, 25th Percentile" class="num">777.9ms</td>
<td data-title="Main thread time, 50th Percentile" class="num">1,365.5ms</td>
<td data-title="Main thread time, 75th Percentile" class="num">2,400.6ms</td>
<td data-title="Main thread time, 90th Percentile" class="num">4,171.8ms</td>
</tr>
<tr>
<td data-title="Dataset">Sites with React</td>
<td data-title="Main thread time, 10th Percentile" class="num">508.0ms</td>
<td data-title="Main thread time, 25th Percentile" class="num">1,045.6ms</td>
<td data-title="Main thread time, 50th Percentile" class="num">2,121.1ms</td>
<td data-title="Main thread time, 75th Percentile" class="num">4,235.1ms</td>
<td data-title="Main thread time, 90th Percentile" class="num">7,444.3ms</td>
</tr>
</tbody>
</table>

<p><picture class="diagram">
<source media="(min-width: 650px)" srcset="/images/cost-of-frameworks-cpu-desktop.png">
<img src="/images/cost-of-frameworks-cpu-desktop-sm.png" alt="Boxplot charts showing the amount of JavaScript cpu time for desktop tests for sites with various frameworks. Also presented by the preceding table"/>
</source></picture></p>

<p>There are some very familiar themes here.</p>

<p>First, sites with jQuery detected spend <em>much</em> less time on JavaScript work on the main thread than the other three analyzed. At the 10th percentile, there’s a 61% increase in JavaScript main thread work being done on mobile devices and 37% more on desktop. At the 90th percentile, jQuery sites are gain pretty darn close to the aggregate, spending 1.3% <em>less</em> time on the main thread for mobile devices and ..7% less time on desktop machines.</p>

<p>The opposite end—the frameworks that correlate to the most time spent on the main thread—is once again made up of Angular and React. The only difference is that while Angular sites shipped more JavaScript than React sites, they actually spend less time on the CPU—<em>much</em> less time.</p>

<p>At the 10th percentile, Angular sites spend 230% more time on the CPU for JavaScript related work on desktop devices, and 313% more on mobile devices. React sites bring up the tail end, spending 248% more time on desktop devices and 658% more time on mobile devices. No, 658% is not a typo. At the 10th percentile, sites with React spend 2.7s on the main thread dealing with all the JavaScript sent down.</p>

<p>Compared to those big numbers, the situation at the 90th percentile at least looks a little better. The main thread of Angular sites spends 29% more time on JavaScript for desktop devices and 27% more time on mobile devices. React sites spend 130% more time on desktop and 98% more time on mobile devices.</p>

<p>Those percentages look much better than at the 10th percentile, but keep in mind that the bulk numbers are pretty scary: that’s 20.8s of main thread work for sites built with React at the 90th percentile on mobile devices. (What, exactly, is happening during that time is a topic for a follow-up post, I think.)</p>

<p>There’s one potential gotcha (thanks <a href="https://jeremy.codes/">Jeremy</a> for making sure I double-checked the stats from this angle)—many sites will pull in multiple libraries. In particular, I see a lot of sites pulling jQuery in alongside React or Vue.js as they’re migrating to that architecture. So, I re-ran the queries, only this time I only included URLs that included only React, jQuery, Angular or Vue.js <em>not some combination of them</em>.</p>

<table cellspacing="3" class="plain">
<caption>Scripting related CPU time (in milliseconds) for mobile devices where only one of the frameworks is detected, in percentiles</caption>
<thead>
<tr>
<th/>
<th class="num">10th</th>
<th class="num">25th</th>
<th class="num">50th</th>
<th class="num">75th</th>
<th class="num">90th</th>
</tr>
</thead>
<tbody>
<tr>
<td data-title="Dataset">Sites with only jQuery</td>
<td data-title="Main thread time, 10th Percentile" class="num">542.9ms</td>
<td data-title="Main thread time, 25th Percentile" class="num">1,062.2ms</td>
<td data-title="Main thread time, 50th Percentile" class="num">2,297.4ms</td>
<td data-title="Main thread time, 75th Percentile" class="num">4,769.7ms</td>
<td data-title="Main thread time, 90th Percentile" class="num">8,718.2ms</td>
</tr>
<tr>
<td data-title="Dataset">Sites with only Vue.js</td>
<td data-title="Main thread time, 10th Percentile" class="num">944.0ms</td>
<td data-title="Main thread time, 25th Percentile" class="num">1,716.3ms</td>
<td data-title="Main thread time, 50th Percentile" class="num">3,194.7ms</td>
<td data-title="Main thread time, 75th Percentile" class="num">5,959.6ms</td>
<td data-title="Main thread time, 90th Percentile" class="num">9,843.8ms</td>
</tr>
<tr>
<td data-title="Dataset">Sites with Angular</td>
<td data-title="Main thread time, 10th Percentile" class="num">1,328.9ms</td>
<td data-title="Main thread time, 25th Percentile" class="num">2,151.9ms</td>
<td data-title="Main thread time, 50th Percentile" class="num">3,695.3ms</td>
<td data-title="Main thread time, 75th Percentile" class="num">6,629.3ms</td>
<td data-title="Main thread time, 90th Percentile" class="num">11,607.7ms</td>
</tr>
<tr>
<td data-title="Dataset">Sites with React</td>
<td data-title="Main thread time, 10th Percentile" class="num">2,443.2ms</td>
<td data-title="Main thread time, 25th Percentile" class="num">4,620.5ms</td>
<td data-title="Main thread time, 50th Percentile" class="num">10,061.4ms</td>
<td data-title="Main thread time, 75th Percentile" class="num">17,074.3ms</td>
<td data-title="Main thread time, 90th Percentile" class="num">24,956.3ms</td>
</tr>
</tbody>
</table>

<p><picture class="diagram">
<source media="(min-width: 650px)" srcset="/images/cost-of-frameworks-cpu-mobile-only.png">
<img src="/images/cost-of-frameworks-cpu-mobile-only-sm.png" alt="Boxplot charts showing the amount of JavaScript cpu time for mobiles tests for sites with only one framework detected. Also presented by the preceding table"/>
</source></picture></p>

<p>First, the unsurprising bit: when only one framework is used, performance improves far more often than not. The numbers for every framework look better at the 10th and 25th percentile. That makes sense. A site that is built well with one framework <em>should</em> perform better than a site that is built well with two or more.</p>

<p>In fact, the numbers for every framework look better at each percentile with one curious exception.
What surprised me, and the reason I ended up including this data, is that at the 50th percentile and beyond, sites using React perform <em>worse</em> when React is the only framework in use.</p>

<p>It’s a bit odd, but here’s my best guess.</p>

<p>If you have React and jQuery running alongside each other, you’re more likely to be in the midst of a migration to React, or a mixed codebase. Since we have already seen that sites with jQuery spend less time on the main thread than sites with React, it makes sense that having some functionality still driven by jQuery would bring the numbers down a bit.</p>

<p>As you move away from jQuery and focus more on React exclusively, though, that changes. If the site is built really well and you’re using React sparingly, you’re fine. But for the average site, more work inside of React means the main thread receives an increasing amount of strain.</p>

<h2 id="the-mobile-desktop-gap">The mobile/desktop gap</h2>

<p>Another angle that’s worth looking at is just how large the gap is between that mobile experience and the desktop experience. Looking at it from a bytes perspective, nothing too scary jumps out. Sure, I’d love to see fewer bytes passed along, but neither mobile nor desktop devices receive significantly more bytes than the other.</p>

<p>But once you look at the processing time, the gap is significant.</p>

<table cellspacing="3" class="plain">
<caption>Percentage increase in main thread scripting work on mobile devices compared to desktop devices, by percentiles</caption>
<thead>
<tr>
<th/>
<th class="num">10th</th>
<th class="num">25th</th>
<th class="num">50th</th>
<th class="num">75th</th>
<th class="num">90th</th>
</tr>
</thead>
<tbody>
<tr>
<td data-title="Dataset">All Sites</td>
<td data-title="Change from Desktop to Mobile, 10th percentile" class="num">144.1%</td>
<td data-title="Change from Desktop to Mobile, 25th percentile" class="num">172.8%</td>
<td data-title="Change from Desktop to Mobile, 50th percentile" class="num">185.5%</td>
<td data-title="Change from Desktop to Mobile, 75th percentile" class="num">208.5%</td>
<td data-title="Change from Desktop to Mobile, 90th percentile" class="num">224.0%</td>
</tr>
<tr>
<td data-title="Dataset">Sites with jQuery</td>
<td data-title="Change from Desktop to Mobile, 10th percentile" class="num">188.2%</td>
<td data-title="Change from Desktop to Mobile, 25th percentile" class="num">187.4%</td>
<td data-title="Change from Desktop to Mobile, 50th percentile" class="num">191.3%</td>
<td data-title="Change from Desktop to Mobile, 75th percentile" class="num">209.6%</td>
<td data-title="Change from Desktop to Mobile, 90th percentile" class="num">221.9%</td>
</tr>
<tr>
<td data-title="Dataset">Sites with Vue.js</td>
<td data-title="Change from Desktop to Mobile, 10th percentile" class="num">222.5%</td>
<td data-title="Change from Desktop to Mobile, 25th percentile" class="num">220.8%</td>
<td data-title="Change from Desktop to Mobile, 50th percentile" class="num">220.2%</td>
<td data-title="Change from Desktop to Mobile, 75th percentile" class="num">221.4%</td>
<td data-title="Change from Desktop to Mobile, 90th percentile" class="num">220.4%</td>
</tr>
<tr>
<td data-title="Dataset">Sites with Angular</td>
<td data-title="Change from Desktop to Mobile, 10th percentile" class="num">205.1%</td>
<td data-title="Change from Desktop to Mobile, 25th percentile" class="num">206.0%</td>
<td data-title="Change from Desktop to Mobile, 50th percentile" class="num">201.6%</td>
<td data-title="Change from Desktop to Mobile, 75th percentile" class="num">210.4%</td>
<td data-title="Change from Desktop to Mobile, 90th percentile" class="num">218.7%</td>
</tr>
<tr>
<td data-title="Dataset">Sites with React</td>
<td data-title="Change from Desktop to Mobile, 10th percentile" class="num">431.5%</td>
<td data-title="Change from Desktop to Mobile, 25th percentile" class="num">386.8%</td>
<td data-title="Change from Desktop to Mobile, 50th percentile" class="num">337.9%</td>
<td data-title="Change from Desktop to Mobile, 75th percentile" class="num">242.6%</td>
<td data-title="Change from Desktop to Mobile, 90th percentile" class="num">179.6%</td>
</tr>
</tbody>
</table>

<p>While some variance is expected between a phone and a laptop, seeing numbers this high tells me that the current crop of frameworks isn’t doing enough to prioritize less powerful devices and help to close that gap. Even at the 10th percentile, React sites spend 431.5% more time on the main thread on mobile devices as they do on desktop devices. jQuery has the lowest gap of all frameworks, but even there, it equates to 188.2% more time on mobile devices. When we make the CPU work harder—and increasingly we are—folks with less powerful devices end up holding the bill.</p>

<h2 id="the-big-picture">The big picture</h2>

<p>Good frameworks should provide a better starting point on the essentials (security, accessibility, performance) or have built-in constraints that make it harder to ship something that violates those.</p>

<p>That doesn’t appear to be happening with performance (<a href="https://webaim.org/projects/million/#frameworks">nor with accessibility</a>, apparently).</p>

<p>It’s worth noting that because sites with React or Angular spend more time on the CPU than others, that isn’t necessarily the same as saying React is more expensive on the CPU than Vue.js. In fact, it says very little about the performance of the core frameworks in play and much more about the approach to development these frameworks may encourage (whether intentionally or not) through documentation, ecosystem, and general coding practices.</p>

<p>It’s also worth noting what we don’t have here: data about how much time the device spends on this JavaScript for subsequent views. The argument for the <abbr title="Single-Page Application">SPA</abbr> architecture is that, once the <abbr title="Single-Page Application">SPA</abbr> is in place, you theoretically get faster subsequent page loads. My own experience tells me that’s far from a given, but we don’t have concrete data here to make that case in either direction.</p>

<p>What is clear: right now, if you’re using a framework to build your site, you’re making a trade-off in terms of initial performance—even in the best of scenarios.</p>

<p><em>Some</em> trade-off may be acceptable in the right situations, but it’s important that we make that exchange consciously.</p>

<p>There’s reason for optimism. I’m encouraged by how closely the folks at Chrome have been working with some of these frameworks to help improve their performance.</p>

<p>But I’m also pragmatic. New architectures tend to create performance problems just as often as they solve them, and it takes time to right the ship. Just as <a href="https://timkadlec.com/remembers/2019-04-18-new-network-fallacies/">we shouldn’t expect new networks to solve all our performance woes</a>, we shouldn’t expect that the next version of our favorite framework is going to solve them all either.</p>

<p>If you are going to use one of these frameworks, then you have to take extra steps to make sure you don’t negatively impact performance in the meantime. Here are a few great starting considerations:</p>

<ul>
<li>Do a sanity check: do you <em>really</em> need to use it? Vanilla JavaScript can do <em>a lot</em> today.</li>
<li>Is there a lighter alternative (Preact, Svelte, etc.) that gets you 90% of the way there?</li>
<li>If you’re going with a framework, does anything exist that provides better, more opinionated defaults (ex: Nuxt.js instead of Vue.js, Next.js instead of React, etc.)?</li>
<li>What’s your <a href="https://timkadlec.com/remembers/2019-03-07-performance-budgets-that-stick/">performance budget going to be</a> for your JavaScript?</li>
<li>What friction can you introduce into the workflow that makes it harder to <a href="https://timkadlec.com/remembers/2020-03-18-building-with-friction/">add any more JavaScript than absolutely necessary</a>?</li>
<li>If you’re using a framework for the developer ergonomics, do you <a href="https://www.gatsbyjs.org/packages/gatsby-plugin-no-javascript/">need to ship it down to the client</a>, or can you handle that all on the server?</li>
</ul>

<p>These are generally good things to consider regardless of your technology choice, but they’re particularly important if you’re starting with a performance deficit from the beginning.</p>


<p>For example, we could use all sites that didn't have any of the mentioned frameworks (jQuery, React, Vue.js, Angular) detected.</p><p> Here are the numbers for the JavaScript main thread work on mobile devices for those URLs:

</p><table cellspacing="3" class="plain">
<caption>Scripting related CPU time (in milliseconds) for mobile devices, in percentiles</caption>
<thead>
<tr>
<th/>
<th class="num">10th</th>
<th class="num">25th</th>
<th class="num">50th</th>
<th class="num">75th</th>
<th class="num">90th</th>
</tr>
</thead>
<tbody>
<tr>
<td data-title="Dataset">All Sites</td>
<td data-title="Main thread time, 10th Percentile" class="num">6.3ms</td>
<td data-title="Main thread time, 25th Percentile" class="num">75.3ms</td>
<td data-title="Main thread time, 50th Percentile" class="num">382.2ms</td>
<td data-title="Main thread time, 75th Percentile" class="num">2,316.6ms</td>
<td data-title="Main thread time, 90th Percentile" class="num">5,504.7ms</td>
</tr>
</tbody>
</table>

<p>Going even further, we could use all sites with no JavaScript framework or library detected at all. Here's what the main thread time looks like for those:</p>

<table cellspacing="3" class="plain">
<caption>Scripting related CPU time (in milliseconds) for mobile devices, in percentiles</caption>
<thead>
<tr>
<th/>
<th class="num">10th</th>
<th class="num">25th</th>
<th class="num">50th</th>
<th class="num">75th</th>
<th class="num">90th</th>
</tr>
</thead>
<tbody>
<tr>
<td data-title="Dataset">All Sites</td>
<td data-title="Main thread time, 10th Percentile" class="num">3.6ms</td>
<td data-title="Main thread time, 25th Percentile" class="num">29.9ms</td>
<td data-title="Main thread time, 50th Percentile" class="num">193.2ms</td>
<td data-title="Main thread time, 75th Percentile" class="num">1,399.6ms</td>
<td data-title="Main thread time, 90th Percentile" class="num">3,714.5ms</td>
</tr>
</tbody>
</table>

<p>With either baseline, the data would have looked less favorable and more dramatic. Ultimately, I ended up using the aggregate data as the baseline because:</p>
<ul>
<li>It's what is used broadly in the community whenever this stuff is discussed.</li>
<li>It avoids derailing the conversation with debates about whether or not sites without frameworks are complex enough to be an accurate comparison. You can build large, complex sites without using a framework—I've worked with companies who have—but it's an argument that would distract from the otherwise pretty clear conclusions from the data.</li>
</ul>

<p>Still, as a few folks pointed out to me when I was showing them the data, it's interesting to see these alternate baselines as they do make it very clear how much these tools are messing with the averages.</p>

</div>

+ 120
- 0
cache/2020/ebaa216561b046ae17b29b399305b294/index.html View File

@@ -0,0 +1,120 @@
<!doctype html><!-- This is a valid HTML5 document. -->
<!-- Screen readers, SEO, extensions and so on. -->
<html lang="fr">
<!-- Has to be within the first 1024 bytes, hence before the <title>
See: https://www.w3.org/TR/2012/CR-html5-20121217/document-metadata.html#charset -->
<meta charset="utf-8">
<!-- Why no `X-UA-Compatible` meta: https://stackoverflow.com/a/6771584 -->
<!-- The viewport meta is quite crowded and we are responsible for that.
See: https://codepen.io/tigt/post/meta-viewport-for-2015 -->
<meta name="viewport" content="width=device-width,initial-scale=1">
<!-- Required to make a valid HTML5 document. -->
<title>Second-guessing the modern web (archive) — David Larlet</title>
<!-- Generated from https://realfavicongenerator.net/ such a mess. -->
<link rel="apple-touch-icon" sizes="180x180" href="/static/david/icons2/apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="32x32" href="/static/david/icons2/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="/static/david/icons2/favicon-16x16.png">
<link rel="manifest" href="/static/david/icons2/site.webmanifest">
<link rel="mask-icon" href="/static/david/icons2/safari-pinned-tab.svg" color="#07486c">
<link rel="shortcut icon" href="/static/david/icons2/favicon.ico">
<meta name="msapplication-TileColor" content="#f0f0ea">
<meta name="msapplication-config" content="/static/david/icons2/browserconfig.xml">
<meta name="theme-color" content="#f0f0ea">
<!-- Documented, feel free to shoot an email. -->
<link rel="stylesheet" href="/static/david/css/style_2020-04-25.css">
<!-- See https://www.zachleat.com/web/comprehensive-webfonts/ for the trade-off. -->
<link rel="preload" href="/static/david/css/fonts/triplicate_t4_poly_regular.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: light), (prefers-color-scheme: no-preference)" crossorigin>
<link rel="preload" href="/static/david/css/fonts/triplicate_t4_poly_bold.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: light), (prefers-color-scheme: no-preference)" crossorigin>
<link rel="preload" href="/static/david/css/fonts/triplicate_t4_poly_italic.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: light), (prefers-color-scheme: no-preference)" crossorigin>
<link rel="preload" href="/static/david/css/fonts/triplicate_t3_regular.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: dark)" crossorigin>
<link rel="preload" href="/static/david/css/fonts/triplicate_t3_bold.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: dark)" crossorigin>
<link rel="preload" href="/static/david/css/fonts/triplicate_t3_italic.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: dark)" crossorigin>

<meta name="robots" content="noindex, nofollow">
<meta content="origin-when-cross-origin" name="referrer">
<!-- Canonical URL for SEO purposes -->
<link rel="canonical" href="https://macwright.org/2020/05/10/spa-fatigue.html">

<body class="remarkdown h1-underline h2-underline h3-underline hr-center ul-star pre-tick">

<article>
<h1>Second-guessing the modern web</h1>
<h2><a href="https://macwright.org/2020/05/10/spa-fatigue.html">Source originale du contenu</a></h2>
<p>The emerging norm for web development is to build a React single-page application, with server rendering. The two key elements of this architecture are something like:</p>

<ol><li>The main UI is built &amp; updated in JavaScript using React or something similar.</li><li>The backend is an API that that application makes requests against.</li></ol>

<p>This idea has really swept the internet. It started with a few major popular websites and has crept into corners like marketing sites and blogs.</p>

<p>I’m increasingly skeptical of it.</p>

<p>There is a sweet spot of React: in moderately interactive interfaces. Complex forms that require immediate feedback, UIs that need to move around and react instantly. That’s where it excels. I helped build the editors in <a href="https://www.mapbox.com/mapbox-studio/">Mapbox Studio</a> and <a href="https://observablehq.com/">Observable</a> and for the most part, React was a great choice.</p>

<p>But there’s a lot on either side of that sweet spot.</p>

<p>The high performance parts aren’t React. <a href="https://docs.mapbox.com/mapbox-gl-js/api/">Mapbox GL</a>, for example, is vanilla JavaScript and probably should be forever. The level of abstraction that React works on is too high, and the cost of using React - in payload, parse time, and so on - is too much for any company to include it as part of an SDK. Same with the <a href="https://github.com/observablehq/runtime">Observable runtime</a>, the juicy center of that product: it’s very performance-intensive and would barely benefit from a port.</p>

<p>The less interactive parts don’t benefit much from React. Listing pages, static pages, blogs - these things are increasingly built in React, but the benefits they accrue are extremely narrow. A lot of the optimizations we’re deploying to speed up these things, things like bundle splitting, server-side rendering, and prerendering, are triangulating what we had before the rise of React.</p>

<p>And they’re kind of messy optimizations. Here are some examples.</p>

<h3 id="bundle-splitting">Bundle splitting.</h3>

<p>As your React application grows, the application bundle grows. Unlike with a traditional multi-page app, that growth affects <em>every visitor</em>: you download the whole app the first time that you visit it. At some point, this becomes a real problem. Someone who lands on the About page is also downloading 20 other pages in the same application bundle. Bundle splitting ‘solves’ this problem by creating many JavaScript bundles that can lazily load each other. So you load the About page and what your browser downloads is an ‘index’ bundle, and then that ‘index’ bundle loads the ‘about page’ bundle.</p>

<p>This <em>sort of</em> solves the problem, but it’s not great. Most bundle splitting techniques require you to load that ‘index bundle’, and then only once that JavaScript is loaded and executed does your browser know which ‘page bundle’ it needs. So you need two round-trips to start rendering.</p>

<p>And then there’s the question of updating code-split bundles. User sessions are surprisingly long: someone might have your website open in a tab for weeks at a time. I’ve seen it happen. So if they open the ‘about page’, keep the tab open for a week, and then request the ‘home page’, then the home page that they request is dictated by <em>the index bundle that they downloaded last week</em>. This is a deeply weird and under-discussed situation. There are essentially two solutions to it:</p>

<ol><li>You keep all generated JavaScript around, forever, and people will see the version of the site that was live at the time of their first page request.</li><li>You create a system that alerts users when you’ve deployed a new version of the site, and prompt them to reload.</li></ol>

<p>The first solution has a drawback that might not be immediately obvious. In those intervening weeks between loading the site and clicking a link, you might’ve deployed a new API version. So the user will be using an old version of your JavaScript frontend with a new version of your API backend, and they’ll trigger errors that none of your testing knows about, because you’ll usually be testing current versions of each.</p>

<p>And the second solution, while it works (and is what we implemented for Mapbox Studio), is a bizarre way for a web application to behave. Prompting users to ‘update’ is something from the bad old days of desktop software, not from the shiny new days of the web.</p>

<p>Sure: traditional non-SPA websites are not immune to this pitfall. Someone might load your website, have a <em>form</em> open for many weeks, and then submit it after their session expired or the API changed. But that’s a much more limited exposure to failure than in the SPA case.</p>

<h3 id="server-side-rendering">Server-Side Rendering</h3>

<p>Okay, so the theory here is that SPAs are initially a blank page, which is then filled out by React &amp; JavaScript. That’s bad for performance: HTML pages don’t <em>need</em> to be blank initially. So, Server-Side Rendering runs your JavaScript frontend code on the backend, creating a filled-out HTML page. The user loads the page, which now has pre-rendered content, and then the JavaScript loads and makes the page interactive.</p>

<p>A great optimization, but again, caveats.</p>

<p>The first is that the page you initially render is dead: you’ve created the <a href="https://web.dev/interactive/">Time To Interactive</a> metric. It’s your startup’s homepage, and it has a “Sign up” button, but until the JavaScript loads, that button doesn’t do anything. So you need to compensate. Either you omit some interactive elements on load, or you try really hard to make sure that the JavaScript loads faster than users will click, or you make some elements not require JavaScript to work - like making them normal links or forms. Or some combination of those.</p>

<p>And then there’s the authentication story. If you do SSR on any pages that are custom to the user, then you need to forward any cookies or authentication-relevant information to your API backend and make sure that you never cache the server-rendered result. Your formerly-lightweight application server is now doing quite a bit of labor, running React &amp; making API requests in order to do this pre-rendering.</p>

<h3 id="apis">APIs</h3>

<p>The dream of APIs is that you have generic, flexible endpoints upon which you can build any web application. That idea breaks down pretty fast.</p>

<p>Most interactive web applications start to triangulate on “one query per page.” API calls being generic or reusable never seems to persist as a value in infrastructure. This is because a large portion of web applications are, at their core, query &amp; transformation interfaces on top of databases. The hardest performance problems they tend to have are query problems and transfer problems.</p>

<p>For example: a generically-designed REST API that tries not to mix ‘concerns’ will produce a frontend application that has to make lots of requests to display a page. And then a new-age GraphQL application will suffer under the <a href="https://engineering.shopify.com/blogs/engineering/solving-the-n-1-problem-for-graphql-through-batching">N+1 query problem</a> at the database level until an optimization arrives. And a traditional “make a query and put it on a page” application will just, well, try to write some good queries.</p>

<p>None of these solutions are silver bullets: I’ve worked with overly-strict REST APIs, optimization-hungry GraphQL APIs, and hand-crafted SQL APIs. But no option really lets a web app be careless about its data-fetching layer. Web applications can’t sit on top of independently-designed APIs: to have a chance at performance, the application and its datasource need to be designed as one.</p>

<h3 id="data-fetching">Data fetching</h3>

<p>Speaking of data fetching. It’s really important and really bizarre in React land. Years ago, I expected that some good patterns would emerge. Frankly, they didn’t.</p>

<p>There are decent patterns in the form of GraphQL, but for a React component that loads data with fetch from an API, the solutions have only gotten weirder. There’s great documentation for everything else, but old-fashioned data loading is relegated to one example of how to mock out ‘fetch’ for testing, and lots of Medium posts of varying quality.</p>

<p><hr/><p>Don’t read this as anti-React. I still think React is pretty great, and for a particular scope of use cases it’s the best tool you can find. And I explicitly want to say that – from what I’ve seen – most other Single-Page-Application tools share most of these problems. They’re issues with the pattern, not the specific frameworks used to implement it. React alternatives have some great ideas, and they might be better, but they are ultimately really similar.</p><p>But I’m at the point where I look at where the field is and what the alternative patterns are – taking a second look at unloved, unpopular, uncool things like Django, Rails, Laravel – and think <em>what the heck is happening</em>. We’re layering optimizations upon optimizations in order to get the SPA-like pattern to fit every use case, and I’m not sure that it is, well, worth it.</p><p><strong>And it should be easy to do a good job.</strong></p><p>Frameworks should lure people into the <a href="https://blog.codinghorror.com/falling-into-the-pit-of-success/">pit of success</a>, where following the normal rules and using normal techniques is the winning approach.</p><p>I don’t think that React, in this context, really is that pit of success. A naïvely implemented React SPA isn’t stable, or efficient, and it doesn’t naturally scale to significant complexity</p><p>You can add optimizations on top of it that fix those problems, or you can use a framework like Next.js that will include those optimizations by default. That’ll help you get pretty far. But then you’ll be lured by all of the easy one-click ways to add bloat and complexity. You’ll be responsible for keeping some of these complex, finicky optimizations working properly.</p><p>And for what? Again - there is a swath of use cases which would be hard without React and which aren’t complicated enough to push beyond React’s limits. But there are also a <em>lot</em> of problems for which I can’t see any concrete benefit to using React. Those are things like blogs, shopping-cart-websites, mostly-<a href="https://en.wikipedia.org/wiki/Create,_read,_update_and_delete">CRUD</a>-and-forms-websites. For these things, all of the fancy optimizations are trying to get you closer to <em>the performance you would’ve gotten if you just hadn’t used so much technology</em>.</p><p>I can, for example, guarantee that this blog is faster than <em>any Gatsby blog</em> (and much love to the Gatsby team) because there is nothing that a React static site can do that will make it faster than a non-React static site.</p><hr/><p>But the cultural tides are <em>strong</em>. Building a company on Django in 2020 seems like the equivalent of driving a PT Cruiser and blasting Faith Hill’s “Breathe” on a CD while your friends are listening to The Weeknd in their Teslas. Swimming against this current isn’t easy, and not in a trendy contrarian way.</p><p>I don’t think that everyone’s using the SPA pattern for no reason. For large corporations, it allows teams to work independently: the “frontend engineers” can “consume” “APIs” from teams that probably work in a different language and can only communicate through the hierarchy. For heavily interactive applications, it has real benefits in modularity, performance, and structure. And it’s beneficial for companies to shift computing requirements from their servers to their customers browsers: a real win for reducing their spend on infrastructure.</p><p>But I think there are a lot of problems that are better solved some other way. There’s no category winner like React as an alternative. Ironically, backends are churning through technology even faster than frontends, which have been loyal to one programming language for decades. There are some age-old technologies like Rails, Django, and Laravel, and there are a few halfhearted attempts to do templating and “serve web pages” from Go, Node, and other new languages. If you go this way, you’re beset by the cognitive dissonance of following in the footsteps of enormous projects - Wikipedia rendering web pages in PHP, Craigslist rendering webpages in Perl - but being far outside the norms of <em>modern web development</em>. If Wikipedia were started today, it’d be React. Maybe?</p><p>What if everyone’s wrong? We’ve been wrong before.</p></p>
</article>


<hr>

<footer>
<p>
<a href="/david/" title="Aller à l’accueil">🏠</a> •
<a href="/david/log/" title="Accès au flux RSS">🤖</a> •
<a href="http://larlet.com" title="Go to my English profile" data-instant>🇨🇦</a> •
<a href="mailto:david%40larlet.fr" title="Envoyer un courriel">📮</a> •
<abbr title="Hébergeur : Alwaysdata, 62 rue Tiquetonne 75002 Paris, +33184162340">🧚</abbr>
</p>
</footer>
<script src="/static/david/js/instantpage-3.0.0.min.js" type="module" defer></script>
</body>
</html>

+ 5
- 0
cache/2020/ebaa216561b046ae17b29b399305b294/index.md
File diff suppressed because it is too large
View File


+ 4
- 0
cache/2020/index.html View File

@@ -66,6 +66,8 @@
<li><a href="/david/cache/2020/3146f1a5743de3217adc3bc854897aaf/" title="Accès à l'article caché">Google: Oops, we may have sent your private Google Photos videos to strangers</a> (<a href="https://mashable.com/article/google-photos-videos-glitch/" title="Accès à l'article original">original</a>)</li>
<li><a href="/david/cache/2020/be8e81e9337d81e7a31a5cc1f4d38435/" title="Accès à l'article caché">The Cost of Javascript Frameworks</a> (<a href="https://timkadlec.com/remembers/2020-04-21-the-cost-of-javascript-frameworks/" title="Accès à l'article original">original</a>)</li>
<li><a href="/david/cache/2020/f45cdbe527b4c4acead78cef9e4d533f/" title="Accès à l'article caché">You Are Now Remotely Controlled</a> (<a href="https://www.nytimes.com/2020/01/24/opinion/sunday/surveillance-capitalism.html" title="Accès à l'article original">original</a>)</li>
<li><a href="/david/cache/2020/7f74e315811927454830814bcb659896/" title="Accès à l'article caché">Minimum</a> (<a href="https://journal.loupbrun.ca/n/021/" title="Accès à l'article original">original</a>)</li>
@@ -150,6 +152,8 @@
<li><a href="/david/cache/2020/fd776407232cd6fd7627bac7dba39755/" title="Accès à l'article caché">Épuiser la pratique</a> (<a href="https://www.quaternum.net/2020/02/29/epuiser-la-pratique/" title="Accès à l'article original">original</a>)</li>
<li><a href="/david/cache/2020/ebaa216561b046ae17b29b399305b294/" title="Accès à l'article caché">Second-guessing the modern web</a> (<a href="https://macwright.org/2020/05/10/spa-fatigue.html" title="Accès à l'article original">original</a>)</li>
<li><a href="/david/cache/2020/4c5cc5e59531ef04e068c883a1a0e166/" title="Accès à l'article caché">Running a Paid Membership Program</a> (<a href="https://craigmod.com/essays/membership_programs/" title="Accès à l'article original">original</a>)</li>
<li><a href="/david/cache/2020/bfce8545a2d7c8d51d3af19f61208134/" title="Accès à l'article caché">On Pair Programming</a> (<a href="https://martinfowler.com/articles/on-pair-programming.html" title="Accès à l'article original">original</a>)</li>

Loading…
Cancel
Save