构建异常和中断处理

webpack 4 之前的版本构建失败不会抛出错误码(error code)

Node.js 中的 process.exit 规范

  • 0 表示成功完成,回调函数中,err 为 null
  • 非 0 表示执行失败,回调函数中,err 不为 null,err.code 就是传给 exit 的数字

image.png

  1. // webpack4 写法
  2. // webpack3 把 this.hooks.done.tap 换成 this.plugin
  3. function() {
  4. // this 指向的是 compiler
  5. this.hooks.done.tap('done', (stats)=> {
  6. if(stats.compilation.errors && stats.compilation.errors.length && process.argv.indexOf('-watch') == -1) {
  7. console.log('build error');
  8. process.exit(1);
  9. }
  10. })
  11. }

构建配置包设计

构建配置抽离成 npm 包的意义

  • 通用性
    • 业务开发者无需关注构建配置
    • 统一团队构建脚本
  • 可维护性
    • 构建配置合理拆分
    • Readme 文档,Changelog 文档
  • 质量
    • 冒烟测试、单元测试、测试覆盖率
    • 持续集成

构建配置管理的可选方案

通过多个配置文件管理不同环境的构建,webpack --config参数进行控制
将构建配置设计成一个库,比如hjs-webpackNeutrinowebpack-blocks

开发人员超过 20 人,可以考虑下面的方案

抽成一个工具进行管理,比如create-react-appkytnwb
将所有配置放在一个文件,通过--env参数控制分支选择

构建配置包的设计

通过多个配置文件管理不同环境的 webpack 配置

  • 基础配置:webpack.base.js
  • 开发环境:webpack.dev.js
  • 生成环境:webpack.prod.js
  • SSR 环境:webpack.ssr.js

抽离成一个 npm 包统一管理

  • 规范:git commit 日志、Readme、eslint 规范、semver 规范
  • 质量:冒烟测试、单元测试、测试覆盖率 和 CI

    通过 webpack-merge 组合配置

    image.png

    功能模块设计

    image.png

目录结构设计

image.png

冒烟测试

冒烟测试是指对提交测试的软件再进行详细深入的测试之前而进行的预测试,这种预测试的主要目的是暴露一些导致软件需重新发布的严重问题,比如基本功能失效。

冒烟测试执行

  • 构建是否成功
  • 每次构建完成 build 目录是否有内容输入
    • 是否有 JS、CSS 等静态资源文件
    • 是否有 HTML 文件

      下载包 cnpm i rimraf -D

webpack 性能分析和优化

速度分析

下载 npm i speed-measure-webpack-plugin -D

  • 分析整个打包总耗时
  • 每个插件和 loader 的耗时情况
  1. // webpack.config.js
  2. const SpeedMeasureWebpackPlugin= require('speed-measure-webpack-plugin')
  3. const smp = new SpeedMeasureWebpackPlugin()
  4. const webpackConfig = {
  5. entry: {},
  6. output: {},
  7. module: {},
  8. plugins: [],
  9. mode: '',
  10. }
  11. module.exports = smp.wrap(webpackConfig);

体积分析

下载 npm i webpack-bundle-analyzer -D

  1. const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');
  2. const webpackConfig = {
  3. entry: {},
  4. output: {},
  5. module: {},
  6. plugins: [
  7. new BundleAnalyzerPlugin()
  8. ],
  9. mode: '',
  10. }
  11. module.exports = webpackConfig;

优化:高版本的 webpack 和 node.js

webpack4 做了哪些优化?由于 webpack4 依赖于更高版本的 node >= 6.11.5 image.png

优化:多进程构建

  • 可选方案
    • happyPack
    • parrallel-webpack
    • thread-loader

      happyPack 构建解析

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

      webpack3 用得多,它的作者不怎么维护了。 webpack4 已经推出了 thread loader,可以用这个

  1. const webpackConfig = {
  2. module: {
  3. rules: [
  4. {
  5. test: /\.m?(js)$/,
  6. exclude: /(node_modules|bower_components)/,
  7. use: ['happypack/loader']
  8. },
  9. ]
  10. }
  11. plugins: [
  12. new HappyPack({
  13. id: 'js',
  14. thread: 4,
  15. loader: ['babel-loader']
  16. }),
  17. new HappyPack({
  18. id: 'styles',
  19. thread: 2,
  20. loader: ['style-loader','css-loader', 'less-loader']
  21. })
  22. ]
  23. }

image.png

使用 thread-loader

