mirror of https://github.com/vuejs/core.git
feat: v-html
This commit is contained in:
parent
567feccb39
commit
74b4328337
|
@ -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-for`
|
||||
- [ ] `v-once`
|
||||
- [ ] `v-html`
|
||||
- [x] `v-html`
|
||||
- [ ] `v-text`
|
||||
- [ ] `v-show`
|
||||
- [ ] `v-pre`
|
||||
|
|
|
@ -3,10 +3,11 @@
|
|||
exports[`basic 1`] = `
|
||||
"import { defineComponent as _defineComponent } from 'vue'
|
||||
import { watchEffect } from 'vue'
|
||||
import { template, insert, setText, on } from 'vue/vapor'
|
||||
const t0 = template(\`<h1 id=\\"title\\">Counter</h1><p>Count: </p><p>Double: </p><button>Increment</button>\`)
|
||||
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><div/>\`)
|
||||
import { ref, computed } from 'vue'
|
||||
|
||||
const html = '<b>HTML</b>'
|
||||
|
||||
export default /*#__PURE__*/_defineComponent({
|
||||
setup(__props) {
|
||||
|
@ -16,6 +17,7 @@ const double = computed(() => count.value * 2)
|
|||
|
||||
const increment = () => count.value++
|
||||
|
||||
|
||||
return (() => {
|
||||
const root = t0()
|
||||
const n1 = document.createTextNode(count.value)
|
||||
|
@ -31,6 +33,9 @@ setText(n3, undefined, double.value)
|
|||
watchEffect(() => {
|
||||
on(n4, \\"click\\", increment)
|
||||
})
|
||||
watchEffect(() => {
|
||||
setHtml(n5, undefined, html)
|
||||
})
|
||||
return root
|
||||
})();
|
||||
}
|
||||
|
|
|
@ -5,6 +5,8 @@ const count = ref(0)
|
|||
const double = computed(() => count.value * 2)
|
||||
|
||||
const increment = () => count.value++
|
||||
|
||||
const html = '<b>HTML</b>'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
@ -12,4 +14,5 @@ const increment = () => count.value++
|
|||
<p>Count: {{ count }}</p>
|
||||
<p>Double: {{ double }}</p>
|
||||
<button @click="increment">Increment</button>
|
||||
<div v-html="html" />
|
||||
</template>
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
import type { CodegenOptions, CodegenResult } from '@vue/compiler-dom'
|
||||
import { type DynamicChildren, type RootIRNode, IRNodeTypes } from './ir'
|
||||
|
||||
// remove when stable
|
||||
function checkNever(x: never): void {}
|
||||
|
||||
// IR -> JS codegen
|
||||
export function generate(
|
||||
ir: RootIRNode,
|
||||
|
@ -74,6 +77,13 @@ export function generate(
|
|||
vaporHelpers.add('on')
|
||||
break
|
||||
}
|
||||
case IRNodeTypes.SET_HTML: {
|
||||
scope += `setHtml(n${effect.element}, undefined, ${expr})\n`
|
||||
vaporHelpers.add('setHtml')
|
||||
break
|
||||
}
|
||||
default:
|
||||
checkNever(effect)
|
||||
}
|
||||
}
|
||||
scope += '})\n'
|
||||
|
|
|
@ -6,6 +6,7 @@ export const enum IRNodeTypes {
|
|||
SET_PROP,
|
||||
SET_TEXT,
|
||||
SET_EVENT,
|
||||
SET_HTML,
|
||||
|
||||
INSERT_NODE,
|
||||
TEXT_NODE,
|
||||
|
@ -48,6 +49,17 @@ export interface SetEventIRNode extends IRNode {
|
|||
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 {
|
||||
type: IRNodeTypes.TEXT_NODE
|
||||
id: number
|
||||
|
@ -61,7 +73,6 @@ export interface InsertNodeIRNode extends IRNode {
|
|||
anchor: number | 'first' | 'last'
|
||||
}
|
||||
|
||||
export type EffectNode = SetPropIRNode | SetTextIRNode | SetEventIRNode
|
||||
export type OprationNode = TextNodeIRNode | InsertNodeIRNode
|
||||
|
||||
export interface DynamicChild {
|
||||
|
|
|
@ -134,20 +134,26 @@ export function transform(
|
|||
vaporHelpers: new Set([]),
|
||||
}
|
||||
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
|
||||
|
||||
return ir
|
||||
}
|
||||
|
||||
function transformChildren(ctx: TransformContext<RootNode | ElementNode>) {
|
||||
function transformChildren(
|
||||
ctx: TransformContext<RootNode | ElementNode>,
|
||||
root?: boolean,
|
||||
) {
|
||||
const {
|
||||
node: { children },
|
||||
} = ctx
|
||||
let index = 0
|
||||
children.forEach((child, i) => walkNode(child, i))
|
||||
|
||||
if (root) ctx.registerTemplate()
|
||||
|
||||
function walkNode(node: TemplateChildNode, i: number) {
|
||||
const child = createContext(node, ctx, index)
|
||||
const isFirst = i === 0
|
||||
|
@ -298,30 +304,56 @@ function transformProp(
|
|||
} else if (node.exp.type === (8 satisfies NodeTypes.COMPOUND_EXPRESSION)) {
|
||||
// TODO: CompoundExpressionNode: :foo="count + 1"
|
||||
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="{}"
|
||||
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"
|
||||
return
|
||||
}
|
||||
|
||||
const expr = processExpression(ctx, node.exp.content)
|
||||
ctx.store = true
|
||||
if (name === 'bind') {
|
||||
ctx.registerEffect(expr, {
|
||||
type: IRNodeTypes.SET_PROP,
|
||||
loc: node.loc,
|
||||
element: ctx.getElementId(),
|
||||
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, {
|
||||
type: IRNodeTypes.SET_EVENT,
|
||||
loc: node.loc,
|
||||
element: ctx.getElementId(),
|
||||
name: node.arg.content,
|
||||
})
|
||||
break
|
||||
}
|
||||
case 'html':
|
||||
ctx.registerEffect(expr, {
|
||||
type: IRNodeTypes.SET_HTML,
|
||||
loc: node.loc,
|
||||
element: ctx.getElementId(),
|
||||
})
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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) {
|
||||
if ((newVal = normalizeClass(newVal)) !== oldVal && (newVal || oldVal)) {
|
||||
el.className = newVal
|
||||
|
|
|
@ -3,6 +3,7 @@ import { ref, computed } from 'vue'
|
|||
|
||||
const count = ref(0)
|
||||
const double = computed(() => count.value * 2)
|
||||
const html = computed(() => `<button>HTML! ${count.value}</button>`)
|
||||
|
||||
const inc = () => count.value++
|
||||
const dec = () => count.value--
|
||||
|
@ -15,6 +16,8 @@ globalThis.double = double
|
|||
globalThis.inc = inc
|
||||
// @ts-expect-error
|
||||
globalThis.dec = dec
|
||||
// @ts-expect-error
|
||||
globalThis.html = html
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
@ -26,6 +29,7 @@ globalThis.dec = dec
|
|||
<button @click="inc">inc</button>
|
||||
<button @click="dec">dec</button>
|
||||
</div>
|
||||
<div v-html="html" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
|
Loading…
Reference in New Issue