When we build sites that rely heavily on JavaScript, it’s not always easy to see the cost of the content we send. In this article, if you want your website to load and interact quickly on mobile devices, I’ll show you why some rules can help.

tl; Dr: Less code = less parsing/compiling + less transmission + less decompression

network

When most developers think about the cost of JavaScript, they think about the download and execution costs. The longer it takes to send more bytes of JavaScript over the line, the slower the user’s connection will be.

This can be a problem, even in developed countries, because the type of Internet connection a user has available may not actually be 3G, 4G or WiFi. You might be using Wifi at a coffee shop, but you’re connecting to a 2G cellular hotspot.

You can reduce the cost of transporting JavaScript over a network by:

  • Send only the code the user needs. This is where code splitting comes in.

  • Compression code (Uglify for ES515, Babel-Minify or Uglify-ES for ES2015)

  • Highly compressed (using Brotli ~ q11, Zopfli or gzip). Brotli has a compression ratio that exceeds Gzip. It saves CertSimple 17% in JS compression bytes and LinkedIn 4% in load time.

  • Remove unused code. By DevTools code coverage. For stripped code, check out Tree Shaking, the closure compiler’s advanced optimization and fine-tuning library plug-ins like the Lodash-Babel-Plugin or WebPack’s ContextReplacementPlugin like the moment.js library. Use babel-preset-env and Browserlist to avoid the translation features that already exist in modern browsers. Senior developers may find that a careful analysis of Webpack bundles can help identify and trim unwanted dependencies.

  • Use HTTP caching to minimize network hops. Determine the optimal life cycle (max-age) of the script and provide token validation (ETag) to avoid transferring unchanged bytes. The Service Worker cache makes your application network resilient, giving you quick access to V8’s code cache and other features. Learn about long-term caching of file name hashes.

Parse/compile

Once the JS is downloaded, one of the biggest costs of JavaScript is the time it takes the JS engine to parse/compile the code. In Chrome DevTools, parsing and compiling are part of the yellow “script” time in the “Performance” panel.

The bottom-up /Call Tree allows you to see the exact parse/compile timing:

Chrome DevTools > Performance > Bottom up. By enabling runtime call statistics for V8, we can see the time spent in the analysis and compilation phases

But why is this a problem?

Taking a long time to parse/compile code can significantly delay user interaction with the site. The more JavaScript you send, the longer it takes to parse and compile before the site interacts.

For the same number of bytes, JavaScript is more expensive for the browser to process than an image or Web font of the same size. – Tom Dale

