Как вещи Magento 2 звонков «Примесь» Реализована?

16

В объектных системах на основе RequireJS в Magento 2 есть функция, называемая «mixins». Миксин в Magento 2 - это не то, что инженер-программист обычно воспринимает как миксин / черта . Вместо этого, Maginto 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 , похоже, не упоминаются миксины (хотя Google считает, что вам может понадобиться страница плагинов для RequireJS ).

Вне requirejs-config.jsфайлов ядро ​​Magento 2 упоминается только 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.jsили scripts.jsвызывается Magento, или как пользовательские mixinsнастройки делают его из requirejs-config.jsв систему слушателя / крючке описано выше.

Есть ли у кого-нибудь объяснение того, как эта система была реализована / реализована / спроектирована, с учетом возможности отладки, почему «миксин» может или не может быть применен?

Алан Сторм
источник

Ответы:

18

Я хотел бы перейти прямо к вашим вопросам, а затем я постараюсь прояснить, что вы действительно можете сделать с плагином mixins . Итак, обо всем по порядку.

Реализация

Главное здесь - способность любого плагина RequireJS полностью взять на себя процесс загрузки определенных файлов. Это позволяет изменить значение экспорта модуля, прежде чем оно будет передано в качестве разрешенной зависимости.

Взгляните на эту схематичную реализацию того, чем на самом деле является плагин Magento Custom 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!» подстрока к запрашиваемым модулям. Для этого мы перехватываем defineи requireвызываем и модифицируем список зависимостей, прежде чем они будут обработаны оригинальным методом загрузки RequireJS. Это немного сложно, и я бы посоветовал взглянуть на реализацию, lib/web/mage/requirejs/mixins.jsесли вы хотите, как она работает.

Отладка

Я бы порекомендовал следующие шаги:

  • Убедитесь, что конфигурация для 'mixins!' плагин на самом деле там .
  • Убедитесь, что путь к модулю изменяется . Т.е. получается из path/to/moduleв mixins!path/to/module.

И последнее, но не менее важное, не requiresjs/mixins.jsимеет ничего общего с модулями main.jsor, script.jsпоскольку они могут только расширять конфигурацию, передаваемую из data-mage-initатрибута:

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

Я имею в виду, что первые два файла не связываются со значением, возвращаемым модулем, вместо этого они предварительно обрабатывают конфигурацию экземпляра.

Примеры использования

Начнем с того, что я бы хотел исправить ситуацию, так как так называемые «миксины» (вы правы насчет неправильного именования) фактически позволяют изменять экспортированное значение модуля любым способом, который вы хотите. Я бы сказал, что это более общий механизм.

Вот быстрый пример добавления дополнительной функциональности к функции, экспортируемой модулем:

// 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
});

Вы можете реализовать фактический миксин для любого объекта / функции, возвращаемого модулем, и вам вообще не нужно зависеть от 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

Завершить ответ Дениса Рула .

Итак, если вы посмотрите на страницу 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.

Этот плагин также содержит программу requirejs после определения плагина mixin.

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плагин , как зависимость, а затем переопределяет глобальную require, defineи requirejsфункцию. Это переопределение - это то, что позволяет системе «не совсем смешивать» подключиться к первоначальной реализации модуля RequireJS перед передачей вещей обратно обычным функциям.

Алан Сторм
источник