Video details

Transitioning to modern JavaScript | Houssein Djirdeh & Jason Miller

JavaScript
12.18.2020
English

Houssein Djirdeh & Jason Miller

Over 90% of web traffic comes from browsers that support modern JavaScript, yet most websites ship legacy syntax in order to support a small number of very old browsers.
We now have the ability to deliver modern and legacy code to browsers based on what they support, but this doesn't extend to the large amount of dependency code in our applications. Our dependencies are still published as verbose legacy syntax in order to support the lowest common denominator of browsers today.
This talk explains how legacy dependency code is one of the biggest performance problems on the web today, and what steps we can all take address it.

Transcript

Hi, folks. We're here today to talk about transitioning to modern JavaScript in order to get better value performance out of every byte I'm using. And I'm Jason Hussein. 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. Hussein, your lucky contestant today. Let's be game for question one. We have these two lines of code. What do you think, Hussein? Modern or not? Hmm. I see months is declared using var and I'm pretty sure indexable was introduced in S5. So this is not modern. Correct. There's no inherently new syntax being used here, so it's not modeled on the question to another two lines of code. Is this modern? Along the same lines object got assigned isn't a relatively new syntax or anything. So again, I'm going to go with not modern. You're right. Not modern. All right, last question. This piece of code is a little larger, and I promise it's not a trick question. What do you think, modern or not modern? Taking a look here, I don't think there's anything modern regarding the problem, the syntax. But I do see a variable being declared with const and constant that wasn't introduced until much later. So this has to be modern code, tricky block scoped, let it cost are actually supported in Internet Explorer alone. There's a few bugs to keep in mind, but we can run this code. Wow. OK, I guess I learned something new today. Are you really going to be doing this the whole time, Jason? No, but that kind of begs the question, what exactly do we mean when we say modern code? Well, for starters, modern JavaScript is not its 2015 syntax or its twenty seventeen or even twenty twenty, it's code written syntax that is supported in all modern browsers. Right now, Chrome Edge, Firefox and Safari make up 90 percent of the browser market. And then another five percent of usage comes from browsers based on the same rendering engines, which support roughly the same features. That means 95 percent of visitors to your site are using a fast modern browser. The browsers that make up the majority market share are also evergreen, which means that they get new JavaScript features over time. But how do you write or generate code for a moving target? The easiest way is to look to the features that are already widely supported. First up, classes which have over ninety five percent broad support and functions. Ninety six percent generators also have ninety six percent browser support. Probably the most underused JavaScript feature in particular, this six line generator implements a lazy binary search over the DOM. Yeah, to be honest, I can't think of ever having to write a generator by hand. This might have been my first block scoped constantly. Declarations save us from var hoisting issues and have 95 percent browser support. And like we mentioned earlier, this is actually partially supported in some older browsers. So if we're careful, we could almost call this 97 percent support. The structuring has 94 percent support, risk parameters and rate spread. Also 94 percent object shorthand, which was easy to forget that this wasn't in the language before. It's 2015 now has 95 percent broad support and finally async way, which even though is an E is twenty seventeen feature, has ninety five percent browser support. This is easily my favorite feature of the language or really not a non generators. We're using the term browser support quite often and it's worth clarifying what that means. You can think of browser support as a percentage of global web traffic from browsers that support a given feature. 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 percent of browser traffic. Now, keep in mind, this is even higher for newer sites and apps as an example. The total here would be 97 percent for visitors only from the US. Yeah, so well, 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 we rely pretty heavily on. Translation say I wanted to have a function that returns promises resolving to the number forty two. I might write a little async error function like this one in order to have that code run in the last five percent of browsers that don't support async URL functions, I might Trents pilot to something like this, or at least I might try. In reality, most current tools are going to take my twenty one bites source code insurance pilot to something like five hundred and eighty three bytes of source code, plus a runtime library that the generated code depends on, which actually brings us up another six and a half kilobytes to seven thousand bytes. Obviously the transpired code will take longer to load in the original version. It's larger, but the dramatically larger compiled code also runs significantly slower. Once downloaded, JavaScript code gets compiled to instructions that are executed by a virtual machine, and we can count those to estimate how much work is required to run a given program. So our original async function compiles to 60 to instructions, whereas the transpired output compiles to over eleven hundred. We can also benchmark these side by side in the transposed version executes more than six times slower and this size increase is actually relatively consistent across modern features. Pulling all of those earlier syntax examples into a single module is about seven hundred and eighty bytes when minified using tercer to remove whitespace. If we then transpired that the generated code is six kilobytes, that seven times more and the only benefit we got was that it could run in Internet Explorer eleven if we add another ten kilobytes of poly feels that it depends on theoretically. We also support opera minnies extreme rendering mode, although in reality most transport apps still won't work in that mode because of other limitations. Now we know that the code in the left here works in ninety five percent of browsers and a lot of us are already writing modern code like this. So it's tempting to say, well, that's fine, I'll just ship that modern code that I wrote to browsers. 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. Data from EU archives shows that half of all websites ship almost 90 percent more third party code than first party, right. So I did some really rough napkin calculations, which is always dangerous. 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. That extra bandwidth shipping unnecessarily polyphenol, then transpired code produces something like fifty four thousand metric tons of carbon dioxide into the atmosphere. We'd have to plant 30 million trees to offset that much carbon dioxide. These are obviously super approximate numbers, but they kind of help paint a picture of the scale of the problem. Yeah, and a big part of that scale comes from how prevalent this issue is on NPM. If we take a look at a top thousand front end modules on the NPM registry, the median syntax version is yes, five, the average is also five. In fact, less than twenty five percent contained any syntax newer than IACI. Only 11 percent of module's use the browser field and 90 percent of these point to is five, two percent of module's have a jazz next Manfield and all but one audience, five and only nine percent of module's use the module feel. So why is this a big part of the reason is that package authors can't rely on application bundlers to transport all dependencies to ensure browser support. We estimate that only half of Bill Tool's transpired dependencies at all, which means that modern code published NPM gets bundled as is by the remaining tools, and that unexpectedly breaks browser support for those users. Thing is, package authors came by this, honestly, as modern JavaScript got popular packages still published in S5 because it could be hand tuned where general purpose transpires, Pilar's have to be spec compliant so they don't break valid code. Package authors could transpire to more efficient output by making assumptions specific to their source. Maybe I'm using classes, but I only use the bits that transpire with simple functions and prototypes inheritance as we found ourselves using more and more modern JavaScript syntax. Over time, those possibilities for losse transformation faded away. Thankfully, this is now a solvable problem. Historically, NPM packages declared a minefield, pointing to some common JS code, which, as we know, is generally assumed to be S5. Recently, Noad and a number of bundlers have standardized a new field called exports, 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 note. This means that module's reference by the sports field imply a nude version of at least twelve point eight and No. Twelve point eight supports twenty nineteen. That means that we have to assume module's referenced by the sports field, our modern JavaScript going forward. There's at least two types of NPM packages I expect to see. We have modern only where there's just a sports field and that implies in twenty nineteen package and then packages with both exports and main fields where Maime provides an S5 and common fallback for older environments. What does this all mean? The bottom line is that soon, if you don't transfer pile package exports, there is a high likelihood that you'll ship modern code by accident and maybe shipping modern code is OK. The key is that you define a version of moderate that strikes the right balance between jazz features and browser support. Our research has shown that is twenty seventeen is a sweet spot here, since it has ninety five percent browser support, it offers huge improvement over year five and it's still really close to what we all think of as modern Syntex. Yet we're not saying that you should only write twenty seventeen. It's much the opposite. Yes. Twenty seventeen is a great pile target transpiring the most recent twenty twenty syntax features to twenty seventeen is generally extremely cost effective. Transport outputs like this for a weight loop are the kind of thing we're looking for. The overhead incurred here is only four bytes, but maybe you do need to support Internet Explorer eleven or opera minnies extreme mode. Thankfully, there's a really solid way to support older browsers without impacting newer ones. First generation is twenty seventeen bundles for your application and serve those to modern browsers using script type module. Then generate holifield these five bundles and serve those to legacy browsers using script No-Match. There's no expensive server setup or user agents living required, and it winds up really nicely with our 2017 sweet spot. There are two ways to generate these two sets of jobs, but the first is to compile your application twice, and the second is to compile your application for modern and entranced, file the app. With the first technique we run to build the application as if we were only going to support modern browsers, each source module gets transpired in twenty 2017 by something like Babbo or typescript, then code is Bundall. Then we run a second full build of the application, but this time with the transponder and configured to convert modules to year five and at points. The result is independent sets of bundles that we can load, model and legacy. The second approach flips things around a bit. We run a single bill to generate is twenty seventeen bundles for modern browsers. Then we transfer all those bundles down at the S5 with Polyphemus. One great advantage of this approach is that all code, including any dependencies, can be 20, 17. Since the whole bill seems modern code, all code can't be modern. It'll all be transpired when generating the legacy versions of Thomas. Now, this is all really important because I live in is a tax we and our users don't have to pay. Consider cost as you think about your site's performance. If you need to support a11, be careful not to degrade experience for the majority of visitors in order to get their. We hope we've made the case for why modern JavaScript is so important. We also want to make this as easy as possible to adopt. So we've published a how to article that provides configurations for Poplarville tools to get you started. Yeah, and depending on your setup, this can be as easy as installing a plug in. 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. 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. Go check it out. Let us know what you think. We're also working on something similar for NPM packages, and any feedback for this version helps make that one better to. Thank you, and we hope you enjoyed listening as much as we enjoyed giving this presentation. Thanks for watching.