webpack 之 代碼拆分

對於大型的app,把所有代碼放入一個文件是比較低效的,特別是一些代碼只有在某些情況下才需要載入。

Webpack 可以把你的代碼拆分到「chunks」裡面去,從而讓你的代碼可以按需載入。有些打包器把這種代碼層 叫 「層」,「歸納集」,或者叫「片段」。這種處理代碼的功能就叫「code splitting 代碼拆分」

這是一種可選的功能,你可以在代碼裡面定義你的的拆分點。Webpack 會處理好依賴,輸出以及運行時。

澄清一個公認的誤解:代碼拆分不僅僅是將公用代碼提取到可共享的模塊裡面,更重要的是它能被用於拆分一些按需載入的模塊。這樣就可以保證初始文件載入變小,在應用需要的時候在載入需要的模塊。

如何定義拆分點

AMD 和 CommonJs 有不同指定的方法去做按需載入,都支持並且和扮演拆分點的角色

CommonJs: require.ensure

require.ensure(dependencies, callback)n

require.ensure 方法確保在每個dependencies中的依賴都能在callback調用時被非同步載入。callback函數以require 作為參數執行。

例子:

require.ensure(["module-a", "module-b"], function(require) {n var a = require("module-a");n // ...n});n

注意: require.ensure 只載入modules, 但不執行.

AMD: require

AMD 規範定義的非同步 require 方法如下:

require(dependencies, callback)n

當被調用時,所有dependencies將被載入,並且callback將被調用參數為載入的依賴的exports。

例子:

require(["module-a", "module-b"], function(a, b) {n // ...n});n

注1: AMD require 載入且執行. 在webpack里 modules 從左到右 執行.

注2: 回調函數是可以省略的.

ES6 Modules

Webpack 不支持 es6 modules, 直接使用 require.ensure 或者 require 取決於你是那種模塊化規範.

Webpack 1.x.x ( 2.0.0快來了!) 沒有原生支持或兼容es6 Modules.

但是,你可以通過使用一種轉換器比如Babel,來將ES6 import 語法轉換成CommonJs 或者 AMD modules從而解決這個問題。這種方法是有效的但是在動態載入的時候有一個很重要的警告。

模塊添加語法(import x from foo) 故意設計成靜態可分析的,也就意味著你不能做動態載入。

// INVALID!!!!!!!!!n[lodash, backbone].forEach(name => import name )n

幸運的是,已經有一個 JS API 『loader』

Luckily, there is a JavaScript API 「loader」 specification being written to handle the dynamic use case:System.load (or System.import). This API will be the native equivalent to the above requirevariations. However, most transpilers do not support converting System.load calls torequire.ensure so you have to do that directly if you want to make use of dynamic code splitting.

//static importsnimport _ from lodashnn// dynamic importsnrequire.ensure([], function(require) {n let contacts = require(./contacts)n})n

模塊內容

在拆分點的所有依賴進入到一個新的模塊,依賴也被遞歸的添加進去。

如果你的拆分點代碼傳入了一個回調函數,webpack也會將回調裡面的依賴自動的駕到chunk上的。

Chunk 優化

如果兩個chunks包涵相同的modules,他們將會被合併到一個,這會導致chunks有多個父級依賴。

如果一個modoule在所有的chunk父級可用,它將從chunk中被移除。

如果一個chunk包涵別的chunk的所有modules,這個chunk將被保存,並最終出現多個chunks

Chunk 載入

根據設置項target運行環境會在bundle裡面加上chunk的載入邏輯。

比如說target選項設置為web,目標chunks將通過jsonp來載入。一個chunk之載入一次並且並行的請求將被合併到一個。運行環境會檢查載入後的chunk是不是多個。

Chunk 類型

入口 chunk

一個入口chunk包涵了一個運行環境外加一堆modules。如果該chunk包含了module0,chunk將被執行。如果沒有,該chunk將等到載入到包含0module的chunk並執行它(只要發現有含有module0 的chunk都執行)

標準 chunk

標準的chunk不包含運行時環境,僅僅包含一堆的modules。它的結構取決於chunk的載入演算法。比如,對於jsonp這些模塊將被包裹在一個jsonp的回調函數裡面。另外標準chunk包含了一個它實現了的chunk ID 列表。

初始 chunk (non-entry)

一個初始的chunk是一個標準的chunk,不同的是它的優化優先順序比較高,因為它像入口文件一樣記入了載入時間裡面。這種類型通常出現在用CommonsChunkPlugin合併chunk裡面。

拆分 app 和 vendor code

拆分你的app為兩個文件,app.js和vendor.js,你可以 require公用的文件到vendor.js,然後將這個文件名傳入到CommonsChunkPlugin ,向下面這樣:

var webpack = require("webpack");nnmodule.exports = {n entry: {n app: "./app.js",n vendor: ["jquery", "underscore", ...],n },n output: {n filename: "bundle.js"n },n plugins: [n new webpack.optimize.CommonsChunkPlugin(/* chunkName= */"vendor", /* filename= */"vendor.bundle.js")n ]n};n

這樣將從app裡面移除所有的在vendor裡面的文件。bundle.js將之包含你的app代碼而沒有他的依賴,他們將被放入vendor.bundle.js裡面。

在你的HTML頁面載入vendor.bundle.js(在bundle.js之前)即可。

<script src="vendor.bundle.js"></script>n<script src="bundle.js"></script>n

多入口 chunks

在config裡面配置多入口chunks是可以實現的。入口的chunk包含了運行時環境,一個頁面有且僅有一個運行時環境(也可以有例外):

運行多入口點

使用CommonsChunkPlugin後,運行環境被移動到了commons chunk 里。他們的入口點在初始chunk裡面。然而只有一個初始chunk 和 多個入口chunk 能被載入,這表明在一個單頁裡面運行多個入口點是可行的。

例如:

var webpack = require("webpack");nmodule.exports = {n entry: { a: "./a", b: "./b" },n output: { filename: "[name].js" },n plugins: [ new webpack.optimize.CommonsChunkPlugin("init.js") ]n}n

<script src="init.js"></script>n<script src="a.js"></script>n<script src="b.js"></script>n

Commons chunk

CommonsChunkPlugin能把出現在多個入口chunk的 modules移動到一個行的入口chunk裡面(commons chunk)。運行時也同樣被移動到commons chunk裡面。這意味著老的入口chunk成為了一個初始chunk了。可以在plugins列表 看到有關配置說明。

優化

有一些優化的插件可以合併chunks,看看plugins列表 。

  • LimitChunkCountPlugin
  • MinChunkSizePlugin
  • AggressiveMergingPlugin

給chunks起個別名

require.ensure函數可以接受額外第三個參數,這個參數必須是一個字元串。如果兩個拆分點傳遞同樣的字元串將使用相同的chunk。

require.include

require.include(request)n

require.includewebpack特殊函數,目的時添加module到當前的chunk裡面,但是不會執行它。(聲明在bundle裡面將會被幹掉)

例子:

require.ensure(["./file"], function(require) {n require("./file2");n});nn// 等價於nnrequire.ensure([], function(require) {n require.include("./file");n require("./file2");n});n

如一個module在多個子chunk裡面時候require.include 會很有用,在父chunk裡面的require.include將include該module,並且在子chunk裡面的module的實例將不會出現。

示例

  • Simple
  • with bundle-loader
  • with context
  • with amd and context
  • with deduplication
  • named-chunks
  • multiple entry chunks
  • multiple commons chunks

看一個demo example-app. 可在 DevTools看一下網路請求.

可以瘋狂轉載,但是要安利一下我的博客,代碼拆分 - Damon

推薦閱讀:

阿里雲前端工程化方案dawn
重溫 Webpack, Babel 和 React
web front end Automation Tools
如果HTTP2普及了,Webpack、Rollup這種打包工具還有意義嗎?
webpack技術講解及入門

TAG:webpack | 前端架构 | 前端开发 |