From fc93949b72cfcdd10a5effcf085a39427b8dca09 Mon Sep 17 00:00:00 2001 From: Steven Orvell Date: Fri, 15 May 2020 12:05:47 -0700 Subject: [PATCH 01/13] Emit '; } - *renderStyles( - instance: LitElement - ): IterableIterator { + *renderStyles(instance: LitElement): IterableIterator { const ctor = customElements.get(instance.tagName); const styles = [(ctor as any).styles].flat(Infinity); let hasCssResult = false; diff --git a/src/lib/render-lit-html.ts b/src/lib/render-lit-html.ts index 782a902..0f294b4 100644 --- a/src/lib/render-lit-html.ts +++ b/src/lib/render-lit-html.ts @@ -112,12 +112,15 @@ declare global { } } -const setAttributeToElement = (element: LitElement, name: string, - value: unknown) => { +const setAttributeToElement = ( + element: LitElement, + name: string, + value: unknown +) => { if (element.attributeChangedCallback) { element.attributeChangedCallback(name, null, value as string); } -} +}; /** * Returns the scoped style sheets required by all elements currently defined. @@ -348,7 +351,7 @@ export function* renderTemplateResult( let attributeString = `${attributeName}="`; // attr.value has the raw attribute value, which may contain multiple // bindings. Replace the markers with their resolved values. - const value = getAttrValue(attr, result, partIndex) + const value = getAttrValue(attr, result, partIndex); // Set attribute into element instance if (instance !== undefined) { setAttributeToElement(instance, attributeName, value); From fd378bf9e05b89e191adc3eec248480e1e19152c Mon Sep 17 00:00:00 2001 From: Steven Orvell Date: Tue, 19 May 2020 12:54:08 -0700 Subject: [PATCH 10/13] Expand ElementRenderer/LitElementRenderer Adds to ElementRenderer: * setProperty * setAttribute Adds to LitElementRenderer * renderLight --- src/demo/app-client.ts | 2 +- src/lib/element-renderer.ts | 29 +++++++++-------- src/lib/lit-element-renderer.ts | 40 ++++++++++++++--------- src/lib/render-lit-html.ts | 56 ++++++++++++++------------------- 4 files changed, 67 insertions(+), 60 deletions(-) diff --git a/src/demo/app-client.ts b/src/demo/app-client.ts index 38e4ca9..6edfd54 100644 --- a/src/demo/app-client.ts +++ b/src/demo/app-client.ts @@ -16,7 +16,7 @@ LitElement.hydrate = hydrate; // at `node_modules/lit-html` and // `node_modules/lit-element/node_modules/lit-html`. This should be addressed // by fixing the dev server, but setting it here works around this problem. -LitElement.render = render; +(LitElement as any).render = render; console.log('Page hydrating with same data as rendered with SSR.'); // The hydrate() function is run with the same data as used in the server diff --git a/src/lib/element-renderer.ts b/src/lib/element-renderer.ts index f072845..9f190be 100644 --- a/src/lib/element-renderer.ts +++ b/src/lib/element-renderer.ts @@ -1,7 +1,5 @@ /// -import {RenderInfo} from './render-lit-html.js'; - /** * @license * Copyright (c) 2019 The Polymer Project Authors. All rights reserved. @@ -22,22 +20,27 @@ export type Constructor = {new (): T}; * An object that renders elements of a certain type. */ export interface ElementRenderer { + element: HTMLElement; + + tagName: string; + /** * Render a single element's ShadowRoot contents. * - * @param e The element instance to render - * @param childRenderer A `ChildRenderer` that can be used to render children - * into slots. */ - renderElement( - e: HTMLElement, - renderInfo: RenderInfo - ): IterableIterator; + renderElement(): IterableIterator; /** - * Render the pre-scoped styles for an element definition. - * - * @param c The element _class_ to render styles for. + * Handles setting a property. + * @param name Name of the property + * @param value Value of the property + */ + setProperty(name: string, value: unknown): void; + + /** + * Handles setting an attribute on an element. + * @param name Name of the attribute + * @param value Value of the attribute */ - renderStyles(c: HTMLElement): Iterator; + setAttribute(name: string, value: string | null): void; } diff --git a/src/lib/lit-element-renderer.ts b/src/lib/lit-element-renderer.ts index 062c177..2df307f 100644 --- a/src/lib/lit-element-renderer.ts +++ b/src/lib/lit-element-renderer.ts @@ -14,8 +14,7 @@ import {ElementRenderer} from './element-renderer.js'; import {LitElement, TemplateResult, CSSResult} from 'lit-element'; -import {render, RenderInfo} from './render-lit-html.js'; -//import StyleTransformer from '@webcomponents/shadycss/src/style-transformer.js'; +import {render, renderValue, RenderInfo} from './render-lit-html.js'; export type Constructor = {new (): T}; @@ -23,21 +22,15 @@ export type Constructor = {new (): T}; * An object that renders elements of a certain type. */ export class LitElementRenderer implements ElementRenderer { - *renderElement( - instance: LitElement, - _renderInfo: RenderInfo - ): IterableIterator { - const renderResult = ((instance as unknown) as { + constructor(public element: LitElement, public tagName: string) {} + + *renderElement(): IterableIterator { + const renderResult = ((this.element as unknown) as { render(): TemplateResult; }).render(); yield ''; - } - - *renderStyles(instance: LitElement): IterableIterator { - const ctor = customElements.get(instance.tagName); + // Render styles. + const ctor = customElements.get(this.tagName); const styles = [(ctor as any).styles].flat(Infinity); let hasCssResult = false; for (const style of styles) { @@ -55,5 +48,24 @@ export class LitElementRenderer implements ElementRenderer { if (hasCssResult) { yield ''; } + yield* render(renderResult); + yield ''; + } + + setProperty(name: string, value: unknown) { + (this.element as any)[name] = value; + } + + setAttribute(name: string, value: string | null) { + // Note, this should always exist for LitElement, but we're not yet + // explicitly checking for LitElement. + if (this.element.attributeChangedCallback) { + this.element.attributeChangedCallback(name, null, value as string); + } + } + + renderLight(renderInfo: RenderInfo) { + const templateResult = (this.element as any)?.renderLight(); + return templateResult ? renderValue(templateResult, renderInfo) : ''; } } diff --git a/src/lib/render-lit-html.ts b/src/lib/render-lit-html.ts index 0f294b4..9f6da28 100644 --- a/src/lib/render-lit-html.ts +++ b/src/lib/render-lit-html.ts @@ -103,7 +103,11 @@ const getTemplate = (result: TemplateResult) => { const globalMarkerRegex = new RegExp(markerRegex, `${markerRegex.flags}g`); export type RenderInfo = { - instances: Array<{tagName: string; instance?: LitElement}>; + instances: Array<{ + tagName: string; + instance?: LitElement; + renderer?: LitElementRenderer; + }>; }; declare global { @@ -112,16 +116,6 @@ declare global { } } -const setAttributeToElement = ( - element: LitElement, - name: string, - value: unknown -) => { - if (element.attributeChangedCallback) { - element.attributeChangedCallback(name, null, value as string); - } -}; - /** * Returns the scoped style sheets required by all elements currently defined. */ @@ -159,10 +153,8 @@ export function* renderValue( // If a value was produced with renderLight(), we want to call and render // the renderLight() method. const instance = renderInfo.instances[renderInfo.instances.length - 1]; - // TODO, move out of here into something LitElement specific - if (instance.instance !== undefined) { - const templateResult = (instance.instance as any).renderLight(); - yield* renderValue(templateResult, renderInfo); + if (instance?.renderer !== undefined) { + yield* instance.renderer.renderLight(renderInfo); } } else if (value === nothing || value === noChange) { // yield nothing @@ -252,8 +244,8 @@ export function* renderTemplateResult( yield* renderValue(value, renderInfo); } } else if (isElement(node)) { - // If the element is custom, this will be the instantiated class - let instance: LitElement | undefined = undefined; + // If the element is custom, this will be a renderer for the instantiated class + let elementRenderer: LitElementRenderer | undefined = undefined; // Whether to flush the start tag. This is neccessary if we're changing // any of the attributes in the tag, so it's true for custom-elements @@ -272,11 +264,12 @@ export function* renderTemplateResult( // Instantiate the element and stream its render() result try { - instance = new ctor(); - (instance as any).tagName = node.tagName; - // renderInfo.instances[ - // renderInfo.instances.length - 1 - // ].instance = instance; + elementRenderer = new LitElementRenderer(new ctor(), node.tagName); + const currentInstance = + renderInfo.instances[renderInfo.instances.length - 1]; + if (currentInstance !== undefined) { + currentInstance.renderer = elementRenderer; + } } catch (e) { console.error('Exception in custom element constructor', e); } @@ -314,8 +307,8 @@ export function* renderTemplateResult( partIndex += statics.length - 1; } // Set property into element instance - if (instance !== undefined) { - (instance as any)[propertyName] = value; + if (elementRenderer !== undefined) { + elementRenderer.setProperty(propertyName, value); } // Property should be reflected to attribute const reflectedName = reflectedAttributeName(tagName, propertyName); @@ -342,8 +335,8 @@ export function* renderTemplateResult( const value = result.values[partIndex++]; if (value) { // Set attribute into element instance - if (instance !== undefined) { - setAttributeToElement(instance, attributeName, value); + if (elementRenderer !== undefined) { + elementRenderer.setAttribute(attributeName, value as string); } yield attributeName; } @@ -353,8 +346,8 @@ export function* renderTemplateResult( // bindings. Replace the markers with their resolved values. const value = getAttrValue(attr, result, partIndex); // Set attribute into element instance - if (instance !== undefined) { - setAttributeToElement(instance, attributeName, value); + if (elementRenderer !== undefined) { + elementRenderer.setAttribute(attributeName, value); } attributeString += value; partIndex += statics.length - 1; @@ -377,13 +370,12 @@ export function* renderTemplateResult( yield ``; } - if (instance !== undefined) { + if (elementRenderer !== undefined) { // TODO: look up a renderer instead of creating one - const renderer = new LitElementRenderer(); - yield* renderer.renderElement(instance as LitElement, renderInfo); + yield* elementRenderer.renderElement(); } // console.log('end element', node.tagName, renderInfo.instances); - // renderInfo.instances.pop(); + renderInfo.instances.pop(); } } From eff10bd31c757e552c8664f70011d293c7edc5ae Mon Sep 17 00:00:00 2001 From: Steven Orvell Date: Tue, 19 May 2020 13:43:23 -0700 Subject: [PATCH 11/13] Remove warning for hydrate. --- src/demo/app-client.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/demo/app-client.ts b/src/demo/app-client.ts index 6edfd54..ac3dde6 100644 --- a/src/demo/app-client.ts +++ b/src/demo/app-client.ts @@ -10,7 +10,7 @@ import {template, initialData} from './module.js'; declare var window: any; // Note, give LitElement a hydrate function. -LitElement.hydrate = hydrate; +(LitElement as any).hydrate = hydrate; // TODO(sorvell): Give LitElement a render function to avoid a version // conflict that results from the dev server config. Here lit-html is loaded // at `node_modules/lit-html` and From 8fa8e449714d8f9781a75da223362fb06b869b7c Mon Sep 17 00:00:00 2001 From: Steven Orvell Date: Tue, 19 May 2020 13:46:03 -0700 Subject: [PATCH 12/13] Update tests. --- src/test/lit/render-lit_test.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/test/lit/render-lit_test.ts b/src/test/lit/render-lit_test.ts index 0fc959c..c44c9e6 100644 --- a/src/test/lit/render-lit_test.ts +++ b/src/test/lit/render-lit_test.ts @@ -192,7 +192,7 @@ test('simple custom element', async (t: Test) => { const result = await render(simpleTemplateWithElement); t.equal( result, - `
` + `` ); }); @@ -202,7 +202,7 @@ test('element with property', async (t: Test) => { // TODO: we'd like to remove the extra space in the start tag t.equal( result, - `
bar
` + `` ); }); @@ -215,7 +215,7 @@ test('no slot', async (t: Test) => { const result = await render(noSlot); t.equal( result, - `

