Webpack 達人的成長之路

GitChat技術雜談發表於2017-11-29

文章推薦

Selenium 自動化測試從零實戰【原文連結】
原來這樣做,才能向架構師靠近【原文連結】
Cordova App 打包全揭祕【原文連結】
TensorFlow on Android:物體識別【原文連結】
TensorFlow on Android:訓練模式【原文連結】
圖解敏捷教練和 ScrumMaster【原文連結】
[運營專題]零預算引爆個人和企業品牌【原文連結】
一學就會的 WordPress 實戰課【原文連結】
快速學習 Spring Boot 技術棧【原文連結】

這裡寫圖片描述

作者介紹

覃亮,BAT 某廠前端開發工程師,CSDN 部落格專家,社群程式碼貢獻名高山上的魚。熱愛技術文章寫作與分享。

課程簡介

本課程包含從 Webpack 的基本概念和使用一直深入到核心的內容,如 Loader 和 Plugin 的書寫,以及 Compiler 和 Compilation 物件分析。同時也涵蓋了 HMR 的實現原理及 Tree-shaking,按需載入等高階知識點。通過本課程,將深入的瞭解和使用 Webpack,並能夠按照專案需求快速開發一個適合於自身專案的打包工具。

導讀:課程內容簡介

前端必不可少的腳手架

對於打包工具的熟悉程度漸漸地也已經成為衡量前端開發工程師水平的一個重要指標。記得在校招面試的時候就有問各種打包工具的問題,如對於 Gulp、Grunt、Webpack 的熟悉程度,各種打包工具的特點及優缺點等。而當我們逐漸融入到一個特定的團隊中,一般都有現成的腳手架提供給我們使用,而對於腳手架本身的關注程度也會慢慢降低。那是否就意味著,不需要掌握腳手架的相關知識了呢?其實不然,有以下幾個理由。

(1)任何腳手架都有一定的適用場景,但是同時也有邊界,如果不小心跨域了這個邊界,那麼很可能遇到意想不到的問題,如 bug。此時,如果對腳手架的原理有一定的瞭解,那麼也能夠更快的定位問題。

(2)任何一個腳手架都不可能是完美的,都會存在一個優化的階段,如果只是用它,而不去了解它、優化它,那麼本身就是一個追求完美的工程師不應該具有的態度。況且,對於工程師來說,只是會用而不知道其原理本身就是一個笑話。

課程內容

本課程是基於對 Webpack 有一定的瞭解,或者是想深入瞭解 Webpack 打包原理的讀者而寫的。如果只是想了解如何使用 Webpack,那麼網上的大部分資料已經足夠了。現在對本課程做一個概括,主要內容包含以下部分。

(1)Webpack 的核心概念

在本章節,首先會通過一個依賴圖譜的例子來展開,詳細的論述 Webpack 的 loader、plugin、entry、output 等核心概念。結合 Webpack 2 官網的說明以及日常開發實踐經驗進行深入的分析。會使用完整的例項讓讀者對 Webpack 核心概念有深入的理解,什麼是 chunk、common chunk、hotUpdated chunk、externals、libraryTarget、library 等疑問會在本章節給出答案。

(2)Webpack 基本使用

本章節從 Webpack 的基本使用出發,但是又不止於基本使用,會結合 7 個例項程式碼來深入的分析 Webpack 與 CommonChunkPlugin 結合後的打包實踐與原理。同時對於 CommonChunkPlugin 的各種配置都會使用具體的例項來深入講解。通過本章節的學習,不僅會使用 Webpack,而且還知道如何更好的使用 Webpack。

(3)webpack-dev-server 核心概念

本章節會深入分析 webpack-dev-server 相關的概念,如 Proxy 代理、HMR 原理、contentBase、publicPath、lazyload、filename 等諸多配置的詳細講解。通過深入的瞭解這部分內容,不僅可以瞭解優化的點,同時也能更好的解決真實專案開發中可能遇到的問題。

(4)webpack-dev-server 基本使用

本章節主要講解如何在專案中使用 webpack-dev-server,並深入的分析了 webpack-dev-server 的 iframe 模式與 inline 模式的區別。網上關於這兩者的區別大都來自於官網的翻譯,在本章節中會結合具體的例項來進行分析。

(5)Webpack 的 HMR 原理分析

在本章節中,不僅會講解 Webpack 實現 HMR 的原理,同時也會講解如何寫出支援 HMR 的程式碼,從而可以深入的瞭解 HMR。這其中會包含常見的函式:decline() 函式、accept() 函式、dispose() 函式、status() 函式、apply() 等函式進行分析,同時也會詳細地講解 Webpack 與 HMR 的相關配置資訊,以便在以後使用 Webpack 的時候得心應手。

(6)Webpack 中的 Compiler 和 Compilation 物件

Compilation 和 Compiler 物件是寫 Webpack 外掛的核心內容,在本章節中不僅會詳細講述兩者的作用以及如何在外掛中使用它們,同時也會講解在 Webpack 外掛書寫中經常使用到的方法或者屬性。通過本章節,不僅能瞭解什麼是模組、依賴模組、chunk、資源等,還能知道如何根據具體場景來使用這些資源。

(7)Webpack 常見外掛原理分析

在本章節中會將關注點放在 Webpack 兩個外掛的原理上,包括 CommonChunkPlugin 和 PrepackWebpackPlugin,通過這兩個外掛來加深對上面知識的理解,從而為下文寫一個 Webpack 外掛做鋪墊。

(8)寫一個 Webpack 外掛

Webpack 外掛是擴充套件 Webpack 基礎功能的主要渠道,在本章節中會講解如何寫一個 Webpack 外掛。

(9)寫一個 Webpack 的 loader

在本章節中會使用 Markdown 檔案處理 loader 來講解如何寫 Webpack 的 loader。

(10)Webpack 結合 react-router 實現按需載入

在上面的章節中,講到了如何使用 require.ensure 來動態產生獨立的 chunk 的問題,在本章節會使用 react-router 的例子來講解如何使用 Webpack 的這種特性。通過動態按需載入的特效能夠減少頁面首次載入的時長,配合單頁面應用絕對是頁面優化的首選。

(11)Webpack 2 的 Tree-shaking 深入分析

Tree-shaking 是 Webpack 2 引入的新特性,本章節會詳細描述如何使用 Tree-shaking 及其原理和適用範圍。本章節內容包含具體的例項,所以讀者一定能夠很好的瞭解這種新特性。

(12)以 Node 方式整合 Webpack 和 webpack-dev-server 打包

