Lit-html: Rendering file templates

Created on 18 Nov 2017  路  23Comments  路  Source: Polymer/lit-html

I would like to use a separate file for rendering a template: Often the actual HTML+CSS part is way bigger than the actual JavaScript. Separating them makes them more readable and manageable for developers and for tools. A lot of time my template is made from multiple parts which often shared and used in multiple template, it would be nice to able to load a view file, pass the variables and use it as a template and pass it to rendering. Within this views you do need tools to load other views so you can build up your template just as any server side rendered template system.

I know loading an other resource has drawbacks but with HTTP2 you should split your code so you can cache them individually and often when you developing you markuping and styling and not writing functionalities or the other way around.

Most helpful comment

That's what I wanted to avoid, because having a js file just to render a plain HTML with some expressions is confusing, The old model where you had your HTML in HTML and JS in JS was quite good for everything.

I understand that this is out of the scope of the project, I will make an directive based approach as a third party so we could explore this topic more.

All 23 comments

You can already write your template as a separate file:

// my-style.js

import { html } from './node_modules/lit-html/lit-html.js'
const style = html`<style>
  .msg {
    font-weight: bold;
    font-size: 20px;
  }
</style>`;
export default style;

// my-header-template.js

import { html } from './node_modules/lit-html/lit-html.js'
const template = html`<div>My Header<div>`;
export default template;

// my-msg-template.js

import { html } from './node_modules/lit-html/lit-html.js'
const template = msg => html`<div class="msg">${msg}<div>`;
export default template;

Then, to use it:

// my-renderer.js

import styleTemplate from './my-style.js';
import headerTemplate from './my-header-template.js';
import msgTemplate from './my-msg-template.js';
import { render, html } from './node_modules/lit-html/lit-html.js'

const template = html`
  ${styleTemplate}
  ${headerTemplate}
  ${msgTemplate('Hello world')}
`;

Or inside a web component:

// my-component.js

import styleTemplate from './my-style.js';
import headerTemplate from './my-header-template.js';
import msgTemplate from './my-msg-template.js';
import { render, html } from './node_modules/lit-html/lit-html.js'

class MyComponent extends SomeLitElement {

  renderCallback() {
    return  html`
      ${styleTemplate}
      ${headerTemplate}
      ${msgTemplate('Hello world')}
    `;
  }

}

customElements.define('my-component', MyComponent);

Of course, they are all .js files. For styles and static templates it should not be hard to set up tooling that converts the separate .css and .html files. But that doesn't work for dynamic templates, like the msgTemplates, as it relies on the template literal syntax. Which is the point of this library :)

That's not what I want to do. What I want to achieve is the same with any other server side templating engine where you assign values and you only writing into the HTML the expressions:

// layout.tpl

${file("common/header.tpl")}
<main>
    ${content}
</main>

// common/header.tpl

<header>
    <span class="title">${title}</span>
</header>

// default.tpl

<p>Hello ${name}!</p>

// my-component.js

import { assign, file } from './node_modules/lit-html/lit-html.js'

class MyComponent extends SomeLitElement {
  renderCallback() {
    assign('title',"Welcome!");
    assign('name', "Adam");
    assign('content', file("default.tpl"));
    return file("layout.tpl");
  }
}

customElements.define('my-component', MyComponent);

So I want to use my templates just as it were inside a html`` template string, but I want to keep the HTML separate from the JavaScript but use their expressions. Look other template engines like Smarty, Dwoo, Mustache, Handlebars. I liked the dom-module idea because it allowed to keep the template and the JavaScript separate, I believe I'm not alone who not like smashing languages together one massive file format which actually belongs in separate formats.

Just because you can put HTML and CSS into JavaScript that's not mean you should do. Nice to have, there are a lot of use-cases when you want to do that, but when you build a huge application you want the template and it's logic language separate but connected.

I get what you're saying, I think it's especially useful for designers to work with the markup and css. With the tooling available today I think it is definitely possible to create what you describe with the .tpl files.

But what would you imagine should happen with the dynamic parts of the template? What kind of syntax should be allowed there? From your example I see the same syntax as if it were written in js, so what's the difference between mine and your example other than the file extension?

Well, if the tpl files were written just as they were in a template string you got the benefit from both sides. You get the JavaScript expressions and tools from the lit-html and you can give to designers and they should able just wrote html and css without knowing too much about the JavaScript part itself. It would be nice if we could just use the previous {{variable}} format because most of the tools are familiar with the handlebars syntax. The problem with the template strings that there are not too much tool what actually detect they format and switch to html whenever you are in that context.

