webpack优化

405 阅读6分钟

记录优化细节

1.分包的优势

image.png

不分包

  • 会导致bundle文件很大很混乱,不方便管理。
  • 而文件很大的话,下载很慢。可能导致长时间的白屏

优化方案

  • 分包处理(prefetch)
  • ssr (1.加快首屏渲染 2.增加seo优化)
  • 代码分离(分离更小更多的bundle,加快渲染)

代码分离的三种方式

1. 配置entry,多个入口

image.png

这样做就会出现问题,二个入口文件都使用axios,那个打包出来的文件每个里面都有axios源代码.可以配置entry的shared进行解决

image.png image.png

这种方式很少使用,像vue/react都已将三方包打包的一个vendors文件中进行共享

2. 动态导入

image.png

就像vue-router点击跳转按钮才会加载文件并渲染页面

需要更改分包后的名字

image.png

或者使用魔法注释

image.png

3. 使用SplitChunksPlugin进行去重和分离代码

webpack默认安装和集成SplitChunksPlugin.使用axios,其打包的文件在默认的主包bundle.js文件中.

为了让三方包进行分离,打包到单独的js文件中.解决办法如下:

image.png

配置之后就会进行分包,其中包含异步包和三方包

image.png

自己设置分包的大小

image.png

自己对需要拆分的内容进行分包,vue/react脚手架就是这样拆分的(异步包,主包,三方包)

image.png

对生成包的名字进行优化(id作为打包文件的名字)

image.png

2.prefetch和preload(代码中进行配置)

prefetch: 预获取,主包加载完之后,再加载预获取的包 preload: 预加载,和主包一起下载下来

image.png image.png

点击按钮之后,从预获取的缓存中进行读取

image.png

3.CDN

相互连接的网络系统,服务器距离用户越近,传输更快更可靠.

修改publicPath属性,设置cdn的地址

image.png image.png

第三方包使用cdn地址.

image.png

4.shimming(了解)

shimming翻译为垫片. 可以通过ProvidePlugin实现shimming的效果.

场景: 默认没有导入某个库,直接使用时就会报错. shimming可以预置全局变量

const { ProvidePlugin } = require("webpack");

image.png

不推荐这样去写,还是要按照模块化的方式去编写代码

5.提取css文件

使用css要配置css-loader和style-loader

提取css到单独的文件需要使用mini-css-extract-plugin插件.因为要提取css,使用此插件的loader

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

image.png image.png image.png

将一类文件放在文件夹中(js文件也是类似)

image.png

5.1对打包的文件进行命名

hash: 通过MD4的散列函数处理后,生成一个128位的hash值(32个十六进制)

1.hash: 改了源代码,hash都会改变.比如: 两个入口,hash都会改变 2.contenthash: 只有修改的文件hash才会改变(推荐) 3.chunkhash: 与contenthash一样,但chunkhash会根据不同入口解析来生成

image.png

6.Terser(压缩和丑化代码的工具集)

打包完成之后,bundle.js有压缩的效果,是因为底层使用TerserPlugin插件.

而terser是独立的工具,可以进行全局或者局部的安装

npm install terser -g/-D

命令行使用(了解)

image.png

上面的方式只能优化到一行,还有-c -m等等的配置项.

webpack中进行配置使用

在webpack中有minimizer属性,在production模式下,默认使用terserplugin来处理代码.

image.png

如果压缩之后没有达到预期的效果,可以自己配置minimizer

image.png image.png

注意: production默认会打开minimize,要压缩的话建议在development和production都打开.

上面是对js代码进行压缩,接下来对css进行压缩

image.png image.png

注意: minimize和minimizer必须在optimization中.

7.配置分离

image.png image.png

配置不同环境不同的变量

image.png image.png

修改common.config.js的导出

image.png

将common的内容复制到dev和prod文件夹中,依次进行修改

注意: 因为要将common.config.js在不同的环境中与dev或者prod合并,可以使用插件进行合并.

