Update from Nov 19, 2020: Please note that due to a Google PageSpeed update the guide found here still works, but no longer has the desired effect for Lighthouse. If you are looking for an alternative to improve the performance of your shop, Hyvä Themes might be of interest to you.
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:
- All requirements listed at the ‘The Basis – default Magento 2 setup‘ section, except for JavaScript minification
- For Magento versions up until 2.3.4: apply the patches listed before: Mixins (<=2.3.4) and the Shims+mage-init fixes (<=2.3.3)
- The
requirejs-bundle-config.js
file created with the Magento 2 DevTools toolbar, as described in a previous section
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 intorequirejs-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

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.
This seems great, have been playing with bundling JS for quite some time on Magento and tried devtools doing it manually and also Magepack which seems like another quite simple solution to solving this bundling problem. Am confused how this knows to pull the bundles in without the module specifically adding the bundles but works none the less.
I do however seem to run into issues with certain files seemingly loading outside the bundles and therefore adding additional requests and not getting minified, mainly jquery (for me so wondering why this would be the case for me?
Still bundle sizes seem smaller than magepack etc and not entirely sure why but just seems faster.
How this approach compares to magepack?
https://github.com/magesuite/magepack
This is the best article I could find about advanced bundling on magento (and I’ve seen a lot of them). I will definitely apply the changes that adobe documentation did not mention, but for me one big question remains, is it possible to make this work with a compact static content deploy where magento also creates a base folder and for each theme a default folder also?