Skip to content

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 にセットしているだけです.
一応,余計なセットが起こらないように.メタ情報から古い値を取り出して差分を見たりはしているようです.