You might have heard the term “MTU” aka. the “Maximum Transmission Unit”. It’s a networking term that describes the maximum number of bytes that can fit in a single packet of information. This packet will travel from your computer, all the way to the recipient intact, without further breaking into smaller parts (most of the times). FYI, a typical MTU size is 1500 bytes.
This means, that if we somehow manage to fit all of our useful information inside a single MTU, we will achieve the maximum possible delivery speed. So what do you think, can we fit something useful in such a tight space?
Press enter or click to view image in full size
Serving something useful
First of all, let’s define “useful”. I would like to set our bar high. As high as possible. So, I want to deliver:
- A website
- That is responsive, and fits in my phone or desktop screen
- That has a nice background, colours and graphics
- That is dynamic, and does something useful
- Let’s say that this “useful” something is a 3-tabbed web-app, with a stopwatch, a timer and a clock!
How much can we really fit?
Before we continue, let’s start by computing our size budget:
- 1500 bytes of MTU (of raw, unstructured data on the wire)
- minus 20 bytes for the IPv4 header
- minus 20 bytes for the TCP header
- minus about 91 bytes for a simple HTTP response
- we are left with 1369 bytes for our website!
If my calculations are correct, we are going to waste most of our space in the CSS styling, so let’s start with this. Maestro, cue the music…
Doing some basic styling
I am dreaming of a silky smooth gradient background, and a white panel floating in the middle of the page. I think a sky-blue palette is a good start. I open my trusty ultimate gradient generator and I put on my values.
Oh and I am assuming we are doing this in the era of 2020’s so no more browser-specific suffixes, or old technologies! (🤣 yeah! less bytes wasted!)
body, html {
background: #b2e1ff;
background: linear-gradient(to bottom, #b2e1ff 0%,#66b6fc 100%);
height: 100%;
overflow: hidden;
}That must look nice! But so empty without a content… let’s add a panel. We are going to use flexbox for sure! Oh, and we are keeping our class names to minimum. That’s 1 byte for ya:
.c {
display: flex;
justify-content: center;
align-items: center;
height: 100%;
}.b {
background: #fff;
border: solid 1px #999;
width: 70%;
padding: 20px;
}
So far we are doing a good job, and we are only 362 bytes deep
Press enter or click to view image in full size
According to my awesome UI skills, we are going to need a few components in our design:
- A tab button
- A large text for the clock
- Some media queries for smaller screen resolutions
A few more CSS definitions later, we end up with the following result. You can check the complete CSS in the following gist: https://gist.github.com/wavesoft/6e33858bab9149b9f1834a590f56c085#file-style-css
Press enter or click to view image in full size
Let’s set this aside, and let’s have a look on the functionality for a sec.
Implementing our business logic
To stress my limits, I am going to use a Virtual DOM library for rendering our dynamic application. If you don’t know what this is, have a look on React and it’s internals. But for my case, I am going to use a special little library (literally) 😉
Over the past years I have been perfecting the smallest, most feature-complete virtual DOM library, that fits in 512 bytes! Ladies and gentlemen, I present you the dot-dom project : https://github.com/wavesoft/dot-dom
.dom is a fully-featured Virtual DOM library that supports proper reconciliation, higher-order components, lifecycle hooks, keyed updates and much more!
To get started, I just opened the minified version of the library and copied in a blank new file that I save as app.js. Now it’s the time to start writing our logic.
Writing a web app with .dom
I am going to start with the main application component:
// Extract accelerators
const { div } = H;// The main application component
function App() {
return div.c(
div.b("Some Content")
);
}// Render the app
R(H(App), document.body);
Let’s break this down for you…
HTML tag accelerators
First I am extracting a few accelerators from the H function. This function is the same as the createElement in other Virtual DOM libraries, but it also has the capability of automatically binding itself to a tag named as the property being accessed.
const { div } = H;So effectively, every time you call div(...) you implicitly call H("div", ...). Neat, right? Why? Because you can now compose an entire HTML page in a natural manner like so:
return div(
div("Some Content")
);Naturally, this renders to <div><div>Some Content</div></div>. But what about styling now?
Styling accelerators
.dom accelerator offers another amazing feature : if instead of calling them, you try to access properties even deeper, they automatically append the respective class names to the class property.
Get Ioannis Charalampidis’s stories in your inbox
Join Medium for free to get updates from this writer.
So, like before, when you call div.a(...) you implicitly call H("div", {className: "a"}, ...). So you now know what this does:
return div.a(
div.b("Some Content")
);That’s right, this renders to <div class="a"><div class="b">Some Content</div></div>. Kudos!
Components, components, components!
Another beautiful feature of .dom and the rest of the Virtual DOM engines is how easy you can create re-usable HTML components. I am also following this pattern in my example.
I am going to start by creating a re-usable component that renders a tab header and the body of the active tab:
const Tabs = ({t}, {a=0}, setState) => {
const names = Object.keys(t);
return div.b(
// The tab header contains the tab buttons
div.t(
// Create a button for each tab
names.map((tabName,i) =>
// Make sure to add the 'a' class on the active tab
div.n[a == i ? 'a' : '']({
onclick: () => setState({a:i})
},
tabName)
)
),
// Render the active tab component
H(t[names[a]])
);
}This tabs component is the main router of our application, so we can now change our main class to:
const App = (props, state, setState) => {
return div.c(
H(Tabs, {t: {
Clock: ClockTab,
Stopwatch: StopwatchTab,
"1h Timer": TimerTab
}})
);
}Higher-order components
Now, like with any other Virtual DOM libraries, we need a trigger to render a property update. Since all of our components are based on clock ticks, it might be a good idea to create a higher-order component that re-renders a component on a fixed interval:
const WithInterval = (Component) => {
return (props, state, setState, hooks) => { // Register a hook that starts a timer when the component
// is mounted
hooks.m.push(() => {
// The `state` object can also be used for local storage`
state.t = setInterval(
() => {
// Update the component, by updating the 'i' variable with
// the number of milliseconds passed since mount
setState({i: (state.i || 0) + 60})
},
60
)
}); // Register a hook that resets the timer when unmounted
hooks.u.push(() => {
clearInterval(state.t);
}); // Call-out to the higher-order component, and share our
// state with it. Alternatively we could have used the H
// function and pass the state as property, like so:
//
// H(Component, { i: state.i })
//
return Component(props, state, setState, hooks);
}
}
Now we can change our main function to:
const App = (props, state, setState) => {
return div.c(
H(Tabs, {t: {
Clock: WithInterval(ClockTab),
Stopwatch: WithInterval(StopwatchTab),
"1h Timer": WithInterval(TimerTab)
}})
);
}We can then spend a few more minutes, writing the implementation for our clock, stopwatch and timer. I am not going to bore you with these details, you can check the gist for the final result though 😉
Putting them all together
Instead I am fast-forwarding you to the final bit. The compression! (Remember our size budget. We need to fit this project in 1369 bytes)
So far we have created 2 files. The style.css and the app.js that contains the styling and the application. (I have cheated, and created also an index.html to render the screenshots for you, but let this be just between us)
For our “production” page, we must be silly-compact. This means that we must embed everything into a single page. We also know that HTML is implicitly closing the open tags, so we can save a few more bytes by not closing the <body> and the <html> tag.
But let’s start by firstly minifying our javascript:
npm install uglify-es -g
uglifyjs <page.js >page.min.jsAnd our CSS:
npm install uglifycss -g
uglifycss <style.css >style.min.cssNow we can compose our page, fragment-by-fragment:
echo '<!DOCTYPE html><html><head><title>Clocks</title><style>' > page.html
cat style.min.css >> page.html
echo '</style><body><script>' >> page.html
cat app.min.js >> page.html
echo '</script>' >> page.htmlBefore we do ls and get terribly disappointed with the result, let’s gzip first our page
gzip -9 page.htmlAnd now we can tap ourselves on the back with the final result, since we have 108 more bytes to spare 💪
~$ ls -l page.html.gz
-rw-r--r-- 1 user staff 1261 Aug 22 23:26 page.html.gzNow the only thing we have to do, is to serve it with the following HTTP response (that takes 91 bytes):
HTTP/1.1 200 OK
Content-Type: text/html
Content-Encoding: gzip
Content-Length: 1270
And we are ready to enjoy the result
Press enter or click to view image in full size
And this, ladies and gentlemen, is how you can create a web app that fits in an MTU! 🎩
This one was particularly simple, but I bet you have already thought of something more complicated for your case. And yes, it’s going to be darn small!
Making things even smaller
Let’s say that we are not satisfied with the current size and we want to take things even further. What could you do?
Here are a few tips for you:
- Reduce the name of the variables that will survive minification
- Move common code into separate functions
- Declare your functions as ES6 lambda functions
- Try to repeat yourself as much as possible
I know that the last point is a bit contradicting with the second, but this is helping a lot with the GZip compression.
Helping GZip
Without going into too much technical details, a compression algorithm is making things smaller by removing duplicate parts. Period.
How good or how bad an algorithm is, vastly depends on it’s heuristics on which parts to combine.
GZip is not an exception. If you help it, it helps you. How? Simply by repeating things in your logic.
Have a look on the following picture, produced by GZThermal. It nicely visualises how well each fragment is compressed. Red means bad, blue means good.
Press enter or click to view image in full size
As you can see, the more you repeat a character, or a pattern, the better it gets compressed. So how can we exploit this? Here are a few more tips:
- Stick on a particular array iteration function, even if it’s not fitting for your case (eg. stick to
array.map()) - If you are repeating a some code (eg. getting defaults of a variable, or sharing some common CSS definitions), chunk as much of the code together, in such a way that GZip will find the same byte chunk twice.
- Avoid capital letters for you variable/function/key names. Don’t be surprised, remember that the rest of your code is lower-case.
- Avoid mixing patters in your code. For instance don’t use both javascript quirks to change the type of the variable (eg.
''+number) and verbose functions (eg.number.toString()).
With these tips, and with the help of gzthermal, I managed to squeeze .dom in just 512 bytes, and this is how you can make your Web App even smaller 👏