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.
- packages/compiler-core
- packages/compiler-dom
- packages/compiler-sfc
- packages/runtime-core
- packages/runtime-dom
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:
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.
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.
<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:
(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!