普通视图

发现新文章,点击刷新页面。
昨天 — 2026年3月2日首页

重走 Vue 长征路 Weapp-vite:编译链路与 Wevu 运行时原理拆解

作者 icebreaker
2026年3月2日 14:02

bg.jpg

重走 Vue 长征路 Weapp-vite:编译链路与 Wevu 运行时原理拆解

书接上篇

我当时在团队里做《Vue 编译本质论》分享,正好把一些判断过程也整理了下来:为什么这么做,没选什么,以及这些取舍在小程序里到底值不值。

如果你更关心怎么上手,先看发布文会更顺:Weapp-vite:原生模式之外,多一种 Vue SFC 选择

先把边界说清:Wevu 不是 Vue 3 的搬运工

Wevu 用起来确实很像 Vue 3,但骨子里不是一回事。

对比维度 Vue 3 Wevu
运行环境 Web 浏览器 微信小程序
响应式系统 Proxy + effect Proxy + effect(同源)
渲染目标 DOM 节点 小程序页面/组件实例
渲染方式 Virtual DOM Diff → DOM API Snapshot Diff → setData
数据模型 VNode 树 纯 JS 对象快照
更新机制 异步调度 + DOM 操作 异步调度 + setData
生命周期 onMounted/onUpdated 等 映射到小程序生命周期
事件系统 DOM 事件 小程序 bind/catch 事件
SFC 编译 @vitejs/plugin-vue Weapp-vite 内置

说白了就一件事:响应式 API 长得一样,但最后数据往哪送、怎么送,完全不同

API 为什么能"几乎同写法"

refcomputedwatch 这些在 wevu 里跟 Vue 3 写法一模一样,没必要再造一套 DSL 出来。

import { computed, ref, watch } from 'wevu'

const count = ref(0)
const doubled = computed(() => count.value * 2)

watch(count, (val) => {
  console.log('count changed:', val)
})

很多团队迁过来之后第一反应不是"又要学新东西",而是"这不就是我平时写的吗,换了个宿主而已"。

渲染链路才是真正不一样的地方

Vue 3 走的是这条路:

状态变化 -> effect 触发 -> 组件更新 -> VNode Diff -> DOM 操作

Wevu 走的是这条:

状态变化 -> effect 触发 -> 快照 Diff -> setData -> 小程序渲染

Wevu 干的事情说穿了就是把"算出哪些东西变了"这一步尽量提前做完,等到真正调 setData 的时候,payload 已经被压到最小了。这在小程序里特别关键——大家踩过坑的都知道,setData 传多了,页面就卡,尤其是列表页。

.vue 到四件套:编译阶段干了啥

一个 MyComponent.vue 最终会变成小程序四件套:

MyComponent.vue
  ├─> MyComponent.js
  ├─> MyComponent.wxml
  ├─> MyComponent.wxss
  └─> MyComponent.json

中间的流程大概是这样:先把 SFC 拆成四块——<script><template><style><json>,各自按小程序的规矩做转换,最后拼成产物。

其中 <json> 块用来声明页面或组件的配置(比如 usingComponentsnavigationBarTitleText 之类的),不过我更推荐用 definePageJson / defineComponentJson / defineAppJson 这几个编译宏来代替它——有类型提示,能跟 <script setup> 共享上下文,IDE 重构的时候也不容易漏改。<json> 块当兼容手段用没问题,但不太适合当主力。

.vue 文件
  ↓
vue/compiler-sfc 解析
  ↓
┌─────────┬──────────┬─────────┬────────┐
│ <script>│<template>│ <style> │ <json> │
└────┬────┴────┬─────┴────┬────┴───┬────┘
     │         │          │        │
     ↓         ↓          ↓        ↓
  处理宏    指令映射     样式转换  配置提取
     │         │          │        │
     └─────────┴──────────┴────────┘
               ↓
         生成 .js/.wxml/.wxss/.json

增量构建的时候只处理改过的文件,HMR 能跑得比较稳也是靠这个缓存策略撑着。

defineXxxJson 宏的用法

上面提到推荐用编译宏来代替 <json> 块,这里展开说一下。defineAppJsondefinePageJsondefineComponentJson 都是编译期宏,构建时提取合并到对应的 .json 文件里,运行时零开销。写起来大概是这样:

<script setup lang="ts">
  definePageJson({
    navigationBarTitleText: '首页',
    usingComponents: {},
  })
</script>

好处就是直接写在 <script setup> 里,有完整的类型推导,改字段名的时候 IDE 能帮你检查,不会出现"json 里改了但别的地方没跟上"的情况。

原生组件与插槽

