From e00f6f52199d5dbc08d4c15f62380422e77cde7f Mon Sep 17 00:00:00 2001 From: Kevin Schaaf Date: Tue, 14 Feb 2023 10:15:32 -0800 Subject: [PATCH] [labs/ssr] Emit lit-node marker before node to support raw text nodes. (#3667) * Emit lit-node marker before node to support raw text nodes. Fixes #3663 * Fix copy/paste mistake. --- .changeset/soft-turtles-protect.md | 6 ++ packages/labs/ssr/src/lib/render-value.ts | 77 +++++++++++-------- .../ssr/src/test/integration/tests/basic.ts | 43 +++++++++++ .../labs/ssr/src/test/lib/render-lit_test.ts | 36 ++++----- packages/lit-html/src/experimental-hydrate.ts | 9 +-- 5 files changed, 115 insertions(+), 56 deletions(-) create mode 100644 .changeset/soft-turtles-protect.md diff --git a/.changeset/soft-turtles-protect.md b/.changeset/soft-turtles-protect.md new file mode 100644 index 0000000000..7a1d296446 --- /dev/null +++ b/.changeset/soft-turtles-protect.md @@ -0,0 +1,6 @@ +--- +'@lit-labs/ssr': patch +'lit-html': patch +--- + +Improved how nodes with attribute/property/event/element bindings are rendered in SSR, to avoid adding comments inside of "raw text elements" like ``; + } + } + customElements.define('raw-element-host', RawElementHost); + }, + render() { + return html``; + }, + expectations: [ + { + args: [], + html: '', + async check(assert: Chai.Assert, dom: HTMLElement) { + const host = dom.querySelector('raw-element-host') as LitElement & { + text: string; + }; + assert.instanceOf(host, LitElement); + assert.equal(host.text, 'hello'); + + await host.updateComplete; + const textarea = host.shadowRoot?.querySelector('textarea'); + assert.equal(textarea?.value, 'hello'); + + host.text = 'goodbye'; + await host.updateComplete; + assert.equal(textarea?.value, 'goodbye'); + }, + }, + ], + stableSelectors: ['textarea'], + }, + /****************************************************** * PropertyPart tests ******************************************************/ diff --git a/packages/labs/ssr/src/test/lib/render-lit_test.ts b/packages/labs/ssr/src/test/lib/render-lit_test.ts index 8e8e8322d0..b87d069687 100644 --- a/packages/labs/ssr/src/test/lib/render-lit_test.ts +++ b/packages/labs/ssr/src/test/lib/render-lit_test.ts @@ -118,7 +118,7 @@ for (const global of [emptyVmGlobal, shimmedVmGlobal]) { const result = await render(templateWithAttributeExpression('foo')); assert.is( result, - `
` + `
` ); }); @@ -127,7 +127,7 @@ for (const global of [emptyVmGlobal, shimmedVmGlobal]) { const result = await render(inputTemplateWithAttributeExpression('foo')); assert.is( result, - `` + `` ); }); @@ -144,7 +144,7 @@ for (const global of [emptyVmGlobal, shimmedVmGlobal]) { ); assert.is( result, - `

hi

` + `

hi

` ); }); @@ -156,7 +156,7 @@ for (const global of [emptyVmGlobal, shimmedVmGlobal]) { // Has marker attribute for number of bound attributes. assert.is( result, - `
` + `
` ); }); @@ -167,7 +167,7 @@ for (const global of [emptyVmGlobal, shimmedVmGlobal]) { ); assert.is( result, - `
` + `
` ); }); @@ -178,7 +178,7 @@ for (const global of [emptyVmGlobal, shimmedVmGlobal]) { const result = await render(inputTemplateWithValueProperty('foo')); assert.is( result, - `` + `` ); }); @@ -187,7 +187,7 @@ for (const global of [emptyVmGlobal, shimmedVmGlobal]) { const result = await render(elementTemplateWithClassNameProperty('foo')); assert.is( result, - `
` + `
` ); }); @@ -196,7 +196,7 @@ for (const global of [emptyVmGlobal, shimmedVmGlobal]) { const result = await render(elementTemplateWithClassnameProperty('foo')); assert.is( result, - `
` + `
` ); }); @@ -205,7 +205,7 @@ for (const global of [emptyVmGlobal, shimmedVmGlobal]) { const result = await render(elementTemplateWithIDProperty('foo')); assert.is( result, - `
` + `
` ); }); @@ -276,7 +276,7 @@ for (const global of [emptyVmGlobal, shimmedVmGlobal]) { // TODO: we'd like to remove the extra space in the start tag assert.is( result, - `` + `` ); }); @@ -286,7 +286,7 @@ for (const global of [emptyVmGlobal, shimmedVmGlobal]) { // TODO: we'd like to remove the extra space in the start tag assert.is( result, - `` + `` ); }); @@ -296,7 +296,7 @@ for (const global of [emptyVmGlobal, shimmedVmGlobal]) { // TODO: we'd like to remove the extra space in the start tag assert.is( result, - `` + `` ); }); @@ -306,7 +306,7 @@ for (const global of [emptyVmGlobal, shimmedVmGlobal]) { // TODO: we'd like to remove the extra space in the start tag assert.is( result, - `` + `` ); }); @@ -316,7 +316,7 @@ for (const global of [emptyVmGlobal, shimmedVmGlobal]) { // TODO: we'd like to remove the extra space in the start tag assert.is( result, - `` + `` ); }); @@ -326,7 +326,7 @@ for (const global of [emptyVmGlobal, shimmedVmGlobal]) { // TODO: we'd like to remove the extra space in the start tag assert.is( result, - `` + `` ); }); @@ -345,7 +345,7 @@ for (const global of [emptyVmGlobal, shimmedVmGlobal]) { // TODO: we'd like to remove the extra space in the start tag assert.is( result, - `` + `` ); }); @@ -442,7 +442,7 @@ for (const global of [emptyVmGlobal, shimmedVmGlobal]) { const result = await render(classMapDirective); assert.is( result, - '
' + '
' ); }); @@ -451,7 +451,7 @@ for (const global of [emptyVmGlobal, shimmedVmGlobal]) { const result = await render(classMapDirectiveMultiBinding); assert.is( result, - '
' + '
' ); }); diff --git a/packages/lit-html/src/experimental-hydrate.ts b/packages/lit-html/src/experimental-hydrate.ts index 4df91a1a34..a8f3478d01 100644 --- a/packages/lit-html/src/experimental-hydrate.ts +++ b/packages/lit-html/src/experimental-hydrate.ts @@ -348,11 +348,10 @@ const createAttributeParts = ( const match = /lit-node (\d+)/.exec(comment.data)!; const nodeIndex = parseInt(match[1]); - // For void elements, the node the comment was referring to will be - // the previousSibling; for non-void elements, the comment is guaranteed - // to be the first child of the element (i.e. it won't have a previousSibling - // meaning it should use the parentElement) - const node = comment.previousElementSibling ?? comment.parentElement; + // Node markers are added as a previous sibling to identify elements + // with attribute/property/element/event bindings or custom elements + // whose `defer-hydration` attribute needs to be removed + const node = comment.nextElementSibling; if (node === null) { throw new Error('could not find node for attribute parts'); }