Speed up Magento 2 JavaScript delivery using baler method

While working at the current job I see many Magento stores are affected by performance issues. Design and a lot of different functionalities can look great, but all that does not matter if you cannot deliver fast rendering to the end-customer who is, by the way, the potential buyer. There are so many different vendors focusing on proper JavaScript bundling, but recently I've tested https://github.com/magento/baler, and I wanted here to share my insights.

In short, baler is an AMD (https://requirejs.org/) module bundler and preloader for Magento 2 stores.

How Does it Work?
baler has two different strategies for optimizing JavaScript delivery

Core Bundle
The "core" bundle contains the code that baler can determine is critical to the first render of the page. This includes the Magento core libraries, along with some theme-specific and feature-specific code.

Graph Preloading
The JavaScript included in the "core" bundle only includes dependencies that can be statically-analyzed from a theme's requirejs-config.js. However, many parts of the Magento front-end are controlled by widgets that are specified using a declarative notation in .phtml files.

This is where Graph Preloading comes in. When baler is run, it crawls the file system and determines which .phtml are eligible to be used in a specific area (frontend/adminhtml/base). These templates are then analyzed for any AMD module dependencies.

When a graph of dependencies has been collected for each .phtml file, the lists are flattened and deduped against the "core" bundle. Then, when a shopper requests a page of your store, preload tags are injected that instruct the browser to immediately begin fetching necessary dependencies.

I've tested on latest Magento 2.3.3 CE version, and you can check page load results here https://magentocommand.ml

It does what it says in manual, creating one core-bundle.js containing all possible aspects from core side which can be bundled safely.

Example:
https://magentocommand.ml/static/version1571521932/frontend/Magento/luma/en_US/balerbundles/core-bundle.js

What is JavaScript bundling?
Javascript bundling is a technique that groups separate files in order to reduce the number of HTTP requests that are required to load a page. Bundling is commonly used in today’s “module-based” development where some functionalities are basically split into Modules (roughly explained).

So let me show today how I did it on Fresh Magento 2.3.3 CE version with default Luma theme.

1) We need to clone current Baler repository:

git clone https://github.com/magento/baler.git  

2) You can cd into baler folder and install it using npm

npm install  

3) Optionally, if you don't wish to get anything installed on your local server there is already ready made binary file within bin/ folder named baler.

4) Make sure that you have node.js version >= 10.12.0 installed.

~/baler/bin$ ./baler 
baler requires a version of node.js >= 10.12.0. You're currently using v8.16.0  

This can be fixed easily by downloading latest Node version:

wget -c https://nodejs.org/dist/v10.14.1/node-v10.14.1-linux-x64.tar.gz  

Then let's unpack archive:

tar -xvf node-v10.14.1-linux-x64.tar.gz  

Once extracted we can export active PATH to new Node installation:

export PATH=/srv/baler/node-v10.14.1-linux-x64/bin:$PATH  

Mine direct path was /srv/baler/node-v10.14.1-linux-x64/bin but you can use pwd command to get real path and configure it.

In case you receive the following error:

 ./baler --help

Error: Cannot find module '../dist/cli/initializeCLI'  
at Function.Module._resolveFilename (internal/modules/cjs/loader.js:581:15)  
at Function.Module._load (internal/modules/cjs/loader.js:507:25)  
at Module.require (internal/modules/cjs/loader.js:637:17)  
at require (internal/modules/cjs/helpers.js:22:18)  
at Object.<anonymous> (/srv/baler/bin/baler:39:1)  
at Module._compile (internal/modules/cjs/loader.js:689:30)  
at Object.Module._extensions..js (internal/modules/cjs/loader.js:700:10)  
at Module.load (internal/modules/cjs/loader.js:599:32)  
at tryModuleLoad (internal/modules/cjs/loader.js:538:12)  
at Function.Module._load (internal/modules/cjs/loader.js:530:3)  

Fix is to run:

npm run build  

To test once this is installed:

~/baler/bin$ ./baler --help
Usage  
  $ baler <command> [options]

  Commands
    build --theme Vendor/name
    graph --theme Vendor/name

  Examples
    Optimize all eligible themes
    $ baler build

    Optimize multiple themes
    $ baler build --theme Magento/foo --theme Magento/bar

    Generate Dependency Graph
    $ baler graph --theme Magento/luma

Next jump to the Magento 2 docroot and run it:

:~/public_html$ /srv/baler/bin/baler 
✔ Collected theme/module data 155ms
✔ (Magento/luma) Created core bundle file 1.2s
✔ (Magento/luma) Minified core bundle and RequireJS 17.6s

Optimization is done, but stats have not been implemented in the CLI yet  

Clear Magento and Redis (if installed) cache:

~/public_html$ n98-magerun2 cache:clean
config cache cleaned  
layout cache cleaned  
block_html cache cleaned  
collections cache cleaned  
reflection cache cleaned  
db_ddl cache cleaned  
compiled_config cache cleaned  
eav cache cleaned  
customer_notification cache cleaned  
config_integration cache cleaned  
config_integration_api cache cleaned  
google_product cache cleaned  
full_page cache cleaned  
config_webservice cache cleaned  
translate cache cleaned  
vertex cache cleaned  
~/public_html$ n98-magerun2 cache:flush
redis-cli flushconfig cache flushed  
layout cache flushed  
block_html cache flushed  
collections cache flushed  
reflection cache flushed  
db_ddl cache flushed  
compiled_config cache flushed  
eav cache flushed  
customer_notification cache flushed  
config_integration cache flushed  
config_integration_api cache flushed  
google_product cache flushed  
full_page cache flushed  
config_webservice cache flushed  
translate cache flushed  
vertex cache flushed  
~/public_html$ redis-cli flushall
OK  

(this one is optional if Redis used)

Next, we need to install Magento 2 module which is going to handle bundlings:

composer config repositories.baler vcs https://github.com/adifucan/m2-baler  

Then run:

composer require magento/module-baler:dev-master  

Activate module:

n98-magerun2 module:enable Magento_Baler  

Run deployment process (setup:upgrade ; setup:di:compile and setup:static-content:deploy)

n98-magerun2 setup:upgrade  
n98-magerun2 setup:di:compile  
n98-magerun2 setup:static-content:deploy  

Clear Magento/Redis cache like we did it upper and then push the following:

php bin/magento config:set dev/js/enable_baler_js_bundling 1  

Run the baler once again:

$ /srv/baler/bin/baler build 
✔ Collected theme/module data 156ms
✔ (Magento/luma) Created core bundle file 1.2s
✔ (Magento/luma) Minified core bundle and RequireJS 17.8s

Optimization is done, but stats have not been implemented in the CLI yet  

Then we need to disable Magento 2 built in JavaScript Minify/Merge/Bundling:

php bin/magento config:set dev/js/minify_files 0  
php bin/magento config:set dev/js/enable_js_bundling 0  
php bin/magento config:set dev/js/merge_files 0  

Clear Magento/Redis cache once again (yeah I know boring...), load frontpage and test...

Addon:
While this is still in Alpha testing, there are certain things that needs to be patched. You can read them here https://github.com/DrewML/baler/issues/6

If you just want to apply the fixes, see the patch in core here https://github.com/magento/magento2/commit/db43c11c6830465b764ede32abb7262258e5f574

That's it! Most pages in your storefront should now be faster than they were before.

Hope this article helps. I strongly encourage you before optimizing your store to check https://drewml.com/posts/improving-javascript-delivery-in-magento2/ excellent blog post about what is Baler and what is the current project state.