Skip to content

Build Compose HTML SSR EAP trees directly during composition - #5733

Merged
Clemens Koprolin (clemenskoprolin) merged 3 commits into
masterfrom
clemenskoprolin/compose-html-direct-ssr
Sep 28, 2026
Merged

Clemens Koprolin (clemenskoprolin) merged 3 commits into
masterfrom
clemenskoprolin/compose-html-direct-ssr

Conversation

@clemenskoprolin

Copy link
Copy Markdown
Collaborator

This PR improves Compose HTML EAP string rendering by building HTML
trees directly during composition, avoiding recorded HTML node factories
and updates. The existing composeHtmlToString and renderHydratedDocument
APIs use the new implementation.

Serialization avoids copying validated attributes and appends ordinary text in bulk.

In our testing, warm rendering takes about 48% less time than master
across eight shared workloads. All eleven measured warm workloads improve.

Involves CMP-10836

Testing

Run from the html directory:

./gradlew -Pcompose.html.eap.enabled=true \
  :html-core-eap:jvmTest \
  :html-core-eap:jsBrowserTest \
  :html-core-eap:wasmJsBrowserTest \
  :html-svg-eap:jvmTest \
  :html-svg-eap:jsBrowserTest \
  :html-svg-eap:wasmJsBrowserTest

Release Notes

N/A

override val supportsDomElementAccess: Boolean = false

override fun <TElement : Element> elementBuilder(tagName: String): ElementBuilder<TElement> =
StringElementBuilder(tagName)

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.

would caching / reusing the element builders help a bit?

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.

I tested it on the testing suit, it does not really show any clear performance or memory benefits.

}

@Composable
@NonRestartableComposable

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.

Can we have @ExplicitGroupsComposable for the composables here?

StringHtmlElementNode(elementBuilder.tagName, namespace)
}

val attrs = AttrsScopeBuilder<TElement>()

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.

I wonder if we can avoid re-allocating the AttrsScopeBuilder every time and save its reference in the node?

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.

Yes, this makes sense for keyed renders. I added it there.

val previousIndex = index
parent = node
index = 0
content?.invoke(StringElementScope())

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.

Let's create a constant StringElementScope() and pass it to all invoke?

Move shared serialization helpers into their own file.

Co-authored-by: Oleksandr Karpovich <a.n.karpovich@gmail.com>
private val hydratable: Boolean,
private val attrsBuilders: AttrsBuilderPool? = null,
) : ComposeHtmlContext {
private val output = StringBuilder()

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.

Is it worth it to try to reuse a StringBuilder too by adding it to ReusableHtmlComposition?

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.

Yes, that helped. It reduced the subsequent render times by ~10%!

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.

Nice! Let's merge this PR :)

@clemenskoprolin
Clemens Koprolin (clemenskoprolin) merged commit dc78ead into master Sep 28, 2026
2 checks passed
@clemenskoprolin
Clemens Koprolin (clemenskoprolin) deleted the clemenskoprolin/compose-html-direct-ssr branch September 28, 2026 14:36
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