Transitioning to modern JavaScript

Dec 18, 2020 02:00 · 2544 words · 12 minute read

(upbeat music) - Hi folks we’re here today to talk about transitioning to modern JavaScript, in order to get better value performance out of every bite.

00:12 - I’m Houssein. - And I’m Jason, Houssein, are you ready? - Ready for what? - Ready to play Is It Modern, the show where I quiz contestants on whether a piece of JavaScript is modern or not modern.

00:23 - Hussein, you’re our lucky contestant today, let’s begin.

00:27 - For question one, we have these two lines of code.

00:29 - What do you think Houssein, modern or not modern? - I, see months is declared using var and I’m pretty sure indexOf was introduced in ES5, so this is not modern.

00:41 - - Correct, there’s no inherently new syntax being used here, so it’s not modern, on to question two.

00:47 - Another two lines of code, is this modern? - Along the same lines, object. assign, isn’t a relatively new syntax or anything.

00:54 - So again, I’m going to go with not modern. - You’re right, not modern, all right, last question.

01:01 - This piece of code is a little larger and I promise, it’s not a trick question.

01:04 - What do you think, modern or not modern? - Taking a look here, I don’t think there’s anything modern regarding the promise syntax, but I do see a variable being declared with const and constant let wasn’t introduced until much later, so this has to be modern code.

01:20 - - [Man] Oh, tricky block scope, let and const are actually supported in Internet Explorer 11.

01:24 - There’s a few bugs to keep in mind, but we can run this code.

01:27 - - Wow, okay, I guess I learned something new today.

01:31 - Are you really going to be doing this the whole time, Jason? (laughing) - No.

01:35 - - But that kind of begs the question. What exactly do we mean when we say modern code? Well, for starters, modern JavaScript is not ES2015 syntax or ES2017, or even ES2020.

01:50 - it’s code written syntax that is supported in all modern browsers.

01:55 - Right now, Chrome, Edge, Firefox and Safari, make up 90% of the browser market.

02:02 - And then another 5% of usage comes from browsers based on the same rendering engines, which support roughly the same features.

02:10 - That means 95% of visitors to your site are using a fast, modern browser.

02:17 - The browsers that make up majority market share, are also Evergreen, which means that they get new JavaScript features over time.

02:26 - But how do we write or generate code for a moving target? The easiest way is to look to the features that are already widely supported.

02:34 - First up classes, which have over 95% browser support.

02:38 - Arrow functions, 96%, generators also have 96% browser support.

02:44 - - [Man] Probably the most underused JavaScript feature, in particular, this six line generator implements a lazy binary search over the DOM.

02:52 - - Yeah, to be honest, I can’t think of ever having to write a generator by hand.

02:56 - - This might have been my first. - [Man 2] Block scoped, constant let declarations, save us from our hosting issues and have 95% browser support.

03:05 - And like we mentioned earlier, this is actually partially supported in some older browsers.

03:10 - So if we’re careful, we can almost call this 97% support.

03:15 - De-structuring has 94% support rest parameters and rest spread, also 94% support.

03:22 - Object shorthand, which was easy to forget that this wasn’t in the language before ES2015, now has 95% browser support.

03:31 - And finally async await, which even though it was an ES2017 2017 feature, has 95% browser support.

03:38 - - This is easily my favorite feature of the language.

03:41 - - Oh really, not non generators? We’re using a term browser support quite often, and it’s worth clarifying what that means.

03:50 - You can think of browser support as a percentage of global web traffic from browsers that support a given feature.

03:57 - To get a full picture of modern JavaScript browser support, we can take the lowest common denominator of the features we just saw, and we can see that all these features are supported in 94% of browser traffic.

04:10 - Now keep in mind, this is even higher for newer sites and apps.

04:14 - As an example, the total here would be 97% for visitors only from the US.

04:21 - - Yeah, so while it’s actually pretty useful to have a rough idea of the browser support for language features, you’re going to use, most of us aren’t writing code that gets delivered totally unmodified, to run in browsers.

04:32 - We rely pretty heavily on transpilation. Say I wanted to have a function that returns promises resolving to the number 42.

