Welcome to Future of Web Application Delivery — Medium
- Dropping 9 script tags at the top of a page and blocking UI
- Dropping 132 script tags at the bottom of a page, screwing up the order of dependencies and flooding the network
- Using AMD without a build with waterfall dependency loading (oops)
- Using modules with a build, sending 650 kilobytes of gzipped code in a single file that the visitor probably won’t ever need to run. Also, not sending any HTML over the network but building it all with JavaScript after JavaScript loaded.
I, and much of the industry, stopped at #4 and just got obsessed with which tool create the bundle(s). But why does my login page send a megabyte of JavaScript‽
I’ve wanted — for a very long time — the ability to deliver an app partially. When somebody visits “/login”, they should only have to download the code needed to render the login page. After they log in, the dashboard code should load and render the dashboard. At any time, the only code in the browser is the code the visitor absolutely needed and nothing else.
I’ve tried very hard to do this with lots of different tools, but I always screwed something up — until now.
These three tools have changed everything for me:
Webpack
The hardest thing for me to deliver an app partially was configuring my build tool to know how to split up my code without duplicating or omitting any modules. This configuration turned into an application all its own. Unfortunately I wasn’t smart or careful enough to ever get it right — Webpack is.
Webpack is a “module bundler”. When you type `node app.js`, node sees your dependencies and loads them up to run the app: it kind of bundles it all up in memory. When you type `webpack app.js app.bundle.js` it does the same thing but bundles everything up into a file, ready to be run in a browser.
But it can do more than that. Webpack has code-splitting built-in, you do nothing more than require your files differently in your code. It looks like this:
const login = require('./login') // will be bundled
login(() => {
// hints to webpack to split this code out of the bundle
// but "ensure" that its available before the callback runs
require.ensure([], () => {
var willNotBeBundled = require('./DashboardPage')
})
})
That’s all you do, webpack does the rest intelligently: it keeps the modules out of the main build and then loads them dynamically when needed.
✔︎︎ Code splitting
React Router
The next tricky part was figuring out smart places in my app to split the code. React Router defines each “screen” in my app, which is exactly where I want to split code.
React Router is something I started right after I got my hands on React.js. Partial application loading was one of my primary motivations (check out the 20th commit on the project). We didn’t get it right, though, until the 1.0 release.
Instead of only configuring a component or its child routes synchronously with a route definition, you can also define them asynchronously. When I first mixed this with webpack’s code loading I audibly giggled in my basement office.
const AccountRoute = {
path: '/account/:accountId',
// React Router will call this to get the component
getComponent(location, cb) {
// webpack will put it in a different "chunk" file
// and load it when this gets called
require.ensure([], () => {
cb(null, require('./components/Account'))
})
},
// Same thing for the child routes
getChildRoutes(location, cb) {
require.ensure([], () => {
cb(null, [
require('./routes/Invoices'),
require('./routes/People'),
])
})
}
}
✔︎ Defining split points and loading only when needed
React.js
The last trick is rendering your app on the server and in the browser.
I had a hard time rendering UI on the server and then making it come to life in the client. I ended up writing my app twice. If I added an item to a list, I needed a server template and a client template. If I needed to format some text in the template I needed a helper in both places. I had bugs in these places all the time. I once worked on a project that loaded up our JavaScript into our Ruby test suite to make sure our assignment grading calculations between the two languages were in sync.
Moving all the rendering to the browser and then communicating with a data API was liberating for me, I no longer had to write two apps. But remember that twitter post about client side rendering? I remember a large portion of front-end developers saying essentially “Well, I guess we can’t render in the browser because twitter.” So lots of people went back to (or kept on) writing two apps.
While not the first to do it, React.js has popularized the idea of rendering the same application on the server and in the browser.
// on the server you can render the app to a string
// and then send it over the wire
res.write(ReactDOMServer.renderToString(<App/>))
// then the client can pick it up
ReactDOM.render(<App/>, elementTheServerRenderedInto)
Every other server+client rendering technique I’ve seen blows away the html the server sent when the client renders. React, however, can recognize that its the same app (using a checksum) and leaves the page alone.
Now the whole question of rendering on the client, or server, or a little bit of both, doesn’t even have to be addressed. You write your app and render it in both places. Having shipped a few of these apps, it has eliminated nearly every client/server rendering tradeoff I’ve had to make in the past.
✔︎ Rendering the same app on the client and server
Welcome to the Future
Bringing all three of these technologies togethers makes creating a server rendered, partially loaded application pretty easy and provides some really exciting benefits.
- Visitors get UI even before JavaScript starts loading
- Visitors only download the JavaScript necessary to render the current page, even as they navigate around, my app can scale without ever increasing the size of the initial download
- Search engines can index my app more easily if that’s important
- I don’t have to write two apps for the same UI
- I don’t have to write an “application” that handles my build’s code splitting
- I don’t have to write code to load the bundles
Here’s a small example to see how little effort it takes, thanks to these great tools:
https://github.com/rackt/example-react-router-server-rendering-lazy-routes
I’m still just geeking out that I can finally deliver an application over the wire the way I’ve always wanted to without having to be a programming genius.
Next step is wrapping up the application in a shell with service workers and not requiring an http connection to render initial UI at all.
To be continued…