Webpack 最佳實踐總結(二)

上一篇介紹了 Webpack 優化項目的四種技巧,分別是經過 UglifyJS 插件實現對 JavaScript 文件的壓縮,css-loader 提供的壓縮功能,配置NODE_ENV能夠進一步去掉無用代碼,tree-shaking幫助找到更多無用代碼javascript

這一篇主要講 Webpack 的改進緩存(hash)、切割代碼css

使用 hash

開發過程常常須要一邊預覽代碼運行結果一邊修改代碼,這個時候文件版本控制就顯得尤其重要。默認作法是告訴瀏覽器這個文件的緩存時間,而後當文件內容被修改,則須要重命名該文件告訴瀏覽器須要從新下載和緩存,例如:html

<!-- Before the change -->
<script src="./index.js?version=15">

<!-- After the change -->
<script src="./index.js?version=16">

Webpack 也能作相似的工做。可是不是採用上面的版本方式去控制文件,它會計算文件的hash值去標識打包後文件。每次你修改了代碼,文件名也跟着變化,這樣瀏覽器就會加載新的新文件,再也不使用緩存過的文件,例如:java

// webpack.config.js
module.exports = {
  entry: './index.js',
  output: {
    filename: 'bundle.[chunkhash].js'
       // → bundle.8e0d62a03.js
  }
};

更多關於 [hash], [chunkhash], [name], [id] 和 [query] 能夠查看:here,這裏列一個表方便查看:node

Template Description
[hash] module 的 hash 標識
[chunkhash] chunk 的 hash 標識
[name] module 的名稱
[id] module 的惟一標識
[query] module 的查詢(文件名帶有?)

如今還剩下的問題是如何將會變化的文件傳輸到客戶端,這裏列出兩種解決方法,分別是:HtmlWebpackPluginWebpackManifestPluginreact

HtmlWebpackPlugin 是一個更自動化的解決方法,在編譯打包的期間,它會生成包含打包資源的 HTML 文件。若是你的業務邏輯相對簡單的狀況下,這個插件是絕對夠用的:webpack

<!-- index.html -->
<!DOCTYPE html>
<!-- ... -->
<script src="bundle.8e0d62a03.js"></script>

WebpackManifestPlugin 相比HtmlWebpackPlugin是一個更靈活的解決方案,尤爲是面對複雜的服務端的部分。它能生成JSON文件,包含文件名以及與其對應(映射)的 hash 過的文件名git

{
  "bundle.js": "bundle.8e0d62a03.js"
}

提示:Webpack的 hash 函數是不穩定的,這意味着在不一樣開發環境等條件下下,即使是同一個文件,webpack 依然會計算出不一樣的 hash 去標識這個相同的文件。正由於此,若是你有跨平臺開發的需求,可使用webpack-chunk-hash去代替webpack的原生的hash函數算法。更多關於 webpack hash 的問題能夠查看:heregithub

切割代碼

想象一下你要開發一個大型網站,有首頁和文章頁,文章頁裏有文章內容和評論系統,可是你要將網站正常工做的代碼都打包到一個文件裏,這顯然是不科學的。每次你修改其中一個模塊,整改打包文件都要從新編譯從新打包和生成,這意味着你僅僅只是修改一下評論模塊,但當用戶只訪問首頁,他們依然會下載這些暫時無用的代碼,從而影響首頁訪問的加載速度web

這個時候就須要切割打包文件,把它切割爲首頁和文章頁所需的兩個打包文件,當用用戶訪問首頁時,只加載首頁的打包文件,當訪問文章頁的時候,只加載文章頁的打包文件,配置以下:

module.exports = {
  // 設置多個入口點,webpack給每一個入口文件生成對應的打包文件
  // 從而實現不一樣頁面加載不一樣的打包文件
  entry: {
    homepage: './index.js',
    article: './article.js'
  },
  output: {
    // [name]對應的是入口點的 name,如 homepage、article
    filename: '[name].[chunkhash].js'
  },
  plugins: [
    // 生成打包資源列表 json 文件
    new WebpackManifestPlugin(),
    // 取代 webpack 原生的 hash 函數
    new WebpackChunkHash(),
    // 生成依賴包的塊文件,轉移全部的`node_modules`依賴到一個特別的該文件中
    // 這容許你更新你的代碼時,無需更新依賴
    new webpack.optimize.CommonsChunkPlugin({
      name: 'vendor',
      minChunks: m => m.context &&
        m.context.includes('node_modules'),
    }),
    // 生成 `webpack’s runtime` 自身的代碼文件
    // 這容許你更新你的代碼時,無需更新其餘無關代碼
    new webpack.optimize.CommonsChunkPlugin({
      name: 'runtime',
      chunks: ['vendor'],
      minChunks: Infinity,
    }),
    // 標識每一個模塊 hash 值,當你添加新的模塊時,若是該模塊的依賴影響到別的模塊
    // 就能夠更新這些受影響的模塊從而區分舊的模塊
    new webpack.HashedModuleIdsPlugin(),
    // 生成資源映射文件,包含文件名以及與其對應的hash過的文件名,用於其餘插件或者服務
    new ChunkManifestPlugin({
      filename: 'chunk-manifest.json',
      manifestVariable: 'webpackManifest'
    })
  ]
};

