close
logologo
Guide
Config
Plugin
API
Community
Version
Changelog
Rsbuild 0.x Doc
English
简体中文
Guide
Config
Plugin
API
Community
Changelog
Rsbuild 0.x Doc
English
简体中文
logologo

Getting Started

Introduction
Quick start
Features
Glossary

Framework

React
Vue
Preact
Svelte
Solid

Basic

CLI
Dev server
Output files
Static assets
HTML
JSON
Wasm
TypeScript
Web Workers
Deploy static site
Upgrade Rsbuild

Configuration

Configure Rspack
Configure Rsbuild
Configure SWC

Styling

CSS
CSS Modules
CSS-in-JS
Tailwind CSS v4
Tailwind CSS v3
UnoCSS

Advanced

Path aliases
Environment variables
Hot module replacement
Browserslist
Browser compatibility
Module Federation
Multi-environment builds
Server-side rendering (SSR)
Testing

Optimization

Code splitting
Bundle size optimization
Improve build performance
Inline static assets

Migration

Migrating from Rsbuild 0.x
webpack
Create React App
Vue CLI
Vite
Vite plugin
Modern.js Builder

Debug

Debug mode
Build profiling
Use Rsdoctor

FAQ

General FAQ
Features FAQ
Exceptions FAQ
HMR FAQ
📝 Edit this page on GitHub
Previous PageBrowserslist
Next PageModule Federation

#Browser compatibility

Rsbuild supports modern browsers by default and provides syntax and API downgrade capabilities to ensure compatibility with legacy browsers that support ES5 (such as IE11).

This chapter explains how to use Rsbuild's features to address browser compatibility issues.

#Set browserslist

Before dealing with compatibility issues, you need to determine which browsers your project needs to support and add the corresponding browserslist config.

  • If you haven't set browserslist yet, please read the Browserslist chapter first.

  • If you have set a browserslist, Rsbuild automatically compiles to match that scope, downgrades JavaScript and CSS syntax, and injects required polyfills. In most cases, you can safely use modern ECMAScript features without worrying about compatibility issues.

After setting the browserslist, if you still encounter compatibility issues, continue reading to find solutions.

What is polyfill

A polyfill is code that provides the functionality of newer features to older browsers that don't support them natively. It fills gaps in older browsers' implementations of web standards, allowing developers to use newer features without worrying about whether they'll work in older browsers. For example, if a browser doesn't support the Array.prototype.flat() method, a polyfill can provide that functionality, allowing code that uses Array.prototype.flat() to work in that browser. Polyfills are commonly used to ensure web applications work across a wide range of browsers, including older ones.

#Background knowledge

Before dealing with compatibility issues, it is recommended that you understand the following background knowledge to better handle related issues.

#Syntax downgrade and API downgrade

When you use higher-version syntax and APIs in your project, to make the compiled code run reliably in lower-version browsers, we need to downgrade two parts: syntax and APIs.

Rsbuild downgrades syntax through transpilation and downgrades APIs through polyfill injection.

Syntax and APIs are not tightly coupled. When browser manufacturers implement the engine, they will support some syntax or implement some APIs in advance according to the specification or their own needs. Therefore, browsers from different manufacturers in the same period are not necessarily compatible with syntax and API. So in general practice, syntax and API are handled in two parts.

#Syntax transpilation

Syntax is a set of rules for how a programming language organizes code, and code that doesn't follow these rules cannot be correctly recognized by the programming language's engine and therefore cannot run. In JavaScript, the following are examples of syntax rules:

  • In const foo = 1, const means to declare an immutable constant.
  • In foo?.bar?.baz, ?. indicates optional chaining of access properties.
  • In async function () {}, async means to declare an asynchronous function.

Because different browser parsers support different syntax, especially older browser engines support less syntax, some syntax will cause errors when run in older browser engines at the AST parsing stage.

For example, the following code will cause an error in IE or an older version of Node.js:

const foo = {};
foo?.bar();

When we run this code in a low version of Node.js, the following error message will appear:

SyntaxError: Unexpected token.
   at Object.exports.runInThisContext (vm.js:73:16)
   at Object.<anonymous> ([eval]-wrapper:6:22)
   at Module._compile (module.js:460:26)
   at evalScript (node.js:431:25)
   at startup (node.js:90:7)
   at node.js:814:3

It's obvious from the error message that this is a syntax error, meaning this syntax is not supported in older versions of the engine.

Syntax cannot be supported by polyfill or shim. To run syntax that's not originally supported in a lower-version browser, you need to transpile the code into syntax the lower-version engine can support.

Transpile the above code into the following to run in older version engines:

var foo = {};
foo === null || foo === void 0 ? void 0 : foo.bar();

After transpilation, the syntax of the code has changed, and some syntax the lower-version engine cannot understand has been replaced with syntax it can understand, but the meaning of the code itself hasn't changed.

If the engine encounters an unrecognized syntax when converting to AST, it will report a syntax error and abort code execution. In this case, if your project doesn't use capabilities such as SSR or SSG, the page will be blank, making it unavailable.

