Come vengono implementate le cose che Magento 2 chiama "mixin"?


16

I sistemi di oggetti basati su RequireJS di Magento 2 contengono una funzione chiamata "mixin". Un mixin Magento 2 non è ciò che un ingegnere del software normalmente considererebbe un mixin / tratto . Invece, un mixin Magento 2 consente di modificare l'oggetto / valore restituito da un modulo RequireJS prima che l'oggetto / valore venga utilizzato dal programma principale. Configura un mixin Magento 2 in questo modo (tramite un file requestjs-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
            }
        }
    }
};

Quindi, devi avere hook.js(o qualsiasi modulo RequireJS che hai configurato),

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

restituisce una funzione. Magento chiamerà questa funzione, passando un riferimento al "modulo" che si desidera modificare. Nel nostro esempio questo sarà l'oggetto restituito dal modulo RequireJSMagento_Checkout/js/view/form/element/email . Potrebbe anche trattarsi di una funzione o persino di un valore dello scaler (a seconda di cosa restituisce il modulo RequireJS).

Questo sistema sembra essere chiamato mixinsperché consente di creare un comportamento simile al mixin se l'oggetto restituito dal modulo RequireJS originale supporta il extendmetodo.

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

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

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


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

Tuttavia, il sistema stesso è solo un modo per agganciarsi alla creazione di oggetti del modulo.

Preambolo finito: qualcuno sa come Magento ha implementato questa funzionalità? Il sito web RequireJS non sembra menzionare i mixin (anche se Google pensa che potresti voler la pagina dei plugin di RequireJS ).

Al di fuori dei requirejs-config.jsfile, il core javascript di Magento 2 menziona solo mixinsin tre file

$ 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);

Il mixins.jsfile sembra essere un plug-in RequireJS (basato sulle !...menzioni nei commenti - è giusto?) Ma non è chiaro al 100% quando main.jso scripts.jssono invocati da Magento o come la mixinsconfigurazione personalizzata lo fa entrare requirejs-config.jsnel sistema listener / hook descritto sopra.

Qualcuno ha una spiegazione di come questo sistema è stato / è implementato / architected, con un occhio verso la possibilità di eseguire il debug perché un "mixin" può o meno essere applicato?

Risposte:


18

Vorrei andare direttamente alle tue domande e poi proverò a chiarire cosa puoi effettivamente fare con il plugin mixins . Quindi, per prima cosa.

Implementazione

La cosa principale qui è la capacità di qualsiasi plug-in RequireJS di assumere completamente il processo di caricamento di determinati file. Ciò consente di modificare il valore di esportazione di un modulo prima che venga passato come dipendenza risolta.

Dai un'occhiata a questa implementazione imprecisa di ciò che è in realtà il plug-in di mixin personalizzati Magento :

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

L'ultima e la parte più impegnativa è anteporre dinamicamente il "sampleMixinPlugin!" sottostringa ai moduli richiesti. Per fare ciò intercettiamo definee requireinvociamo e modifichiamo l'elenco delle dipendenze prima che vengano elaborate dal metodo di caricamento RequireJS originale. È un po 'complicato e consiglierei di guardare l'implementazione lib/web/mage/requirejs/mixins.jsse vuoi come funziona.

Debug

Consiglierei questi passaggi:

  • Assicurati che la configurazione per "mixin!" il plugin è effettivamente .
  • Verificare che il percorso di un modulo sia in fase di modifica . Cioè gira da path/to/modulea mixins!path/to/module.

E l'ultimo ma non meno importante, requiresjs/mixins.jsnon ha nulla a che fare con i moduli main.jso script.jsin quanto possono solo estendere la configurazione che viene passata dall'attributo data-mage-init:

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

Voglio dire che i primi due file non scherzano con il valore restituito da un modulo, ma pre-processano la configurazione di un'istanza.

Esempi di utilizzo

Tanto per cominciare, vorrei mettere subito a posto il record che i cosiddetti "mixin" (hai ragione riguardo al malinteso) in realtà consentono di modificare il valore esportato di un modulo nel modo desiderato. Direi che questo è un meccanismo molto più generico.

Ecco un breve esempio di aggiunta di funzionalità extra alla funzione esportata da un modulo:

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

È possibile implementare un mixin effettivo per qualsiasi oggetto / funzione restituito da un modulo e non è necessario dipendere extendaffatto dal metodo.

Estensione di una funzione di costruzione:

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

Spero che questo risponda alle tue domande.

Saluti.


Grazie! Proprio quello che stavo cercando - l'unica altra domanda che avrei è - cosa fanno la mixinsconfigurazione x-magento-inite le data-mage-initconfigurazioni? cioè - nell'esempio sopra riportato, verrebbe path/to/configuration-modifierrestituito anche un callback che potrebbe modificare i dati di configurazione? O qualcos'altro?
Alan Storm,

Sì, appunto! Dovrebbe restituire un callback dal quale è possibile modificare i dati di configurazione.
Denis Rul,

sembri conoscere abbastanza bene le cose del front-end - qualche idea su queste due domande? magento.stackexchange.com/questions/147899/… magento.stackexchange.com/questions/147880/…
Alan Storm

4

Per completare la risposta di Denis Rul .

Quindi, se guardi una pagina Magento, ecco i tre <script/>tag che caricano 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>

Questo è RequireJS stesso ( require.js), il mixins.jsplug-in e la configurazione RequireJS unita ( requirejs-config.js).

Il mixins.js file definisce un plug-in RequireJS. Questo plug-in è responsabile del caricamento e della chiamata dei moduli RequireJS che ascoltano l'istanza di altri moduli RequireJS.

Questo plugin contiene anche un programma requestjs dopo aver definito il plugin 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;
});

Questo secondo programma carica il proprio definiti mixinsplug-in come una dipendenza, e quindi ridefinisce le globali require, definee le requirejsfunzioni. Questa ridefinizione è ciò che consente al sistema "non proprio un mixin" di collegarsi all'istanza iniziale del modulo RequireJS prima di riportare le cose alle normali funzioni.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.