隨着公司前端項目的增多, 你們會發現各個項目中不少資源都是是大同小異的,這就引起了一個話題,如何跨項目共用前端資源, 這裏的資源泛指前端涉及到的全部靜態資源, 常見的有 HTML/CSS/JS/圖片等等.css
所謂共用前端資源, 就是將公共的前端資源提取出來, 例如公共樣式/公共邏輯/公共組件/公共圖片資源等等, 讓多個項目來引用, 避免複製多份, 避免重複開發, 統一管理和維護。只要更新公共資源,其餘引用的項目就能夠同步更新,提高開發效率,下降開發成本。html
藍圖是宏偉的,但要實現藍圖又是艱辛的, 要實現藍圖, 咱們首先要認清現實存在的一些障礙.前端
- 提取公共資源: 如何模塊化? 模塊化的粒度控制?
- 存放/管理公共資源: 公共資源應該統一放置在哪裏? 如何管理依賴?
- 公共資源的發佈更新: 如何發佈一個公共資源, 又若是更新版本?
- 公共資源的多版本共存: 如何代表我須要公共資源的哪一個版本?
- 使用(引用)公共資源: 如何在項目中使用公共資源, 還要考慮到打包優化的問題?
前端資源是由多種不一樣種類的資源組成的, 並且依賴關係錯綜複雜, 涉及到不一樣的資源路徑, 引用方式是個頭痛的問題, 一直是形成前端資源不方便共用的一大阻礙. 並且前端早些時候連依賴包管理器(例如 Java 的 Maven)的概念都沒有, 全部的依賴都是手工的複製粘帖, 更新的時候再覆蓋一次. 新項目開始的時候, 又是複製粘帖一遍.node
你還記得你複製過多少份 jquery.js 嗎? 你還弄得清各個項目所用的 jQuery 版本嗎? 若是全項目須要統一升級一下 jQuery 的版本, 你該怎麼作? 公共資源以複製多份的方式來共用, 勢必變得難以維護, 最終頗有可能形成公共資源存在各類版本不統一.jquery
JAVA程序員是否還記得當年 Java 沒有 Maven 來管理項目的時候, 各類 jar 依賴在多個項目中複製粘帖, 是否是深有感觸啊?webpack
爲了不復制粘帖帶來的痛苦, 統一維護公共資源, 在先不考慮優化的狀況下, 咱們能夠將公共資源細粒度發佈到 CDN 上.ios
那麼各個項目中, 咱們就能夠這樣使用了git
<!-- 公共組件的 CSS -->
<link rel="stylesheet" href="//cdn.com/lib/component/component.css">
<!-- 公共靜態資源: 例如整個公司級須要共用的資源, 常見的就是公司 LOGO 了 -->
<img src="//cdn.com/lib/logo.png">
<!-- 公共組件的 JS -->
<script src="//cdn.com/lib/component/component.js"></script>
複製代碼
可是, 若是公共資源更新了呢? 因爲涉及到瀏覽器緩存的問題, 你須要將各個項目引用到公共資源的地方所有搜索出來, 而後一個個修改引用資源的時間戳, 強制讓瀏覽器緩存失效.程序員
另外別忘了, 公共資源中若是引用到其餘資源的也須要如此操做一番, 例如 CSS 中引用到的圖片若是更新了的話以下:github
<link rel="stylesheet" href="//cdn.com/lib/component/component.css?v1">
<img src="//cdn.com/lib/logo.png?v2">
<script src="//cdn.com/lib/component/component.js?v3"></script>
複製代碼
可是, 若是公共資源須要多版本共存呢? 咱們可使用版本文件夾來區分, 引用的公共資源更新時, 就須要修改下版本號:
<link rel="stylesheet" href="//cdn.com/lib/component/1.1.0/component.css">
<img src="//cdn.com/lib/logo/1.2.0/logo.png">
<script src="//cdn.com/lib/component/1.1.0/component.js"></script>
複製代碼
但具體哪一個項目使用了哪一個版本, 仍是得全局搜索. 並且公共資源版本更新以後, 如何通知各個項目來更新呢? 難道是靠大聲呼喊嗎?
可是, 若是 CDN 域名要更換呢? 又得全局搜索一遍, 這種全局搜索的方式, 是痛苦而原始的, 項目很少還好處理, 一旦項目愈來愈多, 管理起來就會痛苦不堪. 你可能想到一些變通的方式, 來統一管理公共資源的引用, 例如藉助後端配置管理系統之類的東西, 將這些引用都配置起來.
可是, 若是引用的公共資源太多, 想優化請求的數量, 作資源的合併呢? 這無法經過全局搜索的方式來解決了吧. 起初爲了便於公共資源的按需使用, 咱們細粒度規劃了公共資源, 勢必會增長頁面的請求數量, 要想作優化, 可使用combo 機制 在服務端作資源的動態合併.
開發階段咱們能夠細粒度的引用公共資源
<link rel="stylesheet" href="//cdn.com/lib/component1/1.0.0/component1.css">
<link rel="stylesheet" href="//cdn.com/lib/component2/1.1.0/component2.css">
<link rel="stylesheet" href="//cdn.com/lib/component3/1.2.0/component3.css">
<link rel="stylesheet" href="//cdn.com/lib/component4/1.3.0/component4.css">
<img src="//cdn.com/lib/logo/1.0.0/logo.png">
<script src="//cdn.com/lib/component1/1.0.0/component1.js"></script>
<script src="//cdn.com/lib/component2/1.1.0/component2.js"></script>
<script src="//cdn.com/lib/component3/1.2.0/component3.js"></script>
<script src="//cdn.com/lib/component4/1.3.0/component4.js"></script>
複製代碼
上線階段咱們可使用 combo 機制合併資源減小請求的數量, 以優化前端性能
<link rel="stylesheet" href="//cdn.com/combo?lib/component1/1.0.0/component1.css,lib/component2/1.1.0/component2.css,lib/component3/1.2.0/component3.css,lib/component4/1.3.0/component4.css">
<img src="//cdn.com/lib/logo/1.0.0/logo.png">
<script src="//cdn.com/combo?lib/component1/1.0.0/component1.js,lib/component2/1.1.0/component2.js,lib/component3/1.2.0/component3.js,lib/component4/1.3.0/component4.js"></script>
複製代碼
這麼長的 URL, 就像老太婆的裹腳布又臭又長, 維護起來那是至關辣眼睛啊.
並且你們有沒有注意到, 公共組件的引用方式, 也有點麻煩, 是分兩步來走的, 先引用 CSS, 再引用 JS, 這個 CSS 明顯屬於組件的依賴. 那麼問題來了, 我使用一個組件, 爲何還要關注它的依賴呢? 若是組件的 CSS 或者組件的 JS 再有別的依賴呢? 是否是也須要使用者本身來處理呢? 可見前端資源的引用方式, 最好可以自動處理依賴關係的, 使用者只須要關注到他想用的資源(組件)這一層便可.
要想簡化前端資源的引用方式, 就必須處理好前端資源的依賴關係, 就讓咱們來好好地理一理前端資源的各類依賴關係吧.
首先頁面是全部資源引用的入口, 即依賴的起點(源頭), 依賴的種類主要分爲兩種
index.html -- 頁面入口, 全部依賴的起點
├── <link rel="stylesheet" href="index.css"> -- CSS 依賴(可能又依賴其餘 CSS 和其餘靜態資源)
| |── @import url(a.css); -- 引用資源的相對路徑是相對於當前 CSS 的路徑
| | |── .a {background: url(res/a.png)} -- 依賴的資源又會牽涉出依賴的依賴
| |── .index {background: url(res/index.png)}
|
├── <img src="res/foo.png"> -- 靜態資源依賴
|
├── <script src="index.js"></script> -- JS 預覽(可能(動態)依賴其餘 CSS/JS 和其餘靜態資源)
| |── img.src = 'res/bar.png'; -- 引用資源的相對路徑是相對於當前 HTML 的路徑
| |── link.href = 'b.css';
| | |── ...
| |── js.src = 'a.js';
| | |── ...
複製代碼
這麼多依賴關係, 該如何處理呢? 手工處理確定不現實, 咱們須要一個 module bundler(模塊打包器) 來幫助咱們分析並打包這些依賴.
面對這些重重的阻礙和困擾,要如何實現前端資源共用這藍圖呢?在資深技術總監龍總的啓發下,借鑑國內大牛的思路.......
基於上面的分析結果, 要想共用前端資源, 必須先將公用的前端資源以包的形式統一地管理起來, 造成一個公共倉庫, 在項目中聲明依賴的前端資源包, 經過工具來下載依賴的具體資源文件.
開發時使用模塊打包器分析出具體的依賴文件, 打包出項目依賴的全部前端資源.
所以咱們須要一個包管理器和模塊打包器, 便可實現求之不得地共用前端資源.
npm is the package manager for JavaScript
manage dependencies in your projects
管理項目級別的依賴, 必要時搭建一套npm 私服
webpack is a module bundler for modern JavaScript applications
it recursively builds a dependency graph that includes every module your application needs, then packages all of those modules into a small number of bundles - often only one - to be loaded by the browser.
構建工具分析並打包依賴, 如今你能夠放心大膽地修改和刪除項目歷史遺留的靜態資源文件了
接下來, 共用前端資源的關鍵就是將公共的模塊發佈到 npm 裏面, 而後就是在各個項目聲明依賴實際使用了.
實際使用的方式就落在了: 如何經過 webpack 在 HTML/CSS/JS 文件中引用 npm 模塊(即 node_modules 文件夾), 或者是 npm 模塊中的文件
<!-- 引用 npm 模塊 ionicons 中的文件 -->
<img src="~ionicons/dist/svg/ios-sunny.svg" width="50" height="50">
複製代碼
/* 導入 npm 模塊: normalize.css, 會自動去找 main 文件 */
@import url(~normalize.css);
.test-npm-res {
/* 引用 npm 模塊 ionicons 中的文件 */
background-image: url(~ionicons/dist/svg/ios-partly-sunny-outline.svg);
}
複製代碼
// 導入 npm 模塊 ionicons 中的文件
import svg from 'ionicons/dist/svg/ios-sunny-outline.svg';
複製代碼
咱們只須要關注使用的模塊, 再也不須要關注模塊的依賴了
將公共靜態資源在 npm 上發佈爲一個 company-common-res
的模塊包
company-common-res/
├── src/
| |── logo.psd
| └── ...
|
├── dist/
| |── logo.png
| └── ...
|
└── package.json
複製代碼
在項目的 package.json
中聲明依賴這個模塊
"dependencies": {
"company-common-res": "^1.0.0",
}
複製代碼
而後就是在 HTML/CSS/JS 文件中引用這個靜態資源了, 以 HTML 文件中爲例
<img src="https://user-gold-cdn.xitu.io/2017/12/21/1607716e35b27686" width="50" height="50">
複製代碼
將公共的 CSS 基礎樣式在 npm 上發佈爲一個 company-component-base
的模塊包, 在項目的 package.json
中聲明依賴這個 模塊
company-component-base
模塊的 package.json
應該聲明 main
爲組件的 CSS 文件
"main": "company-component-base.css"
複製代碼
在項目中使用這個組件, 以 CSS 文件中爲例
@import url(~company-component-base);
複製代碼
將公共的 CSS 組件在 npm 上發佈爲一個 company-component-button
的模塊包
@import url(~company-component-button);
複製代碼
將公共的 JS 工具方法在 npm 上發佈爲一個 company-util
的模塊包, 在項目的 package.json
中聲明依賴這個模塊
import util from 'company-util';
util.log('test');
複製代碼
將 toast 組件在 npm 上發佈爲一個 company-component-toast
的模塊包, 在項目的 package.json
中聲明依賴這個模塊
import Toast from 'company-component-toast';
new Toast({
content: '提示內容'
}).show();
複製代碼
基於前端資源共用的障礙
- 多種不一樣種類的資源
- 資源的依賴關係複雜(HTML依賴/CSS依賴/JS依賴/圖片依賴/組件依賴)
- 沒有包管理器來統一維護項目的依賴
- 如何引用依賴以及打包
要實現前端資源共用, 咱們須要一個包管理器和模塊打包器, 以解決上面的這些問題.
接下來的重點就是, 如何造成一個組件的平臺了, 讓你們能夠方便的知道有哪些組件, 以及如何使用這些組件
- 規範化的包名: 例如組件包名都以
component
開頭- 規範化的項目目錄結構
- 組件文檔和可展現的使用示例