在本章節中將使用一個很好的例子來講解如何基於 Webpack、webpack-dev-server 來寫一個打包工具並適應具體的業務場景。通過本章節的內容,能很好的將上面章節的內容做一個串聯,同時也能更好的理解 Webpack。

寫給讀者

其實現在基於 Webpack 的打包工具都已經非常成熟,所以讀者可以隨意的在 Github 或者 npm 中找到需要的腳手架。但是,就像文章開頭所說,只有瞭解了 Webpack 的核心原理,才能在開發中做到得心應手。我見過很多同學,能夠正常的使用 Webpack,對很多 Webpack 的配置也瞭解,但是當遇到問題的時候往往不知所措。通過本系列課程,會讓讀者擺脫現狀,更好的理解 Webpack 原理,而不會知其然不知其所以然。

第01課:Webpack 核心概念

Webpack 的基礎作用與打包例項

Webpack 是一個最新的 JS 模組管理器。當 Webpack 處理應用的時候,它會遞迴建立一個依賴圖譜,這個圖譜會詳細的列出應用需要的每一個模組,最終將這些模組打包到幾個小的輸出檔案中。當然一般只是一個檔案(採用webpack-common-chunk外掛除外),最終這個檔案將會通過瀏覽器來載入。這裡的核心概念是圖譜,這裡在開始介紹 Webpack 的核心概念之前,先給出一個圖譜的例子,希望對理解 Webpack 有一定的幫助。

假如,有如下的 Webpack 配置:

var CommonsChunkPlugin = require("webpack/lib/optimize/CommonsChunkPlugin");
module.exports = {
    entry: {
        main: process.cwd()+'/example3/main.js',
        main1: process.cwd()+'/example3/main1.js',
        common1:["jquery"],
        common2:["vue"]
    },
    output: {
        path: process.cwd()+'/dest/example3',
        filename: '[name].js'
    },
    plugins: [
        new CommonsChunkPlugin({
            name: ["chunk",'common1','common2'],
            minChunks:2

        })
    ]
};

而且各個模組的依賴關係如下圖:

enter image description here

此時可以看一下上文說到的模組依賴圖譜:
這裡寫圖片描述

在這個圖譜中可以清楚地看到各個模組的依賴關係,如 main.js 和 main1.js 的模組 id 分別為3、2,而且它們的父級模組 id 為1,即 chunk.js,而 chunk.js 的父級模組 id 為 0,即 common1.js,最後就是 common1.js 的父級模組 id 為 4,即 common2.js。也就是說,如果使用了 webpack-common-chunk 外掛,那麼會產生多個輸出資原始檔,而且輸出資源的 name 是通過 output.filename 配置來指定的。不過有幾個注意點需要了解下。

webpack-common-chunk 抽取公共模組的邏輯

上面的例子入口檔案的配置如下:

entry: {
        main: process.cwd()+'/example3/main.js',
        main1: process.cwd()+'/example3/main1.js',
        common1:["jquery"],
        common2:["vue"]
    }

而且 output 的配置為:

 output: {
        path: process.cwd()+'/dest/example3',
        filename: '[name].js'
    },

此時,至少輸出 4 個檔案,分別為 main.js、main1.js、common1.js、common2.js,但是在 webpack 中又配置了 webpack-common-chunk 這個外掛:

new CommonsChunkPlugin({
            name: ["chunk",'common1','common2'],
            minChunks:2
            //這個配置表示,如果一個模組的依賴次數至少為 2 次才會被抽取到公共模組中
        })

這個外掛首先會將 main.js 和 main1.js 中出現兩次以上模組依賴次數的模組單獨提取出來,如上圖中的 chunk1.js 和 chunk2.js(在 main.js 和 main1.js 中都被引用了),將程式碼抽取到 chunk.js 中;然後將 chunk.js 中被依賴兩次以上的模組抽取到 common1.js 中;接著,繼續將 common1.js 中被依賴兩次以上的程式碼抽取到 common2.js 中。最後,將會發現:

main1.js 中只含有 jquery.js
main2.js 中只含有 vue.js
chunk.js 中含有 main1.js 和 main2.js 的公共模組,即 chunk1.js 和 chunk2.js 的內容
common1.js 中只含有 jQuery 程式碼,因為 chunk.js 中不含有依賴兩次以上的模組
common2.js 中只含有 vue.js 程式碼,因為 common1.js 中不含有依賴兩次以上的模組

模組載入順序

經過 webpack-common-chunk 處理後的程式碼有一點要注意,即抽取層級越高的程式碼應該越先載入。具體的含義可以通過上面的例子來說明下。比如,上面的 common-chunk-plugin 的配置如下:

new CommonsChunkPlugin({
            name: ["chunk",'common1','common2'],
            minChunks:2
            //這個配置表示,如果一個模組的依賴次數至少為 2 次才會被抽取到公共模組中
        })

這樣應該最先載入 common2.js,然後是 common1.js、chunk.js,最後才是 index.js。所以,我們經常可以看到下面的 html 模板:

<!DOCTYPE html>
<html>
<head>
    <title></title>
</head>
<body>
  <script type="text/javascript" src="common.js"></script>
  <script type="text/javascript" src="index.js"></script>
</body>
</html>

之所以是這樣,其實很好理解。原因之一在於:我們將下級模組公共的程式碼已經抽取到其他檔案中了,這樣,如果不預先載入公共模組而先載入其他模組,那麼就會出現模組找不到的報錯資訊。原因之二:假如你去看過我們打包後的 common2.js,會看到如下程式碼:

/******/ (function(modules) { // webpackBootstrap
/******/    // install a JSONP callback for chunk loading
/******/    var parentJsonpFunction = window["webpackJsonp"];
/******/    window["webpackJsonp"] = function webpackJsonpCallback(chunkIds, moreModules) {
/******/        // add "moreModules" to the modules object,
/******/        // then flag all "chunkIds" as loaded and fire callback
/******/        var moduleId, chunkId, i = 0, callbacks = [];
/******/        for(;i < chunkIds.length; i++) {
/******/            chunkId = chunkIds[i];
/******/            if(installedChunks[chunkId])
/******/                callbacks.push.apply(callbacks, installedChunks[chunkId]);
/******/            installedChunks[chunkId] = 0;
/******/        }
/******/        for(moduleId in moreModules) {
/******/            modules[moduleId] = moreModules[moduleId];
/******/        }
/******/        if(parentJsonpFunction) parentJsonpFunction(chunkIds, moreModules);
/******/        while(callbacks.length)
/******/            callbacks.shift().call(null, __webpack_require__);
/******/        if(moreModules[0]) {
/******/            installedModules[0] = 0;
/******/            return __webpack_require__(0);
/******/        }
/******/    };

/******/    // The module cache
/******/    var installedModules = {};

/******/    // object to store loaded and loading chunks
/******/    // "0" means "already loaded"
/******/    // Array means "loading", array contains callbacks
/******/    var installedChunks = {
/******/        1:0
/******/    };

/******/    // The require function
/******/    function __webpack_require__(moduleId) {

/******/        // Check if module is in cache
/******/        if(installedModules[moduleId])
/******/            return installedModules[moduleId].exports;

/******/        // Create a new module (and put it into the cache)
/******/        var module = installedModules[moduleId] = {
/******/            exports: {},
/******/            id: moduleId,
/******/            loaded: false
/******/        };

/******/        // Execute the module function
/******/        modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);

/******/        // Flag the module as loaded
/******/        module.loaded = true;

/******/        // Return the exports of the module
/******/        return module.exports;
/******/    }

/******/    // This file contains only the entry chunk.
/******/    // The chunk loading function for additional chunks
/******/    __webpack_require__.e = function requireEnsure(chunkId, callback) {
/******/        // "0" is the signal for "already loaded"
/******/        if(installedChunks[chunkId] === 0)
/******/            return callback.call(null, __webpack_require__);

/******/        // an array means "currently loading".
/******/        if(installedChunks[chunkId] !== undefined) {
/******/            installedChunks[chunkId].push(callback);
/******/        } else {
/******/            // start chunk loading
/******/            installedChunks[chunkId] = [callback];
/******/            var head = document.getElementsByTagName('head')[0];
/******/            var script = document.createElement('script');
/******/            script.type = 'text/javascript';
/******/            script.charset = 'utf-8';
/******/            script.async = true;
/******/            script.src = __webpack_require__.p + "" + chunkId + "." + ({"0":"common1","2":"main","3":"main1","4":"chunk"}[chunkId]||chunkId) + ".js";
/******/            head.appendChild(script);
/******/        }
/******/    };

/******/    // expose the modules object (__webpack_modules__)
/******/    __webpack_require__.m = modules;

/******/    // expose the module cache
/******/    __webpack_require__.c = installedModules;

/******/    // __webpack_public_path__
/******/    __webpack_require__.p = "";

/******/    // Load entry module and return exports
/******/    return __webpack_require__(0);
/******/ })

最頂級的輸出檔案中會包含 Webpack 載入其他模組的公共程式碼,可以理解為載入器,如果不先載入頂級的模組(上面的例子是 common2.js),那麼就無法通過它來載入其他的模組,因此也會報錯。這裡提到的例子程式碼可以 點選這裡 來仔細閱讀。

好了,上面講了一個複雜的例子,如果不懂也沒關係,在 Webpack 常見外掛部分中會更加詳細的分析。下面看看一些 Webpack 的基礎概念。

Webpack 的基礎概念

Webpack 的入口檔案

上面說過,Webpack 會構建一個模組的依賴圖譜,而構建這個圖譜的起點就是入口檔案。Webpack 相當於從這個起點來繪製類似於上面的整個圖譜。通過這個圖譜,可以知道哪些模組會被打包到最終的檔案中,而那些沒有出現在圖譜中的模組將會被忽略。在 Webpack 中使用 entry 這個配置引數來指定 Webpack 入口檔案,比如上面的例子:

 entry: {
        main: process.cwd()+'/example3/main.js',
        main1: process.cwd()+'/example3/main1.js',
        common1:["jquery"],
        common2:["vue"]
    }

可知,入口檔案總共是 4 個,那麼最終會構建出 4 個不同的檔案依賴圖譜。當然,這種配置常用於單頁面應用,一般只有一個入口檔案。這個配置可以允許我們使用 CommonChunkPlugin,然後將那些公共的模組抽取到 vendor.js(名字可以自定義)中。這種形式,通過上面的例子應該已經瞭解到了。

當然,上面這種配置依然可以應用於多頁面應用,比如下面的例子:

const config = {
  entry: {
    pageOne: './src/pageOne/index.js',
    pageTwo: './src/pageTwo/index.js',
    pageThree: './src/pageThree/index.js'
  }
};

我們告訴 Webpack 需要三個不同的依賴圖譜。因為在多頁面應用中,當訪問一個 URL 的時候,實際上需要的是一個 html 文件,只需要將打包後的資源插入到 html 文件中就可以了。比如上面的配置,需要分別讀取 pageOne、pageTwo、pageThree 下的 index.js 作為入口檔案,然後結合 html 模板打包一次,並將打包後的 html 以及輸出資源結合起來輸出到特定的檔案路徑下,這樣當訪問固定的 html 的時候就可以了。因此,Webpack 的單頁面打包和多頁面的打包其實原理是完全一致的。

Webpack 的 output 常見配置

output.path

當資源打包後,需要指定特定的輸出路徑,這就是 output 需要完成的事情。此時,可以通過 output 配置來完成,比如上面的例子:

entry: {
        main: process.cwd()+'/example3/main.js',
        main1: process.cwd()+'/example3/main1.js',
        common1:["jquery"],
        common2:["vue"]
    },
    output: {
        path: process.cwd()+'/dest/example3',
        filename: '[name].js'
    },

指定了輸出路徑為 process.cwd()+’/dest/example3’,相當於告訴 Webpack 所有的輸出檔案全部放到這個目錄下。

output.filename

上面的檔名稱指定為’[name].js’,其中[name]和 entry 中的 key 保持一致。其中 filename 的配置還是比較多的,不僅可以使用 name,還可以使用 id、hash、chunkhash 等。

[name]:這個模組的名稱,就是 entry 中指定的 key
[id]:這個模組的 id,由 Webpack 來分配,通過上面的依賴圖譜可以看到
[hash]:每次 Webpack 完成一個打包都會生成這個 hash
[chunkhash]Webpack 每生成一個檔案就叫一個 chunk,這個 chunk 本身的 hash

其中這裡的 hash 如果不懂,可以檢視 webpack-dev-server 的“深入原始碼分析”部分,這裡有詳細的論述。

publichPath 與 output.path

打包好的 bundle 在被請求的時候,其路徑是相對於你配置的 publicPath來說的。因為 publicPath 相當於虛擬路徑,其對映於你指定的 output.path。假如你指定的 publicPath 為 “/assets/”,而且 output.path 為”build”,那麼相當於虛擬路徑 “/assets/” 對應於 “build”(前者和後者指向的是同一個位置),而如果 build 下有一個 “index.css”,那麼通過虛擬路徑訪問就是 /assets/index.css。比如有一個如下的配置:

