導讀:上期咱們討論了 San CLI 的使用,這期咱們再深刻一點,來看看 San CLI 的實現原理。node
爲了方便理解下文的 San CLI 的總體工做流程(主流程),咱們先來看下 San CLI 的核心模塊和核心概念。webpack
San CLI 的核心模塊包括:git
san-cli
:負責 San CLI 的主流程和實現核心功能;san-cli-service
:Service 層,負責 Service 流程;san-cli-command-init
:實現 san init
命令的 Command 插件;san-cli-plugin-*
:Service 插件;san-cli-utils
:工具庫,在插件中也能夠直接使用;san-cli-webpack
:webpack build 和 dev-server 的通用邏輯,webpack 自研插件等。核心概念主要有流程和插件。其中,流程又分爲主流程和 Service 流程,插件又分爲 Command 插件 和 Service 插件:github
san-cli/index.js
的流程,是整個 San CLI 的工做流程。當咱們在命令行輸入 San CLI 相關的命令後,好比 san init
、 san serve
(對應 npm start
) 和 san build
(對應 npm run build
),就會進入主流程,主流程會執行對應命令的 handler
。有的命令的 handler
不直接包含處理邏輯,而是引入 san-cli-service
,好比 san serve
和 san build
,輸入這類命令時,就會從主流程進入 Service 流程。san-cli-service/Service.js
的流程,主要處理 Webpack 構建相關的邏輯。hanlder
;San CLI 的總體工做流程,即主流程,在 san-cli/index.js
中,大體以下:web
san-cli/lib/Commander.js
建立命令實例:option
;logLevel;
NODE_ENV
環境變量;argv
添加日誌等屬性和方法。init
、 serve
、 build
、 inspect
、 ui
等;package.json
和 sanrc.json
裏聲明的預置命令(自定義命令);sanrc.json
是配置San CLI的文件,和san.config.js
不一樣,後者是配置項目的文件,詳見San CLI官方文檔。hanlder
,開始 San CLI 的正式執行。結合核心模塊的主流程以下圖所示:npm
san init
命令用於初始化項目,用法在上期已有所介紹,這期咱們來看該命令是怎麼作到初始化一個項目的。json
san init
初始化項目的原理,簡單來講,就是經過 git 命令遠程拉取項目腳手架模板的代碼庫到本地,或者直接使用本地的項目腳手架模板的代碼庫,而後使用 vinyl-fs 將拉取到的代碼庫的文件依次處理,處理完成就獲得了一個初始化好的項目。gulp
vinyl-fs是gulp的核心。設計模式
san init
主要由四步串行任務組成:數組
vinyl-fs
把項目腳手架模板從緩存目錄遍歷處理到開發者指定的項目目錄;package.json
裏的依賴。對應的流程圖以下圖所示:
其中,檢查目錄和離線包狀態的流程圖以下:
san init
的具體實如今san-cli-command-init
模塊中,san-cli-command-init
模塊是一個 Command 插件,其核心是一個TaskList
類,san init
的執行過程的本質就是:傳入上述4個任務組成的數組來實例化TaskList
並調用實例的run
方法。
咱們來看下 TaskList
的內部實現。
當調用 TaskList
實例的 run
方法時,首先會對執行實例化 TaskList
時傳入的任務列表的第一個任務進行處理。
由於這些任務本質上都是一個個函數,因此先給第一個任務加上一些方法,好比表示這個任務已完成的 complete
方法,加完方法後,就調用第一個任務函數。
調用第一個任務函數,就意味着第一個任務開始執行了,當第一個任務該作的事情都作完後,最後就會在這個任務函數中調用以前給這個任務函數加上的 complete
方法。
complete
方法就作一件事情,讓 TaskList
實例開始處理下一個任務,處理方式和上面說的同樣,只是簡單地重複。
最後, TaskList
實例發現沒有下一個任務了,就收工了。
咱們用簡化版的源碼來看下 TaskList
的使用和實現。
TaskList
的使用:
以 checkStatus
爲例,看下任務函數的實現:
TaskList
的實現:
咱們能夠看出, san init
的設計模式仍是比較優秀的,舉個栗子,若是咱們想給 san init
添加多一個任務,那隻須要關注該任務自己的實現,實現好後放入實例化 TaskList
時傳入的任務列表中,就能夠了,可擴展性很是好。
San CLI 的插件分爲 Command 插件和 Service 插件,在上期咱們以實際例子討論了具體怎麼開發一個 Command 插件或 Service 插件了,這期咱們就來看看 San CLI 的插件機制。
Command 插件相對 Service 插件來講,機制比較簡單。
Command 插件實際是 yargs 的插件系統的擴展,yargs 是一個 npm 包,用它咱們能夠定義咱們本身的命令行命令。
回顧下咱們在上期建立的 Command 插件:
之因此要這麼寫,是由於這是 yargs 對定義一個命令的要求。定義好命令後,就在項目的 package.json
裏聲明這個命令。
當咱們執行任何一個 san 的命令時——注意,是任何一個——在真正執行這個命令以前,San CLI 會先去讀取 package.json
裏聲明的命令,而後找到命令的定義並傳入 yargs,此時,yargs 就知道了都有些什麼命令,在此以後,San CLI 才把咱們執行的命令的名字和參數傳如 yargs,yargs 拿到命令的名字和參數後,就回去執行對應命令的 hanlder。
Command 插件的機制就是這樣。
另外還值得注意的是,在 san-cli/lib/commander.js
裏定義了一個名爲 Command 的類,這個類對 yargs 插件作了一些定製,好比經過中間件機制添加了經常使用的方法和屬性到 argv
對象中,方便下游 handler
直接使用。
San CLI 的 Service 插件機制借鑑了 Vue CLI 的 Service 插件機制,但有一些不一樣之處:
下面咱們會以 san serve 命令爲例,分別看下 Service 流程、Service 插件的設計思路和 Service 類的簡化版源碼。
Service 流程,即 Service 的整個工做流程:
san serve
命令,進入 san-cli/commands/serve
的 handler
;san serve
命令的 handler
主要是實例化 Service
,實例化會將配置項和 Service 插件進行處理;service.run(callback)
,進入 Service 流程,Service 流程的實現主要在 service.run
中:loadEnv
:加載 env 文件;loadProjectOptions
:加載 san.config.js
;init
:service 啓動:callback
。對應的流程圖以下:
咱們自定義的 Service 插件的具體執行時機是在 3-1-1 「初始化插件,即依次執行插件」 這一步,對應上圖中的 「初始化插件(插件.apply)」 這一步。
上圖中的 「初始化 plugin 變量並加載傳入的 plugin」 這一步和 「加載 config 中 plugins 裏設置的插件」 這一步,其實都是在加載 Service 插件,只不過前者是在加載內置插件和 sanrc.json
裏預設的插件,然後者主要是在加載 san.config.json
裏的插件。
加載 Service 插件的流程圖以下:
輸入 san serve
命令,觸發對應的 handler
。 handler
主要就作兩件事情:一是實例化 Service,二是調用 Service 實例的 run
方法。
實例化 Service 時,會加載內置 Service 插件和 sanrc.json
裏預設的 Service 插件。若是咱們自定義的 Service 插件預設在了 sanrc.json
裏,好比上期的 san-cli-plugin-get-entry
,這個時候就會被加載了。
實例化完 Service,就調用 Service 實例的 run
方法。
調用 run
方法時,首先會加載 san.config.js
裏的 Service 插件,固然也包括咱們放在 san.config.js
裏的自定義 Service 插件;而後,該加載的 Service 插件都加載完了,這時就準備依次執行它們了。
在執行每一個 Service 插件以前,會先實例化 PluginAPI
。 PluginAPI
實例給 Service 插件提供了用於處理 Webpack 構建相關邏輯的方法,好比 configWebpack
,經過這個方法咱們能夠在 Service 插件裏獲取和修改 Webpack 配置,好比在上期咱們寫的 Service 插件示例裏,就用這個方法獲取了網站的入口文件名。
最後,把 PluginAPI
實例做爲入參來調用 Service 插件定義的 apply
函數,就正式開始了 Service 插件的執行。
Service
的使用:
Service
的實現:
PluginAPI
的實現:
感謝你閱讀到了這裏,以上即是《San CLI 的實現原理》的所有內容。若是你都看懂了,請收下個人膝蓋:
2021年,San-CLI還會有持續的開發和優化,好比eject功能、CLI和Service要不要分離,想了解後續的更新,能夠關注San CLI的GitHub,歡迎star、歡迎issues、歡迎pr。
地址:https://github.com/ecomfe/san-cli
原文連接:https://mp.weixin.qq.com/s/-yhs_86CAMnsCxIYwrmMeQ
百度架構師
百度官方技術公衆號上線啦!
技術乾貨 · 行業資訊 · 線上沙龍 · 行業大會
招聘信息 · 內推信息 · 技術書籍 · 百度周邊
歡迎各位同窗關注!