Table of Contents

About Lighthouse Audits
Benchmarks
How JavaScript influences the performance

The Basis – default Magento 2 setup
Patches
Default Magento Bundling
Baler
Magento 2 DevTools browser extension
DevTools bundling

Part Four

You are here

integer_net Bundling
A real-life example
Conclusions

integer_net Bundling

A lot of these steps are similar to the DevTools Bundling section. The big difference is that we make some changes to the requirejs-bundle-config file and get rid of the Magento_BundleConfig module.

 

Getting started

Base requirements:

 

Optionally remove Baler:

If you just tested the results with Baler, you might want to clean that up first.


bin/magento module:uninstall Magento_Baler
composer remove magento/module-baler

 

Optionally remove Magento_BundleConfig:

If you just tested the results with M2 DevTools Bundling, you should clean that up first.

The BundleConfig module will inject your bundles into the head of Magento, and we don’t want that. So remove or disable the module first.


bin/magento module:uninstall Magento_BundleConfig
composer remove magento/module-bundle-config

– or –

bin/magento module:disable Magento_BundleConfig

 

Set Magento configuration

Disable all core JavaScript options. Remove –lock-config if you want it set in database instead of app/etc/config.php

bin/magento config:set dev/js/enable_js_bundling 0 --lock-config

bin/magento config:set dev/js/merge_files 0 --lock-config

bin/magento config:set dev/js/minify_files 0 --lock-config

We obviously don’t want default Magento Bundling, and we’ll do minification with Terser, which gets better results and works with modern JavaScript (ES6).

 

Install r.js, the RequireJS optimizer

You can either download r.js from the requirejs.org website or, better, install it with npm:

npm install -g requirejs

You should now be able to run r.js from command line:

r.js

> See https://github.com/requirejs/r.js for usage.

 

Install Terser

If you haven’t already, install Terser globally.

npm install -g terser

 

Compile your themes

I usually run setup:upgrade to remove all compiled assets, and then compile DI and static content:

bin/magento setup:upgrade

bin/magento setup:di:compile

bin/magento setup:static-content:deploy en_US

By the way, did you know there is an excellent feature of Symfony Console, on which the Magento console is based, that allows you to abbreviate those commands?

This one liner performs all three commands above:

bin/magento set:up && bin/magento set:di:co && bin/magento set:sta:dep en_US

 

THE MAGIC

We need to make a few changes to our requirejs-bundle-config.js.

First of all, set optimize to none:

"optimize": "none",

Then, inside the ”modules: []” add this new bundle config:

Exclude requirejs/require from the shared bundle:

Exclude requirejs/require from all the other bundles:

Finally, at the end of the file, remove the last bracket } just before the last }) and add this

You can view these changes in this single commit on Github: https://github.com/integer-net/magento2-requirejs-bundling/commit/883962adf013efd5ee0551ea2700987e3a78d1a1#diff-4333a40a1784ff0986cd218e1e965bd9

So, what did we do here?

  • We took out minification since we’ll do it with Terser after the bundling step
  • We inserted “mage/common”, “jquery/jquery.cookie” and “jquery/jquery-storageapi” into “requirejs/require”. This file already exists and is loaded in the head of your page synchronously.
  • We tell r.js not to include files from “requirejs/require” into other bundles, so that we’re sure they will be loaded once and only from “requirejs/require”. This way we are also sure these JavaScript modules are loaded before our page is being rendered in the frontend.
  • We added onBundleComplete which injects all our bundle configuration into requirejs-config.js. This bundle config helps RequireJS to find the appropriate bundle instead of requesting a single file in the frontend.

Or, to keep it simple, we reduced the absolute minimum of JavaScript we need for a functioning page into requirejs/require and made all other JavaScript available through async bundles.

This means we’re blocking page render as little as possible, while moving all other JavaScript into bundles that are fully optimized and only loaded when needed on the page.

Why did we add those modules to requirejs/require?

We need “mage/common”, “jquery/jquery.cookie” and “jquery/jquery-storageapi” in that main, synchronous, bundle because the browser needs those scripts when parsing the body of the page. Mage/common will for example make x-mage-init work and storageapi is needed for references to localStorage.getItem().

In our tests, all other scripts could be moved to asynchronous bundles without issues.

Create the bundles

We can now go to the root of our project, where you should have saved your improved requirejs-bundle-config.js

Now we are going to perform a couple of commands to first copy the generated themes from pub/static/frontend/ to a backup directory and then run the r.js optimizer which will copy the theme back into the original directory with the optimizations and generated bundles.

You should see a long list of output for bundles that are being created. Then all bundles and require.js and require-config.js are minified by Terser.

We don’t minify the entire theme directory because in theory all files should be in the bundles already, and the minification through Terser otherwise takes extremely long.

That’s it

To be sure that your new files are being loaded, flush your cache (and maybe even force clean your Varnish).

Now visit your store’s frontend and open inspector. You should see bundles/shared.js loaded on any page and for any page that has a custom bundle you should see the bundle with the layout handle e.q. bundles/catalog-product-view

 

Troubleshooting

Bundling fails because certain files cannot be found.

The toolbar will pick up AMD files that might be inside a minified JavaScript file that are not in your RequireJS config. I found it easiest to simply remove those from the bundle configuration and try again.

