swc-project/swc
github.com/swc-project/swc ↗ · 4000 files · 9.6 MB · scan capped
Make the web (development) faster. SWC (stands for Speedy Web Compiler) is a super-fast TypeScript / JavaScript compiler written in Rust.
Overview
SWC is a Rust compiler toolkit for JavaScript and TypeScript, exposed through Rust libraries, native Node packages, WebAssembly bindings, and a standalone CLI. Its central pipeline parses source into an AST, applies configurable transforms or plugins, and emits code with optional source maps; related packages handle HTML, CSS, and React Compiler functionality. At commit 3c46139b86b27a7b3fefcf7b19ca07a63de9db0a, native packages also contain compressed carriers that verify and materialize the original addon before loading it. This brief reflects bounded source inspection, including more than twelve implementation files and concrete loading, transformation, AST round-trip, minification, and CLI flows.
System map
SWC is organized as a compiler workspace with several embedding boundaries. Rust crates provide compilation and language infrastructure, while Node and WebAssembly bindings expose selected operations to JavaScript consumers. The standalone CLI dispatches through a separate implementation crate, and native package loading includes a verified filesystem cache. Build, test, benchmark, and publication workflows are separate operational surfaces.
- The Cargo workspace includes xtask, bindings, crates, code generation, release tooling, and the native addon packer; shared packages use Rust edition 2021.
- The compiler layers separate AST definitions, parsing, code generation, and base transforms; resolver distinguishes bindings, hygiene renames conflicting symbols, and fixer restores valid expression structure.
- @swc/core exposes a TypeScript Compiler facade backed by native bindings or @swc/wasm; SWC_BINARY_PATH overrides the native module location.
- binding_core_node is a cdylib targeting N-API 3; its default features enable swc_v1 bundling and native Wasmer plugins with filesystem caching.
- Compiler owns an Arc<SourceMap> and comments; the Node binding provides both a lazy shared compiler and get_fresh_compiler for isolated source maps.
- The standalone executable calls swc_cli_impl::run, which parses Clap options and dispatches plugin-new, compile, minify, bundle, or lint handlers.
- Development uses pnpm@10.33.3; core build scripts run TypeScript declaration generation and napi build, while package tests invoke Rstest.
- Distribution includes native npm addons and Rust crates: carrier documentation defines eight compressed targets, while automatic crate publication requires a main-branch commit beginning 'chore: Publish' and obtains crates.io authentication through OIDC.
Feature catalog
The principal public features are parsing, transformation, printing, and minification through synchronous and asynchronous APIs. Configuration supports language lowering, module formats, source maps, and custom transformations. Additional surfaces provide bundling, CLI compilation, HTML minification, CSS components, and React analysis. Availability depends on binding, target, and Cargo features; command names alone do not establish implementation.
- Compiler provides parse/parseSync and parseFile/parseFileSync with ECMAScript defaults and Script/Module overloads; filesystem parsing rejects the WASM fallback.
- transform and transformSync accept source text or Program ASTs, while transformFile variants accept paths; Config::adjust enables TSX for .tsx, disables it for .ts, and recognizes .d.ts.
- minify variants accept text or buffers and internally recognize object input; native object input must contain exactly one filename-to-source entry, and extras can share an experimental mangle-name cache.
- Compilation can emit source maps and inline source content, select CommonJS/UMD/AMD/SystemJS/ES6/NodeNext module configuration, and generate isolated declarations when the isolated-dts feature and TypeScript syntax are enabled.
- Node bundle loads configuration through compileBundleOptions, merges multiple bundle results, and delegates to native bindings; swc_v1 handles named/library bundles but rejects dynamic code splitting, while the alternate task implementation is unfinished.
- CLI compile supports stdin or files, config overrides, source maps, output files/directories, copying other files, and watching; plugin new scaffolds a Rust plugin, but CLI bundle, lint, and minify handlers call unimplemented!.
- The HTML native binding exposes document and fragment minification in async/sync forms; the Rust CSS facade exposes AST/parser/codegen plus feature-gated compatibility, minifier, modules, and prefixer components.
- React Compiler integration is exercised through jsc.transform.reactCompiler; a separate native binding exposes async/sync lint diagnostics and an isReactCompilerRequired check that returns true on parsing errors.
Data flow
JavaScript requests cross the native boundary as source strings, serialized Programs, and JSON option buffers. Compiler instances own source maps, while AST envelopes preserve enough original source information to restore positions across calls. Configuration builds the transformation pass and output metadata before code generation. Persistent state mainly appears in native image and plugin caches; CLI handlers separately own generated-file writes.
- toBuffer serializes options with JSON.stringify; transform classifies non-string input as a Program and uses stringifyProgram before calling the native binding.
- Native transform chooses Input::Program with the shared compiler or Input::Source with a fresh compiler; TransformTask::compute deserializes options, adjusts filename-dependent config, and reads files or creates SourceFiles before compilation.
- parse serializes ProgramEnvelope with source text, real filename, and byte bounds; JavaScript stores sourceContext on a non-enumerable Symbol and includes it again only when serializing the Program for native consumption.
- Context-bearing ASTs restore a SourceFile, require its byte length to match the original range, and rebase positions; out-of-range spans become DUMMY_SP, while dummy and reserved positions are preserved.
- parse_js_as_input first applies filename matching, then read_config, then Options::build_as_input; explicit config paths bypass discovery, and RootMode::Upward errors when no .swcrc is found.
- apply_transforms captures auxiliary output while applying the configured pass under HELPERS and HANDLER, optionally downgrades a Flow type-only module, filters comments, and forwards source-map settings to print.
- WASM plugins receive a versioned serialized Program; outputs feed the next plugin as serialized bytes, and the host deserializes once after the configured sequence finishes.
- Native images persist in a user/version namespace under a digest filename, using locks and staging replacement; compiler printing itself does not write its target path, whereas CLI emit_directory_output writes code, maps, and optional declarations.
Behavior traces
The inspected flows trace concrete calls across JavaScript, native bindings, compiler configuration, and output boundaries. Startup includes platform selection and, for carrier packages, verified materialization before dynamic loading. Transformation and AST round-tripping take different compiler ownership branches. Minification and CLI output provide additional complete paths with explicit failure conditions.
- Startup: the core module reads SWC_BINARY_PATH, requires that path or binding.js, checks getTargetTriple, and attempts @swc/wasm on an exception; the generated loader detects Linux libc through filesystem, process-report, then child-process probes.
- Carrier startup: napi_register_module_v1 calls initialize → Payload::parse/with_integrity → CacheMode::from_env → optional try_replace or cache::materialize → OS library load → resolve raw napi_register_module_v1 → materialized.loaded; the raw registration function then receives the original Node pointers.
- Persistent materialization: cached_at creates the secure namespace, obtains namespace/per-image locks, opens and verifies an existing image, or decodes and verifies a staging file before atomic replace_file; special-file errors return without repair.
- Text transformation: Compiler.transform → bindings.transform → AsyncTask<TransformTask> → compute → new_source_file → Compiler::process_js_file; a legacy JS plugin instead parses first, invokes plugin(m), copies source context, and recursively transforms without the plugin option.
- Compiler pipeline: process_js_file → process_js_with_custom_pass → parse_js_as_input → read_config/build_as_input → apply_transforms → print; a configuration-excluded input produces 'cannot process file because it’s ignored by .swcrc'.
- AST round-trip: ParseTask::compute creates source/comments → parse_js → resolver → serialize_program; parseProgramJson attaches context, then print calls stringifyProgram → PrintTask::compute → deserialize_program_input → fresh compiler for context-bearing input → prepare_program_with_context → Compiler::print.
- Minification: Compiler.minify → native minify → MinifyTask::compute → MinifyTarget::to_file → Compiler::minify → parse_js → paren_remover/resolver → optimize → hygiene when mangling is disabled → fixer → comment handling → print.
- CLI compilation: main → run → CompileOptions::execute → validate → execute_once or a watch handler; stdout mode collects inputs, calls execute_transform/process_js_file, then emit_stdout_output, while directory mode processes output entries in parallel.
Testing signals
Tests and CI were inspected, not executed; no passing result or coverage percentage is asserted. The inspected suites combine Rstest API assertions, Rust tests and fixture macros, external execution comparisons, and Node test-runner release contracts. Coverage includes source-context behavior, compiler ordering, source maps, native cache repair, and parser conformance. Feature gates, exclusions, optional suites, and selective CI matrices limit what test existence establishes.
- This investigation inspected test definitions and CI commands without executing them; Rstest configuration and cargo-test workflow declarations provide intended coverage, not evidence that this pinned commit passes.
- Core Rstest has unit and plugin E2E projects with native-test-setup; DISABLE_PLUGIN_E2E_TESTS removes the E2E project, and setup explicitly throws when binding.js cannot initialize.
- Node parse tests cover async/sync AST creation, parse-to-print round-trips, buffer inputs, and missing-file rejection; transform tests cover AST input, legacy plugins, disabled source maps, and React Compiler options.
- Rust source-context unit tests assert span rebasing, dropping out-of-range spans, and preserving reserved positions; a Rust API test verifies custom transforms run after TypeScript stripping.
- Compiler execution fixtures discover exec.js/exec.mjs/exec.ts, build an option matrix, compile each input, execute generated code, and compare stdout; source-map tests write generated artifacts and launch Node validation.
- Parser test262 tests compare normalized pass and pass-explicit ASTs and JSON round-trips; the harness uses unstable libtest and explicitly ignores cases for stack size, Annex B, strict-mode, and Unicode differences.
- Native cache tests cover mode parsing, verified cache hits, corruption recovery, pruning, and custom-root fallback; node:test release contracts assert the 48-artifact inventory and reject missing, duplicate, stale, or substituted reports.
- CI selects affected crates and special feature combinations, runs core binding tests on Windows/macOS with Linux commented out in that matrix, and provides separate CodSpeed benchmarks and actionlint/pinact/zizmor checks; these definitions do not establish exhaustive host coverage.
Risk map
The security-sensitive surfaces are native dynamic loading, cache filesystem operations, executable configuration, plugins, subprocesses, and release automation. The observations below describe explicit source behavior and trust assumptions, not confirmed vulnerabilities. Correctness risks include panic-based input handling, feature-dependent unfinished APIs, and silent loss of AST context through ordinary serialization. This bounded inspection cannot establish repository-wide absence of other risks or tests.
- SWC_BINARY_PATH loads a caller-selected native module, and compileBundleOptions uses require on configuration paths/module names; both are executable inputs that require caller trust.
- Native digest verification establishes equality with the embedded addon, not publisher identity; initialize subsequently passes the materialized pathname to libloading, so trusted installation and filesystem components remain part of the boundary.
- On Unix, exact SWC_NATIVE_BINDING_CACHE_SKIP_SECURITY_CHECK=1 bypasses root/ancestor ownership, permission, and symlink checks; private directories and regular-file checks still enforce owner, mode, link count, and O_NOFOLLOW restrictions.
- File APIs read caller paths, .swcrc discovery can traverse parents according to root mode, and source-map handling opens derived map paths; these inspected paths do not implement repository-root confinement.
- MinifyTarget::Json uses expect, assert_eq!, and unwrap for input shape, including the one-file limitation; this is panic-based validation, and the workspace release profile specifies panic='abort'.
- AST source context is a non-enumerable Symbol property; frozen objects may prevent attachment, and raw Programs fall back to the shared compiler rather than restored context, making object copying/serialization behavior material to later printing.
- CLI bundle/lint/minify deliberately panic as unimplemented, and the non-swc_v1 Node bundle task has todo! branches; callers must distinguish advertised command/API names from the active implementation.
- Plugin runtime cache loading explicitly permits trusted compiled modules without verification; privileged automation also has distinct trust gates, including default-branch-only contributor-policy code and author-association checks before the write-enabled Claude job.
Extension points
SWC primarily extends through typed passes, visitor traits, feature-gated modules, and binding adapters. Plugins have a configurable sequence and an injectable execution runtime rather than a single permanently fixed backend. CLI operations follow a command enum and execution trait. Generated visitors and release carrier formats introduce additional maintenance contracts that extensions must preserve.
- Compiler::process_js_with_custom_pass accepts factories for before/after Pass implementations; its documented before-pass position follows decorators, resolver, and TypeScript stripping, and the after pass is appended to the built pass.
- Visit provides per-node traversal hooks, and overriding a method requires explicit child recursion; the generated visitor file directs maintainers to tools/generate-code rather than manual edits.
- JscExperimental.plugins stores ordered PluginConfig module-name/JSON-option pairs; plugin_env_vars and cache_root configure host inputs, and run_plugin_first moves plugin execution ahead of built-in transforms.
- RuntimeOptions::plugin_runtime accepts Arc<dyn Runtime>; Runtime implementers supply identifier, module preparation, initialization, and optional cache operations, with Wasmer chosen by the corresponding default feature.
- ModuleConfig is a tagged enum whose build function constructs module transformation passes; adding a format requires accounting for both the feature-enabled enum and the deserialization-only stub.
- Bundler inputs are replaceable through Load, returning SourceFile, Module, and Helpers; Resolve is separately re-exported, allowing loading and module resolution to evolve independently.
- CLI extensions plug into the Clap Command enum, implement CommandRunner::execute, and add dispatch in run; repository maintenance tasks use a separate xtask enum for Es, Bench, Git, Npm, and Clean.
- Legacy JavaScript Plugin callbacks remain composable through plugins(ps) but are deprecated in favor of WASM; native and wasm32/plugin-disabled builds have different plugin availability branches, including warning-and-skip behavior.
Contributor handoff
Begin with the public API and the smallest corresponding binding/compiler/configuration files, then follow the exact input and output branches. Preserve pass ordering, source-context ownership, generated artifacts, and feature-dependent behavior. Tests and CI were inspected, not executed, and this brief provides bounded onboarding evidence rather than a whole-repository audit. The commands below describe repository verification expectations for separately authorized implementation work.
- For Node compiler changes, read index.ts, the relevant native operation module, Compiler::process_js_with_custom_pass, and Options/config construction together; raw text, raw AST, and context-bearing AST inputs take different paths.
- Root conventions prefer documented English code, typed enums, small files, structured tracing, and borrowed strings when creating Atoms; inspect local source conventions before changing a pass or binding.
- Generated Rust visitors must be changed through code generation; cargo codegen is a configured alias, and helper generation supports --check against packages/helpers/esm and generated Rust helpers.
- Use existing fixture suites: swc and swc_ecma_parser document their roots and UPDATE=1 commands, followed by the same crate tests without UPDATE; root instructions prohibit removing or modifying existing tests when fixing them.
- Tests and CI here were inspected, not executed; authorized implementation verification normally initializes submodules, runs touched-crate cargo tests, cargo fmt/clippy, and core build:dev plus pnpm test when Node integration changes.
- Resolve toolchain drift before running checks: README states MSRV 1.73, agent rules prohibit nightly-only production features, .cargo/config.toml contains -Zshare-generics, and parser conformance tests use unstable libtest.
- For carrier changes, preserve final stripped raw bytes, target allowlisting, private payload/integrity sections, and verification before loading; documented checks include native Node contracts and cargo tests for swc_native_addon, its packer, and binding_native_addon.
- Check runtime parity explicitly: native defaults enable swc_v1/plugins, @swc/wasm async interfaces do not perform actual async work, WASM plugin transforms may be skipped, and CLI watch requires an output file or directory.
Languages
| Language | Files | Share of source |
|---|---|---|
| JavaScript | 37176 | 76% |
| JSON | 14503 | docs/config |
| TypeScript | 10162 | 21% |
| HTML | 2689 | docs/config |
| CSS | 1921 | docs/config |
| Rust | 1469 | 3% |
| Markdown | 161 | docs/config |
| TOML | 158 | 0% |
Start reading here
Probable source entrypoint: Cargo.toml — inferred from the directory tree and manifest, not the upstream docs.
Source roots
crates/ (3404 files) packages/ (219 files)
Source layout — crates/
| Path | Files |
|---|---|
crates/ast_node/ | 1 |
crates/better_scoped_tls/ | 2 |
crates/binding_macros/ | 1 |
crates/dbg-swc/ | 1 |
crates/from_variant/ | 1 |
crates/hstr/ | 3 |
crates/jsdoc/ | 37 |
Repository structure
| Top-level path | Files |
|---|---|
crates/ | 3404 |
packages/ | 219 |
bindings/ | 119 |
.github/ | 108 |
scripts/ | 60 |
tools/ | 14 |
xtask/ | 13 |
docs/ | 10 |
Docs found: AGENTS.mdARCHITECTURE.mdCLAUDE.mdCONTRIBUTING.mdREADME.mddocs/adr/00000-whats-the-big-idea.mddocs/adr/00001-support-transformation-plugin-native.mddocs/adr/00002-support-event-trace-profiling.mddocs/adr/00003-reserve-bytepos-0-for-dummy-span.mddocs/adr/00004-add-ast-path-info-to-visitor.md
Package
Cargo.toml — , 0 declared dependencies.
CI & config entrypoints
README.md— Repository readmeAGENTS.md— Agent instructionsCLAUDE.md— Claude instructions.github/workflows/CI.yml— GitHub Actions workflow.github/workflows/actions-security.yml— GitHub Actions workflow.github/workflows/bench.yml— GitHub Actions workflow.github/workflows/binary-size.yml— GitHub Actions workflow.github/workflows/claude.yml— GitHub Actions workflow.github/workflows/contributor-policy.yml— GitHub Actions workflow.github/workflows/ecosystem-ci.yml— GitHub Actions workflow
Structural health signals
Average score 8.1/10 across 96 analyzed source files; 15 hotspots surfaced.
tools/generate-code/src/generators/visitor.rs— 1370 lines, score 5 (Large file, Deep nesting)bindings/binding_nodejs_support_wasm/src/nodejs.rs— 1369 lines, score 5 (Large file, Deep nesting)tools/generate-code/src/main.rs— 508 lines, score 5.2 (Large file, Deep nesting)
These are deterministic review leads — size, churn, and structural signals — not proof of a bug in this project.
Recent history
3c46139b86b22026-10-02 — perf(es/parser): Scan template literals with byte search (#12474)1b6d869ec4152026-10-02 — fix(ci): run main CodSpeed benchmarks after policy skipsc19d53129ea22026-10-02 — ci: refresh CodSpeed main baseline70c9ea3e5a8f2026-10-02 — chore: schedule Renovate updates weekly (#12471)7c75e36475802026-10-02 — chore(deps): update npm (#12468)