So you read Second-guessing the modern web and found it compelling. Maybe moving everything to client rendered React SPAs is not the answer. But now what? What comes after the Modern Web? How do we keep the good parts of both server rendering and SPAs? What might the Postmodern Web be?
Declarative vs Imperative
React is rightly praised for being declarative in nature. To review:
- Declarative - Your boss sends you a spreadsheet to update with new data. You punch it in, and automatically a bunch of formula cells update, some of which you didn't even know about. You take a long lunch and relax.
- Imperative - Your boss sends you a spreadsheet to update, but for some reason has exported it to a lifeless CSV. You punch in your data, you're not sure what these other poorly labeled columns are, and it's going to be a long night. (Or similarly, the server sends you some lifeless html, the user has issued a click event, and now you need to figure out all the jQuery DOM mutations you need to do to get this thing working again.)
Your app as a tree-shaped spreadsheet
How do we get those declarative qualities in a server rendered app?
Below is a toy app written to behave like a spreadsheet. Try adding items until you get free shipping, or reducing quantity to zero.
When you hit the plus button for apples, that code simply changes the value in the textbox. The Apple total amount updates itself. Here's the application code for it:
<td
class="subtotal"
data-source="apple"
data-text-content="$num('#apple .unitprice') * $num('#appleqty')"
></td>
That's all the application code needs, just like a spreadsheet cell. The rest is handled by the "library" code:
- The
data-text-content
attribute is the formula for the textContent property of the<td>
, which could be any Javascript expression. The formula is put into a render function on the node. -
$num()
is a convenience function that takes a CSS selector, finds a matching node, and returns its value as a number. This formula is simply multiplying unit price and quantity to get a subtotal. - The
data-source attribute
is the id of a DOM element to monitor. A MutationObserver watches this node, and if there are changes, issues an event that triggers the render function. (There's probably a way to infer this from the formula itself, the way a spreadsheet would, and not need this at all.) - Also, I had to "fix" text
input
s a bit. By default, Javascript changes to their values do not trigger change events or persist to the DOM; I modified their prototype so that they do.
So the plus button modifies the quantity text box (and that's it). The #apple .subtotal
cell notices a change has occurred in its source, so it reruns its formula. Meanwhile, the #itemtotal
cell has been watching all the .subtotal
cells, so it reruns its formula, and so on, all the way down the DAG of the application. (This is similar to using setState()
in React and having props drill all the way down.)
Say you wanted to add a new feature that tells clients how close they are to getting free shipping. This is it:
<span data-source="footer" data-text-content="
$num('#shipping') > 0 ?
'Only $' + (25 - $num('#itemtotal')) + ' until free shipping!' : ''
"></span>
This is one possible way to bring a declarative style back to server rendered apps, without having to completely upend everything. More subjectively, it feels web-y to me.
Animated Transitions
But wait, what about about animated transitions? This one probably matters more for big consumer facing brands, but one of the things you can do with client side routing is have slick animated transitions between "pages" instead of the page flash you typically get between two server rendered pages.
However, with some helpful libraries, you can layer transitions on top of server side routing. Here's the toy app again; try clicking some of the links and you'll see a "shared element transition" where an element from one page will seemingly accompany you to another page with a smooth animation:
How does it work?
There are a few parts:
- Server rendered pages - in this case they are just static pages
-
Turbolinks - this library does most of the work:
- Intercepts clicks on a link to another page
- Gets the contents of that page via XMLHttpRequest
- Does a swap into the current page
- Changes the URL, makes a history entry, etc.
- DOM diffing (of a sort) - I check if any elements on the current page are supposed to persist into the new page and collect their locations and sizes
- FLIP technique - For each persistent element, I take the new instance, "rewind" to the position and size of the old instance, and then let it animate to the new position and size.
This is a fairly naive implementation but hopefully it gets the point across: you don't need to go all the way to a SPA just for page transitions. (There are other libraries as well out there that handle "bigger" page transitions such as barba.js.)
Looking ahead
So, is there anything of value here? What else might the Postmodern Web look like? Will web components play a role? Let me know what you think in the comments!
Top comments (0)