webpack4和webpack5使用上的一些变化及性能优化

974 阅读7分钟

1. 图片打包

webpack4需要使用file-loader url-loader,webpack5直接配置type:asset功能集成到了webpack5内部

字体文件的配置webpack4需要使用file-loader,webpack5使用

{
    type: "asset/resource",
    generator: {
         // 输出名称
         filename: "static/media/[hash:10][ext][query]",
    },
}

每次打包情况文件夹,webpack使用的是plugins插件,webpack5直接在output中配置clear:true

其它文件类型的处理

{
    test: /.(ttf|woff2?|map3|map4|avi)$/,
    type: "asset/resource",
    generator: {
      // 输出名称
      filename: "static/media/[hash:10][ext][query]",
    },
},

eslint编译报错,webpack4使用的是eslint-loader,webpack5使用的plugins插eslint-webpack-plugin

2. # 闪屏现象

我们知道css通过style-loader会将css融合到js中跟随js一起加载,那么就导致js没执行完成前样式是出现不了的,那么在webpack使用的时候就需要进行样式优先加载,mini-css-extract-plugin插件,在所有使用style-loader的地方替换成MiniCssExtractPlugin.loader,在plugins中使用new MiniCssExtractPlugin()

样式兼容

使用postcss postcss-loader postcss-preset-env 可以解决绝大多数样式的兼容问题

  MiniCssExtractPlugin.loader, // 提取css成单独文件
      "css-loader", // 将css资源编译成commonjs的模块到js中
          {
              loader:"postcss-loader",
              options: {
              postcssOptions: {
              plugins: [
                  "postcss-preset-env", // 能解决大多数样式兼容性问题
              ],
          },
      },
  },

到了这里是不是已经完了,那当然不是你还没有告诉postcss要兼容哪些版本的浏览器那么这个配置写在哪里呢,

package.json

  "browserslist": [
      "last 2 version",
      "> 1%",
      "not dead"
  ]

兼容最新的两个版本,并且要覆盖99%的浏览器,并且这些浏览器还是活的

3. # css压缩

css-minimizer-webpack-plugin 进行css压缩直接使用

  const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
  plugins:[new CssMinimizerPlugin()]

补充说明在生产模式下mode: "production",js和html都默认会进行压缩

4. # js兼容es6转es5

使用babel-loader @babel/preset-env @babel/core

  {
      test: /.js$/,
      exclude: /node_modules/, // 排除node_modules下的文件,其他文件都处理
      loader:"babel-loader",
  }

babel-loader的配置可以使用单独的配置文件进行配置,webpack会自动在根目录下查找

babel.config.js

  module.exports = {
      // 智能预设:能够编译ES6语法
      presets: ["@babel/preset-env"],
  };

5. # 开发体验优化

开发环境增加devtool:'eval-cheap-module-source-map',可以在开发时更快的找到报错的位置 还有一些其它的配置可以参考下如下文章,或者看官网介绍,生产环境下不开启

热更新,提升开发体验HOT

  devServer: {
      host:"localhost", // 启动服务器域名
      port:"3000", // 启动服务器端口号
      open:true, // 是否自动打开浏览器
      hot:true, // 开启HMR(默认值)开启后页面不会刷新只会修改的地方进行更新,不开启页面有改动重新进行编译整个页面进行刷新,只对css生效,如果想要js生效需要在代码入口做下处理代码看下面
  },
  if (module.hot) {
      // 判断是否支持热模块替换功能
      module.hot.accept("./js/count");//./js/coun 支持热更新的模块,更新里面的代码其余的代码不会进行重新加载,页面不会刷新
      module.hot.accept("./js/sum");
  }

对于react 可以使用react-hot-loader ,vue使用vue-loader都会自动帮我们实现模块的热更新

6. # loader使用oneOf

处理每个类型的文件只被处理一次,被其中一个命中后续的不再继续执行。

7. # exclude和include

exclude去除哪些文件的处理

使用场景是js文件的处理和eslint的处理,css不处理的原因是第三方样式本身也是需要抽离处理处理的不用排除,更极致的体验是引入具体的默认node_modules省的挨个去处理(个人猜测,有兴趣的去验证下)

