[譯]使用webpack構建發布就緒(production-ready)的實時的SaaS

原文: A production-ready realtime SaaS with webpack
作者: Matt Krick
翻譯: 黃祺(pinqy520)

譯者說:

最近在看meatier,某個meteor的替代方案,解決了meteor的幾個問題:技術方案陳舊(畢竟三年了)、編譯擴展不方便、學習成本高等等。它直接采用現在流行技術,并且使用webpack進行打包,降低了編譯擴展這塊的學習成本。

雖然這篇文章是去年年底的,但是關于webpack配置這塊依然值得學習(也可能會很快過時吧,哈哈)。

最后,第一次進行翻譯,如有錯誤歡迎指出。


我是Meteor的忠實粉絲,它讓一切都socket化(socketize)[1],編譯(transpile)樣式,然后為server生成一些代碼。然而有時候,可能需要更靈活一點。

像標準的SaaS[2],我需要一個可以從CDN獲取的小跳轉頁,一個提供實時websocket連接的門戶首頁,很容易在橫向和縱向進行擴展

最終結果就是Meatier[3],我將其放到github上了:http://github.com/mattkrick/meatier,他解決了很多問題:

  1. JWT(JSON Web Tokens)[4]
  2. sockets
  3. 使用redux做client-side緩存
  4. 將socket狀態存于redux的state中
  5. 優化和實時數據庫更新

但是在本文重心在于webpack,因為有100篇webpack 101指導,卻并沒有一篇webpack 201(講的都不深入?)。

在本項目中,使用了webpack 2 Beta,雖然還有一些bug,但是并不常見

構建production的webpack設置

我假設你已經設置好了一個開發用的webpack配置,如果你不知道如何創建一個開發配置......

嘿嘿嘿

使用路由來拆分你的頁面

第一步先處理路由,然后讓你的同步組件異步處理。參考這個例子

export default store => {
  return {
    onEnter: requireNoAuth(store),
    path: 'signup',
    getComponent: async (location, cb) => {
      let component = await System.import('./Signup');
      cb(null, component)
    }
  }
}

這里的路由并不是指的jsx,而是一個在redux store中的函數(注:類似于一個reducer專門用來處理路由),用來更好的對ruduer進行代碼拆分(更多信息在下篇博客當中),重點在于System.import中,它將創建一個promise來傳輸這個模塊,那么在webpack 2中,它將會變成一個獨立的可以被動態加載的模塊[5]

為client編寫production-ready的webpack配置

好,現在webpack知道如何最好的來分割你的代碼了,這能節約流量。然而,有時候可能不會這么完美。比如說:在某種情況下,節約額外的5kb流量,并不比增加一次額外的http請求收益更高。所以我們要使用AggressiveMergingPlugin,它能平衡你的請求大小比例。還有MinChunkSizePlugin插件,可以用來設置閾值(例如50000)來限制一些小的chunks(注:代碼塊,可以理解為模塊)。

接下來是優化用戶第二次訪問的體驗(如果用戶會第二次訪問你的網站?),我們需要盡可能多的利用用戶的瀏覽器緩存,只傳輸少量的流量(意味著更少的錢和更快的速度)。我們首先拆分vendor包,因為有可能你不想BUG產生的次數和你更新React的次數一樣多。

然后是進行地址更新,當客戶端讀取一個資源的時候,在、瀏覽器看到一個文件名(URL地址),如果它能夠解析到一個本地緩存,那瀏覽器就不會發送請求。也就是說:假設你的文件叫app.js,你更新了這個文件之后,瀏覽器并不會重新下載它,除非你關閉了緩存。為了解決這個問題,當文件改變時,將會給每個文件分配一個hash值(放到文件名里)。

output: {
  filename: '[name]_[chunkhash].js',
  chunkFilename: '[name]_[chunkhash].js',
  ...
},

現在,我們不能在HTML中靜態的請求一個app.js(為了解決上面那個問題),我們需要請求一個在每次build之后文件名都會變化的文件,可以用AssetsPlugin來創建一個查詢表,來存儲資源和hash后的名稱。

new AssetsPlugin({
  path: path.join(root, 'build'), 
  filename: 'assets.json'
}),

然后,在生成HTML之前,我們就需要assets.json,給src賦值assets.app.jsassetsrequire('assets.json')assets.app.js是hash后的app.js的地址,同理assets.vendor.jsassets.manifest.js也一樣)。

https://github.com/mattkrick/meatier/blob/master/src/server/Html.js

現在問題來了,如果一個chunk改變了,那也會改變其他chunk的hash值(因為webpack按照文件名進行獲取不同的模塊,那么一個文件的hash值變了,其他文件因為引用的模塊路徑有變化,也會變化),導致你其他的chunk也會刷新(被瀏覽器重新下載)。為了避免這種問題,NamedModulesPlugin能夠將webpack模塊編號,替換成一個實際的路徑。只需要在沒有參數的情況下調用它,在下一次編譯中,所有的chunk都會由區分開的hash值。一般情況下,你不會想要客戶端知道你的實際路徑,這就是為啥我們要使用HashedModuleIdsPlugin,它會增加一些額外的流量,但是能讓人安心。

現在,唯一會被刷新的chunk只有你改變過代碼的那個,并且會帶有webpack runtime(webpack自帶的一些代碼)。但是,這個webpack runtime已經被打包進了上一個公共的chunk中,可能叫vendor.js。圍繞這一點,我們需要抽取出webpack runtime,因此相比于要刷新兩個文件,可以只更新一個你想要的文件。

