Webpack · Part 2 — The Config Anatomy
webpack.config.js from scratch — mode, entry, output, context, and resolve. Turn the zero-config magic into explicit configuration you control, including aliases and extensions. With an interactive config builder.
Zero-config gets you started, but real projects need control {Zero-config giúp khởi đầu, nhưng project thật cần kiểm soát}. The moment you want a custom output name, an alias, or a loader, you reach for webpack.config.js {Ngay khi bạn muốn tên output tùy chỉnh, một alias, hay một loader, bạn sẽ cần webpack.config.js}. This part decodes the skeleton every config shares {Phần này giải mã bộ khung mà mọi config đều chia sẻ}.
Toggle options below and watch a real config assemble {Bật/tắt tùy chọn bên dưới và xem một config thật ráp lại}:
1. The config is just a JS object {Config chỉ là một object JS}
webpack.config.js exports a plain object (or a function returning one) {webpack.config.js xuất một object thường (hoặc một hàm trả về object)}:
const path = require("path");
module.exports = {
mode: "production",
entry: "./src/index.js",
output: {
filename: "bundle.js",
path: path.resolve(__dirname, "dist"),
clean: true,
},
};
Because it’s JavaScript, you can compute values, read env vars, and conditionally branch {Vì là JavaScript, bạn có thể tính giá trị, đọc biến môi trường, và rẽ nhánh có điều kiện}.
2. mode {mode}
mode sets a batch of sensible defaults {mode đặt một loạt mặc định hợp lý}:
| mode | What it does {Làm gì} |
|---|---|
development | fast rebuilds, readable output, helpful errors {build lại nhanh, output dễ đọc, lỗi rõ} |
production | minification, tree shaking, optimizations on {minify, tree shaking, bật tối ưu} |
none | no defaults — you configure everything {không mặc định — bạn tự cấu hình hết} |
It also sets process.env.NODE_ENV for your code and dependencies {Nó cũng đặt process.env.NODE_ENV cho code và phụ thuộc của bạn}. Don’t ship a development build to production — you lose every optimization {Đừng ship bản development lên production — bạn mất mọi tối ưu}.
3. entry {entry}
Where the graph starts {Nơi đồ thị bắt đầu}. A string for one entry, or an object for several {Một chuỗi cho một entry, hoặc một object cho nhiều}:
// single
entry: "./src/index.js",
// multiple — produces app.js and admin.js
entry: {
app: "./src/index.js",
admin: "./src/admin.js",
},
Multiple entries are how you build separate pages or a vendor bundle (though splitChunks in Part 6 is the modern way to split vendors) {Nhiều entry là cách bạn build các trang riêng hoặc một vendor bundle (dù splitChunks ở Phần 6 mới là cách hiện đại để tách vendor)}.
4. output {output}
Where bundles go and what they’re named {Bundle đi đâu và tên gì}:
output: {
filename: "[name].[contenthash].js", // [name] = entry key
path: path.resolve(__dirname, "dist"), // MUST be absolute
clean: true, // wipe dist/ each build (Webpack 5)
publicPath: "/", // base URL for assets at runtime
}
path must be absolute — that’s why path.resolve(__dirname, ...) is everywhere {path phải là tuyệt đối — đó là lý do path.resolve(__dirname, ...) ở khắp nơi}. The [name], [contenthash] tokens are template placeholders Webpack fills in; we use [contenthash] for long-term caching in Part 8 {Các token [name], [contenthash] là placeholder mẫu Webpack điền; ta dùng [contenthash] cho caching dài hạn ở Phần 8}. clean: true replaces the old clean-webpack-plugin {clean: true thay cho clean-webpack-plugin cũ}.
5. context {context}
The base directory Webpack resolves entry and loaders against; defaults to the process cwd {Thư mục gốc Webpack giải quyết entry và loader; mặc định là cwd của tiến trình}. You rarely set it, but knowing it explains why relative entry paths resolve where they do {Bạn hiếm khi đặt nó, nhưng biết nó giải thích vì sao đường dẫn entry tương đối lại resolve ở đó}:
context: path.resolve(__dirname, "src"), // now entry: "index.js" works
6. resolve — how imports are found {resolve — cách tìm import}
resolve controls how Webpack turns an import string into a file path {resolve điều khiển cách Webpack biến một chuỗi import thành đường dẫn file}. The two settings you’ll actually use {Hai cài đặt bạn thực sự dùng}:
resolve: {
// try these extensions so you can write import "./math" (no .js)
extensions: [".js", ".jsx", ".ts", ".tsx"],
// import "@/components/Button" instead of "../../components/Button"
alias: {
"@": path.resolve(__dirname, "src"),
},
},
extensions lets you drop file extensions in imports {extensions cho bạn bỏ đuôi file trong import}. alias kills the ../../../ import hell and must match your tsconfig.json paths if you use TypeScript {alias diệt địa ngục import ../../../ và phải khớp paths trong tsconfig.json nếu bạn dùng TypeScript}.
7. Splitting dev and prod configs {Tách config dev và prod}
As configs grow, split them and merge {Khi config lớn lên, tách chúng và merge}:
// webpack.common.js — shared
// webpack.dev.js — dev server, cheap source maps
// webpack.prod.js — minify, contenthash
npm install --save-dev webpack-merge
// webpack.prod.js
const { merge } = require("webpack-merge");
const common = require("./webpack.common.js");
module.exports = merge(common, { mode: "production", devtool: "source-map" });
This keeps each file focused and avoids if (isProd) spaghetti {Cách này giữ mỗi file gọn và tránh mớ if (isProd)}.
8. Exercises {Bài tập}
1. Webpack throws “configuration.output.path: The provided value is not an absolute path”. What’s wrong? {Webpack ném “output.path không phải đường dẫn tuyệt đối”. Sai ở đâu?}
Solution {Lời giải}
output.path must be absolute — wrap it in path.resolve(__dirname, "dist") {output.path phải tuyệt đối — bọc trong path.resolve(__dirname, "dist")}.
2. You want to write import Button from "@/components/Button". Which two settings make that work (in Webpack and in TS)? {Bạn muốn viết import Button from "@/components/Button". Hai cài đặt nào làm nó chạy (trong Webpack và TS)?}
Solution {Lời giải}
resolve.alias in Webpack and matching compilerOptions.paths in tsconfig.json {resolve.alias trong Webpack và compilerOptions.paths khớp trong tsconfig.json}.
3. Your bundle is huge and unminified in production. You set mode to what by accident? {Bundle của bạn to và không minify ở production. Bạn lỡ đặt mode thành gì?}
Solution {Lời giải}
development (or none) — only production enables minification and tree shaking {development (hoặc none) — chỉ production bật minify và tree shaking}.
Stretch {Nâng cao}: in the builder, enable multiple entries and notice the filename gains [name]. so the two bundles don’t collide {trong builder, bật nhiều entry và để ý filename thêm [name]. để hai bundle không đụng nhau}.
Key takeaways {Điểm chính}
- The config is a plain JS object exported from
webpack.config.js{Config là object JS thường xuất từwebpack.config.js}. modesets a batch of defaults — alwaysproductionfor shipping {modeđặt loạt mặc định — luônproductionkhi ship}.output.pathmust be absolute; use template tokens like[name]/[contenthash]{output.pathphải tuyệt đối; dùng token mẫu như[name]/[contenthash]}.resolve.extensionsandresolve.aliasclean up imports {resolve.extensionsvàresolve.aliasdọn dẹp import}.- Split dev/prod configs and combine with
webpack-merge{Tách config dev/prod và gộp bằngwebpack-merge}.
Next up {Tiếp theo}
Part 3 — Loaders: how Webpack transforms non-JS files — babel-loader, css-loader/style-loader, asset modules — and why loader order (right-to-left) matters {Phần 3 — Loader: cách Webpack biến đổi file không phải JS — babel-loader, css-loader/style-loader, asset module — và vì sao thứ tự loader (phải-sang-trái) quan trọng}.