I ❤️
🤷🏾
# CHAPTER 1
Parses HTML
Builds the DOM
Parses CSS
Executes JavaScript
Responds to User Events
<!DOCTYPE html>
<html lang="en">
<head>
<title>My App</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.js"></script>
<noscript>Bad luck</noscript>
</body>
</html>
🤔
But what if JavaScript fails?
<!DOCTYPE html>
<html lang="en">
<head>
<title>My App</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.js"></script>
<noscript>Bad luck</noscript>
</body>
</html>
😏
JavaScript never fails, right?
1-2%
Did the HTTP request for your JS fail?
Was the JS blocked by the corporate firewall?
Is something interfering with your JS?
Has the user switched off JS?
Is data saver mode turned on?
Does a browser plugin mess with your JS?
Ad blocker?
Old Browser?
Page Loads | JavaScript Failures |
---|---|
100 | 1-2 |
1,000 | 10-20 |
10,000 | 100-200 |
100,000 | 1,000-2,000 |
1,000,000 | 10,000-20,000 |
some code
😰
Reduce
Reduce your JavaScript footprint by server side rendering HTML.
# CHAPTER 2
The server sends a "ready to be rendered" HTML response to the browser
The browser renders the HTML page which is now interactive for the user
The browser requests JavaScript code from the server
Our JavaScript is parsed and our page is progressively enhanced to be even more interactive
Photo by Rayson Tan on Unsplash
🤓
The first lesson in rendering off the main thread is to leverage your server to do the initial render
Reduce
Reduce your JavaScript footprint by server side rendering HTML
Reuse
Compose our application with re-usable components
# CHAPTER 3
🙋🏻
Why use a framework when Web Components exist?
const template = document.createElement('template');
template.innerHTML = `
<h1>
Hello <slot name="name"></slot>
</h1>`;
class HelloWorld extends HTMLElement {
constructor() {
super();
const shadow = this.attachShadow({ mode: 'open' });
shadow.appendChild(template.content.cloneNode(true));
}
}
customElements.define('hello-world', HelloWorld);
<hello-world>
<span slot="name">
Prairie Dev Con
</span>
</hello-world>
<hello-world>
<span slot="name">Calgary</span>
</hello-world>
💁🏿♀️
😅
Enhance allows developers to write components as pure functions that return HTML. Then render them on the server to deliver complete HTML immediately available to the end user.
Enhance takes care of the tedious parts, allowing you to use today’s Web Platform standards more efficiently. As new standards and platform features become generally available, Enhance will make way for them.
Enhance allows for easy progressive enhancement so that working HTML can be further developed to add additional functionality with JavaScript.
Enhance is a web standards-based HTML framework. It’s designed to provide a dependable foundation for building lightweight, flexible, and future-proof web applications.
All components are server side renderable with no changes.
Leverage your CSS skills and style encapsulation in the light DOM.
Use slots without the shadow DOM
Avoid excessive JavaScript code by leveraging the platform and web standards
🤓
The second lesson in rendering off the main thread is to reuse what the browser already provides
Reduce
Reduce your JavaScript footprint by server side rendering HTML
Reuse
Use the platform to provide a reusable component architecture with Web Components
Recycle Offload
Expensive or time consuming tasks to web workers
# CHAPTER 4
Web Workers makes it possible to run a script operation in a background thread separate from the main execution thread of a web application.
Main Thread
Web Worker
onclick Event
The user clicks a button to save a new todo
postMessage()
postMessage()
onMessage Event
Sends new todo to our server and awaits the response
onMessage Event
Update our Store and emit an event for the web components to update
// Todo API
let worker
export default function saveTodo(todo) {
if (!worker) {
worker = new Worker('worker.mjs')
worker.onmessage = mutate
}
worker.postMessage({
data: todo
})
}
const store = Store()
function mutate(result) {
const copy = Array.from(store.todos)
copy.push(result)
store.todos = copy
}
// worker.js
self.onmessage = async function message({ data }) {
try {
const result = await (await fetch(
`/todos/${data.key}`, {
body: payload,
credentials: 'same-origin',
headers: {
'Content-Type': 'application/json'
},
method: 'POST'
}
)).json()
self.postMessage(result)
}
catch (err) {
console.error(err)
}
}
// Todo API
let worker
export default function saveTodo(todo) {
if (!worker) {
worker = new Worker('worker.mjs')
worker.onmessage = mutate
}
worker.postMessage({
data: todo
})
}
const store = Store()
function mutate(result) {
const copy = Array.from(store.todos)
copy.push(result)
store.todos = copy
}
// store.js
function subscribe (fn, props) {
return listeners.push(fn)
}
function unsubscribe (fn) {
return listeners.splice(listeners.indexOf(fn), 1)
}
_state.subscribe = subscribe
_state.unsubscribe = unsubscribe
function notify () {
listeners.forEach(fn => {
fn(payload)
})
}
const handler = {
set: function (obj, prop, value) {
if (obj[prop] !== value) {
obj[prop] = value
set(notify)
}
return true
}
}
const store = new Proxy(_state, handler)
// todo-list.js
import CustomElement from "@enhance/custom-element"
class TodosList extends CustomElement {
constructor () {
super()
this.api = API()
this.update = this.update.bind(this)
}
connectedCallback () {
this.api.subscribe(this.update, [ 'todos' ])
}
disconnectedCallback () {
this.api.unsubscribe(this.update)
}
update ({ todos }) {
const activeTodos = todos.filter(t => !t.completed)
const completedTodos = todos.filter(t => t.completed)
this.activeTodosList.todos = activeTodos
this.completedTodosList.todos = completedTodos
}
}
🤓
The third lesson in rendering off the main thread is to use web workers for expensive operations after you've mastered the first two lessons!
Reduce: the amount of JavaScript required to build your HTML
Reuse: what the platform already provides
Offload: expensive or time consuming tasks to web workers
Use JavaScript
Not a lot
Mostly for progressive enhancement
h/t Michael Pollan