What is Breeze, and how it’s working?

What is Breeze, and how it’s working?

Breeze — is a performant frontend javascript stack built to be equally flexible to its predecessor.

Before we take a deep dive into the Breeze implementation details, let’s step aside for a moment and quickly examine how Magento frontend works and why it’s so painfully slow.

Luma stack

Lighthouse score (mobile) for the Luma theme.

Here is a shortened example of a Magento page source that the browser will parse and execute to bring a ready-to-use page for each visitor:


<html>
  <head>
    <script src="require.js"/>
    <script src="requirejs-config.js"/>
  </head>
  <body>
    …
    <div data-mage-init='{"component": {}}'></div>
    …
    <script type="text/x-magento-init">
    {
      "*": {
        "Magento_Ui/js/core/app": {
          "components": {}
        }
      }
    }
    …
    </script>
  </body>
</html>

The page source code includes two javascript resources: require.js and requirejs-config.js. The browser download them and then the magic starts:

  1. requirejs-config.js instructs require.js to download few more resources including mage/apply/main
  2. mage/apply/main walks the DOM structure searching for data-mage-init and text/x-magento-init nodes.
  3. Each node instructs require.js to download and initialize components declared in this node.
  4. Each component may ask to download more dependencies and so on.

The idea is brilliant and brings a lot of benefits, but it’s not working efficiently with as many dependencies as Magento has:

  • Main menu — 16 dependencies
  • Catalog swatches — 28 dependencies
  • Header cart — 49 dependencies
  • Auth popup — 57 dependencies

Here is a shortened dependency tree of the Magento_Customer/js/view/authentication-popup component:


‣ Magento_Customer/js/view/authentication-popup
  ‣ knockoutjs/knockout
  ‣ Magento_Ui/js/form/form
    ‣ Magento_Ui/js/lib/spinner
    ‣ mage/requirejs/resolver
    ‣ Magento_Ui/js/form/adapter
    ‣ Magento_Ui/js/lib/core/collection
      ‣ underscore
      ‣ mage/utils/main
        ...
    ‣ Magento_Ui/js/core/app
    ‣ mage/validation
  ‣ Magento_Customer/js/action/login
  ‣ Magento_Customer/js/customer-data
  ‣ Magento_Customer/js/model/authentication-popup
  ‣ mage/translate
  ‣ Magento_Ui/js/modal/alert
  ‣ mage/validation

Magento tries to solve this dependency nightmare with js bundles. Unfortunately, neither bundles nor optimization will help your application if it has too much code.

Breeze stack — The Luma is dead, long live Luma!

Lighthouse score (mobile) for the Breeze-powered Luma theme

When we started to explore the ways to improve the performance of the Magento store it was crucial to keep full compatibility with current CSS, HTML, and at least partial compatibility with JS components. This would allow us to make our existing themes and modules breeze-friendly in a short period.

In a perfect scenario, we want to use the same javascript file for both Luma and Breeze-powered theme.

The solution was pretty straightforward — create a module that will inject a new JS stack instead of the default one. The new stack must automatically work with existing data-mage-init and text/x-magento-init syntax. The biggest plus of this solution is that it doesn’t require any changes to CSS and HTML.

Breeze is fully compatible with Luma and Blank themes because it doesn’t modify CSS and HTML.

Another issue to solve was providing a working and easy-to-use js bundling mechanism. We decided to incorporate bundling definitions directly into the js components declarations in XML layout update files. Layout update was chosen as it allows to inject components on a server-side based on their existence on the generated page output.

Let’s see what Breeze brings us after few months of active development.

Widgets

You can create widgets in a very similar way to Luma widgets:


$.widget('widgetName', 'parentWidget', {
  component: 'menu',
  options: {
    option1: 'defaultValue'
    option2: 'defaultValue'
  },
  create: function () {
    console.log(this.element);
    console.log(this.options);
  }
});

This widget will be automatically mounted on the following node:


<div data-mage-init='{"menu": {"option1": "optionValue"}}'></div>

UI components

You can create a view component that will render a client-side template.

Here is an example of a tiny component that handles the default welcome message block:


$.view('customer', {
  component: 'Magento_Customer/js/view/customer',
  customer: $.sections.get('customer')
});

This component will be automatically mounted on the following node:


<div class="greet welcome" data-bind="scope: 'customer'">
  <!-- ko if: customer().fullname -->
  …
</div>
<script type="text/x-magento-init">
{
  "*": {
    "Magento_Ui/js/core/app": {
      "components": {
        "customer": {
          "component": "Magento_Customer/js/view/customer"
        }
      }
    }
  }
}
</script>

Another common use case in a Luma-based theme is a component that renders a template from an external html file. In Breeze, you should render this template using layout XML instructions:



  <block class="Swissup\Breeze\Block\HtmlTemplate" name="breeze.uiMessages" template="Magento_Ui::messages.html"/>

Then, the view component can refer to this template:

$.view('uiMessages', {
  component: 'Magento_Ui/js/view/messages',
  defaults: {
    template: 'uiMessages'
  }
}

Mixins

It is crucial to have the ability to extend and override built-in components when building extendable frontend. Breeze allows to extend existing components using the simple syntax:


$.mixin('menu', {
  open: function (original, argument) {
    return original(argument);
  }
});

JS Bundles

While Breeze has much fewer javascript resources to import, it’s needless to say that js bundling is a must-have feature for the frontend that will be extended by third-party modules.

Breeze requires developers to assign their JS resources into new or existing bundles using XML layout update files. The following bundles are available out of the box. Here is a short example:


<referenceBlock name="breeze.js">
  <arguments>
    <argument name="bundles" xsi:type="array">
      <item name="default" xsi:type="array">
        <item name="items" xsi:type="array">
          <item name="Vendor_Module/js/component" xsi:type="string">Vendor_Module/js/breeze/component</item>
        </item>
      </item>
    </argument>
  </arguments>
</referenceBlock>

Integration with third-party modules

Here are some integration scenarios we used for our modules:

1. It just works

Some of our modules don’t rely heavily on magento code. This is a perfect integration scenario. We use the same js file for both Luma and Breeze stack.

The following HTML is generated by our block:


<div data-mage-init='{"Vendor_Module/js/component": {}}'></data>

And here is the content of Vendor_Module/js/component


define([
  'jquery',
  'Magento_Customer/js/customer-data'
], function ($, customerData) {
  'use strict';  var cart = customerData.get('cart');  сart.subscribe(...);
});

The only thing that should be done for Breeze is to create breeze_default.xml file and declare component for Vendor_Module/js/component

2. It almost works

Another common solution was to modify the define part of js file:

Before:


define(['jquery', 'underscore', 'mage/cookies'], function ($, _) {
  'use strict';  ...
});

After:


(function (factory) {
  'use strict';  if (typeof define === 'function' && define.amd) {
    define(['jquery', 'underscore', 'mage/cookies'], factory);
  } else {
    $.easybanner = {};
    $.easybanner.Widget = factory($, _);
  }
}(function ($, _) {
  'use strict';  ...
});

3. The worst case. Nothing works.

In this case, we just create a new component in Vendor_Module/js/breeze/component and implement the same logic again:



(function () {
  'use strict';  $.widget('gallery', {
    component: 'mage/gallery/gallery',
    options: {}
    ...
  });
});

Single-page applications (SPA) greatly improve customer experience. We’ve already played with Turbo on a Breeze dev-branch and it’s awesome! Stay tuned to our updates!

Thanks for reading! Leave a comment below if you have any questions.

Leave a Reply