webpack 生產環境優化 【實踐】

上一篇博客:生產環境打包

實踐結果

package.json
{
  "name": "webpack-dep-test-better",
  "version": "1.0.0",
  "devDependencies": {
    "@babel/core": "^7.8.7",
    "@babel/preset-env": "^7.8.7",
    "add-asset-html-webpack-plugin": "^3.1.3",
    "babel-loader": "^8.0.6",
    "core-js": "^3.6.4",
    "css-loader": "^3.4.2",
    "eslint": "^6.8.0",
    "eslint-config-airbnb-base": "^14.1.0",
    "eslint-loader": "^3.0.3",
    "eslint-plugin-import": "^2.20.1",
    "file-loader": "^5.1.0",
    "html-loader": "^0.5.5",
    "html-webpack-plugin": "^3.2.0",
    "jquery": "^3.4.1",
    "less": "^3.11.1",
    "less-loader": "^5.0.0",
    "mini-css-extract-plugin": "^0.9.0",
    "optimize-css-assets-webpack-plugin": "^5.0.3",
    "postcss-loader": "^3.0.0",
    "postcss-preset-env": "^6.7.0",
    "style-loader": "^1.1.3",
    "thread-loader": "^2.1.3",
    "url-loader": "^3.0.0",
    "webpack": "^4.42.0",
    "webpack-cli": "^3.3.11"
  },
  "browserslist": {
    "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
    ],
    "production": [
      ">0.2%",
      "not dead",
      "not op_mini all"
    ]
  },
  "eslintConfig": {
    "extends": "airbnb-base",
    "env": {
      "browser": true
    }
  },
  "sideEffects": [
    "*.css",
    "*.less"
  ],
  "dependencies": {}
}

webpack.config.js
const { resolve } = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
process.env.NODE_ENV = 'development';
const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin');
const webpack = require('webpack');
const AddAssetHtmlWebpackPlugin = require('add-asset-html-webpack-plugin');
module.exports = {
    entry:"./src/js/entry.js",
    output:{
        path:resolve(__dirname,'build'),
        //filename:'js/built.js'
        filename:'js/built.[contenthash:10].js'
    },
    module:{
        rules:[
        //    {
        //    test: /\.js$/,
        //    exclude: /node_modules/,
        //    enforce: 'pre',
        //    loader: 'eslint-loader',
        //    options: {
        //        fix: true
        //    }
        //},
            {
                oneOf:[
                    {
                    test:/\.less$/,
                    use:[MiniCssExtractPlugin.loader,'css-loader',{
                        loader: 'postcss-loader',
                        options: {
                            ident: 'postcss',
                            plugins: () => [require('postcss-preset-env')()]
                        }
                    },'less-loader']
                },
                    {
                        test:/\.css$/,
                        use:[MiniCssExtractPlugin.loader,'css-loader',{
                            loader: 'postcss-loader',
                            options: {
                                ident: 'postcss',
                                plugins: () => [require('postcss-preset-env')()]
                            }
                        }]
                    },
                    {
                        test: /\.js$/,
                        exclude: /node_modules/,
                        use:[
                            'thread-loader',
                            {
                                loader: 'babel-loader',
                                options: {
                                    presets: [
                                        [
                                            '@babel/preset-env',
                                            {
                                                useBuiltIns: 'usage',
                                                corejs: {
                                                    version: 3
                                                },
                                                targets: {
                                                    chrome: '60',
                                                    firefox: '60',
                                                    ie: '9',
                                                    safari: '10',
                                                    edge: '17'
                                                }
                                            }
                                        ]
                                    ],
                                    cacheDirectory: true
                                }
                            }
                        ]
                    },
                    {
                        test: /\.(jpg|png|gif)$/,
                        loader: 'url-loader',
                        options: {
                            limit: 8 * 1024,
                            name: '[hash:10].[ext]',
                            esModule:false,
                            outputPath: 'images'
                        }
                    },
                    {
                        test: /\.html$/,
                        loader: 'html-loader'
                    },
                    {
                        exclude: /\.(html|js|css|less|jpg|png|gif)/,
                        loader: 'file-loader',
                        options: {
                            name: '[hash:10].[ext]',
                            outputPath: 'media'
                        }
                    }]
            }
        ]
    },
    plugins:[
        new HtmlWebpackPlugin({
            template: './src/index.html',
            minify: {
                collapseWhitespace: true,
                removeComments: true
            }
        }),
        new MiniCssExtractPlugin({
            filename: 'css/built.[contenthash:10].css'
        }),
        new OptimizeCssAssetsWebpackPlugin(),

        // 作用:模塊掃描時,不打包dll/manifest.json中說明的模塊。
        new webpack.DllReferencePlugin({
            manifest: resolve(__dirname, 'dll/manifest.json')
        }),
        // 作用:將dll/bundle.js打包輸出到build/bundle.js,並在html中自動引入該資源
        new AddAssetHtmlWebpackPlugin({
            filepath: resolve(__dirname, 'dll/jquery.js')
        })
    ],
    mode: 'production',
    devtool:'source-map',
    externals: {
        jquery: '$'
    },
    optimization: {
        splitChunks: {
            chunks: 'all'
        }
    }
};

實踐準備

創建項目:webpack_dep_test_better
初始化
npm init
npm i webpack webpack-cli -D
// 上篇博客 生產環境打包 涉及到的所有依賴,可複製執行以下命令下載相關庫。
// 或者複製上篇博客產出的package.json更改名字後執行npm i
npm i @babel/core @babel/preset-env babel-loader core-js css-loader eslint eslint-config-airbnb-base eslint-loader eslint-plugin-import file-loader html-loader html-webpack-plugin less less-loader mini-css-extract-plugin optimize-css-assets-webpack-plugin postcss-loader postcss-preset-env style-loader url-loader -D
src/js/entry.js:空入口文件
webpack.config.js:複製上一篇博客實踐產生的webpack.config.js文件
const { resolve } = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
process.env.NODE_ENV = 'development';
const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin');
module.exports = {
    entry:"./src/js/entry.js",
    output:{
        path:resolve(__dirname,'build'),
        filename:'js/built.js'
    },
    module:{
        rules:[
            {
                test:/\.less$/,
                use:[MiniCssExtractPlugin.loader,'css-loader',{
                    loader: 'postcss-loader',
                    options: {
                        ident: 'postcss',
                        plugins: () => [require('postcss-preset-env')()]
                    }
                },'less-loader']
            },
            {
                test:/\.css$/,
                use:[MiniCssExtractPlugin.loader,'css-loader',{
                    loader: 'postcss-loader',
                    options: {
                        ident: 'postcss',
                        plugins: () => [require('postcss-preset-env')()]
                    }
                }]
            },
            {
                test: /\.js$/,
                exclude: /node_modules/,
                enforce: 'pre',
                loader: 'eslint-loader',
                options: {
                    fix: true
                }
            },
            {
                test: /\.js$/,
                exclude: /node_modules/,
                loader: 'babel-loader',
                options: {
                    presets: [
                        [
                            '@babel/preset-env',
                            {
                                useBuiltIns: 'usage',
                                corejs: {
                                    version: 3
                                },
                                targets: {
                                    chrome: '60',
                                    firefox: '60',
                                    ie: '9',
                                    safari: '10',
                                    edge: '17'
                                }
                            }
                        ]
                    ]
                }
            },
            {
                test: /\.(jpg|png|gif)$/,
                loader: 'url-loader',
                options: {
                    limit: 8 * 1024,
                    name: '[hash:10].[ext]',
                    esModule:false,
                    outputPath: 'images'
                }
            },
            {
                test: /\.html$/,
                loader: 'html-loader'
            },
            {
                exclude: /\.(html|js|css|less|jpg|png|gif)/,
                loader: 'file-loader',
                options: {
                    name: '[hash:10].[ext]',
                    outputPath: 'media'
                }
            }
        ]
    },
    plugins:[
        new HtmlWebpackPlugin({
            template: './src/index.html',
            minify: {
                collapseWhitespace: true,
                removeComments: true
            }
        }),
        new MiniCssExtractPlugin({
            filename: 'css/built.css'
        }),
        new OptimizeCssAssetsWebpackPlugin()
    ],
    mode: 'production'
};

實踐過程

一:oneOf【優化構建】

1.優化思路
  • module的rule匹配時,除js文件需匹配兩個rule(eslint-loader,babel-loader)之外,其它文件只需匹配一個rule,所以這些文件一旦匹配成功就無需再往下匹配。
2.配置代碼的結構:使用oneOf之前

在這裏插入圖片描述

3.配置代碼的結構:使用oneOf之後

在這裏插入圖片描述

二:babel緩存【優化構建】

1.優化思路
  • 一個js模塊發生變化,只需要使用babel對這一個js文件進行再編譯,而無需對其它js文件進行再編譯處理。
2.配置示例
  • babel-loader的配置加上cacheDirectory: true
    在這裏插入圖片描述

三:多進程打包【優化構建】

可選:新的進程啓動和通信都存在開銷,使用不當不但無法優化構建速度,還會拖慢構建速度。

1.優化思路
  • nodejs默認是單線程執行的。
  • 合理使用多進程進行打包可以加快構建速度。(當某個loader要處理的文件及其內容很多導致運行時間很長時使用,如babel-loader)
2.配置示例
  • 下載thread-loader
npm i thread-loader -D
  • 配置thread-loader
    在這裏插入圖片描述

四:無需打包(externals)【優化構建】

注意:親測與eslint-loader共同使用時會報錯import/no-unresolved並打包失敗。

1.優化思路
  • 部分第三方庫可使用CDN做外部加載,無需打包進bundle之中。
2.配置示例
  • src/index.html:手動引入該庫
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>webpack_dep_test_better</title>
</head>
<body>
<script src="https://cdn.bootcss.com/jquery/1.12.4/jquery.min.js"></script>
</body>
</html>
  • webpack.config.js:配置模塊與庫的映射
module.exports = {
...,
externals: {
      // 建立映射關係
      // 鍵爲模塊名,如entry.js中import $ from 'jquery'引入的jquery模塊
      // 值爲庫對象,如<script src="https://cdn.bootcss.com/jquery/1.12.4/jquery.min.js"></script>方式引入在全局暴露的jQuery/$對象。
      jquery: '$'// 值爲'jQuery'也可
  }
}
3.與eslint的兼容問題
  • 待處理…

五:支線打包(dll)【優化構建】

1.優化思路
  • 依賴的第三方庫/基本不變的代碼可打包得到一個獨立的bundle,打包一次即可,沒有必要每次構建都重新打包。
2.優化後的目錄結構

在這裏插入圖片描述

3.配置示例
  • webpack.dll.js:webpack支線配置文件,用於生成dll/bundle.js與dll/manifest.json文件。
const { resolve } = require('path');
const webpack = require('webpack');

module.exports = {
  entry: {
    // 鍵:dll/bundle的名稱
    // 值:要打包的庫數組
    jquery: ['./src/js/jquery.js']
  },
  output: {
    // 輸出dll/bundle的具體文件名
    filename: '[name].js',
    path: resolve(__dirname, 'dll'),
    library: '[name]_[hash]' // 聲明爲window的屬性,var [name]_[hash] = function....
  },
  plugins: [
    // 生成manifest.json文件
    new webpack.DllPlugin({
      name: '[name]_[hash]', // 該庫聲明在window的屬性:var [name]_[hash] = function....
      path: resolve(__dirname, 'dll/manifest.json') // 輸出文件路徑
    })
  ],
  mode: 'production'
};
// webpack指定配置文件運行(默認webpack.config.js)
webpack --config webpack.dll.js
  • webpack.config.js:webpack主線配置文件,忽略該支線所有庫的打包(讀取dll/manifest)並將dll/bundle打包到build/bundle(同時在build/index.html中引入)。
npm i add-asset-html-webpack-plugin -D
...,
const webpack = require('webpack');
const AddAssetHtmlWebpackPlugin = require('add-asset-html-webpack-plugin');
module.exports = {
plugins:[
	...,
	// 作用:讀取manifest.json,content鍵的值告訴webpack哪些庫不參與打包。
	new webpack.DllReferencePlugin({
	    manifest: resolve(__dirname, 'dll/manifest.json')
	}),
	// 作用:將dll/bundle.js打包輸出到build/bundle.js,並在build/index.html中引入該資源
	new AddAssetHtmlWebpackPlugin({
	    filepath: resolve(__dirname, 'dll/jquery.js')
	})
]
}
  • entry.js中引入該模塊’./jquery’:主線每次打包都忽略此模塊,僅在支線打包一次。
import $ from './jquery'// './jquery'主線每次打包都忽略此模塊,在支線打包一次。
// eslint-disable-next-line
console.log(jquery_891268e901abb8a8c479);// mainfest.json中的name值
console.log($);

六:文件緩存問題【優化上線】

1.優化思路
  • 瀏覽器從服務端下載的文件資源帶有過期時間,文件在過期之前不會向服務端發送獲取該文件的http請求。
  • 只要每次構建後build/index.html中引入的bundle命名不一致(帶上hash值),就可以避免使用同名的本地緩存文件。
  • 合理利用瀏覽器的本地緩存,只請求服務端有更新的文件,不請求服務端沒有更新的文件。
2.配置示例

