Building a React/TypeScript Development Environment with Webpack and Babel

A step-by-step guide to setting up a React and TypeScript development environment from scratch using Webpack and Babel, with detailed configuration file explanations.

This article explains how to build a modern web application development environment using React and TypeScript from scratch with webpack and Babel.

What is Webpack?

Webpack is a module bundler for JavaScript applications. It analyzes multiple JavaScript files and other assets (CSS, images, etc.) based on their dependencies and bundles them into single or multiple files that can be executed in the browser.

  • Key Features:
    • Module resolution: Interprets import and require syntax and resolves dependencies.
    • Bundling: Combines multiple files into one.
    • Transpilation: Works with loaders like Babel to convert new JavaScript syntax for older browsers.
    • Optimization: Performs optimizations like code minification and dead code elimination.

What is Babel?

Babel is a JavaScript transcompiler. It is primarily used to convert code written in the latest JavaScript (ES2015+) into compatible JavaScript (ES5, etc.) that runs in older browsers and execution environments.

  • Key Features:
    • Syntax conversion: Converts async/await, arrow functions, class syntax, etc.
    • JSX conversion: Converts React’s JSX syntax to regular JavaScript.
    • TypeScript conversion: Converts TypeScript code to JavaScript (does not perform type checking).

Environment Setup

Installing Required Modules

Create a project directory and initialize with npm.

mkdir react_test
cd react_test

# Initialize the project
npm init -y

# Babel-related modules
npm install -D @babel/core          # Babel core
npm install -D @babel/preset-env    # Convert latest JS syntax for target environments
npm install -D @babel/preset-react  # Convert React JSX
npm install -D @babel/preset-typescript # Convert TypeScript

# Webpack-related modules
npm install -D webpack webpack-cli babel-loader ts-loader # Webpack core, CLI, and loaders for Babel/TS
npm install -D webpack-dev-server html-webpack-plugin     # Dev server and HTML generation plugin

# React and TypeScript modules
npm install react react-dom         # React core and DOM library
npm install -D typescript @types/react @types/react-dom # TypeScript and React/ReactDOM type definitions

# Generate tsconfig.json
npx tsc --init

File Structure

The final file structure will look like this.

.
├── dist/
│   ├── index.html
│   └── main.js
├── node_modules/
├── package-lock.json
├── package.json
├── src/
│   ├── index.html
│   └── index.tsx
├── tsconfig.json
└── webpack.config.js

Configuration Files

src/index.html

The HTML file that serves as the entry point for the React application. React components are mounted on the div element.

<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>React App</title>
  </head>
  <body>
    <div id="app"></div>
    <!-- Bundled JavaScript by Webpack is automatically injected here -->
  </body>
</html>

src/index.tsx

The main entry file for the React application, written in TypeScript and JSX.

import React from "react";
import { createRoot } from "react-dom/client"; // Use createRoot from React 18

const App = () => {
  return <h1>Hello React with TypeScript!</h1>;
};

// Use the new React 18 API
const container = document.getElementById("app");
if (container) {
  const root = createRoot(container);
  root.render(<App />);
}

.babelrc

The Babel configuration file. Defines which presets (sets of plugins) are used to transform the code.

{
  "presets": [
    "@babel/preset-env",          // Convert latest JS for target environments
    "@babel/preset-react",        // Convert React JSX
    "@babel/preset-typescript"    // Convert TypeScript (no type checking)
  ]
}

package.json

Defines project metadata, scripts, and dependencies. Add the dev server startup command and build command to the scripts section.

{
  "name": "react_test",
  "version": "1.0.0",
  "description": "React with TypeScript development environment",
  "main": "index.js",
  "scripts": {
    "start": "webpack serve --open",  // Start dev server and auto-open browser
    "build": "webpack --mode production" // Build for production
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "@babel/core": "^7.20.2",
    "@babel/preset-env": "^7.20.2",
    "@babel/preset-react": "^7.18.6",
    "@babel/preset-typescript": "^7.18.6",
    "@types/react": "^18.0.25",
    "@types/react-dom": "^18.0.8",
    "babel-loader": "^9.1.0",
    "html-webpack-plugin": "^5.5.0",
    "ts-loader": "^9.4.1",
    "typescript": "^4.8.4",
    "webpack": "^5.75.0",
    "webpack-cli": "^4.10.0",
    "webpack-dev-server": "^4.11.1"
  },
  "dependencies": {
    "react": "^18.2.0",
    "react-dom": "^18.2.0"
  }
}

tsconfig.json

The TypeScript compiler configuration file. Defines the rules for converting TypeScript code to JavaScript.

{
  "compilerOptions": {
    "target": "es2015",                         // Output JavaScript version
    "module": "esnext",                         // Module system (esnext is flexible for Webpack)
    "jsx": "react-jsx",                         // JSX conversion method (React 17+ new JSX transform)
    "strict": true,                             // Enable strict type checking
    "esModuleInterop": true,                    // Improve CommonJS and ES module compatibility
    "skipLibCheck": true,                       // Skip type checking for declaration files (faster builds)
    "forceConsistentCasingInFileNames": true,   // Enforce consistent file name casing
    "moduleResolution": "node",                 // Module resolution strategy
    "resolveJsonModule": true,                 // Allow JSON file imports
    "isolatedModules": true,                    // Compile each file as an independent module
    "noEmit": true,                             // Don't output JS from TypeScript (Babel/Webpack handles this)
    "allowJs": true,                            // Allow compiling JavaScript files
    "lib": ["dom", "dom.iterable", "esnext"]    // Available libraries
  },
  "include": ["src"]                            // Files to compile
}

webpack.config.js

The Webpack bundling configuration file. Defines the entry point, output, loaders, and plugins.

const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");

module.exports = {
  // Set to development mode (enables optimization and debug info)
  mode: "development",
  // Entry point (application starting point)
  entry: "./src/index.tsx",
  // Output configuration
  output: {
    path: path.resolve(__dirname, "dist"), // Output directory
    filename: "bundle.js",                 // Output filename
    clean: true,                           // Clean dist directory before build
  },
  // Module resolution settings
  resolve: {
    extensions: [".ts", ".tsx", ".js", ".jsx"], // Extensions to resolve on import
  },
  // Module rules (loader configuration)
  module: {
    rules: [
      {
        test: /\.(ts|tsx)$/, // Apply to .ts or .tsx files
        exclude: /node_modules/, // Exclude node_modules
        use: {
          loader: "babel-loader", // Transform using Babel
          options: {
            presets: [
              "@babel/preset-env",
              "@babel/preset-react",
              "@babel/preset-typescript",
            ],
          },
        },
      },
      // Loaders for CSS, images, etc. can be added here
    ],
  },
  // Plugin configuration
  plugins: [
    new HtmlWebpackPlugin({
      template: "./src/index.html", // HTML file to use as template
      filename: "index.html",       // Output HTML filename
    }),
  ],
  // Dev server configuration
  devServer: {
    static: {
      directory: path.join(__dirname, "dist"), // Static file directory
    },
    compress: true, // Enable gzip compression
    port: 3000,     // Dev server port
    open: true,     // Auto-open browser on server start
    hot: true,      // Enable Hot Module Replacement
  },
  // Source map configuration (for debugging)
  devtool: "eval-source-map",
};

Once all these configuration files are in place, you can start the dev server with npm start and create a production build with npm run build.