Comment les choses que Magento 2 appelle les «mixins» sont-elles implémentées?

16

Les systèmes d'objets basés sur RequireJS de Magento 2 contiennent une fonctionnalité appelée "mixins". Un mixage Magento 2 n'est pas ce qu'un ingénieur logiciel devrait normalement considérer comme un mixage / trait . Au lieu de cela, un mix Magento 2 vous permet de modifier l'objet / la valeur retournée par un module RequireJS avant que cet objet / la valeur ne soit utilisé par le programme principal. Vous configurez un mixage Magento 2 comme ceci (via un fichier 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
            }
        }
    }
};

Ensuite, vous devez avoir hook.js(ou le module RequireJS que vous avez configuré),

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

retourner une fonction. Magento appellera cette fonction en passant une référence au "module" que vous souhaitez modifier. Dans notre exemple, ce sera l'objet renvoyé par le module RequireJS Magento_Checkout/js/view/form/element/email. Il peut également s'agir d'une fonction, voire d'une valeur de mise à l'échelle (selon ce que le module RequireJS renvoie).

Ce système semble être appelé mixinscar il vous permet de créer un comportement de type mixin si l'objet renvoyé par le module RequireJS d'origine prend en charge la extendméthode.

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

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

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


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

Cependant, le système lui-même n'est qu'un moyen de se connecter à la création d'objets de module.

Préambule terminé - quelqu'un sait-il comment Magento a mis en œuvre cette fonctionnalité? Le site Web de RequireJS ne semble pas mentionner les mixins (bien que Google pense que vous voudrez peut-être la page du plugin de RequireJS ).

En dehors des requirejs-config.jsfichiers, le noyau javascript de Magento 2 ne mentionne que mixinstrois fichiers

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

Le mixins.jsfichier semble être un plugin RequireJS (basé sur les !...mentions dans les commentaires - est-ce vrai?) Mais il n'est pas clair à 100% quand main.jsou scripts.jssont invoqués par Magento, ou comment la mixinsconfiguration personnalisée le fait à partir requirejs-config.jsdu système d'écoute / crochet décrit ci-dessus.

Quelqu'un a-t-il une explication sur la façon dont ce système a été / est mis en œuvre / architecturé, en vue de pouvoir déboguer pourquoi un "mixin" peut ou non être appliqué?

Alan Storm
la source

Réponses:

18

J'aimerais passer directement à vos questions, puis j'essaierai de clarifier ce que vous pouvez réellement faire avec le plugin mixins . Donc, tout d'abord.

la mise en oeuvre

L'essentiel ici est la capacité de tout plugin RequireJS à prendre complètement en charge le processus de chargement de certains fichiers. Cela permet de modifier la valeur d'exportation d'un module avant qu'elle ne soit transmise en tant que dépendance résolue.

Jetez un œil à cette implémentation sommaire de ce qu'est réellement le plugin de mixage personnalisé 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);
            });
        }
    }
});

La dernière et la plus difficile consiste à ajouter dynamiquement le «sampleMixinPlugin! sous-chaîne aux modules demandés. Pour ce faire, nous interceptons defineet requireinvoquons et modifions la liste des dépendances avant qu'elles ne soient traitées par la méthode de chargement RequireJS d'origine. C'est un peu délicat et je recommanderais de regarder l'implémentation lib/web/mage/requirejs/mixins.jssi vous voulez comment cela fonctionne.

Débogage

Je recommanderais ces étapes:

  • Assurez-vous que la configuration pour 'mixins!' le plugin est réellement .
  • Vérifiez que le chemin d'accès à un module est en cours de modification . C'est-à-dire que cela passe de path/to/moduleà mixins!path/to/module.

Et le dernier mais non le moindre, requiresjs/mixins.jsn'a rien à voir avec les modules main.jsou script.jscar ils ne peuvent étendre la configuration transmise à partir de l' data-mage-initattribut:

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

Je veux dire que les deux anciens fichiers ne jouent pas avec la valeur renvoyée par un module, au lieu de cela ils prétraitent la configuration d'une instance.

Exemples d'utilisation

Pour commencer, je voudrais remettre les pendules à l'heure que les soi-disant "mixins" (vous avez raison sur la dénomination) permettent en fait de modifier la valeur exportée d'un module comme vous le souhaitez. Je dirais que c'est un mécanisme beaucoup plus générique.

Voici un exemple rapide d'ajout de fonctionnalités supplémentaires à la fonction exportée par un module:

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

Vous pouvez implémenter un mixage réel pour n'importe quel objet / fonction retourné par un module et vous n'avez pas du tout besoin de dépendre de la extendméthode.

Extension d'une fonction constructeur:

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

J'espère que cela répond à vos questions.

Cordialement.

Denis Rul
la source
Je vous remercie! Exactement ce que je cherchais - la seule autre question que j'aurais à poser - que fait la mixinsconfiguration x-magento-initet les data-mage-initconfigurations? c'est-à-dire - dans l'exemple ci-dessus, path/to/configuration-modifierretournerait également un rappel qui pourrait modifier les données de configuration? Ou autre chose?
Alan Storm du
Oui, justement! Il est censé renvoyer un rappel à partir duquel vous pouvez modifier les données de configuration.
Denis Rul
vous semblez assez bien vous familiariser avec les choses frontales - un aperçu de ces deux questions? magento.stackexchange.com/questions/147899/… magento.stackexchange.com/questions/147880/…
Alan Storm
4

Pour compléter la réponse de Denis Rul .

Donc, si vous regardez une page Magento, voici les trois <script/>balises qui chargent 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>

Il s'agit de RequireJS lui-même ( require.js), du mixins.jsplug - in et de la configuration fusionnée de RequireJS ( requirejs-config.js).

Le mixins.jsfichier définit un plugin RequireJS. Ce plugin est responsable du chargement et de l'appel des modules RequireJS qui écoutent les instanciations des autres modules RequireJS.

Ce plugin contient également un programme requirejs après avoir défini le 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;
});

Ce second programme se charge juste défini mixinsplug - in comme une dépendance, puis redéfinit les mondiaux require, defineet les requirejsfonctions. Cette redéfinition est ce qui permet au système "pas vraiment un mixage" de s'accrocher à l'instanciation initiale du module RequireJS avant de renvoyer les choses aux fonctions régulières.

Alan Storm
la source