Webpack 核心总结

1,002 阅读5分钟

前言

笔者最近重新复习了一遍 Webpack 核心功能,果然是温故而知新!这篇文章结合高级进阶之 Webpack 篇阅读效果更佳!

常见优化手段

构建速度优化

思路:缩小搜索范围,减少不必要的模块打包,加快构建(缓存,多线程)
缩少搜索范围:

  • 指定第三方目录 resolve.modules,resolve.alias 缓存目录,extensions 减少后缀的搜索
  • loader 配置指定编译范围 exclude/include

减少不必要的模块打包:

  • 通过cdn引入(vue 全家桶)等,然后用externals 提取常用库,不会再打包到 bundle 文件,并可以通过import 引入;
  • 通过 DllPlugin、DllReferencePlugin 插件来预编译模块,减少不必要的打包 注意:在 Webpack5 中已经不⽤ DllPlugin、DllReferencePlugin 了,⽽是⽤ HardSourceWebpackPlugin 替代
const HardSourceWebpackPlugin = require('hard-source-webpack-plugin')
const plugins = [
 new HardSourceWebpackPlugin(
)
]

加快构建:

  • 使用cache-loader进行缓存;
  • 使用 happypack 插件开启多个线程打包资源文件(happypack不再维护),可以用 thread-loader 替代

性能优化

代码压缩 :

使用 ParallelUglifyPlugin 插件:开启多线程对 js 文件压缩和缓存,删除多余的注释和 console.log
注意: 在生产环境下即配置中 mode 设置为 production,webpack 默认开启了 TerserWebpackPlugin 可以实现该功能,如果需要剔除调试代码可以自行配置,具体参考下面的 TerserWebpackPlugin 配置

  1. 多进程并行压缩
  • webpack-paralle-uglify-plugin
  • uglifyjs-webpack-plugin 开启 parallel 参数 (不支持ES6)
  • terser-webpack-plugin 开启 parallel 参数
  1. 通过 mini-css-extract-plugin 提取 Chunk 中的 CSS 代码到单独文件,通过 css-loader 的 minimize 选项开启 cssnano 压缩 CSS。
  2. 图片压缩 配置 image-webpack-loader


代码分割缓存:
使用 optimization.splitChunks.cacheGroup 进行公共代码分割抽离和缓存

如何做 Tree Shaking

Webpack4.0 以上版本在 mode 为 production 时,会自动开启 Tree Shaking 能力。默认 production mode 的配置如下:

const config = {
 mode: 'production',
 optimization: {
 usedExports: true,
 minimizer: [
  new TerserPlugin({...})
 ]
 }
};

什么是副作用

"side effect(副作用)" 的定义是,在导入时会执行特殊行为的代码,而不是仅仅暴露一个 export 或多个 export。举例说明,例如 polyfill,它影响全局作用域,并且通常不提供 export。

解决副作用:通过 package.json 的 "sideEffects" 属性,来实现这种方式。

{
  "name": "your-project",
  "sideEffects": ["./src/some-side-effectful-file.js"]
}

如果你的代码确实有一些副作用,可以改为提供一个数组:

{
  "name": "your-project",
  "sideEffects": ["./src/some-side-effectful-file.js"]
}

loader 与 plugin 的区别

  • loader: loader是一个转换器是在 import 或"加载"模块时预处理文件,将A语言转成B语言,如 TypeScript转换为 JavaScript,less 转成 css,单纯的文件转换成浏览器可以识别的文件。
  • plugin:插件是一个扩展器,可以扩展 webpack 的功能,在 webpack 运行的生命周期会广播许多钩子,plugin 会监听这些事件,在合适的时机通过 webapck API (run、compile、emit)等改变输出结果。

热更新

watch

配置新增 watch: true 或者在脚步加--wacth 即可实现代码变化之后自动打包

Live Reload

  devServer: { 

    contentBase: './dist', //为./dist目录中的静态页面文件提供本地服务渲染 

    open: true          //启动服务后自动打开浏览器网页 

  }

以上代码通过websocket 链接,使打开的网页和本地服务间建立持久化的通信。当源代码变更时,我们就可以通过 Socket 通知到网页端,网页端在接到通知后会自动触发页面刷新。

热更新实现

 devServer: { 

    ... 

    hot: true 

  }, 

加 hot 可以让css更新是局部刷新,但js(文本)内容改变还是会浏览器刷新

加 HotModuleReplacementPlugin 插件才可以全部的局部刷新功能

webpack 热更新原理

以下内容来自字节前端面试题

  1. 当修改了一个或多个文件;
  2. 文件系统接收更改并通知 webpack;
  3. webpack 重新编译构建一个或多个模块,并通知 HMR 服务器进行更新;
  4. HMR Server 使用 webSocket 通知 HMR runtime 需要更新,HMR 运行时
    通过 HTTP 请求更新 jsonp;
  5. HMR 运行时替换更新中的模块,如果确定这些模块无法更新,则触发整
    个页面刷新。

