Webpack · Part 5 — Dev Server, HMR & Source Maps
The fast feedback loop: webpack-dev-server, Hot Module Replacement that preserves state, and choosing the right devtool source-map mode for development vs production. With an interactive HMR and source-map lab.
webpack --watch rebuilds on save, but you still have to reload the browser yourself, and you lose all your app state {webpack --watch build lại khi lưu, nhưng bạn vẫn phải tự tải lại trình duyệt, và mất hết state ứng dụng}. The dev server fixes both, and source maps let you debug your original code instead of the bundle {Dev server sửa cả hai, và source map cho bạn gỡ lỗi code gốc thay vì bundle}. Together they make day-to-day development pleasant {Cùng nhau chúng làm phát triển hằng ngày dễ chịu}.
Compare full reload vs HMR, and explore source-map modes below {So sánh reload toàn trang vs HMR, và khám phá các chế độ source-map bên dưới}:
1. webpack-dev-server {webpack-dev-server}
A small Express server that builds in memory (nothing written to disk), serves your app, and live-reloads on change {Một server Express nhỏ build trong bộ nhớ (không ghi xuống đĩa), phục vụ app, và tự tải lại khi thay đổi}:
npm install --save-dev webpack-dev-server
// webpack.config.js
module.exports = {
mode: "development",
devServer: {
static: "./dist",
port: 3000,
open: true, // open the browser
hot: true, // Hot Module Replacement
historyApiFallback: true, // SPA routing → always serve index.html
},
};
{ "scripts": { "dev": "webpack serve --mode development" } }
Note webpack serve, not webpack {Lưu ý webpack serve, không phải webpack}. Because it builds in memory, the dev server is far faster than writing dist/ each time {Vì build trong bộ nhớ, dev server nhanh hơn nhiều so với ghi dist/ mỗi lần}.
2. Useful devServer options {Tùy chọn devServer hữu ích}
devServer: {
port: 3000,
hot: true,
open: true,
historyApiFallback: true, // for client-side routing
compress: true, // gzip
proxy: [ // forward API calls, dodge CORS in dev
{ context: ["/api"], target: "http://localhost:8080" },
],
client: { overlay: true }, // show errors as a browser overlay
}
proxy is the one you’ll reach for constantly — it forwards /api/* to your backend so you avoid CORS during development {proxy là cái bạn sẽ dùng liên tục — nó chuyển tiếp /api/* tới backend để bạn tránh CORS khi phát triển}.
3. Hot Module Replacement {Hot Module Replacement}
HMR swaps just the changed module into the running app without a full reload, preserving state {HMR hoán đổi chỉ module đã thay đổi vào app đang chạy mà không reload toàn trang, giữ nguyên state}. Edit a component and the page updates while your form inputs, scroll position, and counters stay put {Sửa một component và trang cập nhật trong khi input form, vị trí cuộn, và bộ đếm vẫn nguyên}.
Run the demo’s two panels: increment both counters, then “edit a module” {Chạy hai panel của demo: tăng cả hai bộ đếm, rồi “sửa một module”}. The non-HMR side does a full reload and resets to 0; the HMR side keeps the count {Bên không-HMR reload toàn trang và reset về 0; bên HMR giữ con số}.
For plain JS you sometimes accept a module’s update explicitly {Với JS thuần đôi khi bạn chấp nhận cập nhật của module một cách tường minh}:
if (module.hot) {
module.hot.accept("./render.js", () => rerender());
}
Frameworks wire this up for you — React Fast Refresh, Vue HMR — so you rarely write it by hand {Framework đã nối sẵn cho bạn — React Fast Refresh, Vue HMR — nên hiếm khi bạn tự viết}.
4. Source maps — debug original code {Source map — gỡ lỗi code gốc}
The bundle the browser runs is transformed and minified — useless for debugging {Bundle trình duyệt chạy đã bị biến đổi và minify — vô dụng để gỡ lỗi}. A source map maps bundle positions back to your original files, so DevTools shows src/user.ts:14 instead of main.min.js:1:48213 {Một source map ánh xạ vị trí bundle về file gốc, nên DevTools hiện src/user.ts:14 thay vì main.min.js:1:48213}.
You control it with devtool {Bạn điều khiển bằng devtool}. Switch modes in the demo to see the stack trace and the build/quality trade-off change {Chuyển chế độ trong demo để thấy stack trace và đánh đổi tốc độ/chất lượng đổi}.
5. Choosing a devtool {Chọn một devtool}
| Value | Speed | Use {Dùng} |
|---|---|---|
false | fastest | no maps {không map} |
eval-cheap-module-source-map | fast | development default {mặc định dev} |
inline-source-map | slow | maps embedded in bundle {map nhúng trong bundle} |
source-map | slowest | production (separate .map file) {production} |
For dev, eval-cheap-module-source-map balances rebuild speed and usable line numbers {Cho dev, eval-cheap-module-source-map cân bằng tốc độ build lại và số dòng dùng được}. For production, use source-map — it emits a separate .map file you upload to your error tracker (Sentry) but do not serve to users {Cho production, dùng source-map — nó phát ra file .map riêng bạn tải lên trình theo dõi lỗi (Sentry) nhưng không phục vụ cho user}.
6. The dev workflow {Quy trình dev}
npm run dev # webpack serve — in-memory build, HMR, source maps
Edit a file → Webpack rebuilds only the affected modules → HMR pushes the update over a WebSocket → the browser swaps the module, no reload {Sửa file → Webpack build lại chỉ module liên quan → HMR đẩy cập nhật qua WebSocket → trình duyệt hoán module, không reload}. Error overlay shows compile/runtime errors right in the page {Lớp phủ lỗi hiện lỗi biên dịch/chạy ngay trong trang}. That’s the loop you live in all day {Đó là vòng lặp bạn sống cả ngày}.
7. Exercises {Bài tập}
1. You run webpack serve and get a blank page on a /dashboard route refresh (404). Which devServer option fixes it? {Bạn chạy webpack serve và bị trang trắng khi refresh route /dashboard (404). Tùy chọn devServer nào sửa?}
Solution {Lời giải}
historyApiFallback: true — serves index.html for unknown routes so the SPA router handles them {historyApiFallback: true — phục vụ index.html cho route lạ để router SPA xử lý}.
2. Your dev rebuilds feel slow and you have devtool: "source-map". Better choice for dev? {Build lại dev chậm và bạn để devtool: "source-map". Lựa chọn tốt hơn cho dev?}
Solution {Lời giải}
eval-cheap-module-source-map — much faster rebuilds with still-usable maps {eval-cheap-module-source-map — build lại nhanh hơn nhiều với map vẫn dùng được}.
3. In production, a user’s stack trace shows main.min.js:1:48213. How do you get readable traces without shipping source to users? {Ở production, stack trace của user hiện main.min.js:1:48213. Làm sao có trace đọc được mà không ship source cho user?}
Solution {Lời giải}
Build with devtool: "source-map" and upload the .map to your error tracker; don’t deploy the map publicly {Build với devtool: "source-map" và tải .map lên trình theo dõi lỗi; đừng deploy map công khai}.
Stretch {Nâng cao}: in the source-map tab, compare false and eval-cheap-module-source-map — note the stack trace flip from minified main.min.js to src/user.ts:14 {trong tab source-map, so false và eval-cheap-module-source-map — để ý stack trace đổi từ main.min.js minify sang src/user.ts:14}.
Key takeaways {Điểm chính}
webpack servebuilds in memory, serves your app, and live-reloads {webpack servebuild trong bộ nhớ, phục vụ app, và tự tải lại}.- HMR swaps changed modules without a full reload, preserving state {HMR hoán module đã đổi mà không reload toàn trang, giữ state}.
- Use
devServer.proxyto forward API calls and dodge CORS in dev {DùngdevServer.proxyđể chuyển tiếp API và tránh CORS khi dev}. - Source maps map the bundle back to your original files {Source map ánh xạ bundle về file gốc}.
eval-cheap-module-source-mapfor dev,source-mapfor production {eval-cheap-module-source-mapcho dev,source-mapcho production}.
Next up {Tiếp theo}
Part 6 — Code splitting & lazy loading: stop shipping one giant bundle — dynamic import(), splitChunks for vendor/shared code, and runtimeChunk for caching {Phần 6 — Chia tách code & tải lười: ngừng ship một bundle khổng lồ — import() động, splitChunks cho code vendor/chung, và runtimeChunk cho caching}.