1. 程式人生 > 實用技巧 >webpack效能優化

webpack效能優化

一.概述

1.webpack效能優化

  • 開發環境效能優化
  • 生產環境效能優化

2.開發環境效能優化

  • 優化webpack的打包構建速度
    • HRM
  • 優化程式碼除錯
    • source-map

3.生產環境效能優化

  • 優化打包構建速度
    • oneOf
    • babel快取
    • 多程序打包
  • 優化程式碼執行的效能
    • 快取(hash-chunkhash-contenthash
    • tree shacking
    • code split
    • 懶載入/預載入
    • PWA
    • externals
    • dll

4.存在的問題

當我們修改某一個css或者模組的時候,所有的檔案都會被重新打包;如果模組很多的情況下,這樣是很耗費時間的。我們希望只修改發生變化的模組;這就要使用webpack

中的HMR功能;

二.HMR

hot module replacement熱模組替換/模組熱替換

作用:一個模組發生變化,只會重新打包這一個模組,而不是打包所有;這樣會極大地提升構建速度。

使用方法:只需要在devServer中設定hot屬性為true即可:

  devServer: {
    contentBase: resolve(__dirname, 'build'),
    compress: true,
    port: 3000,
    open: true,
    //開啟HRM
    hot: true
  }

注意:修改了webpack配置之後,一定要重啟devServer

重啟後,再次開啟devServer

npx webpack-dev-server

可以看到開啟的html檔案的輸出中顯示,已開啟HRM

隨後,修改入口檔案index.js依賴的index.less,可以看到這一次只重新打包了index.less模組:

所以驗證了css檔案可以使用HMR功能,其實:

  • 樣式檔案:可以使用HMR功能:因為style-loader內部實現;

  • js檔案:預設不使用HMR功能 ;

    • 解決方法:需要修改js程式碼,新增支援HMR功能的程式碼:

      if (module.hot){
        //一旦 module.hot 為 true ,說明開啟了HMR功能。此時才讓HMR功能生效
        module.hot.accept('./print.js', function(){
          //方法會監聽print.js檔案的變化,一旦發生變化,其他模組不會重新打包構建,會執行後面的回撥函式
        })
      }
      

    上面的print.js為發生變化的模組,如果有其他發生變化需要開啟HMR功能的模組,只需要新增相應路徑就可以了;

    注意:HMR功能對js檔案處理,只能處理非入口js檔案。因為入口檔案引入了很多依賴,它一變就要不可避免的重新引入依賴;

  • html檔案:預設不使用HMR功能,同時會導致問題:html檔案不能熱更新了(devServer的功能,自動更新);

    • 解決方法:修改entry入口,將html檔案引入。但是還是是用不了HMR功能;

        entry: ['./src/js/index.js', './src/index.html']
      
    • 其實HTML檔案不需要HMR功能,因為html檔案通常只有一個,它變了就說明有重新打包的必要;

三.source-map

source-map是一種技術,提供原始碼到構建後代碼的對映技術。(如果構建後代碼出錯了,通過對映可以追蹤原始碼錯誤)

使用方法為,只需要在五大屬性的同級下新增一個devtool屬性即可:

devtool: 'source-map'

執行webpack打包後,會生成一個.map檔案:

裡面儲存著對映關係:

寫法分為:

[inline-|hidden-|eval-][nosources-][cheap-[module-]]source-map

其中:

  • source-map:外部;

  • 作用:顯示錯誤程式碼的準確資訊和原始碼的錯誤位置;

  • inline-source-map:內聯:所有檔案只生成一個source-map檔案,並且拼接在js檔案中的最後;

    • 作用:顯示錯誤程式碼的準確資訊和原始碼的錯誤位置;
  • hidden-source-map:外部

    • 作用:顯示錯誤程式碼和錯誤原因,但是沒有錯誤位置。不能追蹤到原始碼錯誤,只能提示到構建後代碼的位置;
  • eval-source-map:內聯:每一個檔案都生成對應的source-map,並且都在eval函式中;

    • 作用:顯示錯誤程式碼的準確資訊和原始碼的錯誤位置;只不過提示為每個檔案通過eval新增的雜湊值:

  • nosources-source-map:外部;

    • 作用:顯示錯誤程式碼和錯誤原因,但是沒有任何原始碼資訊;與hidden-source-map一起提供隱藏程式碼的服務:

      但是點選提示,不會顯示原始碼資訊:

  • cheap-source-map:外部;

    • 作用:顯示錯誤程式碼的準確資訊和原始碼的錯誤位置;但是隻能精確到行:

      如圖,只有console發生了錯誤,但是隻能精確到行;

  • cheap-module-source-map:外部;

    • 作用:顯示錯誤程式碼的準確資訊和原始碼的錯誤位置;同樣只能將錯誤精確到原始碼中的行;不同在於module會將loadersource-map也加進來;

內聯與外部的區別:

  • 外部方式會生成.map檔案而內聯是沒有的,而是將.map檔案的內容內嵌到打包後的js檔案的最後;
  • 內聯構建速度更快

下面我們來一個個測試它們的作用:

1.作用演示

source-map

首先在入口檔案index.js中增添一處錯誤程式碼:

隨後通過下列指令開啟devServer

npx webpack-dev-server

在顯示的網頁中可以看到出現了錯誤提示:

並且這個提示包含了錯誤出現在哪個檔案的哪一行中,點選這個提示可以跳轉到原始碼出錯的地方:

所以source-map的作用為:顯示錯誤程式碼的準確資訊和原始碼的錯誤位置;

inline-source-map

注意:修改了webpack.config.js的配置後,一定要重新啟動devServer;在開啟的網頁中,一樣顯示了錯誤程式碼的準確資訊和原始碼的錯誤位置;

點選提示即可跳轉到錯誤原始碼的位置:

2.選擇

source-map有這麼多種寫法,那麼我們怎麼選擇呢?

開發環境

要求:速度快,除錯友好;

  • 速度方面:eval > inline > cheap ...

    通過組合,速度從快到慢的寫法如下:

    eval-cheap-source-map

    eval-source-map

  • 除錯友好方面:定位更精確,就越友好。所以友好程度排名為:

    source-map:精確到具體位置;

    cheap-module-source-map:精確到行,但是包含了loader等依賴檔案;

    cheap-source-map:最後是精確到行,但是不包含依賴檔案;

折中方案為採用eval-source-map 或者 eval-cheap-module-source-map,框架中的腳手架預設使用前者;

生產環境

原始碼要不要隱藏?除錯要不要更友好?

內聯會讓程式碼體積變大,所以在生產環境中一般不使用內聯;

  • 隱藏程式碼:

    hidden-source-map:只隱藏原始碼,會提示構建後的程式碼錯誤;

    nosources-source-map:全部隱藏;

最後結論

需要除錯友好時使用source-map,需要速度快時使用cheap-module-source-map

四.oneOf

通常一個loader處理一類檔案,也就是說並不是每一個檔案都需要使用全部的loader去處理,這樣會降低效能;可以使用oneOf設定檔案只匹配oneOf陣列中的一個loader

    rules: [
      {
        //oneOf的意思為:只會匹配以下中的一個loader
        oneOf: [
      //1.處理css檔案
      {
          //...		
      },
      //2.處理less檔案
      {
          //...	
      },
      //3.js語法檢查
      {
          //...	
      },
      //4.js相容性處理
      {
          //...	
      },
      //5.處理圖片
      {
          //...	
      },
      //6.處理html中的圖片
      {
          //...	
      },
      //8.處理其他檔案
      {
          //...	
      }
        ]
      }
    ]

需要注意的是隻能選擇oneOf陣列中的一個loader執行;像eslintbabel這樣的loader都會處理js檔案,這就需要將其中一個loader抽離出oneOf陣列了:

    rules: [
      //js語法檢查eslint
      {
        test: /\.js$/,
        exclude: /node_modules/,
        //優先執行
        enforce: 'pre',
        loader: 'eslint-loader',
        options: {
          fix: true
        }
      },
      {
        oneOf: [
      //js相容性處理babel
      {
          //...	
      },
        ]
      }
    ]

並且eslint-loaderenforce屬性為pre表示會優先執行該loader;這樣兩個loader就不會互相影響了;

這樣配合著使用oneOf可以提升打包構建的速度;

使用webpack進行檔案打包時,並不是每次打包所有的檔案都不一樣,如果可以將每次打包都不變的檔案快取起來,下次直接複用,就可以大幅度提升打包的速度。這就涉及到了webpack中的快取機制;

五.快取

1.babel快取

比如處理了100份檔案,我們希望在第一次處理的時候可以將這100份檔案的處理結果快取起來,以後只重新處理其中發生變化的檔案,其餘檔案直接使用快取。可通過cacheDirectory開啟快取,配置如下:

      {
        test: /\.js$/,
        exclude: /node_modules/,
        loader: 'babel-loader',
        options: {
          presets: [
            [
              '@babel/preset-env',
              {
                useBuiltIns: 'usage',
                corejs: {version: 3},
                targets: {
                  chrome: '60',
                  firefox: '50'
                }
              }
            ]
          ],
          //開啟babel快取
          //第二次構建時,會讀取之前的快取
          cacheDirectory: true
        }
      },

2.檔案基本快取

src的檔案結構如下:

首先在根目錄下建立一個伺服器檔案server.js

/**
 * 伺服器
 * 啟動伺服器指令;
 * 方式一:
 * 首先需要全域性安裝:npm i nodemon -g
 * nodemon server.js
 * 
 * 方式二:
 * node server.js
 */
const express = require('express')

const app = express();

app.use(express.static('build', {maxAge: 1000 * 3600}));

app.listen(3000)

執行該檔案:

node server.js

由於該檔案監聽的是3000埠,所以訪問地址為:http://localhost:3000

開啟除錯工具的network選項,可以看到第一次開啟網頁,資源都需要請求:

重新整理一次後,再次檢視:

可以看到,瀏覽器直接從快取讀取了這兩個檔案。

點開其中一個檔案,可以看到在HTTP響應頭中的max-age欄位正是server.js中設定的3600s

也就是這兩個資源需要被強制快取兩個小時

讀取快取的速度非常之快,所以快取能夠大大提高載入速度。

存在的問題:

當被快取的檔案發生變化時,就不能使用這個過期的快取了,需要重新請求該最新版的該檔案。

解決方案:

hash

每次打包都給檔案拼接上一個hash值當做是版本號,這樣快取的檔案與發生更新後的檔案由於版本號不一樣,名字也就不同,所以會重新載入更新後的檔案;具體配置為在每個檔案的output物件中為filename屬性拼接上hash值:

  output: {
    filename: 'js/built.[hash:10].js',
    path: resolve(__dirname, 'build')
  },
      
  //...
    plugins: [
    new MiniCssExtractPlugin({
      //設定輸出路徑
      filename: 'css/built.[hash:10].css',
    })
    ]

再次執行webpack打包,此時打包出來的檔案就會拼接上10位的hash值了:

這個hash值取的是每次執行webpack時生成hash值的前10位:

這樣就保證了,每次打包都會生成不同的打包檔案。

隨後,執行伺服器程式碼:

node server.js

開啟地址http://localhost:3000,這次請求的資源都帶上了hash值:

當我們修改了原始碼之後,再進行打包,打包出來的檔案就拼接上了另外10hash值了:

此時重新整理網頁,瀏覽器就會重新請求,經過再次打包的更新後的檔案了:

檔案資源快取存在的問題

由於所有檔案都共享同一次webpack打包時生成的hash值,所以只要有一個檔案發生了變化,重新打包後,所有檔案的快取都會失效。

chunkhash

webpack引入了另外一個hash值:chunkhash。只需要將webpack.config.js配置中的hash替換為chunkhash即可。根據chunk生成雜湊值,如果打包來源於同一個chunk雜湊值一樣。

此時,再進行打包,發現打包後的cssjs檔案仍然共有一個hash值:

這是因為:css檔案是在js檔案中引入的,所以同屬於一個chunk

chunk:入口檔案index.js會引入很多檔案,比如cssless等,這樣一個整體被稱為chunk,即程式碼塊;

contenthash

使用contenthash才是最優解,這是根據檔案的內容生成的hash值,不同檔案的hash值一定不一樣;將webpack.config.js中的chunkhash改為contenthash,再次打包:

這次打包出來的css檔案與js檔案的hash值就不一樣了;這樣的話如果只改變了css檔案,那麼瀏覽器第一次快取的js檔案仍然有效,只需要重新請求css檔案即可:

第一次開啟頁面,兩檔案都需要請求:

修改css檔案並重新打包後,再一次重新整理瀏覽器。只需要重新請求css檔案即可:

所以一共有兩種快取策略:

  • babel快取:直接在webpack.config.js中配置即可;
    • 優點:讓第二次打包構建速度更快;
  • 檔案資源快取:給檔案新增hash值,有三種選擇,最優解為新增contenthash值。
    • 優點:讓上線執行的程式碼更好地使用快取;

六.Tree shaking

1.簡介

在應用程式中引入的原始碼和一些庫可以理解為樹上的一個綠色的活的樹葉,哪些應用程式中沒有引用的程式碼就是灰色的,枯萎的樹葉;為了去掉這些灰色的樹葉,我們需要搖晃這棵樹,這就是樹搖(tree shaking)的概念;樹搖是為了去除應用程式中沒有使用的程式碼,這樣能讓程式碼的體積變得更小。

tree shaking的作用為去除無用的程式碼;但是有前提:

  • 必須使用ES6模組化;
  • mode設定為production環境;

2.作用

減少程式碼體積。例如:

// app.js
export function A(a, b) {
    return a + b
}

export function B(a, b) {
    return a + b
}

// index.js
import {A} from '/app.js'

A(1, 2)

index.js引用了app.jsA函式,如果tree-shaking起了作用,B函式是不會被打包進最後的bundle的。因為,index.js中並沒有用到B函式;

3.配置

webpack.josn中配置sideEffects

"sideEffects": false

它表示所有的程式碼都是沒有副作用的,都可進行tree shaking

問題:可能會把css@babel/polyfill等檔案刪除(副作用);

解決方法:將sideEffects的值設定為["*.css"]

"sideEffects": ["*.css"]

這樣寫在該陣列內的元素,就不會執行tree shaking

預設情況下會將入口檔案index.js與其引入的js檔案一起打包,最後輸出一個js檔案。如圖所示,原始碼資料夾src中的test.jsindex.js最後都被打包成了一個js檔案:

如果我們需要將程式碼分門別類地打包呢?這時候就需要用到程式碼分割了;

七.程式碼分割

1.直接使用多入口

要進行抽離,可以先從entry指定的入口檔案路徑下手,配置如下:

  entry: {
    index: './src/js/index.js',
    test: './src/js/test.js'
  },

entry這樣的配置稱為多入口;對於多入口:有一個入口,最終輸出就有一個bundle。配置後,執行webpack進行打包:

由於配置裡有兩個入口,所以打包後輸出的js檔案也有兩個:

為了方便區分打包後輸出的內容,修改output配置:

  output: {
    //[name]:取檔名
    filename: 'js/[name].[contenthash:10].js',
    path: resolve(__dirname, 'build')
  },

再次打包:此時打包後的檔案與打包前的檔案的對應關係就一目瞭然了:

一般,單頁面應用對應的是單入口,多頁面的對應的是多入口;

2.使用optimization

單入口時

webpack.config.js中新增:

entry: './src/js/index.js',
//...
optimization: {
    splitChunks: {
      chunks: 'all',
    } 
  },

然後在入口檔案index.js中引入jquery

import $ from 'jquery'

執行webpack進行打包,可以看到輸出了兩個檔案,另外一個檔案用於儲存jquery

這種方式的好處是:可以將node_modules中程式碼單獨打包一個chunk最終輸出;

多入口時

配置如下:

 entry: {
    index: './src/js/index.js',
    test: './src/js/test.js'
 },
 //...
 optimization: {
  splitChunks: {
    chunks: 'all',
  } 
},

再次打包過後生成了三個chunk

如果沒有在配置中新增optimization屬性,那麼在多入口的情況下,如果兩個js檔案都引入了jquery,那麼打包出來的兩個js檔案,大小是相近的:

這是因為兩個js檔案各自打包一份jquery,這種重複打包比較不好;

解決方法為新增optimization屬性,它的好處為:它就會分析多入口chunk中有沒有公共的檔案,如果有就會打包成單獨的chunk

總結:optimization的作用:

  • 單入口時:可以將node_modules中程式碼單獨打包一個chunk最終輸出;

  • 多入口時:會分析多入口chunk中有沒有公共的檔案,如果有就會打包成單獨的chunk

也就是說多入口時一般配合著optimization使用;

3.單入口動態引入(常用)

當若想要只使用單入口就能實現多入口配合optimization使用時達到的效果的話,可以採用這種方法:

通過js程式碼,讓某個檔案被單獨打包成一個chunkimport動態匯入語法,能將某個檔案單獨打包。比如在入口檔案index.js中動態引入test.js

test.js程式碼:

export function mul(x, y) {
  return x * y;
}

export function count(x, y) {
  return x - y;
}

隨後在index.js中動態引入:

import('./test')
    .then(({mul, count}) => {
      //檔案載入成功
      // eslint-disable-next-line
      console.log(mul(2, 4))
    })
    .catch(() => {
     // eslint-disable-next-line
      console.log('檔案載入失敗')	
    })

import返回的是一個Promise物件,then方法代表載入成功,catch方法代表載入失敗;

隨後打包,輸出了兩個js檔案,其中的1.js代表test.js

打包出來的js檔名字是webpack打包過程生成的隨機id,不太美觀,我們通過給import增加引數設定打包後輸出的js檔案的名字:

//設定輸出檔名為test
import(/* webpackChunkName: 'test' */'./test')
    .then(({mul, count}) => {
      // eslint-disable-next-line
      console.log(mul(2, 4))
    })
    .catch(() => {
     // eslint-disable-next-line
      console.log('檔案載入失敗')	
    })

再次打包:

可見,自定義了test.js打包後輸出檔案的名字;

八.懶載入和預載入

1.懶載入

指的是觸發了某些條件才載入,而不是一開始就載入;這裡講的是js檔案的懶載入;

可以通過import().then的方式實現js檔案的懶載入,比如在入口檔案index.js中這樣寫:

//懶載入
document.getElementById('btn').onclick = function(){
  import((/* webpackChunkName: 'test' */ './test').then((mul) => {
    console.log(mul(3, 5));
  })
}

一般將import語句,放在一些判斷語句中,當滿足一定的條件時才執行import動態引入;如上面的程式碼,將引入程式碼放在了按鈕btn的點選事件中,只有點選按鈕btn才會引入test.js檔案;

首先,原始檔目錄src結構如下:

test.js程式碼如下:

console.log('test.js檔案被載入了');

export function mul(x, y) {
  return x * y;
}

index.js的程式碼如下:

console.log('index.js檔案被載入了');

document.getElementById('btn').onclick = function(){
  //懶載入
  import(/* webpackChunkName: 'test'*/ './test').then(() => {
    console.log('test.js被執行了');
  })
}

index.html程式碼如下,設定了一個按鈕:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <h1>hello lazy-loading</h1>
  <button id="btn">按鈕</button>
</body>
</html>

打包後,開啟經過打包的index.html檔案:

可以看到,此時只加載了index.js,當點選了按鈕後,才會載入test.js檔案:

當我們多次點選按鈕時,發現只會再次執行test.js而不會再次載入。因為第一次載入之後該檔案就被快取起來了,之後使用的時候就可以直接從快取中讀取了:

2.預載入

為入口檔案index.js新增webpackPrefetch

console.log('index.js檔案被載入了');

document.getElementById('btn').onclick = function(){
  //懶載入
  //預載入prefetch
  import(/* webpackChunkName: 'test', webpackPrefetch: true*/ './test').then(() => {
    console.log('test.js被執行了');
  })
}

打包後,開啟經過打包的index.html檔案:可以看到test.js檔案已經提前載入好了:

但是還沒有輸出:

當我們點選按鈕後,會從快取中直接讀取,所以會大大提升test.js的載入速度:

3.區別

  • 懶載入:當檔案需要使用時才載入;

  • 預載入:在使用之前,提前載入js檔案;

    正常載入可以認為是並行載入:同一時間載入多個檔案;這些檔案沒有優先順序,只是按程式碼順序載入,這樣就會導致提前載入還不需要用到的檔案;

    而預載入prefetch:是等其他資源載入完畢,瀏覽器空閒了,才會偷偷地載入資源;不會阻礙重要資源的優先載入;

總的來說:懶載入用的比較多,但是預載入存在相容性問題,需要慎用;

九.PWA

1.簡介

PWA:漸進式網路開發應用程式,簡而言之就是離線訪問技術;

實現它需要用到外掛:workbox-webpack-plugin

首先全域性下載該外掛:

npm i workbox-webpack-plugin -D

然後在webpack.config.js中引入該外掛:

const WorkboxWebpackPlugin = require('workbox-webpack-plugin');

然後直接在plugins屬性中使用就可以了:

  plugins: [
    new WorkboxWebpackPlugin.GenerateSW({
      clientsClaim: true,
      skipWaiting: true
    })
  ],

GenerateSW中的兩個引數作用為:

  • 幫助Service Worker快速啟動;
  • 刪除舊的Service Worker

最終會生成一個Service Worker的配置檔案;

2.配置

需要在入口檔案index.js中註冊Service Worker

// 註冊Service Worker
// 處理相容性問題
if('serviceworker' in navigator){//如果有這個屬性,才在頁面全域性資源載入完後進行註冊
  window.addEventListener('load', () => {
    navigator.serviceW
      
      orker.register('/service-worker.js')
    //成功
    .then(() => {
      console.log('sw註冊成功了');
    })
    //失敗
    .catch(() => {
      console.log('sw註冊失敗了');
    })
  })
}

3.打包

此時執行打包會報eslint的錯誤:

原因:eslint不認識windownavigator這些全域性變數;

解決方法:需要修改webpack.jsoneslint的配置。

  "eslintConfig": {
    "extends": "airbnb-base",
    "env": {
      "browser": true
    }
  },

其中的"browser": true表示:支援瀏覽器端全域性變數;

再次執行webpack構建就不會報錯了,成功輸出了service-worker.js等檔案:

4.驗證Server Worker

sw程式碼必須執行在伺服器上,所以需要在伺服器檔案中啟動構建後生成的sw檔案;有多種方式可以選擇:

  • nodejs

  • 通過npm i serve -g全域性安裝serve包,安裝之後可以直接通過serve -s build啟動伺服器,將build目錄下的所有資源作為靜態資源暴露出去:

開啟這個連結:

可以看到,成功註冊了serviceWorker,隨後就可以在Application選項中的Service Workers中看到註冊的檔案了:

並且可以在Cache中看到儲存的資源:

隨後在Network中將網路設定為離線Offline

隨後重新整理網頁,網頁還是可以顯示。這是因為,這些檔案都是直接從Service Worker中直接讀取的:

這就是PWA:離線訪問技術;

十.多程序打包

1.安裝

我們知道js引擎是單執行緒的,同一時間只能幹一件事。所以可以通過多程序來優化打包速度;

首先需要下載thread-loader

npm i thread-loader -D

2.配置

一般應用在babel中,一旦需要需要使用多個loader時,就要放在use中:

      //4.js相容性處理
      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: [
          //開啟多程序打包
          'thread-loader',
          {
            loader: 'babel-loader',
            options: {
              presets: [
                [
                  '@babel/preset-env',
                  {
                    useBuiltIns: 'usage',
                    corejs: {version: 3},
                    targets: {
                      chrome: '60',
                      firefox: '50'
                    }
                  }
                ]
              ],
              //開啟babel快取
              cacheDirectory: true
            }
          }
        ]
      },

缺點:程序啟動需要時間:600ms,程序通訊也需要時間開銷;所以只有在工作消耗時間比較長時,才需要多程序打包;由於開發中絕大多數都是js檔案,babel除了語法檢查外還有進行相容性處理,生成大量相容性程式碼,所以應該給babel開啟多程序打包;

3.打包

不加thread-loader的打包時間:

加了thread-loader後的打包時間:

需要花費更多的時間。這是因為當前原始檔中的js程式碼量比較少,而開啟多程序需要時間,所以程式碼量小的時候不建議使用多程序打包;只有實際開發中,程式碼量上去了它的加速效果才會凸顯出來。

如果想調整的話,可以將 'thread-loader'改成物件形式:

{
  loader: 'thread-loader',
  options: {
    workers: 2//指定使用2個程序
  }
}

開啟多程序會增大開銷,千萬不能亂用!

十一.Externals和Dll

1.Externals

webpack.config.js中新增externals屬性,該屬性與五大基本屬性同級:

  externals: {
    //拒絕jquery被打包進來
    jquery: 'jQuery'
  }

作用為:可以讓webpack不對某些庫或包進行打包;不過忽略了之後,比如刪除的jquery,就需要在html檔案中通過srcipt標籤手動引入。

注意寫法:字串內指定包名,不能寫錯;

2.Dll

表示動態連結庫。可以將多個功能(包)打包成一個chunk。比如之前我們都是將node_modules目錄下的所有檔案打包成一個chunk,而使用dll可以將它分為多個不同的部分,分別打包出多個chunk

也就是對某些庫進行單獨打包;

生成dll檔案

使用dll需要在根目錄下新增一個webpack.dll.js配置檔案;配置如下:

/**
 * 使用dll技術,對某些庫(vue、jquery、react等)進行單獨打包
 */

const { resolve } = require('path')
//引入webpack外掛
const webpack = require('webpack')

module.exports = {
  entry: {
    //最終打包生成的[name] --> jquery
    //['jquery] --> 要打包的庫是jquery
    jquery: ['jquery']
  },
  output: {
    //這裡的[name]就是上面的jquery
    filename: '[name].js',
    path: resolve(__dirname, 'dll'),
    library: '[name]_[hash]',//打包的庫裡面向外暴露的內容叫什麼名字
  },
  plugins: [
    //該外掛作用為:打包生成一個manifest.json檔案 --> 提供和jquery的對映
    new webpack.DllPlugin({
      name: '[name]_[hash]',//對映的庫暴露的內容名稱
      path: resolve(__dirname, 'dll/manifest.json')//輸出的名稱
    })
  ],
  mode: 'production'
}

總結以下就是:entryoutput中的配置作用為:單獨打包jquery檔案,並將打包輸出的檔案命名為jquery_hash。外掛的作用為,生成一個mainfest.json檔案,管理與jquery的對映關係;

當執行webpack時,預設查詢的是 webpack.config.js 配置檔案;需求:需要執行webpack.dll.js檔案。這就需要:執行以下指令手動指定執行的配置檔案:

webpack --config webpack.dll.js

執行該命令後,打包生成一個dll目錄,裡面有兩個檔案:jquery.jsmanifest.json

並且這兩個檔案內都會拼接上一個hash值,改值與webpack打包時生成的hash一致:

第一個檔案:為指定的庫單獨打包出來的檔案;

第二個檔案:manifest.json檔案記錄了哪些庫不用經過了單獨打包了,不需要重複打包的對映關係;

改變webpack配置

同時,相應的也要修改webpack.config.js的配置,新增兩個外掛:webpackadd-asset-html-webpack-plugin(記住要先全域性下載外掛):

const {resolve} = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
//1.引入webpack外掛
const webpack = require('webpack')
//3.引入add-asset-html-webpack-plugin外掛
const AddAssetHtmlWebpackPlugin = require('add-asset-html-webpack-plugin')

module.exports = {
  entry : './src/index.js',
  output : {
    filename: 'built.js',
    path: resolve(__dirname, 'build')
  },
  plugins: [
    //plugins的配置
    new HtmlWebpackPlugin({
      template: './src/index.html'
    }),
    //2.使用webpack外掛告訴webpack哪些庫不參與打包,同時使用時的名稱也得改變
    new webpack.DllReferencePlugin({
      manifest: resolve(__dirname, 'dll/manifest.json')
    }),
    //4.將該外掛指定的檔案打包輸出,並在html中自動引入該資源
    new AddAssetHtmlWebpackPlugin({
      filepath: resolve(__dirname, 'dll/jquery.js')
    })
  ],
  mode: 'development'
}

具體新增配置如程式碼中的1~4

  • 第一個外掛的作用為:根據第一步中生成的manifest.json告知webpack不用打包該檔案指定的庫(這裡是jquery),這樣打包出來的built.js也就不含有這些庫(jquery)的內容了。

    注意:由於jquery被指定忽略了,即使入口檔案index.js中引入了jquery,打包時也就不會打包jquery

  • 第二個外掛的作用為:將第一步被單獨打包生成的庫檔案(這裡是jquery)自動引入html檔案中。

配置後之後,再次執行webpack打包,可以發現,在第一步中單獨打包的jquery檔案被再次打包了出來:

並且html檔案中自動引入了新增的打包檔案jquery,這是第二個外掛的作用:

使用了webpack外掛,打包時就會忽略外掛指定的jquery檔案。由於打包出來的html只自動引入了打包後的built.js檔案,而built.js因為外掛的原因並沒有打包jquery;這時候就需要在html檔案中手動引入jquery檔案了。此時有兩種方法:

  • 第一:html檔案中直接通過script標籤引入;
  • 第二:使用上述的add-asset-html-webpack-plugin外掛打包引入;

3.總結

經過以上兩步操作之後,原始碼發生改變只需要再次執行webpack.config.js即可,不需要執行webpack.dll.js,也就是說不需要再重複打包jquery了,達到了效能優化的效果;今後專案中會有很多的第三方庫,我們都可以使用dll的方式對它們進行單獨打包。在使用webpack重新打包的時候就不用再打包這些第三方的庫了,這樣能讓webpack第二次打包的速度快很多;

  • externals:只忽略某些指定的庫,不對它們進行打包,使用時需要通過script標籤引入;需要依賴外鏈引入。
  • dll:需要打包一次,將來就不需要重複打包了。如果想要將一些第三方庫整合在一起,不依賴外鏈,就使用這種方式。