Performance Config

This section describes some performance related configurations in Modern.js Builder.

performance.buildCache

  • Type:
type BuildCacheConfig =
  | {
      /**
       * Base directory for the filesystem cache.
       */
      cacheDirectory?: string;
      /**
       * Set different cache names based on cacheDigest content.
       */
      cacheDigest?: Array<string | undefined>;
    }
  | boolean;
  • Default:
const defaultBuildCacheConfig = {
  cacheDirectory: './node_modules/.cache/webpack',
};
  • Bundler: only support webpack

Controls the Builder's caching behavior during the build process.

Builder will enable build cache by default to improve the compile speed, the generated cache files are write to the ./node_modules/.cache/webpack directory by default.

You can configure the cache path with buildCache, e.g.

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

You can also disable the build cache by setting it to false:

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

cacheDigest

cacheDigest is used to add some environment variables that will affect the build results. The Builder will set the cache name based on the cacheDigest content and the current build mode to ensure that different cacheDigests can hit different caches.

Example

The current project needs to set different extensions according to different APP_ID. By default, since the code & configuration & dependencies of the current project have not changed, the previous cache will be hit.

By adding APP_ID to cacheDigest, different cache results will be searched when APP_ID changes, thereby avoiding hitting cache results that do not meet expectations.

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

performance.bundleAnalyze

  • Type: Object | undefined

Used to enable the webpack-bundle-analyzer plugin to analyze the size of the output.

By default, Builder does not enable webpack-bundle-analyzer. When this feature is enabled, the default configuration is as follows:

const defaultConfig = {
  analyzerMode: 'static',
  openAnalyzer: false,
  // target is the compilation target, such as `web`, `node`, etc.
  reportFilename: `report-${target}.html`,
};

Enable Bundle Analyze

You have two ways to enable webpack-bundle-analyzer to analyze the size of the output files:

  • Add the environment variable BUNDLE_ANALYZE=true, for example:
BUNDLE_ANALYZE=true pnpm build
  • Configure performance.bundleAnalyze to enable it permanently:
export default {
  performance: {
    bundleAnalyze: {},
  },
};

After enabling it, Builder will generate an HTML file that analyzes the size of the output files, and print the following log in the Terminal:

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

You can manually open the file in the browser and view the detail of the bundle size. When an area is larger, it indicates that its corresponding bundle size is larger.

Override Default Configuration

You can override the default configuration through performance.bundleAnalyze, such as enabling the server mode:

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

Size Types

In the webpack-bundle-analyzer panel, you can control size types in the upper left corner (default is Parsed):

  • Stat: The size obtained from the stats object of the bundler, which reflects the size of the code before minification.
  • Parsed: The size of the file on the disk, which reflects the size of the code after minification.
  • Gzipped: The file size requested in the browser reflects the size of the code after minification and gzip.

Generate stats.json

By setting generateStatsFile to true, stats JSON file will be generated in bundle output directory.

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

Notes

  1. Enabling the server mode will cause the build process to not exit normally.

  2. Enabling bundleAnalyzer will reduce build speed. Therefore, this configuration should not be enabled during daily development, and it is recommended to enable it on demand through the BUNDLE_ANALYZE environment variable.

  3. Since no code minification and other optimizations are performed in the dev phase, the real output size cannot be reflected, so it is recommended to analyze the output size in the build phase.

performance.chunkSplit

  • Type: Object
  • Default: { strategy: 'split-by-experience' }

performance.chunkSplit is used to configure the chunk splitting strategy. The type of ChunkSplit is as follows:

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 supports the following chunk splitting strategies:

  • split-by-experience: an empirical splitting strategy, automatically splits some commonly used npm packages into chunks of moderate size.

  • split-by-module: split by NPM package granularity, each NPM package corresponds to a chunk.

  • split-by-size: automatically split according to module size.

  • all-in-one: bundle all codes into one chunk.

  • single-vendor: bundle all NPM packages into a single chunk.

  • custom: custom chunk splitting strategy.

Default Strategy

Builder adopts the split-by-experience strategy by default, which is a strategy we have developed from experience. Specifically, when the following npm packages are referenced in your project, they will automatically be split into separate chunks:

  • lib-polyfill.js: includes core-js, @babel/runtime, @swc/helpers, tslib.
  • lib-react.js: includes react, react-dom.
  • lib-router.js: includes react-router, react-router-dom, history, @remix-run/router.
  • lib-lodash.js: includes lodash, lodash-es.
  • lib-antd.js: includes antd.
  • lib-arco.js: includes @arco-design/web-react.
  • lib-semi.js: includes @douyinfe/semi-ui.
TIP

If the above npm packages are not installed or used in the project, the corresponding chunk will not be generated.

If you want to use other splitting strategies, you can specify it via performance.chunkSplit.strategy.

TIP

The split-by-module strategy is not supported when using Rspack as the bundler.

chunkSplit.minSize

  • Type: number
  • Default: 10000

When performance.chunkSplit.strategy is split-by-size, you can specify the minimum size of a chunk via performance.chunkSplit.minSize, the unit is bytes. The default value is 10000. For example:

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

chunkSplit.maxSize

  • Type: number
  • Default: Infinity

When performance.chunkSplit.strategy is split-by-size, you can specify the maximum size of a chunk via performance.chunkSplit.maxSize, the unit is bytes. The default value is Infinity. For example:

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

chunkSplit.forceSplitting

  • Type: RegExp[] | Record<string, RegExp>
  • Default: []

Via performance.chunkSplit.forceSplitting, you can specify the NPM packages that need to be forced to split.

For example, split the axios library under node_modules into axios.js:

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

