SWC Plugin

WARNING

The SWC feature in the current document is no longer maintained, we recommend using the Rspack + SWC solution.

Please refer to 「Use Rspack」 for more information.

SWC (Speedy Web Compiler) is a transformer and minimizer for JavaScript and TypeScript based on Rust. SWC can provide the same abilities with Babel, and it's more than 10x faster than Babel.

Modern.js has a out-of-box plugin for SWC, power your Web application with Polyfill and minification, we also port some common used Babel plugins to SWC.

Usage Scenarios

Before using the SWC plugin, please understand the scenarios and limitations of the SWC plugin to determine whether your project is suitable for using it.

Rspack Scenario

If you are already using Rspack as the bundler in your project, then you do not need to use the SWC plugin, because Rspack uses SWC for transpiler and minifier by default, and the SWC compilation capabilities are available out of the box.

If you have configured the current SWC plugin when using Rspack, it will not have any effect.

Babel Plugins

If your project requires the registration of some custom Babel plugins, you will not be able to register and use Babel plugins after using SWC, since it replaces Babel as the transpiler.

For most common Babel plugins, you can find corresponding replacements in SWC, such as:

If you use Babel plugin capabilities that are not yet supported by SWC, you will no longer be able to use them after switching to SWC compilation. You can give feedback via issues under the swc-plugins repository and we will evaluate if built-in support is needed.

Bundle Size

When using SWC for code minification instead of terser and cssnano, there may be a small change in the bundle size. SWC outperforms terser for JavaScript code compression and slightly underperforms cssnano for CSS code compression.

For a detailed comparison between minifiers, see minification-benchmarks.

Quick Start

Used in Modern.js framework

The Modern.js framework integrates the SWC plugin, and you can use it in the following ways:

First, you need to execute pnpm run new to enable the SWC compile:

? Please select the operation you want: Enable features
? Please select the feature name: Enable SWC Compile

After the installation, please register the SWC plugin in the modern.config.ts file, then the SWC compilation and compression will be enabled.

modern.config.ts
import { appTools, defineConfig } from '@modern-js/app-tools';
import { swcPlugin } from '@modern-js/plugin-swc';

export default defineConfig({
  plugins: [appTools(), swcPlugin()],
});

That's it! Now you can use SWC transformation and minification in your project.

Config

  • Type:
type PluginConfig =
  | ObjPluginConfig
  | ((
      config: ObjPluginConfig,
      utils: { mergeConfig: typeof lodash.merge; setConfig: typeof lodash.set },
    ) => ObjPluginConfig | void);

// SwcOptions is configurations of swc https://swc.rs/docs/configuration/compilation
interface ObjPluginConfig extends SwcOptions {
  presetReact?: ReactConfig;
  presetEnv?: EnvConfig;
  jsMinify?: boolean | JsMinifyOptions;
  cssMinify?: boolean | CssMinifyOptions;
  extensions?: Extensions;
  overrides?: Overrides;
}

The plugin configuration is based on the SWC configuration. In order to simplify some deep-level configurations and improve development experience, certain extensions have been made. When using object-based configuration, for example you can use presetReact and presetEnv to quickly configure React-related features and syntax downgrading. Other configurations will also be directly passed through to SWC.

When using function-based configuration, the default configuration generated by the plugin will be passed in, allowing you to modify it or return a new configuration.

presetReact

Ported from @babel/preset-react. The value you passed will be merged with default option.

By default, the plugin will set runtime field based on your react version, if react version is newer than 17.0.0, it will be set to automatic, otherwish classic.

presetEnv

Ported from @babel/preset-env. The value you passed will be merged with default option.

Default option is:

{
  targets: '', // automatic get browserslist from your project, so you don't have to set this field
  mode: 'usage',
}

jsMinify

If set it to false, then SWC minification will be disabled, if set it to true then will it applies default option. If you pass an object, then this object will be merged with default option.

cssMinify

  • Type:: boolean
  • Default:: true

Whether enable to compress CSS files by SWC. If enabled, it will improve the performance of CSS compression, but the compression ratio will be slightly reduced.

overrides

  • Type:
interface Overrides extends SwcOptions {
  test: RegExp;
  include: RegExp[];
  exclude: RegExp[];
}
  • Default: undefined

Specify special configuration for specific modules. For example if you want to set ie 11 target for foo.ts:

{
  test: /foo.ts/,
  env: { targets: 'ie 11' }
}

This will merged into the default configuration, and do not affect other modules.

extensions

  • Type: Object

Some plugins ported from Babel.

extensions.reactUtils

  • Type: Object
