Return of the script tag

(electronic music) - So, unfortunately this won't be a talk about Blackberries. I've got nothing in there.

No, this will be a talk about how using ES6 modules directly from the browser is going to change the way we start to deliver our code to end users.

It's not going to be a talk about ES6 modules and the format or the history of Javascript modules.

But if you're interested in that, I'd suggest getting a copy of Damon Almond's video, I think from last year's conference.

He did a really great talk around that topic. I want to focus on the actual delivery of our code down to our users and how ES6 modules and using that from the browsers is really going to change things.

But first, let's just, as John said, let's go back a little bit.

Twenty years ago when we wanted to add some Javascript into our Geocities site, we would add-in probably some in-line script, right? There really wasn't much Javascript to do.

There'd be maybe a few quick handlers you know that visitor counter that everyone put on their pages at the bottom.

And if you were really worried about separation concerns maybe you'd go put your code out in a separate file.

We didn't have a lot of third party libraries either. They tended to come up slowly and as they did the solution was simple: you put another script tag on your page just before yours.

Except that we kept adding code and code and things got slightly more complex, and eventually our single Javascript file just got too much to maintain.

So we broke that apart and we'll break it apart into a few smaller files to manage that better. And this was working fine until we got this explosion of frameworks and libraries and Javascript tracking code. And soon we end up with a great big wall of script tags. We have all our vendor tags, we have jQuery, we have our plug-ins, we have Angular, we have all of our code in there as well.

And it started to show some issues with the way we were managing it.

The first of which was ordering.

The ordering of the script tags became incredibly important. Obviously you needed to include jQuery before you include any plug-ins.

How about your code? How can you figure out the order that your code needs to go in.

What dependencies does each of your files have? We also saw a lot of pollution of the global namespace. Things like, well the only way we could share any data between our files was to put something on the global namespace for the next file to pick up and use.

And so libraries started fighting over variables. And we saw a concurrent connection in that we can only make six requests from the browser to a server at any time.

That included CSS, HTML, Javascript.

So we started to see our files getting blocked and our site performance getting impacted.

Until we came up with the great idea of server-side bundling.

So in this way, we would just concat all our files together that we needed to send down on the server and send it down to the user as one file.

We've now solved the problem of the user's browser not being able to request too many files at once, and we've managed to keep our code structure nice on the server.

We can still have some decent architecture up on the server and manage our code better. But we still have the problem of ordering.

We still have to have probably like a manifest up there and figure out what order things need to go in. Until Webpack came along and Browserify and now Rollup. And it has revolutionised how we do bundling on the server. And so now we can just point to a single file. And that file can declare any dependencies it has, and those files can declare any dependencies they have, and so Webpack can just walk through and bundle it all up together.

We don't have to list a manifest of those files any more. It'll include only what it needs and it'll figure out ordering and in many cases, will even resolve circular dependencies if that's a thing that's important.

Except that maybe Webpack made this a little too easy. You know, a quick MPM instal Angular or MPM instal RXJS and we've added a few hundred kilobytes to our own bundle.

We've really optimised the developer experience so it's really easy to add in our dependencies and easy to forget exactly what that's going to mean for our site's performance and to our end user and obviously our bundle size. And it got me wondering, just how much of the bundle is actually my code.

So I did a few quick stats on my code on some Wordpack projects and it turns out on average 4.5% of that code is mine.

It's not a lot.

So 95% of this code is third party dependencies. Dependencies that we're sending down every time I make one line of code change on the server because it's probably all in one big bundle. Dependencies that are very infrequently going to change. And now with continuous delivery becoming a big thing, multiple times per day my team are making changes to the bundle and the bundle's getting updated and the user's browser cache is invalid and they're having to download that bundle again. That bundle, where 95% or more, definitely of the code is all the same.

It's pretty inefficient.

But all is not lost.

Well actually, let me just state this, I want to reiterate this point: server side bundling is really negating the benefits of browser caching.

But all is not lost and the solution is use more script tags, create more bundles. So, or chunks, whatever you want them to be. A common approach to this is a vendor bundle. So put all of your third party dependencies together in a single bundle.

Which is a good approach and means that we can cache that for longer and our code, that maybe changes more frequently, we can send that down more frequently and have a shorter cache on.

Except there's a better approach to managing our third party dependencies, and where possible, we should be looking at Content Delivery Networks, CDN's, like our friends at Section.IO.

Go talk to them afterwards and find out about it. CDN's allow you to share a reference to a single file across multiple sites.

So we can have React 1511 on my site and if you're pointing to React 1511 from the same Section.IO CDN, the browser's going to be able to cache that and reuse that same cache across all the sites that need it. So we're in a better place now.

