feat(html): adoption-style hydration with mismatch reporting
hydrate(oldNode, newNode) normalizes the client tree to parser text-node granularity, walks both trees to report the first server/client markup disagreement (node, text, attribute, or child count, with a path), then morphs — matching nodes are adopted in place, the client render wins. choo.mount() now hydrates and console.warns on mismatch. Building this surfaced two real isomorphism divergences, both fixed: adjacent text nodes from template holes vs the parser's merged runs (folded via Node.normalize), and the server serializing onclick="" where the browser sets a property — event handlers now leave no trace in server markup at all. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014NgfSjHE11oFpoSnLVKLXd
This commit is contained in:
co-authored by
Claude Fable 5
parent
37bd8adf81
commit
13155a3469
@@ -11,6 +11,7 @@ const BOOL_PROPS = [
|
||||
]
|
||||
|
||||
const boolPropRx = new RegExp('([^-a-z](' + BOOL_PROPS.join('|') + '))=["\']?$', 'i')
|
||||
const handlerRx = /[^-a-z](on[a-z]+)=$/i
|
||||
const query = /(?:="|&)[^"]*=$/
|
||||
|
||||
export default function html (pieces, ...values) {
|
||||
@@ -19,6 +20,15 @@ export default function html (pieces, ...values) {
|
||||
for (let i = 0; i < pieces.length; i++) {
|
||||
const piece = pieces[i]
|
||||
if (i < pieces.length - 1) {
|
||||
// Event handlers are behavior, not markup: `onclick=${fn}` renders
|
||||
// nothing at all, matching the browser renderer (which sets the
|
||||
// handler as a property). v7 serialized a useless onclick="".
|
||||
const handlerMatch = handlerRx.exec(piece)
|
||||
if (handlerMatch && typeof values[i] === 'function') {
|
||||
output += piece.slice(0, handlerMatch.index + 1).replace(/\s+$/, ' ')
|
||||
continue
|
||||
}
|
||||
|
||||
if ((boolMatch = boolPropRx.exec(piece))) {
|
||||
output += piece.slice(0, boolMatch.index)
|
||||
if (values[i]) {
|
||||
@@ -59,6 +69,9 @@ function handleValue (value) {
|
||||
if (typeof value === 'object') {
|
||||
if (typeof value.outerHTML === 'string') return value.outerHTML
|
||||
return Object.keys(value).reduce(function (str, key) {
|
||||
// handlers in spread objects are behavior too — never serialized
|
||||
if (typeof value[key] === 'function') return str
|
||||
|
||||
if (str.length > 0) str += ' '
|
||||
|
||||
if (BOOL_PROPS.indexOf(key) !== -1) {
|
||||
|
||||
Reference in New Issue
Block a user