Webpack5学习笔记(四):Webpack优化配置

Webpack5学习笔记(四):Webpack优化配置

Author: 哇哇小仔
Date: 2021-03-18
Webpack版本:webpack 5.24.4 webpack-cli 4.5.0
说明:尚硅谷webpack视频教程总结的原创笔记

第5章:webpack优化配置

5.1 HMR

  1. HMR
    1. hot module replacement 热模块替换/模块热替换
    2. 作用:一个模块发生变化,只会重新打包这一个模块,而不是重新打包所有模块,极大提升构建速度
  2. 不同文件的处理方式
    1. css样式文件
      • 可以使用HMR功能,因为style-loader实现了
      • 这也是为什么开发模式使用style-loader,因为方便开发。生产模式为了效率,不需要HMR功能
    2. js文件
      • 默认没有HMR功能
      • 需要修改js代码,添加支持HMR功能的代码
      • 注意:HMR功能对于js的处理,只能处理非入口的js文件
    3. html文件
      • 默认没有HMR功能,同时会导致问题:html文件不能热更新了
      • 解决:修改entry入口,将html文件引入
        entry: ['./src/js/index.js', './src/index.html']
      • html因为一个项目里边只有一个html文件,不需要做HMR功能
 // 比如修改入口的index.js文件
if (module.hot){
	 // 一旦module.hot为true,说明开启了HMR功能 --> 让HMR代码生效
	module.hot.accept('./print.js', funtion(){
		// 方法会监听print.js文件的变化,一旦发生变化,其他js模块不会重新打包构建	
		// 而只会重新加载print.js并执行这个回调函数
		print();
	 })
}
  1. 开发模式下开启HMR模式
        // 开启HMR只需要在devServer中设置hot属性为true即可
        devServer: {
            contentBase: resolve(__dirname, 'build');
            compress: true,
            port: 3000,
            open: true,
            hot: true  // 开启HMR功能
        }

5.2 source-map

  1. 简介:source-map 是一种提供源代码到构建后代码的技术,如果构建后代码出错了,会通过映射关系追踪到源代码的错误
  2. 启用source-map:
//在webpack.config.js中加入
devtool: 'source-map'
  1. source-map的类型:
    [inline-|hidden-|eval-][nosources-][cheap-[module-]]source-map
  2. 内联 和 外部 source-map 的区别
    1. 外部生成文件,内联没有
    2. 内联构建速度快
  3. source-map类型介绍:
    1. source-map: 外部
      • 错误代码的准确信息,源代码的错误位置
    2. inline-source-map:内联
      • 只生成一个内联的source-map
      • 错误代码的准确信息,源代码的错误位置
    3. hidden-source-map:外部
      • 错误代码的错误原因,但是没有错误位置
      • 不能追踪到源代码的错误,只能提示到构建后代码的错误位置
      • 为了隐藏源代码
    4. eval-source-map:内联
      • 每一个文件都生成对应的source-map,都在eval
      • 错误代码的准确信息,源代码的错误位置
    5. nosources-source-map:外部
      • 错误代码的准确信息,但是没有任何源代码信息
      • 为了隐藏代码
    6. cheap-source-map:外部
      • 错误代码的准确信息,源代码的错误位置
      • 但是只能精确到行
    7. cheap-module-source-map:外部
      • 错误代码的准确信息,源代码的错误位置
      • module会将loader的source-map加入
  4. 开发环境:要求速度快,调试友好
    1. 速度:eval > inline > cheap
      • eval-cheap-source-map
      • eval-source-map
    2. 调试友好
      • source-map
      • cheap-module-source-map
      • cheap-source-map
    3. 选择:
      eval-source-map / eval-cheap-source-map
      (react和vue用的都是eval-source-map)
  5. 生产环境:源代码要不要隐藏?调试是否友好?
    1. 内联会让代码体积非常大,所以在生产环境不用内联
    2. nosources-source-map 全部隐藏
    3. hidden-source-map 只隐藏源代码,会提示构建后的代码错误
    4. 较好的选择:source-map / cheap-module-source-map

