阿里云主机折上折
  • 微信号
您当前的位置:网站首页 > 代码生成(Codegen)的过程

代码生成(Codegen)的过程

作者:陈川 阅读数:9795人阅读 分类: Vue.js

代码生成(Codegen)的过程

Vue3的编译器将模板转换为渲染函数时,代码生成是最后的关键阶段。这个阶段接收经过转换和优化的AST,输出可执行的JavaScript代码字符串,最终形成组件的render函数。

模板AST到渲染代码的转换

编译器遍历AST节点,根据节点类型生成对应的JavaScript代码片段。例如一个简单的div元素:

<div class="container">Hello</div>

对应的AST节点结构大致如下:

{
  "type": 1,
  "tag": "div",
  "attrsList": [{"name": "class", "value": "container"}],
  "children": [{"type": 2, "content": "Hello"}]
}

代码生成器会将其转换为:

const _Vue = Vue
return function render(_ctx, _cache) {
  with (_ctx) {
    return _createVNode("div", { class: "container" }, "Hello")
  }
}

静态提升优化

Vue3会对静态节点进行提升优化,避免重复创建。例如:

<div>
  <span>static</span>
  <span>{{ dynamic }}</span>
</div>

生成的代码会分离静态和动态部分:

const _hoisted_1 = _createVNode("span", null, "static")

return function render(_ctx, _cache) {
  with (_ctx) {
    return _createVNode("div", null, [
      _hoisted_1,
      _createVNode("span", null, dynamic)
    ])
  }
}

事件处理生成

对于事件绑定,代码生成器会做特殊处理:

<button @click="handleClick">Click</button>

生成代码时会将事件转换为正确的格式:

return function render(_ctx, _cache) {
  with (_ctx) {
    return _createVNode("button", {
      onClick: _cache[1] || (_cache[1] = (...args) => handleClick(...args))
    }, "Click")
  }
}

插槽内容生成

插槽的处理是代码生成中较复杂的部分。对于具名插槽:

<slot name="header" :user="user"></slot>

生成的代码会包含插槽的上下文信息:

return function render(_ctx, _cache) {
  with (_ctx) {
    return _renderSlot(_ctx.$slots, "header", {
      user: user
    })
  }
}

条件渲染生成

v-if/v-else指令会转换为条件表达式:

<div v-if="show">A</div>
<div v-else>B</div>

生成的三元表达式代码:

return function render(_ctx, _cache) {
  with (_ctx) {
    return show
      ? _createVNode("div", null, "A")
      : _createVNode("div", null, "B")
  }
}

列表渲染生成

v-for指令会转换为数组map操作:

<li v-for="item in items" :key="item.id">{{ item.text }}</li>

生成的渲染逻辑:

return function render(_ctx, _cache) {
  with (_ctx) {
    return _createVNode("ul", null, 
      items.map(item => 
        _createVNode("li", { key: item.id }, item.text)
      )
    )
  }
}

组件生成

自定义组件的生成会包含更多上下文信息:

<MyComponent :msg="message" @update="handleUpdate" />

生成的组件调用代码:

return function render(_ctx, _cache) {
  with (_ctx) {
    return _createVNode(MyComponent, {
      msg: message,
      onUpdate: _cache[1] || (_cache[1] = (...args) => handleUpdate(...args))
    })
  }
}

静态属性合并

对于静态和动态属性混合的情况:

<div class="static" :class="dynamicClass"></div>

生成器会合并属性处理:

return function render(_ctx, _cache) {
  with (_ctx) {
    return _createVNode("div", {
      class: _normalizeClass(["static", dynamicClass])
    })
  }
}

指令生成

自定义指令需要特殊处理:

<div v-my-directive:arg.modif="value"></div>

生成的指令代码包含完整指令信息:

return function render(_ctx, _cache) {
  with (_ctx) {
    return _withDirectives(_createVNode("div"), [
      [_resolveDirective("my-directive"), value, "arg", { modif: true }]
    ])
  }
}

生成函数的结构

完整的生成函数通常包含这些部分:

export function generate(ast, options) {
  const context = createCodegenContext(ast, options)
  const { push, indent, deindent } = context
  
  // 生成函数前缀
  push(`const _Vue = Vue\n`)
  push(`return function render(_ctx, _cache) {\n`)
  indent()
  push(`with (_ctx) {\n`)
  indent()
  
  // 生成主体内容
  genNode(ast, context)
  
  // 生成函数后缀
  deindent()
  push(`}\n`)
  deindent()
  push(`}`)
  
  return {
    code: context.code,
    ast,
    map: context.map
  }
}

源码中的关键函数

在Vue源码中,几个关键函数负责不同部分的生成:

// packages/compiler-core/src/codegen.ts
function genElement(node: ElementNode, context: CodegenContext) {
  const { push, helper } = context
  const { tag, props, children } = node
  
  if (tag === 'slot') {
    genSlot(node, context)
  } else if (tag === 'template') {
    genChildren(node, context)
  } else {
    // 组件或原生元素
    push(`${helper(CREATE_VNODE)}(`)
    genNodeList([tag, props, children], context)
    push(`)`)
  }
}

function genChildren(node: ParentNode, context: CodegenContext) {
  const { push, children } = context
  if (children.length) {
    push('[')
    genNodeList(children, context)
    push(']')
  }
}

性能优化处理

生成器会针对不同场景应用优化策略:

  1. 静态节点提升
  2. 事件处理缓存
  3. 属性合并
  4. 块标记(Block tree)

例如带v-if的区块:

// 生成的代码会标记为可动态更新的区块
return function render(_ctx, _cache) {
  with (_ctx) {
    return (_openBlock(), _createBlock("div", null, [
      _createVNode("p", null, "Static"),
      show ? _createVNode("p", null, "Dynamic") : _createCommentVNode("v-if")
    ]))
  }
}

源码映射生成

开发环境下会生成source map便于调试:

function generate(
  ast: RootNode,
  options: CodegenOptions
): CodegenResult {
  const context = createCodegenContext(ast, options)
  // ...生成代码
  
  return {
    code: context.code,
    ast,
    map: context.map ? context.map.toJSON() : undefined
  }
}

服务端渲染差异

服务端渲染的代码生成有所不同:

// 客户端渲染
_createVNode("div", { class: "foo" }, "hello")

// 服务端渲染
_createSSRNode("div", { class: "foo" }, "hello")

生成器的可扩展性

Vue的代码生成器设计支持插件扩展:

interface CodegenPlugin {
  (ast: RootNode, context: CodegenContext): void
}

function createCodegenContext(
  ast: RootNode,
  options: CodegenOptions
) {
  const plugins = options.plugins || []
  const context = { /* ... */ }
  
  plugins.forEach(plugin => plugin(ast, context))
  return context
}

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

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

前端川

前端川,陈川的代码茶馆🍵,专治各种不服的Bug退散符💻,日常贩卖秃头警告级的开发心得🛠️,附赠一行代码笑十年的摸鱼宝典🐟,偶尔掉落咖啡杯里泡开的像素级浪漫☕。‌