Instant-loading Offline-first (Progressive Web App Summit 2016)
Skills:
UI Design80%
Key Takeaways
Builds a Progressive Web App with offline-first capabilities using modern web technologies
Full Transcript
[Music] Okay. Right. Do I have everything plugged in now? I've got two clickets here. Okay. So, a lot of times when people do these kind of uh polls of an audience like they ask people to to to raise their hand. It's kind of totally meaningless. It's just to kind of make sure you're all awake. But I'm going to ask you a question and it's actually going to it's going to decide the talk I give. Uh so here goes. Who here has sort of seen a talk about service workers before or kind of know roughly what they are? Okay. Right. So, I'm not going to do the intro talk then. Uh so, if you don't enjoy the talk that I'm about to give, uh it's actually your fault. Uh you picked it, so you should have picked the other one. Um so, firstly, I'm going to do a little bit of a recap. Uh I built a little web app, a web app called Emojoy, which you you can find at this URL. uh it started just as a website, but I turned it into a progressive web app. And if you're looking to do the same thing, uh the first thing you need to do is to integrate the site with the operating system and and the UI. Uh and and one of the ways of doing that is in the head of the page, you can use a theme color and you know, Chrome will use that to kind of change the color of the location bar there. So, it's a a small quick win for integration, but the main part is this uh web app manifest. So, inside the manifest, it it looks like this. It's got details on the the name of the app, the icon, all that sort of stuff. And once you have this, a user will be able to add the site uh to their home screen and they get the icon and the name uh that you specified. But not only that, when the user taps the icon, they get a splash screen. This is reasonably new in Chrome. Uh and this is while the browser is booting up and while your page is getting to first render. And so the icon uh the text there and the background color and the sort of the purple strip across the very top there, that's all taken from your manifest. So you can configure all of that. And once that goes away, you get your site but uh without the URL bar or that's that's an option you can have. So here it just it feels like a native app. However, this whole illusion of native comes crashing down when you're offline. So this is pretty bad, right? Like the user has launched our app. They've come back to our app. They've added it to the home screen. That's how much they like it. But we have failed them to such an extent that the browser has had to step in and defend us. And they do this by blaming the user, saying, "You are offline. This is your fault. If we're going to compete with native, this is this is like an operating system level error message. It's like a full crash. So this is not good enough. But things get worse. So offline isn't the worst thing that we face. This is I call it LiFi. This is when your phone says it has connectivity, but it doesn't really. And I think we've all experienced this that the LiFi experience is just that. so that the splash screen goes away when the the site gets to its first render. But with LiFi, that never happens. This is worse than offline. With offline, you get a quick answer. The answer is no. But it is a quick answer. Here, the user is just left waiting. You're forcing the user to stare at this or give up. And with every passing second, they hate the user experience a little bit more. And this is why we should avoid treating online and offline as these kind of binary states. is if we cater for online and we cater for offline as separate things, this situation goes completely unsolved. And that's why the gold standard here is offline first. Offline first solves these problems. With offline first, we assume offline and do as much as we can with local content, then try and get content from the network. And the more we get to render without a connection, the better. So, you should think of the network as a piece of progressive enhancement, an enhancement that might not be there. And here's how we did it. So to begin with, register for a service worker. And this isn't some kind of magic manifest format. It's just JavaScript because we thought, why do we invent a whole new thing when we've got like a room full of JavaScript developers, got a world full of JavaScript developers and loads of existing tooling. Of course, we should wrap uh this register call here in a simple feature detect because you know there are older browsers out there that don't support service worker and this feature detect prevents them from hurting themselves and others around them. Anyway, in that script, I'm just going to register for this uh event, this fetch event. I'm going to have a debugger statement there. And when I do that now, if I refresh the page, I hit a break point and you get this event object and it has a request property. And this is the request for the page. And you get things like the URL, the headers, the type of request. But you also get one of these events for every request that the page makes. The CSS, the JavaScript, the fonts, the the images. Uh I get the event for these avatars even though they're on another origin. So it's it's not just your own origin. So by default, requests will go from the page to the network. And there's not a lot you can do about it. But once you introduce a service worker, it controls pages and requests will go through it. But like other events on the web, you can prevent the default and do your own thing. And that's where things get really interesting. So let's use that to go offline first. So the first thing we need is an application shell, which is just the the site but without the messages in this case. We want to serve this and the CSS it requires and the JavaScript it requires before we even try a network request, before we even think about going to the network because even if we have a a great network connection, serving this locally is is going to be faster. And that's the offline first approach. So we need to cach this ahead of time and the service worker has an event for doing this. It's the install event. This is fired when the browser sees this version of the service worker for the first time and it's your opportunity to go and uh you know fetch everything you need to work and store it in a cache. So here I'm I'm opening a cache called static v1 and in there I'm adding the HTML for the shell, the CSS and the JavaScript that it needs. But these won't be used by default. Service worker doesn't do anything for you. It kind of it waits to be told what to do. So over in the fetch event, I'm going to start by parsing the URL so we can look at the the component parts of it. And then if the request is to the same origin and it's just slash as in it's the homepage, we're going to respond with the appshell from the cache. So calling respond with here is is my indication saying look, I'm going to handle this request. I'm going to take over here and we pass in either a response object or a promise that resolves to a response. Uh caches.match that uh that that returns a promise which resolves with a response from the cache. So this composes together really well. Otherwise, we're going to try and respond with cacheed content that matches the current request. So, that's how we're going to pick up the CSS and the JavaScript when the the shell page requests those. If there's no match, it's going to resolve if undefined. Uh so, as a fallback, if response is falsy, we'll make a network request. So, this is the the offline first approach. We're treating the cache as our priority, as our primary source, and then falling back to the network, whatever connection the user has. So using this pattern, you know, the page goes to the service worker and the service worker just pulls the the the shell from the the cache and the CSS and the JavaScript it needs. So that gives us our shell render without going to the network. And now we're running JavaScript on the page. So we can display even more content. So with a Mojo, in this case, I actually went to index DB and and pulled out the past messages, the last messages the user saw, and render those on the page as well. So the user is looking at messages without having to go to the network. And then we can go to the network for newer messages if we can and we can show updated content and additional content. So if this network request fails, it doesn't matter because we're displaying content. That's a good offline experience. If the network is slow, then that's maybe okay because the user might just be looking at past messages and we've given them that. So to see the benefits of this, let's pit the original online only site against this new shiny offline first version. We must go to the comparinator. There was going to be sound there. Nah, never mind. Uh, so first up, the online experience. Go. So we get instant full content whereas the original lags behind having to fetch stuff from the network. What about the offline experience? Well, instead of complete failure, we have instant full content. What about LiFi? instant full content rather than the white screen of death. In fact, with our progressive web app, the experience is the same no matter what connection you have. And that's the offline first goal. The network only matters when it comes to fetching new content. And this is how we compete with native. And compete with native we can uh I decided to pit the the emoji progressive web app against a native app, Google Photos in this case because it's a well-built well-refined native app. And I just wanted to launch them at the same time and see what happened. So press them at the same time and it's really close. This is native versus web. And in fact, the Mojo is about 0.2 seconds slower to show content. That's 200 milliseconds. Not a lot. And that's compared to a well-built native app. But that's starting from cold. The browser isn't in memory. If the user has used the browser sometime recently, and let's face it, the browser is a pretty popular app, so it's likely to be in memory. This happens. This is a progressive web app beating a native app to content render by almost half a second. Now, a few people said to me, it's like, well, what about Android instant apps? What about those? Well, my answer to that is progressive web apps are shippable today and they're not Android only. You know, you can build one app across thousands of devices, operating systems, and browsers and it can beat a native app to content render. So service worker is in the stable versions of of Chrome, Firefox, Opera, uh Samsung browser as well and it's coming to Microsoft Edge. It's a high priority implementation for them. It's under consideration by Apple. Um but progressive enhancement means you can use it today. You just don't get the the the offline features uh in Safari. But if you use uh service worker and suddenly your stuff has more features or becomes faster in Chrome and Firefox than it does in Safari, then that will give Apple more reason to implement service worker. As web developers, you are in control of the future of the web here. But not enough about the things that are supported today. I want to talk about the future. This is what I wanted to get on to. The idea was to build something like um uh Wikipedia, but Wikipedia that ran offline first. So, uh something that it was very much very less of an app because the emoji is very much built like a native app. It looks like a native app, but was is this stuff applicable to sites? Like is it something that you'll be able to use for your blog? Um, and we thought Wikipedia is a really a really good demo of that. So, the idea was like when you're online, uh, you're going to get a server render because that's how Wikipedia works. But with a service worker that we can we can start doing different things. So, the first thing I did when I built the Wikipedia demo is uh is to create an appshell as well. That's right. So, I built an appshell uh for the article that would load this generic shell without the network and then the pages JavaScript will handle the fetching of the appropriate uh from thing from the network. So without service worker it was kind of just a standard website but with service worker I'd rearch rearchitected it to be more like a single page app. So I was dead excited about what I built. So I ran straight to the comparinator to see how it you know everything changed. So I'm going to set the the normal website here against my shiny service worker version. Uh and I'm going to load it over a throttled internet connection or as most of the world call it their internet connection. Three two one go. And I watched this and I thought, it got slower. Like way slower. Here it is again. First render is actually quite fast, but the content, which some people would say is the important bit, is over 2 seconds slower on 3G. Now, I had a mild panic, not as much as the panic I just had 2 minutes ago, but a mild panic uh that we' really messed up this service worker thing. Uh, but it wasn't service worker that was the problem. See, I was using this appshell and letting JavaScript do the rest, which is fine if your design already sort of dictates that you do something like this, but it wasn't the case here. I'd rearchitected a site into a single page app. And single page apps are incredibly slow. And this is why you'll see me moan about frameworks all the time on on Twitter. Here's what a website does to get to first render. HTML starts downloading, CSS downloads, and now we start rendering content. And we render more as more HTML downloads. We might download JavaScript as well, but it should be async, so it's not going to block render. Contrast this to s uh single page apps, uh, web apps, where HTML downloads usually instantly because it's normally really small. Uh, CSS downloads and then we get our first render, assuming the JavaScript isn't render blocking, which it often is, but let's be kind. Anyway, this is just a basic UI render, no content, and then JavaScript downloads, it pauses and executes, and then it goes off to fetch the content. And once it has all of the content, it adds it to the page and we get to render. And this is this is how slow happens, you know. And in my case, I wasn't losing too much time on the the CSS, the HTML, and the JavaScript download because they were all cached in the service worker. But it's the problem here is at the bottom. The JavaScript has to download all the content before showing any of it. The more I think about load time performance, the the more I realize it's not about the size of your CSS, the size of your JavaScript. It all comes down to being progressive. And this is related to progressive enhancement, but not quite the same. I mean, show them what you've got. Like at the all the time you're loading, you know, the user is watching and waiting. Don't make them wait until you have everything before showing them anything. Prioritize the first render, the first interaction. Show the user what you've got as you get it. And that's where I was failing. I was hoarding all the content and only showing it once I had all of it. In retrospect, it seems kind of stupid uh to cram a site into this single uh page app model. Uh but for me, this was my app cache hangover. Uh you see, app cache nearly nearly lets you use the page shell pattern, but not quite. But it lets you so close like you can almost smell it, but it just sort of bats you away at the the very last moment. So once app cache was out of the way, I was like, really? Appcache is gone. Does that mean I can have all of the page shells I want and nothing is going to stop me? But I didn't stop to think that the best answer wasn't the one the app cache nearly let me have. It was the thing that it didn't let me anywhere near. What I wanted was streaming. And with streaming you get to finish the this is a simulation here and you can see the sort of final render is somewhat quicker than the non-streaming version. But the important bit is that ah render happens ages beforehand than the in the non-streaming model. So this is the full HTML spec loading here over a throttled internet connection. Like it's a free megabyte document. It's still downloading but it gets on screen after only 20K is fetched which is great. That's streaming. But for a long time we had no access to streams in JavaScript. But that is all starting to change. So we designed the fetch API uh alongside service worker mostly because uh we needed a lower level representation of requests and responses but it was also a good opportunity to kind of to look at XHR and and make something that's a bit easier to use because XHR is almost 18 years old. I don't want to be using XHR when it's legal to drink. You know it's bad enough when it's sober. Uh so this is this is fetch. It returns a promise for a response. There it is. Uh but that that only resolves when we've received headers. So we haven't received the body yet. You have to choose how you want to read it and that gives you a promise as well. Uh and then you get your your data JSON in this case. This compresses really nicely using arrow functions and even better using async functions. So the keyword await there it pauses execution of the function until uh the promise resolves and it's paused in an async way. So it's not blocking the thread. It's not blocking interaction. I I think so Edge has this in their preview builds. I think we've got stuff in in Canary as well. But Babel let you use it in other browsers as well. and it produces much easier to read code. I I think it's great. So, if you haven't encountered it before, you you can pretend that async code is is sync. It makes it a lot easier to read. Anyway, you don't have to read the responses JSON. We added a load of different methods for common types that we you'd want to read. And we added these for two reasons. Like they're nice convenience methods, but we wanted to ship uh fetch before streams were ready. So, we needed a way to to read the body. But we did reserve uh response. first streams when they when they landed and they landed in Chrome and Opera around a year ago and they've been actively developed by by Firefox Edge and even Safari as well. They're working on them. Here's how you use them. So you you fetch something say the HTML spec and we're going to fetch it as a stream and we call get reader. So I'm saying like I I want want to open the stream. I want to get a lock on the stream. And then we can read some of the data. Read returns a promise. Uh result is an object. Result.done is true when there's nothing left in the stream. So um if resultdun isn't true, result do.value is a chunk of the data and that's ains 8 array of bytes that you've received. It's not the whole response. It's just the first part of it. And when we want the next part, we can call read again. So if we wanted to get the length of a whole response, uh we could do this. You know, we uh going to have a variable there to keep a running total. And then we're going to create a loop. I'm just going to keep calling read until result.done is false. And in there, get the value, add the length to the total, and then log it out. And that's it. Done. We should be able to see that working. I don't know. I feels really risky doing live demos after what happened before, but let's give it a go. Let's see. Here we go. So, this is uh let's see if I can make this bit bigger. Oh, wrong window. Maybe I can't make it bigger. Where's my mouse pointer? There we go. So, if we refresh the page here, we can see the sort of log at the bottom there, and it's reading uh the HTML stuff. And we can see that the um we're inspecting parts of the the response here, but we're not having to keep it all in memory. You can see the chunks are of different sizes, so that's something you have to deal with. Uh they're kind of around about 32K, but not always. Uh so, here we're reading the full size. It's 8 megabytes, I think, uh the the uncompressed size right down at the at the bottom there. But we don't have to have the whole thing in memory. We're just sort of reading bits and then they can be garbage collected straight away. So a more practical use of something like this would be to search a stream for a given string. So say we wanted to search the HTML spec for the word horse. Here's how we do it. So this is fairly a simple way of doing it. We're kind of fetching it, reading it as all this text, and then searching. But this means we have to load that full 8 megabyte document into memory and then search across it. We can do better with streams. Instead of fetching all the text, I'm going to loop over the stream as before. Uh but the problem here is that result value is an array of bytes whereas we need a string to do the the matching. In future this will be really easy. You'll just be able to pipe the stream through like a text decoder and and that will kind of read in the bytes, convert it to text and pass it out uh the other end. Uh but transform streams haven't been fully speced yet. They're all coming they're coming soon, but uh at the moment you can do things a little bit manually with with text decoder. So here we fetch get a reader but also create a text decoder and then we're going to loop through the stream as we did before. Um, but on result value I'm going to call decoder.deode and that'll take the bite and give us a string back and it decodes it assuming UTF8. There are other options as well. The stream true there that's that's actually really important. Um, that changes how things work. So let's see if I can show you that. So here's a a simple demo. So this is the uh the code you saw before. I've got three chunks of text there. And for each one I'm going to decode each part. And if I run that, everything is broken. Um, but if I add that stream option in and run things again, right, we get the poo emoji, the toilet emoji, and an exclamation mark. So the reason this happens is that emoji are four bytes long uh in UTF8. So with stream true it tells it that you know it should it might be receiving more uh bytes later as part of the same message. So with stream true it receives the first three bytes and it goes uh this is part of a character. This is not the whole thing. So it just returns an empty string and it holds those free bytes. The next time we call it it adds the next three bytes on. So it adds the the 169 the first bite and it goes ah right I've got enough now to show the the poo emoji. So I'm going to pass that back and I'm going to hold on to the last two bytes. And then we add the next chunk on the next three bytes and it goes, "Ah, right. I've now got enough to for the toilet emoji. Send that back." And also 33 at the end there as the character code for an exclamation mark. So we can send that back as well. So now we can check these chunks as they come along. Now we've got them as text to see if it contains the word horse. Uh and not only that, we can also cancel a download when we find it. And this is really efficient. So if the the it's an 8 megabyte document, but if we find a match in the first 20k, that's great. We can stop the download and and save all that bandwidth. But there is actually a bug with this implementation. It works fine if our chunks are like my lovely and then horse running through the field. Oh, there's the match in the second chunk. Great. But what if the chunks are my lovely H, no match. Or running through the field, no match. and we miss it because the the the match was across two chunk boundaries. So when you're doing things like searching or filtering streams, you need to defend against this. And the way we do it is by keeping a buffer that is uh the size of the thing we're looking for minus one. So in our case, horse five characters, we need a buffer of four. So that means that we know we could see my lovely H fine. And then we keep those last four characters and add them onto the next chunk. And then we see horse. So, the code for that, my slides have gone off again. Isn't that great? Whatever you did, oh, it's back. Excellent. So, the code for that, uh, same as before, but we create a buffer. My heart rate is going so fast. So, we're creating a buffer. We're going to loop through the stream, uh, and we're going to decode it, add it onto the buffer, and if it includes horse, great. And then we, you know, cancel. Otherwise, we reduce the buffer down to those last four characters. Uh, so now we're searching a large document. we're keeping the memory usage down uh and we can stop the download early. So, I'll show you an example of that as well. So, we're actually going to run this on on the HTML spec. It's a slightly uh longer uh piece of code because I'm going to keep a larger buffer so I can show show the search term. Uh so, with horse uh which I didn't expect to be in the HTML spec, but if we run it through, there's a match and it's because Tommy Forson is one of the contributors and he's got horse in his name. Uh let's run a different example. Someone give you an example of something that probably won't be in in the spec but might. Poo. Do you know what that means? You are so much more polite than uh the meetup in London that I gave this same talk to very recently because when I asked for a suggestion, there was actually a lot of silence for and then someone at the back of the audience just sort of stood up and pointed at me went Like okay, I'm going to pretend that's not a heckle and I'm going to uh going to do that sort of nervously. Okay, this will be fine. Uh, obviously you're not expecting anything to happen, but uh, as it searches, whoa, doesn't match. Holy. Uh, oh, it's cuz it's a canvas region. A canvas hit region. Okay, where are my slides? So uh oh yeah I was talking to some developers in India and and uh where connectivity is is quite poor and uh they were sending this blob of JSON down for some search results and they wanted to detect 2G versus 3G. So and the idea was like oh if they're on 2G we're going to send fewer results uh because it'll you know get on screen faster. You'll get a faster render. Um but detecting connectivity is really unreliable. Um especially in India where you can you can have 3G or 4G package but it'll be the speed of 2G. So it's much better to do something else like serve something like this where it's not JSON but every line is a JSON object and this is something you can stream then you can parse each line uh separately as JSON and deal with the data as it arrives and that means you can deal with the first result before dealing with the rest and there's a real there's a huge performance benefit to this. So here I've got an example of that. I'm going to uh throttle the network down to 2G. There we go. So a regular fetch of this JSON uh will take well it depends on the connectivity but it will take around about okay so we're talking about 5 seconds and with regular JSON it has to download the whole thing before it gives you any of it. So the first bit of JSON arrives after 5 seconds and so does the second but with a streaming solution uh it getting the last bit of data is going to take roughly the same amount of time. Yeah. So sort of 5 seconds but we get that first bit after 364 milliseconds. So there's a huge benefit here for slower connections. We can start showing results before we've received the entire uh the entire file. So you might be wondering how I can use this to speed up my my Wikipedia demo. Well, I can't. Uh because although you can use fetch to read a response in little chunks, JavaScript has no access to the streaming HTML paraser. It might look like here. I'm sort of happily adding HTML into an element, but plus equals is the same as getting then setting. So you're asking the browser to serialize the DOM and then you're asking it to parse some more DOM like you've added some more to the end. This is a performance disaster. Don't do this because the elements at the start of your string get created like hundreds of times. I'm hoping we can get like a streaming HTML paraser document fragment or something in future but it's it's not there yet. But in Chrome Beta and Chrome Canary, we we can create our own readable streams now. And this is something that's also being developed in uh in other browsers too. It looks like this. You just pass in an object with these methods and uh so say we wanted to create a stream of random numbers. We going to store an interval. I'll show you why in a moment. Going to create a stream and then we're going to pass in a start method. This is called uh straight away. So in here I'm going to set up an interval that pushes a random number to the stream every second. And the controller has other methods as well such as like close to signal the end of the stream, but we're not using that here. I'm also going to add a cancel method. This is when the the user cancels the stream after they're done reading it. You get this cancel method. So I can use this to to abort the the interval. So this is just like fetch streams now. Um as I show you an example of that running. I think it's this one. So yeah, here here we go. So this is just the code you saw before. But I'm going to read the value three times and then cancel and then try and read it again. Uh but what we'll see uh if the page loads, which should be great. You can see it's logged down at the bottom here. It's getting those numbers. It gets three of them, but then it's had enough of it. So this is a this is a pushbased stream because we're pushing data into it. Um so if no one's reading from the stream, those numbers are going to build up and up in memory and eventually you'll get memory problems. The opposite of this is a pull stream. The pull method is called when a reader is waiting on a value and we don't have anything left in the buffer. So when pull is called, I'm going to return a promise that waits for a second and then pushes the the random number on. And this is much better because you're not generating random numbers if nothing's waiting for them. Your streams can be of anything. With fetch, we saw there are Uints 8 array of of bytes. Here it's numbers. It could also be objects. It's just one interface for all of those things. Uh the designers of web streams, they they talked a lot to to the node folks uh because they have they've had like four three or four versions of streams and they've they've made mistakes along the way. So it was a really good source of like well what what did you do wrong so we can avoid making the same mistakes. So you might be wondering how I can use this to speed up my Wikipedia demo. Well, I can't uh because you know not just using streams on their own anyway because things get much more interesting when they're combined with a service worker. So this this already streams fine. It just does it automatically. The browser connects the dots. Same with this. The browser will stream it from disk from the cache. But in Chrome beta and Chrome Canary, you can create responses uh from streams. You can create your own streams uh and and stream those into the browser. So in my fetch event, I'm going to create an encoder. So this is the opposite of of before. It takes a a string and turns it into bytes because response streams must be bytes. We we saw that before. Then I'm going to create a stream uh with a pull method which waits a second and then pushes a paragraph onto the page. So you can see there I've got encoder.enccode to turn it into intotes. And then I create a response give it the stream and then say this is HTML. Give it the HTML header. So if we run that and it should be here we can see random numbers appearing on a 1second interval. So but as far as the browser's concerned it is just receiving text very slowly and you can see that the spinner at the top there it's as far as the browsers aware it is just loading a page uh very slowly. So you might be wondering how I can use this to speed up my Wikipedia demo. Well, I can. I actually can this time. Before we were piping content uh you know straight into the the browser streaming HTML paraser and that is the key to it all here. You know this is what appcache wouldn't let us anywhere near. So instead of turning like a a perfectly capable server rendered site into a JavaScript driven single page web app, I can use service worker more like my server. So here's what I did for for wiki offline. uh I fetch the article header, the body and the footer. So the header and the footer are coming from the cache. The body is coming from the network, but it will fall back to the something in the cache if it fails. Then I combine those streams together into one single stream. And combine is a function I wrote. It's it's about 10 lines. Uh but uh it's kind of a manual process right now. So that creates a single stream that reads the first stream into it, then the second, then the third, uh and so on until it's ran out and then it ends. And then we create a new response using that stream. So one response made of multiple different parts and places just like you do on the server. You get some stuff from a database, some stuff from a template, etc., etc. Treating your your service worker to a server maps really well to content sites like blogs and Wikipedia which are are server driven. See the result of this for one final time. We go back to the comparinator fight. Oh, the sound this time. Great. H you'd normally be bored of that sound by now, but that was the first time. Uh so here's how it compares. So on the left the appshell render uh and on the right the streaming service worker both over 3G. So launch them at the same time. So the the difference is absolutely massive. But the appshell was a performance regression anyway. So let's compare it to the original server render. So we get the benefit of that quick cached first render which happens without the network. But that allows us to get network stuff on screen quicker because there's less to fetch cuz we've already sent the header and stuff down. I'll play it again. So, the first render is almost like a second faster. Uh, but content render is half a second faster even though it's just coming from the network, the same network in each example. So, needless to say, I'm quite excited about streams, but that doesn't mean uh the appshell model is bad. Uh, you saw it being used earlier and the result was a progressive web app which launched faster than a native app. But a streaming solution will be faster and easier for you if you use server rendering. Say like switching to an appshell model uh here could be a lot of work and could land you with a performance regression. Uh if you're already a single page web app uh you have that performance regression already. So knock yourselves out, carry on doing that. Consider streams if your initial content uh may come from the network. The benefit of streams is being able to build content from multiple sources. uh if your initial content comes from the cache or a database entirely then you don't have so much to gain from streams. So and that was the case with emoji. I was getting all the first render content from caches and databases. So streaming wasn't a whole lot of use there. Consider streams if a partial content render is valuable to you and that I think is in most cases but especially written content. Uh, so if you're looking to build something like an offline first news sites like the Guardian or a shop like Amazon or your blog, streams could be the faster and easier way to do it. And they're landing in Chrome in the next few weeks and are being actively developed in Firefox and Edge. If you want to know more, I've written an article about streams. Uh, gushing praise on my blog. Uh, in this post, I do some sillier stuff as well like kind of transcode and an MPEG to a GIF on the fly or or transform content. actually did this with the uh I ran a a transform stream on the Wikipedia page for cloud computing. This is it. But every time it sees the word cloud, it's replacing it with but uh which is great to read. Where is the there's I've got a favorite bit in here. See if I can find it. Um there it uh there it is. Oracle announced the Oracle butt. Uh while aspects of the Oracle butt are still in development, this butt offering is poised. I love the term butt offering. That's great. I love that. So, service worker is uh this is Bruce Lawson's logo for service worker. I like it. It kind of makes me feel slightly drunk. It's great. Uh you can polyfill new network features with it. You can become a faster network resilient uh more network resilient. We've gone through a lot of stuff here at lightning speed and that monitor's gone off and I've got one slide left so let's rush. There's also a free Udacity course uh where you can take a website from online only to offline first and use it over a series of examples. Covers the appshell model in index DB. Uh, and with that, before everything just sets on fire and breaks, thank you very much. Cheers. [Music]
Original Description
For most of the web, poor network connectivity destroys the user experience. We can do better. In this session we'll take an online-only site and turn it into a fully network-resilient, offline-first installable progressive web app. We'll also break out of the app shell and look at approaches that better-suit traditional server-driven sites.
Subscribe to the Chrome Developers channel at http://goo.gl/LLLNvf
Music by Terra Monk: https://soundcloud.com/terramonk/pwa-amsterdam-2016
Watch on YouTube ↗
(saves to browser)
Sign in to unlock AI tutor explanation · ⚡30
Playlist
Uploads from Chrome for Developers · Chrome for Developers · 0 of 60
← Previous
Next →
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
Polymer Performance Patterns (The Polymer Summit 2015)
Chrome for Developers
Polymer Power Tools (The Polymer Summit 2015)
Chrome for Developers
Chrome Dev Summit 2014 – Chrome Case Studies
Chrome for Developers
Web Directions Code 2015 round up
Chrome for Developers
Maintainable Code - HTTP203
Chrome for Developers
iron-ajax… wat?! -- Polycasts #26
Chrome for Developers
The Guardian - Supercharged
Chrome for Developers
ES2015 (next version of JavaScript), Totally Tooling Tips (S2 Ep1)
Chrome for Developers
#AskPolymer: Rob answers all the questions ever -- Polycasts #27
Chrome for Developers
The Future of JavaScript - HTTP203
Chrome for Developers
Data Binding 101 -- Polycasts #28
Chrome for Developers
The Guardian part 2 - Supercharged
Chrome for Developers
The Future of Web Audio: with Chris Wilson and Chris Lowis
Chrome for Developers
Chrome 46: New motion-path animations, client hints and service worker improvements
Chrome for Developers
Sublime Snippets, Totally Tooling Tips (S2 Ep2)
Chrome for Developers
#AskPolymer: How do you make the show? -- Polycasts #29
Chrome for Developers
Critical Path CSS, Totally Tooling Tips (S2 Mini Tip #1)
Chrome for Developers
Binding to Objects -- Polycasts #30
Chrome for Developers
Player FM - Supercharged
Chrome for Developers
Where’s the Designer? #AskPolymer -- Polycasts #31
Chrome for Developers
Jake Beats Wikipedia - HTTP203
Chrome for Developers
Supercharged Observers! -- Polycasts #32
Chrome for Developers
Jai's Web blog - Supercharged
Chrome for Developers
Windows Command-line Tooling, Totally Tooling Tips (S2, Ep4)
Chrome for Developers
What about internationalization? #AskPolymer -- Polycasts #33
Chrome for Developers
Developing for Billions (Chrome Dev Summit 2015)
Chrome for Developers
Google+ Performance Improvement Comparison
Chrome for Developers
Deploying HTTPS: The Green Lock and Beyond (Chrome Dev Summit 2015)
Chrome for Developers
Progressive Web Apps (Chrome Dev Summit 2015)
Chrome for Developers
Instant Loading with Service Workers (Chrome Dev Summit 2015)
Chrome for Developers
Increase Engagement with Web Push Notifications (Chrome Dev Summit 2015)
Chrome for Developers
Engaging with the Real World: Web Bluetooth and Physical Web (Chrome Dev Summit 2015)
Chrome for Developers
Asking for Permission: respectful, opinionated UI (Chrome Dev Summit 2015)
Chrome for Developers
Polymer - State of the Union (Chrome Dev Summit 2015)
Chrome for Developers
Building Progressive Web Apps with Polymer (Chrome Dev Summit 2015)
Chrome for Developers
Introduction to RAIL (Chrome Dev Summit 2015)
Chrome for Developers
DevTools in 2015: Authoring to the max (Chrome Dev Summit 2015)
Chrome for Developers
RAIL in the real world (Chrome Dev Summit 2015)
Chrome for Developers
#ChromeDevSummit talks are up - W00T! -- Polycast #34
Chrome for Developers
V8 Performance from the Driver's Seat (Chrome Dev Summit 2015)
Chrome for Developers
Quantify and improve real-world RAIL (Chrome Dev Summit 2015)
Chrome for Developers
Owning your performance: RAIL (Chrome Dev Summit 2015)
Chrome for Developers
HTTP/2 101 (Chrome Dev Summit 2015)
Chrome for Developers
Leadership Panel (Chrome Dev Summit 2015)
Chrome for Developers
Build Processes, Totally Tooling Tips (S2, Ep 5)
Chrome for Developers
Accessibility (Chrome Dev Summit 2015)
Chrome for Developers
Binding to Arrays -- Polycasts #35
Chrome for Developers
HTTP2 - HTTP203
Chrome for Developers
Chrome 47: Splash Screens, requestIdleCallback and better desktop notifications (New in Chrome)
Chrome for Developers
Call For Submissions - Supercharged
Chrome for Developers
Cross Device Testing, Totally Tooling Tips (S2 Ep6)
Chrome for Developers
Testing AJAX with Web Component Tester -- Polycasts #37
Chrome for Developers
Slack: Extended Xmas Special - Supercharged
Chrome for Developers
Browser testing with Travis & Sauce Labs -- Polycasts #38
Chrome for Developers
Optimize for production with Vulcanize -- Polycasts #39
Chrome for Developers
Highlights from Chrome Dev Summit 2015
Chrome for Developers
Chrome 48: Custom buttons in notifications, DevTools Security panel, and Presentation mode
Chrome for Developers
Crisper: Protecting your Polymer app with CSP -- Polycasts #40
Chrome for Developers
How do I use Sass with Polymer? #AskPolymer -- Polycasts #41
Chrome for Developers
Colors – DevTools Tonight #0 (Pilot)
Chrome for Developers
More on: UI Design
View skill →Related Reads
📰
📰
📰
📰
Graphic Designing Training in Noida for Beginners & Professionals
Dev.to · Rishu Rajput
React useReducedMotion Hook: Respect prefers-reduced-motion (2026)
Dev.to · reactuse.com
React useReducedMotion Hook: Respect prefers-reduced-motion (2026)
Medium · JavaScript
Good Dashboard Development Is Not Just About Design
Medium · JavaScript
🎓
Tutor Explanation
DeepCamp AI