本地緩存是否合理利用,取決於命名時不同hash值的選擇。如:更新built.css後,只需要built.css的最終文件名發生變化,不需要built.js的最終文件名發生變化。

  • 【棄用】hash:webpack構建時產生的hash值。(如:built.css與built.js同一次構建,hash值一致,每次構建後文件名一起變化。)
  • 【棄用】chunkhash:chunk的hash值。(如:css由entry.js引入,built.css、built.js同屬一個chunk,chunkhash值一致,每次構建後文件名一起變化)
  • 【選用】contenthash:根據文件內容生成的hash值。(built.css與built.js內容不一致,只有更新文件的contenthash以及最終文件名發生變化)
module.exports = {
    entry:"./src/js/entry.js",
    // 1.bundle.js的命名
    output:{
        path:resolve(__dirname,'build'),
        //filename:'js/built.js'
        filename:'js/built.[contenthash:10].js'
    },
    ...,
    // 2.bundle.css的命名
    plugins:[
    	...,
   	  	new MiniCssExtractPlugin({
   	  		//filename: 'css/built.css'
           	filename: 'css/built.[contenthash:10].css'
        }),
    	...
	]
   
}
3.目錄結構:打包後

在這裏插入圖片描述

七:去除無使用代碼 / 模塊(tree shaking)【優化上線】:

1.優化思路
  • 打包時去除引入而不被使用的模塊。
  • 打包時去除引入模塊中不被使用的代碼。
2.試驗1

前提:模塊使用import引入、mode爲production

  • src/js/test.js:被搖的模塊
// eslint-disable-next-line
console.log("test");
function fn(){
// eslint-disable-next-line
    console.log("test");
}
export default {
    fn
};
  • src/js/entry.js:引入被搖的模塊
import test from './test'
import '../css/test.css'
//test.fn();
  • build/js/built.js:test.js模塊被部分打包
... function(e,t,n){"use strict";n.r(t),console.log("test");n(0)}]);
  • 試驗結果:css打包成功、console一個test
2.試驗2
  • 配置不被搖的模塊:package.json(json文件內不允許註釋)
"sideEffects": [
   "*.css",
   "*.less"
 ]
  • src/js/entry.js:引入被搖的模塊
import test from './test'
import '../css/test.css'
//test.fn();
  • build/js/built.js:test.js模塊不被打包
2.試驗3
  • 配置不被搖的模塊:package.json(json文件內不允許註釋)
"sideEffects": [
   "*.css",
   "*.less"
 ]
  • src/js/entry.js:引入被搖的模塊
import test from './test'
import '../css/test.css'
test.fn();
  • build/js/built.js:fn以及console.log都被打包
... function(e,t,n){"use strict";n.r(t),console.log("test");var r={fn:function(){console.log("test")}};n(0);r.fn()}]);
3.試驗結論
  • 試驗結果表明,不管是否做sideEffects配置,css打包都有效。
  • 模塊內有效的代碼會被留下(如test.js中的console.log(“test”),此行代碼被視爲有效與配置有關),模塊內無效的代碼會被過濾(如如test.js中的fn方法)。
  • 模塊內沒有有效的代碼,那麼整個模塊都不會被打包。
4. 對比試驗123,神奇的現象??
  • 未配置sideEffects之前,test.js中的console.log(“test”)被視爲有效代碼,模塊被部分過濾。
  • 配置sideEffects之後,test.js中的console.log(“test”)被視爲無效代碼,模塊全部過濾。
  • 配置sideEffects之後,同時test.js的fn方法被調用,test.js中的console.log(“test”)被視爲有效代碼。

八:代碼分割(code split)【優化上線】

1.優化思路
  • 把一個大的bundle文件(built.js)拆分爲多個小的bundle文件以支持bundle的並行加載與懶加載。
2.配置示例
  • 1.第三方庫(node_module)的拆分
module.exports = {
...,
optimization: {
     splitChunks: {
         chunks: 'all'
     }
 }
}
  • 2.懶加載和預加載文件的拆分(見九)
  • 3.其它入口的拆分(多entry形成多chunk,打包產生多bundle)

九:js的懶加載和預加載【優化上線】

注意:預加載有瀏覽器兼容問題,慎用。

1.優化思路
  • 懶加載可以實現延後加載、按需加載,以更合理、更高效的方式加快瀏覽器的加載速度。
2.代碼示例
// 在按鈕點擊的回調函數中對纔對test.js模塊進行加載
document.getElementById('btn').onclick = function() {
    import(/* webpackChunkName: 'test' */'./test.js').then(({fn}) => {
        fn();
    });
};
  • 低版本webstorm會對import報錯(錯誤提示)

十:離線訪問(pwa)【優化上線】

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