Chapter 12: Build Tools & Configuration
Authored by syscook.dev
What are Build Tools and Configuration in TypeScript?
Build tools and configuration in TypeScript are essential for compiling, bundling, and optimizing TypeScript code for production. They handle the transformation from TypeScript source code to JavaScript, manage dependencies, and provide various optimization features.
Key Concepts:
- TypeScript Compiler (tsc): Core compilation tool
- Build Tools: Webpack, Rollup, Vite, esbuild
- Configuration Files: tsconfig.json, package.json
- Module Systems: CommonJS, ES Modules, UMD
- Code Splitting: Breaking code into smaller chunks
- Tree Shaking: Removing unused code
- Source Maps: Debugging support
Why Use Build Tools and Configuration?
1. Code Transformation
Build tools transform TypeScript code into JavaScript that browsers can understand.
// TypeScript source code
interface User {
id: number;
name: string;
email: string;
}
class UserService {
private users: User[] = [];
addUser(user: Omit<User, 'id'>): User {
const newUser: User = {
id: this.users.length + 1,
...user
};
this.users.push(newUser);
return newUser;
}
}
// Compiled JavaScript (ES5)
var UserService = /** @class */ (function () {
function UserService() {
this.users = [];
}
UserService.prototype.addUser = function (user) {
var newUser = {
id: this.users.length + 1
};
Object.assign(newUser, user);
this.users.push(newUser);
return newUser;
};
return UserService;
}());
2. Module Bundling
Build tools bundle multiple modules into optimized files.
// Before bundling - multiple files
// src/user.ts
export interface User { id: number; name: string; }
// src/user-service.ts
import { User } from './user';
export class UserService { /* ... */ }
// src/index.ts
import { UserService } from './user-service';
export { UserService };
// After bundling - single optimized file
// dist/bundle.js
(function() {
// All code bundled together
})();
How to Use Build Tools and Configuration?
1. TypeScript Configuration (tsconfig.json)
Basic Configuration
{
"compilerOptions": {
"target": "ES2020",
"module": "commonjs",
"lib": ["ES2020", "DOM"],
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"declaration": true,
"declarationMap": true,
"sourceMap": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist", "**/*.test.ts"]
}
Advanced Configuration
{
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
"moduleResolution": "node",
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"noImplicitAny": true,
"strictNullChecks": true,
"strictFunctionTypes": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedIndexedAccess": true,
"exactOptionalPropertyTypes": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"removeComments": false,
"importHelpers": true,
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"resolveJsonModule": true,
"allowJs": true,
"checkJs": false,
"jsx": "react-jsx",
"baseUrl": "./src",
"paths": {
"@/*": ["*"],
"@components/*": ["components/*"],
"@utils/*": ["utils/*"],
"@types/*": ["types/*"]
}
},
"include": [
"src/**/*",
"types/**/*"
],
"exclude": [
"node_modules",
"dist",
"**/*.test.ts",
"**/*.spec.ts"
],
"ts-node": {
"esm": true
}
}
2. Webpack Configuration
Basic Webpack Setup
// webpack.config.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: './src/index.ts',
module: {
rules: [
{
test: /\.tsx?$/,
use: 'ts-loader',
exclude: /node_modules/,
},
],
},
resolve: {
extensions: ['.tsx', '.ts', '.js'],
},
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist'),
clean: true,
},
plugins: [
new HtmlWebpackPlugin({
template: './src/index.html',
}),
],
};
Advanced Webpack Configuration
// webpack.config.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const TerserPlugin = require('terser-webpack-plugin');
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
module.exports = (env, argv) => {
const isProduction = argv.mode === 'production';
return {
entry: {
main: './src/index.ts',
vendor: './src/vendor.ts'
},
module: {
rules: [
{
test: /\.tsx?$/,
use: [
{
loader: 'ts-loader',
options: {
transpileOnly: true,
configFile: 'tsconfig.json'
}
}
],
exclude: /node_modules/,
},
{
test: /\.css$/i,
use: [
isProduction ? MiniCssExtractPlugin.loader : 'style-loader',
'css-loader'
],
},
{
test: /\.(png|svg|jpg|jpeg|gif)$/i,
type: 'asset/resource',
},
{
test: /\.(woff|woff2|eot|ttf|otf)$/i,
type: 'asset/resource',
},
],
},
resolve: {
extensions: ['.tsx', '.ts', '.js'],
alias: {
'@': path.resolve(__dirname, 'src'),
'@components': path.resolve(__dirname, 'src/components'),
'@utils': path.resolve(__dirname, 'src/utils'),
},
},
output: {
filename: isProduction ? '[name].[contenthash].js' : '[name].js',
path: path.resolve(__dirname, 'dist'),
clean: true,
publicPath: '/',
},
optimization: {
splitChunks: {
chunks: 'all',
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
chunks: 'all',
},
},
},
minimizer: [
new TerserPlugin({
terserOptions: {
compress: {
drop_console: isProduction,
},
},
}),
new CssMinimizerPlugin(),
],
},
plugins: [
new HtmlWebpackPlugin({
template: './src/index.html',
minify: isProduction,
}),
...(isProduction ? [new MiniCssExtractPlugin({
filename: '[name].[contenthash].css',
})] : []),
],
devtool: isProduction ? 'source-map' : 'eval-source-map',
devServer: {
static: './dist',
hot: true,
port: 3000,
historyApiFallback: true,
},
};
};
3. Vite Configuration
Basic Vite Setup
// vite.config.ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import { resolve } from 'path';
export default defineConfig({
plugins: [react()],
resolve: {
alias: {
'@': resolve(__dirname, 'src'),
'@components': resolve(__dirname, 'src/components'),
'@utils': resolve(__dirname, 'src/utils'),
},
},
build: {
outDir: 'dist',
sourcemap: true,
rollupOptions: {
output: {
manualChunks: {
vendor: ['react', 'react-dom'],
utils: ['lodash', 'moment'],
},
},
},
},
server: {
port: 3000,
open: true,
},
});
Advanced Vite Configuration
// vite.config.ts
import { defineConfig, loadEnv } from 'vite';
import react from '@vitejs/plugin-react';
import { resolve } from 'path';
import { visualizer } from 'rollup-plugin-visualizer';
export default defineConfig(({ command, mode }) => {
const env = loadEnv(mode, process.cwd(), '');
return {
plugins: [
react(),
...(command === 'build' ? [visualizer()] : []),
],
resolve: {
alias: {
'@': resolve(__dirname, 'src'),
'@components': resolve(__dirname, 'src/components'),
'@utils': resolve(__dirname, 'src/utils'),
'@types': resolve(__dirname, 'src/types'),
},
},
define: {
__APP_VERSION__: JSON.stringify(process.env.npm_package_version),
__BUILD_TIME__: JSON.stringify(new Date().toISOString()),
},
build: {
outDir: 'dist',
sourcemap: true,
minify: 'terser',
terserOptions: {
compress: {
drop_console: true,
drop_debugger: true,
},
},
rollupOptions: {
output: {
manualChunks: {
vendor: ['react', 'react-dom'],
router: ['react-router-dom'],
utils: ['lodash', 'moment', 'axios'],
},
},
},
},
server: {
port: 3000,
open: true,
proxy: {
'/api': {
target: env.VITE_API_URL || 'http://localhost:8080',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, ''),
},
},
},
preview: {
port: 4173,
open: true,
},
};
});
4. Package.json Scripts
Development Scripts
{
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"preview": "vite preview",
"type-check": "tsc --noEmit",
"lint": "eslint src --ext .ts,.tsx",
"lint:fix": "eslint src --ext .ts,.tsx --fix",
"test": "jest",
"test:watch": "jest --watch",
"test:coverage": "jest --coverage",
"clean": "rimraf dist",
"prebuild": "npm run clean",
"postbuild": "npm run type-check"
}
}
Production Scripts
{
"scripts": {
"build:prod": "NODE_ENV=production npm run build",
"build:analyze": "npm run build && npx webpack-bundle-analyzer dist/stats.json",
"deploy": "npm run build:prod && npm run deploy:upload",
"deploy:upload": "aws s3 sync dist/ s3://my-bucket/",
"start": "node dist/index.js",
"start:prod": "NODE_ENV=production npm start"
}
}
5. ESLint Configuration
TypeScript ESLint Setup
// .eslintrc.json
{
"extends": [
"eslint:recommended",
"@typescript-eslint/recommended",
"@typescript-eslint/recommended-requiring-type-checking"
],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaVersion": 2020,
"sourceType": "module",
"project": "./tsconfig.json"
},
"plugins": ["@typescript-eslint"],
"rules": {
"@typescript-eslint/no-unused-vars": "error",
"@typescript-eslint/no-explicit-any": "warn",
"@typescript-eslint/explicit-function-return-type": "warn",
"@typescript-eslint/no-non-null-assertion": "error",
"@typescript-eslint/prefer-nullish-coalescing": "error",
"@typescript-eslint/prefer-optional-chain": "error"
}
}
6. Prettier Configuration
// .prettierrc
{
"semi": true,
"trailingComma": "es5",
"singleQuote": true,
"printWidth": 80,
"tabWidth": 2,
"useTabs": false,
"bracketSpacing": true,
"arrowParens": "avoid"
}
Practical Examples
1. Complete Build Setup
Project Structure
my-typescript-project/
├── src/
│ ├── components/
│ │ ├── Button.tsx
│ │ └── Modal.tsx
│ ├── utils/
│ │ ├── helpers.ts
│ │ └── constants.ts
│ ├── types/
│ │ └── index.ts
│ ├── App.tsx
│ └── index.ts
├── public/
│ └── index.html
├── dist/
├── node_modules/
├── package.json
├── tsconfig.json
├── vite.config.ts
├── .eslintrc.json
├── .prettierrc
└── README.md
Package.json
{
"name": "my-typescript-project",
"version": "1.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"preview": "vite preview",
"type-check": "tsc --noEmit",
"lint": "eslint src --ext .ts,.tsx",
"lint:fix": "eslint src --ext .ts,.tsx --fix",
"format": "prettier --write src/**/*.{ts,tsx}",
"test": "jest",
"test:watch": "jest --watch",
"clean": "rimraf dist"
},
"dependencies": {
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
"devDependencies": {
"@types/react": "^18.2.0",
"@types/react-dom": "^18.2.0",
"@typescript-eslint/eslint-plugin": "^6.0.0",
"@typescript-eslint/parser": "^6.0.0",
"@vitejs/plugin-react": "^4.0.0",
"eslint": "^8.45.0",
"eslint-plugin-react": "^7.32.0",
"eslint-plugin-react-hooks": "^4.6.0",
"jest": "^29.6.0",
"prettier": "^3.0.0",
"rimraf": "^5.0.0",
"typescript": "^5.1.0",
"vite": "^4.4.0"
}
}
2. Docker Configuration
Dockerfile
# Dockerfile
FROM node:18-alpine AS builder
WORKDIR /app
# Copy package files
COPY package*.json ./
# Install dependencies
RUN npm ci --only=production
# Copy source code
COPY . .
# Build the application
RUN npm run build
# Production stage
FROM nginx:alpine
# Copy built files
COPY --from=builder /app/dist /usr/share/nginx/html
# Copy nginx configuration
COPY nginx.conf /etc/nginx/nginx.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
Docker Compose
# docker-compose.yml
version: '3.8'
services:
app:
build: .
ports:
- "80:80"
environment:
- NODE_ENV=production
restart: unless-stopped
dev:
build: .
command: npm run dev
ports:
- "3000:3000"
volumes:
- .:/app
- /app/node_modules
environment:
- NODE_ENV=development
3. CI/CD Configuration
GitHub Actions
# .github/workflows/ci.yml
name: CI/CD Pipeline
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Type check
run: npm run type-check
- name: Lint
run: npm run lint
- name: Test
run: npm run test:coverage
- name: Build
run: npm run build
deploy:
needs: test
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
steps:
- uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Build
run: npm run build
- name: Deploy to S3
run: |
aws s3 sync dist/ s3://my-bucket/ --delete
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
Best Practices
1. Use Appropriate Build Tools
// Good: Use Vite for modern development
// vite.config.ts
export default defineConfig({
plugins: [react()],
build: {
target: 'esnext',
minify: 'terser',
},
});
// Good: Use Webpack for complex applications
// webpack.config.js
module.exports = {
optimization: {
splitChunks: {
chunks: 'all',
},
},
};
2. Optimize Bundle Size
// Good: Use tree shaking
import { debounce } from 'lodash-es';
// Avoid: Import entire library
import _ from 'lodash';
3. Use Source Maps for Debugging
// tsconfig.json
{
"compilerOptions": {
"sourceMap": true,
"declarationMap": true
}
}
4. Configure Path Mapping
// tsconfig.json
{
"compilerOptions": {
"baseUrl": "./src",
"paths": {
"@/*": ["*"],
"@components/*": ["components/*"]
}
}
}
Common Pitfalls and Solutions
1. Module Resolution Issues
// ❌ Problem: Module not found
import { helper } from './utils/helper';
// ✅ Solution: Use path mapping
import { helper } from '@utils/helper';
2. Build Performance Issues
// ❌ Problem: Slow builds
// webpack.config.js
module.exports = {
module: {
rules: [
{
test: /\.tsx?$/,
use: 'ts-loader', // Slow
exclude: /node_modules/,
},
],
},
};
// ✅ Solution: Use faster loaders
module.exports = {
module: {
rules: [
{
test: /\.tsx?$/,
use: 'swc-loader', // Faster
exclude: /node_modules/,
},
],
},
};
3. Type Declaration Issues
// ❌ Problem: Missing type declarations
// @types/missing-package/index.d.ts
declare module 'missing-package' {
export function doSomething(): void;
}
// ✅ Solution: Use proper type declarations
// @types/missing-package/index.d.ts
declare module 'missing-package' {
interface Options {
option1: string;
option2: number;
}
export function doSomething(options: Options): void;
}
Conclusion
Build tools and configuration in TypeScript are essential for creating production-ready applications. By understanding:
- What build tools and configuration are and their purposes
- Why they're important for code transformation and optimization
- How to configure TypeScript, Webpack, Vite, and other tools
You can create efficient build pipelines that transform TypeScript code into optimized JavaScript bundles. Proper configuration ensures type safety, performance, and maintainability throughout the development and deployment process.
Next Steps
- Practice configuring different build tools
- Explore advanced optimization techniques
- Learn about deployment strategies
- Move on to Chapter 13: Performance Optimization
This tutorial is part of the TypeScript Mastery series by syscook.dev