Skip to Content
📖 Guide DocumentsTTSCBundler integration

Bundler integration

When a bundler (Vite, Webpack, Rollup, …) owns your build, you still want the ttsc plugin pass to run. @ttsc/unplugin is the adapter, same plugin contract, embedded inside the bundler.

Install

npm install -D ttsc @ttsc/lint typescript@rc @ttsc/unplugin

Vite

// vite.config.ts import ttsc from "@ttsc/unplugin/vite"; import { defineConfig } from "vite"; export default defineConfig({ plugins: [ttsc()], });

Webpack

// webpack.config.mjs import ttsc from "@ttsc/unplugin/webpack"; export default { entry: "./src/index.ts", plugins: [ttsc()], };

Rollup / Rolldown

import ttsc from "@ttsc/unplugin/rollup"; // or "@ttsc/unplugin/rolldown" export default { input: "src/index.ts", output: { dir: "dist", format: "esm" }, plugins: [ttsc()], };

The adapter emits transformed TypeScript, not JavaScript. Plain Rollup does not compile TypeScript itself, so add a downstream TypeScript-to-JavaScript step after ttsc() (an esbuild- or swc-based Rollup plugin, for example), otherwise the build fails parsing the type syntax. Bundlers that strip types on their own (Vite, esbuild, Bun) need no extra step.

esbuild

import { build } from "esbuild"; import ttsc from "@ttsc/unplugin/esbuild"; await build({ entryPoints: ["src/index.ts"], outdir: "dist", bundle: true, plugins: [ttsc()], });

Rspack

import ttsc from "@ttsc/unplugin/rspack"; export default { entry: "./src/index.ts", plugins: [ttsc()] };

Next.js

// next.config.mjs import withTtsc from "@ttsc/unplugin/next"; export default withTtsc({ /* your Next config */ });

withTtsc covers the webpack path. On Turbopack (next dev --turbopack), use the loader rules below instead.

Turbopack

Turbopack has no JS plugin API, but it runs webpack loaders through turbopack.rules, and a ttsc transform is loader-shaped: TypeScript source in, transformed source out. Reference the standalone loader by module name (do not call it):

// next.config.mjs export default { turbopack: { rules: { "*.ts": { loaders: ["@ttsc/unplugin/turbopack"] }, "*.tsx": { loaders: ["@ttsc/unplugin/turbopack"] }, }, }, };

Options go through the rule’s options object ({ loader: "@ttsc/unplugin/turbopack", options: { project: "tsconfig.build.json" } }).

Farm

import ttsc from "@ttsc/unplugin/farm"; import { defineConfig } from "@farmfe/core"; export default defineConfig({ plugins: [ttsc()] });

Bun

import ttsc from "@ttsc/unplugin/bun"; await Bun.build({ entrypoints: ["./src/index.ts"], outdir: "./dist", plugins: [ttsc()], });

React Native / Expo (Metro)

Metro is not an unplugin target, so it has its own adapter package, @ttsc/metro. Metro transpiles each file with Babel, which strips types and never runs TypeScript transformers; @ttsc/metro inserts the ttsc plugin pass before that Babel step and delegates the rest to your existing Expo/React-Native transformer.

npm install -D ttsc typescript@rc @ttsc/metro
// metro.config.js (Expo) const { getDefaultConfig } = require("expo/metro-config"); const { withTtsc } = require("@ttsc/metro"); module.exports = withTtsc(getDefaultConfig(__dirname));

For bare React Native, wrap getDefaultConfig from @react-native/metro-config instead. withTtsc reads the same tsconfig.json and plugin configuration as the CLI and @ttsc/unplugin; options (project, compilerOptions, plugins, upstreamTransformer, include/exclude) go in the second argument.

Two v1 caveats: the project is type-checked once per Metro worker (a resident incremental compiler is the planned optimization, #255), and because Metro’s transform cache is keyed per-file, restart with --reset-cache after changing tsconfig/plugin config or a depended-upon type. See the @ttsc/metro README for details.

Configuration

@ttsc/unplugin reads your existing tsconfig.json, same plugins, same configs (lint.config.ts, banner.config.ts, …). For most projects no adapter-specific options are needed.

To point at a non-default config file, pass project (resolved from process.cwd()):

ttsc({ project: "tsconfig.build.json", });

Two more options layer on top of that config: compilerOptions for a one-off override without another config file, and plugins to replace the project’s plugin list (plugins: false disables them). See @ttsc/unplugin for the full set.

When do I use this vs the ttsc CLI?

SetupUse
Pure Node library, no bundlernpx ttsc
Frontend with Vite / Webpack / Rspack@ttsc/unplugin inside the bundler
Backend (e.g., NestJS) bundled with Webpack@ttsc/unplugin
Both, monorepo with library + frontendCLI for the library, @ttsc/unplugin for the frontend
React Native / Expo@ttsc/metro (Metro transformer)

The plugin pass is identical either way. Diagnostics show up wherever the bundler surfaces them.

See also

Last updated on