We've got multiple bundles being created from the server so the browser doesn't have to download too many files, but we can cache some of them for a little bit longer. And we're using CDN's to manage our dependencies. But there's a better way.

There's still a lot of complexity in these build systems, so managing these bundles and figuring out where your files are going to go if you want to add a new file, which bundle should it be in? There's still a fair bit of complexity in doing this. And I think ES6 modules and using them natively from the browser is really going to change this for us.

First, ES6 modules, just a real quick recap. If you've used Node JS, you're familiar with the common JS syntax of requiring in your dependencies.

And you're probably pretty familiar with the ES6 module format, it's been around for a while, it's been around since 2014. But instead of requiring things, we just import them. If you want to make something public from an ES6 module, you export it.

You can export name dot items or you can export a single default thing.

And when you want to import things from a module, you import the named items.

You can import all the named things, you can import some of the named things, you can rename some of the things if they don't suit you or they have a clash. You can import everything into a single variable. So while ES6 modules as a format has been around quite a while, being able to reference them directly from your browser is very new, in fact support's really only landed for this at the start of this year.

And using it is very simple.

Essentially we just need to reference our module from a script tag.

But take note, we've got to change the type to module, rather than Javascript, and we'll come back to why later.

You can use modules inline in script or as an external file reference.

And these dinosaurs are telling us it's time for a demo. I thought because we had so many dinosaurs this morning in Aaron's great demo, it feels like there's a theme going on here.

Okay, so let's have a look quickly creating ES6 modules. We're going to create the simplest possible module. And this module is just going to export a string constant called "ES6 Modules." In this case, we've exported at the end of the file, which is pretty common. You can also export things inline with ES6 modules. You can have a number of these exports throughout the file.

I'm going to create another module that's just going to import our stream constant. And we're going to create a "Say Hi" function that will just use that string constant and give us another string back.

And finally we'll just export that "Say Hi" function. And lastly, we're going to create a page that we can use to consume our modules and we'll look at what happens in the browser. So first thing we're going to need, we're going to take the "Say Hi" function and just put it out into a

.

So we'll create a result

.

And then create an inline module of script type modules. And import our main file.

Finally we're going to find our result

and set it's inner text property.

Cool, I'm going to start up a quick server. And you can see we've got what we expected. Our "Say Hi" is imported a module.

What I want to have a look at here is the requests that are coming through.

So if we do this again, you can see the browser's gone and requested both of our modules directly.

We've only imported main.js and it's gone through and found module for us.

If I do a soft refresh, what we can see here is that the browser doesn't need to download it again. In this case the browser's sending e-tag headers back up and the server's saying these files haven't changed.

And here's where the real power of ES6 modules is going to come in.

Rather than having to download the whole bundle again, the server is going to be able to say hey this one file's changed, you can just download this one file and get what you need. Everything else can stay the same.

So we're going to really take advantage of the browser's cache property.

So today, we can use it.

It's supported in all the major browsers, except for I.E., which is in end of life, and not kidding you, ES6 features.

And it's supported in most of the mobile browsers except for Opera Mini and a few others.

But not every user's going to be on the latest version of the browser.

So we're going to have to have support for our users that haven't quite finished their updates. And that's where "nomodule" comes in.

So, "nomodule" attribute tells a new module-aware browser to simply ignore the tag. So in this case, let's imagine we're using the latest version of Chrome.

It's going to see the first script tag and it's going to say, "Cool, type module, I know what that is, I'm going to read this file in as a type of module." I'm going to respect any import statements and export statements.

I'll see the next script tag and I'll see the "nomodule" attribute and that tells me to ignore this.

So this can be my legacy bundle that I was serving before. Newer browsers, they're just going to not download it at all.

If we're on an older version of a browser it's going to do the opposite.

It's going to see script type moduling go "I don't know what that means.

I only respect script type Javascript." So it won't load in our first tag.

It'll see the next tag and think "I don't know what nomodule means either, I'll ignore that attribute.

I'll load in that script happily.

I don't need to be told that to type Javascript because scripts are by default typed Javascript." And so it'll load in our legacy bundle quite happily. What this means is that for, at least for the foreseeable future, we're going to have to keep our build systems going, we're going to have to keep bundling things for a while and serving them, At least until these browsers become really mainstream. We see most of our users upgrading.

So now we're in a better place with how we're serving our code.

We no longer have to do bundling essentially on the server, we can just reference our source files almost directly. What about our third party dependencies though? We could just serve a "nomodules" folder.

That's not really the best way to deal with it. Ideally we want to come back to those CDN's in what we're using.

And I wanted to point out "unpackage." "Unpackage" is a special kind of CDN.

"Unpackage" brands itself as a CDN on everything from MPM. What that means is that "unpackage" allows you to reference any file in any package directly from the browser or from the URL.

You can think of it as fetching that package, unpacking it all, pulling out the file, and serving it just for you.

There's a few different ways you can reference packages through "unpackage." And I will show you just a few now.

So we're going to go to unpkg.com and reference rxjs. That's going to take us to the latest version of the entry point for the latest version of that package. You can see the latest version there is 6.2.2. We can also ask for specific versions using MPM style sim versions.

We can say I want the latest revision of version 5. And it'll give us you see.

We had a different entry point back then.

We can also ask for things like specific files. So it's showing me what package JSON looks like. And we can do a directory listing and say just show me everything that's in the package if I want to browse around.

This looks cool, but straight out of the bat we can't use this directly from the browser. Let's go back to that entry point.

First thing you're going to note is that it's a "nopackage". It has require statements all the way through it. It's common JS format and this makes sense because it's a "nopackage".

It's been served from NPM, the node package manager. Fortunately a lot of package authors are now publishing both common JS formats.

and ES6 formats of their package.

And there's an unofficial standard at the moment to define the entry point for the ES6 version of your package.

If we have a look in package.json, typically the entry point is described by the main attribute here, but there's also a module property which shows us the entry point for ES6 modules. So if we plug this guy in here, now this is something we can use directly from the browser. This is a native ES6 modules version.

Unpkg.com takes us a step further and makes it easier to get here by just supplying query string module attribute. And you'll see that unpkg also rewrites a few of the internal URLs of the package to include the query string, so that if this package depends on another package, it's going to try and fetch the ES6 module's version of it. Oh, that was the dinosaur to tell us we're doing another demo.

So we can use these directly from the browser. It does depend on the package.

It's up to the package authors whether they are publishing ES6 versions.

Vue has an ES6 modules version.

RXJS does and so does D3, there'd be a lot more out there. What I was going to do now, another demo, more dinosaurs is quickly throw this into our sample app.

So let's try and import RXJS.

I'm not going to import all of RXJS.

I'm just going to import fromEvent, which is a observable factory.

So it creates an observable stream from DOM events. In our case, we're going to create an observable stream from some button click events. So let's add a button in.

And we'll just create that stream from our button, from the click events for that button, and we're going to subscribe to that.

Actually sorry, I'm going to put that underneath here. So we'll subscribe to that stream so every time we get a click event happen from that button we're just going to update our result text. If we go back to our demo, we'll refresh.

You can see we've got a "Say Hello" button and we're still loading.

We'll check that this works, cool.

"Say Hello" is loading hello.

But you can see we've done 106 requests here. So RXJS has gone through and pulled down the internet. (audience laughter) Fortunately, as you may have played around with when you're doing webpack or you are bundling things up, which packages like RXJS or lodash you can pull out the specific features you want. And so here we've downloaded the entire RXJS package, which we don't need all of these.

We're not using most of these files.

So if we come here and rather than referencing the entry point, we're going to de-blink into just the fromEvent module. And I'll refresh this guy and we're down to 25 requests, 26. Still going to work, still going to be the same thing. But we've only pulled down the files we need. Now the real benefit comes when I do again a soft refresh. Look at that, all of this is from the browser cache. This is from unpkg.com, it's a CDN.

If you decided to use RXJS version 6.2.2 from unpkg on your site, you'd have the same thing. You wouldn't need to download it again.

The browser can store this for a long time. Packages are essentially supposed to be immutable. We're never going to want to change the contents inside this version of the package. So you can see if we have a look here at a request, one of the responses from unpkg is to cache it for a year. So we can put really long cache lives on these files as opposed to our own files which we might want to have much shorter cache lives on. Coming back to the request limit.

We had a hard limit of six files that we could make. And that's really why we got into bundling in the first place.

So we could serve down one asset at a time. Well it turns out that HTTP Two has largely made this redundant.

So with HTTP Two they've introduced a bunch of new performance improvements.

One of the most important one's of this is connection multiplexing.

What this means is that within a single connection we can make multiple requests for different files. So we can have multiple requests and responses happening at once.

So, where we used to have to have a single request for a single file over one connection... Let me say that again.

So where we used to have one connection per request, now we can have one connection per site.

And that's making all the requests over it. We're not going to be tied down.

We can also do things like ordering in here. So we can save preferences for how we want to receive those files.

The binary framing layer also condenses down the headers that are sent.

It's no longer plain text, it's in binary form, which means it's much, much smaller to receive that. You can effectively think of HTTP Two as doing bundling on the fly.

So in conclusion, the future is moving toward more script tags and more imports directly from the browser, so that we can really optimise use of the browser's cache. It's using our code, writing our code as ES6 modules. And loading in dependencies from CDNs like unpkg.com or section.io.

Thanks.

(audience applause) (techno music)