Leveraging Stream API within Service Worker

Majid Hajian

mhadaily

 service workers can do more than caching

mhadaily

Agenda

  • Stream API
  • How it helps performance

ME.


MaterialApp(
   ThemeData(
        name: "Majid Hajian",
        location: "Oslo, Norway",
        description: '''
        	Passionate Software engineer, 
	        Community Leader, Author and international Speaker
         ''',
        main: "Flutter/Dart, PWA, Performance",
        homepage: "https://www.majidhajian.com",
        socials: {
          twitter: "https://www.twitter.com/mhadaily",
          github: "https://www.github.com/mhadaily"
        },
        author: {
          Pluralsight: "www.pluralsight.com/authors/majid-hajian",
          Apress: "Progressive Web App with Angular, Book",
          PacktPub: "PWA development, 7 hours video course",
          Udemy: "PWA development, 7 hours video course",
        }
        founder: "Softiware As (www.Softiware.com)"
        devDependencies: {
          tea: "Ginger", 
          mac: "10.14+",
        },
        community: {
          MobileEraConference: "Orginizer",
          FlutterVikings: "Orginizer", 
          FlutterDartOslo: "Orginizer",
          GDGOslo: "Co-Orginizer",
          DevFestNorway: "Orginizer",
          ...more
        })
      );

mhadaily

Find me on the internet by

The Streams API

  • Data (de)compression
  • Image decoding
  • Video effects
  • Reduce Time to First Contentful Paint

mhadaily

https://developer.mozilla.org/en-US/docs/Web/API/Streams_API

Core Cocenpt

  • Readable streams (ReadableStream)

  • Writable streams (WritableStream)

  • Chunks
  • Transform streams (TransformStream)

mhadaily

  • Pipe chains (PipeTo(), PipeThrough())
  • Push and Pull sources

https://web.dev/streams/

import GrayscalePNGTransformer from './png-lib';

(async () => {
  const image = document.getElementById('target');

  // Fetch the original image
  const response = await fetch('turtle.png');
  // Retrieve its body as ReadableStream
  const body = response.body;
  // Create a gray-scaled PNG stream out of the original
  const rs = body.pipeThrough(new TransformStream(new GrayscalePNGTransformer()));
  // Create a new response out of the stream
  const newResponse = new Response(rs);
  // Create an object URL for the response
  const blob = await newResponse.blob();
  // create URL
  const url = URL.createObjectURL(blob);
  // Update image
  image.src = url;
})();
addEventListener("fetch", event => {
  event.respondWith(fetchAndStream(event.request))
})

async function fetchAndStream(request) {
  // Fetch from origin server.
  let response = await fetch(request)

  // Create an identity TransformStream (a.k.a. a pipe).
  // The readable side will become our new response body.
  let { readable, writable } = new TransformStream()

  // Start pumping the body. NOTE: No await!
  response.body.pipeTo(writable)

  // ... and deliver our Response while that’s running.
  return new Response(readable, response)
}

This allows you to minimize:

  • The visitor’s time-to-first-byte.
  • The buffering done in the Workers script.

mhadaily

import * from './transformstream.js';
import * from './JSONTransformer.js';

const dial = document.querySelector("progressbar");
fetch('./tweets.json')
  .then(async resp => {

    if (resp.status != 200) {
      //Don't try to parse non JSON responses, such as a 404 error...
      return;
    }

    const bytesTotal = parseInt(resp.headers.get('Content-Length'), 10);
    const jsonStream = resp.body.pipeThrough(new TransformStream(new JSONTransformer()));
    const reader = jsonStream.getReader();

    let bytesCounted = 0;
    while(true) {
      const {value, done} = await reader.read();
      if(done) {
        dial.percentage = 1;
        return;
      }

      bytesCounted += value.length;
      dial.percentage = bytesCounted / bytesTotal;
    }
  });

mhadaily

PRECACHE

mhadaily

PRECACHE

shell_header.html

shell_footer.html

mhadaily

Dynamically changes

mhadaily

https://www.youtube.com/watch?v=25aCD5XL1Jk

import {precache} from 'workbox-precaching';

precache([
  {url: '/shell_header.html', revision: SHELL_HEADER_REV},
  {url: '/shell_footer.html', revision: SHELL_FOOTER_REV},
  // Additional resources to precache...
]);

mhadaily

const contentStrategy = workbox.strategies.staleWhileRevalidate({
  cacheName: API_CACHE_NAME,
  plugins: [
    new workbox.expiration.Plugin({maxEntries: 50}),
  ],
});
const cacheStrategy = workbox.strategies.cacheFirst({
  cacheName: workbox.core.cacheNames.precache,
});

registerRoute(/\.html$/,
  workbox.streams.strategy([

    // Get from cache 
    () => cacheStrategy.handle({
        request: new Request(getCacheKeyForURL("/shell_header.html")),
    }),

    //  Get the body from the network
    ({request}) => contentStrategy.handle({
      request: `${request.url}?content=true`
    });

    // Get from cache 
    () => cacheStrategy.handle({
        request: new Request(getCacheKeyForURL("/shell_footer.html")),
    }),

  ]);
);

mhadaily

Summary

mhadaily

Takeaways

  • Breaking up pages and cache chunck
  • Loading shell from cache, avoid blocking first paint behind any resource requests such as css
  • Service worker Flexibility
  • Stream API is powerful that can help to load data in chunks and transfer them on the fly

The web platform today is way more powerful than what we think!

 

let's embrace it!

Majid Hajian

mhadaily

Slides and link to source code

slides.com/mhadaily

majid[at]softiware[dot]com

SVG icons credited to undraw.co

Leveraging Stream APIs within Service Worker

By Majid Hajian

Leveraging Stream APIs within Service Worker

The Streams API provides an interface for reading or writing asynchronous chunks of data, only a subset of which might be available in memory at any given time. The API helps to enhance UX both in a SPA or MPA (Multi-page) architecture. Using the Streams API within our service worker makes that possible. Workbox Streams abstracts the details of how streaming works. In this short talk, you will learn how to leverage the powerful Stream to enhance UX and boost the web page's performance.

  • 801