webpack常用優化配置

lio-mengxiang發表於2019-02-25

1.1 優化loader配置

1.1.1 include & exclude

    module:{
        rules:[
            {
                 test:/\.js$/,
                 use:['babel-loader?cacheDirectory'],
+                include:path.resolve(__dirname,'src'),
+                exclude:/node_modules/
            }
        ]
    }
複製程式碼

1.1.2 resolve.modules

resolve: {
        modules: [path.resolve(__dirname, 'node_modules')]
},
複製程式碼

1.1.3 resolve.mainFields

mainFields用於配置第三方模組使用那個入口檔案 isomorphic-fetch

  • 當target為web或webworker時,值是["browswer","module","main"]
  • 當target為其他情況時,值是["module","main"] ```js resolve: {
  • mainFields:['main'] }, ```

1.1.4 resolve.alias

resolve.alias配置項通過別名來把原匯入路徑對映成一個新的匯入路徑 此優化方法會影響使用Tree-Shaking去除無效程式碼

alias: {
            'react': path.resolve(__dirname, './node_modules/react/cjs/eact.production.min.js')
        }
複製程式碼

1.1.5 resolve.extensions

在匯入語句沒帶檔案字尾時,Webpack會自動帶上字尾後去嘗試詢問檔案是否存在 預設字尾是 extensions: ['.js', '.json']

  • 字尾列表儘可能小
  • 頻率最高的往前方
  • 匯出語句裡儘可能帶上字尾
resolve: {
+        extensions: ['js']
},
複製程式碼

1.1.5 module.noParse

module.noParse 配置項可以讓 Webpack 忽略對部分沒采用模組化的檔案的遞迴解析處理

    module: {
+        noParse: [/react\.min\.js/]
    }
複製程式碼

被忽略掉的檔案裡不應該包含 import 、 require 、 define 等模組化語句

2.DLL

.dll 為字尾的檔案稱為動態連結庫,在一個動態連結庫中可以包含給其他模組呼叫的函式和資料

  • 把基礎模組獨立出來打包到單獨的動態連線庫裡
  • 當需要匯入的模組在動態連線庫裡的時候,模組不能再次被打包,而是去動態連線庫裡獲取 dll-plugin

2.1 定義Dll

  • DllPlugin外掛: 用於打包出一個個動態連線庫
  • DllReferencePlugin: 在配置檔案中引入DllPlugin外掛打包好的動態連線庫
module.exports = {
    entry: {
        react: ['react'] //react模組打包到一個動態連線庫
    },
    output: {
        path: path.resolve(__dirname, 'dist'),
        filename: '[name].dll.js', //輸出動態連線庫的檔名稱
        library: '_dll_[name]' //全域性變數名稱
    },
    plugins: [
        new webpack.DllPlugin({
            name: '_dll_[name]', //和output.library中一致,值就是輸出的manifest.json中的 name值
            path: path.join(__dirname, 'dist', '[name].manifest.json')
        })
    ]
}
複製程式碼
webpack --config webpack.dll.config.js --mode production
複製程式碼

2.2 使用動態連結庫檔案

plugins: [
+        new webpack.DllReferencePlugin({
+            manifest: require(path.join(__dirname, 'dist', 'react.manifest.json')),
+        })
    ],
複製程式碼
webpack --config webpack.config.js --mode development
複製程式碼

3. HappyPack

HappyPack就能讓Webpack把任務分解給多個子程式去併發的執行,子程式處理完後再把結果傳送給主程式。 happypack

npm i happypack@next -D
複製程式碼
module: {
        rules: [{
            test: /\.js$/,
            //把對.js檔案的處理轉交給id為babel的HappyPack例項
 +          use: 'happypack/loader?id=babel',
            include: path.resolve(__dirname, 'src'),
            exclude: /node_modules/
        }, {
            //把對.css檔案的處理轉交給id為css的HappyPack例項
            test: /\.css$/,
+           use: 'happypack/loader?id=css',
            include: path.resolve(__dirname, 'src')
        }],
        noParse: [/react\.min\.js/]
    },
複製程式碼
   plugins: [
        //用唯一的識別符號id來代表當前的HappyPack是用來處理一類特定檔案
        new HappyPack({
            id: 'babel',
            //如何處理.js檔案,和rules裡的配置相同
            loaders: [{
                loader: 'babel-loader',
                query: {
                    presets: [
                        "env", "react"
                    ]
                }
            }]
        }),
        new HappyPack({
            id: 'css',
            loaders: ['style-loader', 'css-loader'],
            threads: 4, //代表開啟幾個子程式去處理這一型別的檔案
            verbose: true //是否允許輸出日子
        })
    ],
複製程式碼

4. ParallelUglifyPlugin

ParallelUglifyPlugin可以把對JS檔案的序列壓縮變為開啟多個子程式並行執行

npm i -D webpack-parallel-uglify-plugin
複製程式碼
new ParallelUglifyPlugin({
            workerCount: 3, //開啟幾個子程式去併發的執行壓縮。預設是當前執行電腦的 CPU 核數減去1
            uglifyJS: {
                output: {
                    beautify: false, //不需要格式化
                    comments: false, //不保留註釋
                },
                compress: {
                    warnings: false, // 在UglifyJs刪除沒有用到的程式碼時不輸出警告
                    drop_console: true, // 刪除所有的 `console` 語句,可以相容ie瀏覽器
                    collapse_vars: true, // 內嵌定義了但是隻用到一次的變數
                    reduce_vars: true, // 提取出出現多次但是沒有定義成變數去引用的靜態值
                }
            },
        })
複製程式碼

5. 伺服器自動重新整理

我們可以監聽到本地原始碼檔案發生變化時,自動重新構建出可執行的程式碼後再重新整理瀏覽器

5.1 檔案監聽

+ watch: true, //只有在開啟監聽模式時,watchOptions才有意義
+ watchOptions: {
+    ignored: /node_modules/,
+    aggregateTimeout: 300, //監聽到變化發生後等300ms再去執行動作,防止檔案更新太快導致編譯頻率太高
+    poll: 1000 //通過不停的詢問檔案是否改變來判斷檔案是否發生變化,預設每秒詢問1000次
+ }
複製程式碼

5.2 檔案監聽流程

  • webpack定時獲取檔案的更新時間,並跟上次儲存的時間進行比對,不一致就表示發生了變化,poll就用來配置每秒問多少次
  • 當檢測檔案不再發生變化,會先快取起來,等待一段時間後之後再通知監聽者,這個等待時間通過aggregateTimeout配置
  • webpack只會監聽entry依賴的檔案
  • 我們需要儘可能減少需要監聽的檔案數量和檢查頻率,當然頻率的降低會導致靈敏度下降

5.3 自動重新整理瀏覽器

    devServer: {
        contentBase: './dist',
+        inline: true
    },
複製程式碼

webpack負責監聽檔案變化,webpack-dev-server負責重新整理瀏覽器 這些檔案會被打包到chunk中,它們會代理客戶端向伺服器發起WebSocket連線

+  [19] (webpack)-dev-server/client/overlay.js 3.58 KiB {0} [built]
+  [21] (webpack)-dev-server/client/socket.js 1.05 KiB {0} [built]
+  [22] ./node_modules/loglevel/lib/loglevel.js 7.68 KiB {0} [built]
+  [24] ./node_modules/strip-ansi/index.js 161 bytes {0} [built]
+  [31] ./node_modules/url/url.js 22.8 KiB {0} [built]
+  [32] (webpack)-dev-server/client?http://localhost:8080 7.75 KiB {0} [built]
+  [33] multi (webpack)-dev-server/client?http://localhost:8080 ./src/index.js 40 bytes {0} [built]
複製程式碼

5.4 模組熱替換

模組熱替換(Hot Module Replacement)的技術可在不重新整理整個網頁的情況下只更新指定的模組 原理是當一個原始碼發生變化時,只重新編譯發生變化的模組,再用新輸出的模組替換掉瀏覽器中對應的老模組

  • 反應更快,時間更短
  • 不重新整理網頁可以保留網頁執行狀態
    devServer: {
+        hot:true
    }
複製程式碼
[./node_modules/webpack/hot sync ^\.\/log$] (webpack)/hot sync nonrecursive ^\.\/log$ 170 bytes {main} [built]
   [0] multi (webpack)-dev-server/client?http://localhost:8080 webpack/hot/dev-server ./src/index.js 52 bytes {main} [built]
[./node_modules/webpack/hot/dev-server.js] (webpack)/hot/dev-server.js 1.66 KiB {main} [built]
[./node_modules/webpack/hot/emitter.js] (webpack)/hot/emitter.js 77 bytes {main} [built]
複製程式碼
if (module.hot) {
    module.hot.accept('./index.js', function () {
        console.log('accept index.js');
    });
}
複製程式碼

優化模組熱替換瀏覽器日誌

plugins: [
+        new webpack.NamedModulesPlugin(),
+        new webpack.HotModuleReplacementPlugin(),
]
複製程式碼
  • 監聽更少的檔案
  • 忽略掉 node_modules 目錄下的檔案

6. 區分環境

在開發網頁的時候,一般都會有多套執行環境,例如:

  • 在開發過程中方便開發除錯的環境。
  • 釋出到線上給使用者使用的執行環境。

6.1 環境區別

  • 線上的程式碼被壓縮
  • 開發環境可能會列印只有開發者才能看到的日誌
  • 開發環境和線上環境後端資料介面可能不同

6.2 如何使用

if(process.env.NODE_ENV == 'production'){
     console.log('生產環境');
}else{
    console.log('開發環境');
}
複製程式碼

當你使用process模組的時候,webpack會把process模組打包進來

+ new webpack.DefinePlugin({
+             'process.env': {
+                 NODE_ENV:JSON.stringify('production')
+             }
+         }),
複製程式碼

定義環境變數的值時用 JSON.stringify 包裹字串的原因是環境變數的值需要是一個由雙引號包裹的字串,而 JSON.stringify('production')的值正好等於'"production"'

new webpack.DefinePlugin({
  'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV),
})
複製程式碼

6.3 三種區分環境文案

  • 通過npm命令區分
  • 通過環境變數區分 webpack-merge
  • 程式碼中區分

7. CDN

CDN 又叫內容分發網路,通過把資源部署到世界各地,使用者在訪問時按照就近原則從離使用者最近的伺服器獲取資源,從而加速資源的獲取速度。

  • HTML檔案不快取,放在自己的伺服器上,關閉自己伺服器的快取,靜態資源的URL變成指向CDN伺服器的地址
  • 靜態的JavaScript、CSS、圖片等檔案開啟CDN和快取,並且檔名帶上HASH值
  • 為了並行載入不阻塞,把不同的靜態資源分配到不同的CDN伺服器上
    output: {
        path: path.resolve(__dirname, 'dist'),
+        filename: '[name]_[hash:8].js',
+        publicPath: 'http://img.zhufengpeixun.cn'
    },
複製程式碼

8.Tree Shaking

Tree Shaking 可以用來剔除JavaScript中用不上的死程式碼。它依賴靜態的ES6模組化語法,例如通過importexport匯入匯出。

使用Tree

  1. 不要編譯ES6模組
    {
                     loader: 'babel-loader',
                     query: {
                         presets: [
                             [
    +                               "env", {
    +                                   modules: false //含義是關閉 Babel 的模組轉換功能,保留原本的 ES6 模組化語法
    +                               }
                             ],
                             "react"
                         ]
                     }
                 }
    複製程式碼
    webpack --display-used-exports
    複製程式碼
+ const UglifyJSPlugin = require('uglifyjs-webpack-plugin');

 plugins: [
+   new UglifyJSPlugin()
 ]
複製程式碼
webpack --display-used-exports --optimize-minimize
webpack --mode  production
複製程式碼

9.提取公共程式碼

9.1 為什麼需要提取公共程式碼

大網站有多個頁面,每個頁面由於採用相同技術棧和樣式程式碼,會包含很多公共程式碼,如果都包含進來會有問題

  • 相同的資源被重複的載入,浪費使用者的流量和伺服器的成本;
  • 每個頁面需要載入的資源太大,導致網頁首屏載入緩慢,影響使用者體驗。 如果能把公共程式碼抽離成單獨檔案進行載入能進行優化,可以減少網路傳輸流量,降低伺服器成本

9.2 如何提取

  • 基礎類庫,方便長期快取
  • 頁面之間的公用程式碼
  • 各個頁面單獨生成檔案

如何使用 common-chunk-and-vendor-chunk

entry: {
        pageA: './src/pageA',
        pageB: './src/pageB'
},