If the code is successfully converted to AST, the engine will convert the AST into executable code and run it normally inside the engine.

#API polyfill

JavaScript is an interpreted scripting language, unlike compiled languages like Rust. Rust checks calls in the code during compilation, but JavaScript doesn't know whether a function called by a line of code exists until it actually runs to that line, so some errors only appear at runtime.

For example:

var str = 'Hello world!';
console.log(str.notExistedMethod());

The above code has correct syntax and can be converted to AST correctly in the first stage of the engine runtime, but when it is actually running, because the method notExistedMethod does not exist on String.prototype, an error will be reported:

Uncaught TypeError: str.notExistedMethod is not a function
   at <anonymous>:2:17

With the iteration of ECMAScript, new methods are added to built-in objects. For example, String.prototype.replaceAll was introduced in ES2021. The replaceAll method doesn't exist in the built-in object String.prototype of most browser engines before 2021, so the following code works in the latest Chrome but not in earlier versions:

'abc'.replaceAll('abc', 'xyz');

To solve the problem that String.prototype lacks replaceAll in older browsers, we can extend the String.prototype object in older browsers and add the replaceAll method to it. For example:

// The implementation of this polyfill does not necessarily conform to the standard, it is only used as an example.
if (!String.prototype.replaceAll) {
  String.prototype.replaceAll = function (str, newStr) {
    // If a regex pattern
    if (
      Object.prototype.toString.call(str).toLowerCase() === '[object regexp]'
    ) {
      return this.replace(str, newStr);
    }
    // If a string
    return this.replace(new RegExp(str, 'g'), newStr);
  };
}

This technique of providing implementations for legacy environments to align new APIs is called polyfill.

#Compilation scope

By default, Rsbuild uses SWC to compile all JavaScript and TypeScript modules, excluding JavaScript modules in the node_modules directory.

This approach is designed to avoid impacting build performance when downgrading all third-party dependencies while also preventing potential issues from redundantly downgrading pre-compiled third-party dependencies.

#Source code

The source code of the current project will be downgraded by default, so you don't need to add additional config, just make sure that the browserslist config is set correctly.

#Third-party dependencies

When you find that a third-party dependency causes compatibility issues, you can add this dependency to Rsbuild's source.include config. This makes Rsbuild perform extra compilation for that dependency.

Taking the npm package query-string as an example, you can add the following config:

rsbuild.config.ts
import path from 'node:path';

export default {
  source: {
    include: [/node_modules[\\/]query-string[\\/]/],
  },
};

See source.include for detailed usage.

#Polyfills

Rsbuild compiles JavaScript code using SWC and supports injecting polyfills such as core-js and @swc/helpers.

In different usage scenarios, you may need different polyfill solutions. Rsbuild provides output.polyfill config to switch between different polyfill modes.

#Default behavior

Rsbuild does not inject any polyfills by default:

export default {
  output: {
    polyfill: 'off',
  },
};

#Usage mode

When you enable usage mode, Rsbuild will analyze the source code in the project and determine which polyfills need to be injected.

For example, the code uses the Map object:

var b = new Map();

After compilation, only the polyfills for Map will be injected into this file:

import 'core-js/modules/es.map';
var b = new Map();

The advantage of this method is smaller injected polyfill size, which is suitable for projects with higher requirements on bundle size. The disadvantage is that polyfills may not be fully injected because third-party dependencies won't be compiled and downgraded by default, so the polyfills required by third-party dependencies won't be analyzed. If you need to analyze a third-party dependency, you also need to add it to source.include config.

The config of usage mode is:

export default {
  output: {
    polyfill: 'usage',
  },
};

#Entry mode

When using entry mode, Rsbuild will analyze which core-js methods need to be injected according to the browserslist set for the current project and inject them into the entry file of each page. Polyfills injected this way are more comprehensive, eliminating concerns about polyfill issues in project source code and third-party dependencies. However, because some unused polyfill code is included, the bundle size may increase.

The config of entry mode is:

export default {
  output: {
    polyfill: 'entry',
  },
};

#UA polyfill

Cloudflare provides a polyfill service that automatically generates polyfill bundles based on the user's browser User-Agent.

You can use the html.tags config of Rsbuild to inject scripts. For example, to inject a <script> tag at the beginning of the <head> tag:

export default {
  html: {
    tags: [
      {
        tag: 'script',
        attrs: {
          defer: true,
          src: 'https://cdnjs.cloudflare.com/polyfill/v3/polyfill.min.js?features=default',
        },
        append: false,
      },
    ],
  },
};

#Missing polyfills

It should be noted that core-js cannot provide polyfills for all JavaScript APIs. Some APIs cannot be fully simulated through polyfills due to the complexity of their underlying implementation or performance considerations.

The most typical example is the Proxy object. Since Proxy requires engine-level support to implement object operation interception, its behavior cannot be fully simulated through pure JavaScript code, so core-js doesn't provide a polyfill for Proxy.

See core-js - Missing polyfills to understand which APIs core-js cannot provide polyfills for.