Performance Config

本章节描述了 Builder 中与性能有关的配置。

performance.buildCache

  • 类型:
type BuildCacheConfig =
  | {
      /**
       * webpack 文件缓存系统的缓存目录
       */
      cacheDirectory?: string;
      /**
       * 根据 cacheDigest 内容设置不同的缓存名称
       */
      cacheDigest?: Array<string | undefined>;
    }
  | boolean;
  • 默认值:
const defaultBuildCacheConfig = {
  cacheDirectory: './node_modules/.cache/webpack',
};
  • 打包工具: 仅支持 webpack

控制 Builder 在构建过程中的缓存行为。

Builder 默认会开启构建缓存来提升二次构建的速度,并默认把生成的缓存文件写到 ./node_modules/.cache/webpack 目录下。

你可以通过 buildCache 配置缓存路径,比如:

export default {
  performance: {
    buildCache: {
      cacheDirectory: './node_modules/.custom_cache/webpack',
    },
  },
};

如果不希望缓存,你也可以将 buildCache 置为 false 将其禁用掉:

export default {
  performance: {
    buildCache: false,
  },
};

cacheDigest

cacheDigest 用来添加一些会对构建结果产生影响的环境变量。Builder 将根据 cacheDigest 内容和当前构建模式来设置缓存名称,来确保不同的 cacheDigest 可以命中不同的缓存。

示例

当前项目需要根据不同的 APP_ID 来设置不同的 extensions。默认情况下,由于当前项目的代码 & 配置 & 依赖未发生变化,会命中之前的缓存。 通过将 APP_ID 添加到 cacheDigest 中,在 APP_ID 变化时会去查找不同的缓存结果,从而避免命中不符合预期的缓存结果。

export default {
  tools: {
    bundlerChain: chain => {
      if (process.env.APP_ID === 'xxx') {
        chain.resolve.extensions.prepend('.ttp.ts');
      }
    },
  },
  performance: {
    buildCache: {
      cacheDigest: [process.env.APP_ID],
    },
  },
};

performance.bundleAnalyze

  • 类型: Object | undefined

用于开启 webpack-bundle-analyzer 插件来分析产物体积。

默认情况下,Builder 不会开启 webpack-bundle-analyzer。当开启该功能后,内部的默认配置如下:

const defaultConfig = {
  analyzerMode: 'static',
  openAnalyzer: false,
  // target 为编译目标,如 `web`、`node` 等
  reportFilename: `report-${target}.html`,
};

启用 Bundle Analyze

你有两种方式开启 webpack-bundle-analyzer 来分析构建产物的体积:

  • 添加环境变量 BUNDLE_ANALYZE=true,比如:
BUNDLE_ANALYZE=true pnpm build
  • 配置 performance.bundleAnalyze 来固定开启:
export default {
  performance: {
    bundleAnalyze: {},
  },
};

在启用后,Builder 会生成一个分析构建产物体积的 HTML 文件,并在 Terminal 中打印以下日志:

Webpack Bundle Analyzer saved report to /Project/my-project/dist/report-web.html

手动在浏览器中打开该文件,可以看到打包产物的瓦片图;区块的面积越大,说明该模块的体积越大。

覆盖默认配置

你可以通过 performance.bundleAnalyze 来覆盖默认配置,比如开启 server 模式:

export default {
  performance: {
    bundleAnalyze: process.env.BUNDLE_ANALYZE
      ? {
          analyzerMode: 'server',
          openAnalyzer: true,
        }
      : {},
  },
};

Size 类型

webpack-bundle-analyzer 的面板中,你可以在左上角控制 Size 类型(默认为 Parsed):

  • Stat:从打包工具的 stats 对象中获取的体积,它反映了代码在压缩之前的体积。
  • Parsed:磁盘上的文件体积,它反映了代码在压缩之后的体积。
  • Gzipped:浏览器里请求的文件体积,它反映了代码在压缩和 gzip 后的体积。

生成 stats.json

generateStatsFile 设置为 true 时,将会生成 stats JSON 文件。

export default {
  performance: {
    bundleAnalyze: {
      generateStatsFile: true,
    },
  },
};