.vue 里 import 原生组件之后,构建阶段会看模板里到底用没用到,用到了才往 usingComponents 里补。这样就不用手动维护那堆路径配置了,少写少错。

插槽也是类似的思路。你写的是 Vue 的 slot 语法,但输出的时候会按小程序的 slot 语义来生成。作用域插槽稍微复杂一点,背后走的是一套语义映射加代码生成,不是简单的字符串替换能搞定的。

Rolldown:收益主要体现在日常开发体感

v6 切到 Rolldown 不是为了赶时髦,就是想把开发时的等待再缩短一点。

日常能感受到的主要是三个地方:冷启动快了、改完代码后增量构建更灵敏、项目依赖多的时候不容易抽风。不是那种"跑分暴涨 300%"的故事,更像是每次都省个几百毫秒,积少成多,一天下来体感差挺多的。

为什么没走 createRenderer 这条路

@vue/runtime-corecreateRenderer 技术上能跑通,但拿来对小程序用,会发现抽象层对不上:它要求你提供一套完整的宿主节点操作接口,而小程序这边最核心的更新通道就是 setData(payload),两边的假设不太匹配。

Wevu 选了"编译到 WXML + 快照 diff + 最小 setData"这条路,优化点压在更贴近小程序实际约束的地方。不一定是最优雅的方案,但在真实业务里跑下来更稳当。

展开聊的话内容比较多,单独写了一篇:为什么没有使用 @vue/runtime-core 的 createRenderer 来实现

当前能力范围

日常开发用到的东西基本都覆盖了:v-if / v-for / v-model 这些核心指令,事件和属性绑定,SCSS/Less 和 CSS Modules,props/emits/slots/provide/inject,生命周期,常用的响应式 API,还有 TypeScript 类型推导和泛型组件。

如果你是从 Vue 3 过来的,写法上基本不用重新学,主要就是记住最后跑的不是浏览器而是小程序。

最后

感谢每一位提建议、报 bug、提 PR 的同学。


如果 Weapp-vite 帮到了你,欢迎给项目点个 Star

Happy Coding! 🚀

Weapp-vite:原生模式之外,多一种 Vue SFC 选择

作者 icebreaker
2026年3月2日 14:00

bg.jpg

Weapp-vite:原生模式之外,多一种 Vue SFC 选择

大家好呀,我是你们的老朋友,开源爱好者 icebreaker!又到了新的一年了,祝大家财源滚滚,早日不用上班实现财务自由!

今天主要来分享一下我开源项目 Weapp-vite 的开发里程碑,核心就是来给大家秀一把。

前言

我还记得在过去 Weapp-vite@4.0 的发布文章里,写过这样的话:

Weapp-vite 不适用场景:需要使用 Vue/React 等前端框架的写法,来取代小程序原生写法。

但社区的声音让我重新想了想这个定位。说实话,原生小程序的语法写多了确实烦,尤其是你要是平时写 Vue 3 写习惯了,回头再 this.setData、手动绑事件、管生命周期,就会觉得特别笨重。Vue 的 SFC 设计确实好用,这个没什么好争的。

而且即使到了这个 AI 时代,小程序的验收工具也比较笨重,因为小程序缺少 playwright-cli, agent-browser, chrome-devtools-mcp 这类的验收工具, 还原度远远不及 Web。

另外还有一点就是当时我正好在团队里面做《Vue 编译本质论》的技术分享

所以我就在想能不能把 Weapp-vite 改造成一个既保留原生模式优势,又提供 Vue 开发体验的工具?

于是,Weapp-vite@6 来了——在原生模式之外,多一种 Vue 选择

背景故事:从零运行时到 Vue SFC 支持

最初的定位

Weapp-vite 一开始就是奔着零运行时去的——一个纯粹的原生小程序构建工具。你用原生写法写代码,它给你提供现代化的开发体验,打出来的包尽量小、跑起来尽量快。

这个定位确实满足了不少用户,特别是只做微信小程序、对性能有洁癖的那批人,还有用 Skyline 的。

但我后来一直在琢磨:能不能在不动原生模式的前提下,再给一个 Vue 的选项?

市面上的选择

让我们看看现有的方案吧:

Taro

跨端能力确实强,但运行时代码量不小。分包没规划好的话,主包很容易超。语法上虽然说支持 React/Vue,但写起来总有种"变种"的感觉,踩坑成本不低。

而且说实话 Taro 现在维护节奏慢了不少,issue 堆得挺多的。

我也曾经在 2 年前,在他们的公众号上,看到了招聘启事,于是投了简历,结果人家完全没有鸟我(笑~)。

uni-app

上手是挺快的,但 uni.xxx 那套 API 和专属 DSL 毕竟是另一套东西。uni-app x 搞的 uts,跟标准 Vue 生态和社区总感觉有点貌合神离。

