├── .gitignore
├── README.md
├── demo.html
├── package-lock.json
├── package.json
├── rollup.config.js
├── src
├── example-template-processor.ts
├── html-template-element-spec.ts
├── html-template-element.ts
├── template-assembly.ts
├── template-definition-spec.ts
├── template-definition.ts
├── template-instance.ts
├── template-part.ts
├── template-processor.ts
├── template-rule.ts
├── template-string-parser-spec.ts
└── template-string-parser.ts
├── test.html
├── tsconfig.json
└── tslint.json
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | lib
3 | *.swp
4 | .DS_Store
5 | template-instantiation.js
6 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Template Instantiation Strawman Prollyfill
2 |
3 | Template Instantiation is a new web platform feature with ongoing, developing
4 | proposals coming out of a few corners of the ecosystem.
5 |
6 | The most developed proposal so far comes from Ryosuke Niwa at Apple and can be
7 | found [here](https://github.com/w3c/webcomponents/blob/gh-pages/proposals/Template-Instantiation.md).
8 |
9 | This change proposes a Template Instantiation prollyfill implementation based
10 | on Ryosuke Niwa's proposal, and adapted with idioms and features found in
11 | the Polymer Team's own [lit-html](https://github.com/polymerlabs/lit-html).
12 |
13 | ## Concepts and Domain
14 |
15 | Template Instantiation is meant to offer a standard API for enabling some
16 | level of dynamism in otherwise static HTML templates. There are several
17 | relevant use cases outlined in [the Apple proposal](https://github.com/w3c/webcomponents/blob/gh-pages/proposals/Template-Instantiation.md#2-use-cases).
18 |
19 | Conceptually, this implementation consists of many of the same constructs.
20 | However, there are a few notable differences.
21 |
22 | Compared to the Apple proposal, the implementation here is lacking:
23 |
24 | - Special support for "inner" templates via an `InnerTemplatePart`.
25 | - ~Ability to `replace` or `replaceHTML` on `NodeTemplatePart`.~
26 |
27 | Areas where this implementation diverges from Apple's proposal include:
28 |
29 | - No template processor registry. The desired processor is passed by the user
30 | when creating an instance.
31 | - `TemplatePart` is updated by value, which has special meaning depending on
32 | the implementation of the derived `TemplatePart` derivative in question
33 | (a la lit-html).
34 |
35 | Features included in this implementation that are not covered by Apple's
36 | proposal:
37 |
38 | - An incremental construct pairing a parsed template and a future set of
39 | values to fill that template (`TemplateAssembly`)
40 | - ~Explicit support for `TemplateAssembly` as a value type that may be assigned
41 | to a `TemplateNodePart`.~
42 | - ~Ability to customize `TemplatePart` creation via `TemplateProcessor`'s
43 | `partCalback` (a la lit-html)~
44 | - No default implementation for `TemplateProcessor`, with the expectation that
45 | in v0 an author will be expected to provide their own.
46 |
47 | ### Template string parsing
48 |
49 | - Handled by a naive handlebars parser implementation.
50 | - Parser takes a templatized string, and returns a set of static strings and
51 | a set of expressions, very similarly to how JavaScript template literal
52 | tag function arguments work (although in this case the expressions are
53 | string literals and not values meant to be inserted directly).
54 |
55 | ### TemplateRule
56 |
57 | - Represents a spot/range in the otherwise static template that is deemed to
58 | be dynamic
59 | - Has a node index that is a walker-specific index of the node it cares about
60 | - Knows the rule(s) for the dynamic spot, the inner raw string text of
61 | the template part
62 | - Two specializations: `NodeTemplateRule` and `AttributeTemplateRule`.
63 | - A list of these is parsed from an `HTMLTemplateElement` and held by a
64 | `TemplateDefinition`
65 |
66 | ### TemplatePart
67 |
68 | - A value setter for a dynamic spot in a `TemplateInstance`.
69 | - Has a reference to a related `TemplateRule`, where it can access the
70 | related rule.
71 | - Has a reference to a node, which might refer to an attribute or a
72 | positionally relevant `Text` node somewhere.
73 | - Has a reference to its originating `TemplateInstance` (not sure why yet)
74 | - A list of these is generated by cross-referencing a set of `TemplateRule`
75 | instances with a related `DocumentFragment` and held by a
76 | `TemplateInstance`.
77 |
78 | ### TemplateDefinition
79 |
80 | - Prollyfill-specific construct, not yet obviously useful for authors
81 | - Unique per `HTMLTemplateElement` (for cacheability, cache can be overridden
82 | if desired).
83 | - Has a reference to HTMLTemplateElement
84 | - Has a parsed list of dynamic `TemplateRule` spots
85 | - Internally holds a reference to a "parsed" version of the static template
86 | for faster cloning into a `TemplateInstance`.
87 |
88 | ### TemplateAssembly
89 |
90 | - Maps closely to `lit-html`'s `TemplateResult`.
91 | - Has a `TemplateDefinition` instance.
92 | - Has some state that will eventually be set on a `TemplateInstance` generated
93 | from the `TemplateDefinition`.
94 | - Can be assigned as the value of a `NodeTemplatePart`, enabling
95 | `TemplateInstance` nesting.
96 | - Not sure how an author would actually create this yet.
97 |
98 | ### TemplateProcessor
99 |
100 | - Defines stateless-ish updates for template parts.
101 | - ~Implements `TemplatePart` creation via `partCallback` (working name
102 | borrowed from lit-html).~
103 | - Implements `TemplatePart` updating via `processCallback` (working name
104 | borrowed from Apple's proposal).
105 | - Trivial example processor provided in this project
106 |
107 | ### TemplateInstance
108 |
109 | - Created using `TemplateDefinition#createInstance(processor, state?)` (not
110 | to be invoked directly by author at this time).
111 | - Has a reference to its `TemplateProcessor`
112 | - Has a list of `TemplatePart` instances, generated by cross-referencing the
113 | `TemplateDefinition`'s `TemplateRules` against its own content and
114 | invoking the `TemplateProcessor`'s `partCallback` method.
115 | - Has a reference to the previously set state (maybe useful in
116 | `TemplateProcessor`, which is otherwise stateless)
117 | - Can be updated by calling `TemplateInstance#update(newState)`
118 | - Can handle a `TemplateAssembly` as a state value
119 |
120 | ### HTMLTemplateElement
121 |
122 | - Invoking `HTMLTemplateElement#createInstance(processor, state?)` creates
123 | a `TemplateDefinition` for the `HTMLTemplateElement` if one does not exist,
124 | then invokes `TemplateDefinition#createInstance(processor, state?)` and
125 | returns that value.
126 |
127 |
128 |
--------------------------------------------------------------------------------
/demo.html:
--------------------------------------------------------------------------------
1 |
2 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
This is a {{title}} template
20 |
21 |
{{body}}
22 |
23 |
24 |
{{index}}: {{value}}
25 |
26 |
27 |
30 |
31 |
32 |
57 |
58 |
59 |
60 |
61 |
--------------------------------------------------------------------------------
/package-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "template-instantiation-prollyfill",
3 | "version": "0.0.4",
4 | "lockfileVersion": 1,
5 | "requires": true,
6 | "dependencies": {
7 | "@polymer/ristretto": {
8 | "version": "0.3.1",
9 | "resolved": "https://registry.npmjs.org/@polymer/ristretto/-/ristretto-0.3.1.tgz",
10 | "integrity": "sha512-PzPJ2igfht8jmctbpd7bWlYxTsCMICf/QDPrZHFRHeDj7TtC01KOYHyt6RIcUZTotiBAugKAVDklZS7Fuq1msA==",
11 | "dev": true
12 | },
13 | "@types/chai": {
14 | "version": "4.0.10",
15 | "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.0.10.tgz",
16 | "integrity": "sha512-Ejh1AXTY8lm+x91X/yar3G2z4x9RyKwdTVdyyu7Xj3dNB35fMNCnEWqTO9FgS3zjzlRNqk1MruYhgb8yhRN9rA==",
17 | "dev": true
18 | },
19 | "assertion-error": {
20 | "version": "1.0.2",
21 | "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.0.2.tgz",
22 | "integrity": "sha1-E8pRXYYgbaC6xm6DTdOX2HWBCUw=",
23 | "dev": true
24 | },
25 | "chai": {
26 | "version": "4.1.2",
27 | "resolved": "https://registry.npmjs.org/chai/-/chai-4.1.2.tgz",
28 | "integrity": "sha1-D2RYS6ZC8PKs4oBiefTwbKI61zw=",
29 | "dev": true,
30 | "requires": {
31 | "assertion-error": "1.0.2",
32 | "check-error": "1.0.2",
33 | "deep-eql": "3.0.1",
34 | "get-func-name": "2.0.0",
35 | "pathval": "1.1.0",
36 | "type-detect": "4.0.5"
37 | }
38 | },
39 | "check-error": {
40 | "version": "1.0.2",
41 | "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz",
42 | "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=",
43 | "dev": true
44 | },
45 | "deep-eql": {
46 | "version": "3.0.1",
47 | "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz",
48 | "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==",
49 | "dev": true,
50 | "requires": {
51 | "type-detect": "4.0.5"
52 | }
53 | },
54 | "get-func-name": {
55 | "version": "2.0.0",
56 | "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz",
57 | "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=",
58 | "dev": true
59 | },
60 | "pathval": {
61 | "version": "1.1.0",
62 | "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.0.tgz",
63 | "integrity": "sha1-uULm1L3mUwBe9rcTYd74cn0GReA=",
64 | "dev": true
65 | },
66 | "rollup": {
67 | "version": "0.56.3",
68 | "resolved": "https://registry.npmjs.org/rollup/-/rollup-0.56.3.tgz",
69 | "integrity": "sha512-/iH4RfioboHgBjo7TbQcdMad/ifVGY/ToOB1AsW7oZHUhfhm+low6QlrImUSaJO1JqklOpWEKlD+b3MZYLuptA==",
70 | "dev": true
71 | },
72 | "type-detect": {
73 | "version": "4.0.5",
74 | "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.5.tgz",
75 | "integrity": "sha512-N9IvkQslUGYGC24RkJk1ba99foK6TkwC2FHAEBlQFBP0RxQZS8ZpJuAZcwiY/w9ZJHFQb1aOXBI60OdxhTrwEQ==",
76 | "dev": true
77 | },
78 | "typescript": {
79 | "version": "2.6.2",
80 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-2.6.2.tgz",
81 | "integrity": "sha1-PFtv1/beCRQmkCfwPAlGdY92c6Q=",
82 | "dev": true
83 | }
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "template-instantiation-prollyfill",
3 | "version": "0.0.4",
4 | "description": "A prollyfill for HTML template instantiation",
5 | "main": "template-instantiation.js",
6 | "files": [
7 | "lib/*"
8 | ],
9 | "scripts": {
10 | "build": "tsc",
11 | "watch": "tsc -w",
12 | "bundle": "rollup -c ./rollup.config.js",
13 | "prepublishOnly": "npm run build && npm run bundle"
14 | },
15 | "author": "The Polymer Authors",
16 | "license": "BSD-3-Clause",
17 | "devDependencies": {
18 | "@polymer/ristretto": "^0.3.1",
19 | "@types/chai": "^4.0.10",
20 | "chai": "^4.1.2",
21 | "rollup": "^0.56.3",
22 | "typescript": "^2.6.2"
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/rollup.config.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @license
3 | * Copyright (c) 2018 The Polymer Project Authors. All rights reserved.
4 | * This code may only be used under the BSD style license found at
5 | * http://polymer.github.io/LICENSE.txt
6 | * The complete set of authors may be found at
7 | * http://polymer.github.io/AUTHORS.txt
8 | * The complete set of contributors may be found at
9 | * http://polymer.github.io/CONTRIBUTORS.txt
10 | * Code distributed by Google as part of the polymer project is also
11 | * subject to an additional IP rights grant found at
12 | * http://polymer.github.io/PATENTS.txt
13 | */
14 |
15 | export default {
16 | input: './lib/html-template-element.js',
17 | output: {
18 | format: 'umd',
19 | file: './template-instantiation.js',
20 | name: 'TemplateInstantiation'
21 | }
22 | };
23 |
--------------------------------------------------------------------------------
/src/example-template-processor.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @license
3 | * Copyright (c) 2018 The Polymer Project Authors. All rights reserved.
4 | * This code may only be used under the BSD style license found at http:polymer.github.io/LICENSE.txt
5 | * The complete set of authors may be found at http:polymer.github.io/AUTHORS.txt
6 | * The complete set of contributors may be found at http:polymer.github.io/CONTRIBUTORS.txt
7 | * Code distributed by Google as part of the polymer project is also
8 | * subject to an additional IP rights grant found at http:polymer.github.io/PATENTS.txt
9 | */
10 |
11 | import { TemplateProcessor } from './template-processor.js';
12 | import {
13 | TemplatePart,
14 | NodeTemplatePart,
15 | AttributeTemplatePart,
16 | InnerTemplatePart
17 | } from './template-part.js';
18 | import {
19 | NodeTemplateRule,
20 | AttributeTemplateRule
21 | } from './template-rule.js';
22 |
23 | export class ExampleTemplateProcessor extends TemplateProcessor {
24 | createdCallback(_parts: TemplatePart[], _state?: any): void {}
25 |
26 | processCallback(parts: TemplatePart[], state?: any): void {
27 | for (const part of parts) {
28 | if (part instanceof InnerTemplatePart) {
29 | // TODO
30 | } else if (part instanceof NodeTemplatePart) {
31 | const { expression } = part.rule as NodeTemplateRule;
32 | part.value = state && expression && state[expression];
33 | } else if (part instanceof AttributeTemplatePart) {
34 | const { expressions } = part.rule as AttributeTemplateRule;
35 | part.value = state && expressions &&
36 | expressions.map(expression => state && state[expression]);
37 | }
38 | }
39 | }
40 | };
41 |
--------------------------------------------------------------------------------
/src/html-template-element-spec.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @license
3 | * Copyright (c) 2018 The Polymer Project Authors. All rights reserved.
4 | * This code may only be used under the BSD style license found at http:polymer.github.io/LICENSE.txt
5 | * The complete set of authors may be found at http:polymer.github.io/AUTHORS.txt
6 | * The complete set of contributors may be found at http:polymer.github.io/CONTRIBUTORS.txt
7 | * Code distributed by Google as part of the polymer project is also
8 | * subject to an additional IP rights grant found at http:polymer.github.io/PATENTS.txt
9 | */
10 |
11 | import { Spec } from '../../@polymer/ristretto/lib/spec.js';
12 | import { Fixturable } from
13 | '../../@polymer/ristretto/lib/mixins/fixturable.js';
14 | import '../../chai/chai.js';
15 | import { ExampleTemplateProcessor } from './example-template-processor.js';
16 | import './html-template-element.js';
17 |
18 | const spec = new (Fixturable(Spec))();
19 | const { describe, it, fixture } = spec;
20 | const { expect } = chai;
21 |
22 | describe('HTMLTemplateElement', () => {
23 | it('has a method createInstance', () => {
24 | expect(document.createElement('template').createInstance).to.be.ok;
25 | });
26 |
27 | describe('createInstance', () => {
28 | fixture(() => {
29 | const template = document.createElement('template');
30 | template.innerHTML = `
{{content}}
`;
31 | return { template };
32 | });
33 |
34 | describe('without arguments', () => {
35 | it('throws due to missing processor', ({ template }: any) => {
36 | let threw = false;
37 |
38 | try {
39 | template.createInstance();
40 | } catch (e) {
41 | threw = true;
42 | }
43 |
44 | expect(threw).to.be.equal(true);
45 | });
46 | });
47 |
48 | describe('given a processor', () => {
49 | fixture((context: any) => {
50 | return { ...context, processor: new ExampleTemplateProcessor };
51 | });
52 |
53 | it('returns a DocumentFragment', ({ template, processor }: any) => {
54 | const instance = template.createInstance(processor);
55 | expect(instance).to.be.instanceof(DocumentFragment);
56 | });
57 |
58 | it('returns a TemplateInstance', ({ template, processor }: any) => {
59 | const instance = template.createInstance(processor);
60 | expect(instance).to.be.instanceof(DocumentFragment);
61 | });
62 |
63 | describe('with initial state', () => {
64 | fixture((context: any) => {
65 | return { ...context, state: { content: 'Hello world.' } };
66 | });
67 |
68 | it('puts the state in the DOM', ({ template, processor, state }: any) => {
69 | const instance = template.createInstance(processor, state);
70 | expect(instance.childNodes[0].innerText).to.be.equal(state.content);
71 | });
72 | });
73 | });
74 | });
75 | });
76 |
77 | export const htmlTemplateElementSpec: Spec = spec;
78 |
--------------------------------------------------------------------------------
/src/html-template-element.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @license
3 | * Copyright (c) 2018 The Polymer Project Authors. All rights reserved.
4 | * This code may only be used under the BSD style license found at http:polymer.github.io/LICENSE.txt
5 | * The complete set of authors may be found at http:polymer.github.io/AUTHORS.txt
6 | * The complete set of contributors may be found at http:polymer.github.io/CONTRIBUTORS.txt
7 | * Code distributed by Google as part of the polymer project is also
8 | * subject to an additional IP rights grant found at http:polymer.github.io/PATENTS.txt
9 | */
10 |
11 | import { TemplateProcessor } from './template-processor.js';
12 | import { TemplateDefinition } from './template-definition.js';
13 | import { TemplateInstance } from './template-instance.js';
14 |
15 | const templateDefinitionCache: Map = new Map();
16 |
17 | declare global {
18 | interface HTMLTemplateElement {
19 | createInstance(
20 | processor: TemplateProcessor,
21 | state?: any,
22 | overrideDiagramCache?: boolean): TemplateInstance
23 | }
24 | }
25 |
26 | HTMLTemplateElement.prototype.createInstance = function(
27 | processor: TemplateProcessor,
28 | state?: any,
29 | overrideDefinitionCache = false): TemplateInstance {
30 | if (processor == null) {
31 | throw new Error('The first argument of createInstance must be an implementation of TemplateProcessor');
32 | }
33 |
34 | if (!templateDefinitionCache.has(this) || overrideDefinitionCache) {
35 | templateDefinitionCache.set(this, new TemplateDefinition(this));
36 | }
37 |
38 | const definition = templateDefinitionCache.get(this)!;
39 |
40 | return new TemplateInstance(definition, processor, state);
41 | };
42 |
--------------------------------------------------------------------------------
/src/template-assembly.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @license
3 | * Copyright (c) 2018 The Polymer Project Authors. All rights reserved.
4 | * This code may only be used under the BSD style license found at http:polymer.github.io/LICENSE.txt
5 | * The complete set of authors may be found at http:polymer.github.io/AUTHORS.txt
6 | * The complete set of contributors may be found at http:polymer.github.io/CONTRIBUTORS.txt
7 | * Code distributed by Google as part of the polymer project is also
8 | * subject to an additional IP rights grant found at http:polymer.github.io/PATENTS.txt
9 | */
10 |
11 | import { TemplateDefinition } from './template-definition.js';
12 | import { TemplateProcessor } from './template-processor.js';
13 |
14 | export class TemplateAssembly {
15 | constructor(public definition: TemplateDefinition,
16 | public processor: TemplateProcessor,
17 | public state?: any) {}
18 | };
19 |
--------------------------------------------------------------------------------
/src/template-definition-spec.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @license
3 | * Copyright (c) 2018 The Polymer Project Authors. All rights reserved.
4 | * This code may only be used under the BSD style license found at http:polymer.github.io/LICENSE.txt
5 | * The complete set of authors may be found at http:polymer.github.io/AUTHORS.txt
6 | * The complete set of contributors may be found at http:polymer.github.io/CONTRIBUTORS.txt
7 | * Code distributed by Google as part of the polymer project is also
8 | * subject to an additional IP rights grant found at http:polymer.github.io/PATENTS.txt
9 | */
10 |
11 | import { Spec } from '../../@polymer/ristretto/lib/spec.js';
12 | import { Fixturable } from '../../@polymer/ristretto/lib/mixins/fixturable.js';
13 | import '../../chai/chai.js';
14 | import { TemplateDefinition } from './template-definition.js';
15 | import { NodeTemplateRule, AttributeTemplateRule } from './template-rule.js';
16 |
17 | const spec = new (Fixturable(Spec))();
18 | const { describe, it, fixture } = spec;
19 | const { expect } = chai;
20 |
21 | describe('TemplateDefinition', () => {
22 | describe('with an empty template', () => {
23 | fixture(() => {
24 | const template = document.createElement('template');
25 | template.innerHTML = ``;
26 | return {
27 | template,
28 | definition: new TemplateDefinition(template)
29 | };
30 | });
31 |
32 | it('generates no rules', (context: any) => {
33 | const { definition } = context;
34 | expect(definition.rules.length).to.be.equal(0);
35 | });
36 | });
37 |
38 | describe('with a dynamic node part', () => {
39 | fixture(() => {
40 | const template = document.createElement('template');
41 | template.innerHTML = `