// exclude: /node_modules/, // 排除node_modules下的文件,其他文件都处理
include:path.resolve(__dirname, "../src"), // 只处理src下的文件,其他文件不处理
newESLintPlugin({
    // 检测哪些文件
    context:path.resolve(__dirname, "../src"),
    exclude:"node_modules", // 默认值
})

8. # 开启js编译的缓存

提升第一次后的编译速度,开启缓存后编译只会针对修改过的文件进行重新编译

使用场景eslint插件和js编译

newESLintPlugin({
// 检测哪些文件
    context:path.resolve(__dirname, '../src'),
    exclude:'node_modules', // 默认值
    cache:true, // 开启缓存
    cacheLocation:path.resolve(
    __dirname,
    '../node_modules/.cache/eslintcache'
    )//指定缓存文件存放的位置
}),

9. # js的处理缓存处理

{
    loader:'babel-loader',
    options: {
        // presets: ["@babel/preset-env"],
        cacheDirectory:true, // 开启babel缓存
        cacheCompression:false, // 关闭缓存文件压缩(缓存文件没必要压缩,减少压缩文件用的时间,提升编译速度)
    },
},

10. # thread-loader 开启多进程打包

看情况开启,开启一个进行需要毫升600ms如果本身打包速度小于这个值,开启反而降低了速度

自定义js压缩配置

threads=os.cpus().length
new TerserWebpackPlugin({
 parallel: threads, // 开启多进程和设置进程数量
}),

js 编译处理

{
    test: /.js$/,
    // exclude: /node_modules/, // 排除node_modules下的文件,其他文件都处理
    include:path.resolve(__dirname, '../src'), // 只处理src下的文件,其他文件不处理
    use: [
        {
            loader:'thread-loader', // 开启多进程
            options: {
                works:threads, // 进程数量
            },
        },
        {
            loader:'babel-loader',
            options: {
                // presets: ["@babel/preset-env"],
                cacheDirectory:true, // 开启babel缓存
                cacheCompression:false, // 关闭缓存文件压缩
                plugins: ['@babel/plugin-transform-runtime'], // 减少代码体积
            },
        },
    ],
},

eslint校验处理

threads=os.cpus().length
new ESLintPlugin({
    // 检测哪些文件
    context:path.resolve(__dirname, '../src'),
    exclude:'node_modules', // 默认值
    cache:true, // 开启缓存
    cacheLocation:path.resolve(
    __dirname,
    '../node_modules/.cache/eslintcache'
    ),
    threads, // 开启多进程和设置进程数量
}),

11. # webpack5压缩操作统一放到optimization 下的 minimizer官方推荐做法。当然也可以放到plugins中使用

optimization: {
     minimizer: [
          // 压缩css
          new CssMinimizerPlugin(),
          // 压缩js
          new TerserWebpackPlugin({
            parallel: threads, // 开启多进程和设置进程数量
          }),
     ]
}

12. # 减少代码体积Tree Shaking webpack5中默认使用了该功能无需配置,依赖es module 使用commond不支持

webpack4中使用在package.json中使用加上如下代码但是这个会导致一些引入的样式被干掉所有需要在加一个配置

sideEffects:false

防止副作用的产生改成

  "sideEffects": [
      "dist/*",
      "es/**/style/*",
      "lib/**/style/*",
      "*.less"
  ],

13. # 减少代码体积

@babel/plugin-transform-runtime: 禁用了 Babel 自动对每个文件的 runtime 注入,而是引入 @babel/plugin-transform-runtime 并且使所有辅助代码从这里引用。

使用场景:一些es6语法和class等等语法在做兼容性问题需要使用babel生成代码,每个文件如果都使用一次那么就好在多个地方生成多次,@babel/plugin-transform-runtime就是生成一个公共的,使用的地方直接引用即可。

  {
      loader:'babel-loader',
      options: {
          // presets: ["@babel/preset-env"],
          cacheDirectory:true, // 开启babel缓存
          cacheCompression:false, // 关闭缓存文件压缩
          plugins: ['@babel/plugin-transform-runtime'], // 减少代码体积
      },
  },

14. # 优化图片体积

npm i image-minimizer-webpack-plugin imagemin -D

