Skip to content

The v-bind Directive

Now, let's keep reading and progressing.

Consider a component like the following.

vue
<script setup>
import { ref } from "vue";
const dynamicData = ref("a");
</script>

<template>
  <div :data-dynamic="dynamicData">Hello, World!</div>
</template>

Compilation Result and Overview

The compilation result is as follows. (We're getting accustomed to this, so the explanations are becoming a bit rough (lol)).

js
const _sfc_main = {
  vapor: true,
  __name: "App",
  setup(__props, { expose: __expose }) {
    __expose();

    const dynamicData = ref("a");

    const __returned__ = { dynamicData, ref };
    Object.defineProperty(__returned__, "__isScriptSetup", {
      enumerable: false,
      value: true,
    });
    return __returned__;
  },
};

import {
  renderEffect as _renderEffect,
  setDynamicProp as _setDynamicProp,
  template as _template,
} from "vue/vapor";

const t0 = _template("<div>Hello, World!</div>");

function _sfc_render(_ctx) {
  const n0 = t0();
  _renderEffect(() => _setDynamicProp(n0, "data-dynamic", _ctx.dynamicData));
  return n0;
}

In particular,

js
function _sfc_render(_ctx) {
  const n0 = t0();
  _renderEffect(() => _setDynamicProp(n0, "data-dynamic", _ctx.dynamicData));
  return n0;
}

is the _setDynamicProp part.
As expected, you can now predict the implementation method.
In summary, it's an effect that sets the _ctx.dynamicData to the data-dynamic attribute of n0, which is immediately understandable.

Reading the Compiler

Familiar route: transformElement -> buildProps -> transformProps -> directiveTransform -> transformVBind.

packages/compiler-vapor/src/transforms/vBind.ts

...Or is it?
Actually, this only handles shorthand and truly transforms v-bind, without registering effects and such.

In fact, regarding this, it is directly implemented in transformElement's buildProps.
The implementation is around here.

236            context.registerEffect(
237              [prop.exp],
238
239              {
240                type: IRNodeTypes.SET_DYNAMIC_EVENTS,
241                element: context.reference(),
242                event: prop.exp,
243              },
244            )

A bit above, there is also handling for when v-bind does not have an arg (e.g., v-bind="obj").

208    if (prop.type === NodeTypes.DIRECTIVE && !prop.arg) {
209      if (prop.name === 'bind') {
210        // v-bind="obj"
211        if (prop.exp) {
212          dynamicExpr.push(prop.exp)
213          pushMergeArg()
214          dynamicArgs.push({
215            kind: IRDynamicPropsKind.EXPRESSION,
216            value: prop.exp,
217          })
218        } else {

Anyway, since we were able to see where SET_DYNAMIC_EVENTS is registered, it's okay.

Let's also read the Codegen as it is.

33export function genOperation(
34  oper: OperationNode,
35  context: CodegenContext,
36): CodeFragment[] {
40    case IRNodeTypes.SET_DYNAMIC_PROPS:
41      return genDynamicProps(oper, context)
63export function genDynamicProps(
64  oper: SetDynamicPropsIRNode,
65  context: CodegenContext,
66): CodeFragment[] {
67  const { vaporHelper } = context
68  return [
69    NEWLINE,
70    ...genCall(
71      vaporHelper('setDynamicProps'),
72      `n${oper.element}`,
73      ...oper.props.map(
74        props =>
75          Array.isArray(props)
76            ? genLiteralObjectProps(props, context) // static and dynamic arg props
77            : props.kind === IRDynamicPropsKind.ATTRIBUTE
78              ? genLiteralObjectProps([props], context) // dynamic arg props
79              : genExpression(props.value, context), // v-bind=""
80      ),
81    ),
82  ]
83}

There shouldn't have been any particularly difficult parts.

Reading the Runtime

There's almost nothing to read here as well.

When the key is "class" or "style", it just does a bit of formatting.

112export function setDynamicProp(el: Element, key: string, value: any): void {
113  // TODO
114  const isSVG = false
115  if (key === 'class') {
116    setClass(el, value)
117  } else if (key === 'style') {
118    setStyle(el as HTMLElement, value)
119  } else if (isOn(key)) {
120    on(el, key[2].toLowerCase() + key.slice(3), () => value, { effect: true })
121  } else if (
122    key[0] === '.'
123      ? ((key = key.slice(1)), true)
124      : key[0] === '^'
125        ? ((key = key.slice(1)), false)
126        : shouldSetAsProp(el, key, value, isSVG)
127  ) {
128    setDOMProp(el, key, value)
129  } else {
130    // TODO special case for <input v-model type="checkbox">
131    setAttr(el, key, value)
132  }
133}
22export function setClass(el: Element, value: any): void {
23  const prev = recordPropMetadata(el, 'class', (value = normalizeClass(value)))
24  if (value !== prev && (value || prev)) {
25    el.className = value
26  }
27}
12export function setStyle(el: HTMLElement, value: any): void {
13  const prev = recordPropMetadata(el, 'style', (value = normalizeStyle(value)))
14  patchStyle(el, prev, value)
15}