我很喜欢 uni-app, 当时也很早就让我另外一个项目 weapp-tailwindcss 中兼容了 uni-app x,但是我不喜欢 HBuilderX

mpx

滴滴出品,基于 Vue 2.7 + webpack。我不喜欢,技术栈老了,响应式系统跟标准 Vue 也不完全一样。

我的 Weapp-vite 方案,你可以理解成 mpx 的下一代:Vue 3 风格 + Rolldown Vite,只做小程序,但跟原生 API 完全兼容

Weapp-vite 的思路

Weapp-vite@6 想做的事情很简单:同一个工具,两种模式

  • 原生模式:零运行时,包体积和性能都拉满,适合对这些有要求的项目
  • Vue 模式:完整的 Vue 3 写法,适合 Vue 技术栈的团队

两者可以在同一个项目里混着用。.vue 组件能引原生组件,原生组件也能引 .vue 组件,按页面按组件自己选就行。

运行时 Wevu 的诞生

转折点是 wevu 的出现——一个专门给小程序写的 Vue 运行时。

当时本来是叫 wevue 的,但是这个名字 npm 包已经被注册掉了,所以 trimEnd 了一个 e

wevu 保留了 Vue 3 那些核心 API——refcomputedwatchonMounted 之类的,但底层更新走的是小程序的 setData

更重要的是,Wevu 从一开始就是配合 Weapp-vite 的 SFC 编译来设计的,所以编译时能加的糖都尽量加上了,写起来会比较顺手。

编译时 + 运行时

wevu 运行时搞定之后,Vue SFC 编译支持就是顺水推舟的事了。

认识 Wevu:给小程序写的 Vue 3 风格运行时

Wevu 专门给小程序设计,核心思路就是:响应式那套跟 Vue 3 同源,渲染层按小程序的规矩来

它能干什么

  • refreactivecomputedwatchwatchEffect 这些响应式 API 都有,用法跟 Vue 3 一样
  • onMountedonUpdatedonUnmounted 等生命周期钩子,自动映射到小程序对应的生命周期
  • 快照 diff 优化,setData 只传变了的数据路径,不会整坨丢过去
  • 内置了 defineStore/storeToRefs,用法跟 Pinia 差不多
  • 跟 Weapp-vite 的 SFC 编译配合使用,响应式和生命周期都是打通的

Vue 3 和 Wevu 到底哪不一样

响应式 API 和写法基本一致,区别在渲染那层:Wevu 不操作 DOM,而是操作小程序实例,更新走的是"快照 diff + setData"。

为什么没用 createRenderer

@vue/runtime-corecreateRenderer 技术上能做,但拿来对小程序有个根本问题:它假设宿主能提供一套比较完整的节点操作接口,而小程序这边核心就一个 setData(payload),两边的抽象对不上。

Wevu 走的是"编译到 WXML + 快照 diff + 最小 setData",把优化做在更贴近小程序实际情况的地方。

Weapp-vite + Wevu 怎么配合

  • Weapp-vite 管编译:把 Vue SFC 拆开、转换、生成小程序四件套
  • Wevu 管运行时:提供响应式系统和生命周期

两个加一起,你得到的就是:

  1. Vue 3 的开发体验(SFC + Composition API)
  2. 接近小程序原生的运行性能

Vue SFC 支持是直接内置在 weapp-vite 里的,不是外挂插件。

一处编写,四处生成

你写一个 .vue 文件,Weapp-vite 编译完会变成小程序四件套:

MyComponent.vue
    ├─> MyComponent.js    // 脚本逻辑
    ├─> MyComponent.wxml  // 模板结构
    ├─> MyComponent.wxss  // 样式文件
    └─> MyComponent.json  // 组件配置

Vue 的 <script><template><style><json>(可被 defineXXXJson 宏指令取代) 会被拆开,各自转换成小程序能认的格式。整个过程就像是把 Vue 组件"翻译"成了小程序的方言。

Vue 语法怎么转的

这不是简单地把 Vue 代码塞进小程序,而是做了一层语法映射:

Vue 写法 转换为
v-if / v-else-if / v-else wx:if / wx:elif / wx:else
v-for="item in list" wx:for="{{list}}" + wx:key
@click / @tap bindtap / catchtap
:class / :style class="{{...}}" / style="{{...}}"
v-model 双向绑定的完整实现(input/checkbox/radio/textarea 等)
<script setup> 自动处理响应式和生命周期

你按 Vue 的方式写,Weapp-vite 按小程序的方式跑。

工具链友好:智能提示 + AI 协作

智能提示:直接复用 Vue 官方插件

