Skip to content

Directory Structure of core-vapor

About Repository Terminology

In the following explanation, topics that apply to both vuejs/core and vuejs/core-vapor will be referred to as the v3 repository. (For example, "In the v3 repository, ~~~")
By distinguishing what is specific to core-vapor and what comes from the original (vuejs/core), we can understand core-vapor by anticipating the differences.

Main Packages

The v3 repository is managed as a monorepo using pnpm workspace.
Each package is located in the /packages directory.
packages

These packages are broadly divided into two categories: compiler and runtime.
Packages starting with compiler- are related to the compiler, and those starting with runtime- are related to the runtime.


In core-vapor, new packages compiler-vapor and runtime-vapor have been added.


Next, an important package is reactivity.
Implementations like ref, computed, and watch are provided as @vue/reactivity, independently from the runtime packages.
This is located in /packages/reactivity.


The package that serves as the entry point for Vue.js is located in /packages/vue.
In core-vapor, in addition to this, a package called /packages/vue-vapor, which serves as the entry point for Vapor Mode, has been added.


Overview:

structure-overview

compiler-core

As the name suggests, compiler-core provides the core part of the compiler.
In addition to this, compiler packages include compiler-dom and compiler-sfc, among others.
The core provides implementations that are independent of specific use cases or environments like SFC or DOM.

There are various compilers in Vue.js.

For example, when using the template option, the template is compiled at runtime.

ts
createApp({
  template: `<div>{{ msg }}</div>`,
  setup() {
    const msg = ref("Hello, Vue!");
    return { msg };
  },
}).mount("#app");

However, as you can see, this template uses the same template syntax as in SFC.

vue
<script setup lang="ts">
import { ref } from "vue";

const msg = ref("Hello, Vue!");
</script>

<template>
  <div>{{ msg }}</div>
</template>

In addition, there are cases where content written as innerHTML in HTML is compiled.
Vue.js has various ways of compiling templates.
It is roughly correct to understand that compiler-core provides the common parts for these various use cases.

Specifically, it includes the core implementation that compiles template into a render function.

compiler-dom

In Vue.js, operations and code generation related to the DOM are considered environment-dependent, and are therefore separated from the core.
This will also appear later in the runtime section.

Regarding the compiler, it includes implementations that generate code related to DOM events and specific DOM elements.
You might find it easier to understand if you think of event modifiers in Vue.js.

For example, the modifier @submit.prevent requires code like:

ts
(e: Event) => e.preventDefault()

This is code generation that depends on the DOM API.
Providing such functionality is the role of compiler-dom.

Example:

29registerRuntimeHelpers({
30  [V_MODEL_RADIO]: `vModelRadio`,
31  [V_MODEL_CHECKBOX]: `vModelCheckbox`,
32  [V_MODEL_TEXT]: `vModelText`,
33  [V_MODEL_SELECT]: `vModelSelect`,
34  [V_MODEL_DYNAMIC]: `vModelDynamic`,
35  [V_ON_WITH_MODIFIERS]: `withModifiers`,
30const modifierGuards: Record<
31  ModifierGuards,
32  | ((e: Event) => void | boolean)
33  | ((e: Event, modifiers: string[]) => void | boolean)
34> = {
35  stop: (e: Event) => e.stopPropagation(),
36  prevent: (e: Event) => e.preventDefault(),

compiler-sfc

As the name suggests, this is the compiler related to SFC (Single File Components).
Specifically, it provides features like <script setup> and <style scoped>.

In many cases, this compiler functions by being called by plugins of tools like bundlers, which are in separate packages.
Famous examples include vite-plugin-vue used in Vite and vue-loader used in webpack.

25function tryResolveCompiler(root?: string) {
26  const vueMeta = tryRequire('vue/package.json', root)
27  // make sure to check the version is 3+ since 2.7 now also has vue/compiler-sfc
28  if (vueMeta && vueMeta.version.split('.')[0] >= 3) {
29    return tryRequire('vue/compiler-sfc', root)
30  }
31}
8import * as _compiler from 'vue/compiler-sfc'
9
10export let compiler: typeof _compiler
11
12try {
13  // Vue 3.2.13+ ships the SFC compiler directly under the `vue` package
14  // making it no longer necessary to have @vue/compiler-sfc separately installed.
15  compiler = require('vue/compiler-sfc')
16} catch (e) {
17  try {
18    compiler = require('@vue/compiler-sfc')
19  } catch (e) {
20    throw new Error(
21      `@vitejs/plugin-vue requires vue (>=3.2.13) or @vue/compiler-sfc ` +
22        `to be present in the dependency tree.`
23    )
24  }
25}

runtime-core

Provides the core part of the runtime.
Again, it does not depend on the DOM, and includes implementations of the component runtime, virtual DOM and its patching, and the scheduler.
Regarding the patching process (renderer), although it seems that DOM operations might be performed, runtime-core only calls interfaces defined without dependency on the DOM API.
The actual functions are implemented in runtime-dom and injected. (Utilizing the Dependency Inversion Principle.)

Interface:

108export interface RendererOptions<
109  HostNode = RendererNode,
110  HostElement = RendererElement,
111> {
112  patchProp(
113    el: HostElement,
114    key: string,
115    prevValue: any,
116    nextValue: any,
117    namespace?: ElementNamespace,
118    parentComponent?: ComponentInternalInstance | null,
119  ): void
120  insert(el: HostNode, parent: HostElement, anchor?: HostNode | null): void
121  remove(el: HostNode): void
122  createElement(
123    type: string,
124    namespace?: ElementNamespace,
125    isCustomizedBuiltIn?: string,
126    vnodeProps?: (VNodeProps & { [key: string]: any }) | null,
127  ): HostElement
128  createText(text: string): HostNode
129  createComment(text: string): HostNode
130  setText(node: HostNode, text: string): void
131  setElementText(node: HostElement, text: string): void
132  parentNode(node: HostNode): HostElement | null
133  nextSibling(node: HostNode): HostNode | null
134  querySelector?(selector: string): HostElement | null
135  setScopeId?(el: HostElement, id: string): void
136  cloneNode?(node: HostNode): HostNode
137  insertStaticContent?(
138    content: string,
139    parent: HostElement,
140    anchor: HostNode | null,
141    namespace: ElementNamespace,
142    start?: HostNode | null,
143    end?: HostNode | null,
144  ): [HostNode, HostNode]
145}

The function createRenderer accepts the actual operations as options (not directly called in runtime-core):

325export function createRenderer<
326  HostNode = RendererNode,
327  HostElement = RendererElement,
328>(options: RendererOptions<HostNode, HostElement>): Renderer<HostElement> {

runtime-dom

Includes the actual implementation of the DOM operations described above, and the implementation of injecting them into the core.

45export const nodeOps: Omit<RendererOptions<Node, Element>, 'patchProp'> = {
46  insert: (child, parent, anchor) => {
47    parent.insertBefore(child, anchor || null)
48  },
49
50  remove: child => {
51    const parent = child.parentNode
52    if (parent) {
53      parent.removeChild(child)
54    }
55  },
56
57  createElement: (tag, namespace, is, props): Element => {
58    const el =
59      namespace === 'svg'
60        ? doc.createElementNS(svgNS, tag)
61        : namespace === 'mathml'
62          ? doc.createElementNS(mathmlNS, tag)
63          : is
64            ? doc.createElement(tag, { is })
65            : doc.createElement(tag)
66
67    if (tag === 'select' && props && props.multiple != null) {
68      ;(el as HTMLSelectElement).setAttribute('multiple', props.multiple)
69    }
70
71    return el
72  },
73
74  createText: text => doc.createTextNode(text),
75
76  createComment: text => doc.createComment(text),
77
78  setText: (node, text) => {
79    node.nodeValue = text
80  },
81
82  setElementText: (el, text) => {
83    el.textContent = text
84  },
85
86  parentNode: node => node.parentNode as Element | null,
87
88  nextSibling: node => node.nextSibling,
89
90  querySelector: selector => doc.querySelector(selector),

It also includes implementations to actually handle DOM events, as mentioned in the compiler explanation.
(compiler-dom is the implementation for outputting code that calls these.)

18type ModifierGuards =
19  | 'shift'
20  | 'ctrl'
21  | 'alt'
22  | 'meta'
23  | 'left'
24  | 'right'
25  | 'stop'
26  | 'prevent'
27  | 'self'
28  | 'middle'
29  | 'exact'
30const modifierGuards: Record<
31  ModifierGuards,
32  | ((e: Event) => void | boolean)
33  | ((e: Event, modifiers: string[]) => void | boolean)
34> = {
35  stop: (e: Event) => e.stopPropagation(),
36  prevent: (e: Event) => e.preventDefault(),
37  self: (e: Event) => e.target !== e.currentTarget,
38  ctrl: (e: Event) => !(e as KeyedEvent).ctrlKey,
39  shift: (e: Event) => !(e as KeyedEvent).shiftKey,
40  alt: (e: Event) => !(e as KeyedEvent).altKey,
41  meta: (e: Event) => !(e as KeyedEvent).metaKey,
42  left: (e: Event) => 'button' in e && (e as MouseEvent).button !== 0,
43  middle: (e: Event) => 'button' in e && (e as MouseEvent).button !== 1,
44  right: (e: Event) => 'button' in e && (e as MouseEvent).button !== 2,
45  exact: (e, modifiers) =>
46    systemModifiers.some(m => (e as any)[`${m}Key`] && !modifiers.includes(m)),
47}

reactivity

As the name suggests, provides the reactivity system of Vue.js.
You may have heard somewhere that "Vue.js's reactivity system is available out of the box". This is because this package is implemented independently without depending on other packages.
Moreover, the fact that it is "independent" is an important point in the implementation of Vapor Mode.

That's for a good reason. To give a little spoiler, Vapor Mode updates the screen by leveraging the reactivity system without using the virtual DOM.
In fact, there are hardly any changes made to the reactivity package.
In other words, it can be used seamlessly as part of Vapor's functionality, as it does not depend much on Vue.js's runtime.

compiler-vapor, runtime-vapor

Now, finally, the main topic.
As the names suggest, these are the compiler and runtime implementations for Vapor Mode.

Vapor Mode is currently in the R&D phase, so it is implemented as independent packages to avoid modifying existing implementations in the upstream as much as possible.
Therefore, although there is significant overlap with the existing runtime and compiler, these parts are actually re-implemented even in this section.

What kind of implementations are done in these packages will be examined from here on (or rather, that's the main topic of this book), so we'll omit it here.


Now that we have a rough understanding of the overall package structure, let's start reading the source code necessary to understand the implementation of Vapor Mode!