module.exports = {
  entry: {
    app: ["./app/main.js"]
  },
  output: {
    path: path.resolve(__dirname, "build"),
    publicPath: "/assets/",
    //此時相當於 /assets/ 路徑對應於 build 目錄,是一個對映的關係
    filename: "bundle.js"
  }
}

那麼我們要訪問編譯後的資源可以通過 localhost:8080/assets/bundle.js 來訪問。如果在 build 目錄下有一個 html 檔案,那麼可以使用下面的方式來訪問 js 資源:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Document</title>
</head>
<body>
  <script src="assets/bundle.js"></script>
</body>
</html>

hotUpdateChunkFilename vs hotUpdateMainFilename

這裡提供了一個 例子,當修改了 test 目錄下的檔案的時候,比如修改了 scss 檔案,此時會發現在頁面中多出了一個 script 元素,內容如下:

<script type="text/javascript" charset="utf-8" src="0.188304c98f697ecd01b3.hot-update.js"></script>

當開啟它會看到:

webpackHotUpdate(0,{
/***/ 15:
/***/ (function(module, exports, __webpack_require__) {
exports = module.exports = __webpack_require__(46)();
// imports
// module
exports.push([module.i, "html {\n  border: 1px solid yellow;\n  background-color: pink; }\n\nbody {\n  background-color: lightgray;\n  color: black; }\n  body div {\n    font-weight: bold; }\n    body div span {\n      font-weight: normal; }\n", ""]);
// exports

/***/ })
})
//# sourceMappingURL=0.188304c98f697ecd01b3.hot-update.js.map

從內容也可以看出,只是將修改的模組 push 到 exports 物件中!而 hotUpdateChunkFilename 就是為了讓你能夠執行 script 的 src 中的值!而同樣的 hotUpdateMainFilename 是一個 json 檔案用於指定哪些模組發生了變化,在 output 目錄下。

externals vs libraryTarget vs library

假如需要完成下面的兩個需求:

模組依賴於 jQuery,但是不希望 jQuery 打包到最後的檔案中去;
模組要存在於全域性的變數 Foo 上面。

那麼需要將 Webpack 配置如下:

module.exports = {
  entry: 
  {
    main:process.cwd()+'/example1/main.js',
  },
  output: {
    path:process.cwd()+'/dest/example1',
    filename: '[name].js',
    // export itself to a global var
    libraryTarget: "var",
    // name of the global var: "Foo"
    library: "Foo"
  },
    externals: {
        // require("jquery") is external and available
        //  on the global var jQuery
        "jquery": "jQuery"
    },
  plugins: [
   new CommonsChunkPlugin({
       name:"chunk",
       minChunks:2
   }),
    new  HtmlWebpackPlugin()
  ]
};

其中 external 配置表示模組 require(‘jquery’) 中的 jquery 來自於 window.jQuery,也就是來自於全域性物件 jQuery,而不要單獨打包到入口檔案的 bundle 中,在頁面中通過 script 標籤來引入!

 externals: {
        // require("jquery") is external and available
        //  on the global var jQuery
        "jquery": "jQuery"
    }

下面詳細的分析下 libraryTarget 和 library 相關內容。

library:在 output 中配置,可以指定庫的名稱
libraryTarget:指定模組輸出型別,可以是 commonjs、AMD、script 形式、UMD 模式

例子1:其中 libraryTarget 設定為 var,而 library 設定為 ‘Foo’。也就是表示把入口檔案打包的結果封裝到變數 Foo 上面(以下例子的 external 全部是一樣的,見上面的 webpack.config.js 檔案)

output: {
    path:process.cwd()+'/dest/example1',
    filename: '[name].js',
    // export itself to a global var
    libraryTarget: "var",
    // name of the global var: "Foo"
    library: "Foo"
  }

我們看看打包的結果:

var Foo =
webpackJsonpFoo([0,1],[
/* 0 */
/***/ function(module, exports, __webpack_require__) {
  var jQuery = __webpack_require__(1);
  var math = __webpack_require__(2);
  function Foo() {}
  // ...
  module.exports = Foo;
/***/ },
/* 1 */
/***/ function(module, exports) {
       module.exports = jQuery
/***/ },
/* 2 */
/***/ function(module, exports) {

       console.log('main1');

/***/ }
]);

從結果分析目的,入口檔案的 bunle 被打包成為一個變數,變數名就是 library 指定的 Foo。而且 externals 中指定的 jQuery 也被打包成為一個模組,但是這個模組是沒有 jQuery 原始碼的,他的模組內容很簡單,就是引用 window.jQuery:

/* 1 */
/***/ function(module, exports) {
       module.exports = jQuery;
/***/ },

關於 externals vs libraryTarget vs library 還有不懂的地方可以仔細閱讀 webpack 中的 externals vs libraryTarget vs library 深入分析 文章。

Webpack 的 loader

因為瀏覽器並非能夠識別所有的檔案型別(如 scss/less/typescript),因此在瀏覽器載入特定型別的資源之前需要對資源本身進行處理。Webpack 將所有的檔案型別都當做一個模組,如 css、html、scss、jpg 等,但是 Webpack 本身只能識別 JavaScript 檔案。因此,需要特定的 loader 將檔案轉化為 JavaScript 模組,同時將這個模組新增到依賴圖譜中。

Webpack 中 loader 具有如下的作用:

識別特定的檔案型別應該被那個 loader 處理
轉化特定的檔案以便將它新增到依賴圖譜中,並最終新增到打包後的輸出檔案中

下面給出一個例子:

const path = require('path');
const config = {
  entry: './path/to/my/entry/file.js',
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'my-first-webpack.bundle.js'
  },
  module: {
    rules: [
      { test: /\.txt$/, use: 'raw-loader' }
    ]
  }
};

module.exports = config;

上面這個例子相當於告訴 Webpack,當遇到 require()/import 一個字尾名為 txt 的檔案的時候就要使用 raw-loader 來處理,但是必須保證在 node_modules 中已經安裝了 raw-loader,否則報錯。而且,在檔案路徑巢狀很深的情況下,建議使用如下方式:

 rules: [
      { test: /\.txt$/, use: require.resolve('raw-loader') }
    ]

否則可能出現即使安裝了也找不到相應的 loader 的情況,特別是當 npm 3 出現以後。

Webpack 的 plugin

上面 loader 是在特定檔案型別的基礎上完成它的處理的,即它是針對於特定的檔案型別,並把它轉化為 JavaScript 中的模組。而 plugin 用於在編譯和打包過程中對所有的模組執行特定的操作。而且 Webpack 外掛系統是非常強大和可配置的。