VS Code 里装了 Vue 官方插件(Vue - Official / Volar)的话,Weapp-vite 的 .vue 文件直接就能用上模板智能提示和类型检查,不用再折腾一套新的编辑器插件。

  • v-for 场景下的 :key 等属性补全
  • :class / :style 等常用绑定提示
  • 组件属性与事件相关补全

ic.png

in.png

inc.png

AI 协作

如果你准备用 AI 来协作开发,我自己的顺序一直很固定:先把 skills 装好,再起 MCP,最后按需喂 llms 语料。

先装 skills:

npx skills add sonofmagic/skills

常用的几个:weapp-vite-best-practicesweapp-vite-vue-sfc-best-practiceswevu-best-practices

然后启动 MCP:

weapp-vite mcp

最后是 llms 语料入口:

  • 页面:/llms
  • 文件:/llms.txt/llms-full.txt/llms-index.json
  • 顺序:llms.txt -> llms-full.txt -> llms-index.json

几个常见用法

响应式状态 + 计算属性

<script setup lang="ts">
import { computed, ref } from 'wevu'

const count = ref(0)
const doubled = computed(() => count.value * 2)
</script>

<template>
  <view>
    <text>{{ count }} / {{ doubled }}</text>
    <button @tap="count++">+1</button>
  </view>
</template>

definePageJson 宏定义页面配置

<script setup lang="ts">
definePageJson({
  navigationBarTitleText: '首页',
  navigationBarTextStyle: 'white',
})
</script>

.vue 里直接用原生组件

<script setup lang="ts">
import NativeMeter from '../../native/native-meter/index'
</script>

<template>
  <NativeMeter label="构建链能力" :value="80" />
</template>

v-model 表单双向绑定

<script setup lang="ts">
import { ref } from 'wevu'

const message = ref('')
</script>

<template>
  <input v-model="message" placeholder="输入点什么..." />
  <text>{{ message }}</text>
</template>

更多像 slotsprops/emitsapp.vue 配置以及编译行为说明,已放到原理文档统一说明:Weapp-vite@6 原理拆解

适用场景

双模式并存才是 Weapp-vite 的杀手锏

Weapp-vite@6 最实用的一点就是"同仓双模式"。性能敏感的页面继续走原生,迭代快、业务重的页面丢到 Vue 模式里。迁移可以一个页面一个页面来,不用一口气重写整个项目。

什么时候用 Vue 模式:

  • 你平时写 Vue 3,想用同样的写法搞小程序
  • 团队本来就是 Vue 技术栈,想复用过来
  • 想要热重载、TypeScript 这些现代开发体验
  • 希望 Vue 代码后面还能往 Web 项目上搬

什么时候用原生模式:

  • 对性能有洁癖,一点运行时开销都不想要
  • 已经有一大堆原生代码,不想大动
  • 团队对小程序原生 API 很熟
  • 包体积卡得很死

什么时候该选别的框架?

  • Taro:如果你真的要同时出微信、支付宝、百度、字节好几个平台的小程序,甚至还要编 H5 和 RN,那 Taro 确实是绕不开的。不过说真的,大部分项目真需要跨这么多端吗?

  • uni-app:如果你想要一个开箱即用的全家桶,而且已经习惯了 DCloud 那套生态(HBuilderX、uniCloud 之类的),uni-app 挺合适。就是它的 DSL 跟标准 Vue 还是有些差异。

  • mpx:Vue 2.7 + webpack,技术栈偏老了。

快速体验

  1. 创建项目
pnpm create weapp-vite@latest
# 选择 Wevu 模板或者 Wevu + TDesign 模板
  1. 开发
pnpm dev
  1. 享受 Vue 带来的快乐
<script setup>
import { ref } from 'wevu'

const message = ref('Hello, weapp-vite@6!')
</script>

<template>
  <view>{{ message }}</view>
</template>

技术细节

原理和实现细节,如果大家有兴趣的话,我会另外写一篇专门的技术拆解文档。

后面打算做什么

接下来主要推两条线:支持更多小程序平台,以及支持 Web 目标。

Android / iOS 原生方向

现在原生 Android / iOS 这边,很多场景还是得靠微信开发者工具的多端框架来转。这块后面会继续投入,目标是把链路做得更稳、接入成本更低。

最后

Weapp-vite@6 这次就是想把选择权留给你:要性能就走原生,要开发体验就走 Vue 模式,混着来也行。背后靠的是 vue/compiler-sfc 的解析能力、wevu 的运行时设计,以及社区一路给的真实反馈。

感谢每一位提建议、报 bug、提 PR 的同学。


如果 Weapp-vite 帮到了你,欢迎给项目点个 Star

Happy Coding! 🚀

❌
❌