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值:

Preset Alternative Description
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進階篇

歡迎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/5a43064fa192c3b460fce360


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM