BEYOND PROGRESSIVE WEB APPS WITH SERVICE WORKERS @TrentMWillis #ThunderPlains

Atwood’s Law

Atwood’s Law “Any application that can be written in JavaScript, will eventually be written in JavaScript.” blog.codinghorror.com/the-principle-of-least-power

PROGRESSIVE WEB APPS

BEYOND PROGRESSIVE WEB APPS WITH SERVICE WORKERS

@TrentMWillis

Love The Web. Love JavaScript. @TrentMWillis #ThunderPlains

JavaScript continues to evolve. @TrentMWillis #ThunderPlains

Web Hypertext Application Technology Working Group @TrentMWillis #ThunderPlains

WHATWG @TrentMWillis #ThunderPlains

WHATWG Streams @TrentMWillis #ThunderPlains

WHATWG Streams “If installed inside the fetch hook of a service worker, this would allow developers to transparently polyfill new image formats.” streams.spec.whatwg.org @TrentMWillis #ThunderPlains

Idea #1: Transparently Polyfill New File Formats @TrentMWillis #ThunderPlains

New Image Format @TrentMWillis #ThunderPlains

New Image Format <img src=“./my-image.nif”> *Can Display PNG/JPEG/GIF, Not NIF @TrentMWillis #ThunderPlains

New Image Format Proxy Service Worker <img src=“./my-image.nif”> *Can Display PNG/JPEG/GIF, Not NIF @TrentMWillis #ThunderPlains

New Image Format .nif Service Worker .gif <img src=“./my-image.nif”> *Can Display PNG/JPEG/GIF, Not NIF @TrentMWillis #ThunderPlains

All code will be available online @TrentMWillis #ThunderPlains

navigator.serviceWorker.register(‘./service-worker.js’); service-worker.js @TrentMWillis #ThunderPlains

Occurs for every request on the page // service-worker.js self.addEventListener(‘fetch’, (event) => {; }); Lots of details and methods… @TrentMWillis #ThunderPlains

// service-worker.js self.addEventListener(‘fetch’, (event) => {; event.respondWith(); }); @TrentMWillis Used to implement offline functionality #ThunderPlains

// service-worker.js self.addEventListener(‘fetch’, (event) => {; event.respondWith(validFileFromPolyfilledFile(event.request)); }); @TrentMWillis #ThunderPlains

// service-worker.js self.addEventListener(‘fetch’, (event) => {; if (isPolyfilledFile(event.request)) { event.respondWith(validFileFromPolyfilledFile(event.request)); validFileFromPolyfilledFile }; }); @TrentMWillis #ThunderPlains

const validFileFromPolyfilledFile = async (request) => { }; @TrentMWillis #ThunderPlains

const validFileFromPolyfilledFile = async (request) => { const originalResponse = await fetch(request); }; @TrentMWillis #ThunderPlains

