jridgewell / babel-plugin-transform-incremental-dom

Turn JSX into IncrementalDOM
MIT License
145 stars 12 forks source link

Why is the transpilation so different in these two cases? #86

Closed oravecz closed 7 years ago

oravecz commented 7 years ago

I am getting two different transpilations for these two similar use cases. (At least I think they should be more similar). The inline example works, the import approach fails to render any content because the function signatures are not appropriate.

return data => <div>Some Content</div>

Transpiles to a reasonable:

    get: function get() {
        return function ( data ) {
            (0, _incrementalDom.elementOpen)( 'div' );
            (0, _incrementalDom.text)( 'Some Content' );
            return (0, _incrementalDom.elementClose)( 'div' );
        };
    }

However, when I attempt to store my JSX in an imported module, I get additional __jsxWrapper functions added.

> template.js
export default <div>Some Content</div>

> component.js
import template from './template'
return data => template

Transpiles to:

var _jsxWrapper = function _jsxWrapper(func, args) {
    return {
        __jsxDOMWrapper: true,
        func: func,
        args: args
    };
};

var _div$wrapper = function _div$wrapper() {
    (0, _incrementalDom.elementOpen)("div");
    (0, _incrementalDom.text)("Some Content");
    return (0, _incrementalDom.elementClose)("div");
};

template.default = _jsxWrapper(_div$wrapper);

get: function get() {
    return function (data) {
        return template.default;
    };
}

I have attempted to set "inlineExpressions": true, but it doesn't have any effect that I can see.

jridgewell commented 7 years ago

This is due to the "incremental" nature of iDOM. As the methods are call, the DOM is mutated. So:

export default <div>Some Content</div>

If we compiled into "normal" iDOM code

export default = (0, _incrementalDom.elementOpen)("div"), // Error is thrown, iDOM not in patch context
    (0, _incrementalDom.text)("Some Content"),
    (0, _incrementalDom.elementClose)("div");

Obviously, that's no good. So, we wrap them:

// I'm simplifying the output for brevity
template.default = function _div$wrapper() {
    (0, _incrementalDom.elementOpen)("div");
    (0, _incrementalDom.text)("Some Content");
    return (0, _incrementalDom.elementClose)("div");
};

This avoids the error. But now how is that function invoked? Somewhere you have a iDOM.patch call, and inside that patch call you'll have some JSX root node:

import template from "./template";
class Component {
  template() {
    // Note we are not doing anything to `template`.
    // We're just passing it in as a child.
    return <root>{template}</root>;
  }

  render() {
    iDOM.patch(this.element, this.template);
  }
}

That root node is important. It changes the semantics of everything inside it from "call a function" to "render this variable as a JSX child element". And we're able to hook into that to render the wrapped _div$wrapper.

oravecz commented 7 years ago

I suppose there is no means to fully contain the dom in the template without introducing this <root/> element? My Component is a custom element, so it is declared using something like this:

<my-custom></my-custom>

Which ends up rendering:

<my-custom><root><div>Some Content</div></root></my-custom>
jridgewell commented 7 years ago

Not with the "constant" evaluation (it can't be constant with iDOM, hence the wrapper) you're trying to do. Instead, you could define it like you did initially:

export default () => <div>Some Content</div>;