13518219792

建站动态

根据您的个性需求进行定制 先人一步 抢占小程序红利时代

如何阅读源码——以Vetur为例

本文转载自微信公众号「Tecvan」,作者范文杰 。转载本文请联系Tecvan公众号。

专注于为中小企业提供成都网站设计、成都网站制作服务,电脑端+手机端+微信端的三站合一,更高效的管理,为中小企业藁城免费做网站提供优质的服务。我们立足成都,凝聚了一批互联网行业人才,有力地推动了近千家企业的稳健成长,帮助中小企业通过网站建设实现规模扩充和转变。

我很早就意识到,能熟练、高效阅读开源前端框架源码是成为一个高级前端工程师必须具备的基本技能之一,所以在我职业生涯的最早期,就已经开始做了很多次相关的尝试,但结果通常都以失败告终,原因五花八门:

这个列表还可以继续往下拉很长很长,总之既有我自己主观认知上的限制又有切切实实的客观原因。后来因为工作的契机硬着头皮看完 Vue 和 mxGraph 的源码,发现事情并没有自己想象中那么困难,后来前前后后陆续看了很多框架源码,包括 Webpack、Webpack-sources、Vite、Eslint、Babel、Vue-cli、Vuex、Uniapp、Lodash、Vetur、Echarts、Emmet 等等,愚钝如我也慢慢摸索出了一些普适的方式方法,进而斗胆撰下这篇文章,不敢说授人以渔,但至少也该抛砖引玉吧。

所以这是一篇为哪些有意,或准备,或已经在阅读前端框架源码的同学而写的文章,我会在这里抛出一些经过我个人多次实践总结出来的阅读技巧和原则,并结合 Vetur 源码,具体地讲解我在阅读源码的各个阶段所思所想,希望能给读者带来一些启发。

弄清楚目标

在介绍具体的技巧之前,有必要先跟读者探讨一下阅读源码的动机,想清楚到底需不需要通过这种方式提升自身技能,虽然学习优秀框架源码确实有非常多不言自明的好处,但每个人的经验、所处的语境、诉求、思维习惯不同,实际学习效果在不同个体或个体的不同时期必然存在极大的差异,这里面最大的变量一是经验,二是目标,经验因人而异,且很难在短时间内补齐,没有太多讨论空间;倒是目标方面值得盘道盘道。

第一层,先弄清楚为啥要阅读源码?可能的原因有很多,例如:

这里面有一些很抽象,例如最后一个“好奇”;有一些很具体,例如“为了做二次改造”;还有一些在具体与抽象之间。按照 SMART 原则的说法,越具体、可衡量的目标越容易达成,如果读者的目标还处在比较模棱两可,不够具体详细的阶段,那执行过程大概率会翻车,毕竟这是一件特别消耗精力与耐性的活儿。

对于这种情况,我的建议是不妨往更细节的层次再想一想,例如对于最后一点“好奇”,可以想想具体有哪些特性让你特别神奇,值得花时间精力去细致地探索,放在 Vetur 语境下可以是“我想了解 Vetur 的 template 错误提示与 eslint 如何结合在一起,实现模板层面的错误提示功能”,这就很具体很容易衡量了。

第二层,读者如果已经有了明确、具体、可衡量的目标,不妨在开始之前先自问几个问题:

如果经过这番推敲之后,必要性、可行性、相关性都与个人目标契合,那就没啥可犹豫的。

第三层,需要辩证地去看待所谓“目标” —— 不是把整个项目完整读完读通才叫成功,如果能从一些语句、片段、局部模块中习得新的设计思维、工具方法,甚至仅仅是命名规范都可以算作个人的一点进步,积少成多远比拔苗助长靠谱的多。所以一开始没必要把目标定的太高,能刚刚好满足自身需求是最好的,过程中如果发现问题域的复杂度在不断膨胀变大,持续投入很多时间却始终没有明显成效的话,那建议果断放弃或者请求外援,重新评估目标与可行性之后再做决定。

总之,这是一个预期管理的问题,我们可以多参考 SMART 原则,多从具体、可衡量、可行性、相关性几个维度思考,不断推敲是否需要做这件事;如何拆解目标,用目标反推计划,不断推进个人成功。

阅读技巧

了解背景知识

「知识」是形成「理解」的必要条件,展开学习任何一个开源项目之前都有必要花点时间调研项目相关的基础知识,渐进构建起一套属于你自己的知识体系,这包括:

注意,这里的目标是迅速构建起关于这个开源项目的抽象 —— 甚至不太准确的知识框架,有意思地避免陷入无尽的细节中,就像在阅读一篇文章的时候,可以先看看目录结构粗略地了解文章的信息框架,了解文章大概内容。

