I have this page that is a Django server-side rendered page that has on it a form that looks something like this:
<div id="root"> <form action="https://songsear.ch/q/"> <input type="search" name="term" placeholder="Type your search here..." /> <button>Search</button> </form> </div>
It's a simple search form. But, to make it a bit better for users, I wrote a React widget that renders, into this
document.querySelector('#root'), a near-identical
<form> but with autocomplete functionality that displays suggestions as you type.
Anyway, I built that React bundle using
create-react-app. I use the
yarn run build command that generates...
js/main.ec6364ab.chunk.js- 9.0K (gzip 2.8K, br 2.5K)
js/runtime~main.a8a9905a.js- 1.5K (gzip 754B, br 688B)
js/2.b944397d.chunk.js- 119K (gzip 36K, br 33K)
Then, in Python, a piece of post-processing code copies the files from the
build/static/ directory and inserts it into the rendered HTML file. The CSS gets injected as an inline
It's a simple little widget. No need for any service-workers or
react-router or any global state stuff. (Actually, it only has 1 single runtime dependency outside the framework) I thought, how about moving this to Preact?
The app used a couple of React hooks but they were easy to transform into class components. Now I just needed to run:
npx preact create --yarn widget name-of-my-preact-project cd name-of-my-preact-project mkdir src cp ../name-of-React-project/src/App.js src/ code src/App.js
Then, I slowly moved over the
src/App.js from the
create-react-app project and slowly by slowly I did the various little things that you need to do. For example, to learn to build with
preact build --no-prerender --no-service-worker and how I can override the default template.
Long story short, the new built bundles look like this:
bundle.d91f9.js- 18K (gzip 6.4K, br 5.9K)
polyfills.9168d.js- 4.5K (gzip 1.8K, br 1.6K)
polyfills.9168d.js gets injected as a script tag if
window.fetch is falsy)
Unfortunately, when I did the move from React to Preact I did make some small fixes. Doing the "migration" I noticed a block of code that was never used so that gives the build bundle from Preact a slight advantage. But I think it's nominal.
Rendering speed difference
Rendering speed is so darn hard to measure on the web because the app is so small. Plus, there's so much else going on that matters.
However, using WebPageTest I can do a visual comparison with the "Mobile - Slow 3G" preset. It'll be a somewhat decent measurement of the total time of downloading, parsing and executing. Thing is, the server-side rended HTML form has a button. But the React/Preact widget that takes over the DOM hides that submit button. So, using the screenshots that WebPageTest provides, I can deduce that the Preact widget completes 0.8 seconds faster than the React widget. (I.e. instead of 4.4s it became 3.9s)
Truth be told, I'm not sure how predictable or reproducible is. I ran that WebPageTest visual comparison more than once and the results can vary significantly. I'm not even sure which run I'm referring to here (in the screenshot) but the React widget version was never faster.
Conclusion and thoughts
Unsurprisingly, Preact is smaller because you simply get less from that framework. E.g. synthetic events. I was lucky. My app uses
onChange which I could easily "migrate" to
onInput and I managed to get it to work pretty easily. I'm glad the widget app was so small and that I don't depend on any React specific third-party dependencies.
But! In WebPageTest Visual Comparison it was on "Mobile - Slow 3G" which only represents a small portion of the traffic. Mobile is a huge portion of the traffic but "Slow 3G" is not. When you do a Desktop comparison the difference is roughtly 0.1s.
Also, in total, that page is made up of 3 major elements
- The server-side rendered HTML
That HTML controls the "First Meaningful Paint" which takes 3 seconds. And the whole shebang, including the banner ad, takes a total of about 9s. So, all this work of rewriting a React app to Preact saved me 0.8s out of the total of 9s.
Web performance is hard and complicated. Every little counts, but keep your eye on the big ticket items assuming there's something you can do about them.
At the time of writing,
preact-cli uses Preact 8.2 and I'm eager to see how Preact X feels. Apparently, since April 2019, it's in beta. Looking forward to giving it a try!