new webpack.optimize.CommonsChunkPlugin({
  names: ['vendor', 'manifest'],
  minChunks: Infinity
}),

因為現在最新的公共chunk一直都是有webpack runtime的,它將會在CommonsChunkPlugin中被提取出來。需要注意的是,它并沒有相應的入口chunk,并且minChunks被設置成無限的,所以沒有東西會被加進去。

為了得到HTML的內容,我們需要創建另外一個HTTP請求,但是額外的請求會造成額外的浪費,所以,我們將其嵌入其中。我們將從assets.json中獲取路徑,然后將其中的內容讀取成字符串(打印到HTML模板)。

https://github.com/mattkrick/meatier/blob/master/src/server/createSSR.js

const assets = require('../../build/assets.json');
const readFile = promisify(fs.readFile);
assets.manifest.text = await readFile(path.join(root, 'build', path.basename(assets.manifest.js)), 'utf-8');

為server編寫production-ready的webpack配置

最后一步比較麻煩,對于CSS文件你有四種選擇:

  1. 將css提取成樣式表(<link />),
  2. 將你組建中的樣式寫成inline-style的
  3. 將css提取到一系列style標簽中
  4. 混合方案

我個人比較喜歡提取成樣式表,以消耗額外的HTTP請求為代價(但是它加載的很快,不需要javascript,并且可以對樣式進行瀏覽器緩存)。最理想的情況下,每個stylesheet就是一個chunk。但是現在在服務端渲染中還不可能(作者說:如果他錯了就告訴他),同樣的css可能會被重復多次,將會被壓縮到很小,所以就讓我們優化一個大的樣式表吧。

現在有很多hacky的解決方案去解決這個:node并不知道如何需要require一個CSS文件的問題。如下幾種:

  • 在server端忽略CSS require
  • 在每個組件中加入process.env.BROWSER環境變量
  • 將他們探測出來,放置在webpack的stats.json文件中,然后在服務端將它們插入進去。
  • 給每個組件一個能被父級訪問的this.styles,然后一路被父級訪問,直到創建了一個style標簽。
  • 將styles放到組建的上下文中

然而,我并不喜歡上面任何一個。重寫組件只為了能夠使用服務端渲染,是一個錯誤的想法。因此,我決定在server端生成一個路由的webpack build,因為webpack知道如何處理css文件(node不知道)。它(webpack)能編譯整個路由,并且用它在server端渲染每個頁面。這很容易,將你的target設置為node,并且output.libraryTarget: "commonjs2"

https://github.com/mattkrick/meatier/blob/master/webpack/webpack.config.server.js

接下來用ExtractTextPlugin創建你的css文件。然而在webpack 1 和 2 中,出現了一些問題:為了將所有樣式打包成一個文件,我設置allChunkstrue,結果還是生成了多個CSS文件。為了解決這個問題,我使用了LimitChunkCountPlugin,將chunk限制設置為1。結果就是,你的網站會使用一個可以被server用來渲染HTML的預處理好的路由,和一個包含網站所有樣式的CSS文件。

為了加快服務端啟動的時間,假設你在服務端使用了babel。你可以用如下選項排除調這個預渲染好的bundle

require('babel-register')({
  only(filename) {
    return (filename.indexOf('build') === -1 && filename.indexOf('node_modules') === -1);
  }
});

結語

好了,現在你知道如何來便寫一個production ready的webpack配置文件了,它將在服務端渲染中工作,并不需要在客戶端中允許javascript。你不需要重寫你的組件來處理樣式,并且,它在開發中依然很快。無論何時,你在開發server端的時候,你都可以運行你的production構建(build),并且你不需要在每次重啟后,重新編譯你的client端(bundle)。


  1. 譯者注: 『一切編程都是Socket』Socket起源于Unix,而Unix基本哲學之一就是“一切皆文件”,都可以用“打開open –> 讀寫write/read –> 關閉close”模式來操作。Socket就是該模式的一個實現。所以使用javascript同構變成的方式,把網絡請求都變成類似與讀寫的操作,通過websocket將前后端數據同步。 ?

  2. 譯者注:可以理解成一個web app ?

  3. 譯者注:作者寫的一個類似于meteor的webpack構建saas的模板。 ?

  4. 譯者注:https://jwt.io/ ?

  5. 譯者注:在webpack2中,遵循ES6的代碼拆分方式,可以使用System.import方法,在運行時動態加載es6模塊。webpack把System.import作為拆分點,然后把請求的模塊放入一個單獨的『塊』(chunk)中。 ?

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

推薦閱讀更多精彩內容

  • webpack 介紹 webpack 是什么 為什么引入新的打包工具 webpack 核心思想 webpack 安...
    yxsGert閱讀 6,488評論 2 71
  • 構建工具逐漸成為前端工程必備的工具,Grunt、Gulp、Fis、Webpack等等,譯者有幸使用過Fis、Gul...
    陳堅生閱讀 6,040評論 4 64
  • 在現在的前端開發中,前后端分離、模塊化開發、版本控制、文件合并與壓縮、mock數據等等一些原本后端的思想開始...
    Charlot閱讀 5,464評論 1 32
  • react+redux+webpack+babel+npm+shell+git這方面的內容我會隨時更新,更新內容放...
    liangklfang閱讀 664評論 0 1
  • 她一直都是那個她,跟所有人一樣那么的平凡。 出生于平凡的家庭,家庭完整。家里不貧苦也不富裕,條件足夠她順順利利的上...
    非夢非語閱讀 207評論 0 0