How to Configure SWC Preset-Env for Targeted Browser Support and Polyfill Management

SWC's preset-env automatically injects core-js polyfills based on target browser definitions, supporting both static and dynamic polyfill injection via the mode option in .swcrc or the Rust Config struct.

SWC's preset-env transform replicates Babel's @babel/preset-env functionality within the high-performance Rust compiler. According to the swc-project/swc source code, the Config struct in swc_ecma_preset_env/src/lib.rs drives the entire flow, determining which browsers to support and how to manage polyfill injection from core-js 2 or 3.

Defining Browser Targets

The targets field in Config accepts an Option<Targets> that determines which runtime environments require polyfill support. In preset_env_base/src/query.rs, the targets_to_versions function parses your input into a Versions map.

You have two approaches for target definition:

  • Browserslist query: Pass a standard string like "> 0.25%, not dead" to leverage your existing browserslist configuration.
  • Explicit version map: Define specific versions per browser, such as { "chrome": "92", "ie": "11" }.

The internal Version struct in preset_env_base/src/version.rs compares these against feature requirements to determine which transforms and polyfills are necessary.

Selecting Core-JS Versions and Injection Modes

The core_js field specifies which core-js release to reference (2 or 3), defaulting to version 3 when omitted. This selection pairs with the mode option to control polyfill injection strategy.

The Mode enum offers two distinct behaviors:

  • Mode::Usage: SWC scans the AST to detect actually-used features, injecting only the specific core-js modules required by your code and target browsers.
  • Mode::Entry: SWC imports all core-js modules necessary for your target browsers regardless of actual usage, creating a comprehensive polyfill bundle.

Both settings are processed in EnvConfig::from (lines 706-735 of swc_ecma_preset_env/src/lib.rs) and forwarded to the Polyfills visitor.

Fine-Tuning Polyfills with Include and Exclude

SWC provides granular control over polyfill inclusion through two Vec<FeatureOrModule> fields:

  • include: Forces specific features or core-js modules into the output regardless of the target check. Accepts items like "es.object.fromEntries" or features from transform_data::Feature.
  • exclude: Prevents specific polyfills from being injected even if required by the target browsers.

The FeatureOrModule::split method separates these into distinct feature and module sets. During Polyfills::collect, the system consults these sets—omitting excluded modules while ensuring included ones appear in the final bundle.

Skipping Specific Transforms

The skip field holds a Vec<Atom> of transform names to disable (e.g., "es2015.arrowFunctions" or "es2020.nullishCoalescing"). When constructing the transform pipeline in transform_internal, SWC checks this list before adding each pass to the compilation chain. This allows you to preserve specific ES syntax features even when targeting older browsers.

Advanced Configuration Options

Additional boolean flags in Config modify transform behavior:

  • loose: Relaxes spec-compliant transforms (particularly class fields and spread operators) to generate smaller, faster output at the cost of strict adherence to the ECMAScript specification.
  • debug: Outputs the target browser list and injected polyfills to stderr during compilation, helping you verify which optimizations SWC applies.
  • dynamic_import: Controls how dynamic import() expressions are handled for older module systems, configured in the transform_internal builder at line 49 of swc_ecma_preset_env/src/lib.rs.

Practical Configuration Examples

JSON Configuration (.swcrc)

The most common approach uses a .swcrc file defining the presetEnv object within jsc.transform:

{
  "jsc": {
    "target": "es2015",
    "parser": { "syntax": "ecmascript", "jsx": true },
    "transform": {
      "presetEnv": {
        "targets": { "chrome": "92", "ie": "11" },
        "coreJs": 3,
        "mode": "usage",
        "exclude": ["web.url", "es.promise.finally"],
        "include": ["es.object.fromEntries"],
        "skip": ["es2020.nullishCoalescing"],
        "loose": true,
        "debug": true
      }
    }
  },
  "module": { "type": "commonjs" }
}

Command Line Usage

Apply your configuration via the SWC CLI:

swc src/**/*.js --config-file .swcrc

Programmatic Rust API

For direct integration, construct a Config and pass it to transform_from_env:

use swc_ecma_preset_env::{Config, Mode, transform_from_env};
use swc_common::{sync::Lrc, SourceMap, Mark};
use preset_env_base::{query::Targets, version::Version};
use swc_ecma_visit::VisitMutWith;

let config = Config {
    targets: Some(Targets {
        browsers: Some(vec!["> 0.25%, not dead".into()]),
        ..Default::default()
    }),
    core_js: Some(Version { major: 3, minor: 0, patch: 0 }),
    mode: Some(Mode::Usage),
    exclude: vec!["web.url".into()],
    include: vec!["es.object.fromEntries".into()],
    ..Default::default()
};

let env_config = config.into();
let mut pass = transform_from_env(
    Mark::new(),
    None,
    env_config,
    swc_ecma_transforms::assumptions::Assumptions::default(),
);

// Apply pass to your module
module.visit_mut_with(&mut pass);

The transform_from_env function builds the complete pass pipeline, while Polyfills::visit_mut_module and Polyfills::visit_mut_script handle the actual AST manipulation to inject the determined core-js imports.

Summary

  • Target Definition: Use browserslist queries or explicit version maps in preset_env_base/src/query.rs to specify supported browsers.
  • Polyfill Control: Choose between Mode::Usage (tree-shaken) and Mode::Entry (complete) injection strategies.
  • Granular Tuning: Leverage include, exclude, and skip to override automatic feature detection.
  • Configuration: Define settings in .swcrc JSON or programmatically via the Config struct in swc_ecma_preset_env/src/lib.rs.

Frequently Asked Questions

What is the difference between usage mode and entry mode in SWC preset-env?

Usage mode analyzes your actual source code to determine which JavaScript features you use, then injects only the specific core-js polyfills required for those features in your target browsers. Entry mode ignores your source code and instead imports all core-js modules that might be needed for your target browsers, creating a larger but more comprehensive polyfill bundle. Usage mode typically produces smaller bundles, while entry mode ensures runtime safety regardless of code changes.

How does SWC determine which browsers to support?

SWC parses the targets field through the targets_to_versions function in preset_env_base/src/query.rs. This accepts either a browserslist query string (like "> 0.25%, not dead") or a JSON object mapping browser names to version strings (like { "chrome": "92" }). The resulting Versions map is compared against feature requirements in preset_env_base/src/version.rs using the should_enable helper to decide when transforms and polyfills are necessary.

Can I exclude specific polyfills even if my target browsers need them?

Yes. The exclude field in the preset-env configuration accepts an array of core-js module names (such as "web.url" or "es.promise.finally"). During the Polyfills::collect phase, SWC checks this exclusion list and omits any matching modules from the injection phase, even if the target browser versions would normally require them. This is useful when you know a particular feature is handled by external dependencies or runtime guarantees.

What happens when I set loose: true in the configuration?

Setting loose: true relaxes spec-compliance in favor of smaller output size. According to swc_ecma_preset_env/src/lib.rs, this flag adjusts the assumptions passed to individual transforms, causing them to generate simpler code that behaves slightly differently from strict ECMAScript specifications. For example, class fields may use simple assignment instead of DefineProperty semantics. Use this option when you prioritize bundle size over exact spec compliance and understand the semantic differences.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →