Skip to content
This repository has been archived by the owner on Apr 29, 2021. It is now read-only.

Glimmer #11

Open
jridgewell opened this issue Aug 10, 2015 · 11 comments
Open

Glimmer #11

jridgewell opened this issue Aug 10, 2015 · 11 comments

Comments

@jridgewell
Copy link
Owner

After #10 lands, we can shamelessly rip off Glimmer's rendering engine.

First, the naive approach (that we're currently doing) is to diff everything:

function render() {
 return <div>
    <span>{this.props.content}</span>
    <ul>
      { this.props.li.map((li) => { <li>{li}</li> }) }
    </ul>
  </div>;
}

// - - -
function render() {
  elementOpen("div");
  elementOpen("span");

  _renderArbitrary(this.props.content);

  elementClose("span");
  elementOpen("ul");

  this.props.li.map(function (li) {
    elementOpen("li");

    _renderArbitrary(li);

    elementClose("li");
  });

  elementClose("ul");
  return elementClose("div");
}

In essence, we'll diff the <div>, the <span>, the span's content, the <ul> and the ul's content. But, we can do a bit better using Glimmer's approach.

Essentially, we only ever need to patch the <span> and <ul> on second renders:

// First render
function render() {
  render.div = elementOpen("div");
  render.span = elementOpen("span");

  _renderArbitrary(this.props.content);

  elementClose("span");
  render.ul = elementOpen("ul");

  this.props.li.map(function (li) {
    elementOpen("li");

    _renderArbitrary(li);

    elementClose("li");
  });

  elementClose("ul");
  return elementClose("div");
}

// Second render
function secondRender() {
  patch(render.span, _renderArbitrary, this.props.content);
  patch(render.ul, function() {
    elementOpen("li");

    _renderArbitrary(li);

    elementClose("li");
  });
  return render.div;
}

This can pay off immensely for static renders or deeply nested views:

function render() {
  return <div>
    <div>
      <span>Lot</span>
    </div>
    <div>
      <span>o'</span>
    </div>
    <div>
      <span>Content</span>
    </div>
  </div>;
}

// - - -
function secondRender() {
  return render.div;
}
function render() {
  return <div>
    <div>
      <div>
        <div>
          <span>{this.props.content}</span>
        </div>
      </div>
    </div>
  </div>;
}

// - - -
function secondRender() {
  patch(render.span, _renderArbitrary, this.props.content);
  return render.div;
}
@jamiebuilds
Copy link
Contributor

Isn't the element returned on close not open?

@jamiebuilds
Copy link
Contributor

I like the basic concept here, it'd be cool to explore

@jridgewell
Copy link
Owner Author

Isn't the element returned on close not open?

The Element is returned from both.

@jamiebuilds
Copy link
Contributor

How far is this from reality?

@jridgewell
Copy link
Owner Author

Maybe a weekend's worth of hacking. We already have a lot of information about what is constant and what changes from JSX's own semantics.

I'm working on static hosting first, though.

@jridgewell
Copy link
Owner Author

Edit: see #11 (comment).


Hmm, blocked until google/incremental-dom#132 is merged. If any element in the top-level of the render isn't constant, I won't have a reference node to patch up:

function render(data) {
  return <div id={data.id} />;
};

// - - -
// Would have to translate to something like

function render(data) {
  var elements = render.elements = [];
  elements.push(elementVoid("div", null, null, "id", data.id));
  return elements[0];
}
render.secondRender = function(data) {
  // We can't already be in a `patch`, since that would wipe out everything.
  // So we have to call patch a bunch of times.
  patch(getCurrentElement(), render, data);
  return render.elements[0];
};

Specifically with top-level elements, I won't have the current container (or be able to generate one with an elementOpen), so I'll need iDOM to give it to me.

@jridgewell
Copy link
Owner Author

iDOM just added skip, which allows you to skip the clearing of unvisited child nodes. Tied together with getCurrentElement, this works pretty handily to solve a second problem with my example. Namely, even if I were to get the top-level container element and patch it, it would still have it's children cleared by the original patch.

// Top level change
function render(data) {
  return <div id={data.id} />;
};

// - - -
function render(data) {
  return elementVoid("div", null, null, "id", data.id);
}
render.secondRender = function(data) {
  return render(data);
};

patch(container, render, data);
// Later, repatch
patch(container, render.secondRender, data);

Patching a subelement:

function render(data) {
  return <div>
    <div id={data.id} />
  </div>;
};

// - - -
// Translates to something like

function render(data) {
  var elements = render.elements = [];
  elements.push(elementOpen("div"));
  elementVoid("div", null, null, "id", data.id);
  elementClose('div');
  return elements[0];
}
render.secondRender = function(data) {
  patch(render.elements[0], (data) => {
    elementVoid("div", null, null, "id", data.id);
  }, data);

  // Use skip to prevent clearing from outer patch context,
  // since `secondRender` was called like so:
  //     patch(container, render.secondRender, data)
  skip();
  return render.elements[0];
};

patch(container, render, data);
// Later, repatch
patch(container, render.secondRender, data);

@justinfagnani
Copy link

Is this still in the plans?

@justinfagnani
Copy link

I so... I've been experimenting with this transform with web components, and this kind of optimization would be very useful where shadow roots often have large <style> tags amongst other static beginning and end sections, as well as often having nested static sections around <slot>s.

@jridgewell
Copy link
Owner Author

Still stuck waiting for better iDOM support. I had a working implementation of this, but it was actually slower than doing the total diff due to multiple patch calls.

@justinfagnani
Copy link

ok, thanks for the update!

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

3 participants