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

本文首發於 Array_Huang的技術博客—— 實用至上,非經做者贊成,請勿轉載。
原文地址: http://www.javashuo.com/article/p-xwrwoszw-bz.html
若是您對本系列文章感興趣,歡迎關注訂閱這裏: https://segmentfault.com/blog/array_huang

前言

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

基礎設施是什麼?

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

基礎設施有個重要的特徵,那就是與業務邏輯無關,不管是OA仍是CMS又或是CRM,只要總體產品形態相似,咱們就可使用同一套基礎設施。html

框架

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

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

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

固然,這只是個人框架,我但願大家能夠看懂了,而後根據本身的需求來調整,變成大家的框架。甚至說,我本身在作不一樣類型的項目時,總體架構也都會有很多的變化。jquery

爲何要共用基礎設施/框架/架構?

緣起

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

很坑爹是吧?git

多項目共享架構變更

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

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

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

快速建立新項目

不知道大家有沒有這樣子的經驗:接到新項目時,靈機一動「這不就是個人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的技術博客—— 實用至上,非經做者贊成,請勿轉載。
原文地址: http://www.javashuo.com/article/p-xwrwoszw-bz.html
若是您對本系列文章感興趣,歡迎關注訂閱這裏: https://segmentfault.com/blog/array_huang
相關文章
相關標籤/搜索