TypeScript Performance Optimization
TypeScript project performance optimization involves multiple aspects such as compilation speed, runtime performance, and code size.
This tutorial introduces common performance optimization techniques to help build more efficient TypeScript applications.
SVG Diagram: Performance Optimization Dimensions
Why Performance Optimization is Needed
Although TypeScript provides a powerful type system, improper use can affect compilation speed and runtime performance.
Compiling large projects may take several minutes, seriously affecting the development experience.
The configurations and techniques introduced in this tutorial can significantly improve the performance of TypeScript projects.
Optimization Goals:Faster compilation speed, smaller bundle size, and higher runtime performance.
Compilation Configuration Optimization
Improve compilation speed by optimizing the tsconfig.json configuration.
tsconfig.json Optimization Configuration
"compilerOptions": {
// Enable incremental compilation, save last compilation info
"incremental": true,
// Skip type checking of node_modules
// Greatly improve compilation speed
"skipLibCheck": true,
// Skip declaration file generation (only generate during final build)
"noEmit": true,
// Enable fast incremental builds
"assumeChangesOnlyAffectDirectDependencies": true,
// Execute in parallel
"parallel": true,
// Enable caching
"tsBuildInfoFile": ".tsbuildinfo",
// Files that don't need to be parsed
"exclude": [
"node_modules",
"dist",
"build",
"**/*.test.ts"
]
}
}
skipLibCheck:This is the most important optimization option, which can reduce compilation time by more than 50%.
Project References Optimization
Use project references to split large projects into smaller modules to achieve incremental compilation.
packages/utils/tsconfig.json
// Inherit base configuration
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"composite": true,
"outDir": "./dist",
"declaration": true,
"declarationMap": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}
composite:When enabled, TypeScript will generate .tsbuildinfo files to speed up subsequent compilations.
Type Inference Optimization
Make full use of TypeScript's type inference and avoid over-annotation.
const name: string = "Alice";
const age: number = 25;
const isActive: boolean = true;
// Good practice: leveraging type inference
const name = "Alice";
const age = 25;
const isActive = true;
// Function return types can be omitted (TypeScript infers them automatically)
function add(a: number, b: number) {
return a + b;
}
// Use type inference for complex objects
const user = {
id: 1,
name: "Bob",
email: "bob@example.com"
};
// TypeScript will infer:
// { id: number; name: string; email: string }
// Only annotate explicitly when type inference is inaccurate
const elements: HTMLElement[] = [];
Reduce Annotations:TypeScript's type inference is already very intelligent, and in most cases explicit type annotations are not needed.
Avoid Using any
Using any loses the benefits of type checking and affects runtime performance.
Example
function processData(data: any): any {
return data.value;
}
// Good practice: using unknown or specific types
function processData<T extends { value: string }>(data: T): string {
return data.value;
}
// If you really don't know the type, use unknown
function parseJSON(json: string): unknown {
return JSON.parse(json);
}
// Perform type checking when using it
const data = parseJSON('{"key": "value"}');
if (typeof data === "object" && data !== null) {
const obj = data as { key: string };
console.log(obj.key);
}
// Better approach: use generics
function identity<T>(value: T): T {
return value;
}
const result = identity("hello");
console.log("Result: " + result);
unknown vs any:unknown is a type-safe any that requires type checking before use.
Interface vs Type Alias
Choose the appropriate type definition approach based on the scenario.
Example
interface User {
id: number;
name: string;
}
// Extending an interface
interface User {
email: string;
}
// Type alias: suitable for union types, tuples, and function types
type ID = string | number;
type Status = "pending" | "success" | "error";
type Callback = (data: string) => void;
// Utility types usually use type
type PartialUser = Partial<User>;
type ReadonlyUser = Readonly<User>;
// Performance consideration: interfaces generally compile faster than type aliases
// For simple object types, you can use interface
interface Point {
x: number;
y: number;
}
// For union types, use type
type Shape = Circle | Square | Triangle;
Selection Recommendations:Use interfaces for object types, type for union types, and type for function types.
Build Tool Optimization
Configure build tools to achieve optimal performance.
vite.config.ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
// Vite configuration optimization
export default defineConfig({
plugins: [react()],
// Build optimization
build: {
// Enable code splitting
rollupOptions: {
output: {
// Manual chunk splitting
manualChunks: {
'vendor': ['react', 'react-dom'],
'utils': ['lodash', 'axios']
}
}
},
// Minify code
minify: 'terser',
// Generate sourcemap
sourcemap: false,
// Chunk size limit
chunkSizeWarningLimit: 500
},
// Dev server optimization
server: {
// Enable hot module replacement
hmr: {
overlay: true
}
},
// Optimize dependency resolution
optimizeDeps: {
include: ['react', 'react-dom'],
exclude: ['some-large-library']
}
});
Code Splitting:Use manualChunks to separate large libraries and reduce the main bundle size.
Tree Shaking
Configure modules to support Tree Shaking, eliminating unused code.
src/utils/index.ts
export function add(a: number, b: number): number {
return a + b;
}
export function subtract(a: number, b: number): number {
return a - b;
}
export function multiply(a: number, b: number): number {
return a * b;
}
export function divide(a: number, b: number): number {
if (b === 0) {
throw new Error("Cannot divide by zero");
}
return a / b;
}
// Named exports are more conducive to Tree Shaking than default exports
// Wrong: export default prevents Tree Shaking
// export default { add, subtract, multiply, divide };
// Correct: use named exports
console.log("Utility module loaded");
Named Exports:Using named exports instead of default exports allows build tools to perform Tree Shaking more effectively.
Notes
- skipLibCheck:Must be enabled in production
- Incremental Compilation:Recommended to enable in development environment
- Avoid any:Use unknown instead
- Tree Shaking:Use ES modules and named exports
Best Practices:Set up optimization configurations at the early stage of development to avoid later refactoring.
Summary
TypeScript performance optimization involves multiple aspects.
- Compilation Optimization:skipLibCheck、incremental、project references
- Type Optimization:Leverage inference, avoid any, choose appropriate type definitions
- Build Optimization:Code splitting, Tree Shaking, dependency optimization
- Runtime Optimization:Type safety, generic constraints
Other ExtensionsRecommendations:Regularly check compilation time and bundle size, and continuously optimize project performance.