阿里云主机折上折
  • 微信号
Current Site:Index > The separation design between compilation and runtime

The separation design between compilation and runtime

Author:Chuan Chen 阅读数:36593人阅读 分类: Vue.js

Separation of Compile-Time and Runtime Design

In Vue 3's architectural design, the separation of compile-time and runtime is a core concept. This separation makes the framework more flexible and adaptable to different development scenarios. The compilation phase is responsible for converting templates into render functions, while the runtime focuses on executing these render functions and handling reactive data. This decoupling provides Vue 3 with more possibilities for performance optimization and feature expansion.

Compile-Time Responsibilities

The primary task of compile-time is to transform template strings into executable JavaScript code. Vue 3's compiler analyzes the template structure and generates optimized render functions. This process includes:

  1. Parsing the template into an AST (Abstract Syntax Tree)
  2. Performing static analysis on the AST
  3. Generating render function code
// Example template
const template = `
  <div>
    <span>{{ message }}</span>
    <button @click="handleClick">Click</button>
  </div>
`;

// The compiled render function roughly looks like this
function render(_ctx, _cache) {
  return (_openBlock(), _createBlock("div", null, [
    _createVNode("span", null, _toDisplayString(_ctx.message), 1 /* TEXT */),
    _createVNode("button", { onClick: _ctx.handleClick }, "Click", 8 /* PROPS */, ["onClick"])
  ]))
}

The compiler performs multiple optimizations, such as static hoisting and patch flags. These optimizations enable the runtime to handle DOM updates more efficiently.

Runtime Responsibilities

The runtime receives the compiled render functions and is responsible for actual DOM operations and reactive system management. Its main functions include:

  1. Creating and updating the virtual DOM
  2. Handling component lifecycle
  3. Managing reactive data dependencies
  4. Scheduling updates
// Example of the core runtime process
function mountComponent(initialVNode, container) {
  const instance = createComponentInstance(initialVNode);
  setupComponent(instance);
  setupRenderEffect(instance, initialVNode, container);
}

function setupRenderEffect(instance, vnode, container) {
  effect(() => {
    if (!instance.isMounted) {
      // Initial render
      const subTree = (instance.subTree = instance.render());
      patch(null, subTree, container);
      instance.isMounted = true;
    } else {
      // Update
      const nextTree = instance.render();
      const prevTree = instance.subTree;
      instance.subTree = nextTree;
      patch(prevTree, nextTree, container);
    }
  });
}

Advantages of Separation Design

This separation offers several significant advantages:

  1. Smaller runtime size: Compile-time optimizations reduce the logic the runtime needs to handle.
  2. Better performance: Static analysis can be completed during compilation, avoiding runtime overhead.
  3. More flexible build options: Developers can choose to precompile templates or use the runtime compiler.

Example of Compile-Time Optimization

Vue 3's compiler identifies static content in templates and optimizes it:

// Original template
const template = `
  <div>
    <h1>Static Title</h1>
    <p>{{ dynamicContent }}</p>
  </div>
`;

// Optimized render function
function render(_ctx, _cache) {
  return (_openBlock(), _createBlock("div", null, [
    _hoisted_1,  // Hoisted static h1 node
    _createVNode("p", null, _toDisplayString(_ctx.dynamicContent), 1 /* TEXT */)
  ]))
}

// Static node is hoisted outside the render function
const _hoisted_1 = _createVNode("h1", null, "Static Title");

Collaboration Between Runtime and Compile-Time

The compiler and runtime collaborate through specific conventions. For example, the code generated by the compiler uses helper functions provided by the runtime (e.g., _createVNode) and adds optimization hints (e.g., patch flags). The runtime then uses these hints to perform the most efficient DOM operations.

// Example of patch flags
const dynamicProps = {
  class: 'active',  // Prop that needs comparison
  id: 'item1'      // Static prop
};

// Flags generated by the compiler
const patchFlag = 1 << 2;  // PROPS flag

Custom Renderer Implementation

The separation design also makes it possible to implement custom renderers. Developers can replace the default DOM rendering logic while reusing Vue's compilation and reactive systems.

// Simple custom renderer example
const { createRenderer } = Vue;

const nodeOps = {
  createElement(tag) {
    console.log(`Create element: ${tag}`);
    return { tag };
  },
  insert(child, parent) {
    console.log(`Insert ${child.tag} into ${parent.tag}`);
  }
};

const renderer = createRenderer(nodeOps);
renderer.render({ render: () => _createVNode('div') }, document.body);

Special Handling for Server-Side Rendering

In server-side rendering scenarios, compile-time and runtime behaviors differ. The compiler generates code suitable for string concatenation in the server environment, while the runtime avoids DOM operations.

// Example of a server-side render function
function ssrRender(_ctx, _push) {
  _push(`<div><span>${_ctx.message}</span></div>`);
}

Template Compilation Configuration

Vue 3 provides flexible compilation configuration options, allowing developers to control the compilation process:

const { compile } = Vue;

const result = compile(template, {
  mode: 'module',  // Generate ES module code
  hoistStatic: true,  // Enable static hoisting
  cacheHandlers: true  // Cache event handlers
});

Dynamic Component Compilation

