webpack优化

140 阅读2分钟

webpack优化

工具插件

耗时指标

  • npm i speed-measure-webpack-plugin -D
    • 在打包后会展示每个阶段的耗时
const SpeedMeasureWebpackPlugin = require("speed-measure-webpack-plugin");
const smw = new SpeedMeasureWebpackPlugin();

module.exports = smw.wrap({
  mode: "development"
  ......
});

模块占比

  • npm i webpack-bundle-analyzer -D
  • 会在打包后获取到本次打包中每个模块的占比,然后生成页面,自动打开
const { BundleAnalyzerPlugin } = require("webpack-bundle-analyzer");
...
 plugins: [
    new BundleAnalyzerPlugin(),
  ],
...

指定如何查找模块

  resolve: {
    extensions: [".js"], // 查找后缀,自动补全,会依次查找
    alias: { bootstrap }, // 定义指定模块的内容
    modules: ["scopeModules", "node_modules"], // 模块查找路径,优先级按前后
    mainFields: ["style", "main"], // package.json默认找main,可以设置有优先级,先后,当前为先找main
    mainFiles: ["index.js", "base.js"], // 查找文件的顺序,默认是index.js,优先级为倒序
  },

指定如何查找loader

  • options等同于上面的resolve
  • key为resolveLoader
resolveLoader: {
    ...
}

指定不解析的模块

  • 通常我们需要webpack对模块进行分析,用来建立依赖树
  • 但部分模块我们确定它没有依赖,就可以标识,省略这步
  module: {
    noParse: /loadash|jquery/,// 方式1
    noParse: (request) => { //方式2
      return /loadash|jquery/.test(request);
    }
  },

指定某个库中不参与打包的文件

  • 如:moment中会引入好多语言包
  • 但往往用不了那么多,默认会全部打包,所以体积很大
  • 可以指定moment中引入的locale不参与构建
    • 但语言功能会失效,我们只需要在入口单独引入语言包即可
  plugins: [
    new webpack.IgnorePlugin({
      contextRegExp: /moment$/,
      resourceRegExp: /locale/,
    })
  ]

指定单独抽离的模块

  • 如react等模块基本不变,可以单独拿出来打包
  entry: { 
    main: "./src/index.js", 
    vender: ["react", "lodash"] // 如果有多个会合并成一个
  },

打包图片

  • 5里面自带了,不需要单独下载插件,直接使用即可
      {
        test: /\.(jpg|png|gif|bms|svg)$/,
        type: "asset/resource",
        generator: {
          filename: "images/[hash][ext]",
        },
      },

抽离css为单独文件

  • npm i mini-css-extract-plugin -D
  • 分两部分,plugin里new一下,loader里去掉style-loader
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
...
module: {
    rules: [
      {
        test: /\.css$/,
        use: [MiniCssExtractPlugin.loader, "css-loader"],
        // use: ["style-loader", "css-loader"],
      },
    ],
  },
  plugins: [
    // name为从哪个文件引入的,会以当前文件命名
    new MiniCssExtractPlugin({
      filename: "css/[name].css",
    }),
  ],
  ...