5.3 oneOf

  1. 作用:优化生产环境的打包速度,oneOf中的loader每次只会有一个被执行
  2. 用法:
        const { resolve } = require('path')
        const { MiniCssExtractPlugin} = require('mini-css-extract-plugin')

        const commonCssLoader = [ 
            MiniCssExtractPlugin.loader,
            'css-loader',
            {
                loader: 'postcss-loader',
                options: {
                    postcssOptions: {
                        ident: 'postcss',
                        plugins: ['postcss-preset-env']
                    }
                }
            }

        module.exports = {、
            entry: './src/js/index.js',
            output: {
                filename: 'js/built.js',
                path: resolve(__dirname, 'build')
            },
            module: {
                rules: [
                    {
                        test: /\.js$/,
                        exclude: /node_modules/,
                        enforce: 'pre'
                        loader: 'eslint-loader',
                        options: {
                            fix: true 
                        }
                    },
                    {
                        // 以下loader只会匹配一个
                        // 注意:不能有两个配置处理同一个类型的文件
                        // 因此,只能写一个js的处理loader在里边
                        oneOf: [
                            {
                                test: /\.css$/,
                                use: [...commonCssLoader]
                            },     
                            {
                                test: /\.less$/,
                                use: [...commonCssLoader, 'less-loader']
                            },
                            {
                                test: /\.js$/,
                                loader: 'babel-loader',
                                exclude: /node_modules/,
                                options: {
                                    presets: [
                                        '@babel/preset-env',
                                        {
                                            useBuiltIns: 'usage',
                                            corejs: {version: 3},
                                            targets: {
                                                chrome: '60',
                                                firefox: '50'
                                            }
                                        }
                                    ]
                                }
                            },
                            {
                                test: /\.(jpg|png|gif)$/,
                                loader: 'url-loader',
                                options: {
                                    limit: 8 * 1024,
                                    name: '[hash:10].[ext]',
                                    esModule: false,
                                    outputPath: 'imgs'
                                }
                            },
                            {
                                test: /\.html$/,
                                loader: 'html-withimg-loader'
                            },
                            {
                                exclude: /\.(html|js|less|css|jpg|png|gif)$/,
                                loader: 'file-loader',
                                options: {
                                    outputPath: 'media',
                                    name: '[hash:10].[ext]'
                                }
                            }
                        }
                    ]
                ]
            },
            plugins: [
                new HtmlWebpackPlugin({template: './src/index.html'}),
                new MiniCssExtractPlugin({filename: 'css/built.css'}),
                new OptimizeCssAssetsWebpackPlugin()
            ],
            mode: 'production'
        }

5.4 缓存

  1. babel缓存
    1. 设置:在babel-loader的options中添加 cacheDirectory: true
    2. 作用: 让第二次打包构建速度更快
  2. 文件资源缓存
    1. hash:每次webpack打包构建时会生成一个唯一的hash值
      • 问题:因为js和css同时使用一个hash值,如果重新打包,会导致所有缓存失效,但是我们可能只改动一个文件
    2. chunkhash: 根据chunk生成的hash值,如果打包来源于同一个chunk,那么hash值就一样
      • 问题:js和css的hash值还是一样的,因为css是在js中被引入的,所有属于一个chunk
    3. contenthash:会根据文件内容生成hash值,不同文件的hash值肯定是不一样的
      • 让代码上线运行速度更快
  3. 修改配置文件
        const { resolve } = require("path");
        const MiniCssExtractPlugin = require("mini-css-extract-plugin");
        const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin');
        const HtmlWebpackPlugin = require("html-webpack-plugin");
        
        const commonCssLoader = [
          MiniCssExtractPlugin.loader,
          'css-loader',
          {
            // 兼容性处理
            loader: "postcss-loader",
            options: {
              postcssOptions: {
                ident: "postcss",
                plugins: ["postcss-preset-env"],
              },
            },
          },
        ];
        
        module.exports = {
          entry: "./src/js/index.js",
          output: {
            // 文件资源缓存:js文件名加上10位contenthash值
            filename: "js/built.[contenthash:10].js",
            path: resolve(__dirname, "build"),
          },
          module: {
            rules: [
              {
                test: /\.js$/,
                exclude: /node_modules/,
                enforce: 'pre',  // 优先执行
                loader: "eslint-loader",
                options: {
                  fix: true
                }
              }, 
              {
                oneOf: [
                  {
                    test: /\.css$/,
                    use: [...commonCssLoader],
                  },
                  {
                    test: /\.less$/,
                    use: [...commonCssLoader,'less-loader'],
                  },
                  {
                    // js兼容性处理
                    test: /\.js$/,
                    exclude: /node_modules/,
                    loader: 'babel-loader',
                    options: {
                      presets: [
                        [
                          '@babel/preset-env',
                          {
                            useBuiltIns: 'usage',
                            corejs: {version: 3},
                            targets:{
                              chrome: '60',
                              firefox: '50'
                            }
                          },
                        ],
                      ],
                      // 开启babel缓存
                      // 第二次构建时,会读取之前的缓存
                      cacheDirectory: true
                    }
                  },
                  {
                    // 处理图片
                    test: /\.(jpg|png|gif)$/,
                    loader: 'url-loader',
                    options: {
                      esModule: false,
                      name: "[hash:10].[ext]",
                      limit: 8 * 1024,
                      outputPath: 'imgs'
                    }
                  },
                  {
                    test: /\.(html)$/,
                    loader: 'html-withimg-loader',
                  },
                  {
                    exclude: /\.(html|js|less|css|jpg|png|gif)$/,
                    loader: 'file-loader',
                    options: {
                      outputPath: 'media',
                      name: "[hash:10].[ext]"
                    }
                  }
                ]
              }
            ],
          },
          plugins: [
            // 文件资源缓存:css文件名加上10位contenthash值
            new MiniCssExtractPlugin({ filename: "css/built.[contenthash:10].css" }),
            new OptimizeCssAssetsWebpackPlugin(),
            new HtmlWebpackPlugin({
              template: './src/index.html',
            })
          ],
          mode: "production",
          devtool: 'source-map'
        };

5.4 tree shaking

  1. 去掉无用的代码,使代码的体积更小
  2. 当(1)必须使用ES6模块化(2)开启production模式 时,自动对代码进行tree shaking
  3. 作用:减少代码打包体积
  4. 问题:tree shaking 可能无意把一些副作用文件干掉,比如css,需要在package.json中配置
    1. "sideEffects": "false":“false” 所有代码都没有副作用(都可以进行tree shaking)。问题:可能会把css / @babel/pollyfill (副作用)文件干掉,打包后没有css文件
    2. "sideEffects": ["*.css", "*.less"]:把css,less等标记为不要进行 tree shaking 的资源

5.5 code split

  1. 方式一:单入口 变为 多入口
        const { resolve } = require('path');
        const HtmlWebpackPlugin = require('html-webpack-plugin');
        module.exports = {
            // 单入口
            // entry: './src/js/index.js',
            // 多入口,有几个入口就打包成几个chunk
            entry: {
                main: './src/js/index.js',
                test: './src/js/test.js'
            },
            output: {
                filename: 'js/[name].[contenthash:10].js',
                path: resolve(__dirname, 'build')
            },
            module: {},
            plugins: [    
                new HtmlWebpackPlugin({template: './src/index.html'})
            ],
            mode: 'production'
        }
问题:如果多入口中有多个package都引入了node\_modules的依赖,这样会造成node\_modules打包多次
2. 方式二:多入口,加入对公共依赖打包的优化(用的比较少)

        const { resolve } = require('path');
        const HtmlWebpackPlugin = require('html-webpack-plugin');
        module.exports = {
            entry: {
                main: './src/js/index.js',
                test: './src/js/test.js'
            },
            output: {
                filename: 'js/[name].[contenthash:10].[ext]',
                path: resolve(__dirname, 'build')
            },
            module: {},
            plugins: [
                new HtmlWebpackPlugin({template: './src/index.html'})
            ],
            mode: 'production',
            // 1. 可以node_modules中代码单独打包一个chunk,最终输出
            // 2. 自动分析多入口chunk中有没有公共的文件,如果有会打包成单独一个chunk,不会重复打包多次
            optimization: {
                splitChunks: {
                    chunks: 'all'
                }
            }
        }
  1. 方式三:单入口,加上optimization的方式(保证将node_modules的代码进行分割),其次,在js文件中修改,使其他文件也能够单独打包
    1. 修改配置文件
           const{ resolve } = require('path');
           const HtmlWebpackPlugin = require('html-webpack-plugin');
           module.exports = {
               entry: './src/js/index.js',
               output: {
                   filename: 'js/[name].[contenthash:10].js',
                   path: resolve(__dirname, 'build')
               },
               module: {},
               plugin: [
                   new HtmlWebpackPlugin({template: './src/index.html'})
               ],
               mode: 'production',
               optimization: {
                   splitChunks: {
                       chunks: 'all'
                   }
               }
           }
  1. 修改js文件:通过js代码让某个文件单独打包成一个chunk,import动态导入语法,能将某个文件单独打包
            // import 返回一个promise对象
            // test.js中有个两数相乘的方法 mul(x, y)
            /* webpackChunkName: "" */
            // 是 Magic Comments(魔术注释法)。Webpack通过增加内联注释来告诉运行时,该有怎样的行为。
            // 通过向import中添加注释,我们可以执行诸如命名chunk或选择不同模式之类的操作。
            import(/* webpackChunkName: "test" */'./test')  
                .then(({mul}) => {
                    // eslint-disable-next-line
                    console.log(mul(3, 5))
                })
                .catch(() => {
                    // eslint-disable-next-line
                    console.log('文件加载成功');
                })

5.6 懒加载和预加载

5.6.1 Lazy loading
  1. 懒加载:延迟加载,在触发某些条件时才加载文件。也就是在文件需要时再加载
  2. 实现方法:主要是通过修改js代码实现,将import放在一个异步的回调函数中
  3. 例子:点击按钮计算一些东西
        document.getElementById('btn').onclick(function(){
            // import放在异步的回调函数中,点击按钮才会加载test.js
            // 这个语法和分割代码的没有什么区别,因此懒加载的前提条件一定是先进行代码分割
            import(/* webpackChunkName: 'test' */ .'/test/')
                .then(({mul}) => {  // 解构赋值,找到mul方法
                    console.log(mul(4, 5))
                })
        })
5.6.2 预加载 Prefetch
  1. 预加载:prefetch会在使用之前,提前加载js文件
  2. 特点:正常加载可以认为是并行加载(同一时间加载多个文件),而预加载prefetch:等其他资源加载完毕,浏览器空闲了再偷偷加载资源。
  3. 懒加载是用的时候再加载,如果文件较大,会给用户第一次使用时造成延续的效果;预加载不会阻塞其他资源的加载,而且不会造成延迟的效果,但是兼容性比较差,只能在一些PC端的高版本浏览器中使用,预加载慎用!
  4. 例子:点击按钮计算一些东西
        document.getElementById('btn').onclick(function(){
            import(/* webpackChunkName:'test', webpackPrefetch: true */'./test')
                .then(({mul}) => {
                    console.log(mul(2, 4))
                })
        })

5.7 PWA

  1. PWA(Progressive Web App):渐进式网络应用开发程序(离线可访问技术)
  2. PWA有serviceWorker加上Cache方法构成的,作用时帮助我们像APP应用一样,可以离线访问网页,性能也更好。但是PWA由于兼容性问题,普及开来还需要时间,一些大厂在用,比如淘宝。(2021年,貌似淘宝也放弃了PWA技术:))
  3. 使用插件:workbox --> workbox-webpack-plugin
  4. 实现
    1. 修改配置文件
            // 在webpack.config.js中 plugins 添加一个插件
            new WorkboxWebpackPlugin.GenerateSW({
                /* 
                    1 帮助 serviceWorker 快速启动
                    2 删除旧的 serviceWorker
                    最终 生成一个 serviceWorker 的配置文件,通过这个serviceWorker文件注册serviceWorker
                 */
                  clientsClaim: true,
                  skipWaiting: true
            })
    
    1. 修改入口js文件,注册serviceWorker(有兼容性问题,还需要处理兼容性问题)
            if ('serviceWorker' in navigator) {
                window.addEventListener('load', () => {
                navigator.serviceWorker.register('/service-worker.js')
                      .then(() => {
                        console.log('service worker 注册成功');
                      })
                      .catch(() => {
                        console.log('service workder 注册失败');
                      });
                });
            }
    
  5. 使用
    1. 注意eslint不认识 window、navigator全局变量。解决:需要修改package.json中eslintConfig配置
            "eslintConfig": {
                "extends": "airbnb-base",
                "env": {
                    "browser": true  // 支持浏览器端全局变量
                }
            }
    
    1. sw代码必须运行在服务器上,可以用serve这个库,能帮我快速创建一个服务器
      1. 安装 npm i serve -g
      2. 启动 serve -s build (启动服务器,将build目录下所有资源作为静态资源暴露出去)
    2. 最后再构建:webpack

