Skip to content

blitz-dom: `Node::outer_html` drops comments and template contents and writes `<p />`

Generated from repros/blitz-dom-outer-html-serialization/README.md when the site was built at 7aa23e2.

niso serializes innerHTML/outerHTML itself with the HTML fragment serialization algorithm (crates/niso-core/src/serialize.rs, called from crates/niso-core/src/api.rs:1356-1379; commit 8e05962 “Serialize HTML with the spec algorithm, including comments and template contents” replaced calls to Node::outer_html()). Svelte 5’s templates are innerHTML round-trips through <template> with comment anchors, so all of the bugs below break it.

Node::write_outer_html_in_style (packages/blitz-dom/src/node/serialize.rs:117-226):

  1. Comments are dropped (NodeData::Comment { .. } => {}).
  2. <template> contents are dropped: the parser puts them in the detached template_contents fragment (mutator.rs:189-219), and the serializer only walks children.
  3. Every element without children is written as <x />. For non-void HTML elements that is not self-closing when parsed back, so <p /> followed by siblings swallows them. Void elements are written <br /> instead of <br> (harmless when reparsed, but not the spec’s output).
  4. U+00A0 is written raw instead of &nbsp;.
expected: <div id="t" title="a&quot;b"><!--c--><template><b>x</b></template><p></p><br><span>a&nbsp;b</span></div>
actual: <div id="t" title="a&quot;b"><template /><p /><br /><span>a b</span></div>

(the space in a b is a raw U+00A0.)

Not counted as bugs: there is no inner_html (niso writes children itself), and outer_html of the Document node is empty; those are missing APIs. niso’s tree.rs (pre-insert validity, adopt, etc.) is likewise spec logic blitz’s DocumentMutator does not offer rather than a blitz bug, so it has no repro.

Terminal window
CARGO_TARGET_DIR=~/.capy/work/repro-target cargo run

Exit status 1 while the bug reproduces, 0 once fixed. On Ubuntu, blitz needs sudo apt-get install -y libfontconfig-dev pkg-config.

Reproduces. Blitz main was at be60597f67b3cece0456d26e8d7ff5a88600bbc5 when tested (2026-11-08, git ls-remote https://github.com/DioxusLabs/blitz HEAD), the same commit niso pins, so the pinned result is also the main result.

Also tested against open PR DioxusLabs/blitz#1204 (head 9c4db3a0ef23424f58bb87b952ca3ec1555c4141, path dependency on a local checkout): it fixes 3 and 4, but comments and template contents are still dropped:

actual: <div id="t" title="a&quot;b"><template></template><p></p><br><span>a&nbsp;b</span></div>

Title: Node::outer_html drops comments and <template> contents

Body:

write_outer_html_in_style skips NodeData::Comment and never visits a template’s template_contents fragment, so serializing any subtree with comments or templates loses them. Frameworks that build DOM from innerHTML/<template> with comment markers (Svelte 5, Lit, Vue’s static hoisting) cannot round-trip through it. (#1204 fixes the <p /> and &nbsp; parts of the same function but not these.)

use blitz_dom::DocumentConfig;
use blitz_html::HtmlDocument;
fn main() {
let doc = HtmlDocument::from_html(
r#"<!DOCTYPE html><html><body><div id="t"><!--c--><template><b>x</b></template><p></p><br><span>a&nbsp;b</span></div></body></html>"#,
DocumentConfig::default(),
);
let id = doc.get_element_by_id("t").unwrap();
println!("{}", doc.get_node(id).unwrap().outer_html());
// expected: <div id="t"><!--c--><template><b>x</b></template><p></p><br><span>a&nbsp;b</span></div>
// actual: <div id="t"><template /><p /><br /><span>a\u{a0}b</span></div>
}

Tested at be60597f67b3cece0456d26e8d7ff5a88600bbc5 and with #1204 applied. Fix: write <!-- + contents + --> for comments, and for an HTML template element serialize the children of template_contents instead of children, per https://html.spec.whatwg.org/#serialising-html-fragments.