1. 程式人生 > >webpack進階用法你都get到了麼?

webpack進階用法你都get到了麼?

如何消除無用程式碼;打包自己的私有js庫;實現程式碼分割和動態import提升初次載入速度;配置eslint規範團隊程式碼規範;打包異常抓捕你都get到了麼?

搖樹優化:Tree Shaking

webpack借鑑了rollup構建工具,從2.0就實現支援tree shaking,其中,到webpack4.0後 通過開啟mode:'production'即預設開啟。

tree shaking原理

DCE(Dead code elimination),即死碼消除,編譯器原理中,死碼消除(Dead code elimination)是一種編譯最優化技術,它的用途是移除對程式執行結果沒有任何影響的程式碼。

其中死碼的特點:

  • 程式碼不會被執行,不可到達

  • 程式碼執行的結果不會被用到

  • 程式碼只會影響死變數(只讀不寫)

其中,tree shaking就是借鑑了這個原理,利用了ES6模組的特點:

  • 其中import、exports只能作為模組頂層的語句出現

  • import 的模組名只能是字串常量

  • import 的模組名是常量不能進行修改

tree shaking就是利用ES6的這一特點,本質就是對靜態的模組程式碼進行分析,所以需要 在構建過程中確定哪些模組的程式碼能利用到,哪些模組不需要進行區分。通過標識不需要 使用的程式碼在uglify階段刪除無用程式碼。

tree shaking概念和使用

顧名思義,tree shaking搖樹優化其中就是類似於搖晃樹,過程會使一些枯枝枯葉掉落。

tree shaking就是通過在構建過程,如果一個模組存在多個方法,如果只有其中的某個方法 使用到,則將一些沒有引用到的程式碼在這個打包過程移除,只把用到的方法打入bundle中。

通過開啟mode:'production'即可。其中,只支援ES6的語法,commonjs的方式(即require方式)不支援使用。

作用域提升:Scope Hoisting

webpack的模組機制

我們可以瞭解到,webpack打包出來的是一個IIFE(立即呼叫函式表示式,也是常說的匿名閉包), 其中,modules陣列的每一項是一個模組初始化函式,通過webpackrequire的方式來載入模組, 返回module.exports,並且通過webpackrequire_(0)的方式啟動程式。

scope hoisting原理及使用

在沒有開啟scope hoisting前,這樣構建後的程式碼會存在大量的閉包程式碼。

由於模組依賴,通過webpack打包後會轉換成自執行匿名函式,這樣, 由於大量函式閉包包裹程式碼,會導致體積增大(模組越多就越明顯);執行程式碼 時建立的函式作用域變多,導致記憶體開銷也變大。

其中,scope hoisting的概念也是借鑑了rollup的構建原理並在webpak3.0時候引入, 其原理就是將所有模組的程式碼按照引入順序放到一個函式作用域裡,然後適當的重新命名 一些變數以防止變數名衝突。這樣就可以實現減少函式宣告程式碼和記憶體花銷。

在webpack4中,也只需要把mode狀態調整為production即預設開啟,其中,需要注意的是 只支援ES6語法,commonjs的動態引入還不支援。

而webpack3中則需要配置外掛 webpack.optimize.ModuleConcatenationPlugin

程式碼分割和動態import

對於一些大型的web應用,將程式碼打包到一個chunk是不夠有效的,會導致載入的檔案過大,導致頁面載入慢,體驗差等。所以需要通過程式碼分割成多個chunks,當代碼執行到需要時候才進行載入。

一般通過抽離相同程式碼到一個共享塊或者指令碼懶載入使得初始下載的程式碼更小。
前面我們已經說到通過SplitChunksPlugin來進行通用的程式碼抽離,而懶載入指令碼的方式我們需要條件判斷等方式通過ES6的動態import的方式實現。
其中,動態import目前沒有原生支援,需要babel轉換。安裝依賴並配置.babelrc

npm i @babel/plugin-syntax-dynamic-import -D

 

配置.babelrc

 

{
     "plugins": [
        "@babel/plugin-syntax-dynamic-import"
    ]
}

 

通過動態import,點選事件載入指令碼,demo:

JavaScript語法規範:ESLint

ESLint 是一個外掛化並且可配置的 JavaScript 語法規則和程式碼風格的檢查工具,能夠及早的發現程式碼錯誤並且幫助保持團隊程式碼風格的統一。

其中比較有名的ESLint規範實踐有:eslint-config-airbnb、eslint-config-alloy、eslint-config-ivweb等。

可通過基於eslint:recommend配置對規範進行改進。

eslint規則

規則名稱錯誤級別說明
for-direction error for迴圈的方向要求必須正確
getter-return error getter必須有返回值,並且禁止返回值為undefined,比如return
no-await-in-loop off 允許在迴圈裡面使用await
no-console off 允許在程式碼裡面是有console
array-callback-return error 對於資料相關操作函式比如reduce、map、filter等,callback必須有return
accessor-pairs warn 在字串裡面出現(和)進行警告

更多規則可參考:https://eslint.org/docs/rules/

ESLint落地

具體ESLint落地的實施可以有兩個方案,通過CI/CD(持續整合/持續交付)系統整合或者和webpack等構建工具整合。

ESLint與CI/CD系統整合

gitlab整合lint的常用做法:

本地開發時可以通過增加precommit鉤子實現開發環境的檢查。
安裝husky並配置package

npm i husky -D

 

"scripts":{
    "precommit":"lint-staged"
},
"lint-staged":{
    "linters":{
        "*.js": ["eslint-fix","git add"]
    }
}

 

webpack與ESLint整合

