# Webpack development workflow
Build tooling should shorten the feedback loop without changing the application's basic contract. Extend the minimal build, rather than keeping unrelated development and production applications.
# Build, watch, or serve?
With webpack-dev-server 5 added as a development dependency, these scripts have distinct jobs:
{
"scripts": {
"typecheck": "tsc --noEmit",
"build": "webpack --config webpack.config.cjs --mode production",
"watch": "webpack --config webpack.config.cjs --mode development --watch",
"dev": "webpack serve --config webpack.config.cjs --mode development"
}
}
buildemits production assets and exits.watchrebuilds when a dependency changes; it does not provide a web server.devserves the application and rebuilds it. Generated assets are served from memory by default, so a live page does not prove fresh files exist indist.
Source maps connect generated code to its TypeScript source in DevTools. They help explain errors but do not change the code's behavior. Production mode enables optimizations such as minification; it is not a substitute for tests or type checks.
# Importing CSS
Add css-loader and style-loader as development dependencies. Append this rule to module.rules in webpack.config.cjs:
{
test: /\.css$/i,
use: ['style-loader', 'css-loader']
}
Loaders in this chain run right to left: css-loader processes CSS imports/references and style-loader injects styles into the document. In src/index.ts, add import './styles.css';; create src/styles.css, for example:
#root {
font-family: system-ui, sans-serif;
white-space: pre-line;
}
If TypeScript is configured to check side-effect imports, a src/assets.d.ts declaration can describe this kind of import:
declare module '*.css';
That declaration does not load CSS; Webpack's loaders do. Extracting CSS into separate production files requires a different loader/plugin setup and is a later optimization.
# Calling an API through a dev proxy
Run the Koa API separately on port 3000. Add the following property to the Webpack configuration (the array syntax here targets webpack-dev-server 5):
devServer: {
host: '127.0.0.1',
port: 9000,
proxy: [
{
context: ['/api'],
target: 'http://127.0.0.1:3000'
}
]
}
browser at localhost:9000
fetch('/api/students')
→ development server at port 9000
→ Koa at port 3000, still GET /api/students
← JSON [{ "id": 1, "name": "Ada" }]
There is no path rewrite here: the frontend URL and backend route deliberately match. A pathRewrite that strips /api would instead require a backend /students route. Do not mix the older object-shaped proxy configuration with the version shown here.
The browser sends a same-origin request to its development server; that server forwards it to the API. This is not browser CORS enforcement being disabled, and it is not authorization. Production still needs an explicit reverse-proxy/API deployment or correctly configured cross-origin access.
The fixed JSON response is sufficient as a small mock API while building the frontend. It models a response contract, not persistent storage or production behavior.
# Minification is not compression
Minification rewrites JavaScript to reduce its size. HTTP compression transfers a compressed response; generating .gz assets at build time is yet another step, and a server must serve them with the right headers. Enabling dev-server compression alone does not optimize the application's source or configure production hosting.
Try it: stop the API server and inspect the failed request in Network. Then restart it and verify the JSON shape before passing it into the typed application.
Dev-server configuration (opens new window) · CSS loader (opens new window)