Skip to content

Commit

Permalink
Improve types for (un)compiled template results (#4309)
Browse files Browse the repository at this point in the history
* Improve types for (un)compiled template results

Adds two new types: `UncompiledTemplateResult` and `MaybeCompiledTemplateResult`. Currently `UncompiledTemplateResult` is the same as `TemplateResult`, and `MaybeCompiledTemplateResult` is the union of the compiled and uncompiled types.

We'd like for code to be able to handle the `html` and `svg` functions returning a MaybeCompiledTemplateResult. Changing that type will be a major breaking change though, and we don't want to do a major version so quickly, but this lays the ground work, and provides future-compatible types that people can use when they want to be explicit about which type they mean, because while TemplateResult will change, MaybeCompiled, Uncompiled, and Compiled will all have the same meanings in the next major version.

* Changeset

* Add more comments.
  • Loading branch information
rictic committed Nov 3, 2023
1 parent d1b2719 commit 949a546
Show file tree
Hide file tree
Showing 5 changed files with 74 additions and 19 deletions.
5 changes: 5 additions & 0 deletions .changeset/proud-bulldogs-sin.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'lit-html': minor
---

Adds two new types: UncompiledTemplateResult and MaybeCompiledTemplateResult. Currently UncompiledTemplateResult is the same as TemplateResult, and MaybeCompiledTemplateResult is the union of the compiled and uncompiled types.
13 changes: 7 additions & 6 deletions packages/lit-html/src/directive-helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@ import {
_$LH,
Part,
DirectiveParent,
TemplateResult,
CompiledTemplateResult,
MaybeCompiledTemplateResult,
UncompiledTemplateResult,
} from './lit-html.js';
import {
DirectiveResult,
Expand Down Expand Up @@ -49,11 +50,11 @@ export type TemplateResultType =
(typeof TemplateResultType)[keyof typeof TemplateResultType];

type IsTemplateResult = {
(val: unknown): val is TemplateResult | CompiledTemplateResult;
(val: unknown): val is MaybeCompiledTemplateResult;
<T extends TemplateResultType>(
val: unknown,
type: T
): val is TemplateResult<T>;
): val is UncompiledTemplateResult<T>;
};

/**
Expand All @@ -62,11 +63,11 @@ type IsTemplateResult = {
export const isTemplateResult: IsTemplateResult = (
value: unknown,
type?: TemplateResultType
): value is TemplateResult =>
): value is UncompiledTemplateResult =>
type === undefined
? // This property needs to remain unminified.
(value as TemplateResult)?.['_$litType$'] !== undefined
: (value as TemplateResult)?.['_$litType$'] === type;
(value as UncompiledTemplateResult)?.['_$litType$'] !== undefined
: (value as UncompiledTemplateResult)?.['_$litType$'] === type;

/**
* Tests if a value is a CompiledTemplateResult.
Expand Down
48 changes: 43 additions & 5 deletions packages/lit-html/src/lit-html.ts
Original file line number Diff line number Diff line change
Expand Up @@ -455,7 +455,7 @@ const COMMENT_PART = 7;

/**
* The return type of the template tag functions, {@linkcode html} and
* {@linkcode svg}.
* {@linkcode svg} when it hasn't been compiled by @lit-labs/compiler.
*
* A `TemplateResult` object holds all the information about a template
* expression required to render it: the template strings, expression values,
Expand All @@ -466,17 +466,55 @@ const COMMENT_PART = 7;
* [Rendering](https://lit.dev/docs/components/rendering) for more information.
*
*/
export type TemplateResult<T extends ResultType = ResultType> = {
export type UncompiledTemplateResult<T extends ResultType = ResultType> = {
// This property needs to remain unminified.
['_$litType$']: T;
strings: TemplateStringsArray;
values: unknown[];
};

/**
* This is a template result that may be either uncompiled or compiled.
*
* In the future, TemplateResult will be this type. If you want to explicitly
* note that a template result is potentially compiled, you can reference this
* type and it will continue to behave the same through the next major version
* of Lit. This can be useful for code that wants to prepare for the next
* major version of Lit.
*/
export type MaybeCompiledTemplateResult<T extends ResultType = ResultType> =
| UncompiledTemplateResult<T>
| CompiledTemplateResult;

/**
* The return type of the template tag functions, {@linkcode html} and
* {@linkcode svg}.
*
* A `TemplateResult` object holds all the information about a template
* expression required to render it: the template strings, expression values,
* and type of template (html or svg).
*
* `TemplateResult` objects do not create any DOM on their own. To create or
* update DOM you need to render the `TemplateResult`. See
* [Rendering](https://lit.dev/docs/components/rendering) for more information.
*
* In Lit 4, this type will be an alias of
* MaybeCompiledTemplateResult, so that code will get type errors if it assumes
* that Lit templates are not compiled. When deliberately working with only
* one, use either {@linkcode CompiledTemplateResult} or
* {@linkcode UncompiledTemplateResult} explicitly.
*/
export type TemplateResult<T extends ResultType = ResultType> =
UncompiledTemplateResult<T>;

export type HTMLTemplateResult = TemplateResult<typeof HTML_RESULT>;

export type SVGTemplateResult = TemplateResult<typeof SVG_RESULT>;

/**
* A TemplateResult that has been compiled by @lit-labs/compiler, skipping the
* prepare step.
*/
export interface CompiledTemplateResult {
// This is a factory in order to make template initialization lazy
// and allow ShadyRenderOptions scope to be passed in.
Expand Down Expand Up @@ -876,7 +914,7 @@ class Template {

constructor(
// This property needs to remain unminified.
{strings, ['_$litType$']: type}: TemplateResult,
{strings, ['_$litType$']: type}: UncompiledTemplateResult,
options?: RenderOptions
) {
let node: Node | null;
Expand Down Expand Up @@ -1515,7 +1553,7 @@ class ChildPart implements Disconnectable {
// to create the <template> element the first time we see it.
const template: Template | CompiledTemplate =
typeof type === 'number'
? this._$getTemplate(result as TemplateResult)
? this._$getTemplate(result as UncompiledTemplateResult)
: (type.el === undefined &&
(type.el = Template.createElement(
trustFromTemplateString(type.h, type.h[0]),
Expand Down Expand Up @@ -1565,7 +1603,7 @@ class ChildPart implements Disconnectable {

// Overridden via `litHtmlPolyfillSupport` to provide platform support.
/** @internal */
_$getTemplate(result: TemplateResult) {
_$getTemplate(result: UncompiledTemplateResult) {
let template = templateCache.get(result.strings);
if (template === undefined) {
templateCache.set(result.strings, (template = new Template(result)));
Expand Down
10 changes: 6 additions & 4 deletions packages/lit-html/src/test/directive-helpers_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
TemplateResult,
CompiledTemplateResult,
CompiledTemplate,
UncompiledTemplateResult,
} from 'lit-html';
import {assert} from '@esm-bundle/chai';
import {stripExpressionComments} from '@lit-labs/testing';
Expand Down Expand Up @@ -119,7 +120,8 @@ suite('directive-helpers', () => {
test('isTemplateResult type only test', () => {
// This test has no runtime checks, and fails at build time if there are
// type issues.
function acceptTemplateResult(_v: TemplateResult) {}
function acceptUncompiledTemplateResult(_v: UncompiledTemplateResult) {}

function acceptTemplateOrCompiledTemplateResult(
_v: TemplateResult | CompiledTemplateResult
) {}
Expand All @@ -135,16 +137,16 @@ suite('directive-helpers', () => {
acceptTemplateOrCompiledTemplateResult(v);

// @ts-expect-error v could be a CompiledTemplateResult
acceptTemplateResult(v);
acceptUncompiledTemplateResult(v);
}
if (isTemplateResult(v, TemplateResultType.HTML)) {
acceptTemplateResult(v);
acceptUncompiledTemplateResult(v);
acceptTemplateResultHtml(v);
// @ts-expect-error v is an html template result
acceptTemplateResultSvg(v);
}
if (isTemplateResult(v, TemplateResultType.SVG)) {
acceptTemplateResult(v);
acceptUncompiledTemplateResult(v);
acceptTemplateResultSvg(v);
// @ts-expect-error v is an svg template result
acceptTemplateResultHtml(v);
Expand Down
17 changes: 13 additions & 4 deletions packages/lit-starter-ts/src/test/my-element_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import {MyElement} from '../my-element.js';

import {fixture, assert} from '@open-wc/testing';
import {UncompiledTemplateResult} from 'lit';
import {html} from 'lit/static-html.js';

suite('my-element', () => {
Expand All @@ -16,7 +17,9 @@ suite('my-element', () => {
});

test('renders with default values', async () => {
const el = await fixture(html`<my-element></my-element>`);
const el = await fixture(
html`<my-element></my-element>` as UncompiledTemplateResult
);
assert.shadowDom.equal(
el,
`
Expand All @@ -28,7 +31,9 @@ suite('my-element', () => {
});

test('renders with a set name', async () => {
const el = await fixture(html`<my-element name="Test"></my-element>`);
const el = await fixture(
html`<my-element name="Test"></my-element>` as UncompiledTemplateResult
);
assert.shadowDom.equal(
el,
`
Expand All @@ -40,7 +45,9 @@ suite('my-element', () => {
});

test('handles a click', async () => {
const el = (await fixture(html`<my-element></my-element>`)) as MyElement;
const el = (await fixture(
html`<my-element></my-element>` as UncompiledTemplateResult
)) as MyElement;
const button = el.shadowRoot!.querySelector('button')!;
button.click();
await el.updateComplete;
Expand All @@ -55,7 +62,9 @@ suite('my-element', () => {
});

test('styling applied', async () => {
const el = (await fixture(html`<my-element></my-element>`)) as MyElement;
const el = (await fixture(
html`<my-element></my-element>` as UncompiledTemplateResult
)) as MyElement;
await el.updateComplete;
assert.equal(getComputedStyle(el).paddingTop, '16px');
});
Expand Down

0 comments on commit 949a546

Please sign in to comment.