這是一個新開的'實驗性'文章系列,如其名‘技術地圖’,這個系列計劃剖析一些前端開源項目,可能會探討這些項目的設計和組織、整理他們使用到技術棧。 首先拿vue-cli
小試牛刀,再決定後續要不要繼續這個系列.javascript
我一直在思考咱們編程主要在作什麼?咱們有一大部分工做就是選擇各類工具/庫/框架,來黏合業務. 工具和場景越匹配、原理了解越多,運用越嫺熟,咱們效率可能就越高. 這種說法頗有爭議,就像程序=算法+數據結構
不能徹底表達現今的軟件工程同樣, 說咱們的工做就是堆砌工具,黏合業務, 必定程度上有自貶的意思。 但這確實是大部分程序員的真實寫照。css
這系列文章其實有點相似於 github 上面的Awesome
項目. 這些 Awesome 項目就是一個生態展覽館, 裏面項目琳琅滿目. 由於數量太多了,並且缺乏評分機制,大部分狀況咱們不可能一個個去查看,很難從中選擇符合需求的項目(固然你帶着明確的目的,且目標範圍很是小,可能比較有用)。html
是否能夠嘗試換個角度,選取一些有趣的開源項目,看看它是怎麼應用這些工具的, 有序的羅列出來? 對於有相同場景的項目, 參考或者模仿價值可能會更大一些. 這些開源項目就是巨人,站在巨人肩膀上顯然省事多了前端
只是技術棧羅列未免過於簡單,筆者還但願從這些項目中學點東西,好比他的設計和項目組織. 我會嘗試簡化和通俗解釋裏面的關鍵知識或亮點, 可是不求甚解。爲了不陷入細節泥潭,我會盡可能使用圖形化方式展現他們程序流程,避免拘泥於細節。你也能夠把這些文章做爲深刻閱讀這些項目源碼的引導vue
2019.10.23 技術地圖系列失敗java
我也但願讀者同我交流反饋,共同窗習和進步。node
說到 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. 這種模式有利於項目模塊組織
這個設計是借鑑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: 負責項目的實際構建
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
複製代碼
webpack-chain
和webpack-merge
來實現 webpack 可配置化一個簡單的插件結構是這樣子的:
這個 preset 和 babel 的 preset 概念其實是不同的:
vue-cli 的 preset 一個腳手架建立方案, 也就是說它只做用於vue create
階段。好比vue create
時默認使用的就是 babel+eslint preset. preset 能夠簡化項目腳手架的建立。團隊能夠共享一個 preset 來建立腳手架。
而 babel 中的 preset 是一個插件集合,他能夠統一收納和管理一組插件方案. 例如babel-preset-react
、 babel-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 開發服務器.