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.
https://github.com/vuejs/core-vapor/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:

registerRuntimeHelpers({
  [V_MODEL_RADIO]: `vModelRadio`,
  [V_MODEL_CHECKBOX]: `vModelCheckbox`,
  [V_MODEL_TEXT]: `vModelText`,
  [V_MODEL_SELECT]: `vModelSelect`,
  [V_MODEL_DYNAMIC]: `vModelDynamic`,
  [V_ON_WITH_MODIFIERS]: `withModifiers`,

const modifierGuards: Record<
  ModifierGuards,
  | ((e: Event) => void | boolean)
  | ((e: Event, modifiers: string[]) => void | boolean)
> = {
  stop: (e: Event) => e.stopPropagation(),
  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.

function tryResolveCompiler(root?: string) {
  const vueMeta = tryRequire('vue/package.json', root)
  // make sure to check the version is 3+ since 2.7 now also has vue/compiler-sfc
  if (vueMeta && vueMeta.version.split('.')[0] >= 3) {
    return tryRequire('vue/compiler-sfc', root)
  }
}

import * as _compiler from 'vue/compiler-sfc'

export let compiler: typeof _compiler

try {
  // Vue 3.2.13+ ships the SFC compiler directly under the `vue` package
  // making it no longer necessary to have @vue/compiler-sfc separately installed.
  compiler = require('vue/compiler-sfc')
} catch (e) {
  try {
    compiler = require('@vue/compiler-sfc')
  } catch (e) {
    throw new Error(
      `@vitejs/plugin-vue requires vue (>=3.2.13) or @vue/compiler-sfc ` +
        `to be present in the dependency tree.`
    )
  }
}

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:

export interface RendererOptions<
  HostNode = RendererNode,
  HostElement = RendererElement,
> {
  patchProp(
    el: HostElement,
    key: string,
    prevValue: any,
    nextValue: any,
    namespace?: ElementNamespace,
    parentComponent?: ComponentInternalInstance | null,
  ): void
  insert(el: HostNode, parent: HostElement, anchor?: HostNode | null): void
  remove(el: HostNode): void
  createElement(
    type: string,
    namespace?: ElementNamespace,
    isCustomizedBuiltIn?: string,
    vnodeProps?: (VNodeProps & { [key: string]: any }) | null,
  ): HostElement
  createText(text: string): HostNode
  createComment(text: string): HostNode
  setText(node: HostNode, text: string): void
  setElementText(node: HostElement, text: string): void
  parentNode(node: HostNode): HostElement | null
  nextSibling(node: HostNode): HostNode | null
  querySelector?(selector: string): HostElement | null
  setScopeId?(el: HostElement, id: string): void
  cloneNode?(node: HostNode): HostNode
  insertStaticContent?(
    content: string,
    parent: HostElement,
    anchor: HostNode | null,
    namespace: ElementNamespace,
    start?: HostNode | null,
    end?: HostNode | null,
  ): [HostNode, HostNode]
}

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

export function createRenderer<
  HostNode = RendererNode,
  HostElement = RendererElement,
>(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.

export const nodeOps: Omit<RendererOptions<Node, Element>, 'patchProp'> = {
  insert: (child, parent, anchor) => {
    parent.insertBefore(child, anchor || null)
  },

  remove: child => {
    const parent = child.parentNode
    if (parent) {
      parent.removeChild(child)
    }
  },

  createElement: (tag, namespace, is, props): Element => {
    const el =
      namespace === 'svg'
        ? doc.createElementNS(svgNS, tag)
        : namespace === 'mathml'
          ? doc.createElementNS(mathmlNS, tag)
          : is
            ? doc.createElement(tag, { is })
            : doc.createElement(tag)

    if (tag === 'select' && props && props.multiple != null) {
      ;(el as HTMLSelectElement).setAttribute('multiple', props.multiple)
    }

    return el
  },

  createText: text => doc.createTextNode(text),

  createComment: text => doc.createComment(text),

  setText: (node, text) => {
    node.nodeValue = text
  },

  setElementText: (el, text) => {
    el.textContent = text
  },

  parentNode: node => node.parentNode as Element | null,

  nextSibling: node => node.nextSibling,

  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.)

type ModifierGuards =
  | 'shift'
  | 'ctrl'
  | 'alt'
  | 'meta'
  | 'left'
  | 'right'
  | 'stop'
  | 'prevent'
  | 'self'
  | 'middle'
  | 'exact'
const modifierGuards: Record<
  ModifierGuards,
  | ((e: Event) => void | boolean)
  | ((e: Event, modifiers: string[]) => void | boolean)
> = {
  stop: (e: Event) => e.stopPropagation(),
  prevent: (e: Event) => e.preventDefault(),
  self: (e: Event) => e.target !== e.currentTarget,
  ctrl: (e: Event) => !(e as KeyedEvent).ctrlKey,
  shift: (e: Event) => !(e as KeyedEvent).shiftKey,
  alt: (e: Event) => !(e as KeyedEvent).altKey,
  meta: (e: Event) => !(e as KeyedEvent).metaKey,
  left: (e: Event) => 'button' in e && (e as MouseEvent).button !== 0,
  middle: (e: Event) => 'button' in e && (e as MouseEvent).button !== 1,
  right: (e: Event) => 'button' in e && (e as MouseEvent).button !== 2,
  exact: (e, modifiers) =>
    systemModifiers.some(m => (e as any)[`${m}Key`] && !modifiers.includes(m)),
}

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!