skills Documentation

Anthony Fu's curated collection of agent skills.

5,370 stars TypeScript TypeScript 6 files ยท ~62,401 tokens #agent-skills#skills
RAW Doc

1. Project Overview & Quickstart (antfu/skills)

File: README.md

Anthony Fu's Skills

A curated collection of Agent Skills reflecting Anthony Fu's preferences, experience, and best practices, along with usage documentation for the tools.

[!IMPORTANT]
This is a proof-of-concept project for generating agent skills from source documentation and keeping them in sync.
I haven't fully tested how well the skills perform in practice, so feedback and contributions are greatly welcome.

Installation

bash
pnpx skills add antfu/skills --skill='*'

or to install all of them globally:

bash
pnpx skills add antfu/skills --skill='*' -g

Learn more about the CLI usage at skills.

Skills

This collection is aim to be a one-stop collection of you are mainly working on Vite/Nuxt. It includes skills from different sources with different scopes.

Hand-maintained Skills

Opinionated

Manually maintained by Anthony Fu with his preferred tools, setup conventions, and best practices.

Skill Description
antfu Anthony Fu's preferences and best practices for app/library projects (eslint, pnpm, vitest, vue, etc.)
antfu-design UnoCSS-centered design principles, semantic tokens, and UI presentation patterns from Anthony Fu's tooling UIs

Skills Generated from Official Documentation

Unopinionated but with tilted focus (e.g. TypeScript, ESM, Composition API, and other modern stacks)

Generated from official documentation and fine-tuned by Anthony.

Skill Description Source
vue Vue.js core - reactivity, components, composition API vuejs/docs
nuxt Nuxt framework - file-based routing, server routes, modules nuxt/nuxt
pinia Pinia - intuitive, type-safe state management for Vue vuejs/pinia
vite Vite build tool - config, plugins, SSR, library mode vitejs/vite
vitepress VitePress - static site generator powered by Vite vuejs/vitepress
vitest Vitest - unit testing framework powered by Vite vitest-dev/vitest
unocss UnoCSS - atomic CSS engine, presets, transformers unocss/unocss
pnpm pnpm - fast, disk space efficient package manager pnpm/pnpm.io

Vendored Skills

Synced from external repositories that maintain their own skills.

Skill Description Source
slidev (Official) Slidev - presentation slides for developers slidevjs/slidev
tsdown (Official) tsdown - TypeScript library bundler powered by Rolldown rolldown/tsdown
turborepo (Official) Turborepo - high-performance build system for monorepos vercel/turborepo
vueuse-functions (Official) VueUse - 200+ Vue composition utilities vueuse/skills
vue-best-practices Vue 3 + TypeScript best practices vuejs-ai/skills
vue-router-best-practices Vue Router best practices vuejs-ai/skills
vue-testing-best-practices Vue testing best practices vuejs-ai/skills
web-design-guidelines Web design guidelines for building beautiful interfaces vercel-labs/agent-skills

FAQ

What Makes This Collection Different?

This collection is opinionated, but the key difference is that it uses git submodules to directly reference source documentation. This provides more reliable context and allows the skills to stay up-to-date with upstream changes over time. If you primarily work with Vue/Vite/Nuxt, this aims to be a comprehensive one-stop collection.

The project is also designed to be flexible - you can use it as a template to generate your own skills collection.

Skills vs llms.txt vs AGENTS.md

To me, the value of skills lies in being shareable and on-demand.

Being shareable makes prompts easier to manage and reuse across projects. Being on-demand means skills can be pulled in as needed, scaling far beyond what any agent's context window could fit at once.

You might hear people say "AGENTS.md outperforms skills". I think that's true โ€” AGENTS.md loads everything upfront, so agents always respect it, whereas skills can have false negatives where agents don't pull them in when you'd expect. That said, I see this more as a gap in tooling and integration that will improve over time. Skills are really just a standardized format for agents to consumeโ€”plain markdown files at the end of the day. Think of them as a knowledge base for agents. If you want certain skills to always apply, you can reference them directly in your AGENTS.md.

Generate Your Own Skills

Fork this project to create your own customized skill collection.

  1. Fork or clone this repository
  2. Install dependencies: pnpm install
  3. Update meta.ts with your own projects and skill sources
  4. Run pnpm start cleanup to remove existing submodules and skills
  5. Run pnpm start init to clone the submodules
  6. Run pnpm start sync to sync vendored skills
  7. Ask your agent to Generate skills for \ (recommended one at a time to manage token usage)

See AGENTS.md for detailed generation guidelines.

Sponsors

License

Skills and the scripts in this repository are MIT licensed.

Vendored skills from external repositories retain their original licenses - see each skill directory for details.


File: skills/tsdown/references/README.md

tsdown Skill References

This directory contains detailed reference documentation for the tsdown skill.

Created Files (35 total)

Core Guides (3)

  • โœ… guide-getting-started.md - Installation, first bundle, CLI basics
  • โœ… guide-migrate-from-tsup.md - Migration guide from tsup
  • โœ… guide-introduction.md - Introduction and key features

Configuration Options (20)

  • โœ… option-config-file.md - Config file formats, loaders, workspace
  • โœ… option-entry.md - Entry point configuration with globs
  • โœ… option-output-format.md - Output formats (ESM, CJS, IIFE, UMD)
  • โœ… option-output-directory.md - Output directory and extensions
  • โœ… option-dts.md - TypeScript declaration generation
  • โœ… option-target.md - Target environment (ES2020, ESNext, etc.)
  • โœ… option-platform.md - Platform (node, browser, neutral)
  • โœ… option-dependencies.md - External and inline dependencies
  • โœ… option-sourcemap.md - Source map generation
  • โœ… option-minification.md - Minification (boolean | 'dce-only' | MinifyOptions)
  • โœ… option-tree-shaking.md - Tree shaking configuration
  • โœ… option-cleaning.md - Output directory cleaning
  • โœ… option-watch-mode.md - Watch mode configuration
  • โœ… option-shims.md - ESM/CJS compatibility shims
  • โœ… option-package-exports.md - Auto-generate package.json exports
  • โœ… option-css.md - CSS handling (experimental, full pipeline: preprocessors, Lightning CSS, PostCSS, code splitting)
  • โœ… option-unbundle.md - Preserve directory structure
  • โœ… option-cjs-default.md - CommonJS default export handling
  • โœ… option-log-level.md - Logging configuration
  • โœ… option-lint.md - Package validation (publint & attw)

Executable (1)

  • โœ… option-exe.md - Standalone executable bundling (Node.js SEA)

Advanced Topics (6)

  • โœ… advanced-plugins.md - Rolldown, Rollup, Unplugin support
  • โœ… advanced-hooks.md - Lifecycle hooks system
  • โœ… advanced-programmatic.md - Node.js API usage
  • โœ… advanced-rolldown-options.md - Pass options to Rolldown
  • โœ… advanced-ci.md - CI environment detection and CI-aware options

Advanced (continued)

  • โœ… advanced-benchmark.md - Performance benchmarks

Framework Recipes (5)

  • โœ… recipe-react.md - React library setup with JSX
  • โœ… recipe-vue.md - Vue library setup with SFC
  • โœ… recipe-solid.md - Solid.js library setup
  • โœ… recipe-svelte.md - Svelte component libraries
  • โœ… recipe-wasm.md - WASM module support

Reference (1)

  • โœ… reference-cli.md - Complete CLI command reference

Coverage Status

Created: 35 files (100% complete)

Current Skill Features

The tsdown skill now includes comprehensive coverage of:

โœ… Core Functionality

  • Getting started and installation
  • Entry points and glob patterns
  • Output formats (ESM, CJS, IIFE, UMD)
  • TypeScript declarations
  • Configuration file setup
  • CLI reference

โœ… Build Options

  • Target environment configuration
  • Platform selection
  • Dependency management
  • Source maps
  • Minification
  • Tree shaking
  • Output cleaning
  • Watch mode

โœ… Advanced Features

  • Plugins (Rolldown, Rollup, Unplugin)
  • Lifecycle hooks
  • ESM/CJS shims
  • Package exports generation
  • Package validation (publint, attw)
  • Programmatic API (Node.js)
  • Output directory customization
  • CSS handling and modules
  • Unbundle mode
  • CI environment detection and CI-aware options

โœ… Framework & Runtime Support

  • React with JSX/TSX
  • React Compiler integration
  • Vue with SFC support
  • Vue type generation (vue-tsc)
  • WASM module bundling (rolldown-plugin-wasm)

โœ… Migration

  • Complete migration guide from tsup
  • Compatibility notes

Usage

The skill is now ready for use with comprehensive coverage of core features. Additional files can be added incrementally as needed.

File Naming Convention

Files are prefixed by category:

  • guide-* - Getting started guides and tutorials
  • option-* - Configuration options
  • advanced-* - Advanced topics (plugins, hooks, programmatic API)
  • recipe-* - Framework-specific recipes
  • reference-* - CLI and API reference

