webpack打包優化的完美解決方案

webpack打包優化分爲兩部分,一部分是大小優化,另一部分是速度優化。

大小優化

1.CommonsChunk

前端構建項目中,爲了提高打包效率,往往將第三庫與業務邏輯代碼分開打包,因爲第三方庫往往不需要經常打包更新。webpack建議使用CommonsChunk 來單獨打包第三方庫:

module.exports = {
    entry: {
        vendor: ['react','react-dom'],
        app: "./main",
    },
    output: {
        path: './build',
        filename: '[name].js',
        library: '[name]_library'
    },
    plugins: [
        new CommonsChunkPlugin({
            name: "vendor",
        }),
    ]
};

CommonsChunk雖然可以減少包的大小,但存在問題是:即使代碼不更新,每次重新打包,vendor都會重新生成,不符合我們分離第三方包的初衷。

2.Externals

相比於前者,webpack 提供Externals的方法,可以通過外部引用的方法,引入第三方庫: index.html

<script src="https://code.jquery.com/jquery-3.1.0.js"></script>

webpack.config.js

module.exports = {
   externals: {
     jquery: 'jQuery'
   }
};

業務邏輯,如index.js

import $ from 'jquery';
$('.my-element').animate(...);

webpack打包時,發現jquery定義在externals,則不會打包jquery代碼。由於不需要打包jquery,所以也減少打包時間。 不過externals雖然解決了外部引用問題,但是無法解決以下問題:

import xxx from 'react/src/xx';

webpack遇到此問題時,會重新打包react代碼。 參考:https://gold.xitu.io/entry/57996222128fe1005411c649

3.DLL & DllReference

相比於前者,通過前置這些依賴包的構建,來提高真正的build和rebuild構建效率。也就是說只要第三方庫沒有變化,之後的每次build都只需要去打包自己的業務代碼,解決Externals多次引用問題。 webpack通過webpack.DllPlugin與webpack.DllReferencePlugin兩個內嵌插件實現此功能。
1、新建webpack.dll.config.js

const webpack = require('webpack');

module.exports = {
    entry: {
        bundle: [
            'react',
            'react-dom',
            //其他庫
            ],
    },
    output: {
        path: './build',
        filename: '[name].js',
        library: '[name]_library'
    },
    plugins: [
        new webpack.DllPlugin({
            path: './build/bundle.manifest.json',
            name: '[name]_library',
        })
    ]
};

webpack.DllPlugin選項:

  • path:manifest.json文件的輸出路徑,這個文件會用於後續的業務代碼打包;
  • name:dll暴露的對象名,要跟output.library保持一致;
  • context:解析包路徑的上下文,這個要跟接下來配置的 webpack.config.js 一致。

運行:

npm run webpack-dll

生成兩個文件,一個是打包好的bundlejs,另外一個是bundle.mainifest.json,大致內容如下:

{
  "name": "bundle_library",
  "content": {
    "./node_modules/react/react.js": 1,
    "./node_modules/react/lib/React.js": 2,
    "./node_modules/process/browser.js": 3,
    "./node_modules/object-assign/index.js": 4,
    "./node_modules/react/lib/ReactChildren.js": 5,
    "./node_modules/react/lib/PooledClass.js": 6,
    "./node_modules/react/lib/reactProdInvariant.js": 7,
    //其他引用
}

2、配置webpack.config.js

const webpack = require('webpack');
var path = require('path');
module.exports = {
  entry: {
    main: './main.js',
  },
  output: {
    path: path.join(__dirname, "build"),
    publicPath: './',
    filename: '[name].js'
  },
  module: {
    loaders:[
      { test: /\.(png|jpg)$/, loader: 'url-loader?limit=8192'},
      {
        test: /\.jsx?$/,
        loaders: ['babel-loader?presets[]=es2015&presets[]=react'],
        include: path.join(__dirname, '.')
      }
    ]
  },
  plugins: [
     new webpack.DllReferencePlugin({
      context: '.',
      manifest: require("./build/bundle.manifest.json"),
        }),
  ]
};

webpack.DllReferencePlugin的選項中:

  • context:需要跟之前保持一致,這個用來指導webpack匹配manifest.json中庫的路徑;
  • manifest:用來引入剛纔輸出的manifest.json文件。

速度優化

1.優化loader配置
1.1 縮小文件匹配範圍(include/exclude)

通過排除node_modules下的文件 從而縮小了loader加載搜索範圍 高概率命中文件


module: {

  rules: [

    {

      test: /\.js$/,

      use: 'babel-loader',

      exclude: /node_modules/, // 排除不處理的目錄

      include: path.resolve(dirname, 'src') // 精確指定要處理的目錄

    }

  ]

}
1.2 緩存loader的執行結果(cacheDirectory)

cacheDirectory是loader的一個特定的選項,默認值是false。指定的目錄(use: ‘babel-loader?cacheDirectory=cacheLoader’)將用來緩存loader的執行結果,減少webpack構建時Babel重新編譯過程。如果設置一個空值(use: ‘babel-loader?cacheDirectory’) 或true(use: ‘babel-loader?cacheDirectory=true’) 將使用默認的緩存目錄(node_modules/.cache/babel-loader),如果在任何根目錄下都沒有找到 node_modules 目錄,將會降級回退到操作系統默認的臨時文件目錄。

module: {

  rules: [

    {

      test: /\.js$/,

      use: 'babel-loader?cacheDirectory', // 緩存loader執行結果 發現打包速度已經明顯提升了

      exclude: /node_modules/,

      include: path.resolve(dirname, 'src')

    }

  ]

}
2.resolve優化配置
2.1 優化模塊查找路徑 resolve.modules

Webpack的resolve.modules配置模塊庫(即 node_modules)所在的位置,在 js 裏出現 import ‘vue’ 這樣不是相對、也不是絕對路徑的寫法時,會去 node_modules 目錄下找。但是默認的配置,會採用向上遞歸搜索的方式去尋找,但通常項目目錄裏只有一個 node_modules,且是在項目根目錄,爲了減少搜索範圍,可以直接寫明 node_modules 的全路徑;同樣,對於別名(alias)的配置,亦當如此:

const path = require('path');

function resolve(dir) { // 轉換爲絕對路徑

  return path.join(dirname, dir);

}

resolve: {

  modules: [ // 優化模塊查找路徑

    path.resolve('src'),

    path.resolve('node_modules') // 指定node_modules所在位置 當你import 第三方模塊時 直接從這個路徑下搜索尋找

  ]

}

配置好src目錄所在位置後,由於util目錄是在src裏面 所以可以用下面方式引入util中的工具函數


// main.js

import dep1 from 'util/dep1';

import add from 'util/add';
2.2 resolve.alias 配置路徑別名

創建 import 或 require 的路徑別名,來確保模塊引入變得更簡單。配置項通過別名來把原導入路徑映射成一個新的導入路徑 此優化方法會影響使用Tree-Shaking去除無效代碼。
例如,一些位於 src/ 文件夾下的常用模塊:


alias: {

 Utilities: path.resolve(dirname, 'src/utilities/'),

 Templates: path.resolve(dirname, 'src/templates/')

}

現在,替換「在導入時使用相對路徑」這種方式,就像這樣:

import Utility from '../../utilities/utility';

你可以這樣使用別名:

import Utility from 'Utilities/utility';
resolve: {

  alias: { // 別名配置 通過別名配置 可以讓我們引用變的簡單

    'vue$': 'vue/dist/vue.common.js', // $表示精確匹配

    src: resolve('src') // 當你在任何需要導入src下面的文件時可以 import moduleA from 'src/moduleA' src會被替換爲resolve('src') 返回的絕對路徑 而不需要相對路徑形式導入

  }

}

也可以在給定對象的鍵後的末尾添加 $,以表示精準匹配:

alias: {

  util$: resolve('src/util/add.js')

}

這將產生以下結果:

import Test1 from 'util'; // 精確匹配,所以 src/util/add.js 被解析和導入

import Test2 from 'util/dep1.js'; // 精確匹配,觸發普通解析 util/dep1.js
2.3resolve.extensions

當引入模塊時不帶文件後綴 webpack會根據此配置自動解析確定的文件後綴

後綴列表儘可能小

頻率最高的往前放

導出語句儘可能帶上後綴

resolve: {

  extensions: ['.js', '.vue']

}
3.module.noParse

用了noParse的模塊將不會被loaders解析,所以當我們使用的庫如果太大,並且其中不包含import require、define的調用,我們就可以使用這項配置來提升性能, 讓 Webpack 忽略對部分沒采用模塊化的文件的遞歸解析處理。

// 忽略對jquery lodash的進行遞歸解析

module: {

  // noParse: /jquery|lodash/

  // 從 webpack 3.0.0 開始

  noParse: function(content) {

    return /jquery|lodash/.test(content)

  }

}
4.HappyPack

HappyPack是讓webpack對loader的執行過程,從單一進程形式擴展爲多進程模式,也就是將任務分解給多個子進程去併發的執行,子進程處理完後再把結果發送給主進程。從而加速代碼構建 與 DLL動態鏈接庫結合來使用更佳。

npm i happypack@next -D

webpack.config.js

const HappyPack = require('happypack');

const os = require('os'); // node 提供的系統操作模塊

 // 根據我的系統的內核數量 指定線程池個數 也可以其他數量

const happyThreadPool = HappyPack.ThreadPool({size: os.cpus().lenght})

module: {

  rules: [

    {

      test: /\.js$/,

      use: 'happypack/loader?id=babel',

      exclude: /node_modules/,

      include: path.resolve(dirname, 'src')

    }

  ]

},

plugins: [

  new HappyPack({ // 基礎參數設置

    id: 'babel', // 上面loader?後面指定的id

    loaders: ['babel-loader?cacheDirectory'], // 實際匹配處理的loader

    threadPool: happyThreadPool,

    // cache: true // 已被棄用

    verbose: true

  });

]

happypack提供的loader,是對文件實際匹配的處理loader。這裏happypack提供的loader與plugin的銜接匹配,則是通過id=happypack來完成。

5.ParallelUglifyPlugin

這個插件可以幫助有很多入口點的項目加快構建速度。把對JS文件的串行壓縮變爲開啓多個子進程並行進行uglify。

cnpm i webpack-parallel-uglify-plugin -D
// webpck.config.js

const ParallelUglifyPlugin = require('webpack-parallel-uglify-plugin');

plugins: [

  new ParallelUglifyPlugin({

    workerCount: 4,

    uglifyJS: {

      output: {

        beautify: false, // 不需要格式化

        comments: false // 保留註釋

      },

      compress: { // 壓縮

        warnings: false, // 刪除無用代碼時不輸出警告

        drop_console: true, // 刪除console語句

        collapse_vars: true, // 內嵌定義了但是隻有用到一次的變量

        reduce_vars: true // 提取出出現多次但是沒有定義成變量去引用的靜態值

      }

    }

  });

]

執行壓縮

webpack --mode production
6.Tree Shaking

剔除JavaScript中用不上的代碼。它依賴靜態的ES6模塊化語法,例如通過impot和export導入導出

參考鏈接:
webpack dllPlugin 使用
webpack的4.0打包優化如何實現

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章