Wie werden die Dinge implementiert, die Magento 2 "Mixins" nennt?

16

Die RequireJS-basierten Objektsysteme von Magento 2 enthalten eine Funktion namens "Mixins". Ein Magento 2-Mixin ist nicht das, was ein Software-Ingenieur normalerweise als Mixin / Merkmal betrachtet . Stattdessen können Sie mit einem Magento 2-Mixin das von einem RequireJS-Modul zurückgegebene Objekt / den Wert ändern, bevor dieses Objekt / dieser Wert vom Hauptprogramm verwendet wird. Sie konfigurieren ein Magento 2-Mixin wie folgt (über eine requirejs-config.js-Datei)

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

Dann müssen Sie hook.js(oder was auch immer RequireJS-Modul, das Sie konfiguriert haben),

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

Funktion zurückgeben. Magento ruft diese Funktion auf und übergibt einen Verweis auf das "Modul", das Sie ändern möchten. In unserem Beispiel ist dies das Objekt, das vom RequireJS-Modul zurückgegeben wird Magento_Checkout/js/view/form/element/email. Dies kann auch eine Funktion oder sogar ein Skalierungswert sein (abhängig davon, was das RequireJS-Modul zurückgibt).

Dieses System scheint aufgerufen zu werden, mixinsweil Sie damit ein mixin-ähnliches Verhalten erstellen können, wenn das vom ursprünglichen RequireJS-Modul zurückgegebene Objekt die extendMethode unterstützt .

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

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

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


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

Das System selbst ist jedoch nur eine Möglichkeit, sich in die Erstellung von Modulobjekten einzuklinken.

Präambel fertig - weiß jemand, wie Magento diese Funktionalität implementiert hat? Die RequireJS Website scheint nicht Mixins zu erwähnen (obwohl Google denkt , dass Sie vielleicht RequireJS Plugin Seite wollen ).

Außerhalb von requirejs-config.jsDateien erwähnt das Magento 2-Kern-Javascript nur mixinsdrei Dateien

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

Die mixins.jsDatei erscheint ein RequireJS sein Plugin (basierend auf der !...Erwähnungen in den Kommentaren - ist dies der richtige) , aber es ist nicht 100% klar , wann main.jsoder scripts.jswerden von Magento aufgerufen, oder wie die benutzerdefinierte mixinsKonfiguration macht es aus requirejs-config.jsin den Hörer / Hakensystem oben beschrieben.

Hat jemand eine Erklärung dafür, wie dieses System implementiert wurde / wird / wird, mit dem Ziel zu debuggen, warum ein "Mixin" angewendet werden kann oder nicht?

Alan Storm
quelle

Antworten:

18

Ich würde gerne direkt auf Ihre Fragen eingehen und dann versuchen, klar zu machen, was Sie tatsächlich mit dem Mixins- Plugin machen können. Also, das Wichtigste zuerst.

Implementierung

Die Hauptsache hierbei ist die Fähigkeit eines RequireJS-Plugins, den Ladevorgang bestimmter Dateien vollständig zu übernehmen. Auf diese Weise können Sie den Exportwert eines Moduls ändern, bevor es als aufgelöste Abhängigkeit übergeben wird.

Schauen Sie sich diese skizzenhafte Implementierung des Magento Custom Mixins Plugins an:

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

Der letzte und schwierigste Teil ist das dynamische Voranstellen des 'sampleMixinPlugin!' Teilstring zu den gewünschten Modulen. Um dies zu tun wir abfangen defineund requireAnrufungen und ändern Sie die Liste der Abhängigkeiten , bevor sie durch die ursprüngliche RequireJS Load - Methode verarbeitet werden. Es ist ein bisschen knifflig und ich würde empfehlen, sich die Implementierung anzuschauen, lib/web/mage/requirejs/mixins.jswenn Sie möchten, wie es funktioniert.

Debuggen

Ich würde diese Schritte empfehlen:

  • Stellen Sie sicher, dass die Konfiguration für 'mixins!' Plugin ist eigentlich da .
  • Überprüfen Sie, ob der Pfad zu einem Modul geändert wird . Dh es dreht sich ab path/to/moduleum mixins!path/to/module.

Und last but not least requiresjs/mixins.jshat dies nichts mit den Modulen main.jsoder zu tun, script.jsda sie nur die Konfiguration erweitern können, die über das data-mage-initAttribut übergeben wird:

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

Ich meine, dass die beiden vorherigen Dateien nicht mit dem von einem Modul zurückgegebenen Wert in Konflikt geraten, sondern die Konfiguration einer Instanz vorverarbeiten.

Anwendungsbeispiele

Zunächst möchte ich klarstellen, dass sogenannte "Mixins" (Sie haben Recht mit dem falschen Namen) es ermöglichen, den exportierten Wert eines Moduls nach Belieben zu ändern. Ich würde sagen, dass dies ein viel allgemeinerer Mechanismus ist.

Hier ein kurzes Beispiel für das Hinzufügen zusätzlicher Funktionen zu der Funktion, die von einem Modul exportiert wird:

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

Sie können ein tatsächliches Mixin für jedes Objekt / jede Funktion implementieren, das / die von einem Modul zurückgegeben wird, und Sie müssen überhaupt nicht von der extendMethode abhängig sein .

Konstruktorfunktion erweitern:

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

Ich hoffe, dass dies Ihre Fragen beantwortet.

Grüße.

Denis Rul
quelle
Vielen Dank! Genau das, wonach ich gesucht habe - die einzige andere Frage, die ich hätte, ist - was macht die mixinsKonfiguration in x-magento-initund data-mage-initKonfigurationen? dh - in obigem Beispiel würde path/to/configuration-modifierauch ein Rückruf zurückgegeben, der die Konfigurationsdaten ändern könnte? Oder etwas anderes?
Alan Storm
Ja genau! Es soll einen Rückruf zurückgeben, über den Sie die Konfigurationsdaten ändern können.
Denis Rul
Sie scheinen sich im Front-End-Bereich ziemlich gut auszukennen - haben Sie einen Einblick in diese beiden Fragen? magento.stackexchange.com/questions/147899/… magento.stackexchange.com/questions/147880/…
Alan Storm
4

Um die Antwort von Denis Rul abzurunden .

Wenn Sie sich also eine Magento-Seite ansehen, finden Sie hier die drei <script/>Tags, mit denen Magento geladen wird.

<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>

Dies ist RequireJS selbst ( require.js), das mixins.jsPlugin und die zusammengeführte RequireJS-Konfiguration ( requirejs-config.js).

Die mixins.jsDatei definiert ein RequireJS-Plugin. Dieses Plugin ist für das Laden und Aufrufen der RequireJS-Module verantwortlich, die auf die Instantiierung anderer RequireJS-Module warten.

Dieses Plugin auch enthält ein requirejs Programm , nachdem es das mixin Plugin definiert.

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

Dieses zweite Programm lädt die gerade definiert mixinsals Abhängigkeit Plugin, und dann neu definiert die globalen require, defineund requirejsFunktionen. Diese Neudefinition ermöglicht es dem "nicht wirklich mischbaren" System, sich in die anfängliche Instanziierung des RequireJS-Moduls einzuklinken, bevor die Dinge an die regulären Funktionen zurückgegeben werden.

Alan Storm
quelle