/** * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ define('mixins', [ 'module' ], function (module) { 'use strict'; var contexts = require.s.contexts, defContextName = '_', defContext = contexts[defContextName], unbundledContext = require.s.newContext('$'), defaultConfig = defContext.config, unbundledConfig = { baseUrl: defaultConfig.baseUrl, paths: defaultConfig.paths, shim: defaultConfig.shim, config: defaultConfig.config, map: defaultConfig.map }, rjsMixins; /** * Prepare a separate context where modules are not assigned to bundles * so we are able to get their true path and corresponding mixins. */ unbundledContext.configure(unbundledConfig); /** * Checks if specified string contains * a plugin spacer '!' substring. * * @param {String} name - Name, path or alias of a module. * @returns {Boolean} */ function hasPlugin(name) { return !!~name.indexOf('!'); } /** * Adds 'mixins!' prefix to the specified string. * * @param {String} name - Name, path or alias of a module. * @returns {String} Modified name. */ function addPlugin(name) { return 'mixins!' + name; } /** * Removes base url from the provided string. * * @param {String} url - Url to be processed. * @param {Object} config - Contexts' configuration object. * @returns {String} String without base url. */ function removeBaseUrl(url, config) { var baseUrl = config.baseUrl || '', index = url.indexOf(baseUrl); if (~index) { url = url.substring(baseUrl.length - index); } return url; } /** * Extracts url (without baseUrl prefix) * from a module name ignoring the fact that it may be bundled. * * @param {String} name - Name, path or alias of a module. * @param {Object} config - Context's configuration. * @returns {String} */ function getPath(name, config) { var url = unbundledContext.require.toUrl(name); return removeBaseUrl(url, config); } /** * Checks if specified string represents a relative path (../). * * @param {String} name - Name, path or alias of a module. * @returns {Boolean} */ function isRelative(name) { return !!~name.indexOf('./'); } /** * Iteratively calls mixins passing to them * current value of a 'target' parameter. * * @param {*} target - Value to be modified. * @param {...Function} mixins - List of mixins to apply. * @returns {*} Modified 'target' value. */ function applyMixins(target) { var mixins = Array.prototype.slice.call(arguments, 1); mixins.forEach(function (mixin) { target = mixin(target); }); return target; } rjsMixins = { /** * Loads specified module along with its' mixins. * This method is called for each module defined with "mixins!" prefix * in its name that was added by processNames method. * * @param {String} name - Module to be loaded. * @param {Function} req - Local "require" function to use to load other modules. * @param {Function} onLoad - A function to call with the value for name. * @param {Object} config - RequireJS configuration object. */ load: function (name, req, onLoad, config) { var path = getPath(name, config), mixins = this.getMixins(path), deps = [name].concat(mixins); req(deps, function () { onLoad(applyMixins.apply(null, arguments)); }); }, /** * Retrieves list of mixins associated with a specified module. * * @param {String} path - Path to the module (without base URL). * @returns {Array} An array of paths to mixins. */ getMixins: function (path) { var config = module.config() || {}, mixins; // Fix for when urlArgs is set. if (path.indexOf('?') !== -1) { path = path.substring(0, path.indexOf('?')); } mixins = config[path] || {}; return Object.keys(mixins).filter(function (mixin) { return mixins[mixin] !== false; }); }, /** * Checks if specified module has associated with it mixins. * * @param {String} path - Path to the module (without base URL). * @returns {Boolean} */ hasMixins: function (path) { return this.getMixins(path).length; }, /** * Modifies provided names prepending to them * the 'mixins!' plugin prefix if it's necessary. * * @param {(Array|String)} names - Module names, paths or aliases. * @param {Object} context - Current RequireJS context. * @returns {Array|String} */ processNames: function (names, context) { var config = context.config; /** * Prepends 'mixin' plugin to a single name. * * @param {String} name * @returns {String} */ function processName(name) { var path = getPath(name, config); if (!hasPlugin(name) && (isRelative(name) || rjsMixins.hasMixins(path))) { return addPlugin(name); } return name; } return typeof names !== 'string' ? names.map(processName) : processName(names); } }; return rjsMixins; }); require([ 'mixins' ], function (mixins) { 'use strict'; var contexts = require.s.contexts, defContextName = '_', defContext = contexts[defContextName], originalContextRequire = defContext.require, processNames = mixins.processNames; /** * Wrap default context's require function which gets called every time * module is requested using require call. The upside of this approach * is that deps parameter is already normalized and guaranteed to be an array. */ defContext.require = function (deps, callback, errback) { deps = processNames(deps, defContext); return originalContextRequire(deps, callback, errback); }; /** * Copy properties of original 'require' method. */ Object.keys(originalContextRequire).forEach(function (key) { defContext.require[key] = originalContextRequire[key]; }); /** * Wrap shift method from context's definitions queue. * Items are added to the queue when a new module is defined and taken * from it every time require call happens. */ defContext.defQueue.shift = function () { var queueItem = Array.prototype.shift.call(this); queueItem[1] = processNames(queueItem[1], defContext); return queueItem; }; });