為了使用外掛,只需要 require 它並把它新增到配置檔案的 plugins 陣列中。比如下面的例子:

 plugins: [
    //注意:這裡使用了 webpack-common-chunk,所以會產生兩個檔案,即 common.js 和 index.js (名字自由設定)
        new CommonsChunkPlugin({
            name: ["chunk",'common1','common2'],
            minChunks:2

        })
    ]

本章小結

該本章節中通過一個 CommonChunkPlugin 的例子引入了打包後 模組圖譜 的概念,並對 CommonChunkPlugin 的打包原理進行了分析。同時,還介紹了 Webpack 中四大核心概念,即入口檔案、輸出檔案、Loader、Plugin 等,並對輸出檔案中很多容易混淆的概念進行了深入的講解。通過本章節,對 Webpack 本身應該會有一個初略的瞭解,後續會針對 Loader、Plugin 等核心內容進行進一步的剖析。

第02課:Webpack 基本使用

在本章節中將簡要的對 Webpack 基本使用做一個演示。通過本章節的學習,能在專案中快速配置一個 Webpack 打包檔案並完成專案的打包工作。在本章節,我們不牽涉到 Webpack 中那些深入的知識點,後續會針對這些知識點做更加細緻的講解。但是,正如在上一個章節中看到的那樣,我很早就在 Webpack 配置中引入了 CommonChunkPlugin,因此在本章節,將在 Webpack 配置檔案中繼續使用這個外掛。希望能對該外掛引起足夠的重視,並學會如何在自己的專案中使用它。

其中 CommonChunkPlugin 是 Webpack 用於建立一個獨立的檔案,即所謂的 common chunk。這個 chunk 會包含多個入口檔案中共同的模組。通過將多個入口檔案公共的模組抽取出來可以在特定的時間進行快取,這對於提升頁面載入速度是很好的優化手段。

Webpack 打包例子講解

CommonChunkPlugin 引數詳解

開始具體的例子之前先看下這個外掛支援的配置和詳細含義。同時,也給出官網描述的幾個例子:

{
  name: string, // or
  names: string[],
  // The chunk name of the commons chunk. An existing chunk can be selected by passing a name of an existing chunk.
  // If an array of strings is passed this is equal to invoking the plugin multiple times for each chunk name.
  // If omitted and `options.async` or `options.children` is set all chunks are used, otherwise `options.filename`
  // is used as chunk name.
  // When using `options.async` to create common chunks from other async chunks you must specify an entry-point
  // chunk name here instead of omitting the `option.name`.
  filename: string,
  //指定該外掛產生的檔名稱,可以支援 output.filename 中那些支援的佔位符,如 [hash]、[chunkhash]、[id] 等。如果忽略這個這個屬性,那麼原始的檔名稱不會被修改(一般是 output.filename 或者 output.chunkFilename,可以檢視 compiler 和 compilation 部分第一個例子)。但是這個配置不允許和 `options.async` 一起使用
  minChunks: number|Infinity|function(module, count)  boolean,
  //至少有 minChunks 的 chunk 都包含指定的模組,那麼該模組就會被移出到 common chunk 中。這個數值必須大於等於2,並且小於等於沒有使用這個外掛應該產生的 chunk 數量。如果傳入 `Infinity`,那麼只會產生 common chunk,但是不會有任何模組被移到這個 chunk中 (沒有一個模組會被依賴無限次)。通過提供一個函式,也可以新增自己的邏輯,這個函式會被傳入一個參數列示產生的 chunk 數量
  chunks: string[],
  // Select the source chunks by chunk names. The chunk must be a child of the commons chunk.
  // If omitted all entry chunks are selected.
  children: boolean,
  // If `true` all children of the commons chunk are selected
  deepChildren: boolean,
  // If `true` all descendants of the commons chunk are selected

  async: boolean|string,
  // If `true` a new async commons chunk is created as child of `options.name` and sibling of `options.chunks`.
  // It is loaded in parallel with `options.chunks`.
  // Instead of using `option.filename`, it is possible to change the name of the output file by providing
  // the desired string here instead of `true`.
  minSize: number,
  //所有被移出到 common chunk 的檔案的大小必須大於等於這個值
}

上面的 filename 和 minChunks 已經在註釋中說明了,下面重點說一下其他的屬性。

  • children 屬性

其中在 Webpack 中很多 chunk 產生都是通過 require.ensure 來完成的。先看看下面的例子:

//main.js 為入口檔案
if (document.querySelectorAll('a').length) {
    require.ensure([], () => {
        const Button = require('./Components/Button').default;
        const button = new Button('google.com');
        button.render('a');
    });
}
if (document.querySelectorAll('h1').length) {
    require.ensure([], () => {
        const Header = require('./Components/Header').default;
        new Header().render('h1');
    });
}

此時會產生三個 chunk,分別為 main 和其他兩個通過 require.ensure 產生的 chunk,比如 0.entry.chunk.js 和 1.entry.chunk.js。如果配置了多個入口檔案(假如還有一個 main1.js),那麼這些動態產生的 chunk 中可能也會存在相同的模組(此時 main1、main 會產生四個動態 chunk )。而這個 children 配置就是為了這種情況而產生的。通過配置 children,可以將動態產生的這些 chunk 的公共的模組也抽取出來。

很顯然,以前是動態載入的檔案現在都必須在頁面初始的時候就載入完成,那麼對於初始載入肯在時間上有一定的副作用。但是存在一種情況,比如進入主頁面後,需要載入路由 A、路由 B……等一系列的檔案(網站的核心模組都要提前一次性載入),那麼把路由 A、路由 B……這些公共的模組提取到公有模組中,然後和入口檔案一起載入,在效能上還是有優勢的。下面是官網提供的一個例子:

new webpack.optimize.CommonsChunkPlugin({
  // names: ["app", "subPageA"]
  // (choose the chunks, or omit for all chunks)
  children: true,
  // (select all children of chosen chunks)
  // minChunks: 3,
  // (3 children must share the module before it's moved)
})

對於 common-chunk-plugin 不太明白的,可以 檢視這裡。

  • async

上面這種 children 的方案會增加初始載入的時間,這種 async 的方式相當於建立了一個非同步載入的 common-chunk,其包含 require.ensure 動態產生的 chunk 中的公共模組。這樣,當訪問特定路由的時候,會動態的載入這個 common chunk,以及特定路由包含的業務程式碼。下面也是官網給出的一個例項:

new webpack.optimize.CommonsChunkPlugin({
  name: "app",
  // or
  names: ["app", "subPageA"]
  // the name or list of names must match the name or names
  // of the entry points that create the async chunks
  children: true,
  // (use all children of the chunk)
  async: true,
  // (create an async commons chunk)
  minChunks: 3,
  // (3 children must share the module before it's separated)
})
  • names

該引數用於指定 common chunk 的名稱。如果指定的 chunk 名稱在 entry 中有配置,那麼表示選擇特定的 chunk。如果指定的是一個陣列,那麼相當於按照名稱的順序多次執行 common-chunk-plugin 外掛。如果沒有指定 name 屬性,但是指定了 options.async 或者 options.children,那麼表示抽取所有的 chunk 的公共模組,包括通過 require.ensure 動態產生的模組。其他情況下使用 options.filename 來作為 chunk 的名稱。

注意:如果指定了 options.async 來建立一個非同步載入的 common chunk,那麼必須指定一個入口 chunk 名稱,而不能忽略 option.name 引數。可以 點選這個例子 檢視。

  • chunks

通過 chunks 引數來選擇來源的 chunk。這些 chunk 必須是 common-chunk 的子級 chunk。如果沒有指定,那麼預設選中所有的入口 chunk。下面給出一個例子:

var CommonsChunkPlugin = require("webpack/lib/optimize/CommonsChunkPlugin");
module.exports = {
    entry: {
        main: process.cwd()+'/example6/main.js',
        main1: process.cwd()+'/example6/main1.js',
        jquery:["jquery"]
    },
    output: {
        path: process.cwd()  + '/dest/example6',
        filename: '[name].js'
    },
    plugins: [
        new CommonsChunkPlugin({
            name: "jquery",
            minChunks:2,
            chunks:["main","main1"]
        })
    ]
};

此時會發現在* jquery.js* 的最後會打包進來chunk1.js chunk2.js

/* 2 */
/***/ function(module, exports, __webpack_require__) {
  __webpack_require__(3);
  var chunk1=1;
  exports.chunk1=chunk1;

/***/ },
/* 3 */
/***/ function(module, exports) {
  var chunk2=1;
  exports.chunk2=chunk2;

/***/ }

關於 chunks 配置的使用可以 點選這裡檢視。所以,chunks 就是用於指定從那些 chunks 來抽取公共的模組,而 chunks 的名稱一般都是通過 entry 來指定的,比如上面的 entry 為:

 entry: {
        main: process.cwd()+'/example6/main.js',
        main1: process.cwd()+'/example6/main1.js',
        jquery:["jquery"]
    },

而 chunks 指定的為:

 chunks:["main","main1"] 

表明從 main、main1 兩個入口 chunk 中來找公共的模組。

  • deepChildren

如果將該引數設定為 true,那麼 common-chunk 下的所有的 chunk 都會被選中,比如 require.ensure 產生的 chunk 的子級 chunk,從這些 chunks 中來抽取公共的模組。

  • minChunks 為函式

可以給 minChunks 傳入一個函式。CommonsChunkPlugin 將會呼叫這個函式並傳入 module 和 count 引數。這個 module 引數用於指定某一個 chunks 中所有的模組,而這個* chunk* 的名稱就是上面配置的 name/names 引數。這個 module 是一個 NormalModule 例項,有如下的常用屬性:

module.context:表示儲存檔案的路徑,比如 '/my_project/node_modules/example-dependency'

module.resource:表示被處理的檔名稱,比如 '/my_project/node_modules/example-dependency/index.js'

而 count 參數列示指定的模組出現在多少個 chunk 中。這個函式對於細粒度的操作 CommonsChunk 外掛還是很有用的。可自己決定將那些模組放在指定的 common chunk 中,下面是官網給出的一個例子:

new webpack.optimize.CommonsChunkPlugin({
  name: "my-single-lib-chunk",
  filename: "my-single-lib-chunk.js",
  minChunks: function(module, count) {
    //如果一個模組的路徑中存在 somelib 部分,而且這個模組出現在 3 個獨立的 chunk 或者 entry 中,那麼它就會被抽取到一個獨立的 chunk 中,而且這個 chunk 的檔名稱為 "my-single-lib-chunk.js",而這個 chunk 本身的名稱為 "my-single-lib-chunk"
    return module.resource && (/somelib/).test(module.resource) && count === 3;
  }
});

而官網下面的例子詳細的展示瞭如何將 node_modules下引用的模組抽取到一個獨立的 chunk 中:

new webpack.optimize.CommonsChunkPlugin({
  name: "vendor",
  minChunks: function (module) {
    // this assumes your vendor imports exist in the node_modules directory
    return module.context && module.context.indexOf("node_modules") !== -1;
  }
})

因為 node_module 下的模組一般都是來源於第三方,所以在本地很少修改,通過這種方式可以將第三方的模組抽取到公共的 chunk 中。

還有一種情況就是,如果想把應用的* css/scss** vendor* 的 css(第三方類庫的 css)抽取到一個獨立的檔案中,那麼可以使用下面的 minChunk() 函式,同時配合* ExtractTextPlugin *來完成。

new webpack.optimize.CommonsChunkPlugin({
  name: "vendor",
  minChunks: function (module) {
    // This prevents stylesheet resources with the .css or .scss extension
    // from being moved from their original chunk to the vendor chunk
    if(module.resource && (/^.*\.(css|scss)$/).test(module.resource)) {
      return false;
    }
    return module.context && module.context.indexOf("node_modules") !== -1;
  }
})

這個例子在抽取 node_modules 下的模組的時候做了一個限制,即明確指定 node_modules 下的 scss/css 檔案不會被抽取,所以最後生成的 vendor.js 不會包含第三方類庫的 css/scss 檔案,而只包含其中的 js 部分。 同時通過配置 ExtractTextPlugin 就可以將應用的 css 和第三方應用的 css 抽取到一個獨立的 css 檔案中,從而達到 css 和 js 分離。

其中 CommonsChunkPlugin 外掛還有一個更加有用的配置,即用於將 Webpack 打包邏輯相關的一些檔案抽取到一個獨立的* chunk* 中。但是此時配置的 name 應該是* entry* 中不存在的,這對於線上快取很有作用。因為如果檔案的內容不發生變化,那麼 chunk 的名稱不會發生變化,所以並不會影響到線上的快取。比如下面的例子:

new webpack.optimize.CommonsChunkPlugin({
  name: "manifest",
  //這個 name 必須不在 entry 中
  minChunks: Infinity
})

但是你會發現抽取 manifest 檔案和配置 vendor chunk 的邏輯不一樣,所以這個外掛需要配置兩次:

[
  new webpack.optimize.CommonsChunkPlugin({
    name: "vendor",
    minChunks: function(module){
      return module.context && module.context.indexOf("node_modules") !== -1;
    }
  }),
  new webpack.optimize.CommonsChunkPlugin({
    name: "manifest",
    minChunks: Infinity
  }),
]