例如,我刚开始学习 Vetur 的时候只知道这是一个 VS Code 插件,但完全不了解插件怎么写、怎么运行、怎么实现语言特性,所以我做的第一件事情是仔仔细细阅读 VS Code 的官方文档(所幸文档非常齐全,不像某著名打包工具),学习关于插件开发的基本知识,包括:

进一步总结关于 VS Code 语言插件的要素:

VS Code 领域的知识量还是很庞大的,学习背景知识并梳理成这种高度结构化、高度抽象的脑图能够给你一个更高层、全面的视角,理想状态下,后续实际分析源码的时候这些骨架脉络能够让你非常本能地映射到某一个切面的知识点,事半功倍。

六步循环分析

接下来,我会介绍一套我常用的分析流程:

整体分为六个步骤:

之后,再继续设定切入点,重复执行上述流程直到透彻地理解了问题

这是一套在 「总-分-总」 视角之间反复横跳最终构建出完整视角的方法论,重点就在于告诉读者在什么阶段应该关注什么,忽略什么,输入什么,输出什么,我个人就是按照这个方法慢慢摸索出包括 Webpack、Babel、Vue、Vetur、mxGraph 在内的各种开源框架的实现原理。

理解项目结构

刚开始阅读源码的时候,相信大多数人都会很懵逼,无从下手,这是因为读者对项目缺乏一个必要的框架性认知,不了解程序的入口在哪里、关键组件有哪些、各个文件夹有什么作用等,遇到问题无法迅速推测实现路径。

所以,阅读源码的第一个步骤,应该是先花点时间粗浅地分析、理解项目的组织结构。所幸一个值得深入阅读学习的开源项目,通常都会有较强的整体性与一致性,我们只需要梳理出三条线索:

放在 vetur 语境下,我们在上面“了解背景知识”一节已经了解到 VS Code 插件需要在 package.json 文件通过 contributes 等属性声明插件的配置信息,所以这几个问题都能在 package.json 文件找到答案。

入口分析

首先,需要识别出 Vetur 应用的入口,这一步的作用是帮助我们理解 Vetur 是如何向 VS Code 贡献新特性的。分析 vetur 的 package.json 发现有三种直接指向到文件的配置项:

三个入口分别实现三种不同的语言特性功能,略显复杂,这里有必要分别展开了解一下。

探索 contributes.languages 配置

逐个讲解,contributes.languages 配置信息指向到 ./languages/***-language-configuration.json 文件,如:

  
 
 
 
  1. {
  2.     // ...
  3.     "contributes": {
  4.         "languages": [
  5.             {
  6.                 "id": "vue",
  7.                 "configuration": "./languages/vue-language-configuration.json"
  8.             },
  9.             {
  10.                 "id": "vue-html",
  11.                 "configuration": "./languages/vue-html-language-configuration.json"
  12.             }
  13.             // ...
  14.         ]
  15.     }
  16.     // ...
  17. }

这里回过头翻一下 VS Code 对 [contributes.languages](https://code.visualstudio.com/api/references/contribution-points#contributes.languages) 的解释(感谢资源丰富的 VS Code 社区):

Contribute definition of a language. This will introduce a new language or enrich the knowledge VS Code has about a language.

大意是说 contributes.languages 配置项的作用主要是增进 VS Code 对具体语言的理解,至于怎么增强呢?继续打开配置项中的 ./languages/vue-language-configuration.json 文件:

  
 
 
 
  1. {
  2.     "comments": {
  3.         // symbol used for single line comment. Remove this entry if your language does not support line comments
  4.         "lineComment": "//",
  5.         // symbols used for start and end a block comment. Remove this entry if your language does not support block comments
  6.         "blockComment": [
  7.             "/*",
  8.             "*/"
  9.         ]
  10.     },
  11.     // ...
  12. }

文件中定义了行内 comment、块级 comment、括号、折叠等语言规则的配置,规则都很简单直白,篇幅关系这里不展开。

回顾一下探索步骤:

探索 contributes.grammars 配置

contributes.grammars 项包含诸多指向到 ./syntaxes/vue-xxx.json 的配置信息,形如:

  
 
 
 
  1. {
  2.     "contributes": {
  3.       "grammars": [
  4.         {
  5.           "language": "vue",
  6.           "scopeName": "source.vue",
  7.           "path": "./syntaxes/vue-generated.json",
  8.           "embeddedLanguages": {
  9.             "text.html.basic": "html",
  10.             // ...
  11.           }
  12.         },
  13.         {
  14.           "language": "vue-postcss",
  15.           "scopeName": "source.css.postcss",
  16.           "path": "./syntaxes/vue-postcss.json"
  17.         }
  18.         // ...
  19.       ]
  20.     }
  21.   }