注意事项

  1. 开启 Server 模式会导致 build 进程不能正常退出。

  2. 开启 bundleAnalyzer 会降低构建性能。因此,在日常开发过程中不应该开启此配置项,建议通过 BUNDLE_ANALYZE 环境变量来按需开启。

  3. 由于 dev 阶段不会进行代码压缩等优化,无法反映真实的产物体积,因此建议在 build 阶段分析产物体积。

performance.chunkSplit

  • 类型: Object
  • 默认值: { strategy: 'split-by-experience' }

performance.chunkSplit 用于配置 Builder 的拆包策略。配置项的类型 ChunkSplit 如下:

type ForceSplitting = RegExp[] | Record<string, RegExp>;

interface BaseChunkSplit {
  strategy?:
    | 'split-by-module'
    | 'split-by-experience'
    | 'all-in-one'
    | 'single-vendor';
  override?: SplitChunks;
  forceSplitting?: ForceSplitting;
}

interface SplitBySize {
  strategy?: 'split-by-size';
  minSize?: number;
  maxSize?: number;
  override?: SplitChunks;
  forceSplitting?: ForceSplitting;
}

interface SplitCustom {
  strategy?: 'custom';
  splitChunks?: SplitChunks;
  forceSplitting?: ForceSplitting;
}

export type ChunkSplit = BaseChunkSplit | SplitBySize | SplitCustom;

chunkSplit.strategy

Builder 支持设置以下几种拆包策略:

  • split-by-experience: 根据经验制定的拆分策略,自动将一些常用的 npm 包拆分为体积适中的 chunk。
  • split-by-module: 按 NPM 包的粒度拆分,每个 NPM 包对应一个 chunk。
  • split-by-size:根据模块大小自动进行拆分。
  • all-in-one: 将所有代码全部打包到一个 chunk 中。
  • single-vendor: 将所有 NPM 包的代码打包到一个单独的 chunk 中。
  • custom: 自定义拆包配置。

默认拆包策略

Builder 默认采用 split-by-experience 策略,这是我们根据经验制定的策略。具体来说,当你的项目中引用了以下 npm 包时,它们会自动被拆分为单独的 chunk:

  • lib-polyfill.js:包含 core-js@babel/runtime@swc/helperstslib
  • lib-react.js:包含 reactreact-dom
  • lib-router.js:包含 react-routerreact-router-domhistory@remix-run/router
  • lib-lodash.js:包含 lodashlodash-es
  • lib-antd.js:包含 antd
  • lib-arco.js:包含 @arco-design/web-react
  • lib-semi.js:包含 @douyinfe/semi-ui
TIP

如果项目中没有安装或引用以上 npm 包,则不会生成相应的 chunk。

如果你想使用其他拆包策略,可以通过 performance.chunkSplit.strategy 配置项来指定。

chunkSplit.minSize

  • 类型: number
  • 默认值: 10000

performance.chunkSplit.strategysplit-by-size 时,可以通过 performance.chunkSplit.minSize 配置项来指定 chunk 的最小大小,单位为字节。默认值为 10000。比如:

export default {
  performance: {
    chunkSplit: {
      strategy: 'split-by-size',
      minSize: 20000,
    },
  },
};

chunkSplit.maxSize

  • 类型: number
  • 默认值: Infinity

performance.chunkSplit.strategysplit-by-size 时,可以通过 performance.chunkSplit.maxSize 配置项来指定 chunk 的最大大小,单位为字节。默认值为 Infinity。比如:

export default {
  performance: {
    chunkSplit: {
      strategy: 'split-by-size',
      maxSize: 50000,
    },
  },
};

chunkSplit.forceSplitting

  • 类型: RegExp[] | Record<string, RegExp>
  • 默认值: []

通过 performance.chunkSplit.forceSplitting 配置项可以将指定的模块强制拆分为一个独立的 chunk。

比如将 node_modules 下的 axios 库拆分到 axios.js 中:

export default {
  performance: {
    chunkSplit: {
      strategy: 'split-by-experience',
      forceSplitting: {
        axios: /node_modules\/axios/,
      },
    },
  },
};

相比直接配置 webpack 的 splitChunks,这是一个更加简便的方式。

TIP

注意,通过 forceSplitting 配置拆分的 chunk 会通过 <script> 标签插入到 HTML 文件中,作为首屏请求的资源。因此,请根据实际场景来进行适当地拆分,避免首屏资源体积过大。

chunkSplit.splitChunks