What I imagine in the implementation side is the file() function should load the file and pipe it's content trough the parser so you could keep any of the template string format in your files, changing the opening and the ending tag is easy in a tool so designers can adapt to this format and get covered about 80% of their needs. The rest is covered with the lit-html tools and JavaScript and learning them is not that hard since most of the times they need to do that anyways with yet an other templating system.

Well, I somewhat achieved my goal, but it would be nice a real solution. What I did is I created a Mixin for Polymer 3.x and for the render I load my template file and re-render the shadowRoot.

Here is my mixin: https://yarnpkg.com/en/package/polymer-lit

And my-element.js

import {Element as PolymerElement} from '/node_modules/@polymer/polymer/polymer-element.js';
import {Lit} from '/node_modules/polymer-lit/polymer-lit.js';
import {html} from '/node_modules/lit-html/lit-html.js';

class MyElement extends Lit(PolymerElement)
{
    static get properties() { return {
        // ...
    }};

    render() {
        fetch('/templates/default.tpl').then(r => r.text())
            .then(templateContent => {
                const template = document.createElement('div');
                template.innerHTML = templateContent;
                render(html`${template.childNodes}`, this.shadowRoot || this);
            });

        return html`
            <style>:host { display: block; }</style>
            Loading...
    `;
    }
}

customElements.define('my-element', MyElement);

In my templates/default.tpl I have the expected content of a Polymer Element (slots and everything seems working), but I need to experiment with it a littlebit to load multiple templates inside a template and access everything.

@adaliszk I'm in a similar boat. There are still frontend designers out there who I'd rather not have to dive into the javascript and keep the separation of concerns true. I've used https://www.npmjs.com/package/es6-template-strings for compiling everything and then tossing the literals and substitutions into the html function. It was sort of a rough prototype and getting nested partials working wasn't really performant in my prototype.

For what it鈥檚 worth, there is relevant activity in standardizing a declarative HTML-module system on top of ES modules. This may eventually allow designers to write HTML templates that do not contain JavaScript but that may be used by JavaScript code such as Lit-HTML鈥檚.