代码分割

  • 多入口会分割,但公共模块会重复打包,造成资源浪费
    • 可将公共模块分开打包,其他地方引用即可,能有效的减少包的体积
  optimization: {
    splitChunks: {
      // async initial all 选则那些代码块进行分割,异步、同步、所有
      chunks: "all",
      // 分割代码的最小体积,如果分太多的话,会造成很多http请求,0代表不限制
      minSize: 0,
      // 最小引用次数
      minChunks: 2,
      // 加载入口文件时,并行请求的最大数量,默认为5,包含当前包,可以理解为一个代码块,最多拆成5个包,为(5-1(当前)=4)
      maxInitialRequests: 5,
      // 按需加载时,最大并行数量,默认为3,最大同时请求异步文件数,默认为3,同样包含当前包,也就是异步导入最多拆成3个包为(3-1(当前)=2)
      maxAsyncRequests: 3,
      // 缓存组,默认情况下有两组,defaultVendors、default
      // 可以自己设置组,可以自己定义组内的test来标识文件属于哪个组
      cacheGroups: {
        // 第三方库
        defaultVendors: {
          // 模块路径存在node_module就属于第三方
          test: /node_modules/,
          minChunks: 1,
          priority: 1,
        },
        default: {
          // 同上,cacheGroups里面的优先级更高,里面不设置就找外边
          minChunks: 1,
          // 权重,通常会设置多组先匹配哪个,默的缓存组默认权重为-20,自定义组默认为0
          priority: 10,
          // 如果可以重用,就不生成新的代码块
          // 如果是同步,且只有这一个地方用,那么合回去,不生成新的代码
          reuseExistingChunk: true,
        },
      },
    },
    // 主文件里同步的代码也单独打包出去,那么就可以进行缓存了,默认为false
    runtimeChunk: true,
  }

Preload

  • 异步模块会在加载的时候获取,可以使用插件加上魔法注释,来进行预加载
  • 插件:webpackpreload-webpack-plugin
  • 使用:
// webpackPreload: true告诉插件预先获取该资源
btn.onclick = () => {
  import(
    /* webpackPreload: true */
    "./two"
  );
};

// 会在heade里插入一个    <link rel="preload" href="src_two_js.js">标签

产出会额外生成txt文件

  • 5里面会将依赖中的评论收集,并单独打个txt出来,实际上我并不期望它产出
// 自带插件,不需要单独下载
const TerserPlugin = require("terser-webpack-plugin");
module.exports = {
  optimization: {
    minimize: true,
    minimizer: [
      new TerserPlugin({
        extractComments: false, //不将注释提取到单独的文件中
      }),
    ],
  }
};

依赖统计

  • assets-webpack-plugin
  • 会生成一个一个带有文件依赖的json
const AssetsPlugin = require("assets-webpack-plugin");

plugins:[
    new AssetsPlugin({
      // 指定生成json的名字
      filename: "xxx.json",
      entrypoints: true,
      integrity: true,
      prettyPrint: true,
      includeFilesWithoutChunk: true,
      includeAuxiliaryAssets: true,
      includeDynamicImportedAssets: true,
    })
 ]

排除第三方库

  • externals
  • 指定某个模块不打包,比如react和react-dom
  externals: {
    react: "React",
    "react-dom": "ReactDOM",
  },

编译优化(提速)

  • 切记dev环境不要开启代码压缩,会严重影响编译效率
  // 按需编译(嘎嘎快)
  experiments: {
    lazyCompilation: true,
  },
  // babel缓存
  module: {
    rules: [
      {
        test: /\.(js|jsx|ts|tsx)$/,
        exclude: /node_modules/,
        use: {
          loader: "babel-loader",
          options: {
            cacheDirectory: true,
            compact: false,
            ...
          },
        },
      },
    ],
  },
  // webpack缓存
  cache: true,

react热更新

  • devserver本身自带的hot是webpack编译完成,页面刷新,但是会造成页面整体重新渲染,和数据重新加载
  • npm i @pmmmwh/react-refresh-webpack-plugin react-refresh -D
    • 会实现最小粒度的更新
const ReactRefreshPlugin = require("@pmmmwh/react-refresh-webpack-plugin");
//------
module: {
    rules: [
      {
        test: /\.(js|jsx|ts|tsx)$/,
        exclude: /node_modules/,
        use: {
          loader: "babel-loader",
          options: {
            cacheDirectory: true,
            compact: false,
            presets: [
              ["@babel/preset-env"],
              ["@babel/preset-react", { runtime: "automatic" }],
              "@babel/preset-typescript",
            ],
            plugins: ["react-refresh/babel"],
          },
        },
      },
    ],
  },
  plugins: [
    new ReactRefreshPlugin(),
  ],

ssr提取css

  • npm i isomorphic-style-loader-react18 -D