Magento 2所谓的“ mixins”是如何实现的?


16

Magento 2的基于RequireJS的对象系统包含一个称为“ mixins”的功能。Magento 2 mixin不是软件工程师通常认为的mixin / trait。相反,Magento 2 mixin允许您在主程序使用该对象/值之前修改RequireJS模块返回的对象/值。您可以这样配置Magento 2混合(通过requirejs-config.js文件)

var config = {
    'config':{
        'mixins': {
            //the module to modify
            'Magento_Checkout/js/view/form/element/email': {
                //your module that will do the modification
                'Pulsestorm_RequireJsRewrite/hook':true
            }
        }
    }
};

然后,您需要拥有hook.js(或您配置的任何RequireJS模块),

define([], function(){
    console.log("Hello");
    return function(theObjectReturnedByTheModuleWeAreHookingInto){
        console.log(theObjectReturnedByTheModuleWeAreHookingInto);
        console.log("Called");
        return theObjectReturnedByTheModuleWeAreHookingInto;
    };
});

返回一个函数。Magento将调用此函数,并传递对要修改的“模块”的引用。在我们的示例中,这将是RequireJS模块返回的对象Magento_Checkout/js/view/form/element/email。这也可能是一个函数,甚至是一个缩放器值(取决于RequireJS模块返回的值)。

该系统似乎已被调用,mixins因为如果原始RequireJS模块返回的对象支持该extend方法,则它允许您创建类似混合的行为。

define([], function(){
    'use strict';
    console.log("Hello");

    var mixin = {
        ourExtraMethod = function(){
            //...
        }
    };

    return function(theObjectReturnedByTheModuleWeAreHookingInto){
        console.log(theObjectReturnedByTheModuleWeAreHookingInto);
        console.log("Called");


        return theObjectReturnedByTheModuleWeAreHookingInto.extend(mixin);
    };
});

但是,系统本身只是连接模块对象创建的一种方法。

前言已完成-有人知道Magento 如何实现此功能吗?RequireJS网站上似乎没有提到mixins(尽管Google认为您可能需要RequireJS的插件页面)。

requirejs-config.js文件之外,Magento 2的核心javascript仅mixins在三个文件中提及

