Performance Optimization and Build Tools
1. Compilation Performance Tuning
| Optimization | Configuration | Impact | Trade-off |
|---|---|---|---|
| skipLibCheck | "skipLibCheck": true |
Skip type checking of .d.ts files - 2-5x faster compilation | May miss errors in type definitions |
| incremental | "incremental": true |
Cache compilation info - 40-70% faster rebuilds | Requires .tsbuildinfo file storage |
| skipDefaultLibCheck | "skipDefaultLibCheck": true |
Skip checking default lib.d.ts files | Less thorough than skipLibCheck |
| isolatedModules | "isolatedModules": true |
Ensure each file can be transpiled independently | Restricts some TS features (const enums) |
| Module resolution | "moduleResolution": "bundler" |
Faster resolution for bundlers | Only for bundled projects |
| Exclude patterns | "exclude": ["node_modules"] |
Reduce files to check - significant improvement | Must manually include needed files |
| --diagnostics | tsc --diagnostics |
Show compilation performance metrics | Debug information only |
Example: Performance-optimized tsconfig.json
// tsconfig.json - Production optimized
{
"compilerOptions": {
// Speed optimizations
"incremental": true,
"skipLibCheck": true,
"skipDefaultLibCheck": true,
// Fast transpilation
"isolatedModules": true,
"moduleResolution": "bundler",
// Output settings
"target": "ES2020",
"module": "ESNext",
"lib": ["ES2020", "DOM"],
// Type checking (keep strict for quality)
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
// Build caching
"tsBuildInfoFile": "./dist/.tsbuildinfo",
// Module resolution
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"resolveJsonModule": true,
// Output
"outDir": "./dist",
"rootDir": "./src"
},
"include": ["src/**/*"],
"exclude": [
"node_modules",
"**/*.test.ts",
"**/*.spec.ts",
"dist",
"coverage"
]
}
Example: Performance diagnostics
// Run with diagnostics
tsc --diagnostics
// Output example:
Files: 450
Lines: 125000
Nodes: 780000
Identifiers: 245000
Symbols: 198000
Types: 67000
Instantiations: 142000
Memory used: 385MB
I/O read: 0.82s
I/O write: 0.15s
Parse time: 1.45s
Bind time: 0.78s
Check time: 3.21s
Emit time: 0.65s
Total time: 6.09s
// Extended diagnostics for more detail
tsc --diagnostics --extendedDiagnostics
// Analyze slow files
tsc --generateTrace ./trace
// Opens Chrome DevTools Performance tab
Example: Large project optimizations
// For very large codebases (>100k LOC)
{
"compilerOptions": {
// Maximum speed settings
"skipLibCheck": true,
"incremental": true,
"isolatedModules": true,
// Reduce type checking scope
"noEmit": true, // Let bundler handle transpilation
// Assume changes only affect direct dependencies
"assumeChangesOnlyAffectDirectDependencies": true,
// Faster but less accurate
"disableSolutionSearching": true,
"disableReferencedProjectLoad": true
},
// Split into smaller projects
"references": [
{ "path": "./packages/core" },
{ "path": "./packages/ui" },
{ "path": "./packages/utils" }
]
}
// Watch mode optimizations
{
"watchOptions": {
// Use native file system events (fastest)
"watchFile": "useFsEvents",
"watchDirectory": "useFsEvents",
// Fallback options for compatibility
"fallbackPolling": "dynamicPriority",
// Exclude from watching
"excludeDirectories": ["**/node_modules", "**/dist"],
"excludeFiles": ["**/*.test.ts"]
}
}
2. Incremental Builds and Project References
| Feature | Configuration | Benefit | Requirement |
|---|---|---|---|
| Incremental Compilation | "incremental": true |
Cache previous compilation results - faster rebuilds | .tsbuildinfo file |
| Composite Projects | "composite": true |
Enable project references - parallel builds | declaration: true required |
| Project References | "references": [{ "path": "..." }] |
Build dependencies separately - faster monorepo builds | Composite projects |
| Build Mode | tsc -b or tsc --build |
Smart dependency tracking - only rebuild changed projects | Project references setup |
| Parallel Builds | tsc -b --verbose |
Build independent projects concurrently | Multi-core CPU |
| Clean Build | tsc -b --clean |
Remove build outputs and caches | Fresh build needed |
Example: Monorepo with project references
// Root tsconfig.json
{
"files": [],
"references": [
{ "path": "./packages/core" },
{ "path": "./packages/utils" },
{ "path": "./packages/api" },
{ "path": "./packages/web" }
]
}
// packages/core/tsconfig.json
{
"compilerOptions": {
"composite": true,
"declaration": true,
"declarationMap": true,
"outDir": "./dist",
"rootDir": "./src",
"incremental": true,
"tsBuildInfoFile": "./dist/.tsbuildinfo"
},
"include": ["src/**/*"]
}
// packages/api/tsconfig.json (depends on core and utils)
{
"compilerOptions": {
"composite": true,
"declaration": true,
"declarationMap": true,
"outDir": "./dist",
"rootDir": "./src"
},
"include": ["src/**/*"],
"references": [
{ "path": "../core" },
{ "path": "../utils" }
]
}
// packages/web/tsconfig.json (depends on api, core, utils)
{
"compilerOptions": {
"composite": true,
"declaration": true,
"outDir": "./dist",
"rootDir": "./src",
"jsx": "react-jsx"
},
"include": ["src/**/*"],
"references": [
{ "path": "../api" },
{ "path": "../core" },
{ "path": "../utils" }
]
}
Example: Build commands
// package.json scripts
{
"scripts": {
// Build all projects with dependencies
"build": "tsc -b",
// Build specific project
"build:core": "tsc -b packages/core",
"build:api": "tsc -b packages/api",
"build:web": "tsc -b packages/web",
// Watch mode with project references
"dev": "tsc -b --watch",
"dev:api": "tsc -b packages/api --watch",
// Clean all build outputs
"clean": "tsc -b --clean",
// Force rebuild everything
"rebuild": "tsc -b --force",
// Verbose build output
"build:verbose": "tsc -b --verbose",
// Dry run (show what would be built)
"build:dry": "tsc -b --dry"
}
}
// Build with NPM workspaces (parallel execution)
npm run build --workspaces
// Selective builds based on changes
// Only rebuild changed packages and their dependents
tsc -b packages/core packages/api
// Benefits:
// - Faster builds (only changed projects)
// - Parallel compilation (independent projects)
// - Proper dependency tracking
// - Cached builds (.tsbuildinfo)
// - Better IDE performance
3. Bundle Analysis and Tree Shaking with TypeScript
| Technique | Configuration | Purpose | Tool |
|---|---|---|---|
| ES Modules | "module": "ESNext" |
Enable tree shaking - remove unused exports | All modern bundlers |
| Side Effects | "sideEffects": false in package.json |
Mark package as side-effect free - aggressive tree shaking | Webpack, Rollup |
| Named Exports | export { func } not export default |
Better tree shaking - individual function removal | Best practice |
| webpack-bundle-analyzer | npm i -D webpack-bundle-analyzer |
Visualize bundle size - identify large dependencies | Webpack |
| source-map-explorer | npm i -D source-map-explorer |
Analyze bundle composition from source maps | Any bundler |
| Const Enums | const enum with isolatedModules: false |
Inline enum values - zero runtime cost | TypeScript compiler |
Example: Tree-shakeable library structure
// tsconfig.json - for tree-shakeable output
{
"compilerOptions": {
"module": "ESNext", // ES modules for tree shaking
"target": "ES2020",
"declaration": true,
"declarationMap": true,
"moduleResolution": "bundler",
// Important for tree shaking
"isolatedModules": true, // Each file standalone
"esModuleInterop": true,
"preserveConstEnums": false // Inline const enums
}
}
// package.json
{
"name": "my-library",
"version": "1.0.0",
"type": "module",
"main": "./dist/index.cjs",
"module": "./dist/index.js", // ES module entry
"types": "./dist/index.d.ts",
"exports": {
".": {
"import": "./dist/index.js", // ES module
"require": "./dist/index.cjs", // CommonJS
"types": "./dist/index.d.ts"
}
},
"sideEffects": false // Enable aggressive tree shaking
}
// src/index.ts - Use named exports
export { funcA } from './moduleA';
export { funcB } from './moduleB';
export { funcC } from './moduleC';
// Avoid default exports for better tree shaking
// ❌ Bad for tree shaking
export default { funcA, funcB, funcC };
// ✅ Good for tree shaking
export { funcA, funcB, funcC };
// Consumer code - only imports what's needed
import { funcA } from 'my-library'; // Only funcA is bundled
Example: Bundle analysis with Webpack
// webpack.config.js
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');
module.exports = {
// ... webpack config
plugins: [
new BundleAnalyzerPlugin({
analyzerMode: 'static',
reportFilename: 'bundle-report.html',
openAnalyzer: true,
generateStatsFile: true,
statsFilename: 'bundle-stats.json'
})
],
optimization: {
usedExports: true, // Mark unused exports
sideEffects: true, // Respect package.json sideEffects
concatenateModules: true, // Scope hoisting
minimize: true,
splitChunks: {
chunks: 'all',
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
priority: 10
},
common: {
minChunks: 2,
priority: 5,
reuseExistingChunk: true
}
}
}
}
};
// package.json scripts
{
"scripts": {
"analyze": "webpack --mode production --profile --json > stats.json && webpack-bundle-analyzer stats.json",
"build:analyze": "cross-env ANALYZE=true webpack --mode production"
}
}
// Using source-map-explorer
npm install -D source-map-explorer
// package.json
{
"scripts": {
"analyze:sourcemap": "source-map-explorer 'dist/**/*.js'"
}
}
4. Webpack TypeScript Configuration and ts-loader
| Option | Configuration | Purpose | Performance |
|---|---|---|---|
| ts-loader | npm i -D ts-loader |
Standard TypeScript loader for Webpack | Moderate speed, full type checking |
| transpileOnly | transpileOnly: true |
Skip type checking during build - much faster | 5-10x faster, requires separate type check |
| fork-ts-checker-webpack-plugin | npm i -D fork-ts-checker-webpack-plugin |
Run type checking in separate process | Parallel checking, faster builds |
| thread-loader | npm i -D thread-loader |
Run loaders in worker pool | Parallel processing |
| cache | cache: { type: 'filesystem' } |
Cache webpack compilation - persistent cache | Faster rebuilds |
| babel-loader + @babel/preset-typescript | npm i -D babel-loader @babel/preset-typescript |
Use Babel for transpilation - no type checking | Very fast, type check separately |
Example: Webpack with ts-loader (production)
// webpack.config.js
const path = require('path');
const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');
module.exports = {
mode: 'production',
entry: './src/index.ts',
module: {
rules: [
{
test: /\.tsx?$/,
use: [
{
loader: 'ts-loader',
options: {
transpileOnly: true, // Skip type checking (done by plugin)
experimentalWatchApi: true, // Faster watching
configFile: 'tsconfig.json'
}
}
],
exclude: /node_modules/
}
]
},
plugins: [
// Type checking in separate process
new ForkTsCheckerWebpackPlugin({
async: false, // Wait for type checking in production
typescript: {
configFile: path.resolve(__dirname, 'tsconfig.json'),
diagnosticOptions: {
semantic: true,
syntactic: true
}
}
})
],
resolve: {
extensions: ['.tsx', '.ts', '.js'],
alias: {
'@': path.resolve(__dirname, 'src')
}
},
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist'),
clean: true
},
// Performance optimizations
cache: {
type: 'filesystem',
buildDependencies: {
config: [__filename]
}
},
optimization: {
moduleIds: 'deterministic',
runtimeChunk: 'single',
splitChunks: {
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
chunks: 'all'
}
}
}
}
};
Example: Fast development config
// webpack.dev.js
const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');
module.exports = {
mode: 'development',
devtool: 'eval-source-map', // Fast source maps
module: {
rules: [
{
test: /\.tsx?$/,
use: [
// Optional: Use thread-loader for parallel processing
{
loader: 'thread-loader',
options: {
workers: 2,
workerParallelJobs: 50
}
},
{
loader: 'ts-loader',
options: {
transpileOnly: true,
happyPackMode: true, // Required with thread-loader
experimentalWatchApi: true
}
}
],
exclude: /node_modules/
}
]
},
plugins: [
new ForkTsCheckerWebpackPlugin({
async: true, // Don't wait in dev mode (faster)
typescript: {
configFile: 'tsconfig.json',
memoryLimit: 4096 // Increase memory for large projects
}
})
],
cache: {
type: 'filesystem',
allowCollectingMemory: true
},
devServer: {
hot: true,
port: 3000,
historyApiFallback: true
}
};
5. Vite TypeScript Integration and HMR
| Feature | Configuration | Benefit | Note |
|---|---|---|---|
| Native TS Support | Zero config - works out of box | No loader needed - uses esbuild for transpilation | No type checking by default |
| vite-plugin-checker | npm i -D vite-plugin-checker |
Type checking during dev and build | Recommended for type safety |
| isolatedModules | "isolatedModules": true |
Required for esbuild - each file independent | Restricts some TS features |
| HMR (Hot Module Replacement) | Built-in - automatic | Instant updates without full reload | Preserves component state |
| Fast Refresh | @vitejs/plugin-react |
React HMR with state preservation | Only for React |
| Build Speed | Uses esbuild for dev, Rollup for prod | 10-100x faster than Webpack | Excellent DX |
Example: Vite configuration with TypeScript
// vite.config.ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import checker from 'vite-plugin-checker';
import path from 'path';
export default defineConfig({
plugins: [
react({
// Fast Refresh for React
fastRefresh: true,
// Babel options if needed
babel: {
parserOpts: {
plugins: ['decorators-legacy']
}
}
}),
// Type checking plugin
checker({
typescript: true,
eslint: {
lintCommand: 'eslint "./src/**/*.{ts,tsx}"'
},
overlay: {
initialIsOpen: false, // Don't auto-open error overlay
position: 'br' // Bottom right
}
})
],
resolve: {
alias: {
'@': path.resolve(__dirname, './src'),
'@components': path.resolve(__dirname, './src/components'),
'@utils': path.resolve(__dirname, './src/utils')
}
},
build: {
target: 'es2020',
outDir: 'dist',
sourcemap: true,
rollupOptions: {
output: {
manualChunks: {
vendor: ['react', 'react-dom'],
utils: ['lodash', 'date-fns']
}
}
},
// Optimize chunk size
chunkSizeWarningLimit: 1000,
// Minification with esbuild (faster)
minify: 'esbuild'
},
server: {
port: 3000,
open: true,
hmr: {
overlay: true
}
},
// Optimize dependencies
optimizeDeps: {
include: ['react', 'react-dom'],
exclude: ['@vite/client', '@vite/env']
}
});
Example: TypeScript config for Vite
// tsconfig.json for Vite
{
"compilerOptions": {
"target": "ES2020",
"useDefineForClassFields": true,
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"module": "ESNext",
"skipLibCheck": true,
// Bundler mode (Vite-specific)
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true, // Required for esbuild
"noEmit": true, // Vite handles transpilation
// Type checking
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
// Path mapping
"baseUrl": ".",
"paths": {
"@/*": ["src/*"],
"@components/*": ["src/components/*"],
"@utils/*": ["src/utils/*"]
},
// React
"jsx": "react-jsx"
},
"include": ["src"],
"references": [{ "path": "./tsconfig.node.json" }]
}
// tsconfig.node.json - for Vite config files
{
"compilerOptions": {
"composite": true,
"skipLibCheck": true,
"module": "ESNext",
"moduleResolution": "bundler",
"allowSyntheticDefaultImports": true
},
"include": ["vite.config.ts"]
}
// package.json scripts
{
"scripts": {
"dev": "vite",
"build": "tsc && vite build", // Type check before build
"preview": "vite preview",
"type-check": "tsc --noEmit",
"type-check:watch": "tsc --noEmit --watch"
}
}
6. esbuild and SWC TypeScript Compilation
| Tool | Speed | Features | Limitations |
|---|---|---|---|
| esbuild | 10-100x faster than tsc - written in Go | Bundling, minification, transpilation, tree shaking | No type checking, limited TS features |
| SWC | 20-70x faster than Babel - written in Rust | Transpilation, minification, bundling (experimental) | No type checking, newer tool |
| @swc/core | npm i -D @swc/core |
Core SWC library - standalone or with bundlers | Node.js API, CLI |
| esbuild-loader | npm i -D esbuild-loader |
Use esbuild with Webpack - replace ts-loader | Much faster Webpack builds |
| Turbopack | Next.js 13+ bundler - Rust-based | Incremental bundling, HMR, fast builds | Next.js only currently |
| Type Checking | Separate with tsc --noEmit |
Run type checking in parallel or CI | Two-step process |
Example: esbuild configuration
// esbuild.config.js
const esbuild = require('esbuild');
// Build configuration
esbuild.build({
entryPoints: ['src/index.ts'],
bundle: true,
outfile: 'dist/bundle.js',
// TypeScript settings
platform: 'node',
target: 'es2020',
format: 'esm',
// Optimizations
minify: true,
sourcemap: true,
treeShaking: true,
splitting: true,
// External dependencies (not bundled)
external: ['react', 'react-dom'],
// Loaders for different file types
loader: {
'.ts': 'ts',
'.tsx': 'tsx',
'.png': 'file',
'.svg': 'dataurl'
},
// Define environment variables
define: {
'process.env.NODE_ENV': '"production"'
},
// Watch mode
watch: false,
// Logging
logLevel: 'info'
}).catch(() => process.exit(1));
// package.json scripts
{
"scripts": {
"build": "npm run type-check && node esbuild.config.js",
"type-check": "tsc --noEmit",
"dev": "node esbuild.config.js --watch",
"dev:check": "concurrently \"npm run type-check:watch\" \"npm run dev\""
}
}
// Using esbuild programmatically
import * as esbuild from 'esbuild';
// Development with watch
const ctx = await esbuild.context({
entryPoints: ['src/index.ts'],
bundle: true,
outdir: 'dist',
sourcemap: true,
platform: 'node',
target: 'node18'
});
await ctx.watch();
console.log('Watching...');
// Serve mode (dev server)
const { host, port } = await ctx.serve({
servedir: 'public',
port: 3000
});
console.log(`Server running at http://${host}:${port}`);
Example: SWC configuration
// .swcrc
{
"jsc": {
"parser": {
"syntax": "typescript",
"tsx": true,
"decorators": true,
"dynamicImport": true
},
"transform": {
"react": {
"runtime": "automatic",
"development": false,
"refresh": true // Fast Refresh
},
"legacyDecorator": true,
"decoratorMetadata": true
},
"target": "es2020",
"loose": false,
"externalHelpers": false,
"keepClassNames": true
},
"module": {
"type": "es6",
"strict": false,
"strictMode": true,
"lazy": false,
"noInterop": false
},
"minify": false,
"sourceMaps": true
}
// Using SWC with Node.js
// Install: npm i -D @swc/core @swc/cli
{
"scripts": {
"build": "swc src -d dist",
"build:watch": "swc src -d dist --watch",
"type-check": "tsc --noEmit"
}
}
// Using SWC with Webpack
// webpack.config.js
module.exports = {
module: {
rules: [
{
test: /\.(ts|tsx)$/,
use: {
loader: 'swc-loader',
options: {
jsc: {
parser: {
syntax: 'typescript',
tsx: true
}
}
}
},
exclude: /node_modules/
}
]
}
};
// Using esbuild-loader with Webpack
module.exports = {
module: {
rules: [
{
test: /\.tsx?$/,
loader: 'esbuild-loader',
options: {
loader: 'tsx',
target: 'es2020',
tsconfigRaw: require('./tsconfig.json')
}
}
]
},
plugins: [
// Use esbuild for minification too (faster)
new ESBuildMinifyPlugin({
target: 'es2020',
css: true
})
]
};
Example: Performance comparison
// Build time comparison for medium project (50k LOC)
// TypeScript Compiler (tsc)
// Time: ~45s
tsc --project tsconfig.json
// Babel + @babel/preset-typescript
// Time: ~35s (no type checking)
babel src --out-dir dist --extensions ".ts,.tsx"
// ts-loader (Webpack)
// Time: ~40s
webpack --mode production
// ts-loader + transpileOnly + fork-ts-checker
// Time: ~25s (parallel type checking)
webpack --mode production
// esbuild-loader (Webpack)
// Time: ~5s (no type checking)
webpack --mode production
// esbuild (standalone)
// Time: ~2s (no type checking)
node esbuild.config.js
// SWC
// Time: ~3s (no type checking)
swc src -d dist
// Vite (esbuild)
// Dev server start: <1s
// Production build: ~5s
vite build
// Recommendation:
// Development: Vite or esbuild with separate tsc --noEmit --watch
// Production: esbuild/SWC + tsc --noEmit in CI pipeline
// Type safety: Always run tsc --noEmit for type checking
Note: Performance optimization best practices:
- Compilation - Enable skipLibCheck, incremental, use project references for monorepos
- Build tools - Use esbuild/SWC for fast transpilation, run type checking separately
- Webpack - Use ts-loader with transpileOnly + fork-ts-checker, enable filesystem cache
- Vite - Best DX with instant HMR, use vite-plugin-checker for type checking
- Tree shaking - Use ES modules, named exports, mark packages as side-effect free
- Analysis - Use webpack-bundle-analyzer or source-map-explorer to identify bloat
Performance and Build Tools Summary
- Compilation speed - skipLibCheck, incremental builds, project references for 40-70% faster rebuilds
- Webpack - ts-loader with transpileOnly + fork-ts-checker for parallel type checking
- Vite - Best development experience with instant HMR, esbuild transpilation, Rollup production builds
- esbuild - 10-100x faster than tsc, excellent for development and production (no type checking)
- SWC - Rust-based compiler, 20-70x faster than Babel, growing ecosystem
- Strategy - Use fast transpiler (esbuild/SWC) + separate type checking with tsc --noEmit