5.8 多进程打包

  1. 使用 thread-loader,将thread-loader放在其他loader上边,就可以开启多进程打包
  2. 注意:进程开启大概为600ms, 进程通信也有开销,只有工作消耗时间比较长,才需要多进程打包
  3. 多进程打包一般时对js文件,因为文件会比较多
  4. 实现
        // 比如在js兼容性处理时,使用多进程打包
        {
            test: /\.js$/,
            exclude: /node_modules/,
            use: [
                // 不需要修改thread-loader配置时
                // 'thread-loader',
                // 需要修改thread-loader配置时
                {
                    loader: 'thread-loader',
                    options: {
                        workers: 2  // 设置进程数目
                    }
                },
                {
                    loader: 'babel-loader',
                    options: {
                        presets: [
                            [
                                '@babel/preset-env',        
                                {
                                    useBuildIns: 'usage',
                                    corejs: {version: 3},
                                    targets: {
                                        chrome: '60',
                                        firefox: '50'
                                    }
                                }
                            ]
                        ],
                        // 开启babel缓存
                        cacheDirectory: true
                    }
                }
            ]
        }

5.9 externals

  1. 作用:防止将某些包打包到最后输出的bundle中。比如,我们使用jQuery依赖,我们如果用cdn连接使用jQuery,我们不希望打包jQuery进来

  2. 修改配置文件webpack.config.js

     externals: {
         // npm包名 -- 页面引入jquery时提供的变量
         jquery: 'jQuery' // 拒绝jQuery被打包进来
     }
    
  3. 忽略打包之后,一定要记得在index.html中将jQuery手动引进来

     <script src='https://cdn.bootcdn.net/ajax/libs/jquery/1.9.1/jquery.min.js'></script>
    