The final shape of declarative HTML modules (see w3c/webcomponents#645, w3c/webcomponents#677) depends on declarative shadow DOM, declarative custom elements (w3c/webcomponents#135, w3c/webcomponents#136, whatwg/dom#510), and HTML-template instantiation (whatwg/html#2254, w3c/webcomponents#695, w3c/webcomponents#704).

Of course, these will help developers later, not now. But it鈥檚 worth pointing out that Lit-HTML鈥檚 original creator, @justinfagnani, has been active in these discussions, and, if I recall correctly, his philosophy with Lit-HTML is such that he wants its evolution to match the web platform鈥檚 eventual APIs. He would therefore undoubtedly take the discussions above into account for any declarative HTML template system in Lit-HTML, even before one is standardized for the web platform itself.

@js-choi has the right idea here for declarative syntax, IMO: align with the emerging standard so that lit-html is not inventing its own format. I think the template instantiation proposal is _very_ amenable to this, and if not I'll try to nudge it in the right direction ;)

I had previously been hacking on a declarative format that allowed us to use <template> elements in place of JS closures, so that we could avoid JS except for basic expressions. It looks a little like this:

<ul>
  <template directive="repeat(data.items, $this)" parameters="item">
    <li>${item}
  </template>
</ul>

Where the <template> tag is compiled into a JS function. repeat is the plain directive we have today, and $this is a special variable that represents the function the template is compiled to. This is equivalent to this JS (some details to prevent name collisions left out):

html`<ul>
  ${repeat(data.items, function(item) { return html`
    <li>${item}
  `;})}
</ul>`

So that's already really close to the proposed template instantiation, including the directive attribute.

To better align with the proposal, I think we can divide lit-html conceptually into three parts:

  1. The template instantiation functionality: parsing templates to find expressions, creating instances with Parts, updating instances.
  2. A JS syntax for those templates: The html tag that generates an HTML string and creates a <template> element, and a render() function that either creates new instances or updates existing ones, and passes along values.
  3. A template processor, in the template instantiation terminology. It implements the specific semantics that lit-html has, especially around dirty-checking and handling iterables.

A declarative syntax based on the proposal can leave out (2), and just use (1), and (3). This should make it easy for developers to move between imperative and declarative syntaxes while sharing the same set of semantics, directives, and even compose between them.

That's really nice, but for now what I want to use the current system with file templates. There are way too much proposals and experiments, which they are really good but I don't want to wait them. For now I think I can add this file-rendering feature as a directive and later it will expand with the new ideas as well.

The goal for me is not replacing the tag system or the features inside a html tagged template string, just load it's contents from a separate file. I don't want to use JavaScript files just to write html in them and add some boilerplate just to get it done. That just feels wrong and making some tooling solution around it, it doesn't not feel right with the #UseThePlatform policies.

Since I started to work with Polymer Elements a lot of structure for me changed, 95% of the time what I need is just pass datas into the proper elements and places. For example the whole repeat directive and it's template format is not needed for me because I have polymers dom-repeat or iron-list for that job. In the end of the day you will need that functionality but to get start with your application you don't need any of this, because you have elements for that.

I think having the parts in separate file is makes more sense, makes better cached applications and less data required to transfer upon change. I already did this in reverse with Polymer where the script part was in separate file (not to mention I was able to serve multiple version from the same file based on browser or user). This new way gives a lot of flexibility and I can again use the same template splinting methods what I was used to on server side templating again.

The directive is in the #203 PR :wink:

I think that loading plain HTML into lit-html and interpreting it is a bit out of scope at the moment. If the goal is to keep the template separate from the rest of the component/app logic, the lit-html template can be put in a separate .js file, like:

import {html} from 'lit-html/lib/lit-extended.js';

export default const template = (data) => html`
  <h1>${data.title}</h1>
`;
import template from './template.js';

render(template(data), document.body);

This has the benefit of naturally supporting expressions, being loadable by browsers and blocking module readiness properly, and bundable by tools.

I'm going to close issue this for now, but supporting a truly declarative form based on template instantiation is still in the plans.

That's what I wanted to avoid, because having a js file just to render a plain HTML with some expressions is confusing, The old model where you had your HTML in HTML and JS in JS was quite good for everything.

I understand that this is out of the scope of the project, I will make an directive based approach as a third party so we could explore this topic more.

Great point @adaliszk

@adaliszk couldn't you just use ejs or mustache? If tou want HTML in HTML and JS in JS I'd say lit-html is the wrong tool for the job

@obedm503 The goal is to separate the template parts in files. I want to load the markup and style when I actually need it, the whole template or parts of it. For example on desktop I will load the desktop markup and style instead having a huge markup and stylesheet which handles all of the cases and I don't load markup for features what I will anyways hide in the DOM because the user shouldn't access that.

I do use server-side template engine alongside this because the translatons are way faster to just serve from backend than support it on frontend and I do have sensitive parts what I only want to send when it's actually needed. The directive (or the ES import) and lit-html works really well with this structure.

I see the point... Using ES6 literals but keeping the HTML in a separate file for the peace of mind of those who do markup+css only in the team...

Both .html files containing JavaScript and .js files containing HTML seem wrong to me, but I am more used to the latest, as JavaScript is a programming language (not a markup language) and source code use to contain other markup languages in some parts of the code historically and that looks more familiar to me.

Still I prefer separate .html files for template 馃槱

@DavidValin yeah, my initial idea was already scrapped on my end as well. I'm using currently a mustache solution where the template is separate. I might able to create a package from it soon, I'll just need to ask for permission and free up some time for it 馃槃

@adaliszk Would be very interesting to see your solution!

This could make slot useless 馃憤!

@BenoitClaveau I don't think it would make slots useless, in my use-case back then when I needed it I've used it to load Dom functionalities when they were needed. The slots just got connected and transmitted trough. This is basically a server side rendering on client side :D

@aleksip I'll try to make time for it.

I had an exactly same problem and I ended up making a rollup plugin that transform a template file to one export default function.
https://npmjs.com/package/rollup-plugin-es6template

I built Hyperstache for just this
https://github.com/luwes/hyperstache

and for making the tagged template an external file
https://github.com/luwes/rollup-plugin-tagged-template

@adaliszk This wou

@BenoitClaveau I don't think it would make slots useless, in my use-case back then when I needed it I've used it to load Dom functionalities when they were needed. The slots just got connected and transmitted trough. This is basically a server side rendering on client side :D

@aleksip I'll try to make time for it.

This would be amazing. I'm in a similar predicament, do we have a solution for it yet? Help appreciated 馃檪

Was this page helpful?
0 / 5 - 0 ratings

Related issues

valdrinkoshi picture valdrinkoshi  路  5Comments

justinfagnani picture justinfagnani  路  3Comments

kaaninel picture kaaninel  路  3Comments

RyanAfrish7 picture RyanAfrish7  路  4Comments

dflorey picture dflorey  路  4Comments