Skip to content

[labs/ssr] Add a serverhtml function, for templates that are only rendered on the server#4382

Merged
rictic merged 26 commits intomainfrom
ssr-special-templates
Nov 14, 2023
Merged

[labs/ssr] Add a serverhtml function, for templates that are only rendered on the server#4382
rictic merged 26 commits intomainfrom
ssr-special-templates

Conversation

@rictic
Copy link
Copy Markdown
Collaborator

@rictic rictic commented Nov 10, 2023

No description provided.

@changeset-bot
Copy link
Copy Markdown

changeset-bot bot commented Nov 10, 2023

🦋 Changeset detected

Latest commit: 53fb2bc

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@lit-labs/ssr Minor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Nov 10, 2023

📊 Tachometer Benchmark Results

Summary

nop-update

  • this-change, tip-of-tree, previous-release: unsure 🔍 -6% - +5% (-0.66ms - +0.54ms)
    this-change vs tip-of-tree

render

  • this-change: 48.55ms - 51.94ms
  • this-change, tip-of-tree, previous-release: unsure 🔍 -1% - +5% (-0.21ms - +0.95ms)
    this-change vs tip-of-tree
  • this-change, tip-of-tree, previous-release: unsure 🔍 -4% - +0% (-1.32ms - +0.10ms)
    this-change vs tip-of-tree
  • this-change, tip-of-tree, previous-release: unsure 🔍 -0% - +5% (-0.03ms - +1.53ms)
    this-change vs tip-of-tree

update

  • this-change: 525.73ms - 535.77ms
  • this-change, tip-of-tree, previous-release: unsure 🔍 -7% - +7% (-2.66ms - +3.03ms)
    this-change vs tip-of-tree
  • this-change, tip-of-tree, previous-release: unsure 🔍 -2% - +1% (-1.53ms - +0.83ms)
    this-change vs tip-of-tree
  • this-change, tip-of-tree, previous-release: unsure 🔍 -1% - +1% (-4.46ms - +4.01ms)
    this-change vs tip-of-tree

update-reflect

  • this-change: 518.33ms - 524.17ms
  • this-change, tip-of-tree, previous-release: unsure 🔍 -1% - +1% (-4.12ms - +3.90ms)
    this-change vs tip-of-tree

Results

this-change

render

VersionAvg timevs
48.55ms - 51.94ms-

update

VersionAvg timevs
525.73ms - 535.77ms-

update-reflect

VersionAvg timevs
518.33ms - 524.17ms-
this-change, tip-of-tree, previous-release

render

VersionAvg timevs this-change
vs tip-of-tree
tip-of-tree
vs previous-release
previous-release
this-change
18.45ms - 19.28ms-unsure 🔍
-1% - +5%
-0.21ms - +0.95ms
unsure 🔍
-3% - +3%
-0.55ms - +0.65ms
tip-of-tree
tip-of-tree
18.09ms - 18.90msunsure 🔍
-5% - +1%
-0.95ms - +0.21ms
-unsure 🔍
-5% - +1%
-0.91ms - +0.27ms
previous-release
previous-release
18.38ms - 19.25msunsure 🔍
-3% - +3%
-0.65ms - +0.55ms
unsure 🔍
-2% - +5%
-0.27ms - +0.91ms
-

update

VersionAvg timevs this-change
vs tip-of-tree
tip-of-tree
vs previous-release
previous-release
this-change
39.04ms - 42.99ms-unsure 🔍
-7% - +7%
-2.66ms - +3.03ms
unsure 🔍
-6% - +7%
-2.62ms - +2.67ms
tip-of-tree
tip-of-tree
38.78ms - 42.88msunsure 🔍
-7% - +6%
-3.03ms - +2.66ms
-unsure 🔍
-7% - +6%
-2.87ms - +2.55ms
previous-release
previous-release
39.22ms - 42.75msunsure 🔍
-7% - +6%
-2.67ms - +2.62ms
unsure 🔍
-6% - +7%
-2.55ms - +2.87ms
-

nop-update

VersionAvg timevs this-change
vs tip-of-tree
tip-of-tree
vs previous-release
previous-release
this-change
10.94ms - 11.76ms-unsure 🔍
-6% - +5%
-0.66ms - +0.54ms
unsure 🔍
-8% - +1%
-0.89ms - +0.16ms
tip-of-tree
tip-of-tree
10.97ms - 11.85msunsure 🔍
-5% - +6%
-0.54ms - +0.66ms
-unsure 🔍
-7% - +2%
-0.85ms - +0.25ms
previous-release
previous-release
11.38ms - 12.05msunsure 🔍
-2% - +8%
-0.16ms - +0.89ms
unsure 🔍
-2% - +8%
-0.25ms - +0.85ms
-
this-change, tip-of-tree, previous-release

render

