【webpack4系列】webpack构建速度和体积优化策略(五)

文章目录

速度分析:使用 speed-measure-webpack-plugin

使用speed-measure-webpack-plugin插件。

官网地址:https://github.com/stephencookdev/speed-measure-webpack-plugin#readme

示例效果:

安装:

npm i speed-measure-webpack-plugin -D

使用:

const SpeedMeasurePlugin = require("speed-measure-webpack-plugin");
const smp = new SpeedMeasurePlugin();

module.exports = smp.wrap({
  // 其他省略
  plugins: [
    new MyPlugin(), new MyOtherPlugin()
  ]
});

速度分析插件作用:

  • 分析整个打包总耗时
  • 每个插件和loader的耗时情况

体积分析:使用webpack-bundle-analyzer

示例代码:

const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;

module.exports = {
  plugins: [
    new BundleAnalyzerPlugin()
  ]
}

安装:

npm i webpack-bundle-analyzer -D

可以分析哪些问题?

  • 依赖的第三方模块文件大小
  • 业务里面的组件代码大小

使用高版本的 webpack 和 Node.js

高版本的webpack和node.js降低了构建时间。

使用webpack4的优化原因:

  • V8 带来的优化(for of 替代 forEach、Map 和 Set 替代 Object、includes 替代 indexOf)
  • 默认使用更快的 md4 hash 算法
  • webpacks AST 可以直接从 loader 传递给 AST,减少解析时间
  • 使用字符串方法替代正则表达式

多进程/多实例构建

资源并行解析可选方案

  • parallel-webpack
  • HappyPack
  • thread-loader

使用 HappyPack 解析资源

原理:每次 webapck 解析一个模块,HappyPack 会将它及它的依赖分配给 worker 线程中。

安装:

npm i happypack -D

使用示例:

const HappyPack = require('happypack');
 
exports.module = {
  rules: [
    {
      test: /.js$/,
      // 1) replace your original list of loaders with "happypack/loader":
      // loaders: [ 'babel-loader?presets[]=es2015' ],
      use: 'happypack/loader',
      include: [ /* ... */ ],
      exclude: [ /* ... */ ]
    }
  ]
};
 
exports.plugins = [
  // 2) create the plugin:
  new HappyPack({
    // 3) re-add the loaders you replaced above in #1:
    loaders: [ 'babel-loader?presets[]=es2015' ]
  })
];

使用 thread-loader 解析资源

由于webpack4.x目前只能安装thread-loader@3.0.0版本,3.0.0以后的版本需要webpack5.x。

原理:每次 webpack 解析一个模块,thread-loader 会将它及它的依赖分配给 worker 线程中

npm i thread-loader@3.0.0 -D

配置:

module: {
    rules: [
      {
        test: /.js$/,
        use: [
          {
            loader: "thread-loader",
            options: {
              workers: 3
            }
          },
          "babel-loader"
        ]
      }
    ]
  }

多进程并行压缩代码

方法一:使用 parallel-uglify-plugin 插件

const ParalleUglifyPlugin = require("webpack-parallel-uglify-plugin");
module.exports = {
  plugins: [
    new ParalleluglifyPlugin({
      uglifyjs: {
        output: {
          beautify: false,
          comments: false
        },
        compress: {
          warnings: false,
          drop_console: true,
          collapse_vars: true,
          reduce_vars: true
        }
      }
    })
  ]
};

方法二:uglifyjs-webpack-plugin 开启 parallel 参数

建议webpack3.x使用该插件。

const UglifyJsPlugin = require("uglifyjs-webpack-plugin");
module.exports = {
  plugins: [
    new UglifyJsPlugin({
      uglifyoptions: {
        warnings: false,
        parse: {},
        compress: {},
        mangle: true,
        output: null,
        toplevel: false,
        nameCache: null,
        ie8: false,
        keep_fnames: false
      },
      parallel: true
    })
  ]
};

方法三:terser-webpack-plugin 开启 parallel 参数

webpack4.x及以上建议使用terser-webpack-plugin插件

注:Using Webpack v4, you have to install terser-webpack-plugin v4.

安装:

npm i terser-webpack-plugin@4 -D

配置:

const TerserPlugin = require("terser-webpack-plugin");

module.exports = {
  optimization: {
    minimizer: [
      new TerserPlugin({
        parallel: true
      })
    ]
  }
};

进一步分包:预编译资源模块

分包:设置 Externals

思路:将 vue、react、react-dom 等基础包通过cdn 引入,不打入 bundle 中。

方法:使用html-webpack-externals-plugin

安装:

npm i html-webpack-externals-plugin -D

配置:

const HtmlWebpackExternalsPlugin = require('html-webpack-externals-plugin');

module.exports = {
  plugins: [
    new HtmlWebpackExternalsPlugin({
      externals: [
        {
          module: "react",
          entry: "https://unpkg.com/react@18.2.0/umd/react.production.min.js",
          global: "React"
        },
        {
          module: "react-dom",
          entry: "https://unpkg.com/react-dom@18/umd/react-dom.production.min.js",
          global: "ReactDOM"
        }
      ]
    })
  ]
};

进一步分包:预编译资源模块

思路:将react、react-dom基础包和业务基础包打包成一个文件

方法:使用DLLPlugin进行分包,DllReferencePluginmanifest.json 引用

  • DllPlugin:负责抽离第三方库,形成第三方动态库dll。
  • DllReferencePlugin:负责引用第三方库。

使用 DLLPlugin 进行分包

新建一个webpack.dll.js:

const path = require("path");
const webpack = require("webpack");

module.exports = {
  entry: {
    library: ["react", "react-dom"]
  },
  output: {
    filename: "[name].dll.js",
    path: path.join(__dirname, "build/library"),
    library: "[name]_[hash:8]" // 保持与webpack.DllPlugin中name一致
  },
  plugins: [
    new webpack.DllPlugin({
      name: "[name]_[hash:8]", // 保持与output.library中名称一致
      path: path.join(__dirname, "build/library/[name].json")
    })
  ]
};

在package.json中添加命令:

"scripts": {
    "dll": "webpack --config webpack.dll.js"
}

最后执行npm run dll,结果在工程根目录下有如下文件:

  • build
    • library.dll.js
    • library.json

使用 DllReferencePlugin 引用 manifest.json

在webpack.prod.js中插件中配置如下:

plugins: [
    new webpack.DllReferencePlugin({
      manifest: require("./build/library/library.json")
    })
]

当执行npm run build 后其实index.html页面中没有引入library.dll.js文件,我们可以通过安装add-asset-html-webpack-plugin插件,webpack4.x版本使用add-asset-html-webpack-plugin@3

npm i add-asset-html-webpack-plugin@3 -D

在webpack.prod.js中插件中配置如下:

plugins: [
    new webpack.DllReferencePlugin({
      manifest: require("./build/library/library.json")
    }),
    new AddAssetHtmlPlugin({
      filepath: path.resolve("./build/library", "library.dll.js")
    })
]

起作用 就是把build/library/library.dll.js拷贝到编译后的dist文件夹下,并且通过script标签引入到index.html中。

最终页面生成的效果:

<!DOCTYPE html>
<html lang="en">
<meta charset="utf-8">
<head>
  <title>Document</title>
  <link href="search_42937580.css" rel="stylesheet">
</head>
<body>
  <div id="root"></div>
  <script src="library.dll.js"></script>
  <script src="search_c1f12d25.js"></script>
</body>
</html>

add-asset-html-webpack-plugin参考地址:https://www.npmjs.com/package/add-asset-html-webpack-plugin/v/3.2.2?activeTab=versions

充分利用缓存提升二次构建速度

目的:提升二次构建速度。

缓存思路:

  • babel-loader 开启缓存
  • terser-webpack-plugin 开启缓存
  • 使用 cache-loader 或者 hard-source-webpack-plugin

开启了对应方式的缓存,会在node_modules目录下的cache文件夹看到缓存的内容,如下结构:

  • node_modules
    • .cache
      • babel-loader
      • hard-source
      • terser-webpack-plugin

1、babel-loader 开启缓存

rules: [
  {
    test: /.js$/,
    use: ["babel-loader?cacheDirectory=true"
    ]
  }
]

如果是使用的HappyPack,配置如下:

new HappyPack({
    loaders: ["babel-loader?cacheDirectory=true"]
})

2、terser-webpack-plugin 开启缓存

optimization: {
    minimizer: [
      new TerserPlugin({
        parallel: true,
        cache: true
      })
    ]
  }

3、hard-source-webpack-plugin开启缓存

安装:

npm i hard-source-webpack-plugin -D

配置:

const HardSourceWebpackPlugin = require('hard-source-webpack-plugin');

module.exports = {
    plugins: [
         new HardSourceWebpackPlugin()
    ]
}

在webpack4.x中会报错。

缩小构建目标与减少文件搜索范围

缩小构建目标

目的:尽可能的少构建模块