下载 cnpm i thread-loader -D

  1. const webpackConfig = {
  2. module: {
  3. rules: [
  4. {
  5. test: /\.m?(js|ts|jsx)$/,
  6. exclude: /(node_modules|bower_components)/,
  7. use: [
  8. {
  9. loader: 'thread-loader',
  10. options: {
  11. workers: 3
  12. }
  13. },
  14. {
  15. loader: 'babel-loader',
  16. options: {
  17. presets: [
  18. ['@babel/preset-env'],
  19. ['@babel/preset-react']
  20. ],
  21. plugins: ['@babel/plugin-transform-runtime'],
  22. cacheDirectory: true,
  23. }
  24. }
  25. ]
  26. },
  27. ]
  28. }
  29. }

并行压缩

  1. 使用 parallel-uglify-plugin 插件
  2. 使用 uglifyjs-webpack-plugin 开启 parallel 参数

    这个插件不支持压缩 ES6 语法的代码

  3. 使用 terser-webpack-plugin 开启 parallel 参数

    webpack4 默认使用这个。 这个插件支持压缩 ES6 语法的代码

webpack5 开箱即带有最新版本的 terser-webpack-plugin。 如果你使用的是 webpack v5 或更高版本,同时希望自定义配置,那么仍需要安装 terser-webpack-plugin。

  1. const TerserPlugin = require("terser-webpack-plugin");
  2. const webpackConfig = {
  3. optimization: {
  4. minimizer: [
  5. new TerserPlugin({
  6. parallel: true,
  7. }),
  8. '...'
  9. ],
  10. }
  11. }

优化:分包

设置 externals

思路:将 react、react-dom 基础包通过 cdn 引入,不打入 bundle 中
方法:使用 npm i html-webpack-externals-plugin -D

实际项目中,可能有业务的基础包,如果直接放入 cdn ,通过 script 标签获取,不太好

image.png

预编译资源模块

思路:将 react、react-dom、redux、react-redux 基础包和业务基础包打包成一个文件
方法:使用 DLLPlugin 进行分包,DllReferencePlugin 对 manifest.json 引用

  1. 先使用 DLLPlugin 进行分包 ```javascript // webpack.dll.js const webpack = require(‘webpack’) const path = require(‘path’)

const dll = { mode: ‘production’, entry: { library: [ ‘react’, ‘react-dom’, ] }, output: { filename: ‘[name].dll.js’, path: path.join(dirname, ‘../build/library’), library: ‘[name]’ }, plugins: [ new webpack.DllPlugin({ name: ‘[name]_[contentHash:8]’, path: path.join(dirname, ‘../build/library/[name].json’) }) ], module: { rules: [

  1. ],

}, }

module.exports = dll;

  1. 2. 再使用 DllReferencePlugin 引用 manifest.json
  2. ```javascript
  3. // webpack.prod.js
  4. const webpack = require('webpack')
  5. module.exports = {
  6. plugins: [
  7. new webpack.DllReferencePlugin({
  8. manifest: require('../build/library/library.json')
  9. })
  10. ]
  11. }

image.png

优化:缓存

目的:提高二次构建速度

缓存思路

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

babel-loader 开启缓存

  1. const webpackConfig = {
  2. module: {
  3. rules: [
  4. {
  5. test: /\.m?(js|ts|jsx)$/,
  6. exclude: /(node_modules|bower_components)/,
  7. use: [
  8. {
  9. loader: 'babel-loader',
  10. options: {
  11. presets: [
  12. ['@babel/preset-env'],
  13. ['@babel/preset-react']
  14. ],
  15. plugins: ['@babel/plugin-transform-runtime'],
  16. cacheDirectory: true, // 开启缓存
  17. }
  18. }
  19. ]
  20. },
  21. ]
  22. }
  23. }

使用 hard-source-webpack-plugin

下载 npm i hard-source-webpack-plugin -D

  1. const HardSourceWebpackPlugin = require('hard-source-webpack-plugin')
  2. const webpackConfig = {
  3. plugins: [
  4. new HardSourceWebpackPlugin()
  5. ]
  6. }

优化:缩小构建目标

目的:尽可能的少构建模块
比如:babel-loader 不解析 node_modules
image.png

减少文件搜索范围

  • 优化 resolve.modules 配置(减少模块搜索层级)
  • 优化 resolve.mainFields 配置,指定搜索文件夹时,入口文件的名称
  • 优化 resolve.extensions 配置,默认['.js','.json']
  • 合理使用 alias
    1. const webpackConfig = {
    2. resolve: {
    3. alias: {
    4. '@': path.resolve(__dirname, '../src'),
    5. },
    6. mainFields: ['index'],
    7. extensions: ['.js', '.jsx', '.ts'],
    8. }
    9. }

    优化:图片压缩

    要求:基于 node 库的 imagemin 或者 tinypng API
    使用:配置 image-webpack-loader

使用 ImageMin

优点:

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

压缩原理image.png

下载npm i image-webpack-loader -D