蜜豆视频

[PaaS only]{class="badge informative" title="Applies to 蜜豆视频 Commerce on Cloud projects (蜜豆视频-managed PaaS infrastructure) and on-premises projects only."}

Advanced JavaScript bundling

Bundling JavaScript modules for better performance is about reducing two things:

  1. The number of server requests.
  2. The size of those server requests.

In a modular application, the number of server requests can reach into the hundreds. For example, the following screen shot shows only the start of the list of JavaScript modules loaded on the home page of a clean installation.

No bundling

Merging and bundling

Out of the box, Commerce provides two ways to reduce the number of server requests: merging and bundling. These settings are turned off by default. You can turn them on within the Admin UI in Stores > Settings > Configuration > Advanced > Developer > JavaScript Settings, or from the command line.

Bundling

Basic bundling

To enable built-in bundling from the command line:

php -f bin/magento config:set dev/js/enable_js_bundling 1

This is a native Commerce mechanism that combines all assets present in the system and distributes them among same-sized bundles (bundle_0.js, bundle_1.js 鈥 bundle_x.js):

Commerce bundling

Better, but the browser still loads ALL the JavaScript bundles, not just the ones needed.

Commerce bundling reduces the number of connections per page, but for each page request it loads all bundles, even when the requested page may only depend on files within one or two of the bundles. Performance improves after the browser caches the bundles. But, because the browser loads these bundles synchronously, the user鈥檚 first visit to a Commerce storefront could take a while to render and hurt the user experience.

Basic merging

To enable built-in merging from the command line:

php -f bin/magento config:set dev/js/merge_files 1

This command merges all synchronous JavaScript files into one file. Enabling merging without also enabling bundling is not useful because Commerce uses RequireJS. If you don鈥檛 enable bundling, Commerce only merges RequireJS and its configuration. When you enable both bundling and merging, Commerce creates a single JavaScript file:

Real-world merging

Real-world render times

The previous bundled and merged load times look great in a development environment. But in the real world, many things can slow down rendering: slow connections, large connection thresholds, limited networks. In addition, mobile devices do not render as fast as desktops.

To test and prepare your storefront deployment for the real world, we recommend you test with Chrome鈥檚 native throttling profile of 鈥淪low 3G.鈥 With Slow 3G, our previous bundled output times now reflect many users鈥 connection realities:

Real-world bundling

At Slow 3G connectivity, it takes about 44 seconds to load all the bundles for the homepage of a clean Commerce installation.

The same is true when merging the bundles into a single file. Users could still wait around 42 seconds for the initial page load as shown here:

Real-world merging

With a more advanced approach to JavaScript bundling, we can improve these load times.

Advanced bundling

Remember, the goal of JavaScript bundling is to reduce the number and size of requested assets for each page loaded in the browser. To do that, we want to build our bundles so that each page in our store will only need to download a common bundle and a page-specific bundle for each page accessed.

One way to achieve this is to define your bundles by page types. You can categorize Commerce鈥檚 pages into several page types, including Category, Product, CMS, Customer, Cart, and Checkout. Each page categorized into one of these page types has a different set of RequireJS module dependencies. When you bundle your RequireJS modules by page type, you will end up with only a handful of bundles that cover the dependencies of any page in your store.

For example, you might end up with a bundle for the dependencies common to all pages, a bundle for CMS-only pages, a bundle for Catalog-only pages, another bundle for Search-only pages, and a bundle for Checkout pages.

You could also create bundles by purpose: for common features, product-related features, shipping features, checkout features, taxes, and form validations. How you define your bundles is up to you and the structure of your store. You may find that some bundling strategies will work better than others.

A clean Commerce installation allows reaching enough good performance by splitting bundles by page types, but some customizations may require deeper analysis and other asset distributions.

Required tools

The following steps require you to install and have familiarity with the following tools:

  • (optional)

Sample code

Full versions of the sample code used in this article are available here:

Part 1: Create a bundling configuration

1. Add a build.js file

Create a build.js file in the Commerce root directory. This file will contain the entire build configuration for your bundles.

({
    optimize: 'none',
    inlineText: true
})

Later, we will change the optimize: setting from_ none to uglify2 to minify bundle output. But for now, during development, you can leave it set to none to ensure faster builds.

2. Add RequireJS dependencies, shims, paths, and map

Add the following RequireJS build configuration nodes, deps, shim, paths, and map, to your build file:

({
    optimize: 'none',
    inlineText: true,

    deps: [],
    shim: {},
    paths: {},
    map: { "*": {} },
})

3. Aggregate the requirejs-config.js instance values

In this step, you will need to aggregate all of the multiple deps, shim, paths, and map configuration nodes from your store鈥檚 requirejs-config.js file into the corresponding nodes in your build.js file. To do this, you can open the Network tab in your browser鈥檚 Developer Tools panel and navigate to any page in your store, such as the homepage. In the Network tab, you will see your store鈥檚 instance of the requirejs-config.js file near the top, highlighted here:

RequireJS configuration

Within this file, you will find multiple entries for each of the configuration nodes (deps, shim, paths, map). You need to aggregate these multiple node values into your build.js file鈥檚 single configuration node. For example, if your store鈥檚 requirejs-config.js instance has entries for 15 separate map nodes, you will need to merge the entries for all 15 nodes into the single map node in your build.js file. The same will be true for the deps, shim, and paths nodes. Without a script to automate this process, it may take time.

You will need to change the path mage/requirejs/text to requirejs/text in paths configuration node as following:

({
    //...
    paths: {
        //...
        "text": "requirejs/text"
    },
})

4. Add a modules node

At the end of the build.js file, add the modules[] array as a placeholder for the bundles you will define for your storefront later.

({
    optimize: 'none',
    inlineText: true,

    deps: [],
    shim: {},
    paths: {},
    map: { "*": {} },

    modules: [],
})

5. Retrieve RequireJS dependencies

You can retrieve all the RequireJS module dependencies from your store鈥檚 page types by using:

  1. PhantomJS from the command line (assuming you have PhantomJS installed).
  2. RequireJS command in your browser鈥檚 console.

To use PhantomJS:

In the Commerce root directory, create a new file called deps.js and copy in the code below. This code uses PhantomJS to open a page and wait for the browser to load all page assets. It then outputs all the RequireJS dependencies for a given page.

"use strict";
var page = require('webpage').create(),
    system = require('system'),
    address;

if (system.args.length === 1) {
    console.log('Usage: $phantomjs deps.js url');
    phantom.exit(1);
} else {
    address = system.args[1];
    page.open(address, function (status) {
        if (status !== 'success') {
            console.log('FAIL to load the address');
        } else {
            setTimeout(function () {
                console.log(page.evaluate(function () {
                    return Object.keys(window.require.s.contexts._.defined);
                }));
                phantom.exit();
            }, 5000);
        }
    });
}

Open a terminal inside the Commerce root directory and run the script against each page in your store that represents a specific page type:

phantomjs deps.js url-to-specific-page > text-file-representing-pag