Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve types for (un)compiled template results #4309

Merged
merged 3 commits into from
Nov 3, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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> =
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: This could use a JSDoc to clarify why this is used. Probably want to call out that this should ideally be the type of html or svg when you're trying to target both uncompiled and compiled environments.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point! Done

| 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
);
rictic marked this conversation as resolved.
Show resolved Hide resolved
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