webpack多頁應用架構系列(十四):No復制粘貼!多項目共用基礎設施

本文首發于Array_Huang的技術博客——實用至上,非經作者同意,請勿轉載。
原文地址:https://segmentfault.com/a/1190000007301770
如果您對本系列文章感興趣,歡迎關注訂閱這里:https://segmentfault.com/blog/array_huang

前言

本文介紹如何在多項目間共用同一套基礎設施,又或是某種層次的框架

基礎設施是什么?

一個完整的網站,不可能只包含一個jQuery,或是某個MVVM框架,其中必定包含了許多解決方案,例如:如何上傳?如何兼容IE?如何跨域?如何使用本地存儲?如何做用戶信息反饋?又或者具體到如何選擇日期?等等等等……這里面必定包含了UI框架、JS框架、各種小工具庫,不論是第三方的還是自己團隊研發的。而以上所述的種種,就構成了一套完整的解決方案,也稱基礎設施

基礎設施有個重要的特征,那就是與業務邏輯無關,不論是OA還是CMS又或是CRM,只要整體產品形態類似,我們就可以使用同一套基礎設施。

框架

框架這個概念很泛,泛得讓人心生困惑,但抽象出來說,框架就是一套定義代碼在哪里寫、怎么寫的規則。不能說我們要怎么去框架,反倒是框架控制我們怎么去代碼。

本系列前面的十來篇文章,分開來看是不同的,但如果所有文章合起來,并連同示例項目(Array-Huang/webpack-seed),實際上闡述的就是一套完整的多頁應用框架(或稱架構)。這套框架規定了整個應用的方方面面,舉幾個例子:

  • 每個頁面的文件放在哪個目錄?
  • 頁面的HTML、入口文件、css、圖片等等應該怎么放?
  • 編碼規范(由ESLint來保證)。

當然,這只是我的框架,我希望你們可以看懂了,然后根據自己的需求來調整,變成你們的框架。甚至說,我自己在做不同類型的項目時,整體架構也都會有不少的變化。

為什么要共用基礎設施/框架/架構?

緣起

數月前,我找同事要了一個他自己寫的地區選擇器,拉回來一看遍地都是ESLint的報錯(他負責的項目沒有用ESLint,比較隨意),我這人有強迫癥的怎么看得過眼,卷起袖子就開始改,改好也就正常使用了。過了一段時間,來了新需求,同事在他那改好了地區選擇器又發了一份給我,我一看頭都大了,又是滿地報錯,這不是又要我再改一遍嗎?當時我就懵了,只好按著他的思路,對我的版本做了修改。從此,也確立了我們公司會有兩份外觀功能都一致,但是實現卻不一樣的地區選擇器。

很坑爹是吧?

多項目共享架構變動

上面說的是組件級的,下面我們來說架構級別的。

