Skip to the solution
HardFrontend13 min read

How does the Vue compiler optimize templates (static hoisting, patch flags, block tree)?

411 views

The problem

Question presented to candidate: "People say Vue's virtual DOM is faster than a naive one because Vue has a compiler. What exactly does the template compiler do to make updates cheap? Talk me through static hoisting, patch flags and the block tree."

What a strong answer should cover:

  • Templates are compiled ahead of time into render functions, so the compiler knows which parts can never change.
  • Static hoisting / caching: static VNodes are created once and reused; in Vue 3.5 they are cached per instance in _cache (flag -1 /* CACHED */), and static props objects are hoisted to module scope.
  • Patch flags: each dynamic node carries a bitmask (1 TEXT, 2 CLASS, 8 PROPS plus the prop names, 16 FULL_PROPS, 128 KEYED_FRAGMENT, ...) so the renderer compares only what can change.
  • Block tree: openBlock() / createElementBlock() collect dynamic descendants into a flat dynamicChildren array; updates walk that array instead of the whole tree. Structural directives (v-if, v-for) open new blocks.
  • Static stringification: long runs of static elements become one createStaticVNode HTML string.
  • Hand-written render functions get none of this.

Clarifying questions expected:

  • "Do you want the concepts, or the actual compiled output?"

Code / implementation expected: Optional β€” compiled output for a small template, annotated.

  • vue
  • compiler
  • internals
  • performance

Try it yourself

Sketch your approach before you read the solution. That is what the interview tests.

Stuck? Get a hint.

A short nudge toward the approach. It will not give the answer away.

Solution

The solution is hidden so you can try first

13 min read Β· 11 sections

Read the code

VueInspect the block tree: which nodes Vue tracks as dynamic
<script setup>
import { ref, getCurrentInstance, onMounted } from "vue";

const message = ref("hello");
const cls = ref("a");
const show = ref(true);

// Peek at the compiled block tree. Dev-only introspection, for learning.
const instance = getCurrentInstance();
const report = ref("");
const describe = () => {
  const block = instance.subTree; // the root <div> block
  const all = block.el.querySelectorAll("*").length;
  const dyn = block.dynamicChildren.map((v) => `${v.type}(flag ${v.patchFlag})`);
  report.value = `${all} elements under the root, ${dyn.length} tracked as dynamic: ${dyn.join(", ")}`;
};
onMounted(describe);
</script>

<template>
  <div>
    <h3>Static heading</h3>
    <p>Static paragraph one.</p>
    <p>Static paragraph two.</p>
    <p>{{ message }}</p>
    <span :class="cls">class binding</span>
    <section v-if="show"><em>inside a v-if</em></section>
    <button @click="message += '!'">change text</button>
    <pre>{{ report }}</pre>
  </div>
</template>
Print the compiled render function for any template
import { compileTemplate } from "@vue/compiler-sfc";
import { readFileSync } from "fs";
const src = readFileSync(process.argv[2], "utf8");
const { code } = compileTemplate({ source: src, filename: "Demo.vue", id: "demo", isProd: true,
  compilerOptions: { hoistStatic: true, cacheHandlers: true } });
console.log(code);

Discussion

Other approaches, follow-ups and how it went in real interviews.

Sign in to join the discussion.

No comments yet. Share how you would answer it.

Question 85 of 92 in Vue.js

Keep your momentum going.

Mark this question solved to track your progress, then take the next one while it is fresh.

All Vue.js questions
Up nextHard

When should you use shallowRef / shallowReactive / triggerRef?

Continue