Replacing a Core Module

RequireJS map, paths and shim can point a core module id at your own file, which works everywhere and freezes you at the version you copied.

Syntaxmap: { '*': { 'Magento_Catalog/js/catalog-add-to-cart': 'Vendor_Cart/js/catalog-add-to-cart' } }

Three keys in requirejs-config.js redirect module loading rather than extend a module.

  • map rewrites a module id. Under '*' it applies to every module that requires that id.
  • paths points an id at a file or an external URL. An array of values is a fallback list: if the first fails to load, the next is tried.
  • shim declares dependencies, and optionally an exports global, for a library that is not written as AMD.

When replacing is justified

Rarely. A mixin reaches any method on the target and survives upgrades, so it is the first choice. Replacement is the answer when a mixin cannot reach what you need: the module's dependency list is wrong for you, it returns a value no wrapper can usefully change, or a path is fixed inside it. If you are only overriding methods, use a mixin.

The risk

Your file becomes a fork frozen at the version you copied. Fixes Adobe ships to that core module in later 2.4.x releases, including security fixes, no longer reach the storefront, and nothing warns you. A global map is also global: two extensions mapping the same id silently conflict, and the last merged config wins.

The self-reference trap

A '*' map applies inside your replacement too. If your file requires the original id, RequireJS hands it your own module and the load never resolves. Add a context entry mapping that id back to the real path. After editing, run bin/magento cache:flush and rebuild static content.

Example

Example · javascript
// app/code/Vendor/Cart/view/frontend/requirejs-config.js

var config = {
    map: {
        // Every module asking for the core id now gets ours.
        '*': {
            'Magento_Catalog/js/catalog-add-to-cart':
                'Vendor_Cart/js/catalog-add-to-cart'
        },

        // Inside our own replacement, map the core id back to itself,
        // otherwise requiring it would load our file again.
        'Vendor_Cart/js/catalog-add-to-cart': {
            'Magento_Catalog/js/catalog-add-to-cart':
                'Magento_Catalog/js/catalog-add-to-cart'
        }
    },

    paths: {
        slick: [
            'https://cdn.example.com/slick/slick.min',
            'Vendor_Cart/js/lib/slick.min'
        ]
    },

    shim: {
        slick: {
            deps: ['jquery']
        }
    }
};

// On the storefront: every add to cart form runs your module instead of
// the core one, and the carousel loads from the CDN with the bundled copy
// as a fallback.
// Apply: bin/magento cache:flush, then clear pub/static/frontend and
// var/view_preprocessed. In production: setup:static-content:deploy -f

When to use it

  • Load a JavaScript library from a CDN with the local copy as a fallback.
  • Change the dependency list of a core module, which a mixin cannot do.
  • Make a pre-AMD library importable by declaring its global under shim exports.
  • Audit an inherited project for '*' map entries that silently fork core modules.

More examples

A replacement that extends rather than copies

If you must map, still build on the original instead of pasting its body, so core fixes to the methods you did not touch keep arriving. At this point the only thing map buys you over a mixin is control of the dependency list, which is the narrow case it is for.

Example · javascript
// app/code/Vendor/Cart/view/frontend/web/js/catalog-add-to-cart.js

define([
    'jquery',
    // Resolves to the real core file because of the context map entry.
    'Magento_Catalog/js/catalog-add-to-cart',
    'Vendor_Cart/js/model/stock-check',
    'jquery-ui-modules/widget'
], function ($, coreWidget, stockCheck) {
    'use strict';

    $.widget('mage.catalogAddToCart', coreWidget, {
        /** @inheritdoc */
        submitForm: function (form) {
            stockCheck.record(form);
            this._super(form);
        }
    });

    return $.mage.catalogAddToCart;
});

Shorthand and longhand for a shim entry

Both of the first two forms are the same configuration. Use the long form whenever you need exports, which is how a pre-AMD library that assigns itself to window becomes a normal dependency.

Example · javascript
// app/code/Vendor/Cart/view/frontend/requirejs-config.js

var config = {
    shim: {
        // Shorthand: an array is read as the deps list.
        slick: ['jquery'],

        // Longhand: the same thing written out.
        'another-lib': {
            deps: ['jquery']
        },

        // Only the longhand can name the global the library leaves behind,
        // which RequireJS then passes to your module.
        'legacy-lib': {
            deps: ['jquery'],
            exports: 'LegacyLib'
        }
    }
};

Pointing one id at a different file with paths

paths is the right key for a versioned vendor file. Keeping the version in the filename and the version-free alias in the config means a library upgrade touches this file only.

Example · javascript
// app/code/Vendor/Cart/view/frontend/requirejs-config.js

var config = {
    paths: {
        // paths keys are ids, values are file paths without .js,
        // relative to the static root of the area.
        'legacyTracker': 'Vendor_Cart/js/lib/tracker-2.1.0'
    },
    deps: [
        'legacyTracker'
    ]
};

// Upgrading the library later means changing one line here rather than
// every define() that depends on it.
// Apply: bin/magento cache:flush and rebuild static content.

Discussion

  • Be the first to comment on this lesson.