Bài 11 · Vận dụng · 20 phút· Cập nhật 11/06/2026
Module (ESM) & npm
Biên soạn bởi Nguyễn Anh Tuấn
Module ESM (import/export) & npm: package.json, semver ^ và ~, ESM vs CommonJS, và bundler (Vite) gộp code - nền tảng cho dự án JavaScript thật.
Thời các thẻ <script> rời rạc, mọi biến đều nằm chung global - hai file vô tình đặt trùng tên là ghi đè nhau, sinh bug ngầm. Module sửa điều đó: mỗi file có scope RIÊNG, chỉ những gì bạn export mới lộ ra ngoài.
mỗi file là một module có scope riêng
// file: chao.js
const noiBo = "khong lo ra ngoai"; // rieng tu cua module nay
export function chao(ten) { // chu dong chia se
return "Chao " + ten;
} - ▸Mỗi file = một module, có scope RIÊNG (không ô nhiễm global).
- ▸Chỉ thứ được export mới ra ngoài; còn lại là riêng tư.
- ▸Nền tảng để viết code lớn, tái dùng và dễ bảo trì.
Hai kiểu xuất: named (nhiều thứ theo tên) và default (một “cái chính” mỗi file). Khi nhập, named phải khớp tên (có thể đổi bằng as), còn default đặt tên tuỳ ý.
named + default
// math.js
export const PI = 3.14; // named
export function cong(a, b) { return a + b; } // named
export default function tru(a, b) { return a - b; } // default (mot cai)
// main.js
import tru, { PI, cong } from "./math.js"; // default + named
import { cong as themVao } from "./math.js"; // doi ten bang "as"
import * as math from "./math.js"; // gom het thanh "math"
console.log(cong(2, 3), tru(5, 1), PI); // 5 4 3.14 Kết quả khi chạy
5 4 3.14
- ▸named: export theo tên; import { } phải khớp tên (đổi bằng as).
- ▸default: một export chính mỗi file; import đặt tên tuỳ ý.
- ▸import * as ns gom cả module thành một namespace.
npm là kho gói lớn nhất cùng công cụ cài. npm install tải gói vào node_modules và ghi vào package.json. Phiên bản ghi theo semver với ký hiệu khoảng - thử xem ký hiệu nào nhận phiên bản nào:
cài & khai báo phụ thuộc
npm init -y # tao package.json
npm install dayjs # tai vao node_modules + ghi "dependencies" package.json (trích) - dấu ^ khai báo khoảng phiên bản (JSON thật không chứa comment)
{
"dependencies": { "dayjs": "^1.11.0" }
} dùng gói - import từ TÊN gói, không phải đường dẫn file
import dayjs from "dayjs";
console.log(dayjs().format("YYYY-MM-DD")); - ▸npm install: tải vào node_modules + ghi package.json (dependencies).
- ▸semver MAJOR.MINOR.PATCH; ^ cho minor+patch, ~ chỉ patch.
- ▸package-lock.json ghim phiên bản chính xác; ĐỪNG commit node_modules.
Cạm bẫy ^0.x
npm, yarn, pnpm, bun - chọn cái nào?
Có hai hệ module trong thế giới JS. ESM (import/export) là chuẩn hiện đại; CommonJS (require/module.exports) là hệ cũ của Node mà bạn vẫn sẽ gặp.
hai cú pháp
// ESM (chuan - trinh duyet & Node moi)
import fs from "node:fs";
export const x = 1;
// CommonJS (he cu cua Node)
const fs = require("fs");
module.exports = { x: 1 }; - ▸ESM: chuẩn ngôn ngữ, nạp TĨNH (phân tích khi build → tree-shaking tốt).
- ▸CommonJS: hệ cũ của Node, nạp ĐỘNG lúc chạy bằng require.
- ▸Node bật ESM bằng "type": "module" (hoặc đuôi .mjs); code mới ưu tiên ESM.
Trung thực: vẫn còn lẫn lộn
Trình duyệt không hiểu bare import kiểu import x from "dayjs", và nạp hàng trăm file lẻ thì chậm. Bundler (Vite, esbuild, webpack) gộp & tối ưu trước khi đưa lên web.
vòng đời với Vite
npm create vite@latest my-app # tao du an
npm install # cai phu thuoc
npm run dev # dev server: nap nhanh + hot reload
npm run build # gop & toi uu ra thu muc dist/ - ▸Bundler giải import, transpile cú pháp mới, tree-shaking (bỏ code thừa), nén.
- ▸Dev server (Vite) nạp nhanh và hot-reload khi bạn sửa code.
- ▸npm run build sinh thư mục dist/ tối ưu cho production.
Tiếp theo
Câu hỏi thường gặp
Mỗi file JS là một module có scope RIÊNG: biến/hàm không tự lọt ra ngoài (tránh “ô nhiễm” global như kiểu nhiều thẻ script cũ). Bạn chủ động chia sẻ bằng export và dùng lại bằng import → code gọn, tái dùng được, dễ bảo trì.
named: xuất NHIỀU thứ theo TÊN (export function a() {}; rồi import { a }). default: MỘT “cái chính” cho mỗi file (export default …; rồi import tên_tuỳ_ý). Có thể trộn cả hai, nhưng mỗi file chỉ một default. import { } phải khớp đúng tên đã export.
npm là kho gói (package) lớn nhất cùng công cụ cài. npm install <gói> tải gói vào thư mục node_modules và ghi vào package.json. Sau đó bạn import từ tên gói. Đừng commit node_modules - nó tái tạo được từ package.json + lockfile.
Đều là package manager - cài & quản lý gói. npm đi kèm Node nên là mặc định. yarn và pnpm là lựa chọn thay thế phổ biến; pnpm tiết kiệm ổ đĩa nhờ chia sẻ gói giữa các dự án. bun vừa là package manager vừa là một runtime (xem Bài 1). Lệnh gần như giống nhau (install/add/run), nhưng mỗi cái có lockfile riêng - đừng trộn nhiều cái trong cùng một dự án.
Đó là semver (MAJOR.MINOR.PATCH). ^1.2.3 cho phép cập nhật KHÔNG đổi MAJOR (>= 1.2.3 và < 2.0.0). ~1.2.3 chặt hơn, chỉ PATCH (>= 1.2.3 và < 1.3.0). package-lock.json ghim phiên bản CHÍNH XÁC để máy khác cài lại y hệt. (Lưu ý: ^0.x đặc biệt - xem Bước 3.)
Tick những điều em tự tin làm được. Càng lên cao, em càng hiểu sâu.
Trả lời vài câu để chắc rằng em đã nắm bài.
Lợi ích chính của việc chia code thành module là gì?
- 1
Tách module
Viết
toan.jsexport hàmcong(a, b); import vàomain.jsvà dùng.Hoàn thành khi:
import { cong }chạy đúng; biến nội bộ củatoan.jskhông lọt ra global. - 2
default export
Tạo file
export defaultmột hàm; import với một tên do bạn tự đặt.Hoàn thành khi: import tên bất kỳ vẫn lấy đúng giá trị
default. - 3
npm install
npm init -y, rồinpm installmột gói (vddayjs), import và dùng thử.Hoàn thành khi:
node_modulesvàpackage.jsonxuất hiện gói;importchạy được. - 4
Đọc semver
Dùng công cụ ở Bước 3: với
^1.2.3những phiên bản nào thoả? Với~1.2.3thì sao?Hoàn thành khi:
^nhận 1.2.3…1.5.0 (không 2.0.0);~nhận ≥1.2.3 và <1.3.0 - tức1.2.xvới x ≥ 3 (1.2.2 bị loại). - 5
ESM vs CommonJS
Trong một dự án, tìm file dùng
import(ESM) và file dùngrequire(CommonJS).Hoàn thành khi: Nhận ra hai cú pháp; biết
"type": "module"bật ESM cho Node. - 6
Build thử
Chạy một lệnh build (vd
vite build) và mở thư mụcdistxem kết quả.Hoàn thành khi: Thấy bundler gộp nhiều file nguồn thành ít file đã tối ưu/nén.