Como as coisas que Magento 2 chama de "mixins" implementadas?

16

Os sistemas de objetos baseados no RequireJS do Magento 2 contêm um recurso chamado "mixins". Um mixin Magento 2 não é o que um engenheiro de software normalmente pensaria como um mixin / característica . Em vez disso, um mixin do Magento 2 permite modificar o objeto / valor retornado por um módulo RequireJS antes que esse objeto / valor seja usado pelo programa principal. Você configura um mixin Magento 2 como este (através de um arquivo 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
            }
        }
    }
};

Então, você precisa ter hook.js(ou qualquer módulo RequireJS que você configurou),

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

retornar uma função. O Magento chamará essa função, passando uma referência ao "módulo" que você deseja modificar. No nosso exemplo, este será o objeto retornado pelo módulo RequireJS Magento_Checkout/js/view/form/element/email. Isso também pode ser uma função ou mesmo um valor de escalonador (dependendo do que o módulo RequireJS retornar).

Esse sistema parece ser chamado mixinsporque permite criar um comportamento semelhante ao mixin se o objeto retornado pelo módulo RequireJS original suportar o 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);
    };
});

No entanto, o próprio sistema é apenas uma maneira de se conectar à criação de objetos do módulo.

Preâmbulo terminado - alguém sabe como o Magento implementou essa funcionalidade? O site RequireJS parece não mencionar mixins (embora o Google pense que você pode querer a página de plug-in do RequireJS ).

Fora dos requirejs-config.jsarquivos, o javascript principal do Magento 2 menciona apenas mixinsem três arquivos

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

O mixins.jsarquivo parece ser um plug-in RequireJS (com base nas !...menções nos comentários - isso está certo?), Mas não está 100% claro quando main.jsou scripts.jsé invocado pelo Magento, ou como a mixinsconfiguração personalizada faz parte do requirejs-config.jssistema listener / hook descrito acima.

Alguém tem uma explicação de como esse sistema foi / é implementado / arquitetado, com o objetivo de ser capaz de depurar por que um "mixin" pode ou não ser aplicado?

Alan Storm
fonte

Respostas:

18

Gostaria de ir direto às suas perguntas e depois tentarei esclarecer o que você pode realmente fazer com o plug-in mixins . Então, as primeiras coisas primeiro.

Implementação

O principal aqui é a capacidade de qualquer plug-in RequireJS assumir completamente o processo de carregamento de determinados arquivos. Isso permite modificar o valor de exportação de um módulo antes que ele seja passado como uma dependência resolvida.

Dê uma olhada nesta implementação superficial do que realmente é o plugin mixins customizado do 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);
            });
        }
    }
});

A última e a parte mais desafiadora é preceder dinamicamente o 'sampleMixinPlugin!' substring para os módulos solicitados. Para fazer isso, interceptamos definee requireinvocamos e modificamos a lista de dependências antes que elas sejam processadas pelo método de carregamento RequireJS original. É um pouco complicado e eu recomendo olhar para a implementação, lib/web/mage/requirejs/mixins.jsse você quiser como ela funciona.

Depuração

Eu recomendo estas etapas:

  • Verifique se a configuração para 'mixins!' plugin está realmente .
  • Verifique se o caminho para um módulo está sendo modificado . Ou seja, passa de path/to/modulepara mixins!path/to/module.

E o último, mas não menos importante, requiresjs/mixins.jsnão tem nada a ver com os módulos main.jsou, script.jspois eles só podem estender a configuração que está sendo transmitida a partir do data-mage-initatributo:

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

Quero dizer que os dois primeiros arquivos não mexem com o valor retornado por um módulo, em vez disso, pré-processam a configuração de uma instância.

Exemplos de uso

Para começar, eu gostaria de esclarecer que os chamados "mixins" (você está certo sobre os erros de designação) na verdade permitem modificar o valor exportado de um módulo da maneira que desejar. Eu diria que esse é um mecanismo muito mais genérico.

Aqui está um exemplo rápido de adição de funcionalidade extra à função que está sendo exportada por um 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
});

Você pode implementar um mixin real para qualquer objeto / função retornada por um módulo e não precisa depender do extendmétodo.

Estendendo uma função de construtor:

// 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 isso responda às suas perguntas.

Saudações.

Denis Rul
fonte
Obrigado! Apenas o que eu estava procurando - a única outra pergunta que eu teria é - o que a mixinsconfiguração faz x-magento-inite as data-mage-initconfigurações? ie - no exemplo acima, você path/to/configuration-modifiertambém retornaria um retorno de chamada que poderia modificar os dados de configuração? Ou outra coisa?
Alan Storm
Sim precisamente! É suposto retornar um retorno de chamada a partir do qual você pode modificar os dados de configuração.
Denis Rul 31/10/19
você parece conhecer bem as coisas do front-end - algum insight sobre essas duas perguntas? magento.stackexchange.com/questions/147899/... magento.stackexchange.com/questions/147880/...
Alan Storm
4

Para completar a resposta de Denis Rul .

Então, se você olhar uma página do Magento, aqui estão as três <script/>tags que carregam o 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 é o próprio RequireJS ( require.js), o mixins.jsplug - in e a configuração RequireJS mesclada ( requirejs-config.js).

O mixins.jsarquivo define um plug-in RequireJS. Esse plug-in é responsável por carregar e chamar os módulos RequireJS que atendem à instanciação de outros módulos RequireJS.

Este plugin também contém um programa requirejs depois de definir o 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;
});

Este segundo programa carrega o justo definido mixinsplugin como uma dependência e, em seguida, redefine os globais require, definee requirejsfunções. Essa redefinição é o que permite que o sistema "não seja realmente um mixin" se ligue à instanciação inicial do módulo RequireJS antes de repassar as coisas para as funções regulares.

Alan Storm
fonte