type ReactUtilsOptions = {
  autoImportReact?: boolean;
  removeEffect?: boolean;
  removePropTypes?: {
    mode?: 'remove' | 'unwrap' | 'unsafe-wrap';
    removeImport?: boolean;
    ignoreFilenames?: string[];
    additionalLibraries?: string[];
    classNameMatchers?: string[];
  };
};

Some little help utils for React.

reactUtils.autoImportReact

  • Type: boolean

Automatically import React as global variable, eg: import React from 'react'. Mostly used for generated React.createElement.

reactUtils.removeEffect

  • Type: boolean

Remove useEffect call.

reactUtils.removePropTypes

  • Type:
type RemovePropTypesOptions = {
  mode?: 'remove' | 'unwrap' | 'unsafe-wrap';
  removeImport?: boolean;
  ignoreFilenames?: string[];
  additionalLibraries?: string[];
  classNameMatchers?: string[];
};

Remove React runtime type checking. This is ported from @babel/plugin-react-transform-remove-prop-types, All the configurations remain the same.

extensions.lodash

  • Type:
type LodashOptions = {
  cwd?: string;
  ids?: string[];
};
  • Default:
const defaultOptions = {
  cwd: process.cwd(),
  ids: ['lodash', 'lodash-es'],
};

Ported from babel-plugin-lodash, it is used to automatically convert references to Lodash into on-demand imports, thereby reducing the bundle size of Lodash code.

// Input
import { get, throttle } from 'lodash';

// Output
import get from 'lodash/get';
import throttle from 'lodash/throttle';

extensions.styledComponents

  • Type:
boolean | {
  // Enabled by default in development, disabled in production to reduce file size,
  // setting this will override the default for all environments.
  displayName?: boolean,
  // Enabled by default.
  ssr?: boolean,
  // Enabled by default.
  fileName?: boolean,
  // Empty by default.
  topLevelImportPaths?: string[],
  // Defaults to ["index"].
  meaninglessFileNames?: string[],
  // Enabled by default.
  cssProp?: boolean,
  // Empty by default.
  namespace?: string,
};

This is ported by Next.js team from babel-plugin-styled-components.

extensions.emotion

  • Type:
boolean | {
  // default is true. It will be disabled when build type is production.
  sourceMap?: boolean,
  // default is 'dev-only'.
  autoLabel?: 'never' | 'dev-only' | 'always',
  // default is '[local]'.
  // Allowed values: `[local]` `[filename]` and `[dirname]`
  // This option only works when autoLabel is set to 'dev-only' or 'always'.
  // It allows you to define the format of the resulting label.
  // The format is defined via string where variable parts are enclosed in square brackets [].
  // For example labelFormat: "my-classname--[local]", where [local] will be replaced with the name of the variable the result is assigned to.
  labelFormat?: string,
  // default is undefined.
  // This option allows you to tell the compiler what imports it should
  // look at to determine what it should transform so if you re-export
  // Emotion's exports, you can still use transforms.
  importMap?: {
    [packageName: string]: {
      [exportName: string]: {
        canonicalImport?: [string, string],
        styledBaseImport?: [string, string],
      }
    }
  },
}

This is ported by Next.js team from @emotion/babel-plugin

extensions.pluginImport

TIP

Modern.js provides the source.transformImport config, so you don't need to configure extensions.pluginImport manually.

Ported from babel-plugin-import, configurations are the same.

Some configurations can be passed in functions, such as customName, customStyleName. These JavaScript functions will be called by Rust through Node-API, which will cause some performance overhead.

Some simple function logic can be replaced by template language. Therefore, the configuration of function items such as customName, customStyleName can also be passed in strings as templates to replace functions and improve performance.

For example:

import { MyButton as Btn } from 'foo';

Apply following configurations:

PluginSWC({
  extensions: {
    pluginImport: [
      {
        libraryName: 'foo',
        customName: 'foo/es/{{ member }}',
      },
    ],
  },
});

{{ member }} will be replaced by the imported specifier:

import Btn from 'foo/es/MyButton';

Template customName: 'foo/es/{{ member }}' is the same as customName: (member) => `foo/es/${member}` , but template value has no performance overhead of Node-API.

The template used here is handlebars. There are some useful builtin tools, Take the above import statement as an example:

PluginSWC({
  extensions: {
    pluginImport: [
      {
        libraryName: 'foo',
        customName: 'foo/es/{{ kebabCase member }}',
      },
    ],
  },
});

Transformed to:

import Btn from 'foo/es/my-button';

In addition to kebabCase, there are cameraCase, snakeCase, upperCase and lowerCase can be used as well.

Limitation

Do not support @babel/plugin-transform-runtime and other custom Babel plugins.