Skip to main content
Advertisement

9.3 Bundler Integration — Vite, webpack, Rollup + TypeScript

Bundlers and TypeScript

Bundlers combine multiple modules into one (or several) files. Using them with TypeScript requires TS → JS transformation configuration.

BundlerTS ProcessingCharacteristics
Viteesbuild (built-in)Fast dev server, HMR
webpackts-loader / babel-loaderMature ecosystem, complex config
Rollup@rollup/plugin-typescriptOptimal for library bundling
ParcelBuilt-in (auto-detect)Zero config, beginner-friendly

Vite + TypeScript

Creating a Project

npm create vite@latest my-app -- --template react-ts
# or Vue
npm create vite@latest my-app -- --template vue-ts
# or vanilla TS
npm create vite@latest my-app -- --template vanilla-ts

Generated tsconfig Structure

// tsconfig.json (for app code)
{
"compilerOptions": {
"target": "ES2020",
"useDefineForClassFields": true,
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"module": "ESNext",
"skipLibCheck": true,
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true, // Vite handles bundling, tsc doesn't emit
"jsx": "react-jsx",
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true
},
"include": ["src"],
"references": [{ "path": "./tsconfig.node.json" }]
}
// tsconfig.node.json (for vite.config.ts and Node.js tools)
{
"compilerOptions": {
"composite": true,
"skipLibCheck": true,
"module": "ESNext",
"moduleResolution": "bundler",
"allowSyntheticDefaultImports": true,
"strict": true
},
"include": ["vite.config.ts"]
}

Key vite.config.ts Settings

import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import path from 'path';

export default defineConfig({
plugins: [react()],

// Path aliases
resolve: {
alias: {
'@': path.resolve(__dirname, './src'),
},
},

// Build settings
build: {
target: 'es2020',
outDir: 'dist',
sourcemap: true,
rollupOptions: {
output: {
manualChunks: {
vendor: ['react', 'react-dom'],
router: ['react-router-dom'],
},
},
},
},

// Environment variable prefix
envPrefix: 'VITE_',
});

Environment Variable Type Definitions in Vite

// src/vite-env.d.ts
/// <reference types="vite/client" />

interface ImportMetaEnv {
readonly VITE_API_URL: string;
readonly VITE_APP_TITLE: string;
}

interface ImportMeta {
readonly env: ImportMetaEnv;
}
// Usage
const apiUrl = import.meta.env.VITE_API_URL; // string

webpack + TypeScript

Installation

npm install --save-dev webpack webpack-cli ts-loader typescript

webpack.config.ts

import path from 'path';
import webpack from 'webpack';
import HtmlWebpackPlugin from 'html-webpack-plugin';

const config: webpack.Configuration = {
entry: './src/index.tsx',
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].[contenthash].js',
clean: true,
},
resolve: {
extensions: ['.tsx', '.ts', '.js'],
alias: {
'@': path.resolve(__dirname, 'src'),
},
},
module: {
rules: [
{
test: /\.(ts|tsx)$/,
use: 'ts-loader',
exclude: /node_modules/,
},
{
test: /\.css$/,
use: ['style-loader', 'css-loader'],
},
],
},
plugins: [
new HtmlWebpackPlugin({
template: './public/index.html',
}),
],
optimization: {
splitChunks: {
chunks: 'all',
},
},
};

export default config;

ts-loader vs babel-loader Comparison

ts-loader (recommended: includes type checking):

{
test: /\.tsx?$/,
use: 'ts-loader',
exclude: /node_modules/,
}

babel-loader + @babel/preset-typescript (no type checking, faster):

npm install --save-dev babel-loader @babel/core @babel/preset-typescript @babel/preset-env
{
test: /\.tsx?$/,
use: {
loader: 'babel-loader',
options: {
presets: [
'@babel/preset-env',
'@babel/preset-typescript',
['@babel/preset-react', { runtime: 'automatic' }],
],
},
},
}

Optimizing ts-loader