VersionAvg timevs this-change
vs tip-of-tree
tip-of-tree
vs previous-release
previous-release
this-change
33.86ms - 34.86ms-unsure 🔍
-4% - +0%
-1.32ms - +0.10ms
unsure 🔍
-3% - +1%
-0.93ms - +0.44ms
tip-of-tree
tip-of-tree
34.47ms - 35.47msunsure 🔍
-0% - +4%
-0.10ms - +1.32ms
-unsure 🔍
-1% - +3%
-0.32ms - +1.04ms
previous-release
previous-release
34.14ms - 35.07msunsure 🔍
-1% - +3%
-0.44ms - +0.93ms
unsure 🔍
-3% - +1%
-1.04ms - +0.32ms
-

update

VersionAvg timevs this-change
vs tip-of-tree
tip-of-tree
vs previous-release
previous-release
this-change
70.85ms - 72.71ms-unsure 🔍
-2% - +1%
-1.53ms - +0.83ms
unsure 🔍
-2% - +2%
-1.09ms - +1.23ms
tip-of-tree
tip-of-tree
71.41ms - 72.85msunsure 🔍
-1% - +2%
-0.83ms - +1.53ms
-unsure 🔍
-1% - +2%
-0.57ms - +1.42ms
previous-release
previous-release
71.02ms - 72.39msunsure 🔍
-2% - +2%
-1.23ms - +1.09ms
unsure 🔍
-2% - +1%
-1.42ms - +0.57ms
-
this-change, tip-of-tree, previous-release

render

VersionAvg timevs this-change
vs tip-of-tree
tip-of-tree
vs previous-release
previous-release
this-change
33.45ms - 34.87ms-unsure 🔍
-0% - +5%
-0.03ms - +1.53ms
unsure 🔍
-1% - +4%
-0.27ms - +1.33ms
tip-of-tree
tip-of-tree
33.08ms - 33.73msunsure 🔍
-4% - +0%
-1.53ms - +0.03ms
-unsure 🔍
-2% - +1%
-0.72ms - +0.27ms
previous-release
previous-release
33.25ms - 34.01msunsure 🔍
-4% - +1%
-1.33ms - +0.27ms
unsure 🔍
-1% - +2%
-0.27ms - +0.72ms
-

update

VersionAvg timevs this-change
vs tip-of-tree
tip-of-tree
vs previous-release
previous-release
this-change
532.46ms - 537.67ms-unsure 🔍
-1% - +1%
-4.46ms - +4.01ms
unsure 🔍
-1% - +1%
-4.93ms - +3.47ms
tip-of-tree
tip-of-tree
531.95ms - 538.62msunsure 🔍
-1% - +1%
-4.01ms - +4.46ms
-unsure 🔍
-1% - +1%
-5.20ms - +4.18ms
previous-release
previous-release
532.50ms - 539.09msunsure 🔍
-1% - +1%
-3.47ms - +4.93ms
unsure 🔍
-1% - +1%
-4.18ms - +5.20ms
-

update-reflect

VersionAvg timevs this-change
vs tip-of-tree
tip-of-tree
vs previous-release
previous-release
this-change
532.69ms - 538.42ms-unsure 🔍
-1% - +1%
-4.12ms - +3.90ms
unsure 🔍
-1% - +1%
-6.27ms - +3.11ms
tip-of-tree
tip-of-tree
532.86ms - 538.48msunsure 🔍
-1% - +1%
-3.90ms - +4.12ms
-unsure 🔍
-1% - +1%
-6.12ms - +3.19ms
previous-release
previous-release
533.42ms - 540.85msunsure 🔍
-1% - +1%
-3.11ms - +6.27ms
unsure 🔍
-1% - +1%
-3.19ms - +6.12ms
-

tachometer-reporter-action v2 for Benchmarks

@github-actions
Copy link
Copy Markdown
Contributor

The size of lit-html.js and lit-core.min.js are as expected.

const text = child.value;
const textStart = child.sourceCodeLocation!.startOffset;
flushTo(textStart);
const markerRegex = new RegExp(marker.replace(/\$/g, '\\$'), 'g');

Check failure

Code scanning / CodeQL

Incomplete string escaping or encoding

This does not escape backslash characters in the input.
Copy link
Copy Markdown
Collaborator

@jimsimon jimsimon left a comment

Choose a reason for hiding this comment

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

Are there any security implications with using this new API? If so, it might be a good idea to add that info to the README.

Also, do we need to update the website docs?

Copy link
Copy Markdown
Contributor

@AndrewJakubowicz AndrewJakubowicz left a comment

Choose a reason for hiding this comment

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

This is amazing!

Copy link
Copy Markdown
Member

@augustjk augustjk left a comment

Choose a reason for hiding this comment

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

Very nice! I just have minor nits, mostly around docs.

@rictic
Copy link
Copy Markdown
Collaborator Author

rictic commented Nov 11, 2023

bindings in element part silently do nothing. since server only templates will throw on property and event binding for attribute parts, it doesn't seem like a bad idea for it to throw on element part binding too? though in the future we have opportunities to add server only directives like spreading attributes there.

Good point! Threw an error in that case and added a test.


