jvinhit//lab

Search posts

Type to search across journal entries.

navigate open esc close

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ý}:

modeWhat it does {Làm gì}
developmentfast rebuilds, readable output, helpful errors {build lại nhanh, output dễ đọc, lỗi rõ}
productionminification, tree shaking, optimizations on {minify, tree shaking, bật tối ưu}
noneno 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]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}.
  • mode sets a batch of defaults — always production for shipping {mode đặt loạt mặc định — luôn production khi ship}.
  • output.path must be absolute; use template tokens like [name]/[contenthash] {output.path phải tuyệt đối; dùng token mẫu như [name]/[contenthash]}.
  • resolve.extensions and resolve.alias clean up imports {resolve.extensionsresolve.alias dọn dẹp import}.
  • Split dev/prod configs and combine with webpack-merge {Tách config dev/prod và gộp bằng webpack-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}.