SWC TypeScript: Strongly Typed Applications
SWC (Speedy Web Compiler) is a super-fast TypeScript and JavaScript compiler written in Rust. When combined with TypeScript's type system, SWC enables developers to build strongly typed applications that compile at blazing speeds — often 20 to 70 times faster than the default TypeScript compiler (tsc). This tutorial walks through everything you need to know to build robust, strongly typed applications using SWC.
What Is SWC?
SWC is an extensible Rust-based platform for the next generation of fast developer tools. It handles compilation, minification, bundling, and transformation of JavaScript and TypeScript code. Unlike Babel or tsc, SWC leverages Rust's performance characteristics to deliver near-instant feedback during development.
It is important to understand that SWC's primary role is transpilation — stripping types and converting modern syntax into compatible JavaScript. SWC does not perform full type checking by default. For type checking, you still use tsc in a separate step, typically in CI or via an editor integration.
Why SWC Matters for TypeScript Applications
- Speed: SWC compiles TypeScript files in milliseconds, dramatically improving developer experience.
- Compatibility: SWC supports the vast majority of TypeScript syntax, including decorators, enums, and namespaces.
- Ecosystem integration: SWC powers tools like Next.js, Deno, Parcel, and Turbopack.
- Strong typing preserved: Your TypeScript types remain intact in your source code, ensuring editor support and type safety while SWC handles fast transpilation.
- Configurable: Fine-grained control over target environments, JSX behavior, and module systems.
Setting Up a SWC TypeScript Project
Start by initializing a new project and installing the required dependencies.
mkdir swc-ts-app && cd swc-ts-app
npm init -y
npm install --save-dev @swc/cli @swc/core typescript @types/node
Create a basic project structure:
swc-ts-app/
├── src/
│ └── index.ts
├── .swcrc
├── tsconfig.json
└── package.json
Configuring TypeScript
Even though SWC handles transpilation, you still need a tsconfig.json for type checking and editor support. Create one with strict settings:
{
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
"moduleResolution": "Bundler",
"strict": true,
"noImplicitAny": true,
"strictNullChecks": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noImplicitReturns": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"outDir": "./dist",
"rootDir": "./src",
"declaration": true,
"isolatedModules": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}
The isolatedModules flag is critical when using SWC. Because SWC transpiles each file independently, certain TypeScript features (like const enum and namespace re-exports) require this setting to ensure compatibility.
Configuring SWC
Create a .swcrc file to control how SWC transpiles your TypeScript:
{
"$schema": "https://swc.rs/schema.json",
"env": {
"targets": {
"node": "16"
}
},
"jsc": {
"parser": {
"syntax": "typescript",
"tsx": false,
"decorators": true,
"dynamicImport": true
},
"transform": {
"legacyDecorator": true
},
"target": "es2020",
"paths": {
"@/*": ["./src/*"]
}
},
"module": {
"type": "es6"
},
"minify": false
}
Key configuration options explained:
env.targets: Defines the runtime environments SWC should target for syntax downleveling.jsc.parser.syntax: Tells SWC to parse TypeScript syntax.jsc.parser.tsx: Enable if you are using JSX with TypeScript (React projects).jsc.target: The output JavaScript version.module.type: The module system to emit (es6, commonjs, amd, etc.).
Writing Strongly Typed Code
Now let's write some actual TypeScript code that leverages the type system. Create src/index.ts:
// src/types.ts
export interface User {
id: string;
name: string;
email: string;
role: UserRole;
createdAt: Date;
}
export type UserRole = "admin" | "editor" | "viewer";
export interface Repository<T> {
findById(id: string): Promise<T | null>;
save(entity: T): Promise<T>;
delete(id: string): Promise<boolean>;
}
// src/userRepository.ts
import { Repository, User, UserRole } from "./types";
export class UserRepository implements Repository<User> {
private users: Map<string, User> = new Map();
async findById(id: string): Promise<User | null> {
return this.users.get(id) ?? null;
}
async save(entity: User): Promise<User> {
this.users.set(entity.id, entity);
return entity;
}
async delete(id: string): Promise<boolean> {
return this.users.delete(id);
}
async findByRole(role: UserRole): Promise<User[]> {
return Array.from(this.users.values()).filter(
(user) => user.role === role
);
}
}
// src/index.ts
import { UserRepository } from "./userRepository";
import { User } from "./types";
async function main(): Promise<void> {
const repository = new UserRepository();
const adminUser: User = {
id: "1",
name: "Alice",
email: "alice@example.com",
role: "admin",
createdAt: new Date(),
};
await repository.save(adminUser);
const found = await repository.findById("1");
if (found) {
console.log(`Found user: ${found.name} (${found.role})`);
}
const admins = await repository.findByRole("admin");
console.log(`Total admins: ${admins.length}`);
}
main().catch((error) => {
console.error("Application error:", error);
process.exit(1);
});
Building and Running the Application
Add scripts to your package.json to handle both transpilation and type checking:
{
"name": "swc-ts-app",
"version": "1.0.0",
"scripts": {
"build": "swc src -d dist",
"type-check": "tsc --noEmit",
"start": "node dist/index.js",
"dev": "swc src -d dist --watch && node dist/index.js"
}
}
Run the build and type check separately:
npm run build
npm run type-check
npm start
The build command uses SWC for fast transpilation, while type-check uses tsc in --noEmit mode to validate types without producing output files. This separation is the recommended workflow for SWC-based TypeScript projects.
Using SWC with Node.js Directly
For development, you can run TypeScript files directly with SWC without precompiling. Install the SWC register hook:
npm install --save-dev @swc-node/register
Then run your TypeScript files directly:
node --import @swc-node/register/esm-register src/index.ts
This approach is excellent for rapid development and testing, while still benefiting from TypeScript's type system in your editor.
Integrating SWC with a Bundler
For larger applications, you will likely use a bundler. Here is an example using SWC with esbuild or webpack via the swc-loader:
npm install --save-dev swc-loader webpack webpack-cli
Create a webpack.config.js:
const path = require("path");
module.exports = {
entry: "./src/index.ts",
output: {
filename: "bundle.js",
path: path.resolve(__dirname, "dist"),
},
module: {
rules: [
{
test: /\.ts$/,
exclude: /node_modules/,
use: {
loader: "swc-loader",
options: {
jsc: {
parser: {
syntax: "typescript",
},
target: "es2020",
},
},
},
},
],
},
resolve: {
extensions: [".ts", ".js"],
},
};
Best Practices for SWC TypeScript Applications
- Always run type checking separately: Use
tsc --noEmitin CI pipelines and pre-commit hooks. SWC does not type check, so this step is essential for catching type errors. - Enable strict mode in tsconfig: Set
"strict": trueand enable additional strict flags likenoUnusedLocalsandnoImplicitReturnsto maximize type safety. - Use
isolatedModules: This ensures your code is compatible with SWC's per-file transpilation model. Avoidconst enumand re-exporting types without thetypekeyword. - Use explicit type imports: When importing types, use
import type { User }to help SWC and bundlers strip type-only imports efficiently. - Keep .swcrc and tsconfig.json aligned: Ensure the target, module system, and decorator settings match between both configuration files to avoid runtime surprises.
- Leverage SWC for tests: Use SWC to transpile test files for Jest or Vitest, dramatically reducing test startup time.
- Avoid features SWC does not support: While SWC supports most TypeScript features, check the SWC documentation for edge cases, especially around experimental decorators and parameter properties.
Configuring Jest with SWC
Testing is a critical part of strongly typed applications. Configure Jest to use SWC for transpiling TypeScript test files:
npm install --save-dev jest @swc/jest
Add to your package.json or jest.config.js:
module.exports = {
testEnvironment: "node",
transform: {
"^.+\\.ts$": "@swc/jest",
},
testMatch: ["**/__tests__/**/*.test.ts"],
moduleFileExtensions: ["ts", "js", "json"],
};
Write a test for the repository:
// src/__tests__/userRepository.test.ts
import { UserRepository } from "../userRepository";
import { User } from "../types";
describe("UserRepository", () => {
let repository: UserRepository;
beforeEach(() => {
repository = new UserRepository();
});
const mockUser: User = {
id: "test-1",
name: "Test User",
email: "test@example.com",
role: "editor",
createdAt: new Date("2024-01-01"),
};
it("should save and retrieve a user", async () => {
await repository.save(mockUser);
const found = await repository.findById("test-1");
expect(found).toEqual(mockUser);
});
it("should return null for non-existent user", async () => {
const found = await repository.findById("non-existent");
expect(found).toBeNull();
});
it("should filter users by role", async () => {
await repository.save(mockUser);
await repository.save({ ...mockUser, id: "test-2", role: "viewer" });
const editors = await repository.findByRole("editor");
expect(editors).toHaveLength(1);
expect(editors[0].id).toBe("test-1");
});
});
Run the tests:
npx jest
Handling Path Aliases
Path aliases improve code readability. Configure them in both tsconfig.json and .swcrc. For runtime resolution, use a tool like tsc-alias or configure your bundler:
npm install --save-dev tsc-alias
Update your build script:
"scripts": {
"build": "swc src -d dist && tsc-alias"
}
Conclusion
SWC combined with TypeScript offers the best of both worlds: the safety and developer ergonomics of a strong static type system, paired with compilation speeds that keep your feedback loop tight. By separating concerns — using SWC for fast transpilation and tsc for thorough type checking — you can build scalable, maintainable applications without sacrificing performance. Follow the best practices outlined in this tutorial, keep your configurations aligned, and leverage SWC across your build, development, and testing workflows to get the most out of strongly typed TypeScript applications.