当前位置: 首页 > Web前端 > HTML

Vue3源码系列:项目调试与项目架构分析(一)

时间:2023-03-28 12:01:55 HTML

为什么要看源码公司里大部分开发人员都在开发维护一个相对稳定成熟的系统,每天搬砖写业务代码,很少有从事项目工作的机会尝试应用一些新技术。那么,如何提高自己的技术能力呢?阅读优秀的开源项目是推荐的学习方式,尤其是项目中经常用到的框架源码,这样既能加深对框架的理解,又能知其所以然,学习其优秀的代码设计、规范、等,借鉴开源大牛的思想结晶,吸取他们的优秀经验。vue3在vue2的基础上做了很大的改进,重写了响应式系统,编译原理,设计composition组合api,使用TypeScript等,之前看了vue2的核心源码,受益匪浅,让我更有动力了去阅读研究vue3的底层设计带来了哪些变化,从中学习新的技术和思想。源码学习方法看源码不是一上来就啃源码,那样你会头晕目眩,面对海量代码会被劝退。根据以往的经验。首先是搭建开发调试环境,让项目跑起来。这时,我心里有了底线。然后了解项目的结构和技术栈,知道大概的目录结构功能,做什么;最后带着目的和问题阅读源码:比如项目初始化和挂载过程是怎样的?响应式系统是如何实现的?nextTick异步更新策略是如何实现的?…一步步看这些问题,一步步debug,单点突破,沿着这条主线,耐心的寻找自己想要的答案。搭建源码调试环境克隆源码,下载到本地gitclonegit@github.com:vuejs/core.git安装依赖,使用pnpmcdcore&&pnpminstall打包vue文件pnpmrundevrunpnpmrundevcreatepackages/vue/dist目录下,生成两个文件vue.global.js和vue.global.js.map,这是打包好的vue文件启动本地服务ctrl+c结束进程,执行pnpmrunservepnpm上面的命令runserve启动一个服务,默认启动一个端口,打开localhost,显示项目的目录结构界面,进入界面example,点击packages="vue="examples,选择一个classic/todomvc,进入todomvc.html界面。也可以在packages/vue/example目录新建html文件,引入dist/vue.global.js文件,安装http服务插件VSCodeLiveServer,在本地启动服务,打开html文件,打开浏览器开发者工具在源码面板,按快捷键cmd+p,输入todomvc,输入todomvc文件的源码cmd+f搜索Vue.createApp,在第85行下断点进行单步调试,并进入createApp函数;查看源码文件的路径结构,鼠标右键,在左侧边栏选择Revealinsidebar打开,可以看到createApp函数的真面目,在/packages/runtime-打开源码调试模式dom/src/index.ts如果想在源码里放debugger或者console调试,可以在dev命令后面加上调试参数--sourcemap"scripts":{"dev":"nodescripts/dev.js--sourcemap",}rollup打包vue时,根据环境参数配置好打包后的项目架构再开始阅读源码,首先要从全局的角度对框架设计有一个整体的了解,否则在阅读之前的过程中,它容易被细节困住,迷失方向项目结构从分析目录结构开始,了解每个模块的功能,以及模块之间的划分和关联。Vue3梳理代码结构,采用monorepo单仓库模式管理项目代码,使用pnpm工作空间方式实现。它将内部实现部分抽象成模块,放在包下。每个包子目录都有自己的类型声明和单元测试。包可独立发布,易于维护、发布和阅读。比如我们可以单独引用reactivity模块,在导入这些包的时候需要加上@vue/前缀。//目录结构解析├──.github//github工作流程、issue模板、代码贡献指南├──.vscode//vscode编辑器的配置├──packages//vue源码核心包,使用pnpmworkspaceworkspace管理│├──compiler-core//编译器(平台无关),比如baseCompile编译模板文件,baseParse生成AST│├──compiler-dom//基于compiler-core,一个浏览器的编译模块,可以看到是基于baseCompile、baseParse,并重写了complie和parse│├──compiler-sfc//编译vue单文件组件│├──compiler-ssr//服务端渲染编译│├──reactivity//vue独立的响应式模块,可以和任何框架一起工作,使用代理│├──reactivity-transform//响应式实验功能,目前仅用于测试│├──runtime-core//平台无关的运行时。有虚拟DOM渲染器、vue组件和各种API。可以为特定平台实现高级运行时,例如自定义渲染器│├──runtime-dom//浏览器运行时。包含处理原生DOMAPI│├──runtime-test//专门为测试而写的轻量级运行时。由于这个rumtime“渲染”的DOM树实际上是一个JS对象,所以这个runtime可以在所有的JS环境中使用。您可以使用它来测试渲染是否正确。│├──server-renderer//服务器端渲染│├──sfc-playground│├──shared//内部工具库,不暴露API│├──size-check//简单应用,用于测试代码size│├──template-explorer//调试编译器输出的开发工具│└──vue//完整公版,包括运行时和编译器│└──vue-compat//兼容vue2│├──global.d.ts//声明文件├──scripts//vue3脚本文件,包括配置文件、编译打包等│├──bootstrap.js│├──build.js│├──checkYarn.js│├──dev.js│├──release.js│├──setupJestEnv.ts│├──utils.js│└──verifyCommit.js├──test-dts//测试文件│├──README.md│├──component.test-d.ts│├──componentTypeExtensions.test-d.tsx│├──defineComponent.test-d.tsx│├──functionalComponent.test-d.tsx│├──h.test-d.ts│├──index.d.ts│├──inject.test-d.ts│├──reactivity.test-d.ts│├──ref.test-d.ts│├──setupHelpers.test-d.ts│├──tsconfig.build.json│├──tsconfig.json│├──tsx.test-d.tsx│└──watch.test-d.ts├──CHANGELOG.md//很多版本提交记录、时间和内容├──LICENSE//MIT协议是所有开源许可证中最宽松的,除必须包含许可证声明外没有任何限制├──README.md//项目说明├──api-extractor.json//这是所有包共享的基础配置文件├──jest.config.js//测试配置文件├──package.json//项目依赖├──rollup.config.js//rollup打包配置文件├──tsconfig.json//确定用于编译本项目的根文件和编译选项├──pnpm-lock.yaml//锁定依赖版本└──pnpm-workspace.yaml//pnpmworkspacecompiler-core:编译器(平台无关),比如基本的baseCompile编译模板文件,baseParse生成ASTcompiler-dom:基于compiler-core,为浏览器设计的编译模块,可以看到是基于baseCompile的,baseParse,重写complie,parsecompiler-sfc:编译vue单文件组件compiler-ssr:server-siderendering-relatedreactivity:vue独立的响应式模块runtime-core:也是一个平台无关的基础模块,有vue和虚拟dom的各种APIRendererruntime-dom:浏览器运行时。包含原生DOMAPIruntime-test:专门为测试编写的轻量级运行时。由于这个rumtime“渲染”的DOM树实际上是一个JS对象,所以这个runtime可以在所有的JS环境中使用。您可以使用它来测试渲染是否正确。shared:内部工具库,不暴露API.json-所有包共享的配置文件。当我们src下有多个文件时,打包后会生成多个声明文件。@microsoft/api-extractor库的使用是将所有.d.ts综合为一个,仍然可以根据写好的注释自动生成文档。template-explorer:用于调试编译器输出的开发工具。您可以运行npmrundevdevtemplate-explorer并打开其index.html以获取基于当前源代码的模板编译副本。在线编译网址:vue-next-template-explorer.netlify.app/#Vue源码入口先看官网了解函数用法,Vue3实例化应用没有使用new方法,而是使用createAppimport{createApp}from'vue'createApp({data(){return{count:0}}}).mount('#app')从上面debug,把debug放在createApp上,进入这个函数,函数位置在文件packages/runtime-dom/src/index.tsexportconstcreateApp=((...args)=>{constapp=ensureRenderer().createApp(...args)if(__DEV__){injectNativeTagCheck(app)injectCompilerOptionsCheck(app)}const{mount}=appapp.mount=(containerOrSelector:Element|ShadowRoot|string):any=>{constcontainer=normalizeContainer(containerOrSelector)if(!container)返回constcomponent=app._componentif(!isFunction(component)&&!component.render&&!component.template){if(__COMPAT__&&__DEV__){for(leti=0;i以上代码createApp在创建应用时调用,实现流程.ensureRenderer是一个返回渲染器的单例模式函数。如果没有renderer,会调用createRenderer获取renderer,得到一个app实例;在dev环境注册一个方法:isNativeTag,挂载到app.config下;获取实例的挂载方法,并保存;重写实例的挂载方法;调用normalizeContainer获取根元素容器;判断模板,得到需要渲染的模板;清空容器的innerHTML;调用上例的mount方法;删除v-cloak属性,添加data-v-app属性;返回挂载的代理;从上面来看,createApp主要做的就是调用`ensureRenderer().createApp(...args)创建一个app实例,然后重写mount`方法挂载,返回这个实例,整个实例化和挂载的过程很清楚,后面会详细研究分析。其次,当代码逻辑复杂难懂时,可以从测试用例入手。比如官网上对某个方法的描述不是很清楚,想了解更多。与直接深挖源码相比,测试用例是一种相对快捷、省时的方式。测试文件的后缀是spec,比如createApp测试文件createApp.spec.ts,有两个测试用例可以挂载到svg元素上,应该不会改变原来的根组件选项对象,也给出相应的修复issue,你也可以去了解这个特性的背景onissuedescribe('createAppfordom',()=>{//#2926test('mounttoSVGcontainer',()=>{constroot=document.createElementNS('http://www.w3.org/2000/svg','svg')createApp({render(){returnh('g')}}).mount(root)expect(root.children.length).toBe(1)expect(root.children[0]instanceofSVGElement).toBe(true)})//#4398test('shouldnotmutateoriginalrootcomponentoptionsobject',()=>{constoriginalObj={data(){return{counter:0}}}consthandler=jest.fn(msg=>{expect(msg).toMatch(`Componentismissingtemplateorrenderfunction`)})constRoot={...originalObj}constapp=createApp(Root)app.config.warnHandler=handlerapp.mount(document.createElement('div'))//确保挂载基于c复制Root对象而不是Root对象本身合适的工具先让代码跑起来,才能在源码上调试,然后理清代码的组织结构和目的,单点突破,带着问题和目的去阅读,做好单元测试的使用,同时权衡利弊,跳转就是跳转,长期处于蒙昧状态很容易走进死胡同,可以标记一下,回头看看;利用搜索引擎结合网上的源码分析资料进行了解。