Skip to content

The v-html Directive

Consider a component like the following.

vue
<script setup>
import { ref } from "vue";
const inner = ref("<p>Hello, v-html</p>");
</script>

<template>
  <div v-html="inner" />
</template>

Compilation Result and Overview

The compilation result is as follows.

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;
}

A new helper called setHtml has appeared.
Its implementation does not change in particular.

Reading the Compiler

We will follow the path transformElement -> buildProps -> transformProps -> directiveTransform -> transformVHtml.

It's very simple, so I'll include the entire text.

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}

It simply registers an effect with SET_HTML.
It performs processing similar to when transformText is called.

Let's look at 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}

It's familiar.

Reading the Runtime

Let's just read 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}

It's very simple and just sets innerHTML.
For the time being, it seems to extract the old value from the meta information to prevent unnecessary sets.