Skip to content

Commit

Permalink
fix(mustache): do not render mustaches ahead of child processing
Browse files Browse the repository at this point in the history
  • Loading branch information
lowlighter committed Oct 28, 2024
1 parent 56a0043 commit c8ea451
Show file tree
Hide file tree
Showing 3 changed files with 57 additions and 23 deletions.
2 changes: 1 addition & 1 deletion @mizu/mustache/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,5 @@ Enable content interpolation within « mustaches » ( `{{` and `}}`) from [`Text
> [!NOTE]
> HTML content is automatically escaped.
> [!WARNING]
> [!NOTE]
> There is currently no distinction between double mustaches ( `{{` and `}}`) and triple mustaches ( `{{{` and `}}}`), but future versions may introduce specific behavior for these.
53 changes: 31 additions & 22 deletions @mizu/mustache/mod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,33 +10,42 @@ export const _mustache = {
init(renderer) {
renderer.cache<Cache<typeof _mustache>>(this.name, new WeakMap())
},
setup(renderer, _, { state }) {
if (state[renderer.internal("mustaching")]) {
return { execute: true }
}
},
async execute(renderer, element, { cache, ...options }) {
if (!renderer.isHtmlElement(element)) {
return
}
const filter = renderer.window.NodeFilter.SHOW_TEXT
const walker = renderer.document.createTreeWalker(element, filter, { acceptNode: () => renderer.window.NodeFilter.FILTER_ACCEPT })
while (walker.nextNode()) {
const node = walker.currentNode as Text
if (!cache.has(node)) {
cache.set(node, node.textContent!)
}
try {
const template = cache.get(node)!
let templated = template
let offset = 0
let captured = null as ReturnType<typeof capture>
// deno-lint-ignore no-cond-assign
while (captured = capture(template, offset)) {
const { b, match, captured: expression } = captured
templated = templated.replace(match, `${await renderer.evaluate(element, expression, options).catch((error) => (renderer.warn(error, element), null)) ?? ""}`)
offset = b
await Promise.allSettled(
Array.from(element.childNodes).map(async (node) => {
if (node.nodeType !== renderer.window.Node.TEXT_NODE) {
return
}
node.textContent = templated
} catch (error) {
renderer.warn(`${error}`.split("\n")[0], element)
}
}
const text = node as Text
if (!cache.has(text)) {
cache.set(text, text.textContent!)
}
try {
const template = cache.get(text)!
let templated = template
let offset = 0
let captured = null as ReturnType<typeof capture>
// deno-lint-ignore no-cond-assign
while (captured = capture(template, offset)) {
const { b, match, captured: expression } = captured
templated = templated.replace(match, `${await renderer.evaluate(element, expression, options).catch((error) => (renderer.warn(error, element), null)) ?? ""}`)
offset = b
}
text.textContent = templated
} catch (error) {
renderer.warn(`${error}`.split("\n")[0], element)
}
}),
)
return { state: { [renderer.internal("mustaching")]: true } }
},
} as Directive<WeakMap<Text, string>> & { default: NonNullable<Directive["default"]> }

Expand Down
25 changes: 25 additions & 0 deletions @mizu/mustache/mod_test.html
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,31 @@
</expect>
</test>

<test name="[*mustache] templates children delimiters">
<script>
context.foo = "bar"
</script>
<render>
<p *mustache>
<span>foo {{ foo }} foo</span>
</p>
</render>
<expect>
<p>
<span>foo bar foo</span>
</p>
</expect>
<script>
context.foo = "baz"
</script>
<render></render>
<expect>
<p>
<span>foo baz foo</span>
</p>
</expect>
</test>

<test name="[*mustache] defaults to empty string on nullish values">
<render>
<p *mustache>
Expand Down

0 comments on commit c8ea451

Please sign in to comment.