1. Project Overview & Quickstart (antfu/LICENSE)
LICENSE
Open-source repository antfu/LICENSE
Repository Details
- Repository: antfu/LICENSE
- Primary Language: Code
Note: High-volume repository documentation is actively indexed and synchronized by YakaAI.
2. Official Technical Reference & Guides (antfu/website)
File: README.md
SWC Website
Built Using
File: pages/docs/configuration/bundling.mdx
import Callout from 'nextra-theme-docs/callout'
Bundling Configuration
SWC is able to bundle multiple JavaScript or TypeScript files into one.
This feature is currently named spack, but will be renamed to swcpack in v2. spack.config.js will be deprecated for swcpack.config.js.
View a basic example of bundling.
Configuration
You can configure bundling using spack.config.js with similar options to webpack. In the future, we are exploring a webpack compatible plugin system.
// spack.config.js
module.exports = {
entry: {
web: __dirname + "/src/index.ts",
},
output: {
path: __dirname + "/lib",
},
};Note: CommonJS is currently required. In the future, ES Modules will be supported.
If you want auto-completion or type checking for configuration, you can wrap the export with a config function from @swc/core/spack. It's an identity function with type annotation.
const { config } = require("@swc/core/spack");
module.exports = config({
entry: {
web: __dirname + "/src/index.ts",
},
output: {
path: __dirname + "/lib",
},
});mode
Possible values: production, debug, none.
Currently this value is not used, but it will behave similarly to webpack.
entry
Determines the entry of bundling. You may specify a file or a map of bundle name to file path.
Note: Currently this should be absolute path. You can use
__dirnameto create one.In the future, SWC will support using relative paths and will resolve files relative to
spack.config.js.
output
You can change destination directory of the bundler using output.
const { config } = require("@swc/core/spack");
module.exports = config({
output: {
path: __dirname + "/lib",
// Name is optional.
name: "index.js",
},
});options
Used to control the behavior of SWC. This field is optional.
File: pages/docs/configuration/compilation.md
Compilation
Compilation works out of the box with SWC and does not require customization. Optionally, you can override the configuration. Here are the defaults:
{
"jsc": {
"parser": {
"syntax": "ecmascript",
"jsx": false,
"dynamicImport": false,
"privateMethod": false,
"functionBind": false,
"exportDefaultFrom": false,
"exportNamespaceFrom": false,
"decorators": false,
"decoratorsBeforeExport": false,
"topLevelAwait": false,
"importMeta": false,
"preserveAllComments": false
},
"transform": null,
"target": "es5",
"loose": false,
"externalHelpers": false,
// Requires v1.2.50 or upper and requires target to be es2016 or upper.
"keepClassNames": false
},
"isModule": false
}jsc.externalHelpers
{
"jsc": {
"externalHelpers": true
}
}The output code may depend on helper functions to support the target environment. By default, a helper function is inlined into the output files where it is required.
You can use helpers from an external module by enabling externalHelpers and the helpers code will be imported by the output files from node_modules/@swc/helpers.
While bundling, this option will greatly reduce your file size.
You must add @swc/helpers as a dependency in addition to @swc/core.
jsc.parser
typescript
{
"jsc": {
"parser": {
"syntax": "typescript",
"tsx": false,
"decorators": false,
"dynamicImport": false
}
}
}ecmascript
{
"jsc": {
"parser": {
"syntax": "ecmascript",
"jsx": false,
"dynamicImport": false,
"privateMethod": false,
"functionBind": false,
"classPrivateProperty": false,
"exportDefaultFrom": false,
"exportNamespaceFrom": false,
"decorators": false,
"decoratorsBeforeExport": false,
"importMeta": false
}
}
}jsc.target
Starting from @swc/core v1.0.27, you can specify the target environment by using the field.
{
"jsc": {
// Disable es3 / es5 / es2015 transforms
"target": "es2016"
}
}jsc.loose
Starting from @swc/core v1.1.4, you can enable "loose" transformations by enabling jsc.loose which works like babel-preset-env loose mode.
{
"jsc": {
"loose": true
}
}jsc.transform
{
"jsc": {
"transform": {
"react": {
"pragma": "React.createElement",
"pragmaFrag": "React.Fragment",
"throwIfNamespace": true,
"development": false,
"useBuiltins": false
},
"optimizer": {
"globals": {
"vars": {
"__DEBUG__": "true"
}
}
}
}
}
}jsc.transform.legacyDecorator
You can use the legacy (stage 1) class decorators syntax and behavior.
{
"jsc": {
"parser": {
"syntax": "ecmascript",
"decorators": true
},
"transform": {
"legacyDecorator": true
}
}
}jsc.transform.decoratorMetadata
This feature requires v1.2.13+.
If you are using typescript and decorators with emitDecoratorMetadata enabled, you can use swc for faster iteration:
{
"jsc": {
"parser": {
"syntax": "typescript",
"decorators": true
},
"transform": {
"legacyDecorator": true,
"decoratorMetadata": true
}
}
}jsc.transform.react
jsc.transform.react.runtime
Possible values: automatic, classic. This affects how JSX source code will be compiled.
- Use
runtime: automaticto use a JSX runtime module (e.g.react/jsx-runtimeintroduced in React 17). - Use
runtime: classicto useReact.createElementinstead - with this option, you must ensure thatReactis in scope when using JSX.
jsc.transform.react.importSource
- Defaults to
react. - When using
runtime: automatic, determines the runtime library to import. - This option can be overrided with
@jsxImportSource foo.
jsc.transform.react.pragma
- Defaults to
React.createElement. - When using
runtime: classic, replaces the function used when compiling JSX expressions. - This option can be overrided with
@jsx foo.
jsc.transform.react.pragmaFrag
- Defaults to
React.Fragment - Replace the component used when compiling JSX fragments.
- This option can be overrided with
@jsxFrag foo.
jsc.transform.react.throwIfNamespace
Toggles whether or not to throw an error if an XML namespaced tag name is used. For example: <f:image />
Though the JSX spec allows this, it is disabled by default since React's JSX does not currently have support for it.
jsc.transform.react.development
Toggles debug props __self and __source on elements generated from JSX, which are used by development tooling such as React Developer Tools.
This option is set automatically based on the Webpack mode setting when used with swc-loader. See Using swc with webpack.
jsc.transform.react.useBuiltins
Use Object.assign() instead of _extends. Defaults to false.
jsc.transform.react.refresh
Enable react-refresh related transform. Defaults to false as it's considered experimental.
Pass refresh: true to enable this feature, or an object with the following:
interface ReactRefreshConfig {
refreshReg: String;
refreshSig: String;
emitFullSignatures: boolean;
}jsc.transform.constModules
{
"jsc": {
"transform": {
"constModules": {
"globals": {
"@ember/env-flags": {
"DEBUG": "true"
},
"@ember/features": {
"FEATURE_A": "false",
"FEATURE_B": "true"
}
}
}
}
}
}Then, source code like:
import { DEBUG } from "@ember/env-flags";
import { FEATURE_A, FEATURE_B } from "@ember/features";
console.log(DEBUG, FEATURE_A, FEATURE_B);is transformed to:
console.log(true, false, true);jsc.transform.optimizer
The SWC optimizier assumes:
- It's a module or wrapped in an iife.
- Accessing (get) global variables does not have a side-effect. It is the same assumption as the google closure compiler.
- You don't add fields to literals like a numeric literal, regular expression or a string literal.
- Files are served as gzipped.
SWC will not focus on reducing the size of non-gzipped file size.
Setting this to undefined skips optimizer pass.
jsc.transform.optimizer.simplify
Requires
v1.2.101+
You can set this to false to use inline_globals while skipping optimizations.
{
"jsc": {
"transform": {
"optimizer": {
"simplify": false,
"globals": {
"vars": {
"__DEBUG__": "true"
}
}
}
}
}
}jsc.transform.optimizer.globals
Requires
v1.2.101+
vars- Variables to inline.typeofs- If you set{ "window": "object" },typeof windowwill be replaced with"object".
{
"jsc": {
"transform": {
"optimizer": {
"globals": {
"vars": {
"__DEBUG__": "true"
}
}
}
}
}
}Then, you can use it like npx swc '__DEBUG__' --filename input.js.
jsc.transform.optimizer.jsonify
Requires
v1.1.1+
minCost- If cost of parsing a pure object literal is larger than this value, the object literal is converted toJSON.parse('{"foo": "bar"}'). Defaults to 1024.
{
"jsc": {
"transform": {
"optimizer": {
"jsonify": {
"minCost": 0
}
}
}
}
}This will change all pure object literals to JSON.parse("").
jsc.keepClassNames
Requires
v1.2.50+and target to be es2016 or higher
Enabling this option will make swc preserve original class names.
jsc.paths
Requires
v1.2.62+
Syntax is identical as it of tsconfig.json: learn more.
Requires jsc.baseUrl. See below.
jsc.baseUrl
jsc.minify
Requires
v1.2.67+
See the documentation for minification for more details.
jsc.experimental
jsc.experimental.keepImportAssertions
Preserve import assertions.
This is experimental because import assertions are not covered by ecmascript specifications yet.
jsc.experimental.plugins
It follows resolving rule of node.js,.
Specify the plugin name like
{
"jsc": {
"experimental": {
"plugins": [
["@swc/plugin-styled-jsx", {}]
]
}
}
}styled-jsx works because it's published as @swc/plugin-styled-jsx.
jsc.preserveAllComments
Indicate that all comments should be preserved during compilation. Comments from source may be shifted in order to preserve thier relative location from source
to compiled output.
This feature is useful for transpilation that requires comments remain relatively close to the source: e.g. files under test with istanbul-ignore coverage
annotations.
Multiple Entries
Requires
v1.0.47+
[
{
"test": ".*.js$",
"module": {
"type": "commonjs"
}
},
{
"test": ".*.ts$",
"module": {
"type": "amd"
}
}
]This make SWC compile JavaScript files as CommonJS modules and compile TypeScript files as AMD modules.
Note that test option can be used to transcompile only typescript files, like
{
"test": ".*.ts$",
"jsc": {
"parser": {
"syntax": "typescript",
"tsx": false,
"decorators": true,
"dynamicImport": true
}
}
}test
Type: Regex / Regex[]
{
"test": ".*.ts$",
"jsc": {
"parser": {
"syntax": "typescript",
"tsx": false,
"decorators": true,
"dynamicImport": true
}
}
}exclude
Type: Regex / Regex[]
{
"exclude": [".*.js$", ".*.map$"],
"jsc": {
"parser": {
"syntax": "typescript",
"tsx": false,
"decorators": true,
"dynamicImport": true
}
}
}sourceMaps
Requires
v1.2.50+
Enable source map by adding sourceMaps: true or sourceMaps: 'inline' to the .swcrc.
{
"sourceMaps": true
}inlineSourcesContent
Requires
v1.2.101+
Defaults to true. If you want to make swc store contents of files into sourcemap, you can set inlineSourcesContent to true.
{
"sourceMaps": true,
"inlineSourcesContent": true
}isModule
Possible values: true, false, "unknown"
Used to treat input as a module or script.
If this is set to unknown, it will be Module if it's esm and Script otherwise.
File: pages/docs/configuration/minification.mdx
import Callout from "nextra-theme-docs/callout";
Minification
Starting with v1.2.67, you can configure SWC to minify your code by enabling minify in your .swcrc file:
{
// Enable minification
"minify": true,
// Optional, configure minifcation options
"jsc": {
"minify": {
"compress": {
"unused": true
},
"mangle": true
}
}
}Configuration
Note about comments
If you set jsc.minify.compress to true or {}, SWC will remove all comments.
If you don't want this, modify jsc.minify.format.
`jsc.minify.compress`
Type: boolean | object.
Similar to the compress option of terser.
{
"jsc": {
"minify": {
"compress": true // equivalent to {}
}
}
}arguments, defaults tofalse.arrows, defaults totrue.booleans, defaults totrue.booleans_as_integers, defaults tofalse.collapse_vars, defaults totrue.comparisons, defaults totrue.computed_props, defaults totrue.conditionals, defaults totrue.dead_code, defaults tofalse.defaults, defaults totrue.directives, defaults totrue.drop_console, defaults tofalse.drop_debugger, defaults totrue.ecma, defaults to5.evaluate, defaults totrue.global_defs, defaults to{}.hoist_funs, defaults tofalse.hoist_props, defaults totrue.hoist_vars, defaults tofalse.ie8, Ignored.if_return, defaults totrue.inline, defaults to ``.join_vars, defaults totrue.keep_classnames, defaults tofalse.keep_fargs, defaults tofalse.keep_infinity, defaults tofalse.loops, defaults totrue.negate_iife, defaults totrue.passes, defaults to0, which means no limit.properties, defaults totrue.pure_getters, defaults to ``.pure_funcs, defaults to[]. Type is an array of string.reduce_funcs, defaults tofalse.reduce_vars, defaults tofalse.sequences, defaults totrue.side_effects, defaults totrue.switches, defaults totrue.top_retain, defaults to ``.toplevel, defaults totrue.typeofs, defaults totrue.unsafe, defaults tofalse.unsafe_arrows, defaults tofalse.unsafe_comps, defaults tofalse.unsafe_Function, defaults tofalse.unsafe_math, defaults tofalse.unsafe_symbols, defaults tofalse.unsafe_methods, defaults tofalse.unsafe_proto, defaults tofalse.unsafe_regexp, defaults tofalse.unsafe_undefined, defaults tofalse.unused, defaults totrue.module, Ignored. Currently, all files are treated as module.
`jsc.minify.mangle`
Type: boolean | object.
Similar to the mangle option of terser.
{
"jsc": {
"minify": {
"mangle": true // equivalent to {}
}
}
}properties, Defaults tofalse, andtrueis identical to{}.topLevel, Defaults totrue. Aliased astoplevelfor compatibility withterser.keepClassnames, Defaults tofalse. Aliased askeep_classnamesfor compatibility withterser.keepFnames, Defaults tofalse.keepPrivateProps, Defaults tofalse. Aliased askeep_private_propsfor compatibility withterser.reserved, Defaults to[]ie8, Ignored.safari10, Not implemented yet.
`jsc.minify.mangle.properties`
Type: object.
Similar to the mangle properties option of terser.
{
"jsc": {
"minify": {
"mangle":{
"properties":{
"reserved": ["foo", "bar"],
"undeclared":false,
"regex":"rust regex"
}
}
}
}
}reserved: Don't use these names as properties.undeclared: Mangle properties even if it's not delcared.regex: Mangle properties only if it matches this regex
`jsc.minify.format`
These properties are mostly not implemented yet, but it exists to support passing terser config to swc minify without modification.
asciiOnly, Defaults tofalse. Implemented asv1.2.184and aliased asascii_onlyfor compatibility withterser.beautify, Defaults tofalse. Currently noop.braces, Defaults tofalse. Currently noop.comments, Defaults tofalse.falseremoves all comments'some'preserves some comments'all'preserves all comments
ecma, Defaults to 5. Currently noop.indentLevel, Currently noop and aliases asindent_levelfor compatibility withterser.indentStart, Currently noop and aliases asindent_startfor compatibility withterser.inlineScript, Currently noop and aliases asinline_scriptfor compatibility withterser.keepNumbers, Currently noop and aliases askeep_numbersfor compatibility withterser.keepQuotedProps, Currently noop and aliases askeep_quoted_propsfor compatibility withterser.maxLineLen, Currently noop, and aliases asmax_line_lenfor compatibility withterser.preamble, Currently noopquoteKeys, Currently noop and aliases asquote_keysfor compatibility withterser.quoteStyle, Currently noop and aliases asquote_stylefor compatibility withterser.preserveAnnotations, Currently noop and aliases aspreserve_annotationsfor compatibility withterser.safari10, Currently noop.semicolons, Currently noop.shebang, Currently noop.webkit, Currently noop.wrapIife, Currently noop and aliases aswrap_iifefor compatibility withterser.wrapFuncArgs, Currently noop and aliases aswrap_func_argsfor compatibility withterser.
@swc/core Usage
swc.minify(code, options)
This API is asynchronous and all of parsing, minification, and code generation will be done in background thread. The options argument is same as jsc.minify object. For example:
import swc from "@swc/core";
const { code, map } = await swc.minify(
"import foo from '@src/app'; console.log(foo)",
{
compress: false,
mangle: true,
}
);
expect(code).toMatchInlineSnapshot(`"import a from'@src/app';console.log(a);"`);Returns Promise<{ code: string, map: string }>.
swc.minifySync(code, options)
This API exists on @swc/core, @swc/wasm, @swc/wasm-web.
import swc from "@swc/core";
const { code, map } = swc.minifySync(
"import foo from '@src/app'; console.log(foo)",
{
compress: false,
mangle: true,
}
);
expect(code).toMatchInlineSnapshot(`"import a from'@src/app';console.log(a);"`);Returns { code: string, map: string }.
APIs for WebAssembly
Replacing Terser
You can reduce build time and override Terser without needing a library to update their dependencies through yarn resolutions. Example package.json would include:
{
"resolutions": { "terser": "npm:@swc/core" }
}This will use the SWC minifier instead of Terser for all nested dependencies. Ensure you remove your lockfile and re-install your dependencies.
$ rm -rf node_modules yarn.lock
$ yarnFile: pages/docs/configuration/modules.mdx
import Callout from 'nextra-theme-docs/callout'
Modules
SWC can transpile your code using ES Modules to CommonJS or UMD/AMD. By default, module statements will remain untouched.
CommonJS
To emit a CommonJS module, change the type in .swcrc:
{
"$schema": "http://json.schemastore.org/swcrc",
"module": {
"type": "commonjs",
// These are defaults.
"strict": false,
"strictMode": true,
"lazy": false,
"noInterop": false
}
}ES6
To emit a ES6 module, change the type in .swcrc:
{
"module": {
"type": "es6",
// These are defaults.
"strict": false,
"strictMode": true,
"lazy": false,
"noInterop": false
}
}AMD
To emit an AMD module, change the type in .swcrc:
{
"module": {
"type": "amd",
// Optional. If specified, swc emits named AMD module.
"moduleId": "foo",
// These are defaults.
"strict": false,
"strictMode": true,
"lazy": false,
"noInterop": false
}
}UMD
To emit an UMD module, change the type in .swcrc:
{
"module": {
"type": "umd",
"globals": {},
// These are defaults.
"strict": false,
"strictMode": true,
"lazy": false,
"noInterop": false
}
}Shared Options
These options are shared by commonjs / es6 / umd / amd inside .swcrc:
{
"module": {
// You can specify "commonjs", "es6", "amd", "umd"
"type": "commonjs",
"strict": false,
"strictMode": true,
"lazy": false,
"noInterop": false,
"ignoreDynamic": false
}
}strict
Defaults to false. By default, when using exports with SWC, a non-enumerable __esModule property is exported. In some cases, this property is used to determine if the import is the default export or if it contains the default export.
To prevent the __esModule property from being exported, you can set the strict option to true.
strictMode
Defaults to true. If true, swc emits 'use strict' directive.
lazy
Defaults to false. This option changes Babel's compiled import statements to be lazily evaluated when their imported bindings are used for the first time. This can improve the initial load time of your module because evaluating dependencies upfront is sometimes entirely unnecessary. This is especially the case when implementing a library module.
The value of lazy has a few possible effects:
false- No lazy initialization of any imported module.true- Do not lazy-initialize local./fooimports, but lazy-initfoodependencies. Local paths are much more likely to have circular dependencies, which may break if loaded lazily, so they are not lazy by default, whereas dependencies between independent modules are rarely cyclical.Array<string>- Lazy-initialize all imports with source matching one of the given strings.
The two cases where imports can never be lazy are:
import "foo";
Side-effect imports are automatically non-lazy since their very existence means that there is no binding to later kick-off initialization.export from "foo"
Re-exporting all names requires up-front execution because otherwise there is no
way to know what names need to be exported.
noInterop
Defaults to false. By default, when using exports with swc a non-enumerable __esModule property is exported.
This property is then used to determine if the import is the default export or if it contains the default export.
In cases where the auto-unwrapping of default is not needed, you can set the noInterop option to true to avoid the usage of the interopRequireDefault helper (shown in inline form above).
ignoreDynamic
If set to true, dynamic imports will be preserved.
File: pages/docs/configuration/supported-browsers.mdx
import Callout from 'nextra-theme-docs/callout'
Supported Browsers
Starting with v1.1.10, you can now use browserslist to automatically configure supported browsers.
Usage
First, install browserslist. Then, update your .swcrc:
{
"env": {
"targets": {
"chrome": "79"
},
"mode": "entry",
"coreJs": "3.22"
}
}Options
browserslist
If you want to use browserlists with SWC, omit targets in your .swcrc:
{
"env": {
"coreJs": "3.22"
}
}browserlists can be configured in multiple ways:
.browserslistrcbrowserslistfield in package.json
You can use path to specify a custom path to load these configuration files.
targets
string | Array<string> | { [string]: string }, defaults to {}.
Describes the environments you support/target for your project. This can either be a browserslist-compatible query:
{
"env": {
"targets": "> 0.25%, not dead"
}
}Or an object of minimum environment versions to support:
{
"env": {
"targets": {
"chrome": "58",
"ie": "11"
}
}
}Example environments:
chromeoperaedgefirefoxsafariieiosandroidnodeelectron
If targets is not specified, SWC uses browserslist to get target information.
path
string, defaults to current directory.pathspecifies the directory to load thebrowserslistmodule and any browserslist configuration files. For example,.browserslistrcorbrowserslistfield in package.json. This can be useful if your build system isn't in the root of your project.
mode
string, defaults toundefined.- Possible values:
usage,entry,undefined(this matchesuseBuiltInsfrom Babel)
skip
Define ES features to skip to reduce bundle size. For example, your .swcrc could be:
{
"env": {
"skip": ["core-js/modules/foo"]
}
}coreJs
string, defaults toundefined.coreJsspecifies the version ofcore-jsto use, can be any core-js versions supported by swc. E.g.,"3.22".
The option has an effect when used alongside mode: "usage" or mode: "entry". It is recommended to specify the minor version (E.g. "3.22") otherwise "3" will be interpreted as "3.0" which may not include polyfills for the latest features.
Additional Options
debug: (boolean) defaults tofalse.dynamicImport: (boolean) defaults tofalse.loose: (boolean) defaults tofalse. Enable loose transformations for any plugins that allow them.include: (string[]) can be acore-jsmodule (es.math.sign) or an SWC pass (transform-spread).exclude: (string[]) can be acore-jsmodule (es.math.sign) or an SWC pass (transform-spread).shippedProposals: (boolean) defaults tofalse.forceAllTransforms: (boolean) defaults tofalse. Enable all possible transforms.
File: pages/docs/configuration/swcrc.mdx
import Callout from 'nextra-theme-docs/callout'
Configuring SWC
SWC can be configured with an .swcrc file.
Compilation
Compilation works out of the box with SWC and does not require customization. Optionally, you can override the configuration. Here are the defaults:
{
"$schema": "https://json.schemastore.org/swcrc",
"jsc": {
"parser": {
"syntax": "ecmascript",
"jsx": false,
"dynamicImport": false,
"privateMethod": false,
"functionBind": false,
"exportDefaultFrom": false,
"exportNamespaceFrom": false,
"decorators": false,
"decoratorsBeforeExport": false,
"topLevelAwait": false,
"importMeta": false
},
"transform": null,
"target": "es5",
"loose": false,
"externalHelpers": false,
// Requires v1.2.50 or upper and requires target to be es2016 or upper.
"keepClassNames": false
},
"minify": false
}Read more about configuring compilation.
Supported Browsers
Starting with v1.1.10, you can now use browserslist to automaticallly configure supported browsers.
Usage
First, install browserslist. Then, update your .swcrc:
{
"env": {
"targets": {
"chrome": "79"
},
"mode": "entry",
"coreJs": "3.22"
}
}Read more about configuring supported browsers.
Modules
Read more about configuring modules.
Minification
Starting with v1.2.67, you can configure SWC to minify your code by enabling minify in your .swcrc file:
{
"minify": true
}Read more about configuring the JavaScript minifier.
File: pages/docs/plugin/ecmascript/cheatsheet.mdx
import Callout from "nextra-theme-docs/callout";
Plugin cheatsheet
Understanding types
`JsWord`
String allocates, and 'text'-s of source code has a special trait.
Those are lots of duplicates. Obviously, if your variable is named foo, you need to use foo multiple times.
So SWC interns the string to reduce the number of allocations.
JsWord is a string type that is interned.
You can create a JsWord from &str, or from a String.
Use .into() to convert to JsWord.
`Ident`, `Id`, `Mark`, `SyntaxContext`
SWC uses a special system for managing variables.
See the rustdoc for Ident for details.
Common issues
Getting AST representation of input
SWC Playground supports getting AST from the input code.
Variable management of SWC
Error reporting
See rustdoc for swc_common::errors::Handler.
Comparing `JsWord` with `&str`
If you don't know what JsWord is, see the rustdoc for swc_atoms.
You can create &str by doing &val where val is a variable of type JsWord.
Matching `Box`
You will need to use match to match on various nodes, including Box<T>.
For performance reason, all expressions are stored in a boxed form. (Box<Expr>)
SWC stores callee of call expressions as a Callee enum, and it has Box<Expr>.
use swc_core::ast::*;
use swc_core::visit::{VisitMut, VisitMutWith};
struct MatchExample;
impl VisitMut for MatchExample {
fn visit_mut_callee(&mut self, callee: &mut Callee) {
callee.visit_mut_children_with(self);
if let Callee::Expr(expr) = callee {
// expr is `Box<Expr>`
if let Expr::Ident(i) = &mut **expr {
i.sym = "foo".into();
}
}
}
}Changing AST type
If you want to change ExportDefaultDecl to ExportDefaultExpr, you should do it from visit_mut_module_decl.
Inserting new nodes
If you want to inject a new Stmt, you need to store the value in the sturct, and inject it from visit_mut_stmts or visit_mut_module_items.
See a destructuring core transform.
struct MyPlugin {
stmts: Vec<Stmt>,
}Tips
Apply `resolver` while testing
SWC applies plugin after applying resolver, so it's better to test your transform with it.
As written in the rustdoc for the resolver, you have to use correct SyntaxContext if you need to reference global variable (e.g. __dirname, require) or top-level bindings written by the user.
fn tr() -> impl Fold {
chain!(
resolver(Mark::new(), Mark::new(), false),
// Most of transform does not care about globals so it does not need `SyntaxContext`
your_transform()
)
}
test!(
Syntax::default(),
|_| tr(),
basic,
// input
"(function a ([a]) { a });",
// output
"(function a([_a]) { _a; });"
);Make your handlers stateless
Let's say we are going to handle all array expressions in a function expression.
You can add a flag to the visitor to check if we are in a function expression.
You will be tempted to do
struct Transform {
in_fn_expr: bool
}
impl VisitMut for Transform {
noop_visit_mut_type!();
fn visit_mut_fn_expr(&mut self, n: &mut FnExpr) {
self.in_fn_expr = true;
n.visit_mut_children_with(self);
self.in_fn_expr = false;
}
fn visit_mut_array_lit(&mut self, n: &mut ArrayLit) {
if self.in_fn_expr {
// Do something
}
}
}but this cannot handle
const foo = function () {
const arr = [1, 2, 3];
const bar = function () {};
const arr2 = [2, 4, 6];
}After visiting bar, in_fn_expr is false.
You have to do
struct Transform {
in_fn_expr: bool
}
impl VisitMut for Transform {
noop_visit_mut_type!();
fn visit_mut_fn_expr(&mut self, n: &mut FnExpr) {
let old_in_fn_expr = self.in_fn_expr;
self.in_fn_expr = true;
n.visit_mut_children_with(self);
self.in_fn_expr = old_in_fn_expr;
}
fn visit_mut_array_lit(&mut self, n: &mut ArrayLit) {
if self.in_fn_expr {
// Do something
}
}
}instead.
Test with `@swc/jest`
You can test your transform with @swc/jest by adding your plugin to your jest.config.js.
module.exports = {
rootDir: __dirname,
moduleNameMapper: {
"css-variable$": "../../dist",
},
transform: {
"^.+\\.(t|j)sx?$": [
"@swc/jest",
{
jsc: {
experimental: {
plugins: [
[
require.resolve(
"../../swc/target/wasm32-wasi/release/swc_plugin_css_variable.wasm"
),
{
basePath: __dirname,
displayName: true,
},
],
],
},
},
},
],
},
};See https://github.com/jantimon/css-variable/blob/main/test/swc/jest.config.js
`Path` is one of unix, while FileName can be one of host OS
This is because linux version of Path code is used while compiling to wasm.
So you may need to replace \\ with / in your plugin.
As / is a valid path separator in windows, it's valid thing to do.
Ownership model (of rust)
This section is not about
swcitself. But this is described at here because it's the cause of almost all trickyness of APIs.
In rust, only one variable can own a data, and there's at most one mutable reference to it.
Also, you need to own the value or have a mutable reference to it if you want to modify the data.
But there's at most one owner/mutable reference, so it means if you have a mutable reference to a value, other code cannot modify the value.
Every update operation should performed by the code which owns the value or has a mutable reference to it.
So, some of babel APIs like node.delete is super tricky to implement.
As your code has ownership or mutable refernce to some part of AST, SWC cannot modify the AST.
Tricky operations
Deleting node
You can delete a node in two step.
Let's say, we want to drop the variable named bar in the code below.
var foo = 1;
var bar = 1;There are two ways to do this.
Mark & Delete
The first way is to mark it as invalid and delete it later.
This is typically more convinient.
/* Detailed source-code truncated for AI context efficiency. */Delete from the parent handler
Another way to delete the node is deleting it from the parent handler.
This can be useful if you want to delete the node only if the parent node is specific type.
e.g. You don't want to touch the variables in for loops while deleting free variable statements.
use swc_core::ast::*;
use swc_core::visit::{VisitMut,VsiitMutWith};
struct Remover;
impl VisitMut for Remover {
fn visit_mut_stmt(&mut self, s: &mut Stmt) {
// This is not required in this example, but just to show that you typically need this.
s.visit_mut_children_with(self);
match s {
Stmt::Decl(Decl::Var(var)) => {
if var.decls.len() == 1 {
match var.decls[0].name {
Pat::Ident(i) => {
if &*i.sym == "bar" {
s.take();
}
}
}
}
}
_ => {}
}
}
fn visit_mut_stmts(&mut self, stmts: &mut Vec<Stmt>) {
stmts.visit_mut_children_with(self);
// We do same thing here.
stmts.retain(|s| {
!matches!(s, Stmt::Empty(..))
});
}
fn visit_mut_module_items(&mut self, stmts: &mut Vec<ModuleItem>) {
stmts.visit_mut_children_with(self);
// We do same thing here.
stmts.retain(|s| {
!matches!(s, ModuleItem::Stmt(Stmt::Empty(..)))
});
}
}Referencing parent node from handler of child node
This includes usage of paths and scope.
Caching some information about an AST node
You have two way to use informantion from a parent node.
For first, you can precompute information from the parent node handler.
Alternatively, you can clone the parent node and use it in the child node handler.
Alternatives for babel APIs
`generateUidIdentifier`
This returns a unique identifier with a monotonically increasing integer suffix.swc does not provide API to do this, because there's a very easy way to do this.
You can store an integer field in transformer type and use it while calling quote_ident! or private_ident!.
struct Example {
// You don't need to share counter.
cnt: usize
}
impl Example {
/// For properties, it's okay to use `quote_ident`.
pub fn next_property_id(&mut self) -> Ident {
self.cnt += 1;
quote_ident!(format!("$_css_{}", self.cnt))
}
/// If you want to create a safe variable, you should use `private_ident`
pub fn next_variable_id(&mut self) -> Ident {
self.cnt += 1;
private_ident!(format!("$_css_{}", self.cnt))
}
}`path.find`
Upward traversal is not supported by swc.
It's because upward traversal requires storing information about parent at children nodes, which requires using types like Arc or Mutex in rust.
Instead of traversing upward, you should make it top-down.
For example, if you want to infer name of a jsx component from variable assignments or assignments, you can store name of component while visiting VarDecl and/or AssignExpr and use it from the component handler.
`state.file.get`/`state.file.set`
You can simply store the value in the transform struct as an instance of transform struct only process one file.
File: pages/docs/plugin/ecmascript/getting-started.mdx
import Callout from 'nextra-theme-docs/callout'
Implementing a plugin
Setup environment
Install required toolchain
As plugin is written in the rust programming language and built as a .wasm file, you need to install rust toolchain and wasm target.
Install rust
You can follow instructions at 'Install Rust' page from the official rust website
Add wasm target to rust
SWC supports two kinds of .wasm files.
Those are
- wasm32-wasi
- wasm32-unknown-unknown
In this guide, we will use wasm-wasi as a target.
Install `swc_cli`
You can install a rust-based CLI for SWC by doing
cargo install swc_cliConfiguring IDE
If you are going to use vscode, it's recommended to install rust-analyzer extension.rust-analyzer is a language server for the rust programming language, which provides good features for code completion, code navigation, and code analysis.
Implementing simple plugin
Create a project
SWC CLI supports creating a new plugin project.
Run
swc plugin new --target-type wasm32-wasi my-first-plugin
# You should to run this
rustup target add wasm32-wasito create a new plugin, and open my-first-plugin with your preferred rust IDE.
Implementing a visitor
The generated code has
impl VisitMut for TransformVisitor {
// Implement necessary visit_mut_* methods for actual custom transform.
// A comprehensive list of possible visitor methods can be found here:
// https://rustdoc.swc.rs/swc_ecma_visit/trait.VisitMut.html
}which is used to transform code.
The trait VisitMut supports mutating AST nodes, and as it supports all AST types, it has lots of methods.
We will use
foo === bar;as the input. From the SWC Playground, you can get actual representation of this code.
{
"type": "Module",
"span": {
"start": 0,
"end": 12,
"ctxt": 0
},
"body": [
{
"type": "ExpressionStatement",
"span": {
"start": 0,
"end": 12,
"ctxt": 0
},
"expression": {
"type": "BinaryExpression",
"span": {
"start": 0,
"end": 11,
"ctxt": 0
},
"operator": "===",
"left": {
"type": "Identifier",
"span": {
"start": 0,
"end": 3,
"ctxt": 0
},
"value": "foo",
"optional": false
},
"right": {
"type": "Identifier",
"span": {
"start": 8,
"end": 11,
"ctxt": 0
},
"value": "bar",
"optional": false
}
}
}
],
"interpreter": null
}Let's implement a method for BinExpr.
You can do it like
use swc_core::{
ast::*,
visit::VisitMut,
};
impl VisitMut for TransformVisitor {
fn visit_mut_bin_expr(&mut self, e: &mut BinExpr) {
e.visit_mut_children_with(self);
}
}Note that visit_mut_children_with is required if you want to call the method handler for children.
e.g. visit_mut_ident for foo and bar will be called by e.visit_mut_children_with(self); above.
Let's narrow down it using the binary operator.
use swc_core::ast::*;
use swc_core::common::Spanned;
impl VisitMut for TransformVisitor {
fn visit_mut_bin_expr(&mut self, e: &mut BinExpr) {
e.visit_mut_children_with(self);
if e.op == op!("===") {
e.left = Ident::new("kdy1".into(), e.left.span()).into();
}
}
}op!("===") is a macro call, and it returns various types of operators.
It returns BinaryOp in this case, because we provided "===", which is a binary operator.
See the rustdoc for op! macro for more details.
If we run this plugin, we will get
kdy1 === bar;Testing your transform
You can simply run cargo test to test your plugins.
SWC also provides a utility to ease fixture testing.
You can take a look at the real fixture test for typescript type stripper.
#[testing::fixture("tests/fixture/**/input.ts")]
#[testing::fixture("tests/fixture/**/input.tsx")]
fn fixture(input: PathBuf) {
let output = input.with_file_name("output.js");
test_fixture(
Syntax::Typescript(TsConfig {
tsx: input.to_string_lossy().ends_with(".tsx"),
..Default::default()
}),
&|t| chain!(tr(), properties(t, true)),
&input,
&output,
);
}Things to note:
- The glob provided to
testing::fixtureis relative to the cargo project directory. - The output file is
output.js, and it's stored in a same directory as the input file. test_fixturedrives the test.- You can determine the syntax of the input file by passing the syntax to
test_fixture. - You then provide your visitor implementation as the second argument to
test_fixture. - Then you provide the input file path and the output file path.
Logging
SWC uses tracing for logging.
By default, SWC testing library configures the log level to debug by default, and this can be controlled by using an environment variable named RUST_LOG.
e.g. RUST_LOG=trace cargo test will print all logs, including trace logs.
If you want, you can remove logging for your plugin by using cargo features of tracing.
See the documentation for it.
Publishing your plugin
Please see plugin publishing guide
File: pages/docs/plugin/publishing.mdx
Publishing plugins
If you prefer reading codes, you can refer to the repository for official plugins.
Creating a npm package
Building a plugin as a wasm
You can run your plugin as a wasm file by running
cargo prepublish --releaseIt will create target/wasm32-wasi/release/your_plugin_name.wasm or target/wasm32-unknown-unknown/release/your_plugin_name.wasm, depending on your config.
Creating a npm package for plugin
Add the following to your package.json:
{
"main": "your_plugin_name.wasm",
"scripts": {
"prepack": "cargo prepublish --release && cp target/wasm32-wasi/release/your_plugin_name.wasm ."
},
}Advanced: Improving your plugin
Adjusting configuration for smaller binary
You can reduce the size of the plugin by configuring cargo.
In your Cargo.toml file, you can add the following lines.
[profile.release]
# This removes more dead code
codegen-units = 1
lto = true
# Optimize for size
opt-level = "s"
# Optimize for performance, this is default so you don't need to specify it
# opt-level = "z"Removing log for release mode
If logging of your crate is too much, you can remove it by enabling release_max_level_* of tracing, like
tracing = { version="0.1", features = ["release_max_level_info"] }