Errors in the frontend

Did you apply the patches for bundling?

Is there any JavaScript loaded directly without the needed require([], function(){}) around it?

Some things don’t work in the checkout

Mixins might not be working, and the first place you usually find out is in the checkout process.

Again, did you apply those patches?

Terser throws a “file read” error

If you changed the Terser command above, check whether you haven’t placed spaces between the reserved list items. You would see this error if you did: “ERROR: ENOENT: no such file or directory, open ‘jQuery,’”

Other issues

You might have errors in your requirejs-config.js files, mixins or shims or any other issue. This is unfortunately a case by case issue and you’ll have to debug.

 

Profile

You can now start profiling. Results really depend on your project, but we got the best scores using this approach. Hopefully Baler will be even better when it’s finished.

 

Results

No Bundling

integer_net

Homepage

84

91

Category

85

90

Product

81

88

Cart

71

82

Checkout

47

73

 

Implementation

We’ve managed to get this into our deployment pipeline installing requirejs locally in the project during deployment. This means you run npm install requirejs and execute node_modules/bin/r.js instead. This works well.

The same goes for Terser, it can be run from node_modules/bin/terser instead.

Just like with the DevTools Bundling approach, the downside to this implementation is that any time you change your shop and JavaScript files are added or removed, you need to create a new bundle config.

In case new files are added or missing from your bundle configuration, this is not a big issue because RequireJS will always fall back to loading a single file if it can’t be found in the bundles.

If files are removed, however, you won’t be able to create the bundles because r.js will fail and exit with an error file not found.

So keeping your configuration up to date is an ongoing process.

A real-life example

The reason we did such extensive research on the topic is because we were hitting walls with every approach we took. We don’t really have standard cases that compare to the default Magento 2 shop with sample data.

There are either a lot of extra JavaScript libraries, or we added a lot of JavaScript, since we’re really into building React components into Magento lately.

To show an example of a project where we struggled with frontend performance, here’s a benchmark of one of our clients:

There are certain factors we haven’t been able to optimize yet, such as the amount of CSS, because we had to take over the legacy design from the old website and it yet has to be refactored.

Like JavaScript, CSS is a page-blocking factor and hurts the performance score a lot. It’s out of the scope of this article (wasn’t it long enough already?), but Magento recently added a CSS critical path functionality that will greatly help with reducing the initial CSSload and making all other CSS load asynchronously.

Another factor is the sheer amount of third-party libraries that are loaded on the live website. We see 16 seconds of execution time on mobile devices just to load in all the tracking scripts (Analytics, Facebook, Bing, Criteo, Hotjar, TrustedShops, etc). This completely drags down the score.

The scores below are without third-party scripts. For this website we see almost a 50% decrease of Performance Score as soon as these scripts are added.

The configurations that I have tested are:

These are the exact same circumstances as tested before on default Magento with sample data.

We only left out default Magento bundling. But if you must know, it’s all zeros.

Default: JS minification enabled, no bundling

Baler: JS minification and bundling disabled, Baler bundling and Terser minification via command line

Devtools bundling: JS minification and bundling disabled. Advanced bundling and minification via r.js using configuration generated by M2-Devtools plugin

Our ‘integer_net’ config: JS minification and bundling disabled. Advanced bundling via r.js using configuration generated by M2-Devtools plugin, with some adjustments. Minification through Terser.js

Results

Default

Baler

DevTools

integer_net

Homepage

56

56

29

62

Category

59

46

33

65

Product

47

45

17

55

Cart

54

56

31

60

Checkout

56

61

24

62

Conclusions

Magento 2 frontend optimization is not an easy task. Hopefully we gave you enough tools to cruise through this and make the biggest gains possible, which is bundling JavaScript.

You might find different outcomes than we did, and we’d be very interested to hear about it.

If you can validate our methods and send us your results, that would be very helpful too. We want to give the best advice possible.

It’s understandable that Magento is moving on to a more modern tech stack (being the React based PWA Studio) that will replace the current frontend, and it will be a giant leap forward for the future.

However, we hope Magento – an Adobe company – finds it important enough to keep investing into making the current RequireJS/KnockoutJS frontend perform better.

After all, many merchants and agencies are heavily invested in the current frontend and won’t switch to the new PWA suite any time soon.

Let’s hope 2020 will bring us a full-featured Baler which will make this process easier.

Have feedback? Drop it in the comments below or on twitter @willemwigman.

We would really appreciate you sharing your results with us. What solution worked best and what are the before/after scores?

You finished reading the fourth and last part of our series.

Do you want to read another part?

About Lighthouse Audits
Benchmarks
How JavaScript influences the performance

The Basis – default Magento 2 setup
Patches
Default Magento Bundling
Baler
Magento 2 DevTools browser extension
DevTools bundling

Part Four

You are here

integer_net Bundling
A real-life example
Conclusions
Willem Wigman

Author: Willem Wigman

Willem Wigman is Certified Magento 2 developer at integer_net. His focus lies in front- and backend development and ReactJS.

Willem started as a freelance developer in 2007, works with Magento since 2011 and ran his own Magento Agency for a couple of years.

More Information · Twitter · GitHub