So, hi, I'm Jeff Posnick from
the Chrome Developer Relations
Team.
We've heard a lot about the
benefits of progressive web
apps, and we've also heard
a lot about Service Workers
and we'll be hearing more about
those in sessions to come.
But I want to take the
time to talk about one
approach to structuring your
progressive web app using what
we're calling the App Shell.
The App Shell
architecture works really
well with a Service
Worker caching
as progressive enhancement.
So during the session we'll
talk in detail about the SW
pre-cache and SW tool
box libraries, which
make it easy for
developers to add Service
Workers to their web apps.
Web apps that use the App
Shell plus Service Worker
model effectively can feel
like they load instantly.
And before we go
any further, let
me demonstrate what I mean
with a real world example.
So I built a web app to browse
iFixit repair guides using
iFixit's excellent public API.
The iFixit API returns
a mix of text and images
like you'd find in many
content browsing applications,
and this the screencast
we're using the iFixit API
to load in a list
of featured guides
and then browsing
through a few of them.
So you can imagine
each of these pages
being something a user discovers
on the web as a search result
or finds a link to
from some other site.
It's the web so there's nothing
special to install in advance.
So loading obviously
looks fast, but let's put
some hard numbers behind a few
different loading scenarios.
So all of these examples
were run on a simulated 2G
mobile network against
the App Engine instance
we've deployed on, and
we're showing off top level
navigation's here.
So what you see on the
screen is experience
loading the web app
for the first time
with nothing in the cache and
no Service Worker in place yet.
We're able to get all the
above-the-fold content
on screen in a very
respectable 650 milliseconds.
So let's compare
that first visit
to what your users will
experience the next time they
visit the same page.
With a Service Worker in place
and your cache populated,
we're able to get the
above-the-fold content
on screen in around
200 milliseconds.
As a point of
comparison, the same page
loaded with a populated cache
but without a Service Worker,
maybe because your browser
doesn't support it,
takes around 560 milliseconds.
And let's take a look at the
learning experience for a page
that we haven't
previously visited.
So because a web app Service
Worker is already installed
and the App Shell is cached, new
pages load instantly as well.
So we can get
above-the-fold content
on the screen in
225 milliseconds.
With the same cache content
but without a service worker,
we're looking at
about 650 milliseconds
to get the content
on the screen.
You can see that even on
a slower mobile network,
great load times are
possible across the board.
So regardless of how you
structure your web app,
that initial request for
the HTML landing page
can take time.
On the left is your
network traffic
requesting our iFixit demo
app for the first time
with no Service Worker
and nothing in our cache.
It takes 423 milliseconds before
we get a full response back.
And this includes the
time it takes the server
to make the iFixit
API request for us.
On the right, we're serving
the same page directly
from our cache.
It only takes five milliseconds
once the Service Worker
gets involved.
So with a Service Worker
and with the right caching
strategy, the network
no longer stands
in the way of getting your
app on your users' screens.
So we strongly
feel that adopting
the model we'll be
talking about today
is the surest way to build web
apps that consistently load
in under a second, which is a
target we recommend in the Rail
performance model.
You'll hear more about Rail
during tomorrow's sessions,
but keep that in mind.
And as my colleague
[? Paul ?] pointed out,
loading instantly can
make a big difference
in a web app's success.
Studies have shown that an
additional one second delay
in page load times
can lead to 11%
overall fewer page views as well
as a 16% decrease in customer
satisfaction.
So instant loading
isn't just nice to have,
there's a direct impact
on your bottom line.
So hopefully
everyone out there is
ready to go and adopt this
model in your own web apps.
But before you can
do that, let's run
through an explanation
of the key concepts
you'll need to understand.
So first, what's an App Shell?
So your shell is the
HTML, JavaScript, css,
and any other static
resources that
provide the structure for your
page minus the actual content
specific to the page.
So the shell should
be cached, obviously,
and should load quickly.
Think of your shell
as being kind of
similar to the native
code that would
get uploaded to an
app store if you were
writing a native application.
But you don't have to
sacrifice the linkabilty
and discoverability of the web.
And unlike with native
apps, you deploy updates
to App Shell in a second.
The concept of an
App Shell is going
to come up again and again
throughout this presentation.
Anytime you see
something colored green,
note that we're talking
about the App Shell.
So once the shell
content loads, we
fetch dynamic content, in
our case from the iFixit API,
to populate the view.
The App Shell plus
our dynamic content
equals the complete
rendered page.
Any time you see
something colored blue,
know that we're talking
about the dynamic content.
So now that we've talked
about the App Shell,
we can turn to
the next question,
what's a Service Worker.
Technically speaking,
a Service Worker
is a network proxy
written in JavaScript
and intercepts your HTTP
requests made from your web
pages.
But an analogy
that I like to use
is to think of a Service
Worker as being an air traffic
controller.
So let's see how this
analogy plays out.
You can think of your
web app's HTTP requests
as planes taking off.
With a Service Worker you now
have an air traffic controller
making sure that your requests
load quickly and efficiently.
So the request plans might
land using the network runway,
or they might land using the
cache runway in which case
the request can bypass
the network completely.
So it's up to the
Service Worker to decide
how to handle each request.
And here's a quick
visual walk-through
of the life of a Service Worker.
We start out with a page
that isn't yet controlled
by a Service Worker
but contains code
to kick off the registration.
So that creates a new
Service Worker instance
and fires the install
event that the Service
Worker will respond to.
And now is our chance to
add our App Shell's content
to the cache.
Let's dig in deeper.
So while every setup
may be different,
in our iFixit sample we're
serving the HTML for our App
Shell from the /shell URL.
Keep that in mind throughout
this presentation when
you'll see a /shell
pop up again.
So to populate our cache, the
service worker makes an HTTP
get request for /shell
during the install events.
And let's walk through
the contents of our shell.
So we're including some
site-wide inline styles that'll
be shared by all of our pages.
And next we have a
template insertion point
within our body element.
So this doesn't contain
any real content
but it serves kind of as a
placeholder for the client side
templating that will take place
when our content is loaded.
And finally, in this
example, we have a reference
to an external file app.js
that contains all the site
wide JavaScript
that our page needs.
So back to the life
of the Service Worker.
The next event you'll get a
chance to handle is activate.
So here you normally
perform cache cleanup
of any out-of-date resources you
no longer need in your shell.
And once those
handlers complete,
your Service Worker
enters into an idle state.
So it's running, but
it's not doing anything
until there's a network request
that fires off a new event.
And in response to
a network request,
a fetch event handler will get a
chance to intercept the request
and respond as you see fit.
So that happens after
a period of idleness,
your service worker script
is stopped automatically.
So when the next
network request is
made when your page is loaded
again it's started back up
and can immediately
respond to fetch events.
So the air traffic controller
analogy breaks down a bit
here because you
probably wouldn't
want to tear down your
air traffic control
tower every time a plane lands
and wait for another one,
but bear with me here.
OK, so let's take a look at
how our fetch handler serves up
our App Shell.
Our Service Worker can intercept
any requests for any URL
under its scope, including
pages that haven't
been previously visited.
So in our case, we want to
respond to any navigation
requests with the /shell
response that we've previously
cached.
And this will bypass
the network completely.
So this is a model that you
might already be familiar with.
It's what happens with server
side rendering when there's
a URL pattern that's routed to a
matching handler, which is then
responsible for returning
the full page to the browser.
And this is the Service Worker
equivalent of that routing.
And it's code that runs
client side on the App Shell
that ends up populating the
content in the scenario.
And if you're using a
universal JavaScript framework
it might end up being the
same templating code that
runs on both the
server and the client,
but that's not a requirement
for using this model.
So at this point, I hope you see
the advantages of structuring
your web app using the App
Shell plus Service Worker model.
And while you could write
your own service worker code,
we have two libraries
that will take
care of many of
the details for you
while following
the best practices
and avoiding common gotchas.
So, going back to our
analogy for a second,
you may be an awesome
web developer,
but you might not
feel comfortable
writing the code responsible
for landing an airplane
and that's fine.
These libraries will
handle that code for you.
So the first library that
we're going to talk about
is sw-precache.
So it takes care of
caching and maintaining
all the resources
in your App Shell.
Second library we'll
talk is sw-toolbox.
It provides a canonical
implementation
of all the runtime caching
strategies that you
need for your dynamic content.
So we built these libraries
to go together hand-in-hand.
And using them
together gives you
a Service Worker that can
load your App Shell instantly
while giving you
fine-grained control over how
your dynamic content is cached.
So both of these libraries
are battle tested
and ready for production.
They've powered the Service
Worker for this year's Google
I/O web app, so chances
are you've already
visited a page that uses them.
I was a member of the team
that built the I/O web app
and we specifically
designed these libraries
to handle the real
world needs that came up
while building the site.
I wrote up a case study
about our experiences
which you can read at
the URL on the slide.
And we're also really
excited to announce
the flipkart is using the
sw-toolbox library directly
as part of their new
progressive web app.
You'll hear more
about that tomorrow.
All right so let's dive into
the first library, sw-precache.
So most web apps already have
some sort of build process
in place, and sw-precache
drops right into your existing
build process and uses wildcards
to identify all the resources
that make up your App Shell.
And we're using Gulp
in the iFixit demo app,
but sw-precache works
equally well with Grunt,
and we have a command
line version as well.
All right so let's take a look
at what happens when your build
process includes sw-precache.
So here we have a set
of local files in our
build directory that
make up our App Shell.
We perform a Gulp build,
which includes a step that
runs sw-precache for us.
sw-precache is configured
with some wildcards
to match all those local files.
And what it does is calculate
a hash of each of the files
that it finds that
matches the pattern,
and generates a service
worker code for you that
contains a mapping of the
URL and the current hash.
So this Service Worker
JS file that it generates
contains all the code
that you need to maintain
the cache of those files.
You don't have to write a thing,
you can just use it as is.
And the initial visit to a web
app that uses a Service Worker
triggers a pre-caching of all
the URLs that were identified.
And you can think
of this as kind
of being equivalent to like
a native app store install
process.
OK?
So your App Shell changes
over time and sw-precache
will make sure that your
cache changes along with it.
So let's assume you make
some updates to two files
and you're ready to push those
changes out to your web server.
When you run Gulp as
part of your deployment,
it will regenerate the
service worker JS file.
Since two of the files
now have new contents,
the associated hash
will change as well.
And there's a logic in the
generated service worker
file that will detect
those changes for you
and automatically update
the cache entries just
for the files that have changed.
When the users
return to your app,
only new or modified
resources are downloaded.
You can kind of think of
this as a native app store's
incremental updates.
So those who prefer a
command line build process
to [? Gulp or Grunt ?] can
install the sw-precache module
globally from NPM and
then run it directly.
So remember to rerun
the command prior
to deploying an
update to your site
to ensure that the generated
service worker picks up
on all the local changes.
All right, let's get
our hands dirty a bit
and take a look at how I'm using
sw-precache in the iFixit web
app demo that I showed earlier.
So this snippet is
part of the Gulp
build strip for the
project which gets run
through Babel, so it can use all
soft of [? ES2015 ?] goodness.
And first and most importantly,
we have the dynamic URL
to dependencies
option and this is
how we keep an always up-to-date
copy of the application shell
cached.
So we use the URL /shell as
a key and the value here is
an array of all the files that
uniquely define the inline
contents of /shell.
Next we set the navigate
fallback option to be /shell
again.
Our new service worker file
will automatically serve
the contents of /shell whenever
there is a navigation to any
URL on our site.
It will serve out the
application show even
if the actual navigation for
the request is something like
/guide123, and client side URL
routing will handle populating
content.
Next, static file globs
is also really important.
This is where we list all
the individual App Shell
resources that might be
requested directly as opposed
to those that are included
in-line within the shell.
And finally we have the
import scripts option.
And here's where we can
extend the generated service
worker to include additional
logic that we might need.
In this case, we're
pulling in a configuration
file for sw-toolbox which is the
next library we'll talk about.
All right so sw-toolbox
is the answer
for caching your web
apps dynamic content.
So everything that we've colored
blue in this presentation
basically.
And while sw-precache integrates
with your build process,
sw-toolbox is loaded by your
Service Worker at run time.
And there are number of very
common caching strategies
sw-toolbox provides a canonical
implementation of each.
So let's take a look at some
of those caching strategies.
And one approach is to have
the service worker check
the cache for a response.
If it's there, great,
return to the page.
If it's not there in
the cache already,
then return a response
from the network instead.
So this is a good
strategy to use
when you're dealing with
remote resources that
are very unlikely to change
like static images or something
like that.
And it corresponds
to sw-toolbox's cache
first handler.
The converse of that approach
is to check the network first
for a response, and
if that's successful,
return it to the page.
If the network
request fails, you
can fall back to a previously
cached entry instead.
And this is a good
strategy to use
when you're dealing
with data that
needs to be as
fresh as possible,
like, say, a real-time
API response.
But you still want to display
something as a fallback
when the network is unavailable.
And this corresponds
to sw-toolbox's network
first handler.
So finally, here's an
interesting strategy--
The Service Worker fires
off both network requests
and requests it goes against
the cache at the same time.
Cache response will
normally come back first,
and that will get returned
directly to the page.
But in the mean time, the
response from the network
is used to update the
previously cached entry,
and that keeps things
relatively fresh.
This updates from the
background without blocking
rendering of the cache content.
And you can get this
for free just by using
the toolbox.fastest strategy.
It's pretty powerful.
So you do need to think
a bit strategically here.
You need to take time and
think about which strategy
is most appropriate for
the dynamic resources that
populate your App Shell.
But you don't have
to choose just one.
sw-toolbox's routing syntax lets
you apply different strategies
to different URL old patterns.
But you might be
saying you could
implement those caching
strategies yourself.
So why use sw-toolbox?
sw-toolbox has a couple
of additional options
that help solve
problems that arise
while fetching your content.
These options make Service
Worker caching much more useful
in real world scenarios.
So first a phenomenon that
I'm sure everyone here
has encountered, Lie-Fi.
So it's one of your
device's network connection,
but it's one that's
extremely unreliable or slow.
While your App Shell
should always be cached,
[? as ?] be fetched
cache first, you
might request
dynamic content used
to populate your
shell using a network
for a strategy in some cases.
And Lie-Fi can be
deadly in those cases.
If there were no network
at all, your requests
would fall back to
the cache right away.
But when there is a
network request that
just drags on and on and on
before eventually failing,
you end up wasting precious
seconds just waiting
for the inevitable.
So using sw-toolbox, you can
set an explicit network timeout.
And here we're setting
it to three seconds
when fetching an
image network first.
And after those
three seconds have
passed, if we don't have
a response from network,
it'll automatically fallback
to the cache content for us.
So here's another
real world problem
you might bump up against.
What happens as users go from
page to page on your site.
So you probably
caching those page
specific contents,
like images associated
with each page the user
visits at one time,
to make sure that
if they ever return,
the full page will
load instantly,
not just the App Shell.
If you keep adding to your
dynamic caches indefinitely,
you'll end up using an ever
increasing amount of storage.
So sw-toolbox will actually
take care of cache expiration
for you saving you the trouble
of implementing it yourself.
Here we have sw-toolbox to use
a dedicated cache for images
with a maximum
cache size of six.
So once the cache is
full, as it is now,
new images will cause the
least recently used images
to be evicted.
In addition to the least
recently used expiration
option, sw-toolbox also gives
you a time-based expiration
option where you can say
automatically expire everything
once it reaches a certain age.
So flipkart was looking for a
solution to this exact problem
and we're really
happy that they turned
to sw-toolbox for that job.
All right, so now
let's take a look
at the sw-toolbox configuration
used in our iFixit demo app
that we showed earlier.
So this file gets included with
our Service Worker at runtime
by using the import scripts
option in sw-precache.
So first, we define a route
for our iFixit API requests
and we're using the tool box
stuff fastest strategy here,
which strikes a nice balance
between automatically returning
stale content immediately
and automatically
keeping our caches
up-to-date in the background.
So next, we explicitly
add an image
to our cache that will act as
a placeholder missing image
if the request for
a real image fails.
That, in turn, uses as part
of our image handler function,
which takes a request, attempts
to get a response using a cache
first strategy,
and if that fails
falls back to that
missing image placeholder.
And finally, you can
see how we set up
a route that uses
our image handler
function to handle
requests for images
served from CloudFront.net.
We're also making
use of a maximum size
for this dedicated
cache that we're
using for images so after
50 images have loaded
in the cache, the least recently
used ones will automatically
be expired.
All right so I'm sure
that throughout this talk
there's been something
in the back of your mind.
This App Shell model
is great, but how does
it work in browsers that
don't support Service Workers?
Well, Service Workers
are, in general,
progressive enhancement and
that applies when they're used
in the App Shell model as well.
So going back to
our analogy, it's
possible to land
planes at an airport
without an air
traffic controller,
but you're probably gonna want
to do that very slowly and very
deliberately.
And I wouldn't really want to
be a passenger on that plane.
So putting our
analogy aside, how
do you go about
treating service workers
as a progressive enhancement?
Well remember that the first
time you visit a web app,
you won't yet be a
Service Worker in place.
So you need to make sure that
your web app renders properly
without Service Workers anyway
it'd handle that scenario.
And browsers that support
Service Workers, next visit
to your web can take full
advantage of everything
that Service Workers
have to offer.
But for browsers that don't
support Service Workers,
the next visit to
your site ends up
looking very much
like that first visit.
There won't be a Service
Worker, so you just
continue to serve things
via network requests.
So your architecture
shouldn't have
to change in order to
accommodate browsers that lack
Service Worker support.
So whether there's a Service
Worker in your browser or not,
HTTP caching best
practices still apply.
So you should continue
to follow patterns
like adding in hash fingerprints
into your file names
and using far future HTTP
caching expiration headers.
This doesn't change any of that.
And sw-precache will happily
work with that set up.
But if you are in a browser
that supports Service Workers,
you now have a key to unlock
additional performance ones.
So you can serve even your
initial HTML landing page
directly from the cache when
using a Service Worker, which
is difficult to do safely
with just HTTP cache headers.
If you remember from
the start of the talk,
that means the difference
between 423 milliseconds
to retrieve your landing
page and five milliseconds.
And you can use that
same cache for a strategy
even for pages that
are dynamically
rendered on the server
because sw-precache will keep
those pages fresh for you when
one of the underlying partials
or in-line continent changes.
All right so where
does this talk
leave you as a developer
who's interested in using
this architecture?
Well, we have a number of demos
and jumping off points that you
can choose from to get started.
So first of all,
you should realize
that if you're building
a modern single page app,
you're probably
already using something
similar to an App Shell
already, whether you call it
that or not.
The details might
vary a bit depending
upon which libraries or
frameworks you're using,
but the concept itself is
definitely framework agnostic.
So your next step could be
just to add in a Service Worker
to your existing web app using
sw-precache and sw-toolbox
libraries.
But if you're
starting from scratch
and you want some
inspiration or just want
to see a finished real
world example here's
the source code
for the iFixit API
client I demonstrated earlier.
It uses universal JavaScript,
both server and client side
rendering, and can be adapted
to go against a different API
or source of dynamic
content if you want.
Next for those who prefer
a step by step, hands
on learning experience,
we put together
a code lab that walks
you through adding
sw-precache to an existing
web apps build process.
Those of you who are
attending in person,
you can do it in the code
section that's outside.
I also want to mention
a Polymer based demo
that my colleague Rob
Dotson put together,
which makes use of an App
Shell, a Service Worker
model in a blog web app which
I think is called Zuperkulblog.
Rob will be talking more
about that project including
the correct pronunciation
a little bit later today
in his session.
Next most recent release of
the Web Starter Kit project
includes sw-precache and
sw-toolbox and projects
that use Web Starter
Ki as a starting point
will take advantage
of them by default.
So this is super
powerful in terms
of getting this technology out
there to folks who are getting
started on web development.
And finally my colleagues
[? Attius ?] [? Mony ?]
and [? Matt ?] [? Gant ?] spent
a great deal of time putting
together this Vanilla
JavaScript starting point.
It gives you an App
Shell plus Service Worker
setup with minimal opinions
about frameworks or libraries.
I highly recommend that
folks check this project out.
And it gives you a great place
to start with all the best
practices already baked in.
OK so we strongly believe that
the App Shell plus service
worker model is the best way to
structure your web apps if you
want reliable instant
load times without having
to worry about the network
getting in your way.
We hope you are inspired
to go out there and adopt
this model today.
There's no reason
to wait, and we
know your users will appreciate
not having to wait either.
Thank you.
[APPLAUSE]
[MUSIC PLAYING]