04:39 - I might write a little async arrow function like this one.

04:42 - In order to have that code run in the last 5% of browsers that don’t support async arrow functions, I might transpile it to something like this, or at least I might try.

04:52 - In reality, most current tools are going to take my 21 bytes of source code and transpile it to something like 583 bites of source code.

05:00 - Plus a runtime library that the generated code depends on, which actually brings us up another six and a half kilobytes, to 7,000 bytes.

05:07 - Obviously the transpiled code will take longer to load than the original version, it’s larger, but the dramatically larger compiled code also runs significantly slower once downloaded.

05:17 - JavaScript code gets compiled to instructions that are executed by a virtual machine.

05:21 - And we can count those to estimate how much work is required to run a given program.

05:25 - So our original async function compiles to 62 instructions.

05:29 - Whereas the transpiled output compiles to over 1100.

05:33 - We can also benchmark these side by side and the transparent version executes more than six times slower.

05:39 - And this size increase is actually relatively consistent across modern features.

05:44 - Pulling all of those earlier syntax examples into a single module, is about 780 bytes when minified using terser, to remove white space.

05:51 - If we then transpiled that, the generated code is six kilobytes, that’s seven more and the only benefit we got was that it could run in Internet Explorer 11, if we add another 10 kilobytes of polyfills, that it depends on.

06:05 - Theoretically, we also support opera mini’s extreme rendering mode.

06:08 - Although in reality, most transpiled apps still won’t work in that mode, because of other limitations.

06:14 - Now we know that the code in the left here works in 95% of browsers, and a lot of us are already writing modern code like this.

06:22 - So it’s tempting to say, well, that’s fine.

06:24 - I’ll just ship that modern code that I wrote, to browsers.

06:28 - - Well, you can think that you can change all the code in your application, but if we actually take a step back and look at what makes up our website code, we’ll find that the majority of our code base comes from third party dependencies.

06:43 - Daya from ACB archive, shows that half of all websites ship almost 90% more third-party code, than first party.

06:51 - - Right, so I did some really rough napkin calculations, which is always dangerous.

06:56 - But working back from global web traffic, we can estimate that the overhead of shipping legacy JavaScript, accounts for around 80 petabytes per month of internet bandwidth.

07:05 - That extra bandwidth shipping unnecessarily poly-filled, and transpiled code, produces something like 54,000 metric tons of carbon dioxide into the atmosphere.

07:14 - We’d have to plant 30 million trees, to offset that much carbon dioxide.

07:19 - These are obviously super approximate numbers, but they kind of help paint a picture of the scale of the problem.

07:24 - - Yeah, and a big part of that scale comes from how prevailing this issue is on NPM.

07:30 - If we take a look at a top thousand front end modules on the MPM registry, the median syntax version is ES5.

07:37 - The average is also ES5. In fact, less than 25% contain any syntax newer than ES5.

07:46 - only 11% of modules use the browser field. And 90% of these point to ES5.

07:52 - 2% of modules have a JS next main field, and all but one are ES5.

07:58 - And only 9% of modules use the module field.

08:02 - - So why is this? A big part of the reason is that, package offers can’t rely on application bundlers to transpile dependencies, to ensure browser support.

08:12 - We estimate that only half of built tools transpile dependencies at all, which means that modern code published NPM gets bundled as is, by the remaining tools.

08:20 - And that unexpectedly breaks browser support for those users.

08:25 - Thing is, package authors came by this honestly.

08:28 - As modern JavaScript got popular, packages still published in ES5, because it could be hand tuned, where general purpose transpilers have to be spec compliant, so they don’t break valid code, package authors could transpile to more efficient output by making assumptions specific to their source.

08:43 - Maybe I’m using classes, but I only use the bits that transpile to simple functions and prototypal inheritance.

08:49 - As we found ourselves using more and more modern JavaScript syntax over time, those possibilities, for lossy transpilstion, faded away.

08:58 - Thankfully, this is now a solvable problem.

09:01 - Historically NPM packages declared a main field, pointing to some common JS code, which as we know, is generally assumed to be ES5.