Creating New Reference Files

When creating new reference files:

  1. Read source documentation from /docs directory
  2. Simplify for AI consumption - concise, actionable content
  3. Include code examples - practical, copy-paste ready
  4. Add cross-references - link to related options
  5. Follow naming convention - use appropriate prefix
  6. Keep it focused - one topic per file

Updating Existing Files

When documentation changes:

  1. Check git diff: git diff <sha>..HEAD -- docs/
  2. Update affected reference files
  3. Update SKILL.md if needed
  4. Update GENERATION.md with new SHA

See skills/GENERATION.md for detailed update instructions.


File: skills/tsdown/README.md

tsdown Skills

Agent skills that help AI coding agents understand and work with tsdown, the elegant library bundler.

Installation

bash
npx skills add rolldown/tsdown

This will install all tsdown skills (including the migration skill). To install only the tsdown skill:

bash
npx skills add rolldown/tsdown --skill tsdown

What's Included

The tsdown skill provides Claude Code with knowledge about:

  • Core Concepts - What tsdown is, why use it, key features
  • Configuration - Config file formats, options, multiple configs, workspace support
  • Build Options - Entry points, output formats, type declarations, targets
  • Dependency Handling - External/inline dependencies, auto-externalization
  • Output Enhancement - Shims, CJS defaults, package exports
  • Framework Support - React, Vue, Solid, Svelte integration
  • Advanced Features - Plugins, hooks, programmatic API, Rolldown options
  • CLI Commands - All CLI options and usage patterns
  • Migration - Migrating from tsup to tsdown

Usage

Once installed, Claude Code will automatically use tsdown knowledge when:

  • Building TypeScript/JavaScript libraries
  • Configuring bundlers for library projects
  • Setting up type declaration generation
  • Working with multi-format builds (ESM, CJS, IIFE, UMD)
  • Migrating from tsup
  • Building framework component libraries

Example Prompts

text
Set up tsdown to build my TypeScript library with ESM and CJS formats
text
Configure tsdown to generate type declarations and bundle for browsers
text
Add React support to my tsdown config with Fast Refresh
text
Help me migrate from tsup to tsdown
text
Set up a monorepo build with tsdown workspace support
  • tsdown-migrate - Dedicated skill for migrating from tsup to tsdown, with complete option mappings, config transformations, and troubleshooting guidance.

Documentation

License

MIT


File: skills/slidev/README.md

Slidev Skills for Claude Code

Agent skills that help Claude Code understand and work with Slidev presentations.

Installation

bash
npx skills add slidevjs/slidev

This will add the Slidev skill to your Claude Code configuration.

What's Included

The Slidev skill provides Claude Code with knowledge about:

  • Core Syntax - Markdown syntax, slide separators, frontmatter
  • Animations - Click animations, transitions, motion effects
  • Code Features - Line highlighting, Monaco editor, code groups, magic-move
  • Diagrams - Mermaid, PlantUML, LaTeX math
  • Layouts - Built-in layouts, slots, global layers
  • Presenter Mode - Recording, timer, remote access
  • Exporting - PDF, PPTX, PNG, SPA hosting

Usage

Once installed, Claude Code will automatically use Slidev knowledge when:

  • Creating new presentations
  • Adding slides with code examples
  • Setting up animations and transitions
  • Configuring themes and layouts
  • Exporting presentations

Example Prompts

text
Create a Slidev presentation about TypeScript generics with code examples
text
Add a two-column slide with code on the left and explanation on the right
text
Set up click animations to reveal bullet points one by one
text
Configure the presentation for PDF export with speaker notes

Documentation

License

MIT

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

This feature is still under construction.

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.

js
// 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.

ts
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 __dirname to 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.

ts
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:

json
{
  "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

json
{
  "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

json
{
  "jsc": {
    "parser": {
      "syntax": "typescript",
      "tsx": false,
      "decorators": false,
      "dynamicImport": false
    }
  }
}

ecmascript

json
{
  "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.

json
{
  "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.

json
{
  "jsc": {
    "loose": true
  }
}

jsc.transform

json
{
  "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.

json
{
  "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:

json
{
  "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: automatic to use a JSX runtime module (e.g. react/jsx-runtime introduced in React 17).
  • Use runtime: classic to use React.createElement instead - with this option, you must ensure that React is in scope when using JSX.

Learn more here.

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:

ts
interface ReactRefreshConfig {
  refreshReg: String;
  refreshSig: String;
  emitFullSignatures: boolean;
}

jsc.transform.constModules

json
{
  "jsc": {
    "transform": {
      "constModules": {
        "globals": {
          "@ember/env-flags": {
            "DEBUG": "true"
          },
          "@ember/features": {
            "FEATURE_A": "false",
            "FEATURE_B": "true"
          }
        }
      }
    }
  }
}

Then, source code like:

js
import { DEBUG } from "@ember/env-flags";
import { FEATURE_A, FEATURE_B } from "@ember/features";

console.log(DEBUG, FEATURE_A, FEATURE_B);

is transformed to:

js
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.

json
{
  "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 window will be replaced with "object".
json
{
  "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 to JSON.parse('{"foo": "bar"}'). Defaults to 1024.
json
{
  "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

Learn more.

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

json
{
  "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+

json
[
  {
    "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

json
{
  "test": ".*.ts$",
  "jsc": {
    "parser": {
      "syntax": "typescript",
      "tsx": false,
      "decorators": true,
      "dynamicImport": true
    }
  }
}

test

Type: Regex / Regex[]

json
{
  "test": ".*.ts$",
  "jsc": {
    "parser": {
      "syntax": "typescript",
      "tsx": false,
      "decorators": true,
      "dynamicImport": true
    }
  }
}

exclude

Type: Regex / Regex[]

json
{
  "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.

json
{
  "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.

json
{
  "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:

json
{
  // 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.

json
{
  "jsc": {
    "minify": {
      "compress": true // equivalent to {}
    }
  }
}
  • arguments, defaults to false.
  • arrows, defaults to true.
  • booleans, defaults to true.
  • booleans_as_integers, defaults to false.
  • collapse_vars, defaults to true.
  • comparisons, defaults to true.
  • computed_props, defaults to true.
  • conditionals, defaults to true.
  • dead_code, defaults to false.
  • defaults, defaults to true.
  • directives, defaults to true.
  • drop_console, defaults to false.
  • drop_debugger, defaults to true.
  • ecma, defaults to 5.
  • evaluate, defaults to true.
  • global_defs, defaults to {}.
  • hoist_funs, defaults to false.
  • hoist_props, defaults to true.
  • hoist_vars, defaults to false.
  • ie8, Ignored.
  • if_return, defaults to true.
  • inline, defaults to ``.
  • join_vars, defaults to true.
  • keep_classnames, defaults to false.
  • keep_fargs, defaults to false.
  • keep_infinity, defaults to false.
  • loops, defaults to true.
  • negate_iife, defaults to true.
  • passes, defaults to 0, which means no limit.
  • properties, defaults to true.
  • pure_getters, defaults to ``.
  • pure_funcs, defaults to []. Type is an array of string.
  • reduce_funcs, defaults to false.
  • reduce_vars, defaults to false.
  • sequences, defaults to true.
  • side_effects, defaults to true.
  • switches, defaults to true.
  • top_retain, defaults to ``.
  • toplevel, defaults to true.
  • typeofs, defaults to true.
  • unsafe, defaults to false.
  • unsafe_arrows, defaults to false.
  • unsafe_comps, defaults to false.
  • unsafe_Function, defaults to false.
  • unsafe_math, defaults to false.
  • unsafe_symbols, defaults to false.
  • unsafe_methods, defaults to false.
  • unsafe_proto, defaults to false.
  • unsafe_regexp, defaults to false.
  • unsafe_undefined, defaults to false.
  • unused, defaults to true.
  • module, Ignored. Currently, all files are treated as module.

`jsc.minify.mangle`

Type: boolean | object.

Similar to the mangle option of terser.

json
{
  "jsc": {
    "minify": {
      "mangle": true // equivalent to {}
    }
  }
}
  • properties, Defaults to false, and true is identical to {}.
  • topLevel, Defaults to true. Aliased as toplevel for compatibility with terser.
  • keepClassnames, Defaults to false. Aliased as keep_classnames for compatibility with terser.
  • keepFnames, Defaults to false.
  • keepPrivateProps, Defaults to false. Aliased as keep_private_props for compatibility with terser.
  • reserved, Defaults to []
  • ie8, Ignored.
  • safari10, Not implemented yet.

`jsc.minify.mangle.properties`

Type: object.

Similar to the mangle properties option of terser.

json
{
  "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 to false. Implemented as v1.2.184 and aliased as ascii_only for compatibility with terser.
  • beautify, Defaults to false. Currently noop.
  • braces, Defaults to false. Currently noop.
  • comments, Defaults to false.
    • false removes all comments
    • 'some' preserves some comments
    • 'all' preserves all comments
  • ecma, Defaults to 5. Currently noop.
  • indentLevel, Currently noop and aliases as indent_level for compatibility with terser.
  • indentStart, Currently noop and aliases as indent_start for compatibility with terser.
  • inlineScript, Currently noop and aliases as inline_script for compatibility with terser.
  • keepNumbers, Currently noop and aliases as keep_numbers for compatibility with terser.
  • keepQuotedProps, Currently noop and aliases as keep_quoted_props for compatibility with terser.
  • maxLineLen, Currently noop, and aliases as max_line_len for compatibility with terser.
  • preamble, Currently noop
  • quoteKeys, Currently noop and aliases as quote_keys for compatibility with terser.
  • quoteStyle, Currently noop and aliases as quote_style for compatibility with terser.
  • preserveAnnotations, Currently noop and aliases as preserve_annotations for compatibility with terser.
  • safari10, Currently noop.
  • semicolons, Currently noop.
  • shebang, Currently noop.
  • webkit, Currently noop.
  • wrapIife, Currently noop and aliases as wrap_iife for compatibility with terser.
  • wrapFuncArgs, Currently noop and aliases as wrap_func_args for compatibility with terser.

@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:

js
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.

js
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:

json
{
  "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.

sh
$ rm -rf node_modules yarn.lock
$ yarn

File: 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:

json
{
  "$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:

json
{
  "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:

json
{
  "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:

json
{
  "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:

json
{
  "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 ./foo imports, but lazy-init foo dependencies. 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:

json
{
  "env": {
    "targets": {
      "chrome": "79"
    },
    "mode": "entry",
    "coreJs": "3.22"
  }
}

Options

browserslist

If you want to use browserlists with SWC, omit targets in your .swcrc:

json
{
  "env": {
    "coreJs": "3.22"
  }
}

browserlists can be configured in multiple ways:

  • .browserslistrc
  • browserslist field 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:

json
{
  "env": {
    "targets": "> 0.25%, not dead"
  }
}

Or an object of minimum environment versions to support:

json
{
  "env": {
    "targets": {
      "chrome": "58",
      "ie": "11"
    }
  }
}

Example environments:

  • chrome
  • opera
  • edge
  • firefox
  • safari
  • ie
  • ios
  • android
  • node
  • electron

If targets is not specified, SWC uses browserslist to get target information.

path

  • string, defaults to current directory.
  • path specifies the directory to load the browserslist module and any browserslist configuration files. For example, .browserslistrc or browserslist field in package.json. This can be useful if your build system isn't in the root of your project.

mode

  • string, defaults to undefined.
  • Possible values: usage, entry, undefined (this matches useBuiltIns from Babel)
The `usage` mode is currently not as efficient as Babel, yet.

skip

Define ES features to skip to reduce bundle size. For example, your .swcrc could be:

json
{
  "env": {
    "skip": ["core-js/modules/foo"]
  }
}

coreJs

  • string, defaults to undefined.
  • coreJs specifies the version of core-js to 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 to false.
  • dynamicImport: (boolean) defaults to false.
  • loose: (boolean) defaults to false. Enable loose transformations for any plugins that allow them.
  • include: (string[]) can be a core-js module (es.math.sign) or an SWC pass (transform-spread).
  • exclude: (string[]) can be a core-js module (es.math.sign) or an SWC pass (transform-spread).
  • shippedProposals: (boolean) defaults to false.
  • forceAllTransforms: (boolean) defaults to false. 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:

jsonc
{
  "$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:

json
{
  "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:

json
{
  "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

This page describes the known hard points for implementing plugins for ecmascript.

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>.

rust
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.

rust
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.

rust
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

rust
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

js
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

rust
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.

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 swc itself. 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.

js
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.

text
/* 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.

rust
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!.

rust
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

sh
cargo install swc_cli

Configuring 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

sh
swc plugin new --target-type wasm32-wasi my-first-plugin
# You should to run this
rustup target add wasm32-wasi

to create a new plugin, and open my-first-plugin with your preferred rust IDE.

Implementing a visitor

The generated code has

rust
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

js
foo === bar;

as the input. From the SWC Playground, you can get actual representation of this code.

json
{
  "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

rust
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.

rust
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

js
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.

rust
#[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::fixture is 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_fixture drives 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

sh
cargo prepublish --release

It 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:

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.

toml
[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

toml
tracing = { version="0.1", features = ["release_max_level_info"] }