image.png image.png
// common.config.js
const path = require("path"); // 导入path模块
const HtmlWebpackPlugin = require("html-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const { ProvidePlugin } = require("webpack");
// 进行合并
const { merge } = require("webpack-merge");
const devConfig = require("./dev.webpack");
const prodConfig = require("./prod.webpack");

const commonConfig = (isDev) => {
  return {
    // entry: "./src/index.js",
    output: {
      clean: true,
      path: path.resolve(__dirname, "../build"),
      filename: "[name]-bundle.js",
      chunkFilename: "[name]_chunk.js",
    },

    // 文件名后缀
    resolve: {
      extensions: [".js", ".json", ".wasm", ".jsx", ".ts"],
    },

    // loader
    module: {
      rules: [
        {
          test: /\.css$/i,
          use: [
            // 通过传参来区分环境使用不同的loader
            isDev ? "style-loader" : MiniCssExtractPlugin.loader,
            // "style-loader",
            // MiniCssExtractPlugin.loader, // 使用这个loader对css进行提取
            "css-loader",
          ],
        },
      ],
    },

    plugins: [
      new HtmlWebpackPlugin({
        template: "./index.html",
        title: "Fhup",
      }),
      new ProvidePlugin({
        axios: ["axios", "default"],
      }),
    ],
  };
};

module.exports = function (env) {
  const isDev = env.dev;
  if (isDev) {
    // 将commonConfig变为函数,就可以进行传参进行判断
    return merge(commonConfig(isDev), devConfig);
  } else {
    return merge(commonConfig(isDev), prodConfig);
  }
};

// dev.webpack.js
module.exports = {
  mode: "development",

  devServer: {
    static: ["public", "src/js"],
    open: true,
    port: 7777,
    proxy: {
      "/api": {
        target: "http://123.207.32.32:8000",
        pathRewrite: { "^/api": "" },
        secure: false,
        changeOrigin: true,
      },
    },
    // historyApiFallBack: true,
  },
};

// prod.webpack.js
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const TerserPlugin = require("terser-webpack-plugin"); // 安装webpack,默认进行安装
const CssMinimizerWebPlugin = require("css-minimizer-webpack-plugin");
module.exports = {
  mode: "production",
  devtool: "source-map",

  // 优化配置
  optimization: {
    // 设置生成的chunkid的算法
    // development: named
    // production: deterministic(确定性)
    chunkIds: "deterministic",

    splitChunks: {
      chunks: "all", // async / all
      // 当一个包大于指定大小时,继续进行拆包
      // maxSize: 20000, // 20kb
      // minSize: 10,

      // 自己对需要拆分的内容进行分包
      cacheGroups: {
        vendors: {
          // node_modules文件夹(加入 \/ (mac和windows二种文件路径解析格式 \ 和 / ,而第一个 \ 是转义)
          // 导入三方包查找路径:  ./node_moudules/
          test: /[\\/]node_modules[\\/]/,
          filename: "[id]_f_vendors.js",
        },
        utils: {
          // utils包比较小,大于20kb才会分包 所以要设置minSize
          test: /utils/, // utils文件夹
          filename: "[id]_utils.js",
        },
      },
    },

    minimize: true, // development下将其设置为true,terser才能进行压缩
    minimizer: [
      // minimizer中包含js压缩插件和css压缩插件
      new TerserPlugin({
        extractComments: false, // 启用/禁用剥离注释功能
        terserOptions: {},
      }),
      // 对css进行压缩
      new CssMinimizerWebPlugin({}),
    ],
  },

  plugins: [
    // 完成css的提取
    new MiniCssExtractPlugin({
      filename: "css/[contenthash:6]_style.css",
      chunkFilename: "css/[contenthash:6]_chunk_style.css",
    }),
  ],
};

再贴上未分离的webpack.config.js

// 没有分离之前的配置项
const path = require("path"); // 导入path模块
const HtmlWebpackPlugin = require("html-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const { ProvidePlugin } = require("webpack");
const TerserPlugin = require("terser-webpack-plugin"); // 安装webpack,默认进行安装
const CssMinimizerWebPlugin = require("css-minimizer-webpack-plugin");
module.exports = {
  mode: "development",
  // devtool: "source-map",

  // entry: "./src/index.js",
  // 多入口配置
  // entry: {
  //   index: {
  //     import: "./src/index.js",
  //     dependOn: "shared1",
  //   },
  //   main: {
  //     import: "./src/main.js",
  //     dependOn: "shared1",
  //   },
  //   shared1: ["axios"],
  //   // shared2: ["dayjs", "redux"],
  // },
  output: {
    clean: true,
    path: path.resolve(__dirname, "./build"),
    // filename: "js/bundle.js", // bundle会生成到js文件夹下
    filename: "[name]-bundle.js", // 二个入口对应二个bundle.js  可以使用name进行占位
    // 针对单独分包的文件进行命名
    chunkFilename: "[name]_chunk.js",
    // 打包后文件的路径前缀(设置cdn地址)
    // publicPath: "http://fhup/cdn",
  },

  devServer: {
    open: true,
  },

  // 优化配置
  optimization: {
    // 设置生成的chunkid的算法
    // development: named
    // production: deterministic(确定性)
    chunkIds: "deterministic",

    splitChunks: {
      chunks: "all", // async / all
      // 当一个包大于指定大小时,继续进行拆包
      // maxSize: 20000, // 20kb
      // minSize: 10,

      // 自己对需要拆分的内容进行分包
      cacheGroups: {
        vendors: {
          // node_modules文件夹(加入 \/ (mac和windows二种文件路径解析格式 \ 和 / ,而第一个 \ 是转义)
          // 导入三方包查找路径:  ./node_moudules/
          test: /[\\/]node_modules[\\/]/,
          filename: "[id]_f_vendors.js",
        },
        utils: {
          // utils包比较小,大于20kb才会分包 所以要设置minSize
          test: /utils/, // utils文件夹
          filename: "[id]_utils.js",
        },
      },
    },

    minimize: true, // development下将其设置为true,terser才能进行压缩
    minimizer: [
      // minimizer中包含js压缩插件和css压缩插件
      new TerserPlugin({
        extractComments: false, // 启用/禁用剥离注释功能
        terserOptions: {},
      }),
      // 对css进行压缩
      new CssMinimizerWebPlugin({}),
    ],
  },

  // loader
  module: {
    rules: [
      {
        test: /\.css$/i,
        use: [
          // "style-loader",
          MiniCssExtractPlugin.loader, // 使用这个loader对css进行提取
          "css-loader",
        ],
      },
    ],
  },

  plugins: [
    new HtmlWebpackPlugin({
      template: "./index.html",
      title: "Fhup",
    }),
    new ProvidePlugin({
      // axios: "axios", // 从axios中提供全局变量axios
      // axios导出比较特殊
      axios: ["axios", "default"],
    }),
    // 完成css的提取
    new MiniCssExtractPlugin({
      filename: "css/[contenthash:6]_style.css",
      chunkFilename: "css/[contenthash:6]_chunk_style.css",
    }),
  ],
};

8.TreeShaking (针对dev,自己配置)

usedExports 标记要删除的函数

打包时没有进行使用的函数默认不会打包,而dev环境不会被删除

image.png

针对dev: 标记要删除的函数,使用terser进行删除. 而prod默认开启进行删除

image.png

配置的terser压缩代码

image.png

sideEffects 告知webpack那些文件有副作用

dev环境: 如果只导入文件,那么文件里的导出的函数和执行的逻辑代码连同文件一起被打包.而我只是导入文件,没有使用任何的函数,配置sideEffects可以删除整个模块.

image.png

这样在treeshaking时都会被优化,但配置sideEffects就要注意代码的编写.

不要出现含有副作用的代码,尽量编写纯模块.

image.png

告诉webpack,math模块有副作用不要进行treeshaking

注意: 这样写的话,导入的css也会被treeshaking掉.所以要在sideEffects数组中添加 "*.css"

image.png

css的treeshaking

css压缩时不会将className压缩为一个字母,那么浏览器就会识别不到.css的treeshaking只能将没有使用的class进行移除,并将代码压缩到一行.

PurgeCss插件: 删除未使用的css ==> pnpm add purgecss-webpack-plugin -D

注意: 安装glob 7.* 版本,不然paths拿到的是空数组

image.png

打包时删除未使用的css

image.png

Scope Hoisting (作用域提升)将函数合并到一个模块中运行

打包后的代码外层会有函数作用域. 因为导入其他文件的函数进行使用,二个在不同的模块中,使用函数的那个模块还得导入要使用的函数,不如将函数直接放到使用函数的那个模块中.也就是作用域提升.

production默认进行作用域提升,dev环境配置Scope Hoisting

image.png

9.http压缩

对代码进行http压缩,浏览器拿到http压缩过的文件自动进行解压.

压缩格式: deflate(deflate算法压缩) gzip(常用) br(开源压缩算法 http内容编码而设计)

webpack使用 pnpm install compression-webpack-plugin -D

针对js和css进行压缩

const CompressionPlugin = require("compression-webpack-plugin");
module.exports = {
    plugins: [
        // http压缩js和css
        new CompressionPlugin({
          algorithm: "gzip", // 压缩算法
          test: /\.(css|js)$/, // 配置的文件
          // threshold: 10240, // 从多大开始压缩
          minRatio: 0.7, // 压缩比例
        }),
    ]
}
可以直接部署.gz
image.png

针对html进行压缩(注意: minify也是对应一个插件)

const HtmlWebpackPlugin = require("html-webpack-plugin");

image.png

10.打包速度分析

pnpm install speed-measure-webpack-plugin -D

特别注意: SpeedMeasurePlugin可能会与MiniCssExtractPlugin不兼容,建议注释 MiniCssExtractPlugin(接下来的二张截图)

image.png image.png image.png image.png image.png

11.打包后文件分析

pnpm add webpack-bundle-analyzer -D

  plugins: [
    // 对打包后文件进行分析
    new BundleAnalyzerPlugin(),
  ],

执行完npm run build,默认在浏览器打开这个页面(查看包的大小 来源 压缩后大小...)

image.png