Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Conversation

@jimsimon
Copy link
Collaborator

@jimsimon jimsimon commented Oct 10, 2025

This change switches static html to use a double keyed cache. The outer key is the pre-flattened template strings and the inner key is the final template string after all of the static parts have been merged into it (which was the previous behavior). It also implements the inner cache as a simple LRU with a relatively small max size. The idea here is that the ideal usage of static html has a small cardinality, but improper usage may have a high/infinite cardinality and we don't want to OOM the server (when using SSR) with an ever-growing cache.

@changeset-bot
Copy link

changeset-bot bot commented Oct 10, 2025

🦋 Changeset detected

Latest commit: 81dd1be

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

This PR includes changesets to release 1 package
Name Type
lit-html Patch

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
Contributor

github-actions bot commented Oct 10, 2025

📊 Tachometer Benchmark Results

Summary

nop-update

  • this-change, tip-of-tree, previous-release: unsure 🔍 -4% - +8% (-0.52ms - +1.05ms)
    this-change vs tip-of-tree

render

  • this-change: 45.65ms - 61.25ms
  • this-change, tip-of-tree, previous-release: unsure 🔍 -3% - +4% (-0.58ms - +0.74ms)
    this-change vs tip-of-tree
  • this-change, tip-of-tree, previous-release: unsure 🔍 -3% - +2% (-1.02ms - +0.64ms)
    this-change vs tip-of-tree
  • this-change, tip-of-tree, previous-release: unsure 🔍 -15% - +26% (-9.33ms - +16.91ms)
    this-change vs tip-of-tree

update

  • this-change: 445.70ms - 451.84ms
  • this-change, tip-of-tree, previous-release: unsure 🔍 -3% - +4% (-1.15ms - +1.61ms)
    this-change vs tip-of-tree
  • this-change, tip-of-tree, previous-release: unsure 🔍 -0% - +3% (-0.04ms - +2.21ms)
    this-change vs tip-of-tree
  • this-change, tip-of-tree, previous-release: unsure 🔍 -1% - +1% (-5.38ms - +3.52ms)
    this-change vs tip-of-tree

update-reflect

  • this-change: 428.36ms - 431.60ms
  • this-change, tip-of-tree, previous-release: unsure 🔍 -1% - +1% (-2.51ms - +6.60ms)
    this-change vs tip-of-tree

Results

this-change

render

VersionAvg timevs
45.65ms - 61.25ms-

update

VersionAvg timevs
445.70ms - 451.84ms-

update-reflect

VersionAvg timevs
428.36ms - 431.60ms-
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.03ms - 18.95ms-unsure 🔍
-3% - +4%
-0.58ms - +0.74ms
unsure 🔍
-4% - +2%
-0.81ms - +0.40ms
tip-of-tree
tip-of-tree
17.93ms - 18.88msunsure 🔍
-4% - +3%
-0.74ms - +0.58ms
-unsure 🔍
-5% - +2%
-0.90ms - +0.33ms
previous-release
previous-release
18.30ms - 19.09msunsure 🔍
-2% - +4%
-0.40ms - +0.81ms
unsure 🔍
-2% - +5%
-0.33ms - +0.90ms
-

update

VersionAvg timevs this-change
vs tip-of-tree
tip-of-tree
vs previous-release
previous-release
this-change
36.03ms - 37.87ms-unsure 🔍
-3% - +4%
-1.15ms - +1.61ms
unsure 🔍
-3% - +3%
-1.29ms - +1.15ms
tip-of-tree
tip-of-tree
35.69ms - 37.74msunsure 🔍
-4% - +3%
-1.61ms - +1.15ms
-unsure 🔍
-4% - +3%
-1.61ms - +0.99ms
previous-release
previous-release
36.23ms - 37.82msunsure 🔍
-3% - +3%
-1.15ms - +1.29ms
unsure 🔍
-3% - +4%
-0.99ms - +1.61ms
-

nop-update

VersionAvg timevs this-change
vs tip-of-tree
tip-of-tree
vs previous-release
previous-release
this-change
12.36ms - 13.52ms-unsure 🔍
-4% - +8%
-0.52ms - +1.05ms
slower ❌
0% - 13%
0.05ms - 1.52ms
tip-of-tree
tip-of-tree
12.15ms - 13.19msunsure 🔍
-8% - +4%
-1.05ms - +0.52ms
-unsure 🔍
-1% - +10%
-0.17ms - +1.21ms
previous-release
previous-release
11.70ms - 12.60msfaster ✔
1% - 12%
0.05ms - 1.52ms
unsure 🔍
-9% - +1%
-1.21ms - +0.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
35.03ms - 36.11ms-unsure 🔍
-3% - +2%
-1.02ms - +0.64ms
unsure 🔍
-3% - +2%
-1.11ms - +0.76ms
tip-of-tree
tip-of-tree
35.12ms - 36.40msunsure 🔍
-2% - +3%
-0.64ms - +1.02ms
-unsure 🔍
-3% - +3%
-0.98ms - +1.01ms
previous-release
previous-release
34.98ms - 36.51msunsure 🔍
-2% - +3%
-0.76ms - +1.11ms
unsure 🔍
-3% - +3%
-1.01ms - +0.98ms
-

update

VersionAvg timevs this-change
vs tip-of-tree
tip-of-tree
vs previous-release
previous-release
this-change
80.19ms - 81.80ms-unsure 🔍
-0% - +3%
-0.04ms - +2.21ms
slower ❌
0% - 3%
0.06ms - 2.28ms
tip-of-tree
tip-of-tree
79.12ms - 80.70msunsure 🔍
-3% - +0%
-2.21ms - +0.04ms
-unsure 🔍
-1% - +1%
-1.02ms - +1.19ms
previous-release
previous-release
79.06ms - 80.59msfaster ✔
0% - 3%
0.06ms - 2.28ms
unsure 🔍
-1% - +1%
-1.19ms - +1.02ms
-
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
61.24ms - 79.61ms-unsure 🔍
-15% - +26%
-9.33ms - +16.91ms
unsure 🔍
-5% - +45%
-1.45ms - +25.00ms
tip-of-tree
tip-of-tree
57.27ms - 76.00msunsure 🔍
-24% - +13%
-16.91ms - +9.33ms
-unsure 🔍
-11% - +38%
-5.37ms - +21.33ms
previous-release
previous-release
49.14ms - 68.16msunsure 🔍
-34% - +1%
-25.00ms - +1.45ms
unsure 🔍
-31% - +7%
-21.33ms - +5.37ms
-

update

VersionAvg timevs this-change
vs tip-of-tree
tip-of-tree
vs previous-release
previous-release
this-change
434.85ms - 441.05ms-unsure 🔍
-1% - +1%
-5.38ms - +3.52ms
unsure 🔍
-1% - +0%
-6.45ms - +1.99ms
tip-of-tree
tip-of-tree
435.69ms - 442.07msunsure 🔍
-1% - +1%
-3.52ms - +5.38ms
-unsure 🔍
-1% - +1%
-5.58ms - +2.98ms
previous-release
previous-release
437.33ms - 443.03msunsure 🔍
-0% - +1%
-1.99ms - +6.45ms
unsure 🔍
-1% - +1%
-2.98ms - +5.58ms
-

update-reflect

VersionAvg timevs this-change
vs tip-of-tree
tip-of-tree
vs previous-release
previous-release
this-change
441.84ms - 450.27ms-unsure 🔍
-1% - +1%
-2.51ms - +6.60ms
unsure 🔍
-1% - +1%
-3.09ms - +6.24ms
tip-of-tree
tip-of-tree
442.29ms - 445.73msunsure 🔍
-1% - +1%
-6.60ms - +2.51ms
-unsure 🔍
-1% - +0%
-3.10ms - +2.16ms
previous-release
previous-release
442.49ms - 446.47msunsure 🔍
-1% - +1%
-6.24ms - +3.09ms
unsure 🔍
-0% - +1%
-2.16ms - +3.10ms
-

tachometer-reporter-action v2 for Benchmarks

@github-actions
Copy link
Contributor

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

@jimsimon jimsimon changed the title [lit-ssr] Use double-keyed LRU cache for static html [lit-ssr] Use a double-keyed LRU cache for static html Oct 10, 2025
@jimsimon jimsimon changed the title [lit-ssr] Use a double-keyed LRU cache for static html [lit-ssr] Use a double-keyed LRU cache for server rendered static html Oct 10, 2025
@jimsimon jimsimon marked this pull request as ready for review October 10, 2025 21:02
@jimsimon jimsimon changed the title [lit-ssr] Use a double-keyed LRU cache for server rendered static html [lit-html] Use a double-keyed LRU cache for server rendered static html Oct 10, 2025
Copy link
Contributor

@kyubisation kyubisation left a comment

Choose a reason for hiding this comment

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

Generally looks good to me.
One question about the public API; Should the LRU cache be exposed or should this be an internal implementation detail?

Comment on lines +128 to +139
let stringsCache: Cache;
if (isServer) {
stringsCache = new Map<
TemplateStringsArray,
LRUCache<string, TemplateStringsArray>
>();
} else {
stringsCache = new Map<
TemplateStringsArray,
Map<string, TemplateStringsArray>
>();
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Is there any difference here? Both paths initialize a Map and the type is given by Cache.
From my understanding this can simply be reduced to the following suggestion. Or am I missing something?

Suggested change
let stringsCache: Cache;
if (isServer) {
stringsCache = new Map<
TemplateStringsArray,
LRUCache<string, TemplateStringsArray>
>();
} else {
stringsCache = new Map<
TemplateStringsArray,
Map<string, TemplateStringsArray>
>();
}
const stringsCache: Cache = new Map();

super.delete(key);
super.set(key, value);
if (this.size > this.maxSize) {
const keyToDelete = this.keys().next().value;
Copy link
Contributor

Choose a reason for hiding this comment

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

During the community call there was a question about performance of accessing this.keys().next().value.
From a brief, naive test case this seems acceptable.

Performance test case
const perfTest = (name, setup, test) => {
  console.log(`Starting performance test: ${name}`);
  const times = [];
  for (let i = 0; i < 100; i++) {
    const data = setup();
    const start = performance.now();
    test(data);
    times.push(performance.now() - start);
    if (i % 10 === 0) {
      console.log(`Completed ${i} runs`);
    }
  }
  const avgTime = times.reduce((a, b) => a + b, 0) / times.length;
  console.log(`Average time over 100 runs: ${avgTime.toFixed(6)} ms`);
}

perfTest(
  'Small map key access',
  () => new Map().set(1, 1).set(2, 2),
  (data) => data.keys().next().value
);
perfTest(
  'Large map key access',
  () => [...Array(100000).keys()].map(() => crypto.randomUUID()).reduce((m, k) => m.set(k, k), new Map()),
  (data) => data.keys().next().value
);

Comment on lines +185 to +189
if (isServer) {
innerCache = new LRUCache<string, TemplateStringsArray>(10);
} else {
innerCache = new Map<string, TemplateStringsArray>();
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Code style nitpick; Feel free to ignore.

Suggested change
if (isServer) {
innerCache = new LRUCache<string, TemplateStringsArray>(10);
} else {
innerCache = new Map<string, TemplateStringsArray>();
}
innerCache = isServer
? new LRUCache<string, TemplateStringsArray>(10)
: new Map<string, TemplateStringsArray>();

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.

2 participants