注意:启动 HMR 后,css 抽离会不⽣效,还有不⽀持 contenthash,chunkhash

webapck 打包原理

高级进阶之 Webpack 篇

重要配置代码展示

webpack-paralle-uglify-plugin

 // 使用 ParallelUglifyPlugin 并行压缩输出的 JS 代码
    new ParallelUglifyPlugin({
      // 传递给 UglifyJS 的参数
      uglifyJS: {
        output: {
          // 最紧凑的输出
          beautify: false,
          // 删除所有的注释
          comments: false,
        },
        compress: {
          // 在UglifyJs删除没有用到的代码时不输出警告
          warnings: false,
          // 删除所有的 `console` 语句,可以兼容ie浏览器
          drop_console: true,
          // 内嵌定义了但是只用到一次的变量
          collapse_vars: true,
          // 提取出出现多次但是没有定义成变量去引用的静态值
          reduce_vars: true,
        }
      },
    }),

uglifyjs-webpack-plugin

const UglifyJSPlugin = require('uglifyjs-webpack-plugin');

module.exports = {
	optimization: {
		minimizer: [
			new UglifyJsPlugin({
				test: /\.js(\?.*)?$/i, //测试匹配文件,
				// include: /\/includes/, //包含哪些文件
				// exclude: /\/node_modules/, //不包含哪些文件
				//允许过滤哪些块应该被uglified(默认情况下,所有块都是uglified)。
				//返回true以uglify块,否则返回false。
				chunkFilter: chunk => {
				  // `vendor` 模块不压缩
				  if (chunk.name === "vendor") {
					return false;
				  }
				  return true;
				},
				cache: false, //是否启用文件缓存,默认缓存在node_modules/.cache/uglifyjs-webpack-plugin.目录
				parallel: true //使用多进程并行运行来提高构建速度
			})
		]
	},
}

//  等价于

{
    plugins: [    
            // 压缩JS文件
            new UglifyJSPlugin({
                            test: /\.js(\?.*)?$/i, //测试匹配文件,
                            // include: /\/includes/, //包含哪些文件
                            // exclude: /\/node_modules/, //不包含哪些文件
                            //允许过滤哪些块应该被uglified(默认情况下,所有块都是uglified)。
                            //返回true以uglify块,否则返回false。
                            chunkFilter: chunk => {
                              // `vendor` 模块不压缩
                              if (chunk.name === "vendor") {
                                    return false;
                              }
                              return true;
                            },
                            cache: false, //是否启用文件缓存,默认缓存在node_modules/.cache/uglifyjs-webpack-plugin.目录
                            parallel: true //使用多进程并行运行来提高构建速度,不支持 es6
                    }),
    ],
}

terser-webpack-plugin

// webpack.config.js

// 导入terser-webpack-plugin-->减少js体积(其中删除js的console.log和注释)
const TerserWebpackPlugin = require('terser-webpack-plugin');
// 实例化TerserWebpackPlugin对象
const terserPlugin = new TerserWebpackPlugin({
  parallel: 4,
  extractComments: true,
  terserOptions: {
    compress: {
      warnings: false,
      drop_console: true,
      drop_debugger: true,
      pure_funcs: ['console.log'] //移除console
    }
  }
});

module.exports = {
	optimization: {
    	minimizer: [
      	// 只有打包环境为production时才能生效
      	terserPlugin
    ],
  },
}

mini-css-extract-plugin

const MiniCssExtractPlugin = require('mini-css-extract-plugin');

module.exports = {
  plugins: [
    new MiniCssExtractPlugin({
      filename: "css/[name].css",
      // chunkFilename: "[name].css",
      disable: isDebug
    }),],
  module: {
    rules: [
      {
        test: /\.css$/i,
        use: [MiniCssExtractPlugin.loader, 'css-loader'],
      },
    ],
  },
};

optimization.splitChunks

    splitChunks:{
        cacheGroups: {
            common:{
                chunks: 'initial',
                name:'Common', // 打包后的文件名
                minSize: 0, 
                minChunks: 2 // 重复2次才能打包到此模块
            },
            vendor: {
                priority: 1, // 优先级配置,优先匹配优先级更高的规则,不设置的规则优先级默认为0
                test: /node_modules/, // 匹配对应文件
                chunks: 'initial',
                name:'Vendor',
                minSize: 0,
                minChunks: 1
            }
        }
    }

热更新实现

const path = require('path')
const webpack = require('webpack')
module.exports = {
    entry: './index.js',
    output: {
        filename: 'bundle.js',
        path: path.join(__dirname, '/')
    },
    devServer: {
        hot: true
    },
    plugins:[
    new webpack.HotModuleReplacementPlugin()
    ]
}

如果不使用 HotModuleReplacementPlugin 可以直接 package.json 配置

"dev": "webpack-dev-server --hot --open"