`serverhtml` templates can be composed, and combined, and they support almost all features that normal Lit templates do, with the exception of features that don't have a pure HTML representation, like event handlers or property bindings.

`serverhtml` templates can only be rendered once, so they can't be updated on the client. However if you render a normal Lit template inside a serverhtml template, then it can be hydrated and updated. Likewise, if you place a custom element inside a serverhtml template, it can be hydrated and update like normal.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Nit–non blocking docs. Can always be addressed in followup.

"serverhtml templates should only be rendered once, so they can't be updated on the client.".

This could be misconstrued as: you can use them somehow on the client as long as they are only rendered once. May be worth clarifying again here that serverhtml templates throw if they are used on the client.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Ah, good point! Did another pass

`serverhtml` templates can only be rendered once, so they can't be updated on the client. However if you render a normal Lit template inside a serverhtml template, then it can be hydrated and updated. Likewise, if you place a custom element inside a serverhtml template, it can be hydrated and update like normal.

```js
import {html} from 'lit';
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This import is unused in this file and can be removed.

Suggested change
import {html} from 'lit';

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Good catch! Done

});
} else if (
!hydratable &&
/^(title|textarea|script)$/.test(node.tagName)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Should style be added here. (It's also handled as a raw text element by lit-html)?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Good point! Treating similarly to <script>, with unsafeHTML as the escape hatch.


if (partIndex !== result.values.length) {
if (partIndex !== result.values.length && hydratable) {
throwErrorForPartIndexMismatch(partIndex, result);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Why are part index mismatches ok with server templates?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Ah, I think that was from an old and worse prototype! Thank you for catching this!


/**
* Returns true if the given node is a <script> node that the browser will
* automatically executeif it's rendered on server-side, outside of a
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Nit: executeif -> "execute if"

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Done!

* features that don't have a pure HTML representation, like event handlers or
* property bindings.
*
* `serverhtml` templates can only be rendered once, so they can't be updated
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

"can't be updated on the client". Maybe here is where we add the text around "can't be used on the client at all".

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Updated!

Copy link
Copy Markdown
Contributor

@AndrewJakubowicz AndrewJakubowicz left a comment

Choose a reason for hiding this comment

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

AMAZING!

rictic and others added 22 commits November 14, 2023 15:08
A serverhtml template seems like it works fine to render both full pages and fragments, from doctype on down. If we discover a need for a document template later, we can add that API.
But not server templates inside normal templates (currently).

Also add integration tests of partial hydration of client side templates inside a server side template.
Co-authored-by: Augustine Kim <augustinekim@google.com>
@rictic rictic force-pushed the ssr-special-templates branch from 8c6ce54 to 53fb2bc Compare November 14, 2023 23:11
@rictic rictic merged commit 011b762 into main Nov 14, 2023
@rictic rictic deleted the ssr-special-templates branch November 14, 2023 23:34
@lit-robot lit-robot mentioned this pull request Nov 14, 2023
Here's an example that shows how to use a serverhtml template to render a full document, and then lazily hydrate both a custom element and a template:

```js
import {render, serverhtml} from '@lit-labs/ssr';
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I think like with lit-html/static.js exporting just html, we should probably name this just html too. If users want to rename it they can do:

import {render, html as serverHtml} from '@lit-labs/ssr';

With the serverhtml name, they'll need to rename it if they want to get automatic syntax highlighting:

import {render, serverhtml as html} from '@lit-labs/ssr';

and I think that'll be a fairly common case. I suspect that few, if any, files will import both client and server html tags. Instead server files would import client functions that use the client html in their own module.


## Server-only templates

You can also write templates that will only render on the server. These templates can be used for rendering full documents, including the doctype, and rendering into elements that Lit normally cannot, like `<title>`, `<textarea>`, `<template>`, and non-executing `<script>` tags like `<script type="text/json">`. They are also slightly more efficient than normal Lit templates, because the generated HTML doesn't need to include markers for updating.
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

The "non-executing <script> tags" bit isn't clear to me. Can you render to executing script tags?


You can also write templates that will only render on the server. These templates can be used for rendering full documents, including the doctype, and rendering into elements that Lit normally cannot, like `<title>`, `<textarea>`, `<template>`, and non-executing `<script>` tags like `<script type="text/json">`. They are also slightly more efficient than normal Lit templates, because the generated HTML doesn't need to include markers for updating.

`serverhtml` templates can be composed, and combined, and they support almost all features that normal Lit templates do, with the exception of features that don't have a pure HTML representation, like event handlers or property bindings.
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I think it'd be good to spell out the limitations if they're short. It's that they can't set properties, add event listeners, or use element directives, yeah? It might also be good to spell out some more of the the things they can do that make them useful, mainly: escaping, handling arrays, and directives?

const [, prefix, caseSensitiveName] = /([.?@])?(.*)/.exec(
name as string
)!;
if (!hydratable) {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

In hot code I prefer direct comparisons, especially if code size isn't a concern, like if (hydratable === false)

@lit-robot lit-robot mentioned this pull request Nov 16, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants