淺談 vue-cli 擴展性和插件設計

這是一個新開的'實驗性'文章系列,如其名‘技術地圖’,這個系列計劃剖析一些前端開源項目,可能會探討這些項目的設計和組織、整理他們使用到技術棧。 首先拿vue-cli小試牛刀,再決定後續要不要繼續這個系列.javascript


我一直在思考咱們編程主要在作什麼?咱們有一大部分工做就是選擇各類工具/庫/框架,來黏合業務. 工具和場景越匹配、原理了解越多,運用越嫺熟,咱們效率可能就越高. 這種說法頗有爭議,就像程序=算法+數據結構不能徹底表達現今的軟件工程同樣, 說咱們的工做就是堆砌工具,黏合業務, 必定程度上有自貶的意思。 但這確實是大部分程序員的真實寫照。css

這系列文章其實有點相似於 github 上面的Awesome項目. 這些 Awesome 項目就是一個生態展覽館, 裏面項目琳琅滿目. 由於數量太多了,並且缺乏評分機制,大部分狀況咱們不可能一個個去查看,很難從中選擇符合需求的項目(固然你帶着明確的目的,且目標範圍很是小,可能比較有用)。html

是否能夠嘗試換個角度,選取一些有趣的開源項目,看看它是怎麼應用這些工具的, 有序的羅列出來? 對於有相同場景的項目, 參考或者模仿價值可能會更大一些. 這些開源項目就是巨人,站在巨人肩膀上顯然省事多了前端

只是技術棧羅列未免過於簡單,筆者還但願從這些項目中學點東西,好比他的設計和項目組織. 我會嘗試簡化和通俗解釋裏面的關鍵知識或亮點, 可是不求甚解。爲了不陷入細節泥潭,我會盡可能使用圖形化方式展現他們程序流程,避免拘泥於細節。你也能夠把這些文章做爲深刻閱讀這些項目源碼的引導vue

2019.10.23 技術地圖系列失敗java

我也但願讀者同我交流反饋,共同窗習和進步。node




vue-cli

說到 CLI, 不得不提Rails框架,它多是框架提供 CLI 的先祖(具體歷史沒有深刻考究). Rails 有一個重要的指導思想,即約定大於配置, 它爲 Web 應用的大多數需求都提供了最好的解決方法,而且默認使用這些約定,而不是在長長的配置文件中設置每一個細節react

CLI 也是這個指導思想下的產物, 例如經過它提供的 CLI,能夠在15 分鐘內構建一個簡易的博客, 能夠經過 CLI 啓動服務器和 REPL、生成項目腳手架、生成代碼文件、路由、數據庫遷移等等:webpack

Rails 的不少設計在那個年代就是就是一個明星(閃瞎 PHP、JSP、 ASP..., 想一想要配置各類服務器,各類 xml 文件),它的不少設計模式深入影響了後面的 web 框架,好比 Django、Laravel, 甚至不少模仿 Rails 命名的,如 Sails、Grails.git

Rails 對於前端開發影響也很深遠,好比在 Nodejs 出來以前,Rails 社區就開始使用 coffeescript + sass預編譯語言進行前端開發了, Asset Pipeline能夠說是最先的'前端工程化', 配合Turbolink可讓傳統後端渲染頁面擁有不亞於單頁應用的用戶體驗...

當初 Rails 給我帶來的各類震撼還歷歷在目, Ruby China 社區也是國內最好社區之一. 可是目前 Rails 的關注度不如從前, 在前端社區像 Rails 這種集大成的框架也早已不吃香(參考 Ember, 某種程度上 Angular 也算吧?).

說實在話若是一輩子只學一門語言,我會選 Ruby,若是選一個 web 框架,那就是 Rails。

推薦你們閱讀The Rails Doctrine - Rails 信條 這篇文章裏面有一句話筆者很是喜歡: "只要放下了自負的我的喜愛,即可以跳過無謂的世俗決定,專一在最重要的地方下更快的決定。"。爲人寫程序,而不是爲了機器寫程序.

約定大於配置能夠減小咱們作決定的數量,減小無謂的爭論和考慮,讓咱們能夠專一於更重要的事情. 這個原則能夠提升開發和團隊協做效率, 甚至能夠凝聚一個社區.

以 Webpack 爲例,噁心複雜的配置被人詬病,因此才須要 vue-cli 或者 create-react-app 這些工具.

沒有用 Ruby/Rails 工做過, 默默寫了個 Ruby China 小程序(微信搜Ruby CN),算是感恩回饋社區吧


Ok, 忍不住吹了一波 Rails, 回到正題.

筆者是使用 React 做爲主力開發的,Vue 也是我很是喜歡的一個開源項目,不說別的,在開發者的'用戶體驗'方面 Vue 是我見過最好之一,主要體如今 API 的簡潔性和易用性、文檔還有項目構建工具(今天的主角).

vue-cli-ui 是我想寫這系列文章的動機之一. 前陣子用了一下vue-cli-ui, 感受很不錯, 支持可視化配置和任務運行,比我在終端下一個項目一個項目跑 task 清爽多了. 很想在咱們自家的構建工具上也搞一套,怎搞? 學習它的源碼, 我以爲能夠做爲博客記錄下來.

如今前端工程師也有‘webpack 配置工程師’的戲稱,這能說明 webpack 配置是費時費力的苦事(Angular 例外). 這不後來就有了parcel宣稱零配置的輪子, 還有 React 社區的create-react-app, vue-cli 前期是基於模板的建立項目, 不算此列。

後來 vue-cli 汲取着前者的不少優勢,把這塊作大作優了(看來 vue 很擅長作這些事情). 咱們能夠來對比一下這些工具:


Vue CLI create-react-app parcel
快速原型開發 支持 - 支持
全局模式 零配置原型開發就是全局的 - 支持
插件 支持 - 支持,擴展文件類型和文件輸出
擴展性 強,經過插件擴展 wepack 配置 弱, 強約定, 沒法配置 webpack,能夠 eject, 而後手工配置;支持 babel-macro;(嚴格說能夠經過react-app-rewired進行擴展) 中(能夠配置 babel,postcss,Typescript); 提供了 Node API; 支持插件擴展文件類型
多頁面 支持 - 支持
適用範圍 Vue 組件的第一公民。經過擴展能夠支持任意前端框架 針對 React 開發,不支持其餘框架 parcel 是一個通用的打包工具,它的競爭對手是 webpack
編譯速度 cache-loader,thread-loader 來加速 JS 和 TS 編譯 babel-loader 開啓了 cache 編譯速度號稱是 webpack 的兩倍
可升級性 支持升級 cli-service, 插件須要單獨升級, 插件須要遵循語義化版本. 太多插件存在升級風險 支持升級 react-script, 官方維護,且強約定基本能夠保障向下兼容 支持升級 parcel-bundler
UI 圖形化管理是 CLI 的特點之一 - -

經過上面的對比,能夠看出 vue-cli 是一個擴展性很是強的構建工具,以至於它不只限於 Vue,也能夠用來構建 React 甚至其餘前端框架

相比而言 create-react-app 就是一個很是 Opinionated(堅持己見) 的工具,強約定. 一個典型的例子就是它不內置開啓 babel 裝飾器轉譯,CRA 團隊認爲已經廢棄(或者不成熟)的語言特性不該該帶到 CRA 中; 後面爲了給‘優雅’地給 babel 擴展插件,就搗鼓出來了babel-macro, 這是一種'免配置'的 babel 插件規範.

這種強約定也是有好處的,好比不須要管理配置; 並且 CRA 團隊謹慎可靠地維護着 CRA,這使得開發者能夠通常無痛地升級 CRA. 若是要擴展 webpack,通常只有 eject,這就走回了手動配置 webpack 的老路, 不可取.

vue-cli 也是一個'漸進式'的 cli,vue-cli 提供了默認的 preset,但不阻止你對其進行擴展. vue-cli 的擴展接口也很是簡潔(合理, 很少很多), 還有 UI 管理界面,可視化管理項目的配置和插件,用戶體驗很棒,計劃在下一篇文章介紹 vue ui. 惟一比較不舒服的是若是濫用這種擴展性,裝 N 多插件,並且插件之間還存在依賴關係時,也會成爲升級維護的負擔.




基本設計

注意,本文不是 vue-cli 的教程,最好的教程是官方文檔.


目錄結構

下面是 vue-cli 的基本目錄結構. 大部分大型的前端項目都使用 lerna 實現 mono-repo 模式, 而後統一分發到 npm. 這種模式有利於項目模塊組織


分離 CLI 層和 Service 層

這個設計是借鑑create-react-app的, CLI 層只是一些基礎的命令通常不須要頻繁升級,並且是全局安裝; 而 Service 層是多變的, 做爲項目的局部依賴,不該該硬編碼在 CLI 裏面. CLI 和 Service 的職責劃分以下:


  • CLI: 用於項目建立和管理

    • 全局安裝
    • vue create 建立項目腳手架. 拉取最新的 Service,並選擇配置須要的插件
    • vue ui. 啓動 UI 管理界面
    • 快速原型開發: vue serve | vue build, 直接伺服和編譯一個 Vue 文件
    • 插件管理: vue add | vue invoke 安裝插件和調用插件生成器
  • Service: 負責項目的實際構建

    • 局部安裝
    • 集成 webpack 構建環境,Service 自己只有一個插件機制, 全部構建相關邏輯都由內置插件和外部插件提供
    • 內置插件(命令): serve, build, inspect


插件系統

vue-cli 提供了相似 babel、eslint 的插件機制。


插件

插件機制是 vue-cli 的核心, 用於擴展 Service. Service 的命令和 webpack 配置都由插件提供.

其實插件機制自己並無什麼技術難度, 換句話說插件其實就是一個協議的設計. vue-cli 插件的協議以下:

  • 命名: @vue/cli-plugin-*vue-cli-plugin-*. package.json 中按着這個命名約定的依賴會被識別爲 vue-cli 插件,另外命名約定也有利於在 github 或 npm 上篩選

  • 生命週期: 一個插件的生命週期能夠分爲安裝階段運行階段. vue create命令建立項目腳手架、vue add以及vue invoke插件安裝命令都屬於安裝階段; 而 cli-service 命令執行時屬於運行階段.

  • 基本結構: 區分了生命週期後,插件的結構就比較清晰了:

    .
    ├── README.md
    ├── generator.js  # generator (可選)
    ├── prompts.js    # prompt 文件 (可選)
    ├── index.js      # service 插件
    └── package.json
    複製代碼
    • 安裝階段:
      • prompts: 收集用戶意見和配置
      • gernerator: 在安裝階段生成模板文件
    • 運行時: index.js
      • 注入 service 命令
      • 擴展和修改 webpack 配置. vue-cli 經過webpack-chainwebpack-merge來實現 webpack 可配置化

一個簡單的插件結構是這樣子的:


preset

這個 preset 和 babel 的 preset 概念其實是不同的:

vue-cli 的 preset 一個腳手架建立方案, 也就是說它只做用於vue create階段。好比vue create時默認使用的就是 babel+eslint preset. preset 能夠簡化項目腳手架的建立。團隊能夠共享一個 preset 來建立腳手架

而 babel 中的 preset 是一個插件集合,他能夠統一收納和管理一組插件方案. 例如babel-preset-reactbabel-preset-env. 上文說到若是擴展性被濫用,裝 N 多插件,並且插件之間還存在依賴關係時,也會成爲升級維護的負擔. 而 'babel 式'的 preset 可讓插件更方便維護和和一鍵式升級

儘管目前 vue 也提供了vue upgrade對插件進行升級,這個是基於語義化版本約定的, 且當插件之間存在依賴關係時, 不排除升級存在風險. 尤爲對於團隊項目仍是推薦有統一地管理這些插件, 實現傻瓜化的升級。 實際上這種 'babel 式'的 preset 是能夠經過 vue-plugin 實現和轉發的。


