I'm looking to add support for parts based on the mustache syntax proposed in this document to some of my projects. Initially I started doing this based on this polyfill which worked well enough. However once I started adding support for conditions and loops I realized that lit-html already had all of this, albeit from a different API.
Would lit-html be open to having support for templates written in HTML, not in JavaScript tagged template literals? I don't have a particular API proposal at this time, just wanting to see if this is a desired feature or not.
As far as implementation, I think this would probably involve a different type of TemplateResult, and changing Template to use TemplateResult to find the marker. For example, instead of here:
data.indexOf(marker) >= 0)
We could possibly do:
result.hasMarker(data)
Note: I'm not familiar with lit-html's code so this could be way off, just my initial impression.
The part I'm most unsure about is how important it is in lit-html that values are an array. In this version they would be an object and looked up by key within the mustache tags.
So instead of
new TemplateResult(strings,[ 'value1', 'value2' ], 'html', defaultTemplateProcessor);
This version would do something like:
new HTMLTemplateResult(instance, { one: 'value1', two: 'value2' }, 'html', defaultTemplateProcessor);
So right now I'm really trying to gauge 2 things:
The issue here is where do you get the data you render? In normal lit, it's provided in line with the template. In mustache, it has to be passed later:
html`<div>${ 'data is evaluated now' }</div>`
const temp = mustache`<div>{{ lazy_evaluated_data }} </div>`
temp({ lazy_evaluated_data: 'data is evaluated now' });
You could get close with something like:
const mapping = new WeakMap();
const mustache = (strings: TemplateStringsArray, ...values: unknown[]) => {
console.assert(values.length === 0);
let templateStrings = mapping.get(strings);
if (templateStrings === undefined) {
// do mustache template parsing here, split into an array
// Eg, `<div>{{one}}</div>` turns into `['<div>', '</div>']`
}
return directive((data) => {
// Transform data into a values array here.
// Eg, `<div>{{one}}</div>` with `{ one: 'value1' }` turns into `['value1']`
return new TemplateResult(templateStrings, values, 'html', defaultTemplateProcessor);
});
}
render(mustache`<div>{{one}}</div>`({ one: 'value1' }), container);
I actually started working on prototyping this a couple of weeks back for an internal customer, and it's relatively straight forward - it's just work.
The idea is that a template would be defined in the HTML document, and devs would make a new Template out of it directly. Something like:
import {HTMLTemplate, render} from 'lit-html';
const template = new HTMLTemplate(document.querySelector('#my-template'));
render(template.eval(data), container);
The expressions are supported by an updated version of https://github.com/googlearchive/polymer-expressions/tree/2.0-preview
@jridgewell To clarify, in this case we already have a HTMLTemplateElement, we are not passing a string template that uses mustache instead of JS string interpolation. So it's unfortunately not as simple as in your example.
@justinfagnani Great! I don't know how far you've gotten with this, but I'd like to experiment with it some if I can.
Another reason for wanting to use lit-html here is the desire to use the logic for loops and the repeat directive. I am hoping to replicate the directive/expression APIs described here. I wouldn't expect this to necessarily be part of lit-html, I'm just hopefully to be able to reuse NodePart's logic for iterables and repeat(). I just bring this up in case that part sounds challenging.
The trickiest part of using directives, or any JS value, is providing the scope to the expression evaluator. You'll have to feed in the directives along with the data.
@justinfagnani Can you share your prototype code or do I need to start from scratch?
I have a very minimal proof-of-concept working here: https://github.com/matthewp/lit-html/commit/b3b6ad88e3cff7f5c6dc61d0fcd46525991a89e7
The difficulty I ran into was that the codebase assumes its operating on arrays pretty much everywhere (understandably). So that made reuse hard. Instead of making the types more generic I think it might be easier to make the important bits understand both parts with expressions and index-based parts. Or at least that's my impression so far.
Any feedback on that commit is appreciated.
The most code here comes from traversing the template to find markers: https://github.com/matthewp/lit-html/commit/b3b6ad88e3cff7f5c6dc61d0fcd46525991a89e7#diff-e0581ff99178f930aed484330f3a8340R17
Since a lot of this code is the same as the tagged template path this could be in a generic traverser that calls back on each node. Then the HTML and tagged template paths could detect the markers and add their parts.
Any feedback on my implementation above? 馃憜 . I would love to proceed with completing it. 馃檹
Please? 馃檹
I've moved on.
Not exactly your use case but similar. We extract templates provided by the user in HTML via element.innerHTML and pass this String into mustache.js. Because we have started to migrate to lit-html, I have played around with a parser, which transforms a mustache.js-template-string into a function which receives the data and returns a lit-html Template Result. It is meant to be extendable.
You are welcome to take a long around at lit-transformer. The basic idea is as followed:
import { html, render } from 'lit-html';
import createTransformer from 'lit-transformer';
const mustache2litHtml = createTransformer(html);
const renderTemplate = mustache2litHtml('<div>Hello {{who}}!</div>');
render(renderTemplate({ who: 'world' }), document.getElementById('basic'));
Sorry for the inaction here. This is still a good feature request and one of our goals, we've just been busy with other things recently. I'm going to reopen.
@shaman-apprentice I'll only say that your lit-transformer project is 鉂わ笍 .