performance.chunkSplit.strategycustom 时,可以通过 performance.chunkSplit.splitChunks 配置项来指定自定义的 webpack 拆包配置。此配置会和 webpack 的 splitChunks 配置进行合并(cacheGroups 配置也会合并)。比如:

export default {
  performance: {
    chunkSplit: {
      strategy: 'custom',
      splitChunks: {
        cacheGroups: {
          react: {
            test: /[\\/]node_modules[\\/](react|react-dom)[\\/]/,
            name: 'react',
            chunks: 'all',
          },
        },
      },
    },
  },
};

chunkSplit.override

performance.chunkSplit.strategysplit-by-experiencesplit-by-modulesplit-by-sizesingle-vendor 时,可以通过 performance.chunkSplit.override 配置项来自定义 webpack 拆包配置,此配置会和 webpack 的 splitChunks 配置进行合并(cacheGroups 配置也会合并)。比如:

export default {
  performance: {
    chunkSplit: {
      strategy: 'split-by-experience',
      override: {
        cacheGroups: {
          react: {
            test: /[\\/]node_modules[\\/](react|react-dom)[\\/]/,
            name: 'react',
            chunks: 'all',
          },
        },
      },
    },
  },
};

当 Builder 构建 "node" 类型的产物时,由于 Node Bundles 不需要通过拆包来优化加载性能,因此 chunkSplit 规则不会生效。

performance.dnsPrefetch

  • 类型: undefined | string[]

  • 默认值: undefined

为哪些资源配置 dns-prefetch 属性。

配置该属性可以在请求资源之前解析域名,降低请求延迟,提升加载性能。

更多信息可参考:Using dns-prefetch

示例

export default {
  performance: {
    dnsPrefetch: ['http://example.com'],
  },
};

performance.preconnect

  • 类型: undefined | Array<string | PreconnectOption>
interface PreconnectOption {
  href: string;
  crossorigin?: boolean;
}
  • 默认值: undefined

为哪些资源配置 preconnect 属性。

配置该属性会预先建立与服务器的连接,如果站点是 HTTPS 的,则此过程包括 DNS 解析,建立 TCP 连接以及执行 TLS 握手。将 Preconnect 和 DnsPrefetch 结合使用可进一步减少跨域请求的延迟。

示例

export default {
  performance: {
    preconnect: ['http://example.com'],
  },
};

performance.prefetch

  • 类型: undefined | true | PrefetchOption
type IncludeType = 'async-chunks' | 'initial' | 'all-assets' | 'all-chunks';

type Filter = Array<string | RegExp> | ((filename: string) => boolean);

interface PrefetchOption {
  type?: IncludeType;
  include?: Filter;
  exclude?: Filter;
}
  • 默认值: undefined

为哪些资源配置 prefetch 属性。

该属性用于配置在将来某些导航下可能需要的资源,此时,浏览器通常在空闲状态时获取此资源。

Boolean 类型

当设置 performance.prefetchtrue,将根据如下配置对资源进行预获取:

{
  type: 'async-chunks',
}

Object 类型

performance.prefetch 的值为 object 类型时,Builder 会根据当前配置对指定资源开启 prefetch 能力。

prefetch.type

type 字段控制了哪些资源会被预获取,同时支持通过 includeexclude 对指定资源进行二次过滤。

目前支持的资源类型如下:

  • async-chunks: 预获取所有异步资源(当前页面),包含异步 js 及其关联的 css、image、font 等资源。
  • initial: 预获取所有非异步资源(当前页面)。需要注意的是,如果当前脚本已经被添加到 html 模版中,则不会进行额外的预获取。
  • all-chunks: 预获取所有资源(当前页面),包含所有异步和非异步资源。
  • all-assets: 预获取所有资源,MPA 场景下会包含其他页面的资源。

示例

当你希望对当前页面中所有 png 格式的图片资源进行预获取时,可以配置如下:

export default {
  performance: {
    prefetch: {
      type: 'all-chunks',
      include: [/.*\.png$/]
    },
  },
};

performance.preload

  • 类型: undefined | true | PreloadOption
type IncludeType = 'async-chunks' | 'initial' | 'all-assets' | 'all-chunks';

type Filter = Array<string | RegExp> | ((filename: string) => boolean);

interface PreloadOption {
  type?: IncludeType;
  include?: Filter;
  exclude?: Filter;
}
  • 默认值: undefined

