BEYOND PROGRESSIVE WEB APPS WITH SERVICE WORKERS @TrentMWillis #ThunderPlains
A presentation at ThunderPlains in October 2019 in Oklahoma City, OK, USA by Trent Willis
 
                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
 
                @TrentMWillis #ThunderPlains
 
                @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