This is an easier way than configuring webpack's splitChunks directly.

TIP

Chunks split using the forceSplitting configuration will be inserted into the HTML file as resources requested for the initial screen using <script> tags. Therefore, please split them appropriately based on the actual scenario to avoid excessive size of initial screen resources.

chunkSplit.splitChunks

When performance.chunkSplit.strategy is custom, you can specify the custom webpack chunk splitting config via performance.chunkSplit.splitChunks. This config will be merged with the webpack splitChunks config (the cacheGroups config will also be merged). For example:

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

chunkSplit.override

When performance.chunkSplit.strategy is split-by-experience, split-by-module, split-by-size or single-vendor, you can specify the custom webpack chunk splitting config via performance.chunkSplit.override. This config will be merged with the webpack splitChunks config (the cacheGroups config will also be merged). For example:

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

When the Builder target is "node", since Node Bundles do not need to be splitted to optimize loading performance, the chunkSplit rule will not take effect.

performance.dnsPrefetch

  • Type: undefined | string[]

  • Default: undefined

Specifies that the user agent should preemptively perform DNS resolution for the target resource's origin, refer to dns-prefetch.

After this property is set, the domain name can be resolved before the resource is requested, reducing request latency and improving loading performance.

See Using dns-prefetch for more details.

Example

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

performance.preconnect

  • Type: undefined | Array<string | PreconnectOption>
interface PreconnectOption {
  href: string;
  crossorigin?: boolean;
}
  • Default: undefined

Specifies that the user agent should preemptively connect to the target resource's origin, refer to preconnect.

Configuring this property will establish a connection with the server. If the site is HTTPS, this process includes DNS resolution, TCP connection establishment, and TLS handshake. Combining Preconnect and DnsPrefetch can further reduce the delay of cross-domain requests.

Example

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

performance.prefetch

  • Type: 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;
}
  • Default: undefined

Specifies that the user agent should preemptively fetch and cache the target resource as it is likely to be required for a followup navigation. Refer to prefetch.

Boolean Type

When setting performance.prefetch to true, resources will be prefetched according to the following configuration:

{
  type: 'async-chunks',
}

Object Type

When the value of performance.prefetch is object type, the Builder will enable the prefetch capability for the specified resource according to the current configuration.

prefetch.type

The type field controls which resources will be pre-fetched, and supports secondary filtering of specified resources through include and exclude.

Currently supported resource types are as follows:

  • async-chunks: prefetch all asynchronous resources (on the current page), including asynchronous js and its associated css, image, font and other resources.
  • initial: prefetch all non-async resources (on the current page). It should be noted that if the current script has been added to the html template, no additional pre-fetching will be performed.
  • all-chunks: prefetch all resources (on the current page), including all asynchronous and non-asynchronous resources.
  • all-assets: prefetch all resources, and resources of other pages will be included in the MPA scenario.

Example

When you want to prefetch all image resources in png format on the current page, you can configure it as follows:

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

performance.preload

  • Type: 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;
}
  • Default: undefined

Specifies that the user agent must preemptively fetch and cache the target resource for current navigation according to the potential destination given by the as attribute (and the priority associated with the corresponding destination). Refer to preload.

Boolean Type

When setting performance.preload to true, resources will be preloaded according to the following configuration:

{
  type: 'async-chunks',
}

Object Type

When the value of performance.preload is object type, the Builder will enable the preload capability for the specified resource according to the current configuration.

preload.type

The type field controls which resources will be pre-fetched, and supports secondary filtering of specified resources through include and exclude.

Currently supported resource types are as follows:

  • async-chunks: preload all asynchronous resources (on the current page), including asynchronous js and its associated css, image, font and other resources.
  • initial: preload all non-async resources (on the current page). It should be noted that if the current script has been added to the html template, no additional pre-fetching will be performed.
  • all-chunks: preload all resources (on the current page), including all asynchronous and non-asynchronous resources.
  • all-assets: preload all resources, and resources of other pages will be included in the MPA scenario.

Example

When you want to preload all image resources in png format on the current page, you can configure it as follows:

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

performance.printFileSize

  • Type: boolean
  • Default: true

Whether to print the file sizes after production build.

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

Example

Disable the logs:

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

performance.profile

  • Type: boolean
  • Default: false

Whether capture timing information for each module, same as the profile config of webpack / Rspack.

Example

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

When turned on, webpack / Rspack generates a JSON file with some statistics about the module that includes information about timing information for each module.

performance.removeConsole

  • Type: boolean | ConsoleType[]
  • Default: false

Whether to remove console.xx in production build.

Remove all consoles

When removeConsole is set to true, all types of console.xx are removed:

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

Remove specific console

You can also specify to remove only certain types of console.xx, such as console.log and console.warn:

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

The following types of console are currently supported:

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

performance.removeMomentLocale

  • Type: boolean
  • Default: false

Whether to remove the locales of moment.js.

moment.js contains a lot of locales by default, which will increase the bundle size.

When moment.js is used in the project, it is recommended to enable this option to automatically exclude all locales:

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

Once enabled, you can load a specific locale via:

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

moment.locale('zh-cn');

performance.transformLodash

  • Type: boolean
  • Default: true
  • Bundler: only support webpack

Specifies whether to modularize the import of lodash and remove unused lodash modules to reduce the code size of lodash.

This optimization is implemented using babel-plugin-lodash and swc-plugin-lodash under the hood.

Example

This option is enabled by default, and Builder will automatically redirects the code references of lodash to sub-paths.

For example:

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

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

The transformed code will be:

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

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

Disabling the Transformation

In some cases, the import transformation of lodash may generate unexpected code. In such cases, you can manually disable this optimization:

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