开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 11 天,点击查看活动详情
前言
不甘于平庸又不努力
2023继续!!!
vue3源码走起
首先 git clone https://github.com/vuejs/vue-next.git,然后安装依赖npm run dev
Vue3的核心源码在packages这里,基于RollUp构建,且V3整体源码采用的是 Monorepo 规范.每个子模块都可以独立编译和打包的从而独立对外提供服务
├── packages
│ ├── compiler-core // 核心编译器(平台无关)
│ ├── compiler-dom // dom编译器
│ ├── compiler-sfc // vue单文件编译器
│ ├── compiler-ssr // 服务端渲染编译
│ ├── global.d.ts // typescript声明文件
│ ├── reactivity // 响应式模块,可以与任何框架配合使用【很重要】
│ ├── runtime-core // 运行时核心实例相关代码(平台无关)
│ ├── runtime-dom // 运行时dom 关api,属性,事件处理
│ ├── runtime-test // 运行时测试相关代码
│ ├── server-renderer // 服务端渲染
│ ├── sfc-playground // 单文件组件在线调试器
│ ├── shared // 内部工具库,不对外暴露API
│ ├── size-check // 简单应用,用来测试代码体积
│ ├── template-explorer // 用于调试编译器输出的开发工具
│ └── vue // 面向公众的完整版本, 包含运行时和编译器
入口文件[packages/vue/src/index.ts]
const compileCache: Record<string, RenderFunction> = Object.create(null)
function compileToFunction(
template: string | HTMLElement,
options?: CompilerOptions
): RenderFunction {
if (!isString(template)) { // 如果模板不是字符串
if (template.nodeType) { // 判断是否为dom的节点
template = template.innerHTML // 取innerHtml
} else {
__DEV__ && warn(`invalid template option: `, template)
return NOOP
}
}
const key = template
const cached = compileCache[key]
if (cached) { // 如果有缓存函数,返回缓存
return cached
}
if (template[0] === '#') { // 如果模板以#开头,表明要找对应的id元素
const el = document.querySelector(template)
if (__DEV__ && !el) {
warn(`Template element not found or is empty: ${template}`)
}
template = el ? el.innerHTML : ``
}
const opts = extend(
{
hoistStatic: true,
onError: __DEV__ ? onError : undefined,
onWarn: __DEV__ ? e => onError(e, true) : NOOP
} as CompilerOptions,
options
)
const { code } = compile(template, opts) // 返回code,下面着重讲解下
function onError(err: CompilerError, asWarning = false) {
...
}
const render = (
__GLOBAL__ ? new Function(code)() : new Function('Vue', code)(runtimeDom) //将code作为参数构建匿名函数并调用,返回结果为渲染函数
) as RenderFunction
// mark the function as runtime compiled
;(render as InternalRenderFunction)._rc = true // 将函数标记为运行时编译
return (compileCache[key] = render) // 返回渲染函数并缓存 ,与31行相对应
}
registerRuntimeCompiler(compileToFunction) // 将编译函数注册到运行时
// 导出
export { compileToFunction as compile }
export * from '@vue/runtime-dom'
AST
V2中也有这一块,但是写到这时候我已经忘了。再学一遍吧!
定义:抽象语法树,是对源代码的结构抽象。因此我们对该树进行语法分析,通过变换该抽象结构,而不改变原来的语义,达到优化的目的。AST在线解析器
Eg:
function add(a, b) { return a+b }
那V3中生成ast的位置就是:compile(template, opts)这个函数中返回了一个baseCompile()[packages/compiler-core/src/compile.ts]的函数,这个函数中利用baseParse生成ast,接着transform对ast进行变换,利用generate根据变换后的ast生成code 并返回
compile方法
export function baseCompile(
template: string | RootNode,
options: CompilerOptions = {}
): CodegenResult {
const onError = options.onError || defaultOnError
const isModuleMode = options.mode === 'module'
const ast = isString(template) ? baseParse(template, options) : template // 生成ast.最重要的方法
const [nodeTransforms, directiveTransforms] =
getBaseTransformPreset(prefixIdentifiers) //根据前缀标识,获取预设转换函数
...
// 对ast进行变换
transform(
ast,
extend({}, options, {
prefixIdentifiers,
nodeTransforms: [
...nodeTransforms,
...(options.nodeTransforms || []) // user transforms
],
directiveTransforms: extend(
{},
directiveTransforms,
options.directiveTransforms || {} // user transforms
)
})
)
// 根据ast生成vue入口需要的编译代码code
return generate(
ast,
extend({}, options, {
prefixIdentifiers
})
)
}
v3在这个过程中的新特性
- transform()[packages/compiler-core/src/transform.ts]方法中出现了一段代码
- parseTag()[packages/compiler-core/src/parse.ts]方法中v-if v-for
运行入口creatApp
从入口文件中可以看到调用方法来看runtime-dom,
export const createApp = ((...args) => {
//获取匿名单例的createApp函数,其中匿名单例可以返回render、hydrate、createApp三种渲染函数
const app = ensureRenderer().createApp(...args)
const { mount } = app // 对原有的mount方法进行包装
app.mount = (containerOrSelector: Element | ShadowRoot | string): any => {
const container = normalizeContainer(containerOrSelector)
if (!container) return
const component = app._component
...
// clear content before mounting 挂载前清空内容
container.innerHTML = ''
const proxy = mount(container, false, container instanceof SVGElement) 挂载并获得代理对象
if (container instanceof Element) {
container.removeAttribute('v-cloak') 清除v-cloak
container.setAttribute('data-v-app', '') 容器增加data-v-app属性
}
return proxy
}
return app
}) as CreateAppFunction<Element>
看到这我发现V3的代码梳理起来有点难得样子。我现在歇一歇 准备整体看完一遍再来写
后记
本文仅作为自己一个阅读记录,具体还是要看大佬们的文章