配置

vue 支持在 package.json 的 vue 字段或vue.config.js中進行配置。這裏能夠對 Service 核心功能和插件進行配置, 也能夠直接修改 webpack 配置. 另外部分構建行爲是經過環境變量進行影響的,這些能夠經過.env.*文件進行配置


基本流程

如今來看看一個 vue-cli 內部的基本流程, Service 的插件實現是 vue-cli 比較有意思的點. 以vue serve爲例:

Service 對象是 vue-cli 的核心對象,負責管理和應用插件,全部命令和 webpack 配置都是以插件的形式存在:

首先劃分爲配置階段和運行階段。 配置階段 vue-cli 會加載配置文件,並查找和應用全部插件。將 PluginAPI 實例和項目配置傳遞給插件運行時, 插件運行時經過 PluginAPI 注入命令(registerCommand)和 擴展 webpack 配置(chainWebpack, configureWebpack).

運行階段則根據用戶傳入的命令名調用插件注入命令。在命令實現函數中,能夠調用 resolveWebpackConfig()來生成最終的 webpack 配置。以 serve 命令爲例,獲取到 webpackConfig 後會建立一個 webpack 編譯器,並開啓 webpack-dev-server 開發服務器.


技術地圖

  • 組織
  • cli 命令行相關工具
    • chalk: 命令行字體顏色樣式
    • cli-highlight: 終端語法高亮輸出, 相似於 Highlight.js
    • cliui: 在終端中進行多列輸出
    • didyoumean: 根據單詞類似度,來對用戶輸入糾正提示
    • semver: 提供語義化版本號相關的工具函數。 例如比較,規範化
    • commander TJ 寫的命令行選項和參數解析器,支持子命令,選項校驗和類型轉換,幫組信息生成等等. API 簡單優雅
    • minimist: 一個極簡的命令行參數解析器。若是隻是簡單的選項解析,能夠用這個庫
    • inquirer 命令行詢問
    • ora 命令行 spinner
    • launch-editor 打開編輯器. 經過 node 打開編輯器,前端能夠 express 暴露接口調用打開
    • open 打開 URL、文件、可執行文件
    • execa 更好的 child_process,修復了原生 exec 的一些問題
    • validate-npm-package-name: 驗證 npm 包名稱,好比建立的項目名是否合法
    • dotenv & dotenv-expand: 從.env 文件中加載配置,環境變量
  • 網絡相關
    • portfinder: 獲取可用的端口
    • address: 獲取當前主機的 ip,MAC 和 DNS 服務器
  • 文件處理相關
    • slash 一致化處理路徑中的分隔符
    • fs-extra node fs 模塊擴展
    • globby: glob 模式匹配
    • rimraf 跨平臺文件刪除命令
    • memfs 兼容 Node fs API 的內存文件系統
  • 數據檢驗
  • 調試
    • debug: 這是一個 debug 日誌利器, 支持經過環境變量或動態設置來肯定是否須要輸出; 支持 printf 風格格式化
  • 算法
    • hash-sum: 散列值計算
    • deepmerge 深合併
  • 其餘
    • recast Javascript 語法樹轉換器,支持非破壞性的格式化輸出. 經常使用於擴展 js 代碼
    • javascript-stringify: 相似於 JSON.stringify, 將對象字符串化。
  • webpack
    • 配置定義
      • webpack-merge: 合併 webpack 配置對象
      • webpack-chain: 鏈式配置 webpack. 這兩個庫是 vue-cli 插件的重要成員
    • webpack-dev-server: webpack 開發服務器,支持代碼熱重載,錯誤信息展現,接口代理等等
    • webpack-bundle-analyzer: webpack 包分析器
  • 擴展(一些相關的技術棧)
    • http-server 快速伺服靜態文件
    • plop 模板生成器
    • yeoman 項目腳手架工具
相關文章
相關標籤/搜索