{
test: /\.tsx?$/,
use: [
{
loader: 'ts-loader',
options: {
transpileOnly: true, // Skip type checking (faster)
// Type checking handled by fork-ts-checker-webpack-plugin
},
},
],
}
npm install --save-dev fork-ts-checker-webpack-plugin
import ForkTsCheckerWebpackPlugin from 'fork-ts-checker-webpack-plugin';

{
plugins: [
new ForkTsCheckerWebpackPlugin(), // Type check in separate process
],
}

Rollup + TypeScript

Ideal for library publishing. Excellent tree-shaking.

Installation

npm install --save-dev rollup @rollup/plugin-typescript rollup-plugin-dts

rollup.config.ts

import { defineConfig } from 'rollup';
import typescript from '@rollup/plugin-typescript';
import dts from 'rollup-plugin-dts';
import resolve from '@rollup/plugin-node-resolve';
import commonjs from '@rollup/plugin-commonjs';

export default defineConfig([
// Main build (CJS + ESM simultaneous output)
{
input: 'src/index.ts',
external: ['react', 'react-dom'], // Exclude peer dependencies
output: [
{
file: 'dist/index.cjs',
format: 'cjs',
sourcemap: true,
},
{
file: 'dist/index.mjs',
format: 'es',
sourcemap: true,
},
],
plugins: [
resolve(),
commonjs(),
typescript({ tsconfig: './tsconfig.json' }),
],
},
// Bundle type declaration files
{
input: 'dist/types/index.d.ts',
output: [{ file: 'dist/index.d.ts', format: 'es' }],
plugins: [dts()],
},
]);

package.json Library Configuration

{
"name": "my-library",
"version": "1.0.0",
"main": "./dist/index.cjs",
"module": "./dist/index.mjs",
"types": "./dist/index.d.ts",
"exports": {
".": {
"import": "./dist/index.mjs",
"require": "./dist/index.cjs",
"types": "./dist/index.d.ts"
}
},
"scripts": {
"build": "rollup -c rollup.config.ts --configPlugin typescript",
"typecheck": "tsc --noEmit"
},
"peerDependencies": {
"react": ">=18"
}
}

Parcel + TypeScript

TypeScript works without any configuration.

npm install --save-dev parcel
{
"scripts": {
"dev": "parcel src/index.html",
"build": "parcel build src/index.html"
}
}

Parcel automatically detects tsconfig.json. Best for small projects or prototyping.


The isolatedModules Option

An important option when using bundlers.

{
"compilerOptions": {
"isolatedModules": true
}
}

Forces each file to be transpilable independently. esbuild, swc, and Babel process files individually, so this option is needed.

// Patterns that violate isolatedModules

// ❌ const enum cannot be inlined from other files
const enum Direction { Up, Down } // Error

// ❌ Type re-exports need 'export type'
export { SomeType }; // Error
export type { SomeType }; // ✅

// ❌ Namespaces must contain only types
namespace MyNs {
export const value = 1; // Error (contains runtime value)
}

Common Bundle Optimization Strategies

Enabling Tree-shaking

// ✅ Named exports (tree-shakable)
export function add(a: number, b: number) { return a + b; }
export function subtract(a: number, b: number) { return a - b; }

// ❌ Default export object (harder to tree-shake)
export default { add, subtract };

Code Splitting with Dynamic Imports

// Load on demand (lazy loading)
const { PDFViewer } = await import('./components/PDFViewer');

// In React
const LazyComponent = React.lazy(() => import('./HeavyComponent'));

Pro Tips

Bundler selection guide

New React/Vue project      → Vite (fastest dev experience)
Legacy CRA project → webpack (maintain existing config)
Library publishing → Rollup (best tree-shaking)
Server-side Node.js → esbuild directly (fastest)
Prototype/small project → Parcel (zero config)

Separation of tsc and bundler responsibilities

tsc:      Type checking only (noEmit: true)
Bundler: Transpilation + bundling + optimization
Advertisement