入口文件:packages/vue/src/index.ts
packages/vue/src/index.ts 并不是 Vue 框架的"启动器",而是一个精心设计的"API 导出器"和"构建入口" 它的核心任务是把所有分散在独立包(如 @vue/runtime-dom, @vue/reactivity)中的核心功能,重新组装并统一导出 ,最终打包成你熟悉的、可以通过 <script src="..."> 或 import Vue from 'vue' 引入的"完整版 Vue"。 
这种设计带来了巨大的灵活性:
-
按需使用:
- 完整版 (
import Vue from 'vue'):包含运行时和编译器,可以在浏览器中动态编译模板。 - 仅运行时版 (
import Vue from '@vue/runtime-dom'):体积小约30%,需配合构建工具(如Vite)预编译模板。
- 完整版 (
-
技术栈无关 :
@vue/runtime-core不依赖 DOM,这让 Vue 可以用于小程序、Canvas 等非 Web 环境。packages/vue只是其 Web 版本的具体实现。
🚀 实际构建产物
当你运行 npm run build 时,构建工具(Rollup)会以这个 index.ts 为入口,生成最终分发文件:
vue.global.js- 用于浏览器的全局变量版本vue.esm-browser.js- 用于浏览器的 ES 模块版本vue.runtime.esm-bundler.js- 给构建工具用的仅运行时版本
所以,packages/vue/src/index.ts 并不是 Vue 应用的逻辑起点,而是 Vue 库本身的"包装出口" 。它的存在是为了:
- 技术整合:将分离的编译器与运行时连接起来。
- 构建入口:为打包工具提供唯一的起始点。
- 提供便利 :为开发者提供一个简单、统一的导入入口 (
import Vue from 'vue')。
vue应用真正的启动过程,@vue/runtime-dom中的导出函数,这才是应用生命周期的开始
createApp是Vue3应用的真正的起点,将你的跟组件和用户代码连接到真实的DOM世界。 首先明确,你在 runtime-dom 中调用的 createApp,实际上来自 runtime-core 。runtime-dom 只是对它进行了类型扩展 和包装导出。
核心代码位于 packages/runtime-dom/src/index.ts,但主要逻辑在 packages/runtime-core/src/apiCreateApp.ts 中。下面我将结合两者进行解析。

第一步:创建渲染器(基础设施)
当 createApp 被调用时,它背后依赖的核心是 createRenderer。
typescript
javascript
// 位于 runtime-dom/src/index.ts
// 1. 创建专用于DOM的渲染器,传入所有DOM操作方法(rendererOptions)
export const createApp = ((...args) => {
// 2. ensureRenderer() 会调用 createRenderer(rendererOptions),
// 生成一个具备 render 和 hydrate 方法的渲染器对象
const app = ensureRenderer().createApp(...args)
// ... 后续对 app.mount 的增强(见下文)
return app
}) as CreateAppFunction<Element>
ensureRenderer():获取或创建唯一的DOM渲染器。它是整个应用与DOM交互的发动机。createRenderer(rendererOptions):这是关键。rendererOptions就是之前定义的包含patchProp、insert、createElement等几十个具体DOM操作方法的对象。createRenderer函数(来自runtime-core)接收这些具体方法,返回一个平台通用的渲染器。 这是依赖注入的经典实现:核心逻辑是通用的,具体实现由外部注入。
第二步:创建App实例(核心逻辑)
接着,调用渲染器的 .createApp() 方法,这是在 runtime-core 中定义的 createAppAPI 函数。
typescript
typescript
// 位于 runtime-core/src/apiCreateApp.ts
export function createAppAPI<HostElement>(
render: RootRenderFunction,
hydrate?: RootHydrateFunction
): CreateAppFunction<HostElement> {
// 这个函数返回的就是我们最终使用的 createApp 函数
return function createApp(rootComponent, rootProps = null) {
// 1. 创建应用上下文对象 (context),这是全局配置和状态的集合
const context = createAppContext()
const installedPlugins = new Set() // 用于记录已安装插件,防止重复
let isMounted = false // 标记应用是否已挂载
// 2. 创建 app 对象(这就是我们得到的 app 实例)
const app: App = (context.app = {
_uid: uid++,
_component: rootComponent, // 保存根组件对象
_props: rootProps,
_container: null, // 初始为null,mount后指向根DOM容器
_context: context, // 关联上文创建的上下文
// 3. 核心:mount 方法(初始版本,runtime-dom 会增强它)
mount(rootContainer: HostElement, isHydrate?: boolean): any {
if (isMounted) {
warn(`App has already been mounted.`) // 开发环境警告
return
}
// 3.1 标准化容器:支持字符串选择器(如'#app')或DOM元素
const container = normalizeContainer(rootContainer)
if (!container) return
// 3.2 创建根组件的虚拟节点 (vnode)
const vnode = createVNode(
rootComponent as ConcreteComponent,
rootProps
)
vnode.appContext = context // 将应用上下文关联到vnode
// 3.3 核心渲染调用:将虚拟树转换为真实DOM
if (isHydrate) {
hydrate(vnode as VNode<Node, Element>, container)
} else {
render(vnode, container) // 这里调用的是 runtime-dom 提供的 render 函数
}
isMounted = true
app._container = container // 记录挂载容器
container.__vue_app__ = app // 在DOM元素上记录app实例,便于调试或HMR
// 3.4 返回根组件实例的代理(方便少数需要直接操作实例的场景)
return getExposeProxy(vnode.component!) || vnode.component!.proxy
},
// 4. 其他应用API
use(plugin: Plugin, ...options: any[]) {
// 插件安装逻辑,调用 plugin.install(app, ...options)
},
component(name: string, component?: Component): any {
// 全局组件注册,存入 context.components
},
directive(name: string, directive?: Directive) {
// 全局指令注册,存入 context.directives
},
// unmount, provide 等其他方法...
})
return app
}
}
关键行解读:
const context = createAppContext():创建应用级别的共享上下文 。所有组件树中的组件都能访问到它内部的全局组件、指令、配置等。这是实现app.component、app.directive全局注册的基石。const vnode = createVNode(...):将你传入的根组件(可以是一个对象或一个组件定义)转换为初始的虚拟DOM节点。Vue 3 的所有渲染都围绕虚拟DOM进行。render(vnode, container):这是点睛之笔 ,也是连接所有模块的枢纽。这个render函数就是第一步中createRenderer返回的核心渲染函数。它会启动整个patch(协调)算法 ,递归地将vnode树比对并渲染到真实的containerDOM节点中。
第三步:runtime-dom 对 mount 的增强(最后一公里)
在 runtime-dom 中,它拿到了 runtime-core 返回的基础 app 实例,但对 mount 方法做了关键增强,以支持更符合Web开发习惯的用法。
typescript
typescript
// 位于 runtime-dom/src/index.ts (接第一步的代码)
const app = ensureRenderer().createApp(...args)
// 从 app 实例上获取原始的 mount 方法
const { mount } = app
// 重写(增强)mount 方法
app.mount = (containerOrSelector: Element | string): any => {
// 1. 标准化容器(核心增强点):支持字符串选择器
const container = normalizeContainer(containerOrSelector)
if (!container) return
// 2. 获取用户传入的根组件(可能是一个简单的模板对象)
const component = app._component
// 3. 如果根组件不是函数 & 没有render函数 & 有template,则将template编译为render函数
if (!isFunction(component) && !component.render && !component.template) {
component.template = container.innerHTML // 注意:这仅用于无构建步骤的CDN用法
}
// 4. 清除容器内的现有内容(在挂载前)
container.innerHTML = ''
// 5. 调用从 runtime-core 拿来的原始 mount 方法,执行真正的挂载
const proxy = mount(container, false, container instanceof SVGElement)
// ... 后续处理
return proxy
}
为什么需要这个增强?
- 用户体验 :让
app.mount('#app')这种写法成为可能,而不必每次都写document.querySelector。 - 支持纯HTML开发 :当你在没有构建步骤(如Webpack/Vite)的环境中,直接通过
<script>标签引入Vue时,你可能需要在HTML中写模板。这段代码会检查根组件,如果它没有render函数,就会尝试将template选项(或容器内的HTML)在运行时编译 成render函数。这就是"完整版"Vue(包含编译器)与"仅运行时版"的区别所在。
💎 总结
createApp 函数的主要工作可以概括为以下几步:
| 步骤 | 所在模块 | 核心工作 | 产出 |
|---|---|---|---|
| 1. 创建渲染器 | runtime-dom |
注入DOM API,创建平台专属渲染器。 | 具备 render 和 createApp 方法的渲染器对象。 |
| 2. 创建App实例 | runtime-core |
创建应用上下文和基础App对象,定义核心API。 | 一个包含 _context、基础 mount 方法的 app 实例。 |
| 3. 增强Mount | runtime-dom |
为 mount 增加容器标准化和模板编译支持。 |
最终用户使用的、功能完整的 app.mount 方法。 |
| 4. 启动应用 | 用户调用 | 用户调用 app.mount,触发虚拟DOM渲染流程。 |
根组件被渲染成真实DOM,应用启动。 |
本质上,createApp 是一个高级工厂函数,它:
- 装配环境:将平台(浏览器)的具体操作与Vue的通用核心连接。
- 创建上下文:为整个组件树建立一个共享的配置和状态空间。
- 封装启动 :提供一个简单易用的
mount方法,隐藏了虚拟DOM创建、渲染、挂载等复杂细节。