5.10 dll

  1. 使用dll技术对某些库进行单独打包,比如jquery,React,Vue
  2. 需要一个 webpack.dll.js 文件(这个文件名可以修改)。以jQuery单独打包为例,单独打包后在dll文件夹中会生成一个manifest.json文件和打包后的jQuery文件。manifest.json提供一个打包后的库和原来的库的映射关系,可以告诉webpack哪些库不需要打包
       const { resolve } = require('path');
       const webpack = require('webpack');
       
       module.exports = {
           entry: {
               // 最终打包生成的[name] --> jquery
               // ['jquery'] --> 要打包的库时jQuery
               jquery: ['jquery']
           },
           output: {
               filename: '[name].js',
               path: resolve(__dirname, 'dll'),
               library: '[name]_[hash:10]', // 打包的库里边向外暴露出去的那内容叫什么名字
           },
           plugins: [
               new webpack.DllPlugin({
                   // 打包生成一个manifest.json文件,提供和jquery的映射
                   // 通过这个映射就知道jquery这个库不需要打包,包的名称为 '[name]_[hash:10]'
                   name: '[name]_[hash:10]', // 映射库的暴露的内容的名称
                   path: resolve(__dirname, 'dll/manifest.json')
               })
           ],
           mode: 'production'
       }
  1. 运行webpack.dll.json文件
    • 当运行webpack时,默认查找 webpack.config.js 配置文件,如果需要运行 webpack.dll.js 文件,使用下面的命令:webpack --config webpack.dll.js
  2. 使用dll打包后,还需要修改webpack.config.js配置
        plugins: [
            new HtmlWebpackPlugin({
              template: "./src/index.html",
            }),
            // 告诉webpack哪些库不参与打包,同时使用jQuery时的名称也需要改变
            new webpack.DllReferencePlugin({
                  manifest: resolve(__dirname, 'dll/manifest.json')
            }),
            // 将某个文件打包输出去,并在HTML中自动引入该资源
            // 将之前打包的js库引进来
            new AddAssetHtmlWebpackPlugin({
                  filepath: resolve(__dirname, 'dll/jquery.js')
            })
        ],
  1. dll和externals的区别
    1. dll是需要打包,只需要打包一次,以后就不需要再打包了
    2. 如果是使用cdn链接引入,建议使用externals的方式
    3. 如果是第三方的库,需要打包放在一起,可以使用dll

