Skip to content

Commit ab1eb6e

Browse files
committed
Fix TSX playground mount detection
1 parent a648e8f commit ab1eb6e

File tree

1 file changed

+44
-17
lines changed

1 file changed

+44
-17
lines changed

tko.io/plugins/playground-button.js

Lines changed: 44 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,15 @@
2020

2121
const MASK_SVG = `url("data:image/svg+xml,%3Csvg%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%20viewBox%3D'0%200%2024%2024'%20fill%3D'none'%20stroke%3D'black'%20stroke-width%3D'1.75'%3E%3Cpath%20d%3D'M15%203h6v6'%2F%3E%3Cpath%20d%3D'M10%2014%2021%203'%2F%3E%3Cpath%20d%3D'M18%2013v6a2%202%200%200%201-2%202H5a2%202%200%200%201-2-2V8a2%202%200%200%201%202-2h6'%2F%3E%3C%2Fsvg%3E")`
2222
const DEFAULT_PLAYGROUND_HTML = '<div id="root"></div>'
23-
const GET_ELEMENT_BY_ID_RE = /document\.getElementById\(\s*(['"`])([^'"`]+)\1\s*\)/
23+
const DIRECT_MOUNT_PATTERNS = [
24+
/document\.getElementById\(\s*(['"`])([^'"`]+)\1\s*\)\s*\.\s*(?:appendChild|append|replaceChildren)\s*\(/,
25+
/(?:ko|tko)\.applyBindings\s*\([\s\S]*?,\s*document\.getElementById\(\s*(['"`])([^'"`]+)\1\s*\)\s*\)/,
26+
/tko\.jsx\.render\s*\([\s\S]*?,\s*document\.getElementById\(\s*(['"`])([^'"`]+)\1\s*\)\s*\)/,
27+
/(?:ReactDOM\.)?createRoot\s*\(\s*document\.getElementById\(\s*(['"`])([^'"`]+)\1\s*\)\s*\)\s*\.render\s*\(/,
28+
/ReactDOM\.render\s*\([\s\S]*?,\s*document\.getElementById\(\s*(['"`])([^'"`]+)\1\s*\)\s*\)/
29+
]
30+
const ELEMENT_REF_RE =
31+
/(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=\s*document\.getElementById\(\s*(['"`])([^'"`]+)\2\s*\)/g
2432

2533
function h(tag, props, children = []) {
2634
return {
@@ -79,23 +87,40 @@ function indentBlock(code, spaces) {
7987
.join('\n')
8088
}
8189

90+
function findExplicitMountId(code) {
91+
for (const pattern of DIRECT_MOUNT_PATTERNS) {
92+
const match = code.match(pattern)
93+
if (match) return match[2]
94+
}
95+
96+
for (const match of code.matchAll(ELEMENT_REF_RE)) {
97+
const [, refName, , id] = match
98+
const refPattern = new RegExp(
99+
String.raw`\b${refName}\b\s*\.\s*(?:appendChild|append|replaceChildren)\s*\(|` +
100+
String.raw`(?:^|\W)(?:ko|tko)\.applyBindings\s*\([\s\S]*?,\s*${refName}\b|` +
101+
String.raw`tko\.jsx\.render\s*\([\s\S]*?,\s*${refName}\b|` +
102+
String.raw`(?:ReactDOM\.)?createRoot\s*\(\s*${refName}\b\s*\)\s*\.render\s*\(|` +
103+
String.raw`ReactDOM\.render\s*\([\s\S]*?,\s*${refName}\b`,
104+
'm'
105+
)
106+
if (refPattern.test(code)) return id
107+
}
108+
109+
return null
110+
}
111+
82112
function inferPlaygroundHtml(tsx) {
83-
const mountId = tsx.match(GET_ELEMENT_BY_ID_RE)?.[2]
113+
const mountId = findExplicitMountId(tsx)
84114
return mountId ? `<div id="${mountId}"></div>` : DEFAULT_PLAYGROUND_HTML
85115
}
86116

87117
function wrapTsxForPlayground(tsx) {
88118
const code = tsx.trim()
89119
if (!code) return code
90120

91-
// Hand-authored full examples should keep their explicit setup.
92-
if (
93-
/tko\.jsx\.render\s*\(/.test(code) ||
94-
/(?:^|\W)(?:ko|tko)\.applyBindings\s*\(/.test(code) ||
95-
/document\.getElementById\s*\(/.test(code)
96-
) {
97-
return code
98-
}
121+
// Hand-authored full examples should keep their explicit setup only when
122+
// they already include a concrete mount target.
123+
if (findExplicitMountId(code)) return code
99124

100125
const blocks = code.split(/\n\s*\n/)
101126
const jsxIndex = blocks.findIndex(block => looksLikeJsxExpression(block.trim()))
@@ -108,13 +133,15 @@ function wrapTsxForPlayground(tsx) {
108133

109134
let wrapped = ''
110135
if (prelude) wrapped += `${prelude}\n\n`
111-
wrapped += '// boilerplate added by the docs playground\n'
112-
wrapped += "const root = document.getElementById('root')\n"
113-
wrapped += 'const { node } = tko.jsx.render(\n'
114-
wrapped += `${indentBlock(jsxBlock, 2)}\n`
115-
wrapped += ')\n'
116-
wrapped += 'root.appendChild(node)\n'
117-
wrapped += 'tko.applyBindings({}, root)'
136+
wrapped += '{\n'
137+
wrapped += ' // boilerplate added by the docs playground\n'
138+
wrapped += " const __docsPlaygroundRoot = document.getElementById('root')\n"
139+
wrapped += ' const __docsPlaygroundRendered = tko.jsx.render(\n'
140+
wrapped += `${indentBlock(jsxBlock, 4)}\n`
141+
wrapped += ' )\n'
142+
wrapped += ' __docsPlaygroundRoot.appendChild(__docsPlaygroundRendered.node)\n'
143+
wrapped += ' tko.applyBindings({}, __docsPlaygroundRoot)\n'
144+
wrapped += '}'
118145
return wrapped
119146
}
120147

0 commit comments

Comments
 (0)