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/unpluginVite
// 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?
| Setup | Use |
|---|---|
| Pure Node library, no bundler | npx ttsc |
| Frontend with Vite / Webpack / Rspack | @ttsc/unplugin inside the bundler |
| Backend (e.g., NestJS) bundled with Webpack | @ttsc/unplugin |
| Both, monorepo with library + frontend | CLI 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
- TTSC ·
ttscCLI: the standalone path. - Lint & Prettier: the linter that runs inside the same pass.
- FAQ: bundler-specific gotchas and monorepo notes.