你可能會好奇,假如有如下的配置:

  module.exports = {
    entry: './src/index.js',
    plugins: [
      new CleanWebpackPlugin(['dist']),
      new HtmlWebpackPlugin({
       title: 'Caching'
      })
    ],
    output: {
    filename: '[name].[chunkhash].js',
      path: path.resolve(__dirname, 'dist')
    }
  };

那麼如果 entry 中檔案的內容沒有發生變化,執行 webpack 命令多次,那麼最後生成的檔名稱應該是一樣的,為什麼會重新通過 CommonsChunkPlugin 來生成一個 manifest 檔案呢?這個官網也有明確的說明:

因為 Webpack 在入口檔案中會包含特定的樣板檔案,特別是 runtime 檔案和 manifest 檔案。而最後生成的檔案的名稱到底是否一致還與 Webpack 的版本有關,在新版本中可能不存在這個問題,但是在老版本中可能會存在,所以為了安全起見一般都會使用它。那麼問題又來了,樣板檔案和 runtime 檔案指的是什麼?可以查 我的這個例子,把關注點放在文中說的為什麼要提前載入最後一個 chunk 的問題上。下面就這部分做一下深入的分析:

  • runtime

當程式碼在瀏覽器中執行的時候,Webpack 使用 runtime 和 manifest 來處理應用中的模組化關係。其中包括在模組存在依賴關係的時候,載入和解析特定的邏輯,而解析的模組包括已經在瀏覽中載入完成的模組和那些需要懶載入的模組本身。

  • manifest

一旦應用程式中,如 index.html 檔案、一些 bundle 和各種靜態資源被載入到瀏覽器中,會發生什麼?精心安排的 /src 目錄的檔案結構現在已經不存在,所以 Webpack 如何管理所有模組之間的互動呢?這就是 manifest 資料用途的由來……

當編譯器(compiler)開始執行、解析和對映應用程式時,它會保留所有模組的詳細要點。這個資料集合稱為 “Manifest”,當完成打包併傳送到瀏覽器時,會在執行時通過 Manifest 來解析和載入模組。無論選擇哪種模組語法,那些 import 或 require 語句現在都已經轉換為 webpack_require方法,此方法指向模組識別符號(module identifier)。通過使用 manifest 中的資料,runtime 將能夠查詢模組識別符號,檢索出背後對應的模組。比如提供的 這個例子,其中入口檔案中有 main.js,入口檔案中載入 chunk1.js 和 chunk2.js,而最後看到的就是下面轉化為webpack_require 後的內容:

  webpackJsonp([0,1],[
/* 0 */
/***/ function(module, exports, __webpack_require__) {
    __webpack_require__(1);
    __webpack_require__(2);
/***/ },
/* 1 */
/***/ function(module, exports, __webpack_require__) {

    __webpack_require__(2);
    var chunk1=1;
    exports.chunk1=chunk1;
/***/ },
/* 2 */
/***/ function(module, exports) {
    var chunk2=1;
    exports.chunk2=chunk2;
/***/ }
]);

而 manifest 檔案的作用就是在執行的時候通過webpack_require後的模組標識(Module identifier)來載入指定的模組內容。比如 manifest例子生成的 manifest.json 檔案內容是如下的格式:

{
  "common.js": "common.js",
  "main.js": "main.js",
  "main1.js": "main1.js"
}

這樣就可以在原始檔和目標檔案之間有一個對映關係,而這個對映關係本身依然存在於打包後的輸出目錄,而不會因為 src 目錄消失了而不知道具體的模組對應關係。而至於其中 moduleId 等的對應關係是由 Webpack 自己維護的,通過打包後的 視覺化 可以瞭解。

CommonChunkPlugin 無法抽取單入口檔案公共模組

上面講了 Webpack 官網提供的例子以及原理分析,下面通過自己構造的幾個例子來深入理解上面的概念。假如有如下的 Webpack 配置檔案:

var CommonsChunkPlugin = require("webpack/lib/optimize/CommonsChunkPlugin");
module.exports = {
  entry: 
  {
    main:process.cwd()+'/example1/main.js',
  },
  output: {
    path:process.cwd()+'/dest/example1',
    filename: '[name].js'
  },
  devtool:'cheap-source-map',
  plugins: [
   new CommonsChunkPlugin({
       name:"chunk",
       minChunks:2
   })
  ]
};

下面是入口檔案內容:

//main.js
require("./chunk1");
require("./chunk2");
console.log('main1.');

其中 chunk1.js 內容如下:

require("./chunk2");
var chunk1=1;
exports.chunk1=chunk1;

而 chunk2.js 內容如下:

var chunk2=1;
exports.chunk2=chunk2;

我們引入了 CommonsChunkPlugin,並將那些引入了兩次以上的模組輸出到 chunk.js 中。那麼你肯定會認為,chunk2.js 被引入了兩次,那麼它肯定會被外掛抽取到 chunk.js 中,但是實際上並不是這樣。可以檢視 main.js,內容如下:

webpackJsonp([0,1],[
/* 0 */
/***/ function(module, exports, __webpack_require__) {
    __webpack_require__(1);
    __webpack_require__(2);
/***/ },
/* 1 */
/***/ function(module, exports, __webpack_require__) {
    __webpack_require__(2);
    var chunk1=1;
    exports.chunk1=chunk1;

/***/ },
/* 2 */
/***/ function(module, exports) {
    var chunk2=1;
    exports.chunk2=chunk2;

/***/ }
]);

通過這個例子可知:“單入口檔案時候不能把引用多次的模組列印到 CommonChunkPlugin 中”。

CommonChunkPlugin 抽取多入口檔案公共模組

假如有如下的 Webpack 配置檔案:

var CommonsChunkPlugin = require("webpack/lib/optimize/CommonsChunkPlugin");
module.exports = {
  entry: 
  {
      main:process.cwd()+'/example2/main.js',
      main1:process.cwd()+'/example2/main1.js',
  },
  output: {
    path:process.cwd()+'/dest/example2',
    filename: '[name].js'
  },
  plugins: [
   new CommonsChunkPlugin({
       name:"chunk",
       minChunks:2
   })
  ]
};

其中 main1.js 內容如下:

require("./chunk1");
require("./chunk2");

而 main.js 內容如下:

require("./chunk1");
require("./chunk2");

而 chunk1.js 內容如下:

require("./chunk2");
var chunk1=1;
exports.chunk1=chunk1;

而 chunk2.js 內容如下:

var chunk2=1;
exports.chunk2=chunk2;

