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)【优化上线】

猜你喜欢

转载自blog.csdn.net/jw2268136570/article/details/104906933