做者簡介 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 轉譯代碼的基本流程。
本文最核心的觀點就是,看源碼的目的是爲了解決問題。咱們鼓勵你們在本地把大型項目的源碼跑起來,本身隨意把玩,研究。由於源碼也是普通的代碼,並無太多門檻。惟一的門檻可能就來源於開源項目做者和普通開發者之間的信息不對稱,普通開發者對項目的原理和目錄結構不夠了解。
咱們能夠從開發者那裏獲取資源,同時也能夠多閱讀社區裏的源碼分析文章,這些都有助於咱們理解項目的原理,爲後續的源碼分析打下基礎。