为哪些资源配置 preload 属性。

该属性通常用于配置当前导航下可能需要的资源,此时,浏览器通常以中等优先级(不是布局阻塞)获取此资源。

Boolean 类型

当设置 performance.preloadtrue,将根据如下配置对资源进行预加载:

{
  type: 'async-chunks',
}

Object 类型

performance.preload 的值为 object 类型时,Builder 会根据当前配置对指定资源开启 preload 能力。

preload.type

type 字段控制了哪些资源会被预加载,同时支持通过 includeexclude 对指定资源进行二次过滤。

目前支持的资源类型如下:

  • async-chunks: 预加载所有异步资源(当前页面),包含异步 js 及其关联的 css、image、font 等资源。
  • initial: 预加载所有非异步资源(当前页面)。需要注意的是,如果当前脚本已经被添加到 html 模版中,则不会进行额外的预加载。
  • all-chunks: 预加载所有资源(当前页面),包含所有异步和非异步资源。
  • all-assets: 预加载所有资源,MPA 场景下会包含其他页面的资源。

示例

当你希望对当前页面中所有 png 格式的图片资源进行预加载时,可以配置如下:

export default {
  performance: {
    preload: {
      type: 'all-chunks',
      include: [/.*\.png$/]
    },
  },
};

performance.printFileSize

  • 类型: boolean
  • 默认值: true

是否在生产环境构建后输出所有静态资源文件的体积。

info    Production file sizes:

  File                                      Size         Gzipped
  dist/static/js/lib-react.09721b5c.js      152.6 kB     49.0 kB
  dist/html/main/index.html                 5.8 kB       2.5 kB
  dist/static/js/main.3568a38e.js           3.5 kB       1.4 kB
  dist/static/css/main.03221f72.css         1.4 kB       741 B

示例

禁用相关日志:

export default {
  performance: {
    printFileSize: false,
  },
};

performance.profile

  • 类型: boolean
  • 默认值: false

是否捕获每个模块的耗时信息,对应 webpack / Rspack 的 profile 配置。

示例

export default {
  performance: {
    profile: true,
  },
};

开启后,Webpack / Rspack 生成一些有关模块的统计数据的 JSON 文件会将模块构建的耗时信息也包含进去。

performance.removeConsole

  • 类型: boolean | ConsoleType[]
  • 默认值: false

在生产环境构建时,是否自动移除代码中的 console.xx

移除所有 console

removeConsole 被设置为 true 时,会移除所有类型的 console.xx

export default {
  performance: {
    removeConsole: true,
  },
};

移除特定的 console

你也可以指定仅移除特定类型的 console.xx,比如移除 console.logconsole.warn

export default {
  performance: {
    removeConsole: ['log', 'warn'],
  },
};

目前支持配置以下类型的 console:

type ConsoleType = 'log' | 'info' | 'warn' | 'error' | 'table' | 'group';

performance.removeMomentLocale

  • 类型: boolean
  • 默认值: false

是否移除 moment.js 的语言包文件。

moment.js 默认包含了大量的语言包文件,会导致打包后的包体积增大。

当项目中使用了 moment.js 时,推荐开启此选项,自动排除所有的语言包文件:

export default {
  performance: {
    removeMomentLocale: true,
  },
};

开启后,可以通过以下方式来加载语言包文件:

import moment from 'moment';
import 'moment/locale/zh-cn';

moment.locale('zh-cn');

performance.transformLodash

  • 类型: boolean
  • 默认值: true
  • 打包工具: 仅支持 webpack

是否模块化 lodash 的导入,删除未使用的 lodash 模块,从而减少 lodash 代码体积。

这项优化基于 babel-plugin-lodashswc-plugin-lodash 实现。

示例

该选项默认开启,Builder 会自动将 lodash 的代码引用指向子路径。

比如:

input.js
import _ from 'lodash';
import { add } from 'lodash/fp';

const addOne = add(1);
_.map([1, 2, 3], addOne);

转换后的代码为:

output.js
import _add from 'lodash/fp/add';
import _map from 'lodash/map';

const addOne = _add(1);
_map([1, 2, 3], addOne);

关闭转换

在个别情况下,lodash 的 import 转换可能会生成不符合预期的代码,此时你可以手动关闭这项优化:

export default {
  performance: {
    transformLodash: false,
  },
};