web.dev

Houssein Djirdeh

In this codelab, improve the performance of this simple application that allows users to rate random cats. Learn how to optimize the JavaScript bundle by minizming how much code is transpiled.

App screenshot

In the sample app, you can select a word or emoji to convey how much you like each cat. When you click a button, the app displays the button's value underneath the current cat image.

Measure

It's always a good idea to begin by inspecting a website before adding any optimizations:

  1. To preview the site, press View App. Then press Fullscreen fullscreen.
  2. Press `Control+Shift+J` (or `Command+Option+J` on Mac) to open DevTools.
  3. Click the Network tab.
  4. Select the Disable cache checkbox.
  5. Reload the app.

Original bundle size request

Over 80 KB is used for this application! Time to find out if parts of the bundle aren't being used:

  1. Press Control+Shift+P (or Command+Shift+P on Mac) to open the Command menu. Command Menu

  2. Enter Show Coverage and hit Enter to display the Coverage tab.

  3. In the Coverage tab, click Reload to reload the application while capturing coverage.

    Reload app with code coverage

  4. Take a look at how much code was used versus how much was loaded for the main bundle:

    Code coverage of bundle

Over half the bundle (44 KB) is not even utilized. This is because a lot of the code within consists of polyfills to ensure that the application works in older browsers.

Use @babel/preset-env

The syntax of the JavaScript language conforms to a standard known as ECMAScript, or ECMA-262. Newer versions of the specification are released every year and include new features that have passed the proposal process. Each major browser is always at a different stage of supporting these features.

The following ES2015 features are used in the application:

The following ES2017 feature is used as well:

Feel free to dive into the source code in src/index.js to see how all of this is used.

All of these features are supported in the latest version of Chrome, but what about other browsers that don't support them? Babel, which is included in the application, is the most popular library used to compile code that contains newer syntax into code that older browsers and environments can understand. It does this in two ways:

  • Polyfills are included to emulate newer ES2015+ functions so that their APIs can be used even if it is not supported by the browser. Here is an example of a polyfill of the Array.includes method.
  • Plugins are used to transform ES2015 code (or later) into older ES5 syntax. Since these are syntax related changes (such as arrow functions), they cannot be emulated with polyfills.

Look at package.json to see which Babel libraries are included:

"dependencies": {
  "@babel/polyfill": "^7.0.0"
},
"devDependencies": {
  //...
  "babel-loader": "^8.0.2",
  "@babel/core": "^7.1.0",
  "@babel/preset-env": "^7.1.0",
  //...
}
  • @babel/core is the core Babel compiler. With this, all the Babel configurations are defined in a .babelrc at the root of the project.
  • babel-loader includes Babel in the webpack build process.

Now look at webpack.config.js to see how babel-loader is included as a rule:

module: {
  rules: [
    //...
    {
      test: /\.js$/,
      exclude: /node_modules/,
      loader: "babel-loader"
    }
  ]
},
  • @babel/polyfill provides all the necessary polyfills for any newer ECMAScript features so that they can work in environments that do not support them. It is already imported at the very top of src/index.js.
import "./style.css";
import "@babel/polyfill";
  • @babel/preset-env identifies which transforms and polyfills are necessary for any browsers or environments chosen as targets.

Take a look at the Babel configurations file, .babelrc, to see how it's included:

{
  "presets": [
    [
      "@babel/preset-env",
      {
        "targets": "last 2 versions"
      }
    ]
  ]
}

This is a Babel and webpack setup. Learn how to include Babel in your application if you happen to use a different module bundler than webpack.

The targets attribute in .babelrc identifies which browsers are being targeted. @babel/preset-env integrates with browserslist, which means you can find a full list of compatible queries that can be used in this field in the browserlist documentation.

The "last 2 versions" value transpiles the code in the application for the last two versions of every browser.

Debugging

To get a complete look at all the browser's Babel targets as well as all the transforms and polyfills that are included, add a debug field to .babelrc:

{
  "presets": [
    [
      "@babel/preset-env",
      {
        "targets": "last 2 versions",
        "debug": true
      }
    ]
  ]
}
  • Click Tools.
  • Click Logs.

Reload the application and take a look at the Glitch status logs at the bottom of the editor.

Targeted browsers

Babel logs a number of details to the console about the compilation process, including all the target environments that the code has been compiled for.

Targeted browsers

Notice how discontinued browsers, such as Internet Explorer, are included in this list. This is a problem because unsupported browsers won't have newer features added, and Babel continues to transpile specific syntax for them. This unnecessarily increases the size of your bundle if users are not using this browser to access your site.

Babel also logs a list of transform plugins used:

List of plugins used

That's a pretty long list! These are all the plugins that Babel needs to use to transform any ES2015+ syntax to older syntax for all the targeted browsers.

However, Babel doesn't show any specific polyfills that are used:

No polyfills added

This is because the entire @babel/polyfill is being imported directly.

Load polyfills individually

By default, Babel includes every polyfill needed for a complete ES2015+ environment when @babel/polyfill is imported into a file. To import specific polyfills needed for the target browsers, add a useBuiltIns: 'entry' to the configuration.

{
  "presets": [
    [
      "@babel/preset-env",
      {
        "targets": "last 2 versions",
        "debug": true
        "useBuiltIns": "entry"
      }
    ]
  ]
}

Reload the application. You can now see all the specific polyfills included:

List of polyfills imported

Although only needed polyfills for "last 2 versions" is now included, it is still a super long list! This is because polyfills needed for the target browsers for every newer feature is still included. Change the value of the attribute to usage to only include those needed for features that are being used in the code.

{
  "presets": [
    [
      "@babel/preset-env",
      {
        "targets": "last 2 versions",
        "debug": true,
        "useBuiltIns": "entry"
        "useBuiltIns": "usage"
      }
    ]
  ]
}

With this, polyfills are automatically included where needed. This means you can remove the @babel/polyfill import in src/index.js.

import "./style.css";
import "@babel/polyfill";

Now only the required polyfills needed for the application are included.

List of polyfills automatically included

The application bundle size is reduced significantly.

Bundle size reduced to 30.1 KB

Narrowing the list of supported browsers

The number of browser targets included is still quite large, and not many users use discontinued browsers such as Internet Explorer. Update the configurations to the following:

{
  "presets": [
    [
      "@babel/preset-env",
      {
        "targets": "last 2 versions",
        "targets": [">0.25%", "not ie 11"],
        "debug": true,
        "useBuiltIns": "usage",
      }
    ]
  ]
}

Take a look at the details for the fetched bundle.

Bundle size of 30.0 KB

Since the application is so small, there really isn't much of a difference with these changes. However, using a browser market share percentage (such as ">0.25%") along with excluding specific browsers that you are confident your users are not using is the recommended approach. Take a look at the "Last 2 versions" considered harmful article by James Kyle to learn more about this.

"Use

Read the original on web.dev ↗