做者简介 Daniel 蚂蚁金服·数据体验技术团队html
转自: https://github.com/ProtoTeam/blog/blob/master/201805/3.md前端
目前网上有不少「XX源码分析」这样的文章,不过这些文章分析源码的范围有限,有时候讲的内容不是读者最关心的。同时我也注意到,源码是在不断更新的,文章里写的源码每每已通过时了。由于这些问题,不少同窗都喜欢本身看源码,本身动手,丰衣足食。vue
这篇文章主要讲的是阅读大型的前端开源项目好比 React、Vue、Webpack、Babel 的源码时的一些技巧。目的是让你们在遇到须要阅读源码才能解决的问题时,能够更快的定位到本身想看的代码。授人以鱼不如授人以渔,但愿你们能够经过这篇博客,了解到阅读大型前端项目源码时的切入点。在以后遇到好奇的问题时,能够本身去探索。node
首先咱们要明确一点,看源码的目的是什么?react
我我的的意见是,看源码是为了解决问题。开源项目的源代码并无什么很是特殊的地方,也都是普通的代码。这些代码的数量级通常都挺大,若是想是从源码中学到东西,直接浏览整个 Codebase 无疑是大海捞针。webpack
但若是是带着问题去看源码,好比想了解一下 React 的合成事件系统的原理,想了解 React 的 setState 先后发生了什么,或者想了解 Webpack 插件系统的原理。也有多是遇到了一个 bug,怀疑是框架/工具的问题。在这样的状况下,带着一个具体的目标去看源码,就会有的放矢。git
以前看到一种说法,看源码要从项目的第一个 commit 开始看。若是是为了解决前文中对框架/工具产生的困惑,那天然要看当前项目中用到的框架/工具的版本。github
若是是为了学习源码,我也建议看最新的源码。由于一个项目是在不断迭代和重构的。不一样版本之间多是一次彻底的重写。好比 Vue 2.x 和 React 16。重构致使了代码架构上的一些变化,Vue 2.x 引入了 Vritual DOM,Pull + Push 的数据变化检测方式让整个代码的结构变的更清晰了,因此 2.x 的代码其实比 1.x 的更容易阅读。React 16 重写了 Reconciler,引入了 fiber 这个概念,整个代码仓库结构也更清晰,因此更推荐阅读。web
看源码怎么看,固然不能一把梭了。chrome
看源码以前须要对项目的原理有一个基本的了解。所谓原理就是,这个项目有哪些组成部分,为了达到最终的产出,要通过哪几步流程。这些流程里,业界主流的方案有哪几种。
好比前端 View 层框架,要渲染出 UI,组件要通过 mount、 render 等等步骤。数据驱动的前端框架,在 mounted 以后,就会进入一个循环,当用户交互触发组件数据变化时,会更新 UI。其中数据的检测方式又有分 Push 和 Pull 两种方案。渲染 UI 能够是全量的字符串模板替换,也能够是基于 Virtual DOM 的差量 DOM 更新。
又好比前端的一些工具,Webpack 和 Babel 这些工具都是基于插件的。基本的工做流程就是读取文件,解析代码成 AST,调用插件去转换 AST,最后生成代码。要了解 Webpack 的原理,就要知道 Webpack 基于一个叫 tapable 的模块系统。
那咱们要如何了解这些呢?要了解这些,能够去各大网站和博客上的《XXX源码解析》系列。经过这些文章,咱们能够对咱们要看的框架/工具的原理有一个大体的了解。
不过最终咱们仍是要直接看源码。笔者真正看源码的第一步就是把项目的代码仓库 clone 到本地。而后按项目 README 上的构建指南,在本地 build 一下。
若是是前端框架,咱们能够在 HTML 中里直接引入本地 build 出的 umd bundle(记得用 development build,否则会把代码压缩,可读性差),而后写一个简单的 demo,demo 里引入本地的 build。若是是基于 Nodejs 的工具,咱们能够用 npm link 把这个工具的命令 link 到本地。也能够直接看项目的 package.json 的入口文件,直接用 node 运行那个文件。
这里要强调一下,大型的开源项目通常都会有一个 Contribution Guide,目的是让想贡献代码的开发者更快上手。里面就有讲怎么在本地构建代码。
以 React 为例,React 的 Contributing Guide 里就 Development Workflow 这一节。里面有这么一段话:
The easiest way to try your changes is to run yarn build core,dom --type=UMD and then open fixtures/packaging/babel-standalone/dev.html. This file already uses react.development.js from the build folder so it will pick up your changes.
因此 React 仓库中的 fixtures/packaging/babel-standalone/dev.html 就是一个方便的 demo 页。咱们能够在这个页面快速查看咱们在本地对代码的改动。
你能够尝试着在项目的入口文件中加入一句 log,看看是否是能够在控制台/终端看到这句 log。若是能够的话,恭喜你,你如今能够随便把玩这个项目了!
在看具体的代码以前,咱们须要理清项目的目录结构,这样咱们才能更快的知道在哪里地方找相关功能的代码。
咱们看看 React 的目录结构。React 是一个 monorepo。也就是一个仓库里包含了多个子仓库。咱们在 packages 目录下能够看到不少单独的 package:
在 React 16 以后,React 的代码分为 React Core,Renderer 和 Reconciler 三部分。这是由于 React 的设计让咱们能够把负责映射数据到 UI 的 Reconciler 以及负责渲染 Vritual DOM 到各个终端的 Renderer 和 React Core 分开。React Core 包含了 React 的类定义和一些顶级 API。大部分的渲染和 View 层 diff 的逻辑都在 Reconciler 和 Renderer 中。
Babel 也是一个 monorepo。Babel 的核心代码是 babel-core 这个 package,Babel 开放了接口,让咱们能够自定义 Visitor,在AST转换时被调用。因此 Babel 的仓库中还包括了不少插件,真正实现语法转换的实际上是这些插件,而不是 babel-core 自己。
Vuejs 的代码比较典型,核心代码在 src 目录下,按功能模块划分。由于 Vue 也支持多平台渲染,因此把平台相关的代码都放到了 platform 文件夹下,core 文件夹中是 Vue 的核心代码,compiler 是 Vue 的模板编译器,把 HTML 风格的模板编译为 render function。
Webpack 和 Babel 同样,能够说都是基于插件的系统。Webpack 的主要源码在 lib 目录下,里面的 webpack.js 就是入口文件。
上面说了四个项目的目录结构,那咱们遇到一个新的开源项目,应该怎么了解它的目录结构呢?
若是这个项目是一个 monorepo,首先咱们要找到核心的那个 package,而后看里面的代码。
不是 monorepo 的话,通常来讲,若是这个项目是一个 CLI 的工具,那 bin 目录下放的就是命令行界面相关的入口文件,lib 或者 src 下面就是工具的核心代码。若是这个项目是一个前端 View 层框架,那目录结构就和 Vue 相似。
做为验证,你们能够看一下打包工具 parcel 和前端 View 层库 moon 的目录结构。目录结构这个东西每每是大同小异,多看几个项目就熟悉了。
运行了本地的 build,了解了目录结构,接下来咱们就能够开始看源码了!以前说了,咱们要以问题驱动,下面我就以 React 调用 setState 先后发生了什么这个问题做为例子。
咱们能够在 setState 的地方打一个断点。首先咱们要找到 setState 在什么地方。这个时候以前的准备工做就派上用处了。咱们知道 React 的共有 API 在 react 这个 package 下面。咱们就在那个 package 里面全局搜索。咱们发现这个 API 定义在 src/ReactBaseClasses.js 这个文件里。
因而咱们就在这里打一个断点:
Component.prototype.setState = function(partialState, callback) { invariant( typeof partialState === 'object' || typeof partialState === 'function' || partialState == null, 'setState(...): takes an object of state variables to update or a ' + 'function which returns an object of state variables.', ); debugger; this.updater.enqueueSetState(this, partialState, callback, 'setState'); };
而后运行本地 React build 的 demo 页面,让组件触发 setState,咱们就能够在 Devtool 里看到断点了。
咱们走进 this.updater.enqueueSetState 这个调用,就来到了 ReactFiberClassComponent 这个函数中的 enqueueSetState,这里调用了 enqueueUpdate 和 scheduleWork 两个函数,若是要深刻 setState 以后的流程,咱们只须要再点击 走进这两个函数里看具体的代码就能够了。
若是想看 setState 以前发生了什么,咱们只须要看 Devtool 右边的调用栈:
点击每个 frame 就能够跳到对应的函数中,而且恢复当时的上下文。
结合一步一步的代码调试,咱们能够看到框架的函数调用栈。对于每一个重要的函数,咱们能够在仓库里搜索到源码,进一步研究。
Node 工具的调试方法也是类似的,咱们能够在运行 node 命令时加上 --inspect 参数。具体能够看 Debugging Node.js with Chrome DevTools 这篇博客。
其实你们都知道单步调试这种办法,但在哪里打断点才是最关键的。咱们在熟悉框架的原理以后,就能够在框架的关键链路上打断点,好比前端 View 层框架的声明周期钩子和 render 方法,Node 工具的插件函数,这些代码都是框架运行的必经之地,是不错的切入点。
若是是为了了解一个特定的问题,你们能够直接在本身以为有问题的地方打断点。而后把源码运行起来,想办法让代码运行到那个地方。咱们在断点能够看到局部变量等等信息,有助于定位问题。
其实开源项目的开发团队也都致力于让更多的人参与到项目中来,下降项目的门槛。因此咱们在线上其实能够找到不少来自开发团队的资源。这些资源能够帮助咱们去理解项目的原理。
每一个项目都有一些核心开发者,好比 React 的 Dan Abramov, Andrew Clark 和 Sebastian Markbåge。Webpack 的 Tobias Koppers 和 Sean Larkin。Vue 的 Evan You。咱们能够在 Twitter 上关注他们,了解项目的动态。
若是咱们关注了上面的核心开发者,就会发现他们时常会发布一些和源码/项目原理有关的博客或者视频。
React 的官方博客最近就有不少和项目开发有关的博客。
Andrew Clark 一开始就写了一篇介绍 fiber 架构的文档。 Dan Abramov 最近在 JSConf 上对 React 将来的一些新特性的介绍 - Beyond React 16。React 博客中的 Sneak Peek: Beyond React 16 也是对此次 Talk 的介绍。
Evan You 介绍前端框架数据变化侦测原理的 Talk。Vue 文档中也有 Reactivity in Depth 这样的介绍原理的章节。
Sean Larkin 的 Everything is a plugin! Mastering webpack from the inside out 介绍了 Webpack 的核心组件 Tapable。
James Kyle 的 How to Build a Compiler 可让咱们了解 Babel 转译代码的基本流程。
本文最核心的观点就是,看源码的目的是为了解决问题。咱们鼓励你们在本地把大型项目的源码跑起来,本身随意把玩,研究。由于源码也是普通的代码,并无太多门槛。惟一的门槛可能就来源于开源项目做者和普通开发者之间的信息不对称,普通开发者对项目的原理和目录结构不够了解。
咱们能够从开发者那里获取资源,同时也能够多阅读社区里的源码分析文章,这些都有助于咱们理解项目的原理,为后续的源码分析打下基础。