Compared to JavaScript, when dealing with images of equivalent size (which involves a lot of images that still need to be decoded! There is a lot of overhead involved, but on the average mobile device, JS is more likely to have a negative impact on the interactivity of the page.

There is a big difference in JavaScript and image byte overhead. Images generally do not block the main thread and do not prevent interfaces from interacting during decoding and rasterization. However, JS delays interactivity due to the cost of parsing, compiling, and execution.

Context matters when we’re talking about parsing and compiling being slow – we’re talking about regular phones here. Refers to a phone with a slow CPU and GPU that most users use, no L2 / L3 cache, and maybe even little memory.

Network capabilities and device capabilities do not always match. Users using fiber-optic connections don’t necessarily have the best CPU to parse and execute the JavaScript sent to their devices. The reverse is also true. A bad Internet connection, but with a fast CPU. — Kristofer Baxter, LinkedIn

In JavaScript launch performance, I noticed the overhead of parsing about 1MB of (simple) JavaScript that has been unzipped on both low – and high-end hardware. The time to parse/compile code varies by a factor of 2-5 between the fastest phone on the market and a regular phone.

Parsing time for a 1MB JavaScript package (about 250KB compressed by GZIP) on different categories of desktop and mobile devices. When looking at the parsing overhead, the unzipped data should take into account that approximately 250KB of gZIP compression space will be freed when approximately 1MB of code is unzipped

How about real world sites like CNN.com. On a high-end iPhone 8, parsing/compiling CNN’s JS takes about 4 seconds, compared to 13 seconds on a regular phone (Moto G4). This can significantly affect the speed at which users can fully interact with the site.

Comparison of parse time performance between Apple’s A11 Bionic chip and the Snapdragon 617 in very common Android hardware.

This highlights the importance of testing common hardware like the Moto G4, not just the phone in your pocket. However, the context is important: optimize the devices and network conditions that your users own.

Analytics can provide insight into the categories of mobile devices on which your real users visit your site. This can provide an opportunity to understand the real CPU/GPU limitations they are using.

Are we really sending too much JavaScript? Wrong, this is very possible 🙂

Using the HTTP Archive (up to about 500,000 sites) to analyze the state of JavaScript on mobile devices, we can see that 50% of sites take 14 seconds to get an interaction. These sites take up to four seconds just to parse and compile the JS.

In the time it takes to get and process JS and other resources, it’s perhaps not surprising that users might wait a while because the web page feels ready to use. But we can definitely do better here.

Removing unimportant JavaScript from a web page can reduce transfer time, CPU-intensive parsing and compilation, and potential memory overhead. This also helps make your web pages interact faster.

The execution time

It’s not just parsing and compiling, but it can be an additional overhead. JavaScript execution (parsing/compiling run code once) is one of the actions that happens on the main thread. Long execution times can also push out the amount of time users can interact with the site.

If the script takes more than 50ms to execute, the interaction time will be downloaded and the time taken to compile and execute JS will be delayed – Alex Russell

To solve this problem, JavaScript benefits from small chunks to avoid locking the main thread. Explore whether you can reduce the amount of work that is going on during execution.

Patterns to reduce JavaScript sending overhead

There are patterns like route-based Chunking or PRPL that can help when you’re trying to keep JavaScript parsing/compiling and network transfer times slow.

PRPL is a pattern that optimizes interactivity through code segmentation and caching:

Let’s look at the impact it can have. We used RUNTIME call statistics from V8 to analyze load times for popular mobile websites and Progressive Web Apps. As we can see, parsing time (shown in orange) is the larger part of the time spent by many of these sites:

Wego is a site that uses PRPL and manages to keep route resolution times low and interactions very fast. Many of the other sites above use code decomposition and performance budgets to reduce JS overhead.

Other costs

JavaScript can affect page performance in other ways:

  • Memory. Pages may often stall or pause due to GC (garbage collection). JS execution is paused when the browser recollects memory, so browsers that do a lot of garbage collection can pause execution more often than we’d like. Avoid memory leaks and frequent GC pauses to keep the page flowing.

  • At run time, long-running JavaScript can prevent the main thread from causing unresponsive pages. Breaking the work into smaller pieces (using requestAnimationFrame () or programmatically using requestIdleCallback ()) minimizes responsiveness problems.

Progressive guidance

Many web sites optimize for content visibility at the expense of interactivity. To get a quick home page with large JavaScript packages, developers sometimes use server-side rendering; It is then “upgraded” to attach event handlers when the JavaScript finally gets it.

Be careful – it has its own cost. 1) This usually sends a larger HTML response, which delays interactivity, and 2) it leaves the user in a surreal valley where half the actual functionality is impossible to interact with until the JavaScript is running.

Incremental bootstrapping may be a better approach. Send a minimal functional page (consisting of the HTML/JS/CSS required for the current route). As more resources arrive, the application can lazily load and unlock more features.

Progressive guidance

Loading code based on what you see is the Holy grail. PRPL and progressive boot are patterns that can help achieve this.

conclusion

Transmission size is critical for low-end networks. Parsing time is important for CPU-constrained devices. Keep the two offset.

The team found success in adopting a strict performance budget to keep JavaScript transfer and parse/compile times short. See Alex Russell’s “Can You Handle it? : Real-world Web Performance Budgeting “for guidance on mobile budgeting.

If you’re building a website for mobile devices, develop on representative hardware as much as possible, keep JavaScript analysis/build times low, and use a performance budget to ensure that your team pays attention to its JavaScript costs.

The original address