File Extension Conventions in Magento's RequireJS Configuration

Magento’s implementation of mapping JavaScript and Knockout.js template files with RequireJS may seem awkward at first glance, but there is a rationale behind the approach.

Mark Shust

Mark Shust

Last Updated 7 min read

How RequireJS Loads JavaScript Files

Let’s first take a look at how we would override a JavaScript file in Magento with RequireJS. The following example is a code snippet from the DevDocs of how this is accomplished:

var config = {
    "map": {
        "*": {
            "menu": "js/navigation-menu",
            "mage/backend/menu": "js/navigation-menu"
        }
    }
};

Note that the navigation menu didn’t need to be specified as js/navigation-menu.js — the .js extension was omitted. This is because, by default, RequireJS does not require you to add the .js extension when defining paths or aliases in requirejs-config.js files.

This is not just a Magento practice, but a standard of RequireJS and is even defined in their API. The concept is also used when mapping aliases to RequireJS modules.

So far, no problems… everything works as expected. But some confusion comes into play when Magento loads Knockout.js template files. And this is because loading KO.js templates is a Magento-specific implementation — the ability to do this does not exist in the RequireJS core API.

How Magento Loads Knockout.js Templates

Due to the fact that the ability to load Knockout.js templates does not exist in RequireJS, Magento needed to create this implementation. And it does this by defining a special path to Magento module template path locations.

Here is an excerpt from my Customize the Magento 2 Checkout course, in which we start customizing the checkout sidebar. This requirejs-config.js file tells Magento to substitute out the core sidebar Knockout.js template file with our customized file:

let config = {
    map: {
        '*': {
            'Magento_Checkout/template/sidebar.html':
                'Macademy_CustomCheckout/template/sidebar.html'
        }
    }
};

This mapping loads the Knockout.js template with our custom app/code/Macademy/CustomCheckout/view/frontend/web/template/sidebar.html file. We’re essentially telling RequireJS (and Magento) that when the Magento_Checkout/template/sidebar.html is requested, load Macademy_CustomCheckout/template/sidebar.html. This is similar to overriding a class with a class preference in PHP.

Note though that the path that is defined is a completely non-standard location. The Macademy_CustomCheckout/ path defined in the requirejs-config.js file refers to either the view/frontend/web directory within our custom module, or the Macademy_CustomCheckout/web directory within a custom theme. Both of these locations are specific to Magento’s implementation.

Alternate Knockout.js Template Loading Format

In the example above, we loaded the Knockout.js template by specifying the .html extension. But can we load it another way?

We can, in fact, …by not specifying an extension at all. The following code works just the same:

let config = {
    map: {
        '*': {
            'Magento_Checkout/template/sidebar':
                'Macademy_CustomCheckout/template/sidebar'
        }
    }
};

Now, since this works, it would appear at first glance that we should be using this “no file extension” approach to remain consistent with the RequireJS’s approach of loading JavaScript files. But remaining “consistent” here actually leads can lead to confusion and a potentially unpredictable result.

Load Order With and Without .html

Let’s revisit our previous example, and assume that a third-party module (which we’ll call Module A) includes the .html extension when overriding the sidebar template file:

Third-Party, Module A

requirejs-config.js

map: {
    '*': {
        'Magento_Checkout/template/sidebar.html':
        'Macademy_CustomCheckout/template/sidebar.html'
}}

Now let’s assume that you are creating a customization for a client and would like to override the same file. You create an override in your code (which we’ll reference as Module B), but this time, without using the .html extension:

Local Code, Module B

requirejs-config.js

map: {
    '*': {
        'Magento_Checkout/template/sidebar':
        'Macademy_CustomCheckout/template/sidebar'
}}

What will happen in this situation? The result isn’t a predictable one. But in this case, since two modules override the same Knockout.js template file, the module which declares the .html extension will take precedence — regardless of other load order rules.

Not only will this cause you massive confusion with trying to figure out why your updates aren’t applying, but it leads to unexpected results. If we instead used the .html extension in our custom override, we would be able to control the load order of our modules by adding a sequence node to our module.xml file. This leads to a much more predictable outcome for our code.

Why Use .html for Knockout.js Templates?

Just because we can omit the file extension when loading Knockout.js template files, doesn’t mean we should. And there are a few solid reasons for this:

  • Consistency: Magento has established conventions for how to load files, and sticking to these conventions helps to maintain consistency across your projects. When you start deviating from standards, you only create additional confusion for other developers (and your future self) to understand what’s going on in your code. This is the same reason that we omit the .js extension for JavaScript files: it’s because this is the expected approach when working with JavaScript files in RequireJS, and defining the .js extension actually makes your code less consistent.
  • Compatibility: By following these conventions, you are also ensuring your custom themes or modules are compatible with the larger Magento ecosystem. This reduces the possibility of introducing conflicts or bugs in your code, which in turn, leads to code that meshes better with third-party code.
  • Predictability: When the .html extension isn’t defined, it’s very possible that a JavaScript file located in the same place can be loaded instead. I’m not saying this will ever happen; but it’s possible, and you want your code to be as predictable as possible, especially when there are no advantages to omitting the extension.
  • Self-Documentation: When another developer looks at your code, you want it to be very obvious that you are dealing with either JavaScript file or a Knockout.js template. By leaving the .html extension off, you are creating an ambiguous situation where it’s possible the developer will get confused and not be able to tell if you are overriding a JavaScript file or a Knockout.js template, or some other file.

My Takeaway

So, should you always include the .html for Knockout templates, and omit the .js for JavaScript files in your RequireJS configs?

The answer is a resounding: yes!

Magento has conventions defined for a reason, and when you start deviating from the expected norm, you start creating unexpected results. You want your app’s code to be routine, compatible, predictable, and consistent with the broader ecosystem. Why walk away from this unexpected outcome?

Is there ever a situation or scenario in which omitting the .html extension is useful? Not likely.

While there could always be very niche cases where this may apply, deviating from the norm should be looked at as a temporary workaround — and the term “temporary workaround” almost always sounds like a hack, rather than a practice that you should rely on.

Excited to learn more about how RequireJS or Knockout templates work in Magento? Get ready to learn a ton more:

  1. Learn how local & external JavaScript state works in Magento (free article)
  2. Defeat Knockout.js with a 1-2 punch by taking this course (700+ students)
  3. Start really learning Magento with all courses & lessons (700+ students)