我在公司主要負責的項目有兩個,在我的不懈努力下,已經做到跟我的腳手架項目Array-Huang/webpack-seed大體上同構了。但維持同構顯然是要付出代價的,我在腳手架項目試驗過的改進,小至改個目錄路徑,大至引入個plugin啊loader啊什么的,都要分別在公司的兩個項目里各做一遍,超煩噠(嫌棄臉

試想只是兩個項目就已經這樣了,如果是三個、四個,甚至六個、七個呢?堪憂啊堪憂啊!

快速創建新項目

不知道你們有沒有這樣子的經驗:接到新項目時,靈機一動“這不就是我的XX項目嗎?”,然后趕緊搬出XX項目的源碼,然后刪掉業務邏輯,保留可復用的基礎設施。

也許你會說,這不已經比從零開始要好多了嗎?總體上來說,是吧,但還不夠好:

  • 你需要花時間重溫整個項目的架構,搞清楚哪些要刪、哪些要留。
  • 畢竟是快刀斬亂麻,清理好的架構比不上原先的思路那么清晰。
  • 清理完代碼想著跑跑看,結果一大堆報錯,一個一個來調煩的要命,而且還很可能是刪錯了什么了不得的東西,還要去原先額項目里搬回來。

以上這些問題,你每創建一個新項目都要經歷一遍,我問你怕了沒有。

腳手架不是可以幫助快速創建新項目嗎?

是的沒錯,腳手架本身就算是一整套基礎設施了,但依然有下列問題:

  • 維護一套腳手架你知道有多麻煩嗎?公司項目一忙起來,加班都做不完,哪顧得上腳手架啊。最后新建項目的時候發現腳手架已經落后N多了,你到底是用呢還是不用呢?
  • 甭跟我提Github上開源的腳手架,像我這么有個性的人,會直接用那些妖艷賤貨嗎?
  • 不同類型的項目技術選型不一樣,比如說:需不需要兼容低版本IE;是web版的還是Hybrid App的;是前臺還是后臺。每一套技術選型就是一套腳手架,難道你要維護這么多套腳手架嗎?

上述問題,通過共用基礎設施,都能解決

  • 既然共用了基礎設施,要怎么改肯定都是所有項目一起共享的了,不論是組件層面的還是架構本身。
  • 假設你每個不同類型的項目都已經準備好了與其它項目共用基礎設施,那么,你根本不需要花費多余的維護成本,創建新項目的時候看準了跟之前哪個項目是屬于同一類型的,湊一腳就行了唄,輕松。

怎么實現多項目共用一套基礎設施呢?

示例項目

在之前的文章里,我使用的一直都是Array-Huang/webpack-seed這個腳手架項目作為示例,而為了實踐多項目共用基礎設施,我對該項目的架構做了較大幅度的調整,升級為2.0.0版本。為免大家看前面的文章時發現示例項目貨不對板,感到困惑,我新開了一個repo來存放調整后的腳手架:Array-Huang/webpack-seed-v2(https://github.com/Array-Huang/webpack-seed-v2),并且,我在兩個項目的README里我都注明了相應的內容,大家可不要混淆了哈。

下面就以從Array-Huang/webpack-seedArray-Huang/webpack-seed-v2的改造過程來介紹如何實現多項目共用基礎設施。

改造思路

改造思路其實很簡單,就是把預想中多個項目都能用得上的部分從現有項目里抽離出來

如何抽離

抽離的說法是針對原項目的,如果單純從文件系統的角度來說,只不過是移動了某些文件和目錄。

移動到哪里了呢?自然是移動到與項目目錄同級的地方,這樣就方便多個項目引用這個核心了。

如果你跟我一樣,在原項目中定義了大量路徑和alias的話,移動這些文件/目錄就只是個改變量的活了:

選自webpack-seed/webpack-config/base/dir-vars.config.js

var path = require('path');
var moduleExports = {};

// 源文件目錄
moduleExports.staticRootDir = path.resolve(__dirname, '../../'); // 項目根目錄
moduleExports.srcRootDir = path.resolve(moduleExports.staticRootDir, './src'); // 項目業務代碼根目錄
moduleExports.vendorDir = path.resolve(moduleExports.staticRootDir, './vendor'); // 存放所有不能用npm管理的第三方庫
moduleExports.dllDir = path.resolve(moduleExports.srcRootDir, './dll'); // 存放由各種不常改變的js/css打包而來的dll
moduleExports.pagesDir = path.resolve(moduleExports.srcRootDir, './pages'); // 存放各個頁面獨有的部分,如入口文件、只有該頁面使用到的css、模板文件等
moduleExports.publicDir = path.resolve(moduleExports.srcRootDir, './public-resource'); // 存放各個頁面使用到的公共資源
moduleExports.logicDir = path.resolve(moduleExports.publicDir, './logic'); // 存放公用的業務邏輯
moduleExports.libsDir = path.resolve(moduleExports.publicDir, './libs');  // 與業務邏輯無關的庫都可以放到這里
moduleExports.configDir = path.resolve(moduleExports.publicDir, './config'); // 存放各種配置文件
moduleExports.componentsDir = path.resolve(moduleExports.publicDir, './components'); // 存放組件,可以是純HTML,也可以包含js/css/image等,看自己需要
moduleExports.layoutDir = path.resolve(moduleExports.publicDir, './layout'); // 存放UI布局,組織各個組件拼起來,因應需要可以有不同的布局套路

// 生成文件目錄
moduleExports.buildDir = path.resolve(moduleExports.staticRootDir, './build'); // 存放編譯后生成的所有代碼、資源(圖片、字體等,雖然只是簡單的從源目錄遷移過來)

module.exports = moduleExports;

選自webpack-seed/webpack-config/resolve.config.js

var path = require('path');
var dirVars = require('./base/dir-vars.config.js');
module.exports = {
  // 模塊別名的配置,為了使用方便,一般來說所有模塊都是要配置一下別名的
  alias: {
    /* 各種目錄 */
    iconfontDir: path.resolve(dirVars.publicDir, 'iconfont/'),
    configDir: dirVars.configDir,

    /* vendor */
    /* bootstrap 相關 */
    metisMenu: path.resolve(dirVars.vendorDir, 'metisMenu/'),

    /* libs */
    withoutJqueryModule: path.resolve(dirVars.libsDir, 'without-jquery.module'),
    routerModule: path.resolve(dirVars.libsDir, 'router.module'),

    libs: path.resolve(dirVars.libsDir, 'libs.module'),

    /* less */
    lessDir: path.resolve(dirVars.publicDir, 'less'),

    /* components */

    /* layout */
    layout: path.resolve(dirVars.layoutDir, 'layout/html'),
    'layout-without-nav': path.resolve(dirVars.layoutDir, 'layout-without-nav/html'),

    /* logic */
    cm: path.resolve(dirVars.logicDir, 'common.module'),
    cp: path.resolve(dirVars.logicDir, 'common.page'),

    /* config */
    configModule: path.resolve(dirVars.configDir, 'common.config'),
    bootstrapConfig: path.resolve(dirVars.configDir, 'bootstrap.config'),
  },

  // 當require的模塊找不到時,嘗試添加這些后綴后進行尋找
  extentions: ['', 'js'],
};

抽離對象

抽離的方法很簡單,那么關鍵就看到底是哪些部分可以抽離、需要抽離了,這一點看我抽離后的成果就比較清晰了:

先來看根目錄:

├─ core # 抽離出來的基礎設施,或稱“核心”
├─ example-admin-1 # 示例項目1,被抽離后剩下的
├─ example-admin-2 # 示例項目2,嗯,簡單起見,直接復制了example-admin-1,不過還是要做一點調整的,比如說配置
├─ npm-scripts # 沒想到npm-scripts也能公用吧?
├─ vendor # 無法在npm上找到的第三方庫
├─ .eslintrc # ESLint的配置文件
├─ package.json # 所有的npm庫依賴建議都寫到這里,不建議寫到具體項目的package.json里

再來看看core目錄

├─ _webpack.dev.config.js # 整理好公用的開發環境webpack配置,以備繼承
├─ _webpack.product.config.js # 整理好公用的生產環境webpack配置,以備繼承
├─ webpack-dll.config.js # 用來編譯Dll文件用的webpack配置文件
├─ manifest.json # Dll文件的資源目錄
├─ package.json # 沒有什么實質內容,我這里就放了個編譯Dll用的npm script
├─components # 各種UI組件
│  ├─footer
│  ├─header
│  ├─side-menu
│  └─top-nav
├─config # 公共配置,有些是提供給具體項目的配置來繼承的,有些本身就有用(比如說“核心”部分本身需要的配置)
├─dll # 之前的文章里就說過,我建議把各種第三方庫(包括npm庫也包括非npm庫)都打包成Dll來加速webpack編譯過程,這部分明顯就屬于基礎設施了
├─iconfont # 字體圖標能不能公用,這點我也是比較猶豫的,看項目實際需要吧,不折騰的話還是推薦公用
├─layout # 布局,既然是同類型項目,布局肯定是基本一樣的
│  ├─layout
│  └─layout-without-nav
├─less # 樣式基礎,在我這項目里就是針對bootstrap的SB-Admin主題做了修改
│  ├─base-dir
│  └─components-dir
├─libs # 自己團隊研發的一些公共的方法/庫,又或是針對第三方庫的適配器(比如說對alert庫封裝一層,后面要更換庫的時候就方便了)
├─npm-scripts # 與根目錄下的npm-scripts目錄不一樣,這里的不是用來公用的,而是“核心”使用到的script,比如我在這里就放了編譯dll的npm script
└─webpack-config # 公用的webpack配置,尤其是關系到“核心”部分的配置,比如說各第三方庫的alias。這里的配置是用來給具體項目來繼承的,老實說我現在繼承的方法也比較復雜,回頭看看有沒有更簡單的方法。
    ├─base
    ├─inherit
    └─vendor

最后總結一下,是哪些資源被抽離出來了:

  • webpack配置中屬于架構的部分,比如說各種loader、plugin、“核心”部分的alias。
  • “核心”部分所需的配置,比如我這項目里為了定制bootstrap而建的配置。
  • 各種與UI相關的資源,比如UI框架/樣式、UI組件、字體圖標。
  • 第三方庫,以Dll文件的形式存在。
  • 自研庫/適配器。

結構圖

上傳上來以后發現圖被壓小了,請到這里看原圖

Array-Huang-webpack-seed-v2 結構圖
Array-Huang-webpack-seed-v2 結構圖

附系列文章目錄(同步更新)

本文首發于Array_Huang的技術博客——實用至上,非經作者同意,請勿轉載。
原文地址:https://segmentfault.com/a/1190000007301770
如果您對本系列文章感興趣,歡迎關注訂閱這里:https://segmentfault.com/blog/array_huang

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,505評論 6 533
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,556評論 3 418
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,463評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,009評論 1 312
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,778評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,218評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,281評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,436評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,969評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,795評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,993評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,537評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,229評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,659評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,917評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,687評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,990評論 2 374

推薦閱讀更多精彩內容