v-html ディレクティブ
以下のようなコンポーネントを考えます.
vue
<script setup>
import { ref } from "vue";
const inner = ref("<p>Hello, v-html</p>");
</script>
<template>
<div v-html="inner" />
</template>
コンパイル結果と概要
コンパイル結果は以下のようになります.
js
const _sfc_main = {
vapor: true,
__name: "App",
setup(__props, { expose: __expose }) {
__expose();
const inner = ref("<p>Hello, v-html</p>");
const __returned__ = { inner, ref };
Object.defineProperty(__returned__, "__isScriptSetup", {
enumerable: false,
value: true,
});
return __returned__;
},
};
import {
renderEffect as _renderEffect,
setHtml as _setHtml,
template as _template,
} from "vue/vapor";
const t0 = _template("<div></div>");
function _sfc_render(_ctx) {
const n0 = t0();
_renderEffect(() => _setHtml(n0, _ctx.inner));
return n0;
}
setHtml
という新しいヘルパーが登場しました.
それ以外は特に変わりません.
コンパイラを読む
transformElement
-> buildProps
-> transformProps
-> directiveTransform
-> transformVHtml
と辿っていきます.
非常にシンプルなので全文載せてしまいます.
1import { IRNodeTypes } from '../ir'
2import type { DirectiveTransform } from '../transform'
3import { DOMErrorCodes, createDOMCompilerError } from '@vue/compiler-dom'
4import { EMPTY_EXPRESSION } from './utils'
5
6export const transformVHtml: DirectiveTransform = (dir, node, context) => {
7 let { exp, loc } = dir
8 if (!exp) {
9 context.options.onError(
10 createDOMCompilerError(DOMErrorCodes.X_V_HTML_NO_EXPRESSION, loc),
11 )
12 exp = EMPTY_EXPRESSION
13 }
14 if (node.children.length) {
15 context.options.onError(
16 createDOMCompilerError(DOMErrorCodes.X_V_HTML_WITH_CHILDREN, loc),
17 )
18 context.childrenTemplate.length = 0
19 }
20
21 context.registerEffect([exp], {
22 type: IRNodeTypes.SET_HTML,
23 element: context.reference(),
24 value: exp,
25 })
26}
SET_HTML
でエフェクトを登録しているだけです.
Codegen をみてみましょう.
33export function genOperation(
34 oper: OperationNode,
35 context: CodegenContext,
36): CodeFragment[] {
↓
48 case IRNodeTypes.SET_HTML:
49 return genSetHtml(oper, context)
↓
6export function genSetHtml(
7 oper: SetHtmlIRNode,
8 context: CodegenContext,
9): CodeFragment[] {
10 const { vaporHelper } = context
11 return [
12 NEWLINE,
13 ...genCall(
14 vaporHelper('setHtml'),
15 `n${oper.element}`,
16 genExpression(oper.value, context),
17 ),
18 ]
19}
お馴染みです.
ランタイムを読む
setHtml
だけ読んでみましょう.
196export function setHtml(el: Element, value: any): void {
197 const oldVal = recordPropMetadata(el, 'innerHTML', value)
198 if (value !== oldVal) {
199 el.innerHTML = value
200 }
201}
非常にシンプルで innerHTML
にセットしているだけです.
一応,余計なセットが起こらないように.メタ情報から古い値を取り出して差分を見たりはしているようです.