$ find vendor/magento/ -name '*.js' | xargs ack mixins
vendor/magento/magento2-base/lib/web/mage/apply/main.js
73:                            if (obj.mixins) {
74:                                require(obj.mixins, function () {
79:                                    delete obj.mixins;

vendor/magento/magento2-base/lib/web/mage/apply/scripts.js
39:            if (_.has(obj, 'mixins')) {
41:                data[key].mixins = data[key].mixins || [];
42:                data[key].mixins = data[key].mixins.concat(obj.mixins);
43:                delete obj.mixins;

vendor/magento/magento2-base/lib/web/mage/requirejs/mixins.js
5:define('mixins', [
24:     * Adds 'mixins!' prefix to the specified string.
30:        return 'mixins!' + name;
76:     * Iterativly calls mixins passing to them
80:     * @param {...Function} mixins
84:        var mixins = Array.prototype.slice.call(arguments, 1);
86:        mixins.forEach(function (mixin) {
96:         * Loads specified module along with its' mixins.
102:                mixins   = this.getMixins(path),
103:                deps     = [name].concat(mixins);
111:         * Retrieves list of mixins associated with a specified module.
114:         * @returns {Array} An array of paths to mixins.
118:                mixins = config[path] || {};
120:            return Object.keys(mixins).filter(function (mixin) {
121:                return mixins[mixin] !== false;
126:         * Checks if specified module has associated with it mixins.
137:         * the 'mixins!' plugin prefix if it's necessary.
172:    'mixins'
173:], function (mixins) {
237:        deps = mixins.processNames(deps, context);
252:            queueItem[1] = mixins.processNames(lastDeps, context);

mixins.js文件似乎是一个RequireJS插件(基于!...在评论中提到- ?这是正确的),但它不是100%清楚何时main.jsscripts.js通过Magento的调用,或自定义如何mixins配置使得它requirejs-config.js成为听者/挂机系统如上所述。

有没有人对这个系统的实现/实现/架构有任何解释,以期能够调试为什么可能会或可能不会使用“ mixin”?

Answers:


18

我想直接回答您的问题,然后尝试明确说明您可以使用mixins插件实际做什么。所以,首先是第一件事。

实作

这里最主要的是任何RequireJS插件都能完全接管某些文件的加载过程。这样可以在将模块的导出值作为已解决的依赖项传递之前对其进行修改。

看一下这个Magento自定义mixins插件实际上是什么的粗略实现:

// RequireJS config object.
// Like this one: app/code/Magento/Theme/view/base/requirejs-config.js
{
    //...

    // Every RequireJS plugin is a module and every module can
    // have it's configuration.
    config: {
        sampleMixinPlugin: {
            'path/to/the/sampleModule': ['path/to/extension']
        }
    }
}

define('sampleMixinPlugin', [
    'module'
] function (module) {
    'use strict';

    // Data that was defined in the previous step.
    var mixinsMap = module.config();

    return {
        /**
         * This method will be invoked to load a module in case it was requested
         * with a 'sampleMixinPlugin!' substring in it's path,
         * e.g 'sampleMixinPlugin!path/to/the/module'.
         */
        load: function (name, req, onLoad) {
            var mixinsForModule = [],
                moduleUrl = req.toUrl(name),
                toLoad;

            // Get a list of mixins that need to be applied to the module.
            if (name in mixinsMap) {
                mixinsForModule = mixinsMap[name];
            }

            toLoad = [moduleUrl].concat(mixinsForModule);

            // Load the original module along with mixins for it.
            req(toLoad, function (moduleExport, ...mixinFunctions) {
                // Apply mixins to the original value exported by the sampleModule.
                var modifiedExport = mixinFunctions.reduce(function (result, mixinFn) {
                        return mixinFn(result);
                }, moduleExport);

                // Tell RequireJS that this is what was actually loaded.
                onLoad(modifiedExport);
            });
        }
    }
});

最后也是最具挑战性的部分是动态添加“ sampleMixinPlugin!” 子字符串到请求的模块。为此,我们将拦截definerequire调用并修改依赖项列表,然后再将它们用原始的RequireJS加载方法处理。这有点棘手,lib/web/mage/requirejs/mixins.js如果您想了解其实现方式,建议您看一下实现。

调试

我建议执行以下步骤:

  • 确保“ mixins!”的配置!插件实际上在那里
  • 检查模块的路径是否已被修改。即从path/to/module变成mixins!path/to/module

最后但并非最不重要的requiresjs/mixins.js一点是,与main.jsscript.js模块无关,因为它们只能扩展从data-mage-init属性传递的配置:

<div data-mage-init='{
    "path/to/module": {
        "foo": "bar",
        "mixins": ["path/to/configuration-modifier"]
    }
}'></div>

我的意思是前两个文件不会弄乱模块返回的值,而是对实例的配置进行预处理。

使用范例

首先,我想设置一条记录,即所谓的“ mixins”(您对错误的命名是正确的)实际上允许以您想要的任何方式修改模块的导出值。我想说这是一种更通用的机制。

这是向模块导出的功能添加额外功能的快速示例:

// multiply.js
define(function () {
    'use strict';

    /**
     * Multiplies two numeric values.
     */
    function multiply(a, b) {
        return a * b;
    }

    return multiply;
});

// extension.js
define(function () {
    'use strict';

    return function (multiply) {
        // Function that allows to multiply an arbitrary number of values.
        return function () {
            var args = Array.from(arguments);

            return args.reduce(function (result, value) {
                return multiply(result, value);
            }, 1);
        };
    };
});

// dependant.js
define(['multiply'], function (multiply) {
    'use strict';

    console.log(multiply(2, 3, 4)); // 24
});

您可以为模块返回的任何对象/函数实现实际的mixin,而完全不需要依赖该extend方法。

扩展构造函数:

// construnctor.js
define(function () {
    'use strict';

    function ClassA() {
        this.property = 'foo';
    }

    ClassA.prototype.method = function () {
        return this.property + 'bar';
    }

    return ClassA;
});

// mixin.js
define(function () {
    'use strict';

    return function (ClassA) {
        var originalMethod = ClassA.prototype.method;

        ClassA.prototype.method = function () {
            return originalMethod.apply(this, arguments) + 'baz';
        };

        return ClassA;
    }
});

我希望这能回答您的问题。

问候。


谢谢!我一直在寻找的-我唯一要回答的问题是- mixins配置在做什么x-magento-init以及data-mage-init配置如何?即-在您上面的示例中,path/to/configuration-modifier还会返回可以修改配置数据的回调吗?或者是其他东西?
艾伦·风暴

是的,正好!它应该返回一个回调,您可以从中修改配置数据。
丹尼斯·鲁

您似乎非常了解前端方面的知识-对这两个问题有何见解?magento.stackexchange.com/questions/147899/... magento.stackexchange.com/questions/147880/...
艾伦风暴

4

完善Denis Rul的 答案

因此,如果您查看Magento页面,则这是<script/>加载Magento 的三个标签。

<script  type="text/javascript"  src="http://magento.example.com/pub/static/frontend/Magento/luma/en_US/requirejs/require.js"></script>
<script  type="text/javascript"  src="http://magento.example.com/pub/static/frontend/Magento/luma/en_US/mage/requirejs/mixins.js"></script>
<script  type="text/javascript"  src="http://magento.example.com/pub/static/_requirejs/frontend/Magento/luma/en_US/requirejs-config.js"></script>

这是RequireJS本身(require.js),mixins.js插件和合并的RequireJS配置requirejs-config.js)。

mixins.js文件定义了RequireJS插件。此插件负责加载和调用RequireJS模块,以侦听其他RequireJS模块的实例化。

定义mixin插件后,该插件包含一个requirejs程序。

require([
    'mixins'
], function (mixins) {
    'use strict';
    //...

    /**
     * Overrides global 'require' method adding to it dependencies modfication.
     */
    window.require = function (deps, callback, errback, optional) {
        //...
    };

    //...

    window.define = function (name, deps, callback) {
        //...
    };

    window.requirejs = window.require;
});

这第二个程序加载刚刚定义mixins的插件作为依赖,然后重新定义了全球requiredefinerequirejs功能。这种重新定义使“不是真正的mixin”系统能够在将事物传递回常规函数之前,钩入RequireJS模块的初始实例。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.