RequireJS and Magento2

How they work together and, if you want, how to turn it off.

Magento2 uses RequireJS to load its javascript. The library is made to be a browser-based solution to a classic problem in frontend development: how to incorporate third-party dependencies in your javascript. Rather than having to place a page’s dependencies in script tags in each page’s header, RequireJS handles everything for you asynchronously, allowing your javascript to define its own dependencies, and allowing a progressive-enhancement-oriented workflow.

More of a visual learner? Check out this article in YouTube video form: RequireJS and Magento2!

Anatomy of x-magento-init

If you do a simple view source of any Magento2 shop’s page, you may see some weird-looking code like this:

<script type="text/x-magento-init">
    {
        "*": {
            "Magento_Ui/js/core/app": {
                "components": {
                    "wishlist": {
                        "component": "Magento_Wishlist/js/view/wishlist"
                    }
                }
            }
        }
    }
</script>

That’s how Magento2 defines its component and page-level javascript dependencies. Those javascript dependencies, in turn, have dependencies on other scripts (that RequireJS also loads), which are defined across your application’s code base and stitched together (and cached) by Magento2 at run time.

By default, every Magento page brings in three javascript files:

  1. requirejs/require.js – The base RequireJS script
  2. requirejs/requirejs-config.js – The Magento2 RequireJS config script
  3. requirejs/mixins.js (see also, Alan Storm’s article on Magento2 mixins)

We’re going to be focusing on the first two. When the page loads, RequireJS loads the configuration defined in the requirejs-config.js file, which bootstraps the page and parses blocks of JSON like we found above.

So what does it do with these blocks of JSON? Step with me through the code!

Step 1: Oh hello, there. Let’s load up some Javascript!

<script type="text/x-magento-init">
</script>

When Magento’s bootstrapping script runs across any script with the attribute type="text/x-magento-init", it knows that therein is a blob of JSON that points to an element and a component that RequireJS is going to have to handle.

Step 2: Let’s look for an element, shall we? Or not.

<script type="text/x-magento-init">
    {
        "*": {

        }
    }
</script>

In this step, the front end developer tells Magento’s script resolver to only continue with this component dependency if the element matching the selector (in the case above, *, so it will always match) is found on the page.

Note: if that element is not found on the current page, Magento won’t even go searching for the component’s javascript at all. RequireJS won’t inject the script tag into the header, and the client will never see it.

Moreover, when you’re creating custom components, Magento will pass the element(s) matching that selector into the component for you to play with. Pretty cool, huh?

Step 3: Scripts away!

<script type="text/x-magento-init">
    {
        "*": {
            "Magento_Ui/js/core/app": {
                
            }
        }
    }
</script>

We’ve made it this far, which means it’s time to call up some javascript! In this block, the text to the left of the colon tells RequireJS that it needs to load scripts from that location. On my local development environment, this means that RequireJS throws the following script into the header:

<script type="text/javascript" charset="utf-8" async="" data-requirecontext="_" data-requiremodule="Magento_Ui/js/core/app" src="http://127.0.0.1:8080/static/version1491571223/frontend/Magento/blank/en_US/Magento_Ui/js/core/app.js"></script>

Then, the client loads that script (asynchronously), and it executes. Typically, these files will return a function, however, that passes in the element we discussed above, as well as the configuration…

Step 4: All together now…

<script type="text/x-magento-init">
    {
        "*": {
            "Magento_Ui/js/core/app": {
                "components": {
                    "wishlist": {
                        "component": "Magento_Wishlist/js/view/wishlist"
                    }
                }
            }
        }
    }
</script>

If the script that RequireJS just loaded up is a component (using the define function whose second argument is a function returning a function), RequireJS will execute that returned function and pass it two arguments. The first argument is the JSON blob, as a variable, nested under the Magento_Ui/js/core/app node. (Confused? Don’t worry. We will cover this more in a future article.)

The second, is the element (a DOM node) or elements found with the initial selector. In this case, since we’re using the global selector (*), it actually just passes in false as the second argument.

This way, you can load a page and fully render it via Magento2’s PHP templating engine, yet still have dynamic and fully configured/configurable javascript embedded in the page. However, the client never has to execute any javascript within a <script> tag.

Pretty nifty!

RequireJS Dependencies

These isolated components won’t be able to do much by themselves. That’s where another feature of RequireJS comes into play: dependency management and injection.

If you inspect any Magento2 component, you will find that they look something like this:

define([
    'jquery',
    'underscore'
], function ($, _) {
    'use strict';

    // Setup stuff

    return function (config, element) {
        // Actual component-level stuff here
    };
});

As you may be able to tell, the define function simply defines a component’s dependencies before defining the component’s behavior. In this example, the two dependencies are jquery and underscore, which are passed into the actual component initialization function as two arguments (here, $ for jquery, and _ for underscore.js).

How does RequireJS know where to find dependencies?

Dependencies are mapped via the requirejs/requirejs-config.js that we mentioned above! To see the lot of them, view the the configuration file. Dependency mappings will look something like:

'ko': 'knockoutjs/knockout',
'knockout': 'knockoutjs/knockout',
'mageUtils': 'mage/utils/main',
'rjsResolver': 'mage/requirejs/resolver'

RequireJS will then look for the KnockoutJS dependency in knockoutjs/knockout (which it will later append .js to, after adding the appropriate static asset path to the URL) whenever you request a dependency named either ko or knockout.

Why is this useful?

So why do we do this instead of just using the old-school inline javascript method? There are a few reasons:

1. Inline Javascript is difficult to debug. It’s hard enough tracking down what goes where in Magento without having to constantly regression test bits of functionality when we’re making casual template changes, or trying to modify certain bits of JS code.

2. SECURITY! Magento2 is slowly turning the ship towards a focus on security, and part of that is adding support for tighter content security policies. While Magento has always chosen flexibility over strict security practices, it is absolutely impossible to bring a platform up to date in the fight against cross-site scripting without the ability to do something like the x-magento-init functionality allows. With it, we can move toward disallowing inline javascript from executing, which walls off one of the largest surface areas for XSS attacks on the web.

3. Dependency Management. If you’ve done any work with Magento 1, you have probably come upon an instance where you were trying to inject some form of javascript into your page that uses jQuery. But uh oh! It won’t work at all because jQuery isn’t loaded yet… Doh! So you had to do all kinds of hacks (like a setTimeout) to get it to work. Not anymore. RequireJS waits for all of your component’s dependencies to resolve before invoking the function, so you can be sure that jQuery is loaded.

4. Component Isolation. Let’s face it. The old way (inline scripts, no build process, no dependency management) can lead you to nicely isolated, reusable components. But I’ll bet you didn’t really do it, did you? I like the x-magento-init method of javascript injection because it nudges developers toward better practices like code reuse and component isolation (no more cluttering up the global namespace!). If Mage2 isn’t going to be using the newest, hottest Server-Side Rendered React methods, I’m happy that it’s at least drifting toward better practices.

If you create components using the methods outlined above (e.g., using the define function), you can make your components unit-testable, reusable, and as pure as possible.

Conclusion

Now you know the basics of RequireJS and how Magento2 uses it. Our next article will cover how we can remove the enormous bloat that Mage2 ships with core: or, how to reduce the script size on your default Mage2 home page from 125+ javascripts, down to something more manageable for your project.