无损压缩

npm install imagemin-gifsicle imagemin-jpegtran imagemin-optipng imagemin-svgo -D

有损压缩

npm install imagemin-gifsicle imagemin-mozjpeg imagemin-pngquant imagemin-svgo -D
optimization: {
    // 压缩的操作
    minimizer: [
      // 压缩css
      new CssMinimizerPlugin(),
      // 压缩js
      new TerserWebpackPlugin({
        parallel: threads, // 开启多进程和设置进程数量
      }),
      // 压缩图片
      new ImageMinimizerPlugin({
        minimizer: {
          implementation: ImageMinimizerPlugin.imageminGenerate,
          options: {
            plugins: [
              ['gifsicle', { interlaced: true }],
              ['jpegtran', { progressive: true }],
              ['optipng', { optimizationLevel: 5 }],
              [                'svgo',                {                  plugins: [                    'preset-default',                    'prefixIds',                    {                      name: 'sortAttrs',                      params: {                        xmlnsOrder: 'alphabetical',                      },                    },                  ],
                },
              ],
            ],
          },
        },
      }),
    ],
}

15. # 优化代码的运行性能(将公共代码抽离出来单独打包)

optimization: {
    // 代码分割配置
    splitChunks: {
      chunks: 'all', // 对所有模块都进行分割
      // 以下是默认值
      // minSize: 20000, // 分割代码最小的大小20kb
      // minRemainingSize: 0, // 类似于minSize,最后确保提取的文件大小不能为0
      // minChunks: 1, // 至少被引用的次数,满足条件才会代码分割
      // maxAsyncRequests: 30, // 按需加载时并行加载的文件的最大数量
      // maxInitialRequests: 30, // 入口js文件最大并行请求数量
      // enforceSizeThreshold: 50000, // 超过50kb一定会单独打包(此时会忽略minRemainingSize、maxAsyncRequests、maxInitialRequests)
      // cacheGroups: { // 组,哪些模块要打包到一个组
      //   defaultVendors: { // 组名
      //     test: /[\/]node_modules[\/]/, // 需要打包到一起的模块
      //     priority: -10, // 权重(越大越高)
      //     reuseExistingChunk: true, // 如果当前 chunk 包含已从主 bundle 中拆分出的模块,则它将被重用,而不是生成新的模块
      //   },
      //   default: { // 其他没有写的配置会使用上面的默认值
      //     minChunks: 2, // 这里的minChunks权重更大
      //     priority: -20,
      //     reuseExistingChunk: true,
      //   },
      // },
      // 修改配置
      cacheGroups: {
        // 组,哪些模块要打包到一个组
        // defaultVendors: { // 组名
        //   test: /[\/]node_modules[\/]/, // 需要打包到一起的模块
        //   priority: -10, // 权重(越大越高)
        //   reuseExistingChunk: true, // 如果当前 chunk 包含已从主 bundle 中拆分出的模块,则它将被重用,而不是生成新的模块
        // },
        default: {
          // 其他没有写的配置会使用上面的默认值
          minSize: 0, // 我们定义的文件体积太小了,所以要改打包的最小文件体积
          minChunks: 2,
          priority: -20,
          reuseExistingChunk: true,
        },
      },
    },
  },

16. # 按需加载动态载入

// 给打包输出的其他文件命名

output 中配置,没有这个生成的文件名

 // 入口
  entry: './src/main.js', // 相对路径
  // 输出
  output: {
    // 所有文件的输出路径
    // __dirname nodejs的变量,代表当前文件的文件夹目录
    path: path.resolve(__dirname, '../dist'), // 绝对路径
    // 入口文件打包输出文件名
    filename: 'static/js/[name].[contenthash:10].js',
    // 给打包输出的其他文件命名
    chunkFilename: 'static/js/[name].chunk.[contenthash:10].js',
    // 原理:在打包前,将path整个目录内容清空,再进行打包
    clean: true,
  },
import(/* webpackChunkName: "message-editing.index" */ './count')
    .then((res) => {
        console.log('模块加载成功', res.default(2, 1))
    })
    .catch((err) => {
        console.log('模块加载失败', err)
    })

会生成message-editing.index文件,点击按钮动态加载文件