5.11 性能优化总结

5.11.1 webpack性能优化
  1. 开发环境性能优化
  2. 生产环境性能优化
5.11.2 开发环境性能优化
  1. 优化打包构建速度
    1. HMR 模块热替换:利用缓存,只需要替换修改的文件
      • css文件:style-loader自动做,不需要我们操心!
      • js文件:默认不支持,需要修改js文件手动实现
      • html文件:不需要做,一般只有一个
  2. 优化代码调试
    1. source-map:提供源代码到构建后代码的映射关系
5.11.3 生产环境性能优化
  1. 优化打包构建速度(提升开发者的体验)
    1. oneOf:切记如果一个文件需要两个loader处理,不能都放在oneOf里边
    2. babel缓存:优化打包构建速度,项目中js代码是最多的,有利于js文件的处理
      • cacheDirectory: true
      • 让第二次打包构建速度更快
    3. 多进程打包:应用程序过多过复杂的时候,开启thread-laoder多进程打包,一般是针对babel-loader进行优化
  2. 优化代码运行性能(提升用户的体验)
    1. 缓存:文件缓存,将打包输出的css和js文件名字上加上哈希值
      • hash
      • chunkhash
      • contenthash
    2. tree shaking:优化代码性能,将没有的代码和库去除,让代码体积更小
      • 必须使用ES6模块化
      • 开启production环境
      • package.json设置sideEffects,防止删除了需要的文件(比如样式css代码)
    3. code split:将bundle拆分成多个js文件,可以并行加载
      • 单入口场景
        • 通过 optimization 分割 node_modules 代码
        • 通过 import 分割特定的 js 代码
      • 多入口场景:有几个入口就输出几个bundle,也会加上optimization将公共文件提取成一个,也可以提起node_modules代码
    4. lazy loadingprefetch
      • lazy loading:触发时候再加载再执行,文件较大时会卡顿
      • prefecth:闲时预先加载,但是兼容性太差(可以使用https://caniuse.com/网站查看是否可以使用)
    5. PWA:serviceWorker和cache方法组成
    6. externals:让某些库不打包
      • 声明哪些库不打包
      • 用script标签将cdn库引进来
    7. dll:让某些库打包
      • 单独将一下库先打包好,后边用不需要再打包
      • dll可以和code split结合使用,code split借助optimization将node_modules单独打包,然后通过dll将node_modules第三方库分别打包,再一个个引入
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值