此時,很顯然採用的是多入口檔案模式,在相應的目錄下會生成 main.js 和 main1.js,以及 chunk.js,而 chunk.js 中抽取的是 main.js 和 main1.js 中被引入了兩次以上的模組,很顯然 chunk1.js 和 chunk2.js 都會被引入到 chunk.js 中,下面是 chunk.js 中的部分程式碼:

/******/ ([
/* 0 */,
/* 1 */
/***/ function(module, exports, __webpack_require__) {
    __webpack_require__(2);
    var chunk1=1;
    exports.chunk1=chunk1;
/***/ },
/* 2 */
/***/ function(module, exports) {
    var chunk2=1;
    exports.chunk2=chunk2;

/***/ }
/******/ ]);

CommonChunkPlugin 分離業務程式碼與框架程式碼

假如有如下的 Webpack 配置內容,同時 chunk1、chunk2、main1、main 的內容和上面保持一致。

var CommonsChunkPlugin = require("webpack/lib/optimize/CommonsChunkPlugin");
module.exports = {
    entry: {
        main: process.cwd()+'/example3/main.js',
        main1: process.cwd()+'/example3/main1.js',
        common1:["jquery"],
        //只含有 jquery.js
        common2:["vue"]
        //只含有 vue.js 和載入器程式碼
    },
    output: {
        path: process.cwd()+'/dest/example3',
        filename: '[name].js'
    },
    plugins: [
        new CommonsChunkPlugin({
            name: ["chunk",'common1','common2'],
            minChunks:2
            //引入兩次以及以上的模組
        })
    ]
};

按照 CommonsChunkPlugin 的抽取公共程式碼的邏輯,會有如下的結果:

chunk.js 中儲存的是 main.js 和 main1.js 的公共程式碼,即 chunk1.js 和 chunk2.js 
common1.js 中只有 jquery.js
common2.js 中只有 vue.js,但是必須含有 Webpack 的載入器程式碼

其實道理很簡單,chunk.js 中只有 chunk1.js 和 chunk2.js,而不存在被引入了兩次的模組,最多引入次數的就是 chunk2.js,所以 common1.js 只含有 jquery.js。但是,正如前文所說,common2.js 必須最先載入。

minChunks 為 Infinity 配置

假如 Webpack 配置如下:

var CommonsChunkPlugin = require("webpack/lib/optimize/CommonsChunkPlugin");
module.exports = {
    entry: {
        main: process.cwd()+'/example5/main.js',
        main1: process.cwd()+'/example5/main1.js',
        jquery:["jquery"]
        //minChunks: Infinity 時候框架程式碼依然會被單獨打包成一個檔案
    },
    output: {
        path: process.cwd() + '/dest/example5',
        filename: '[name].js'
    },
    plugins: [
        new CommonsChunkPlugin({
            name: "jquery",
            minChunks:2//被引用兩次及以上
        })
    ]
};

上面的檔案輸出將會是如下內容:

main.js 包含去掉的公共程式碼部分
main1.js 包含去掉的公共程式碼部分
main1.js 和 main2.js 的公共程式碼將會被打包到 jquery.js 中,即 jquery.js 包含 jquery+ 公共的業務程式碼

其實,這個配置稍微晦澀難懂一點,假如將上面的 minChunks 配置修改為”Infinity”,那麼結果將截然不同:

main.js 原樣打包
main1.js 原樣打包
jquery 包含 jquery.js 和 webpack 模組載入器   

因為將 minChunks 設定為 Infinity,也就是無窮大,那麼 main.js 和 main1.js 中不存在任何模組被依賴的次數這麼大,因此 chunk.js 和 chunk1.js 都不會被抽取出來。

chunks 指定那些入口檔案中的公共模組會被抽取

繼續修改 Webpack 配置如下:

var CommonsChunkPlugin = require("webpack/lib/optimize/CommonsChunkPlugin");
module.exports = {
    entry: {
        main: process.cwd()+'/example6/main.js',
        main1: process.cwd()+'/example6/main1.js',
        jquery:["jquery"]
    },
    output: {
        path: process.cwd()  + '/dest/example6',
        filename: '[name].js'
    },
    plugins: [
        new CommonsChunkPlugin({
            name: "jquery",
            minChunks:2,
            chunks:["main","main1"]
            //main.js 和 main1.js 中都引用的模組才會被打包的到公共模組
        })
    ]
};

此時 chunks 設定為 [“main”,”main1”],表示只有 main.js 和 main1.js 中都引用的模組才會被打包的到公共模組,而且必須是依賴次數為 2 次以上的模組。因此結果將會如下:

jquery.js 中包含 main1.js 和 main.js 中公共的模組,即 chunk1.js 和 chunk2.js,以及 jquery.js 本身
main1.js 表示是去掉公共模組後的檔案內容
main.js 表示是去掉公共模組後的檔案內容   

我們也可以通過檢視打包後的 jquery.js 看到結果驗證,即 jquery.js 包含了 jquery.js 本身以及公共的業務程式碼:

/* 2 */
/***/ function(module, exports, __webpack_require__) {
  __webpack_require__(3);
  var chunk1=1;
  exports.chunk1=chunk1;

/***/ },
/* 3 */
/***/ function(module, exports) {
  var chunk2=1;
  exports.chunk2=chunk2;

/***/ }

本章小結

本章節主要通過 7 個例子展示了 Webpack 配合 CommonsChunkPlugin 的打包結果,但是為什麼結果是這樣,會在 Webpack 常見外掛原理分析章節進行深入的剖析。本章節所有的例子程式碼你可以 點選這裡 檢視。文中的配置都是參考 Webpack 2 的,如果使用的是 Webpack 1,請升級。如果需要檢視上面的例子的執行結果,請執行下面的命令:

npm install webpack -g
git clone https://github.com/liangklfangl/commonsChunkPlugin_Config.git
webpack
//修改 webpack.config.js 並執行 webpack 命令

下一篇

課程內容

導讀:課程內容簡介

第01課:Webpack 核心概念

第02課:Webpack 基本使用

第03課:webpack-dev-server 核心概念

第04課:webpack-dev-server 基本使用

第05課:Webpack 的 HMR 原理分析

第06課:Webpack 中的 Compiler 和 Compilation 物件

第07課:Webpack 常見外掛原理分析

第08課:寫一個 Webpack 外掛

第09課:寫一個 Webpack 的 Loader

第10課:Webpack 結合 React-Router 實現按需載入

第11課:Webpack 2 的 Tree-shaking 深入分析

第12課:以 Node 方式整合 Webpack 和 webpack-dev-server 打包

相關文章