webpack4.0打包優化策略(一)

布羅利發表於2018-03-28

webpack4.0新特性

webpack4.0 新特性介紹

打包優化策略

當前依賴包的版本

依賴包版本

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 目錄,將會降級回退到作業系統預設的臨時檔案目錄。

webpack4官方文件 babel-loader很慢!

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)的配置,亦當如此:

resolve

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.3 resolve.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().length})

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來完成。 資料:happypack原理解析-淘寶前端

npm run dev
複製程式碼

5.DLL動態連結庫

在一個動態連結庫中可以包含其他模組呼叫的函式和資料,動態連結庫只需被編譯一次,在之後的構建過程中被動態連結庫包含的模組將不會被重新編譯,而是直接使用動態連結庫中的程式碼。

  • 將web應用依賴的基礎模組抽離出來,打包到單獨的動態連結庫中。一個連結庫可以包含多個模組。
  • 當需要匯入的模組存在於動態連結庫,模組不會再次打包,而是去動態連結庫中去獲取。
  • 頁面依賴的所有動態連結庫都需要被載入。

5.1 定義DLL配置

依賴的兩個內建外掛:DllPlugin 和 DllReferencePlugin

5.1.1 建立一個DLL配置檔案webpack_dll.config.js

module.exports = {
    entry: {
        react: ['react', 'react-dom']
    },
    output: {
        filename: '[name].dll.js', // 動態連結庫輸出的檔名稱
        path: path.join(__dirname, 'dist'), // 動態連結庫輸出路徑
        libraryTarget: 'var', // 連結庫(react.dll.js)輸出方式 預設'var'形式賦給變數 b
        library: '_dll_[name]_[hash]' // 全域性變數名稱 匯出庫將被以var的形式賦給這個全域性變數 通過這個變數獲取到裡面模組
    },
    plugins: [
        new webpack.DllPlugin({
            // path 指定manifest檔案的輸出路徑
            path: path.join(__dirname, 'dist', '[name].manifest.json'),
            name: '_dll_[name]_[hash]', // 和library 一致,輸出的manifest.json中的name值
        })
    ]
}
複製程式碼

5.1.2 output.libraryTarget 規定了以哪一種匯出你的庫 預設以全域性變數形式 瀏覽器支援的形式

libraryTarget

具體包括如下:

  • "var" - 以直接變數輸出(預設library方式) var Library = xxx (default)

  • "this" - 通過設定this的屬性輸出 this["Library"] = xxx

  • "commonjs" - 通過設定exports的屬性輸出 exports["Library"] = xxx

  • "commonjs2" - 通過設定module.exports的屬性輸出 module.exports = xxx

  • "amd" - 以amd方式輸出

  • "umd" - 結合commonjs2/amd/root

5.1.3 打包生成動態連結庫

webpack --config webpack_dll.config.js --mode production
複製程式碼

DLL動態連結庫
在dist目錄下 多出react.dll.js 和 react.manifest.json

  • react.dll.js 動態連結庫 裡面包含了 react和react-dom的內容
  • react.manifest.json 描述連結庫(react.dll)中的資訊

5.2 在主配置檔案中使用動態連結庫檔案

// webpack.config.js

const webpack = require('webpack');

plugins: [
    // 當我們需要使用動態連結庫時 首先會找到manifest檔案 得到name值記錄的全域性變數名稱 然後找到動態連結庫檔案 進行載入
    new webpack.DllReferencePlugin({
       manifest: require('./dist/react.manifest.json')
    })
]
複製程式碼

5.3 將動態連結庫檔案載入到頁面中

需要藉助兩個webpack外掛

  • html-webpack-plugin 產出html檔案
  • html-webpack-include-assets-plugin 將js css資源新增到html中 擴充套件html外掛的功能
npm i html-webpack-plugin html-webpack-include-assets-plugin -D
複製程式碼

配置webpack.config.js

const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const HtmlIncludeAssetsPlugin = require('html-webpack-include-assets-plugin');

pluings: [
    new webpack.DllReferencePlugin({
       manifest: require('./dist/react.manifest.json')
    }),
    new HtmlWebpackPlugin({
       template: path.join(__dirname, 'src/index.html')
    }),
    new HtmlIncludeAssetsPlugin({
        assets: ['./react.dll.js'], // 新增的資源相對html的路徑
        append: false // false 在其他資源的之前新增 true 在其他資源之後新增
    });
]
複製程式碼

此時react.dll.js和main.js被自動引入到頁面中,並且dll檔案在main.js之前載入

載入dll

6.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
複製程式碼

7.Tree Shaking

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

commonJS模組 與 es6模組的區別

commonJS模組:

  • 1.動態載入模組 commonJS 是執行時載入 能夠輕鬆實現懶載入,優化使用者體驗

  • 2.載入整個模組 commonJS模組中,匯出的是整個模組

  • 3.每個模組皆為物件 commonJS模組被視作一個物件

  • 4.值拷貝 commonJS的模組輸出和函式的值傳遞相似,都是值得拷貝

es6模組

  • 1.靜態解析 es6模組時 編譯時載入 即在解析階段就確定輸出的模組的依賴關係,所以es6模組的import一般寫在被引入檔案的開頭

  • 2.模組不是物件 在es6裡,每個模組並不會當做一個物件看待

  • 3.載入的不是整個模組 在es6模組中 一個模組中有好幾個export匯出

  • 4.模組的引用 es6模組中,匯出的並不是模組的值得拷貝,而是這個模組的引用

7.1 保留ES6模組化語法

// .babelrc

{
    "presets": [
        [
           "env", {
               modules: false // 不要編譯ES6模組
           },
           "react",
           "stage-0"
        ]
    ]
}
複製程式碼

7.2 執行生產編譯 預設已開啟Tree Shaking

webpack --mode production
複製程式碼

什麼是Tree Shaking?

有個funs.js 裡面有兩個函式

// funs.js
export const sub = () => 'hello webpack!';
export const mul = () => 'hello shaking!';
複製程式碼

main.js 中依賴funs.js

// main.js
import {sub} from './funs.js'

sub();

複製程式碼

在main.js只使用了裡面的 sub函式 預設情況下也會將funs.js裡面其他沒有的函式也打包進來, 如果開啟tree shaking 生產編譯時

webpack --mode production  //此時funs.js中沒有被用到的程式碼並沒打包進來 而被剔除出去了
複製程式碼

下一章 webpack4.0打包優化策略整理(二)

  • 區分環境
  • 提取公共代
  • 程式碼分離
  • 開啟Scope Hoisting

原始碼參考

GitHub原始碼

參考

相關文章