Jak wdrażane są rzeczy, które Magento 2 nazywa „miksami”?

16

Systemy obiektowe oparte na RequireJS w Magento 2 zawierają funkcję o nazwie „mixins”. Mixin Magento 2 nie jest tym, co inżynier oprogramowania normalnie uważa za mixin / cechę . Zamiast tego, mixin Magento 2 pozwala modyfikować obiekt / wartość zwróconą przez moduł RequireJS, zanim ten obiekt / wartość zostanie wykorzystany przez program główny. Konfigurujesz mixin Magento 2 w ten sposób (za pomocą pliku requjs-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
            }
        }
    }
};

Następnie musisz mieć hook.js(lub dowolny skonfigurowany moduł RequireJS),

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

zwraca funkcję. Magento wywoła tę funkcję, przekazując odwołanie do „modułu”, który chcesz zmodyfikować. W naszym przykładzie będzie to obiekt zwrócony przez moduł RequireJS Magento_Checkout/js/view/form/element/email. Może to być również funkcja lub nawet wartość skalera (w zależności od tego, co zwraca moduł RequireJS).

Wygląda na to, mixinsże ten system jest wywoływany, ponieważ pozwala tworzyć zachowanie podobne do miksowania, jeśli obiekt zwrócony przez oryginalny moduł RequireJS obsługuje tę extendmetodę.

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

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

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


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

Jednak sam system jest tylko sposobem na przyłączenie się do tworzenia obiektów modułowych.

Preambuła zakończona - czy ktoś wie, jak Magento wdrożyło tę funkcjonalność? Witryna RequireJS nie wydaje się wymieniać wtyczek (chociaż Google uważa, że ​​możesz chcieć strony wtyczek RequireJS ).

Poza requirejs-config.jsplikami, podstawowy javascript Magento 2 wspomina tylko mixinsw trzech plikach

$ 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.jsPlik wydaje się być RequireJS plugin (na podstawie !...wzmianki w komentarzach - czy to prawda), ale nie jest to w 100% jasne, kiedy main.jsalbo scripts.jssą wywoływane przez Magento, albo jak zwyczaj mixinskonfiguracja sprawia, że od requirejs-config.jsdo słuchacza / system hakowy opisane powyżej.

Czy ktoś ma wyjaśnienie, w jaki sposób ten system został / jest wdrożony / opracowany, z myślą o możliwości debugowania, dlaczego „mixin” może być zastosowany?

Alan Storm
źródło

Odpowiedzi:

18

Chciałbym przejść od razu do twoich pytań, a następnie postaram się wyjaśnić, co naprawdę możesz zrobić z wtyczką mixins . Najpierw najważniejsze.

Realizacja

Najważniejsze jest to, że dowolna wtyczka RequireJS może całkowicie przejąć proces ładowania niektórych plików. Pozwala to zmodyfikować wartość eksportu modułu, zanim zostanie on przekazany jako rozwiązana zależność.

Spójrz na ten szkic realizacji co Magento niestandardowe wstawek Plugin jest w rzeczywistości:

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

Ostatnią i najtrudniejszą częścią jest dynamiczne przygotowanie „sampleMixinPlugin!” podciąg do żądanych modułów. W tym celu przechwytujemy definei requirewywołujemy oraz modyfikujemy listę zależności, zanim zostaną one przetworzone oryginalną metodą ładowania RequireJS. To trochę trudne i polecam przyjrzeć się implementacji, lib/web/mage/requirejs/mixins.jsjeśli chcesz, jak to działa.

Debugowanie

Poleciłbym następujące kroki:

  • Upewnij się, że konfiguracja „mixins!” wtyczka faktycznie tam jest .
  • Sprawdź, czy ścieżka do modułu jest modyfikowana . To znaczy zmienia się z path/to/modulena mixins!path/to/module.

I na koniec, nie requiresjs/mixins.jsma to nic wspólnego z modułami main.jslub script.js, ponieważ mogą one jedynie rozszerzyć konfigurację przekazywaną z data-mage-initatrybutu:

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

Mam na myśli to, że dwa poprzednie pliki nie bałaganią wartości zwróconej przez moduł, zamiast tego wstępnie przetwarzają konfigurację instancji.

Przykłady użycia

Na początek chciałbym ustawić rekord prosto, że tak zwane „miksy” (masz rację co do błędnego nazewnictwa) pozwalają na modyfikację eksportowanej wartości modułu w dowolny sposób. Powiedziałbym, że jest to mechanizm o wiele bardziej ogólny.

Oto krótka próbka dodania dodatkowej funkcjonalności do funkcji eksportowanej przez moduł:

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

Możesz zaimplementować rzeczywistą składankę dla dowolnego obiektu / funkcji zwróconej przez moduł i nie musisz wcale polegać na extendmetodzie.

Rozszerzanie funkcji konstruktora:

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

Mam nadzieję, że to odpowiada na twoje pytania.

Pozdrowienia.

Denis Rul
źródło
Dziękuję Ci! Właśnie tego szukałem - jedyne inne pytanie, jakie mam - to, co robi mixinskonfiguracja x-magento-initi data-mage-initkonfiguracje? tj. - w powyższym przykładzie zwróciłbyś path/to/configuration-modifierrównież wywołanie zwrotne, które mogłoby zmodyfikować dane konfiguracyjne? Albo coś innego?
Alan Storm
Tak, dokładnie! Ma to zwrócić wywołanie zwrotne, z którego można zmodyfikować dane konfiguracyjne.
Denis Rul
Wygląda na to, że znasz się dobrze na froncie - masz wgląd w te dwa pytania? magento.stackexchange.com/questions/147899/... magento.stackexchange.com/questions/147880/…
Alan Storm
4

Aby zaokrąglić się Denis RUL za odpowiedź .

Tak więc, jeśli spojrzysz na stronę Magento, oto trzy <script/>tagi, które ładują 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>

Jest to sam RequireJS ( require.js), mixins.jswtyczka i scalona konfiguracja RequireJS ( requirejs-config.js).

mixins.jsPlik definiuje RequireJS wtyczki. Ta wtyczka jest odpowiedzialna za ładowanie i wywoływanie modułów RequireJS, które nasłuchują instancji innych modułów RequireJS.

Ta wtyczka zawiera również program RequJS po zdefiniowaniu wtyczki 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;
});

Ten drugi ładunki programu sprawiedliwy zdefiniowane mixinsplugin jako zależność, a następnie na nowo definiuje globalne require, definei requirejsfunkcje. Ta redefinicja pozwala systemowi „tak naprawdę nie mieszającemu” podłączyć się do początkowej instancji modułu RequireJS przed przekazaniem rzeczy z powrotem do zwykłych funkcji.

Alan Storm
źródło