const validFileFromPolyfilledFile = async (request) => { const originalResponse = await fetch(request); const originalData = originalResponse.blob(); // .text(), .json(), etc. }; @TrentMWillis #ThunderPlains

const validFileFromPolyfilledFile = async (request) => { const originalResponse = await fetch(request); const originalData = originalResponse.blob(); // .text(), .json(), etc. const modifiedData = polyfillTransform(originalData); }; @TrentMWillis #ThunderPlains

const validFileFromPolyfilledFile = async (request) => { const originalResponse = await fetch(request); const originalData = originalResponse.blob(); // .text(), .json(), etc. const modifiedData = polyfillTransform(originalData); const modifiedResponse = new Response(modifiedData); return modifiedResponse; }; @TrentMWillis #ThunderPlains

cryptogram-naive.glitch.me @TrentMWillis #ThunderPlains

Polyfill new extensions to the web platform @TrentMWillis #ThunderPlains

Like importing HTML into JavaScript modules @TrentMWillis #ThunderPlains

html-modules-polyfill.glitch.me @TrentMWillis #ThunderPlains

Idea #2: Compile Code At Runtime @TrentMWillis #ThunderPlains

Idea #2: Compile TypeScript At Runtime @TrentMWillis #ThunderPlains

<script src=”my-module.ts”></script>

@TrentMWillis #ThunderPlains

<script src=”my-module.ts” type=”module”></script>

@TrentMWillis #ThunderPlains

Cache API @TrentMWillis #ThunderPlains

Only compile changed files @TrentMWillis #ThunderPlains

const compileWithCache = async (response, compile) => { }; @TrentMWillis #ThunderPlains

const compileWithCache = async (response, compile) => { const cache = await caches.open(‘compile-cache’); }; @TrentMWillis #ThunderPlains

const compileWithCache = async (response, compile) => { const cache = await caches.open(‘compile-cache’); if (response.headers.get(‘status’) === ‘304’) { } }; @TrentMWillis #ThunderPlains

const compileWithCache = async (response, compile) => { const cache = await caches.open(‘compile-cache’); if (response.headers.get(‘status’) === ‘304’) { return cache.match(request.url); } }; @TrentMWillis #ThunderPlains

const compileWithCache = async (response, compile) => { const cache = await caches.open(‘compile-cache’); if (response.headers.get(‘status’) === ‘304’) { return cache.match(request.url); } const compiledResponse = compile(response); }; @TrentMWillis #ThunderPlains

const compileWithCache = async (response, compile) => { const cache = await caches.open(‘compile-cache’); if (response.headers.get(‘status’) === ‘304’) { return cache.match(request.url); } const compiledResponse = compile(response); cache.put(request.url, compiledResponse.clone()); }; @TrentMWillis #ThunderPlains

const compileWithCache = async (response, compile) => { const cache = await caches.open(‘compile-cache’); if (response.headers.get(‘status’) === ‘304’) { return cache.match(request.url); } const compiledResponse = compile(response); cache.put(request.url, compiledResponse.clone()); return compiledResponse; }; @TrentMWillis #ThunderPlains

typescript-in-browser.glitch.me @TrentMWillis #ThunderPlains

Idea #3: Process Data Off The Main Thread @TrentMWillis #ThunderPlains

API JS @TrentMWillis Process Here #ThunderPlains

API Service Worker Process Here UI @TrentMWillis #ThunderPlains

API Web Worker Service Worker Process Here UI @TrentMWillis #ThunderPlains

The page makes a request… @TrentMWillis #ThunderPlains

The request from the page An identifier of which page const transform = async (request, clientId) => { const [response, port] = await Promise.all([ fetch(request), getPortToWebWorkerFromClient(clientId) getPortToWebWorkerFromClient ]); }; @TrentMWillis #ThunderPlains

const getPortToWebWorkerFromClient = async (clientId) => { const client = await self.clients.get(clientId); }; @TrentMWillis #ThunderPlains

const getPortToWebWorkerFromClient = async (clientId) => { const client = await self.clients.get(clientId); return new Promise(resolve => { }); }; @TrentMWillis #ThunderPlains

const getPortToWebWorkerFromClient = async (clientId) => { const client = await self.clients.get(clientId); return new Promise(resolve => { client.postMessage({}); }); }; Ask the page how to communicate with the web worker @TrentMWillis #ThunderPlains

const webWorker = new Worker(‘web-worker.js’); navigator.serviceWorker.onmessage = () => { }; @TrentMWillis #ThunderPlains

const webWorker = new Worker(‘web-worker.js’); navigator.serviceWorker.onmessage = () => { const channel = new MessageChannel(); }; @TrentMWillis #ThunderPlains

const webWorker = new Worker(‘web-worker.js’); navigator.serviceWorker.onmessage = () => { const channel = new MessageChannel(); const serviceWorker = navigator.serviceWorker.controller; serviceWorker.postMessage({port: channel.port2}, [channel.port2]); }; @TrentMWillis #ThunderPlains

const webWorker = new Worker(‘web-worker.js’); navigator.serviceWorker.onmessage = () => { This will let the SW talk to the WW! const channel = new MessageChannel(); const serviceWorker = navigator.serviceWorker.controller; serviceWorker.postMessage({port: channel.port2}, [channel.port2]); webWorker.postMessage({port: channel.port1}, [channel.port1]); }; @TrentMWillis #ThunderPlains

const getPortToWebWorkerFromClient = async (clientId) => { const client = await self.clients.get(clientId); return new Promise(resolve => { client.postMessage({}); }); }; @TrentMWillis #ThunderPlains

const getPortToWebWorkerFromClient = async (clientId) => { const client = await self.clients.get(clientId); return new Promise(resolve => { self.onmessage = (msg) => { Now we know how to self.onmessage = null; talk to the Web Worker resolve(msg.data.port); }; client.postMessage({}); }); }; @TrentMWillis #ThunderPlains

const transform = async (request, clientId) => { const [response, port] = await Promise.all([ fetch(request), getPortToWebWorkerFromClient(clientId) ]); }; @TrentMWillis #ThunderPlains

const transform = async (request, clientId) => { const [response, port] = await Promise.all([ fetch(request), getPortToWebWorkerFromClient(clientId) ]); return new Promise(async (resolve) => { }); }; @TrentMWillis #ThunderPlains

const transform = async (request, clientId) => { const [response, port] = await Promise.all([ fetch(request), getPortToWebWorkerFromClient(clientId) ]); return new Promise(async (resolve) => { port.postMessage(await response.json()); }); Send the data to the Web Worker }; @TrentMWillis #ThunderPlains

const transform = async (request, clientId) => { const [response, port] = await Promise.all([ fetch(request), getPortToWebWorkerFromClient(clientId) ]); Get the result back return new Promise(async (resolve) => { from the Web Worker port.onmessage = (msg) => { resolve(new Response([${msg.data.toString()}])); }; port.postMessage(await response.json()); }); }; @TrentMWillis #ThunderPlains

service-worker-blocker.glitch.me @TrentMWillis #ThunderPlains

service-worker-worker.glitch.me @TrentMWillis #ThunderPlains

Idea #4: Process Data As It Streams @TrentMWillis #ThunderPlains

Original Data @TrentMWillis #ThunderPlains

Original Data @TrentMWillis Modified Data #ThunderPlains

BATCH Batch digra PROCESSING Encrypted Chunk Encrypted Chunk Decrypted Chunk @TrentMWillis Encrypted Chunk Decrypted Chunk Encrypted Chunk Decrypted Chunk Encrypted Chunk Decrypted Chunk Encrypted Chunk Decrypted Chunk Decrypted Chunk #ThunderPlains

Encrypted Chunk STREAM PROCESSING Encrypted Chunk Decrypted Chunk @TrentMWillis Encrypted Chunk Decrypted Chunk Encrypted Chunk Decrypted Chunk Encrypted Chunk Decrypted Chunk Encrypted Chunk Decrypted Chunk Decrypted Chunk #ThunderPlains

Data Chunk Data Chunk Modified Chunk @TrentMWillis Data Chunk Modified Chunk Data Chunk Modified Chunk Data Chunk Modified Chunk Data Chunk Modified Chunk Modified Chunk #ThunderPlains

Data Chunk Data Chunk Modified Chunk @TrentMWillis Data Chunk Modified Chunk Data Chunk Modified Chunk Data Chunk Modified Chunk Data Chunk Modified Chunk Modified Chunk #ThunderPlains

Data Chunk Data Chunk Modified Chunk @TrentMWillis Data Chunk Modified Chunk Data Chunk Modified Chunk Data Chunk Modified Chunk Data Chunk Modified Chunk Modified Chunk #ThunderPlains

Data Chunk Data Chunk Modified Chunk @TrentMWillis Data Chunk Modified Chunk Data Chunk Modified Chunk Data Chunk Modified Chunk Data Chunk Modified Chunk Modified Chunk #ThunderPlains

Original Data Data Chunk Data Chunk Modified Chunk @TrentMWillis Data Chunk Modified Chunk Modified Data Data Chunk Modified Chunk Data Chunk Modified Chunk Data Chunk Modified Chunk Modified Chunk #ThunderPlains

const validFileFromPolyfilledFile = async (request) => { const originalResponse = await fetch(request); const originalData = originalResponse.blob(); // .text(), .json(), etc. const modifiedData = polyfillTransform(originalData); const modifiedResponse = new Response(modifiedData); return modifiedResponse; }; @TrentMWillis #ThunderPlains

const validFileFromPolyfilledFile = async (request) => {; const originalResponse = await fetch(request); const transformStream = new TransformStream(new Transformer()); const transformedBody = originalResponse.body.pipeThrough( transformStream ); const transformedResponse = new Response(transformedBody); return transformedResponse; }; @TrentMWillis #ThunderPlains

const validFileFromPolyfilledFile = async (request) => {; const originalResponse = await fetch(request); const transformStream = new TransformStream(new Transformer()); const transformedBody = originalResponse.body.pipeThrough( transformStream ); const transformedResponse = new Response(transformedBody); return transformedResponse; }; @TrentMWillis #ThunderPlains

const validFileFromPolyfilledFile = async (request) => {; const originalResponse = await fetch(request); const transformStream = new TransformStream(new Transformer()); const transformedBody = originalResponse.body.pipeThrough( transformStream ); const transformedResponse = new Response(transformedBody); return transformedResponse; }; @TrentMWillis #ThunderPlains

const validFileFromPolyfilledFile = async (request) => {; const originalResponse = await fetch(request); const transformStream = new TransformStream(new Transformer()); const transformedBody = originalResponse.body.pipeThrough( transformStream ); const transformedResponse = new Response(transformedBody); return transformedResponse; }; @TrentMWillis #ThunderPlains

const validFileFromPolyfilledFile = async (request) => {; const originalResponse = await fetch(request); const transformStream = new TransformStream(new Transformer()); const transformedBody = originalResponse.body.pipeThrough( transformStream ); const transformedResponse = new Response(transformedBody); return transformedResponse; }; @TrentMWillis #ThunderPlains

const validFileFromPolyfilledFile = async (request) => {; const originalResponse = await fetch(request); const transformStream = new TransformStream(new Transformer()); Transformer const transformedBody = originalResponse.body.pipeThrough( transformStream ); const transformedResponse = new Response(transformedBody); return transformedResponse; }; @TrentMWillis #ThunderPlains

class Transformer {; async start() {}; async transform() {}; async flush() {}; }; @TrentMWillis #ThunderPlains

cryptogram-streaming.glitch.me @TrentMWillis #ThunderPlains

Idea #1: Transparently Polyfill New File Formats Idea #2: Compile Code At Runtime Idea #3: Process Data Off The Main Thread Idea #4: Process Data As It Streams @TrentMWillis #ThunderPlains

Service Workers, Streams, MessageChannels, Web Workers, Caches, Web Assembly… @TrentMWillis #ThunderPlains

Service Workers, Streams, MessageChannels, Web Workers, Caches, Web Assembly…these are all just tools to build things, so go build something! @TrentMWillis #ThunderPlains

EXPERIMENT WITH JAVASCRIPT AND HAVE FUN! *ALSO, USE GLITCH glitch.com/@trentmwillis/fun-with-service-workers @TrentMWillis #ThunderPlains