Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="icon" type="image/svg+xml" href="data:" />
<link rel="stylesheet" href="reset.css" />
<meta http-equiv="Content-Security-Policy" content="require-trusted-types-for 'script'">
<title>dhtml</title>
<script type="module">
import { html } from './dist/index.js'
Expand Down
44 changes: 29 additions & 15 deletions src/client/compiler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,28 +23,42 @@ const DYNAMIC_GLOBAL = /dyn-\$(\d+)\$/g
const FORCE_ATTRIBUTES = /-|^class$|^for$/i
const NEEDS_UPPERCASING = /\$./g

function createPolicy<T extends { createHTML(input: string, ...args: unknown[]): string }>(_name: string, rules: T): T {
return rules
}

declare var trustedTypes: { createPolicy: typeof createPolicy }

const policy = (typeof trustedTypes === 'undefined' ? createPolicy : trustedTypes.createPolicy.bind(trustedTypes))(
'dhtml',
{
createHTML: (_text, statics: TemplateStringsArray) => {
let i = 0
return [...lexer.lex(statics)]
.map(([char, state]) => {
if (char === '\0') {
if (state === lexer.DATA) return `<!--dyn-$${i++}$-->`
else return `dyn-$${i++}$`
}
if (state === lexer.ATTR_NAME && char.toLowerCase() !== char) {
return `$${char}`
}
return char
})
.join('')
},
},
)

const templates: WeakMap<TemplateStringsArray, CompiledTemplate> = new WeakMap()
export function compile_template(statics: TemplateStringsArray): CompiledTemplate {
const cached = templates.get(statics)
if (cached) return cached

const template_element = document.createElement('template')
let next_part = 0
template_element.innerHTML = [...lexer.lex(statics)]
.map(([char, state]) => {
if (char === '\0') {
if (state === lexer.DATA) return `<!--dyn-$${next_part++}$-->`
else return `dyn-$${next_part++}$`
}
if (state === lexer.ATTR_NAME && char.toLowerCase() !== char) {
return `$${char}`
}
return char
})
.join('')

next_part = 0
template_element.innerHTML = policy.createHTML('', statics)

let next_part = 0
const compiled: CompiledTemplate = {
_content: template_element.content,
_parts: Array(statics.length - 1),
Expand Down