09:10 - Recently node, and a number of bundlers, have standardized a new field called exports.

09:16 - It’s great, does a lot of things, but it has one very important attribute, which is that it’s ignored by older versions of node.

09:23 - This means that modules referenced by the exports field imply a node version of at least 12. 8, and node 12. 8 supports ES2019.

09:32 - That means that we have to assume, modules referenced by the exports field are modern JavaScript.

09:39 - Going forward, there’s at least two types of NPM packages I expect to see.

09:44 - We have modern only, where there’s just an exports field and that implies an ES2019 package, and then packages with both exports and main fields, where main provides an ES5 and common JS fallback, for older environments.

09:58 - What does this all mean? The bottom line is that soon, if you don’t transpile package exports, there’s a high likelihood that you’ll ship modern code by accident.

10:08 - - And maybe shipping modern code is okay. The key is, that you define a version of modern that strikes the right balance between JS features and browser support.

10:19 - Our research has shown that EAS2017, is a sweet spot here, since it has 95% browser support, offers huge improvements over ES5, and it’s still really close to what we all think of as modern Syntax.

10:34 - - Yeah, we’re not saying that you should only write ES2017, it’s much the opposite.

10:38 - ES2017 is a great transpile target. Transpiling the most recent ES2020 syntax features to ES2017, is generally extremely cost-effective.

10:48 - Transpiled outputs, like this for await loop, are the kind of thing we’re looking for.

10:53 - The overhead incurred here is only four bytes.

10:56 - - But maybe you do need to support Internet Explorer 11.

10:59 - Or Opera Mini’s extreme mode. Thankfully, there’s a really solid way to support older browsers without impacting newer ones.

11:08 - First generate ES2017 bundles for your application and serve those to modern browsers, using script type module.

11:16 - Then generate polyfill ES5 bundles and serve those to legacy browsers using script nomodule.

11:23 - There’s no expensive server setup or user agent sniffing required, and it lines up really nicely with our ES2017 sweet spot.

11:31 - There are two ways to generate these two sets of JavaScript files.

11:34 - The first, is to compile your application twice.

11:38 - And the second is to compile your application for modern, and then transpile the output.

11:44 - With the first technique, we want to build the application, as if we were only going to support modern browsers.

11:50 - Each source module gets transpiled to ES2017, by something like Babel or TypeScript, then code is bundled.

11:58 - Then we run a second full build of the application, but this time with the transpiler configured to convert modules to ES5 add polyfills.

12:07 - The result is independent sets of JavaScript bundles that we can load modern and legacy browsers.

12:13 - The second approach flips things around a bit.

12:16 - We run a single build to generate ES2017 bundles for modern browsers, then we transpile those bundles down to ES5, with polyfills.

12:26 - One great advantage of this approach, is that all code, including any dependencies, can be ES2017.

12:32 - Since the whole build assumes modern code, all code can be modern.

12:38 - It’ll all be transpired when generating the legacy versions of bundles.

12:43 - Now, this is all really important because IE 11 is a tax we and our users don’t have to pay.

12:50 - Consider the cost, as you think about your site’s performance.

12:54 - If you need to support IE 11, be careful not to degrade the experience for the majority of visitors, in order to get there.

13:02 - We hope we’ve made the case for why modern JavaScript is so important.

13:07 - We also want to make this as easy as possible to adopt.

13:10 - So we’ve published a how to article, that provides configurations for popular build tools to get you started.

13:16 - - Yeah, and depending on your setup, this can be as easy as installing a plugin.

13:21 - If you’re curious, what kind of impact turning on modern JavaScript would have on your website, today we’re launching a tool for that.

13:30 - Load the tool, enter the URL of a page and it will analyze the JavaScript of that page, in order to calculate how much smaller and faster that page would be, if it leveraged modern code.

13:42 - Go check it out, let us know what you think.

13:44 - We’re also working on something similar for NPM packages and any feedback for this version, helps make that one better too.

13:51 - - Thank you, and we hope you enjoyed listening, as much as we enjoyed giving this presentation.

13:55 - - Thanks for watching. .