Hi

` + `

Hi

` ); }); From e93083e4a87f8357acb4c9ee0ca1236af5effb1c Mon Sep 17 00:00:00 2001 From: Steven Orvell Date: Tue, 19 May 2020 13:49:27 -0700 Subject: [PATCH 13/13] Address feedback. --- src/lib/render-lit-html.ts | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/src/lib/render-lit-html.ts b/src/lib/render-lit-html.ts index 7d1310c..a519d31 100644 --- a/src/lib/render-lit-html.ts +++ b/src/lib/render-lit-html.ts @@ -296,9 +296,7 @@ const getTemplate = (result: TemplateResult) => { }; export type RenderInfo = { - customElementInstanceStack: Array< - {element: HTMLElement; renderer: LitElementRenderer} | undefined - >; + customElementInstanceStack: Array; }; declare global { @@ -346,7 +344,7 @@ export function* renderValue( // the renderLight() method. const instance = getLast(renderInfo.customElementInstanceStack); if (instance !== undefined) { - yield* instance.renderer.renderLight(renderInfo); + yield* instance.renderLight(renderInfo); } } else if (value === nothing || value === noChange) { // yield nothing @@ -409,7 +407,7 @@ export function* renderTemplateResult( const propertyName = name.substring(1); const value = result.values[partIndex]; if (instance !== undefined) { - instance.renderer.setProperty(propertyName, value); + instance.setProperty(propertyName, value); } // Property should be reflected to attribute const reflectedName = reflectedAttributeName( @@ -432,7 +430,7 @@ export function* renderTemplateResult( const value = result.values[partIndex]; if (value) { if (instance !== undefined) { - instance.renderer.setAttribute(attributeName, value as string); + instance.setAttribute(attributeName, value as string); } yield attributeName; } @@ -443,10 +441,7 @@ export function* renderTemplateResult( partIndex )}"`; if (instance !== undefined) { - instance.renderer.setAttribute( - attributeName, - attributeString as string - ); + instance.setAttribute(attributeName, attributeString as string); } yield attributeString; } @@ -460,8 +455,7 @@ export function* renderTemplateResult( try { const element = new ctor(); (element as any).tagName = op.tagName; - const renderer = new LitElementRenderer(element); - instance = {element, renderer}; + instance = new LitElementRenderer(element); } catch (e) { console.error('Exception in custom element constructor', e); } @@ -471,7 +465,7 @@ export function* renderTemplateResult( case 'custom-element-render': { const instance = getLast(renderInfo.customElementInstanceStack); if (instance !== undefined) { - yield* instance.renderer.renderElement(); + yield* instance.renderElement(); } break; }