同样的,我们先查一下官网对 [contributes.grammars](https://code.visualstudio.com/api/references/contribution-points#contributes.grammars) 配置项的解释:

Contribute a TextMate grammar to a language. You must provide the language this grammar applies to, the TextMate scopeName for the grammar and the file path.

这段描述略微复杂,大意是开发者可以通过 grammars 属性提供关于语言的 TextMate 形式的语法描述,grammars 配置项包含三个属性:

这里面 path 属性指向一个内容更复杂的配置文件 ./syntaxes/vue-xxx.json,我们可以接着打开其中任意一个文件,关键内容结构如下:

  
 
 
 
  1. {
  2.     "name": "Vue HTML",
  3.     "scopeName": "text.html.vue-html",
  4.     "fileTypes": [],
  5.     "uuid": "ca2e4260-5d62-45bf-8cf1-d8b5cc19c8f8",
  6.     "patterns": [
  7.         // ...
  8.         {
  9.             "name": "meta.tag.any.html",
  10.             "begin": "(<)([A-Z][a-zA-Z0-9:-]*)(?=[^>]*>)",
  11.             "beginCaptures": {
  12.                 "1": {
  13.                     "name": "punctuation.definition.tag.begin.html"
  14.                 },
  15.                 "2": {
  16.                     "name": "support.class.component.html"
  17.                 }
  18.             }
  19.         }
  20.     ],
  21.     "repository": {
  22.         // ...
  23.     }
  24. }

按照 Syntax Highlight Guide(https://zjsms.com/e7E5Jdq/) 一节的说法这里面最重要的是 patterns 属性,而 patterns 属性最关键的功能就是以正则语句表达语言的词法分析规则,并分配词法对应的 name 命名,详细的配置规则还可以继续参考 TextMate 官网,这里大致理解作用即可,先不展开深究。

探索 main 配置

接着往下看,第三个值得关注的是 main 属性,在 vetur 中对应的值为:

  
 
 
 
  1. "main": "./dist/vueMain.js"

VS Code 官网对 main 属性的解释非常精简:「The entry point to your extension」,也就是插件的入口,通常需要指向到可执行的 JS 文件,插件启动时 VS Code 会执行这个入口文件导出的 activate 方法,内容框架大致为:

  
 
 
 
  1. import vscode from 'vscode';
  2. export async function activate(context: vscode.ExtensionContext) {
  3.     // ... 启动逻辑
  4. }

在 Vetur 中,activate 函数定义在 client/vueMain.ts 文件,分析源码可知该函数主要完成如下事项:

这两个操作具体的作用,我们先按下不表,后面再展开。

小结

对入口的分析就到这里了,我们先总结、记录下关键信息:

到这里,虽然我们还是不了解 Vetur 的实现细节,但是对 Vetur 的背景知识与项目结构应该已经有了一个比较基础的认知,已经能大致识别哪些功能由哪些模块实现。

OK,这里先保持好这个模模糊糊的认知就行了,不要花太多时间。

基础依赖分析

接下来,需要梳理一下 Vetur 的基础依赖,这一步的作用是帮助我们理解 Vetur 可能用到哪些基础技术,比如用到哪些工程化工具、怎么编译、怎么检查代码等。

工程化命令,核心有:

项目的 devDependencies 依赖,主要包含 typescript、tslint、rollup、vscode-languageclient、husky、mocha、vscode-test、prettier

那么,从这些信息我们基本可以推断出如下信息:

文件结构

接着,还需要稍微展开看看 Vetur 的文件结构,这一步能够一定程度上帮助我们理解 Vetur 的代码架构及要素,推测各种特性是在什么位置实现的。Vetur 的文件结构大致上如下:

  
 
 
 
  1. vetur
  2. ├─ .vscode
  3. │  ├─ ...
  4. ├─ build
  5. │  ├─ ...
  6. ├─ client
  7. │  ├─ client.ts
  8. │  ├─ commands
  9. │  │  ├─ ...
  10. │  ├─ grammar.ts
  11. │  ├─ ...
  12. ├─ languages
  13. │  ├─ vue-html-language-configuration.json
  14. │  ├─ ...
  15. ├─ scripts
  16. │  ├─ build_grammar.ts
  17. │  └─ tsconfig.json
  18. ├─ server
  19. │  ├─ .gitignore
  20. │  ├─ .mocharc.yml
  21. │  ├─ .npmrc
  22. │  ├─ bin
  23. │  │  └─ vls
  24. │  ├─ package.json
  25. │  ├─ rollup.config.js
  26. │  ├─ src
  27. │  │  ├─ ...
  28. ├─ syntaxes
  29. │  ├─ markdown-vue.json
  30. │  ├─ pug
  31. │  │  ├─ ...
  32. │  ├─ ...
  33. │  └─ vue.yaml
  34. ├─ test
  35. │  ├─ ...
  36. ├─ vti
  37. │  ├─ README.md
  38. │  ├─ bin
  39. │  │  └─ vti
  40. │  ├─ package.json
  41. │  ├─ rollup.config.js
  42. │  ├─ src
  43. │  │  ├─ ...
  44. │  ├─ tsconfig.json
  45. │  └─ yarn.lock
  46. ├─ tsconfig.options.json
  47. ├─ package.json
  48. ├─ ...
  49. └─ yarn.lock

其中,比较关键的有:

我们还可以继续往下探索各个子目录的内容,但是注意浅尝辄止即可,后面随着源码阅读的深入,读者对各个目录的理解应该会不断迭代增长,现在没必要花太多时间。

小结

回顾一下,我们首先学习了一些背景知识,之后花了一些时间分析项目的入口、基础依赖、文件结构,到这里我们基本上可以推断出:

这些信息是后续分析源码的必要条件,而这个过程跟学习一门新语言很类似,读者可以回想一下最开始学习 JavaScript 的时候,有经验的学习者不会一上来马上深入诸如原型、变量提升、事件循环等语言细节,而是先以更高层、更抽象的视角学习 JavaScript 语言的基本骨架,包括函数、循环语句、分支判断语句、对象等,从而构建起一个抽象的结构化认知,后续再慢慢填充细节,有点自顶向下的味道。

设定切入点

在对项目背景与结构有基本了解之后,我们可以正式开始分析源码了。首先,读者要找到一个匹配自身状态和需求的切入点,本质上就是将大目标拆解成一系列小目标,将大问题拆解成一系列更具体的小问题,然后带着具体问题更聚焦地去看代码。

所谓切入点可以直接对标到框架的具体功能,或者某些底层机制的实现上,以 Vetur 为例,它实现了诸多辅助开发 Vue SFC 组件的特性,包括代码补全、错误诊断、代码高亮、跳转到定义、hover 提示等等,这里面任意一个展开来都有大量可以挖掘的空间,如果从一开始就漫无目的瞎逛乱看那铁定是看不出个所以然的,鉴于我的目标就是想通过 Vetur 学习 VS Code 插件的开发套路,所以选择了一个看起来比较简单的特性:「代码补全」 作为第一个切入点,后续的学习经历证明这是一个非常合适的点,不复杂但是已经能帮我窥见 Vetur 的核心工作机制,以此类推后面分析其它高级特性如代码高亮、代码补全等,基本上就是很轻车熟路的状态了。

如果你有一些更明确的目的,比如解决某个具体的 bug,那你应该会更容易 get 到当下最需要做的事情;如果始终抓不到要点,那么建议先回到前面“了解背景知识”或“理解项目结构”的步骤,继续探索一些上下文信息,再试试问自己:我接下来到底应该先了解哪些具体功能的实现逻辑?

记住,这并不是一锤子买卖,如果你在后续的分析过程中发现这个切入点变得越来越复杂,超出最开始的预期,不要有心理负担,这再正常不过了,而且反而侧面表现出你对问题域有越来越少的理解了,可以回过头来重新调整目标,找一个更小的切入点。

善用搜索引擎

定下切入点后,首先要做的不是打开代码咔咔就干,而应该首先试试在社区搜索相关的资料,毕竟自媒体时代了,很多开源框架的知识已经被无数人吃透、捏碎、重组成各种维度的文章,顺着这些文章的思路去理解源码会比完全靠自己摸索效率高很多。

列举几种我常用的搜索渠道:

假如搜了一通找不到答案,可以试试不同的关键词组合,我经常用的关键词有:

假如还是找不到,还可以试试换一个意思接近的关键词,绕点弯路。总之就是想尽办法找到有用的,适合当下问题的信息,帮助读者更快更平滑地深入研究源码,这一步对新手尤为重要。


当前标题:如何阅读源码——以Vetur为例
标题路径:http://cdbrznjsb.com/article/dhjegjh.html

其他资讯

让你的专属顾问为你服务