Setting Up a Basic Webpack Development Environment
- typescript
- javascript
- web
1. Introduction
I have taken on the role of managing the program at ICPC Sinchon and have decided to document my experiences, especially as I embark on this new project with many fresh ideas.
This article discusses the basic Webpack setup I worked on while creating an attendance-related page. Although the plan to allow students to enter their attendance codes ultimately failed, the effort put into the setup seems worthwhile to document as it may be revisited in the future.
1.1. Background
I have been involved with ICPC Sinchon for quite some time. I attended lectures there and eventually started teaching after gaining some skills.
While dining with the organizer of Sinchon Union Camp, I ended up assisting with various tasks. The first of these was to create a page for attendance in the algorithm course, designed by another member of the management team.
This document organizes the settings I established as development began.
1.2. Existing Setup
The existing page had necessary elements, and the design was decent. Since various components simply embedded images created with Figma, it was relatively clean.
However, the development environment and logic design were lacking. It was built using only HTML, CSS, and vanilla JavaScript, with CSS elements created through inserted images. Libraries were utilized via CDN <script>
tags, resulting in significant bundle size waste. For instance, the entire bootstrap
was used just for a navigation bar.
There was no setup for a development server, and distribution was scattered, with user pages hosted on Firebase and admin pages on repl.it.
I aim to refactor this into a better structure by introducing React and TypeScript, using a package manager, and enabling deployment through builds. I also plan to incorporate testing later.
Rather than starting with complete knowledge, I may not cover everything. Nonetheless, I will document the process here.
We could entirely overhaul it using project templates like create-vite
, but since the original code was authored and already in use, I want to preserve as many original choices and structures as possible.
Thus, I will conduct project settings for Webpack almost from scratch and will address project setup in this article.
1.3. Plan
Objectives for this article include:
- Setting up the development environment
- Introducing the popular stack React and TypeScript
2. Basic Setup
First, I configured the following in each project.
2.1. Package Manager Configuration
Start the project setup in the project folder with npm init
. This will create a basic package.json
.
{
"name": "sinchonattendance",
"version": "1.0.0",
"description": "### ...",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC"
}
2.2. Installing ESLint
Next, let's install eslint
, a tool for static code analysis. You can quickly start using npm init @eslint/config
. It will prompt several questions, generating a .eslintrc.json
file. I selected the following options since I will be using TypeScript and running the code in the browser:
npm init @eslint/config
✔ How would you like to use ESLint? · problems
✔ What type of modules does your project use? · esm
✔ Which framework does your project use? · none
✔ Does your project use TypeScript? · No / Yes
✔ Where does your code run? · browser
✔ What format do you want your config file to be in? · JSON
Additionally, I chose to use yarn
as the package manager.
Alternatively, a more modern tool called Biome could be used for static analysis. I documented this process in Basic React Project Setup. However, for the sake of basic setup, I will use eslint
.
3. Basic Webpack Configuration
3.1. Installing Webpack and Basic Configuration
Now, let's install the bundler. I will use the classic Webpack. As of 2024, even the latest framework nextjs still utilizes Webpack, indicating it is not an obsolete technology.
webpack
must be installed alongside webpack-cli
.
yarn add webpack webpack-cli --dev
Next, create source files. Initially, create dist/index.html
and src/index.js
in the project root.
dist/index.html
will be included in the build results later.
Then, delete the entry point from package.json
and set private to true to prevent accidental publishing.
{
"name": "sinchonattendance",
"version": "1.0.0",
"description": "### ...",
"main": "index.js", // delete
"private": true, // add
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
// ...
}
By default, the Webpack bundling result is main.js
. Update dist/index.html
to call it.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Getting Started</title>
</head>
<body>
<script src="main.js"></script>
</body>
</html>
Running npx webpack
generates dist/main.js
, which is called from dist/index.html
.
3.2. Creating a Configuration File
Now, let's create a configuration file that will be read during the execution of npx webpack
. Generate webpack.config.js
in the project root.
In webpack commands, you can specify a different configuration file using the --config
option, but webpack.config.js
is the default name. For convenience, we will use this name.
Add only the mode
in line with the example in the Webpack documentation, as follows:
// webpack.config.js
const path = require("path");
module.exports = {
mode: "development",
entry: "./src/index.js",
output: {
filename: "main.js",
path: path.resolve(__dirname, "dist"),
},
};
entry
represents the starting point for Webpack to bundle files, and output
sets configurations for the bundled files. In short, it means to build in development mode, bundling src/index.js
into dist/main.js
.
Let’s also add a build command to package.json
.
{
// ...
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "webpack"
},
// ...
}
This allows you to run yarn build
to create dist/main.js
.
4. TypeScript Configuration
Next, let’s install TypeScript and create tsconfig.json
using tsc --init
.
yarn add typescript --dev
npx tsc --init
Then set tsconfig.json
as follows. This follows a slightly modified version of vite's react-ts settings.
{
"compilerOptions": {
"target": "ES2020",
"useDefineForClassFields": true,
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"module": "ES6",
"skipLibCheck": true,
"moduleResolution": "Node",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx",
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"typeRoots": ["src/types"],
"outDir": "./dist/"
},
"include": ["src"]
}
5. React Setup
5.1. Library Installation
Install the libraries required to use React.
yarn add react react-dom
yarn add -D webpack-dev-server html-webpack-plugin
yarn add -D @types/react @types/react-dom
yarn add -D ts-loader
5.2. Modifying Webpack Configuration
Next, modify webpack.config.js
to include basic loaders and plugins.
Update Webpack settings to handle TypeScript. In module.rules
, add rules for .ts
and .tsx
to be processed by ts-loader
. Also, add .ts
and .tsx
to resolve.extensions
. Set ./src/index.ts
as the entry point, and configure the output file to be ./dist/bundle.js
.
const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
module.exports = {
mode: "development",
entry: "./src/index.tsx",
output: {
filename: "bundle.js",
path: path.resolve(__dirname, "dist"),
clean: true,
},
resolve: {
modules: [path.resolve(__dirname, "src"), "node_modules"],
extensions: [".tsx", ".ts", ".js", ".jsx", ".json", ".css"],
},
module: {
rules: [
{
test: /\.(ts|tsx)$/,
use: "ts-loader",
exclude: /node_modules/,
},
],
},
plugins: [
new HtmlWebpackPlugin({
title: "Sinchon Attendance Development",
template: "./src/index.html",
}),
]
};
5.3. Importing Assets
We will also configure Webpack to handle CSS and images. Install style-loader
and css-loader
.
yarn add style-loader css-loader --dev
Then, reference the Asset Management official documentation to modify the rules
in the module
property of webpack.config.js
as follows:
const path = require("path");
module.exports = {
// ...
module: {
rules: [
{
test: /\.tsx?$/,
use: "ts-loader",
exclude: /node_modules/,
},
{
test: /\.css$/i,
use: ["style-loader", "css-loader"],
},
{
test: /\.(png|svg|jpg|jpeg|gif)$/i,
type: "asset/resource",
},
],
},
// ...
};
5.4. Setting Up the Development Server
To confirm everything is working, write a basic React code.
// index.tsx
import "./style.css";
import App from "./App";
import React from "react";
import ReactDOM from "react-dom/client";
ReactDOM.createRoot(document.getElementById("root")!).render(
<React.StrictMode>
<App />
</React.StrictMode>
);
The index.html
document should look like this:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no" />
<meta property="og:image" content="./image/240.png" />
<title>Algorithm Camp Attendance</title>
</head>
<body>
<div id="root"></div>
</body>
</html>
Now, start the development server to ensure everything displays correctly. With webpack-dev-server
installed, you can add the following command to package.json
.
{
// ...
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"dev": "webpack serve --mode development --open --hot",
"build": "webpack --mode production"
},
// ...
}
Additionally, append the devServer
property in webpack.config.js
.
{
// ...
devServer: {
static: {
directory: path.join(__dirname, "dist"),
},
compress: true,
port: 9000,
},
}
Now, running the command yarn dev
will start the development server at localhost:9000
.
If there are multiple entry points, set optimization.runtimeChunk
to single
.
module.exports = {
// ...
optimization: {
runtimeChunk: "single",
},
// ...
};
However, since we only have one entry point, this option is not necessary.
Note that webpack-dev-server
serves the bundled files from the directory defined in output.path
. For example, specific files will be accessible via the following address:
http://[devServer.host]:[devServer.port]/[output.publicPath]/[output.filename]
6. Hot Module Replacement Configuration for the Development Server
Adding Hot Module Replacement (HMR) to the development server environment increases efficiency during development. HMR allows you to avoid refreshing the entire page to reflect changes saved during development.
6.1. Basic HMR Setup
Traditionally, react-hot-loader
was commonly used. However, React's fast refresh is considered more versatile and favorable. Therefore, we will utilize react-refresh
.
yarn add -D @pmmmwh/react-refresh-webpack-plugin react-refresh
Since I am using ts-loader
, I will also install react-refresh-typescript
as per the react-refresh official documentation.
yarn add -D react-refresh-typescript
Next, modify webpack.config.js
as follows, based on a slight modification of the official documentation's example.
const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin');
const ReactRefreshTypeScript = require('react-refresh-typescript');
const isDevelopment = process.env.NODE_ENV !== 'production';
module.exports = {
mode: isDevelopment ? 'development' : 'production',
module: {
rules: [
{
test: /\.(ts|tsx)$/,
use: [
{
loader: require.resolve("ts-loader"),
options: {
getCustomTransformers: () => ({
before: [isDevelopment && ReactRefreshTypeScript()].filter(Boolean),
}),
transpileOnly: isDevelopment,
},
},
],
exclude: /node_modules/,
},
// Other plugin settings...
],
},
plugins: [isDevelopment && new ReactRefreshWebpackPlugin()].filter(Boolean),
};
However, this will cause ts-loader
to use transpileOnly
in development mode, meaning type checking will not occur. We will use the fork-ts-checker-webpack-plugin
to run type checks concurrently.
yarn add --dev fork-ts-checker-webpack-plugin
Then add the plugin to your Webpack configuration file as follows:
module.exports = {
plugins: [
new HtmlWebpackPlugin({
title: "Sinchon Attendance Development",
template: "./src/index.html",
}),
isDevelopment && new ReactRefreshWebpackPlugin(),
new ForkTsCheckerWebpackPlugin(),
].filter(Boolean),
watchOptions: {
ignored: /node_modules/,
},
};
Once configured, you can launch yarn dev
to run the development server with HMR enabled. Further optimizations like minification can be applied later after making some progress in development.
7. Additional Configurations
7.1. Image Loading
Currently, Webpack may have trouble locating image paths. To resolve this, you’ll need to configure types. Create src/types/custom.d.ts
and write the following:
declare module "*.png";
declare module "*.jpg";
declare module "*.jpeg";
Then add the following to tsconfig.json
:
{
"compilerOptions": {
"typeRoots": ["src/types"],
// ...
},
// ...
}
7.2. CSS Plugin Configuration
Let’s set up plugins for CSS compression and modularization.
yarn add -D mini-css-extract-plugin css-minimizer-webpack-plugin
Now, modify the Webpack configuration file accordingly. Since mini-css-extract-plugin
cannot be used alongside style-loader
, configure the use of style-loader
in development mode and mini-css-extract-plugin
in production mode.
The Webpack settings will look like this:
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
module.exports = {
module: {
rules: [
{
test: /\.css$/,
use: [
isDevelopment ? "style-loader" : MiniCssExtractPlugin.loader,
"css-loader",
],
},
],
},
optimization: {
minimizer: [
new CssMinimizerPlugin(),
],
},
plugins: [
new HtmlWebpackPlugin({
title: "Sinchon Attendance Development",
template: "./src/index.html",
}),
isDevelopment && new ReactRefreshWebpackPlugin(),
new ForkTsCheckerWebpackPlugin(),
!isDevelopment && new MiniCssExtractPlugin(),
].filter(Boolean),
};
At this point, you should have enough basic setup to proceed with development, with reasonably minified build outputs.
References
Starting Vanilla TypeScript - Project Environment Setup
Installing ESLint and Configuration Guide
https://lakelouise.tistory.com/199
Getting Started with Webpack
https://webpack.kr/guides/getting-started
Webpack TypeScript Guide
https://webpack.kr/guides/typescript/
Webpack Asset Management
https://webpack.kr/guides/asset-management/
Webpack Output Management
https://webpack.kr/guides/output-management/
Webpack HTML-Loader
https://webpack.js.org/loaders/html-loader/
Webpack DevServer
https://webpack.kr/configuration/dev-server/
Setting Up Webpack 5 by Zero Cho
https://www.zerocho.com/category/Webpack/post/58aa916d745ca90018e5301d
Understanding the package.json scripts
Section
https://programmingsummaries.tistory.com/385
TypeScript 'Cannot find module ...' Error
https://chiabi.github.io/2018/08/30/typescript/
Webpack Introduction: Using HTML and CSS
Integrating Firebase with React TypeScript
React Refresh Webpack Plugin
https://github.com/pmmmwh/react-refresh-webpack-plugin/
TypeScript TS2307: Cannot find module '.png' or its corresponding type declarations
Official Plugin Documentation
https://www.npmjs.com/package/css-minimizer-webpack-plugin
https://www.npmjs.com/package/mini-css-extract-plugin
Webpack (Webpack + TypeScript + React)