How/Where does Magento Convert a RequireJS module Name into a URL?
RequireJS has a feature that allows you to set a custom baseUrl.
Magento generates baseUrl for RequireJS in the body of the page, where JS resources are requested. So, it's basically one more part of RequireJS config generated by Magento. This baseUrl is calculated on server side and is based on current theme, locale and base URL for static view files for the store. Then native RequireJS functionality calculates full path as follows
baseUrl + file + '.js'
You are wondering how
Module_Name/js/path/to/module
becomes
//magento.example.com/static/frontend/Package/Theme/locale/Module_Name/js/path/to/module.js
First of all, it's important to understand that this is entirely requireJS, not any Magento special sauce (, although there is some of that elsewhere). For the most part, the front-end uses only the normal behaviour of RequireJS. The magic is usually in how it generates pub/static
, namely how view/area/web/js/path/to/module.js
is symlinked to pub/static/area/Package/theme/Module_Name/js/path/to/module.js
. This is handled by Magento's static asset compilation process, and I don't go into it here.
requirejs-config.js
Let's introduce a new file, which you mention: requirejs-config.js
. This is some Magento 2 special sauce, but probably not as much as you might think.
This file can be any JavaScript, but should at the very least declare a (global) variable called config
. The object bound to config
is passed directly to requireJS to configure it.
The way this works is Magento finds all of the requirejs-config.js
in a project. These can be in a module, under view/area
, or in a theme, in its root directory, and in a theme's module override, e.g. Magento_Catalog/requirejs-config.js
. Note that this does not include any child of a web
directory. This file, should, in general be a sibling to a web
directory.
Once globbed, each file is decorated in a closure (so our global variable actually isn't), and a line at the end of the closure passes the config
variable to the require
object. This can be seen:
This is Magento_Checkout::view/frontend/requirejs-config.js
:
/**
* Copyright © 2015 Magento. All rights reserved.
* See COPYING.txt for license details.
*/
var config = {
map: {
'*': {
discountCode: 'Magento_Checkout/js/discount-codes',
shoppingCart: 'Magento_Checkout/js/shopping-cart',
regionUpdater: 'Magento_Checkout/js/region-updater',
opcOrderReview: 'Magento_Checkout/js/opc-order-review',
sidebar: 'Magento_Checkout/js/sidebar',
payment: 'Magento_Checkout/js/payment',
paymentAuthentication: 'Magento_Checkout/js/payment-authentication'
}
}
};
When it gets to the front-end, it will look like this:
(function() {
/**
* Copyright © 2015 Magento. All rights reserved.
* See COPYING.txt for license details.
*/
var config = {
map: {
'*': {
discountCode: 'Magento_Checkout/js/discount-codes',
shoppingCart: 'Magento_Checkout/js/shopping-cart',
regionUpdater: 'Magento_Checkout/js/region-updater',
opcOrderReview: 'Magento_Checkout/js/opc-order-review',
sidebar: 'Magento_Checkout/js/sidebar',
payment: 'Magento_Checkout/js/payment',
paymentAuthentication: 'Magento_Checkout/js/payment-authentication'
}
}
};
require.config(config);
})();
This decoration can be seen in Magento\Framework\RequireJs\Config
.
Each of these decorated files is concatenated and dumped in static/_requirejs/area/Package/theme/locale/secure/requirejs-config.js
. This location is agreed upon ahead of time, so that the HTML loads this script as it loads requireJS:
<script type="text/javascript" src="https://magento.example.com/static/area/Package/theme/locale/requirejs/require.js"></script>
<script type="text/javascript" src="https://magento.example.com/static/_requirejs/area/Package/theme/locale/secure/requirejs-config.js"></script>
I consider how to configure RequireJS out of scope for this answer, but they have fairly good documentation on this. There are two important things to note, though:
- Successive calls to
require.config
will layer objects on top of each other, so last write wins. They don't replace, which is crucial. - There is one configuration at the top of this configuration that sets baseUrl. This is not in a
requirejs-config.js
. This is inserted at compile time byMagento\Framework\RequireJs\Config
.
Forgetting for a moment how Magento works out which RequireJS modules need loading (a good discussion for another time, perhaps; As a hint, look in mage/apply/main.js
), let's suppose we have the code:
require(['modulename'], function () {});
in a vacuum somewhere. How does Magento know what to do?
Well, the first thing requireJS will do is look up modulename
in its mapping. In our case, it will learn that it should treat all requests to modulename
as a request to Module_Name/js/path/to/module
. It only does this once. Mappings are not recursive. I repeat. If you have a mapping from a
to b
, and from b
to a
, this will swap each request, and will not cause an infinite loop.
Once we have gone through the mapping brouhaha, RequireJS looks at what it has. If it ends in .js
, and doesn't look like an absolute link or a URL, it will prepend the configured baseUrl
and load that script through its normal procedures. If it doesn't end in .js
and isn't an absolute link or URL, it will add .js
to the end, then prepend the configured baseUrl
and load through its normal procedures. If requireJS reckons it has a URL, it just tries to load that.