上面這個配置將會生成6個文件:

// 兩個打包入口文文件,當你修改了業務代碼,它們也會跟着變化
homepage.a68cd93e1a43281ecaf0.js
article.d07a1a5e55dbd86d572b.js

// 通用依賴文件與 webpack’s runtime 的文件,前者當你依賴包變化也跟着變化,後者極少變化,除非使用的 webpack 版本這類狀況變化了纔會跟着變化
vendor.1ebfd76d9dbc95deaed0.js
runtime.d41d8cd98f00b204e980.js

// 兩個 manifest 文件,用於其餘插件或服務,如 DllReferencePlugin
manifest.json
chunk-manifest.json

按需切割代碼

除了按照不一樣頁面的切割爲不一樣的入口文件外的切割代碼外,還能夠按照構成頁面的組件的順序作到按需加載的去切割代碼

想象一下,有一個頁面佈局方式以下所所示:

+-------------------------------+
| logo                    menu  |
+-------+----------------+------+
|       |                |      |
| left  |                | right|
| bar   |                | bar  |
|       |    article     |      |
|       |    content     |      |
|       |                |      |
|       |                |      |
|       +----------------+      |
|       |                |      |
|       |    comments    |      |
|       |                |      |
+-------+----------------+------+
|           copyright           |
+-------------------------------+

當訪問這個頁面時,用戶但願首先能閱讀到文章的內容,諸如其餘評論、側邊欄等其餘頁面組成部分是能夠被延後查看的。惋惜的是,若是你將這些頁面組成部分都打包到一個文件裏,用戶就須要等到整改打包文件加載後才能訪問到他想要訪問的內容

如何解決頁面中各部分的按需加載?webpack 容許你作這方面的優化去實現代碼的按需加載。首先你須要本身識別哪些代碼是要首先加載的,而後 webpack 會移動須要延遲加載的代碼到單獨的塊中,只有在當須要這些被延遲加載的代碼時,纔會下載

假設有一個article-page.js業務代碼文件,當你打包加載其會所有加載,包括文章內容、評論和側邊欄,以下:

// article-page.js
import { renderArticle } from './components/article';
import { renderComments } from './components/comments';
import { renderSidebar } from './components/sidebar';

renderArticle();
renderComments();
renderSidebar();

作到按需加載則須要你將靜態的import修改成動態的import(),webpack 會自帶轉移這些代碼到單獨的塊中,只有當被須要時纔會加載,以下:

// article-page.js
import { renderArticle } from './components/article';
renderArticle();

import('./comments.js')
  .then((module) => { module.renderComments(); });
import('./sidebar.js')
  .then((module) => { module.renderSidebar(); });

將靜態的import修改成動態的import()的操做會帶來提高首次訪問時加載性能,同事也會優化緩存,當你更改這些業務代碼時,只會修改對應的塊文件,從而不影響其餘塊文件

固然,還須要從新設置 output ,以下:

// webpack.config.js
module.exports = {
  output: {
    filename: '[name].[chunkhash].js',
    chunkFilename: '[name].[chunkhash].js',
  }
};

output.chunkFilename能夠標識按需加載的塊文件

提示:當你使用默認的 presets 的 babel 去編譯這些代碼,你會獲得一個語法錯誤提示:Babel don’t understand import() out of the box. 要避免這個錯誤,你須要給babel安裝syntax-dynamic-import插件

externals

在一個大型項目中,若是有兩段業務代碼有共同的依賴,經過 webpack 的externals,你在兩段代碼間能夠共享這些依賴,以下:

// webpack.config.js
module.exports = {
  externals: {
    'react': 'React',
    'react-dom': 'ReactDOM',
  }
};

上面的作法是將這些框架或庫的對象掛靠在全局對象中,而後經過另一個對象存儲對象名以及映射到對應模塊名的變量,webpack 就會替換全部全部模塊下的相關的引用,而後你須要手動引入這些被externals的框架或庫到網站入口文件中,如HtmlWebpackPlugin定義的template文件

總結

此次介紹如何經過 webpack 克服瀏覽器緩存打包文件,不去更新新的打包文件的問題,以及講解了從不一樣的頁面的角度去切割代碼和從不一樣的頁面組成部分去切割代碼的過程,還有經過externals去分離去共有的框架和庫,從而實現對這些框架或庫的CDN資源加載

內容較多,大概就這樣~

文章首發於:https://www.linpx.com/p/webpa...
歡迎訪問個人博客:https://www.linpx.com

相關文章
相關標籤/搜索