The compiler's handling of dynamic components demonstrates the close collaboration between runtime and compile-time:

// Dynamic component template
const template = `
  <component :is="currentComponent" />
`;

// Compilation result
function render(_ctx, _cache) {
  return (_openBlock(), _resolveDynamicComponent(_ctx.currentComponent));
}

Slot Compilation Handling

The implementation of slots also reflects the separation design. The compiler converts slot content into special render functions, and the runtime is responsible for positioning and rendering the slot content.

// Component template with slots
const template = `
  <Child>
    <template #default="{ msg }">
      {{ msg }}
    </template>
  </Child>
`;

// Compilation result
function render(_ctx, _cache) {
  return (_openBlock(), _createBlock(_resolveComponent("Child"), null, {
    default: _withCtx(({ msg }) => [
      _createTextVNode(_toDisplayString(msg), 1 /* TEXT */)
    ])
  }))
}

Compile-Time Error Detection

The compiler can catch many template errors during compilation, reducing runtime issues:

// Example of an invalid template
const invalidTemplate = `
  <div>
    <p v-for="item in items">{{ item }}</p>
    <p v-else>No items</p>
  </div>
`;

// Compiler will report an error: v-else has no corresponding v-if

Runtime Compilation Options

Although precompilation is recommended, Vue 3 retains runtime compilation capabilities for scenarios requiring dynamic templates:

// Example of runtime compilation
const { compile, createApp } = Vue;

const app = createApp({
  template: `<div>{{ message }}</div>`
});

app.config.compilerOptions = {
  delimiters: ['${', '}']  // Custom interpolation syntax
};

Performance Optimization Comparison

Compile-time optimizations significantly improve update performance. Here's a comparison example:

// Unoptimized render function
function unoptimizedRender() {
  return h('div', [
    h('span', { class: isActive ? 'active' : '' }, text),
    h('button', { onClick: handler }, 'Submit')
  ]);
}

// Optimized render function
function optimizedRender() {
  return (_openBlock(), _createBlock("div", null, [
    _createVNode("span", {
      class: _ctx.isActive ? 'active' : ''
    }, _toDisplayString(_ctx.text), 9 /* TEXT, PROPS */, ['class']),
    _hoisted_2  // Static button
  ]))
}

const _hoisted_2 = _createVNode("button", { onClick: _ctx.handler }, "Submit", 8 /* PROPS */, ["onClick"]);

Reflection in Source Code Structure

Vue 3's source code structure clearly reflects this separation:

packages/
  compiler-core/    # Compile-time core
  compiler-dom/     # DOM-specific compiler
  runtime-core/     # Runtime core
  runtime-dom/      # DOM-specific runtime

Template Precompilation Tools

In real-world projects, @vue/compiler-sfc is typically used to handle single-file components:

const { parse, compileTemplate } = require('@vue/compiler-sfc');

const { descriptor } = parse(`
  <template>
    <div>{{ msg }}</div>
  </template>
  
  <script>
  export default {
    data() {
      return { msg: 'Hello' }
    }
  }
  </script>
`);

const { code } = compileTemplate({
  source: descriptor.template.content,
  id: 'example'
});

Integration with the Reactive System

The compiled code seamlessly integrates with the reactive system:

// Compiled render function
function render(_ctx) {
  return _ctx.count;
}

// The render function automatically re-executes when reactive data changes
const reactiveData = reactive({ count: 0 });
effect(() => {
  console.log(render(reactiveData));
});

reactiveData.count++;  // Triggers re-rendering

Type-Safe Templates

When combined with TypeScript, Vue 3's compiler provides better type safety:

// Type-safe template expressions
const template = `
  <div>
    {{ user.name.toUpperCase() }}  // The compiler checks the user type
  </div>
`;

interface User {
  name: string;
  age: number;
}

Custom Directive Handling

The handling of custom directives also demonstrates the collaboration between compile-time and runtime:

// Custom directive template
const template = `
  <div v-highlight="color"></div>
`;

// Compilation result
function render(_ctx) {
  return _withDirectives(_createVNode("div"), [
    [_ctx.vHighlight, _ctx.color]
  ]);
}

Compile-Time Metadata

The compiler embeds metadata in the generated code to assist runtime optimizations:

// Generated code includes static node markers
function render() {
  return (_openBlock(), _createBlock("div", null, [
    _createStaticVNode("<span>static</span>", 1)
  ]))
}

Dynamic Style Compilation

The compiler's handling of style bindings demonstrates its intelligence:

// Dynamic style template
const template = `
  <div :style="{ color: activeColor, fontSize: size + 'px' }"></div>
`;

// Compilation result
function render(_ctx) {
  return _createVNode("div", {
    style: _normalizeStyle({
      color: _ctx.activeColor,
      fontSize: _ctx.size + 'px'
    })
  });
}

本站部分内容来自互联网,一切版权均归源网站或源作者所有。

如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn

Front End Chuan

Front End Chuan, Chen Chuan's Code Teahouse 🍵, specializing in exorcising all kinds of stubborn bugs 💻. Daily serving baldness-warning-level development insights 🛠️, with a bonus of one-liners that'll make you laugh for ten years 🐟. Occasionally drops pixel-perfect romance brewed in a coffee cup ☕.