通過使用eslint-loader,構建時檢查JS規範。這種做法比較適合新專案的使用。因為該方案會預設在開發構建時對所有檔案進行規範的檢查。

安裝基於react的eslint的依賴包,eslint-config-alloy

npm install --save-dev eslint babel-eslint eslint-plugin-react eslint-config-alloy

 

npm i eslint-loader -D

 

配置.eslintrc.js parser使用babel-eslint,並繼承eslint-config-airbnb

npm i eslint-config-airbnb -D

 

 

// .eslintrc.js
module.exports = {
    "parser": "babel-eslint",
    "extends": [
        'alloy',
        'alloy/react',
    ],
    "rules": {
        "indent": ["error",4]
    },
    "env": { // 當前生效環境
        "browser": true,
        "node": true
    }
}

 

打包元件和基礎庫

webpack不僅可以用來打包應用,也可以用來打包js庫來方便我們的日常開發。

實現大整數加法庫的打包demo

1、確定打包需求:

  • 需要打包壓縮版和非壓縮版本

  • 支援script標籤/AMD/CJS/ESM模組引入

2、js庫的目錄結構

 

.
├── dist   // 打包輸出資料夾
|   ├── webpack-larger-number.js    // 未壓縮版輸出檔案
|   └── webpack-larger-number.min.js //壓縮版
├── package.json // 依賴包配置說明
├── webpack.config.js  // 打包配置
├── index.js     // 
├── src         // 原始碼
     └── index.js      

 

3、配置webpack

相對於一般打包應用,我們需要配置output引數實現將庫暴露出去,其中,library可以指定庫的名稱

 

module.exports = {
    output: {
        filename: "[name].js",
        library: "WebpackLargeNumber", // 指定庫的全域性變數
        libraryExport: "default", 
        libraryTarget: "umd" // 支援庫引入的方式,預設以libary指定的變數名
    }
}

 

更多詳情引數可參考:https://www.webpackjs.com/configuration/output/#output-library

配置webpack,指定.min檔案壓縮

 

module.exports  = {
    mode: 'none',
    optimization: {
        minimize: true, // 預設為true,壓縮js程式碼
        minimizer: [
            new TerserPlugin({ // terser-webpack-plugin支援es6語法壓縮
                include: /\.min\.js$/
            })
        ]
    }

 

更多optimization可參考:https://webpack.docschina.org/configuration/optimization/
然後設定入口檔案,對於開發環境則引入未打包的js庫,而生產環境則使用壓縮後的庫

4、設定package的入口檔案並設定對應環境變數引入不同的庫。

// package.js
 "main": "index.js",

 

 

// index.js
if (process.env.NODE_ENV == 'production') {
    module.exports = requier('./dist/webpack-large-number.min.js')
} else {
    module.exports = require('./dist/webpack-large-number')
}

 

最後,我們也可通過npm publish釋出到npm上(ps:需要npm賬號),然後我們就可以通過npm下載依賴包 並且通過預設的匯出名WebpackLargeNumber.add('99','2')方式直接使用函式。        

大整數加法demo:https://github.com/PCAaron/blogCode/tree/master/webpack/webpack-large-number

優化構建命令列日誌:stats + friendly-errors-webpack-plugin

使用webpack打包的時候,預設會將所有的打包構建資訊打印出來,而stats選項則可以很好獲取部分需要的bundle資訊。

常見的stats值:

PresetAlternativeDescription
errors-only none 只在發生錯誤時輸出
minimal none 只在發生錯誤或有新的編譯時輸出
none false 沒有輸出
normal true 標準輸出
verbose none 全部輸出

配置webpack,其中,需要注意的是,對於webpack-dev-server,stats需要放到devServer中。

 

module.exports = { 
    stats: 'errors-only'
}

 

通過設定stats為errors-only,我們可以看到dev和build的日誌成功的話也沒有一些bundle資訊,這是,我們可以藉助friendly-errors-webpack-plugin對命令列的日誌進行優化。

安裝friendly-errors-webpack-plugin並配置。

npm i friendly-errors-webpack-plugin -D

 

配置webpack

 

const FriendlyErrorsWebpackPlugin = require('friendly-errors-webpack-plugin')
module.exports = {
    plugins: [
        new FriendlyErrorsWebpackPlugin()
    ]
}

 

構建異常和中斷處理

如果打包時候存在一些構建異常和中斷,需要捕獲並做一些異常提示或者內容上報時候,我們可以通過compiler在每次構建結束後出發done的鉤子實現異常的抓捕。

其中,我們需要藉助node的process.exit丟擲異常,預設情況下, process.exit丟擲0表示成功,err為null;而非0則執行失敗, 其中err為錯誤資訊,code為對應的狀態碼。

配置webpack

module.exports = {
    plugins: [
        function () {
            this.hooks.done.tap('done', stats => {
                if (stats.compilation.errors && stats.compilation.errors.length &&
                    process.argv.indexOf('--watch') == -1) {
                    console.log('build err')
                    process.exit(1)
                }
            })
        }
    ]
}

 

webpack系列

導讀

  • webpack導讀
  • webpack與構建工具發展史

webpack基礎篇

  • webpack的基本用法:核心概念
  • webpack的基本用法:常用配置

webpack進階篇

  • webpack的進階用法(一)

歡迎star關注更新:https://github.com/PCAaron/PCAaron.github.io

推薦閱讀

程式碼demo:https://github.com/PCAaron/blogCode/tree/master/webpack/webpack-improveMore

Scope Hoisting優化Webpack輸出:https://www.imweb.io/topic/5a43064fa192c3b460fce