Magento 2: What's an `<each/>` Tag?

As Raphael hinted at, it turns out that when Magento downloads its KnockoutJS templates via an XHR (i.e. ajax) request, it also passes them through some custom parsing routines that look for a number of custom tags and attributes

This custom parsing is done by the Magento_Ui/js/lib/knockout/template/renderer RequireJS module. This module's source code sets up a number of default tags and attributes to search for. There are also other modules which may add additional tags and attributes to this renderer. For example, the following

#File: vendor/magento/module-ui/view/base/web/js/lib/knockout/bindings/scope.js
renderer
    .addNode('scope')
    .addAttribute('scope', {
        name: 'ko-scope'
    });

will will add the <scope/> tag and scope attribute (<div scope="...">) to the list of parseable attributes.

Is seems like the basic idea is to translate these tags and attributes into native Knockout "tagless" template blocks. For example, the following Magento KnockoutJS template

<each args="data: elems, as: 'element'">
    <render if="hasTemplate()"/>
</each>

Translates into the following native KnockoutJS code

<!-- ko foreach: {data: elems, as: 'element'} -->
    <!-- ko if: hasTemplate() --><!-- ko template: getTemplate() --><!-- /ko --><!-- /ko -->
<!-- /ko -->

The exact rules of this translation are still unclear to me -- the code in Magento_Ui/js/lib/knockout/template/renderer is a little indirect, and it seems like they can change from tag to tag, attribute to attribute.

I've ginned up the following code snippet that can download a Magento KnockoutJS template, and translate it into native KnockoutJS code.

jQuery.get('http://magento-2-1-0.dev/static/adminhtml/Magento/backend/en_US/Magento_Ui/templates/collection.html', function(result){
    var renderer = requirejs('Magento_Ui/js/lib/knockout/template/renderer')
    var fragment = document.createDocumentFragment();
    $(fragment).append(result);

    //fragment is passed by reference, modified
    renderer.normalize(fragment);
    var string = new XMLSerializer().serializeToString(fragment);
    console.log(string);    
})

As to why Magento might do this -- my guess is wanting some sort of syntax highlighting and readability for KnockoutJS's commenting template, but never rule out more Mallory-ish reasons.


Both tags are implemented under app/code/Magento/Ui/view/base/web/js/lib/knockout/template/renderer.js, I'm not too sure to understand exactly how they are implemented though:

_.extend(preset.nodes, {
    foreach: {
        name: 'each'
    },

    /**
     * Custom 'render' node handler function.
     * Replaces node with knockout's 'ko template:' comment tag.
     *
     * @param {HTMLElement} node - Element to be processed.
     * @param {String} data - Data specified in 'args' attribute of a node.
     */
    render: function (node, data) {
        data = data || 'getTemplate()';
        data = renderer.wrapArgs(data);

        renderer.wrapNode(node, 'template', data);
        $(node).replaceWith(node.childNodes);
    }
});