feat: v-html

This commit is contained in:
三咲智子 Kevin Deng 2023-11-24 14:44:57 +08:00
parent 567feccb39
commit 74b4328337
No known key found for this signature in database
GPG Key ID: 69992F2250DFD93E
8 changed files with 99 additions and 28 deletions

View File

@ -19,7 +19,7 @@ See the To-do list below or `// TODO` comments in code (`compiler-vapor` and `ru
- [ ] `v-if` / `v-else` / `v-else-if` - [ ] `v-if` / `v-else` / `v-else-if`
- [ ] `v-for` - [ ] `v-for`
- [ ] `v-once` - [ ] `v-once`
- [ ] `v-html` - [x] `v-html`
- [ ] `v-text` - [ ] `v-text`
- [ ] `v-show` - [ ] `v-show`
- [ ] `v-pre` - [ ] `v-pre`

View File

@ -3,10 +3,11 @@
exports[`basic 1`] = ` exports[`basic 1`] = `
"import { defineComponent as _defineComponent } from 'vue' "import { defineComponent as _defineComponent } from 'vue'
import { watchEffect } from 'vue' import { watchEffect } from 'vue'
import { template, insert, setText, on } from 'vue/vapor' import { template, insert, setText, on, setHtml } from 'vue/vapor'
const t0 = template(\`<h1 id=\\"title\\">Counter</h1><p>Count: </p><p>Double: </p><button>Increment</button>\`) const t0 = template(\`<h1 id=\\"title\\">Counter</h1><p>Count: </p><p>Double: </p><button>Increment</button><div/>\`)
import { ref, computed } from 'vue' import { ref, computed } from 'vue'
const html = '<b>HTML</b>'
export default /*#__PURE__*/_defineComponent({ export default /*#__PURE__*/_defineComponent({
setup(__props) { setup(__props) {
@ -16,6 +17,7 @@ const double = computed(() => count.value * 2)
const increment = () => count.value++ const increment = () => count.value++
return (() => { return (() => {
const root = t0() const root = t0()
const n1 = document.createTextNode(count.value) const n1 = document.createTextNode(count.value)
@ -31,6 +33,9 @@ setText(n3, undefined, double.value)
watchEffect(() => { watchEffect(() => {
on(n4, \\"click\\", increment) on(n4, \\"click\\", increment)
}) })
watchEffect(() => {
setHtml(n5, undefined, html)
})
return root return root
})(); })();
} }

View File

@ -5,6 +5,8 @@ const count = ref(0)
const double = computed(() => count.value * 2) const double = computed(() => count.value * 2)
const increment = () => count.value++ const increment = () => count.value++
const html = '<b>HTML</b>'
</script> </script>
<template> <template>
@ -12,4 +14,5 @@ const increment = () => count.value++
<p>Count: {{ count }}</p> <p>Count: {{ count }}</p>
<p>Double: {{ double }}</p> <p>Double: {{ double }}</p>
<button @click="increment">Increment</button> <button @click="increment">Increment</button>
<div v-html="html" />
</template> </template>

View File

@ -1,6 +1,9 @@
import type { CodegenOptions, CodegenResult } from '@vue/compiler-dom' import type { CodegenOptions, CodegenResult } from '@vue/compiler-dom'
import { type DynamicChildren, type RootIRNode, IRNodeTypes } from './ir' import { type DynamicChildren, type RootIRNode, IRNodeTypes } from './ir'
// remove when stable
function checkNever(x: never): void {}
// IR -> JS codegen // IR -> JS codegen
export function generate( export function generate(
ir: RootIRNode, ir: RootIRNode,
@ -74,6 +77,13 @@ export function generate(
vaporHelpers.add('on') vaporHelpers.add('on')
break break
} }
case IRNodeTypes.SET_HTML: {
scope += `setHtml(n${effect.element}, undefined, ${expr})\n`
vaporHelpers.add('setHtml')
break
}
default:
checkNever(effect)
} }
} }
scope += '})\n' scope += '})\n'

View File

@ -6,6 +6,7 @@ export const enum IRNodeTypes {
SET_PROP, SET_PROP,
SET_TEXT, SET_TEXT,
SET_EVENT, SET_EVENT,
SET_HTML,
INSERT_NODE, INSERT_NODE,
TEXT_NODE, TEXT_NODE,
@ -48,6 +49,17 @@ export interface SetEventIRNode extends IRNode {
name: string name: string
} }
export interface SetHtmlIRNode extends IRNode {
type: IRNodeTypes.SET_HTML
element: number
}
export type EffectNode =
| SetPropIRNode
| SetTextIRNode
| SetEventIRNode
| SetHtmlIRNode
export interface TextNodeIRNode extends IRNode { export interface TextNodeIRNode extends IRNode {
type: IRNodeTypes.TEXT_NODE type: IRNodeTypes.TEXT_NODE
id: number id: number
@ -61,7 +73,6 @@ export interface InsertNodeIRNode extends IRNode {
anchor: number | 'first' | 'last' anchor: number | 'first' | 'last'
} }
export type EffectNode = SetPropIRNode | SetTextIRNode | SetEventIRNode
export type OprationNode = TextNodeIRNode | InsertNodeIRNode export type OprationNode = TextNodeIRNode | InsertNodeIRNode
export interface DynamicChild { export interface DynamicChild {

View File

@ -134,20 +134,26 @@ export function transform(
vaporHelpers: new Set([]), vaporHelpers: new Set([]),
} }
const ctx = createRootContext(ir, root, options) const ctx = createRootContext(ir, root, options)
transformChildren(ctx)
ctx.registerTemplate() // TODO: transform presets, see packages/compiler-core/src/transforms
transformChildren(ctx, true)
ir.children = ctx.children ir.children = ctx.children
return ir return ir
} }
function transformChildren(ctx: TransformContext<RootNode | ElementNode>) { function transformChildren(
ctx: TransformContext<RootNode | ElementNode>,
root?: boolean,
) {
const { const {
node: { children }, node: { children },
} = ctx } = ctx
let index = 0 let index = 0
children.forEach((child, i) => walkNode(child, i)) children.forEach((child, i) => walkNode(child, i))
if (root) ctx.registerTemplate()
function walkNode(node: TemplateChildNode, i: number) { function walkNode(node: TemplateChildNode, i: number) {
const child = createContext(node, ctx, index) const child = createContext(node, ctx, index)
const isFirst = i === 0 const isFirst = i === 0
@ -298,30 +304,56 @@ function transformProp(
} else if (node.exp.type === (8 satisfies NodeTypes.COMPOUND_EXPRESSION)) { } else if (node.exp.type === (8 satisfies NodeTypes.COMPOUND_EXPRESSION)) {
// TODO: CompoundExpressionNode: :foo="count + 1" // TODO: CompoundExpressionNode: :foo="count + 1"
return return
} else if (!node.arg) { }
ctx.store = true
const expr = processExpression(ctx, node.exp.content)
switch (name) {
case 'bind': {
if (!node.arg) {
// TODO support v-bind="{}" // TODO support v-bind="{}"
return return
} else if (node.arg.type === (8 satisfies NodeTypes.COMPOUND_EXPRESSION)) { } else if (
node.arg.type === (8 satisfies NodeTypes.COMPOUND_EXPRESSION)
) {
// TODO support :[foo]="bar" // TODO support :[foo]="bar"
return return
} }
const expr = processExpression(ctx, node.exp.content)
ctx.store = true
if (name === 'bind') {
ctx.registerEffect(expr, { ctx.registerEffect(expr, {
type: IRNodeTypes.SET_PROP, type: IRNodeTypes.SET_PROP,
loc: node.loc, loc: node.loc,
element: ctx.getElementId(), element: ctx.getElementId(),
name: node.arg.content, name: node.arg.content,
}) })
} else if (name === 'on') { break
}
case 'on': {
if (!node.arg) {
// TODO support v-on="{}"
return
} else if (
node.arg.type === (8 satisfies NodeTypes.COMPOUND_EXPRESSION)
) {
// TODO support @[foo]="bar"
return
}
ctx.registerEffect(expr, { ctx.registerEffect(expr, {
type: IRNodeTypes.SET_EVENT, type: IRNodeTypes.SET_EVENT,
loc: node.loc, loc: node.loc,
element: ctx.getElementId(), element: ctx.getElementId(),
name: node.arg.content, name: node.arg.content,
}) })
break
}
case 'html':
ctx.registerEffect(expr, {
type: IRNodeTypes.SET_HTML,
loc: node.loc,
element: ctx.getElementId(),
})
break
} }
} }

View File

@ -74,6 +74,12 @@ export function setText(el: Element, oldVal: any, newVal: any) {
} }
} }
export function setHtml(el: Element, oldVal: any, newVal: any) {
if (newVal !== oldVal) {
el.innerHTML = newVal
}
}
export function setClass(el: Element, oldVal: any, newVal: any) { export function setClass(el: Element, oldVal: any, newVal: any) {
if ((newVal = normalizeClass(newVal)) !== oldVal && (newVal || oldVal)) { if ((newVal = normalizeClass(newVal)) !== oldVal && (newVal || oldVal)) {
el.className = newVal el.className = newVal

View File

@ -3,6 +3,7 @@ import { ref, computed } from 'vue'
const count = ref(0) const count = ref(0)
const double = computed(() => count.value * 2) const double = computed(() => count.value * 2)
const html = computed(() => `<button>HTML! ${count.value}</button>`)
const inc = () => count.value++ const inc = () => count.value++
const dec = () => count.value-- const dec = () => count.value--
@ -15,6 +16,8 @@ globalThis.double = double
globalThis.inc = inc globalThis.inc = inc
// @ts-expect-error // @ts-expect-error
globalThis.dec = dec globalThis.dec = dec
// @ts-expect-error
globalThis.html = html
</script> </script>
<template> <template>
@ -26,6 +29,7 @@ globalThis.dec = dec
<button @click="inc">inc</button> <button @click="inc">inc</button>
<button @click="dec">dec</button> <button @click="dec">dec</button>
</div> </div>
<div v-html="html" />
</div> </div>
</template> </template>