Skip to main content
Dublin Library

The Publishing Project

Further thoughts on performance

 

In the time I've been researching performance I've become even more convinced that it's not just an issue with Javascript, CSS, images, HTML or what framework you're using, although any one of those items, if not used carefully, can have a negative impact on your users' experience. We have to think deeper than just the tools and ask ourselves why are we using these technologies and how to leverage them to improve the user experience of our sites and applications. ## What technology stack to use I will not preach one technology over another one, particularly frameworks. I have an opinion and, if you know me, you know what it is. However, there are things to consider when starting a new project or when rearchitecting for performance. Don’t pay much attention to the latest and greatest tool. As long as you get the results you want and are happy maintaining the tool, you’re doing just fine. The only exception might be a bundler that provides code splitting, tree shaking, and a good plugin ecosystem. Besides images, JavaScript is one of the largest components. Staying within the boundaries of our budget that sill contains the critical-path HTML/CSS/JavaScript, all the resources, and the app logic necessary for the route the bundle will handle we need to be extra careful when creating the bundle and the costs (network, transfer, parse/compile and runtime) of the components that you choose to use Not every project needs a framework, and not every part of a SPA needs to load the framework. Be deliberate in your choices. Be thorough in evaluating third-party JS regarding these areas: * features * accessibility * stability * performance * package ecosystem * community * learning curve * documentation * tooling * track record * team * compatibility * security Pick your framework battles wisely. Make sure that your chosen framework has all the feature you need for your project and understand the way your framework works. If you make multiple calls to an API or use multiple APIs in your app, they might become a performance bottleneck. Explore ways to reduce your dependencies on foreign APIs **Depending on how much dynamic data you may be able to move your content to a static site and serve it through a content delivery network.** Make sure that your static site generator has plugins to do what you need to increase performance (e.g. image optimization in terms of formats, compression and resizing at the edge), support for servers workers and other tasks you've identified as necessary ## Optimize your build Run an inventory on all of your assets (JavaScript, images, fonts, third-party scripts, “expensive” modules on the page), and break them down in groups. Break the content into groups: * The basic core experience (fully accessible core content for legacy browsers) * An enhanced, full experience for capable browsers * Whatever extras assets that are nice to have and, thus, that can be lazy-loaded ### Define what "cutting-the-mustard" means for your site Either use feature detection to send features only to those browsers that support them or use ES2015 modules to break the difference between core and enhanced tools. If you haven't seen them, feature detection queries the Browser's Javascript engine if it supports a given feature. ```js if ('querySelector' in document) { console.log('Yay!') } else { console.log('Boo') } ``` We can have multiple items in the if statement to give us finer control over how we cut the mustard for the enhanced experience. This example uses the `and` logical operator to only return true if both arguments are true, meaning that the browser must support both features to return true. ```js if (('querySelector' in document) && ('serviceWorker' in navigator)) { console.log('Yay!') } else { console.log('Boo') } ``` ### Modules in browsers Another, coarser, way to cut the mustard is to use ES2016 ES Modules and the `type="module` attribute in the `script` tag for loading JavaScript. Modern browsers will interpret the script as a JavaScript module and run it as expected, while legacy browsers won't recognize it and hence ignore it > Be aware that cheap Android phones will cut the mustard despite their limited memory and CPU capabilities, so consider feature detect [Device Memory API](https://developers.google.com/web/updates/2017/12/device-memory) and then decide on what features you send the user, using feature detection to make sure they are supported). > > ```js > const memory = navigator.deviceMemory > console.log ("This device has at least " + memory + "GiB of RAM.") > ``` Remember that parsing JavaScript is expensive, so keep it small. Look for modules and techniques to speed up the initial rendering time (loading, parsing, and rendering). Be mindful that this can take significantly longer in low-end mobile devices like those used in emerging markets. ### Reducing the size of your payload Use tree-shaking, scope hoisting, and code-splitting to reduce payloads where appropriate. I'm not against tree shaking and minimizing, as long as we can understand the code when we expand the minimized files. * Tree-shaking is a way to clean up your build process by only including code that is actually used in production * Code-splitting splits your code base into “chunks” that are loaded on demand * Scope hoisting detects where import chaining can be flattened and converted into one inlined function without compromising the code. Make use of these techniques via your bundler of choice. ### Figure out if you can you offload JavaScript Offloading expensive computations to a worker (running in a separate thread) or to WebAssembly improve your site's performance and free the main thread for UI work and user interaction. [WebAssembly](https://webassembly.org/) provides several benefits when working with Javascript for performance * Access to libraries that are not available in Javascript or that provide better performance than built-in libraries in current browsers. (See [Emscripting a C library to Wasm](https://developers.google.com/web/updates/2018/03/emscripting-a-c-library)) * A mechanism to improve your application's hot path code, those pieces of script that slow down your application. In [Replacing a hot path in your app's JavaScript with WebAssembly](https://developers.google.com/web/updates/2019/02/hotpath-with-wasm) we get a second way to work around * Integrate modules written in other languages into your web projects. [Emscripten and NPM](https://developers.google.com/web/updates/2019/01/emscripten-npm) give an example of how you can do this ### Trim unused CSS/JavaScript: Differential loading and using smaller packages You can also serve different code to browsers based on the features that they support (called differential loading). This is different from using `type="module"` on script tags. Use [`babel-preset-env`](https://babeljs.io/docs/en/babel-preset-env) to only transpile ES2015+ features unsupported by the modern browsers you are targeting. Then set up two builds, one in ES6 and one in ES5. ```html