Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Relationship to Lit / Polymer? #439

Open
dakom opened this issue Dec 9, 2019 · 5 comments
Open

Relationship to Lit / Polymer? #439

dakom opened this issue Dec 9, 2019 · 5 comments

Comments

@dakom
Copy link

dakom commented Dec 9, 2019

It seems to me that lit-html (or lit-element, polymer, etc.) Are addressing roughly the same space.

Can someone please summarize the difference in terms of performance?

(I get that it's a different API, more thinking about raw bottleneck issues.)

Thanks!

@sgammon
Copy link

sgammon commented Dec 9, 2019

@dakom the two are rather difficult to compare, because they are so different in their approach.

lit-element uses web component APIs to define Shadow DOM. if you're not familiar, "Shadow DOM" is system-level UI. for instance, when you put an empty and unskinned <input />, the UI that shows up is implemented in Shadow DOM. using lit-element lets you hook into this mechanism and define your own HTML tags, which are implemented generally by JS-provided DOM templates, bound to JS objects. together, the JS object, DOM shadow root, and any styles constitute a "component."

now, in contrast, idom does not use Shadow DOM, and in fact does not maintain any virtual DOM at all (like React, or Angular when operating with normal view encapsulation). here is a rough comparison summary:

  • lit-element/Polymer/Angular with ShadowDOM:

    • Benefits:
      • some would say development is easier because components are largely de-coupled
      • isolation via shadow DOM prevents one component's styles from trampling another's
      • hook into the browser to create custom components, via the Web Components spec, which is a browser feature, implying good rendering performance, and future-proof standards (ha ha sure though)
      • tons of pre-built components out there to use
      • immediately compatible with any Web Components-based UI
    • Drawbacks:
      • if there isn't support for the Web Components v1 spec (v0 is deprecated), then you must load polyfills to support your UI, which kind of sucks
      • much of your shadow DOM templating must exist in JS, which is fast, but isn't as fast as the browser interpreting native DOM
      • the page generally cannot render until javascript kicks in
      • in practice, client side rendering is the only practical option (i.e. rehydration, SSR, are both difficult with Shadow DOM)
      • SEO difficulties of component-based CSR are well known
  • React/Angular with regular encapsulation:

    • Benefits:
      • "just works" without polyfills, because it only uses well-supported longstanding JS APIs
      • development is pretty easy, because you are essentially coding in a VM
      • you can do cool stuff with syntax (see: JSX)
    • Drawbacks:
      • must maintain a virtual DOM, which costs memory and compute cycles
      • when changes are made to the virtual DOM, they must be flushed to the real DOM, which costs additional cycles and memory, too
      • same SEO issues apply, because the page content is not ready until javascript renders it
  • Soy with idom:

    • Benefits:
      • no virtual DOM. idom instead merges against the actual DOM, incrementally buffering changes as it goes, and then flushing them. this means you don't need to maintain the virtual DOM or sync changes between it and the real DOM, which saves cycles and memory.
      • highly typed. this is an orthogonal benefit to rendering, but it's worth mentioning that Soy can either beat or trounce most other template languages when it comes to safety.
      • deep computer model of your page. for instance, Soy knows where all your CSS classes are, so it can rewrite them. to Soy, a CSS class is a programming symbol, whereas in most other frameworks it's just a constant string.
      • you can use Soy with idom server-side (with Soy Java or Soy Python), essentially seamlessly, making re-hydration and SSR significantly more efficient
      • SEO "just works" if you use SSR-based Soy
    • Drawbacks:
      • lack of tooling. many frameworks don't leverage idom correctly, or don't support it at all.
      • lack of support. Google is much more active now, but they've lagged supporting this project before and that stings for adopters.
      • lack of compatibility. basically, you are best off using idom and nothing else, rather than mixing idom and some other technology - if idom isn't aware of some piece of your DOM, it may trample it, which causes subtle issues that can be hard to debug.

Honestly, where bottlenecks are concerned, I would imagine Soy/idom not to be one of them. It is a small, tuned package driven by a compiler, so, it really depends how your templates use it. I would expect idom to be more performance out of the box compared to Shadow DOM, though, because there is much less overhead to render the same thing.

Hope this helps. FWIW we absolutely love idom. If you are performance-oriented, you won't be disappointed. But prepare yourself for some real tooling challenges.

@dakom
Copy link
Author

dakom commented Dec 9, 2019

Thanks - great breakdown!

Though I'm still not totally clear on the comparison to lit-html (as opposed to lit-element).

For example let's say we re-render the entire page every tick. My understanding is that lit-html will also only make updates to the parts that changed, without vdom diffing... so it sounds similar to idom, but I'm sure there is some major difference I don't quite see yet...

@sgammon
Copy link

sgammon commented Dec 9, 2019

AFAIK because lit-html operates via JS, it has to compile or interpret your template first, then merge it against the DOM. so, the overhead i'm talking about here is that compiled idom does no interpretation at runtime, it is compiled to calls into Soy which drive the template render process.

for example, a lit-html template (which lit-element builds atop) would look something like this:

html`<div id='some-id' class='some class list here'><b>${salutation}</b></div>`

that's leveraging JS template tags to interpret and construct the template. idom also operates in JS, so they both incur the JS overhead, but a compiled idom template looks more like this:

function someRenderFunc($salutation) {
    idomRenderer.elementOpen('div', 'some-id', 'some class list here');
    idomRenderer.elementOpen('b');
    soyIdom.print(idomRenderer, $salutation);
    idomRenderer.elementClose('b');
    idomRenderer.elementClose('div');
}

the template has been serialized by idom into direct instructions, rather than needing to assemble those instructions when lit-html first loads your template.

@sgammon
Copy link

sgammon commented Dec 9, 2019

@dakom ive borrowed from our dist/ dir there, the compiled template is nearly identical to what is depicted, btw, in case that helps. here is a larger snippet

/**
 * @param {!incrementaldomlib.IncrementalDomRenderer} idomRenderer
 * @param {!$noticeDialog.Params} opt_data
 * @param {?goog.soy.IjData=} opt_ijData
 * @return {void}
 * @suppress {checkTypes}
 */
var $noticeDialog = function(idomRenderer, opt_data, opt_ijData) {
  /** @type {string} */
  var title = soy.asserts.assertType(goog.isString(opt_data.title), 'title', opt_data.title, 'string');
  /** @type {null|string|undefined} */
  var message = soy.asserts.assertType(opt_data.message == null || goog.isString(opt_data.message), 'message', opt_data.message, 'null|string|undefined');
  /** @type {!goog.soy.data.SanitizedHtml|!google3.javascript.template.soy.element_lib_idom.IdomFunction|function(!incrementaldomlib.IncrementalDomRenderer): undefined|null|undefined} */
  var content = soy.asserts.assertType(opt_data.content == null || (goog.module.get('google3.javascript.template.soy.soyutils_directives').$$isIdomFunctionType(opt_data.content, SanitizedContentKind.HTML) || opt_data.content instanceof goog.soy.data.SanitizedContent), 'content', opt_data.content, '!goog.soy.data.SanitizedHtml|!google3.javascript.template.soy.element_lib_idom.IdomFunction|function(!incrementaldomlib.IncrementalDomRenderer): undefined|null|undefined');
  /** @type {!goog.soy.data.SanitizedHtml|!google3.javascript.template.soy.element_lib_idom.IdomFunction|function(!incrementaldomlib.IncrementalDomRenderer): undefined|null|undefined} */
  var actions = soy.asserts.assertType(opt_data.actions == null || (goog.module.get('google3.javascript.template.soy.soyutils_directives').$$isIdomFunctionType(opt_data.actions, SanitizedContentKind.HTML) || opt_data.actions instanceof goog.soy.data.SanitizedContent), 'actions', opt_data.actions, '!goog.soy.data.SanitizedHtml|!google3.javascript.template.soy.element_lib_idom.IdomFunction|function(!incrementaldomlib.IncrementalDomRenderer): undefined|null|undefined');
  /** @type {boolean|null|undefined} */
  var selectableText = soy.asserts.assertType(opt_data.selectableText == null || (goog.isBoolean(opt_data.selectableText) || opt_data.selectableText === 1 || opt_data.selectableText === 0), 'selectableText', opt_data.selectableText, 'boolean|null|undefined');
  idomRenderer.elementOpenStart('div', xid('bloombox.ui.base.notice.noticeDialog-0'), $noticeDialog_statics_0 || ($noticeDialog_statics_0 = ['class', goog.getCssName('mdl-card') + ' ' + goog.getCssName('mdl-shadow--2dp')]));
      if (goog.DEBUG && soy.$$debugSoyTemplateInfo) {
        idomRenderer.attr('data-debug-soy', 'bloombox.ui.base.notice.noticeDialog bloombox/ui/templates/base/notice.soy:16');
      }
  idomRenderer.elementOpenEnd();
  idomRenderer.elementOpen('div', xid('bloombox.ui.base.notice.noticeDialog-1'), $noticeDialog_statics_1 || ($noticeDialog_statics_1 = ['class', goog.getCssName('mdl-card__title') + ' ' + goog.getCssName('no-select')]));
  idomRenderer.elementOpen('h2', xid('bloombox.ui.base.notice.noticeDialog-2'), $noticeDialog_statics_2 || ($noticeDialog_statics_2 = ['class', '' + (goog.getCssName('mdl-card__title-text'))]));
  // ...
}

when compiled with Closure Compiler, the above is very tightly optimized. here's an example of the final, non-debug output:

Screen Shot 2019-12-09 at 1 48 50 PM

so, i guess, if you hate shipping string templates to be interpreted at runtime, Soy is your solution.

@sparhami
Copy link
Contributor

sparhami commented Dec 9, 2019

Thanks - great breakdown!

Though I'm still not totally clear on the comparison to lit-html (as opposed to lit-element).

For example let's say we re-render the entire page every tick. My understanding is that lit-html will also only make updates to the parts that changed, without vdom diffing... so it sounds similar to idom, but I'm sure there is some major difference I don't quite see yet...

With Incremental DOM, you still need to walk through everything to see what changed. The DOM updates are only applied where things are changed, but it still needs to take all the data in, walk the DOM nodes and check if things changed one by one. My understanding of lit-html is that it looks at the data, sees what changed, and then applies each change to the appropriate place in the DOM, without walking the tree. To do this, it does a bit of work the first time it renders something to map where things need to go.

I think as far as update speed, that probably isn't too much of a difference. There is a minor difference where Incremental DOM prefers to optimize performance for the worst case (rendering something new from scratch) and lit-html prefers to optimize for best case (doing a single, small update). I think the bigger differences are in the different aims of the projects and how the influences the different models.

Incremental DOM is more focused on supporting a template compilation which is useful for server-side rendering (as @sgammon mentioned) which can be a very large performance improvement. The model of how things work (e.g. how you can do control flow with if-statements) is very much suited for that, especially for Closure Templates aka Soy.

Depending on the scale you are operating at, there are other things to consider, such as message translations, which Closure Templates supports. By breaking things up into instructions as Incremental DOM does, you can handle such translated strings in a first class way when doing rendering. For example, you might have some content like:

<a href="example.com/learn-more.html">Learn more</a> about our thing.

In general, for these kinds of things you cannot split this up into two separate strings for localization since you need the whole sentence to make sense and be localized as a unit.

Other approaches usually require you to insert the translated strings using innerHTML in some form (and as a result do sanitization if dealing with user data).

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

No branches or pull requests

3 participants