¿Cómo se implementan las cosas que Magento 2 llama "mixins"?

16

Los sistemas de objetos basados ​​en RequireJS de Magento 2 contienen una característica llamada "mixins". Un Magento 2 mixin no es lo que un ingeniero de software normalmente consideraría como un mixin / rasgo . En cambio, un mixin de Magento 2 le permite modificar el objeto / valor devuelto por un módulo RequireJS antes de que el programa principal use ese objeto / valor. Configura un Magento 2 mixin como este (a través de un archivo 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
            }
        }
    }
};

Luego, debe tener hook.js(o cualquier módulo RequireJS que haya configurado),

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

devolver una función Magento llamará a esta función, pasando una referencia al "módulo" que desea modificar. En nuestro ejemplo, este será el objeto devuelto por el módulo RequireJS Magento_Checkout/js/view/form/element/email. Esto también puede ser una función, o incluso un valor de escala (dependiendo de lo que devuelva el módulo RequireJS).

Parece que se llama a este sistema mixinsporque le permite crear un comportamiento similar a la mezcla si el objeto devuelto por el módulo RequireJS original admite el extendmétodo.

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

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

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


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

Sin embargo, el sistema en sí mismo es solo una forma de conectarse a la creación de objetos de módulo.

Preámbulo terminado: ¿alguien sabe cómo Magento ha implementado esta funcionalidad? El sitio web RequireJS no parece mencionar mixins (aunque Google cree que puede querer la página de complementos de RequireJS ).

Fuera de los requirejs-config.jsarchivos, el núcleo de JavaScript de Magento 2 solo menciona mixinstres archivos

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

El mixins.jsarchivo parece ser un RequireJS plugins (basado en el !...menciones en los comentarios -? Es este derecho), pero no es 100% claro cuando main.jso scripts.jsse invocan por Magento, o cómo la costumbre mixinsde configuración hace que sea desde requirejs-config.jsdentro del sistema oyente / gancho descrito arriba.

¿Alguien tiene una explicación de cómo este sistema fue / es implementado / diseñado, con miras a poder depurar por qué un "mixin" puede o no ser aplicado?

Alan Storm
fuente

Respuestas:

18

Me gustaría ir directamente a sus preguntas y luego trataré de dejar en claro lo que realmente puede hacer con el complemento mixins . Entonces, lo primero es lo primero.

Implementación

Lo principal aquí es la capacidad de cualquier complemento RequireJS para hacerse cargo por completo del proceso de carga de ciertos archivos. Esto permite modificar el valor de exportación de un módulo antes de que se pase como una dependencia resuelta.

Eche un vistazo a esta implementación incompleta de lo que en realidad es el complemento Mins de Mixins personalizado :

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

La última y más desafiante parte es anteponer dinámicamente el 'sampleMixinPlugin!' subcadena a los módulos solicitados. Para hacer esto, interceptamos definee requireinvocamos y modificamos la lista de dependencias antes de que sean procesadas por el método de carga RequireJS original. Es un poco complicado y recomendaría mirar la implementación lib/web/mage/requirejs/mixins.jssi quieres cómo funciona.

Depuración

Recomiendo estos pasos:

  • Asegúrese de que la configuración de 'mixins!' El complemento está realmente allí .
  • Compruebe que la ruta a un módulo se está modificando . Es decir, se convierte de path/to/modulea mixins!path/to/module.

Y el último, pero no menos importante, requiresjs/mixins.jsno tiene nada que ver con los módulos main.jso script.jsya que solo pueden extender la configuración que se pasa desde el data-mage-initatributo:

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

Quiero decir que los dos archivos anteriores no se meten con el valor devuelto por un módulo, sino que preprocesan la configuración de una instancia.

Ejemplos de uso

Para empezar, me gustaría aclarar el registro de manera que los llamados "mixins" (tiene razón sobre la denominación errónea) realmente permiten modificar el valor exportado de un módulo de la forma que desee. Yo diría que este es un mecanismo mucho más genérico.

Aquí hay una muestra rápida de cómo agregar funcionalidad adicional a la función que exporta un módulo:

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

Puede implementar una combinación real para cualquier objeto / función devuelta por un módulo y no necesita depender del extendmétodo en absoluto.

Extender una función constructora:

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

Espero que esto responda sus preguntas.

Saludos.

Denis Rul
fuente
¡Gracias! Justo lo que estaba buscando, la única otra pregunta que tengo es: ¿qué hace la mixinsconfiguración x-magento-inity las data-mage-initconfiguraciones? es decir, en su ejemplo anterior, ¿ path/to/configuration-modifiertambién devolvería una devolución de llamada que podría modificar los datos de configuración? ¿O algo mas?
Alan Storm
Si, precisamente! Se supone que devuelve una devolución de llamada desde la que puede modificar los datos de configuración.
Denis Rul
parece que conoces bastante bien las cosas del front-end: ¿alguna idea de estas dos preguntas? magento.stackexchange.com/questions/147899/… magento.stackexchange.com/questions/147880/…
Alan Storm
4

Para completar la respuesta de Denis Rul .

Entonces, si mira una página de Magento, aquí están las tres <script/>etiquetas que cargan 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>

Este es RequireJS en sí ( require.js), el mixins.jscomplemento y la configuración de RequireJS combinada ( requirejs-config.js).

El mixins.jsarchivo define un complemento RequireJS. Este complemento es responsable de cargar y llamar a los módulos RequireJS que escuchan la instanciación de otros módulos RequireJS.

Este complemento también contiene un programa requirejs después de definir el complemento 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;
});

Este segundo programa se carga la acaban de definir mixinsplug-in como una dependencia, y luego redefine los mundiales require, definey requirejsfunciones. Esta redefinición es lo que permite que el sistema "no sea realmente un mixin" se enganche en la instanciación inicial del módulo RequireJS antes de devolver las cosas a las funciones normales.

Alan Storm
fuente