canjs / can-stache

Live binding handlebars templates
https://canjs.com/doc/can-stache.html
MIT License
10 stars 13 forks source link

Turn off observables in stache to help performance #691

Open justinbmeyer opened 5 years ago

justinbmeyer commented 5 years ago

This example renders slow: https://codepen.io/anon/pen/yWgmoq?editors=0011

Part of the issue is that there is a decent amount of live binding within 2 levels (actually 3) of nested looping:

200 rows X 6 columns X 7 bindings = 8400

That means there's likely tens of thousands of observables that need to be created to render this template.

However, it's likely that we could create a single observable for each row. This would limit the number of observables to some multiple of 200 (probably 2x). We could do this by having a single observable for an entire row's content:

image

When any part of the row changed, we could simply replace the entire row again. This wouldn't be as efficient as diffing (which could come later), but it would still improve performance quite a bit.

This could work with a special helper that would wrap some stache logic. It could look like:

{{#for(row of visibleItems)}}
  {{#logicOnly}}
    <tr>
            {{#for(entity of row)}}  ... {{/for}}
    </tr>
  {{/logicOnly}}
{{/for}}

{{#logicOnly}} would tell stache to build logic-only expressions. Instead of new Call, it could build new CallLogic(). EXPRESSIONLogic constructors would only perform the logic and not return an observable.

{{#logicOnly}} would probably need to work on parse-time.

Testing this

To test if this is a viable strategy, we should have a helper render equivalent JS code that produces the content of a row. It could look like:

stache.addHelper("rowContent", function(row){
  var fragment = document.createDocumentFragment();
  for( let entity of row ){
    var tr = document.createElement("tr");
    tr.appendChild( BUILD_STUFF_THAT_GOES_IN_TR( ... )
    fragment.appendChild( tr );
  }
  return fragment;
})

rowContent will wrap the building of that fragment with a single observable, just like {{#logicOnly}} would do. If this is much faster, then we should continue looking at this method.