optimization: {
        splitChunks: {
            cacheGroups: {
                commons: {
                    chunks: "initial",
                    minChunks: 2,
                    maxInitialRequests: 5, // The default limit is too small to showcase the effect
                    minSize: 0 // This is example is too small to create commons chunks
                },
                vendor: {
                    test: /node_modules/,
                    chunks: "initial",
                    name: "vendor",
                    priority: 10,
                    enforce: true
                }
            }
        }
    },
複製程式碼

10.開啟 Scope Hoisting

Scope Hoisting 可以讓 Webpack 打包出來的程式碼檔案更小、執行的更快, 它又譯作 "作用域提升",是在 Webpack3 中新推出的功能。

  • 程式碼體積更小,因為函式申明語句會產生大量程式碼
  • 程式碼在執行時因為建立的函式作用域更少了,記憶體開銷也隨之變小 hello.js
export default 'Hello';
複製程式碼
import str from './hello.js';
console.log(str);
複製程式碼
var util = ('Hello');
console.log(util);
複製程式碼

函式由兩個變成了一個,hello.js 中定義的內容被直接注入到了 main.js 中

const ModuleConcatenationPlugin = require('webpack/lib/optimize/ModuleConcatenationPlugin');

module.exports = {
  resolve: {
    // 針對 Npm 中的第三方模組優先採用 jsnext:main 中指向的 ES6 模組化語法的檔案
    mainFields: ['jsnext:main', 'browser', 'main']
  },
  plugins: [
    // 開啟 Scope Hoisting
    new ModuleConcatenationPlugin(),
  ],
};
複製程式碼
--display-optimization-bailout
複製程式碼

11.程式碼分離

程式碼分離是 webpack 中最引人注目的特性之一。此特效能夠把程式碼分離到不同的 bundle 中,然後可以按需載入或並行載入這些檔案。 有三種常用的程式碼分離方法:

  • 入口起點:使用 entry 配置手動地分離程式碼。
  • 防止重複:使用 splitChunks 去重和分離 chunk。
  • 動態匯入:通過模組的行內函數呼叫來分離程式碼。

11.1 多個入口

`entry: {
  index: './src/index.js',
  another: './src/another-module.js'
}
複製程式碼

11.2 防止重複

splitChunks可以將公共的依賴模組提提取到一個新生成的 chunk. common-chunk-and-vendor-chunk

optimization: {
        splitChunks: {
            cacheGroups: {
                commons: {
                    chunks: "initial",
                    minChunks: 2
                },
                vendor: {
                    test: /node_modules/,
                    chunks: "initial",
                    name: "vendor",
                }
            }
複製程式碼

11.3 動態匯入和懶載入(dynamic imports)

使用者當前需要用什麼功能就只載入這個功能對應的程式碼,也就是所謂的按需載入 在給單頁應用做按需載入優化時,一般採用以下原則:

  • 對網站功能進行劃分,每一類一個chunk
  • 對於首次開啟頁面需要的功能直接載入,儘快展示給使用者
  • 某些依賴大量程式碼的功能點可以按需載入
  • 被分割出去的程式碼需要一個按需載入的時機
document
    .getElementById('clickMe')
    .addEventListener('click', () => {
        import (/*webpackChunkName:"alert"*/
        './alert').then(alert => {
            console.log(alert);

            alert.default('hello');
        });
    });
複製程式碼
loaders: [
                {
                    loader: 'babel-loader',
                    query: {
                        presets: ["env", "stage-0", "react"]
                    }
                }
            ]
複製程式碼

12. webpack-dev-middleware 外掛

webpack-dev-middleware 外掛對更改的檔案進行監控,編譯,一般和 webpack-hot-middleware 配合使用,實現熱載入功能 webpack-dev-middleware webpack-hot-middleware

const path = require("path")
const express = require("express")
const webpack = require("webpack")
const webpackDevMiddleware = require("webpack-dev-middleware")
const webpackConfig = require('./webpack.config.js')
const app = express(),
            DIST_DIR = path.join(__dirname, "dist"),// 設定靜態訪問檔案路徑
            PORT = 9090, // 設定啟動埠
            complier = webpack(webpackConfig)

app.use(webpackDevMiddleware(complier, {
//繫結中介軟體的公共路徑,與webpack配置的路徑相同
    publicPath: webpackConfig.output.publicPath,
    quiet: true  //向控制檯顯示內容
}))

// 這個方法和下邊註釋的方法作用一樣,就是設定訪問靜態檔案的路徑
app.use(express.static(DIST_DIR))
app.listen(PORT,function(){
    console.log("成功啟動:localhost:"+ PORT)
})
複製程式碼

13. 輸出分析

  • profile:記錄下構建過程中的耗時資訊;
  • json:以 JSON 的格式輸出構建結果,最後只輸出一個 .json 檔案,這個檔案中包括所有構建相關的資訊。
webpack --profile --json > stats.json
複製程式碼

Webpack 官方提供了一個視覺化分析工具 Webpack Analyse

  • Modules:展示所有的模組,每個模組對應一個檔案。並且還包含所有模組之間的依賴關係圖、模組路徑、模組ID、模組所屬 Chunk、模組大小;
  • Chunks:展示所有的程式碼塊,一個程式碼塊中包含多個模組。並且還包含程式碼塊的ID、名稱、大小、每個程式碼塊包含的模組數量,以及程式碼塊之間的依賴關係圖;
  • Assets:展示所有輸出的檔案資源,包括 .js、.css、圖片等。並且還包括檔名稱、大小、該檔案來自哪個程式碼塊;
  • Warnings:展示構建過程中出現的所有警告資訊;
  • Errors:展示構建過程中出現的所有錯誤資訊;
  • Hints:展示處理每個模組的過程中的耗時。

備註

libraryTarget 和 library

當用 Webpack 去構建一個可以被其他模組匯入使用的庫時需要用到它們。

  • output.libraryTarget 配置以何種方式匯出庫。
  • output.library 配置匯出庫的名稱。 它們通常搭配在一起使用。

output.libraryTarget 是字串的列舉型別,支援以下配置。

var (預設)

編寫的庫將通過 var 被賦值給通過 library 指定名稱的變數。

假如配置了 output.library='LibraryName',則輸出和使用的程式碼如下:

// Webpack 輸出的程式碼
var LibraryName = lib_code;

// 使用庫的方法
LibraryName.doSomething();
假如 output.library 為空,則將直接輸出:
複製程式碼

lib_code 其中 lib_code 代指匯出庫的程式碼內容,是有返回值的一個自執行函式。

commonjs

編寫的庫將通過 CommonJS 規範匯出。

假如配置了 output.library='LibraryName',則輸出和使用的程式碼如下:

// Webpack 輸出的程式碼
exports['LibraryName'] = lib_code;

// 使用庫的方法
require('library-name-in-npm')['LibraryName'].doSomething();
其中 library-name-in-npm 是指模組釋出到 Npm 程式碼倉庫時的名稱。
複製程式碼

commonjs2

編寫的庫將通過 CommonJS2 規範匯出,輸出和使用的程式碼如下:

// Webpack 輸出的程式碼
module.exports = lib_code;

// 使用庫的方法
require('library-name-in-npm').doSomething();
CommonJS2 和 CommonJS 規範很相似,差別在於 CommonJS 只能用 exports 匯出,而 CommonJS2 在 CommonJS 的基礎上增加了 module.exports 的匯出方式。

在 output.libraryTarget 為 commonjs2 時,配置 output.library 將沒有意義。
複製程式碼

this

編寫的庫將通過 this 被賦值給通過 library 指定的名稱,輸出和使用的程式碼如下:

// Webpack 輸出的程式碼
this['LibraryName'] = lib_code;

// 使用庫的方法
this.LibraryName.doSomething();
複製程式碼

window

編寫的庫將通過 window 被賦值給通過 library 指定的名稱,即把庫掛載到 window 上,輸出和使用的程式碼如下:

// Webpack 輸出的程式碼
window['LibraryName'] = lib_code;

// 使用庫的方法
window.LibraryName.doSomething();
複製程式碼

global

編寫的庫將通過 global 被賦值給通過 library 指定的名稱,即把庫掛載到 global 上,輸出和使用的程式碼如下:

// Webpack 輸出的程式碼
global['LibraryName'] = lib_code;

// 使用庫的方法
global.LibraryName.doSomething();複製程式碼


相關文章