比如 babel-loader 不解析 node_modules

 rules: [
  {
    test: /.js$/,
    include: [path.resolve(__dirname, "src")],
    use: [
        "babel-loader"
    ]
  }

当然也可以使用exclude,来缩小构建范围。

减少文件搜索范围

  • 优化 resolve.modules 配置(减少模块搜索层级)
  • 优化 resolve.mainFields 配置
  • 优化 resolve.extensions 配置
  • 合理使用 alias

示例代码:

resolve: {
    alias: {
      "@": path.resolve(__dirname, "src"),
      react: path.resolve(__dirname, "./node_modules/react/umd/react.production.min.js"),
      "react-dom": path.resolve(__dirname, "./node_modules/react-dom/umd/react-dom.production.min.js")
      extensions: [".js"],
      mainFields: ["main"]
  },

使用Tree Shaking擦除无用的JavaScript和CSS

概念:1 个模块可能有多个方法,只要其中的某个方法使用到了,则整个文件都会被打到

bundle 里面去,tree shaking 就是只把用到的方法打入 bundle ,没用到的方法会在

uglify 阶段被擦除掉。

使用:webpack 默认支持,在 .babelrc 里设置 modules: false 即可

  • production mode的情况下默认开启

要求:必须是 ES6 的语法,CJS 的方式不支持

无用的 CSS 如何删除掉?

  • PurifyCSS: 遍历代码,识别已经用到的 CSS class
  • uncss: HTML 需要通过 jsdom加载,所有的样式通过PostCSS解析,通过document.querySelector 来识别在 html 文件里面不存在的选择器

在 webpack 中如何使用 PurifyCSS?

PurifyCSS官网已经不再维护了,使用 purgecss-webpack-plugin这个插件和 mini-css-extract-plugin 配合使用。

安装purgecss-webpack-plugin插件:

npm i purgecss-webpack-plugin@4 -D

配置:

const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const PurgeCSSPlugin = require('purgecss-webpack-plugin')

const PATHS = {
  src: path.join(__dirname, 'src')
}

module.exports = {
 // 其他省略
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [
          MiniCssExtractPlugin.loader,
          "css-loader"
        ]
      }
    ]
  },
  plugins: [
    new MiniCssExtractPlugin({
      filename: "[name].css",
    }),
    new PurgeCSSPlugin({
      paths: glob.sync(`${PATHS.src}/**/*`,  { nodir: true }),
    }),
  ]
}

使用webpack进行图片压缩(压缩有问题)

基于 Node 库的 imagemin 或者 tinypng API

使用:配置 image-webpack-loader

安装image-webpack-loader:

npm i image-webpack-loader@6 -D

其中imagemin-mozjpeg对node版本有要求:

 "engines": {
    "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
  }

配置:

{
test: /.(png|jpe?g|gif)$/,
    use: [
      {
        loader: "file-loader",
        options: { name: "[name]_[hash:8].[ext]" }
      },
      {
        loader: "image-webpack-loader",
        options: {
          mozjpeg: {
            progressive: true,
            quality: 65
          },
          // optipng.enabled: false will disable optipng
          optipng: {
            enabled: false
          },
          pngquant: {
            quality: [0.65, 0.9],
            speed: 4
          },
          gifsicle: {
            interlaced: false
          },
          // the webp option will enable WEBP
          webp: {
            quality: 75
          }
        }
      }
    ]
}

Imagemin的优点分析:

  • 有很多定制选项
  • 可以引入更多第三方优化插件,例如pngquant
  • 可以处理多种图片格式

Imagemin的压缩原理:

  • pngquant: 是一款PNG压缩器,通过将图像转换为具有alpha通道(通常比24/32位PNG
  • 文件小60-80%)的更高效的8位PNG格式,可显著减小文件大小。
  • pngcrush:其主要目的是通过尝试不同的压缩级别和PNG过滤方法来降低PNG IDAT数据
  • 流的大小。
  • optipng:其设计灵感来自于pngcrush。optipng可将图像文件重新压缩为更小尺寸,而不
  • 会丢失任何信息。
  • tinypng:也是将24位png文件转化为更小有索引的8位图片,同时所有非必要的metadata
  • 也会被剥离掉

构建体积优化:动态 Polyfill

直接把babel-polyfill打包到工程,一般会很大。

Polyfill Service原理:识别 User Agent,下发不同的 Polyfill

如何使用动态 Polyfill service?

polyfill.io 官方提供的服务

<script src="https://cdn.polyfill.io/v3/polyfill.js"></script>
相关推荐
熊的猫31 分钟前
JS 中的类型 & 类型判断 & 类型转换
前端·javascript·vue.js·chrome·react.js·前端框架·node.js
瑶琴AI前端1 小时前
uniapp组件实现省市区三级联动选择
java·前端·uni-app
会发光的猪。1 小时前
如何在vscode中安装git详细新手教程
前端·ide·git·vscode
我要洋人死2 小时前
导航栏及下拉菜单的实现
前端·css·css3
科技探秘人2 小时前
Chrome与火狐哪个浏览器的隐私追踪功能更好
前端·chrome
科技探秘人2 小时前
Chrome与傲游浏览器性能与功能的深度对比
前端·chrome
JerryXZR2 小时前
前端开发中ES6的技术细节二
前端·javascript·es6
七星静香3 小时前
laravel chunkById 分块查询 使用时的问题
java·前端·laravel
q2498596933 小时前
前端预览word、excel、ppt
前端·word·excel
小华同学ai3 小时前
wflow-web:开源啦 ,高仿钉钉、飞书、企业微信的审批流程设计器,轻松打造属于你的工作流设计器
前端·钉钉·飞书