Versioning trong Node.js Ecosystem
Tài liệu này trình bày toàn diện về cách quản lý, hiểu và làm việc với version trong hệ sinh thái Node.js — từ SemVer cơ bản đến lockfile, version ranges, và các best practices thực tế.
Mục lục
- SemVer — Nền tảng
- Version Ranges trong package.json
- npm vs pnpm vs yarn — Điểm khác biệt
- Lockfile — Tại sao quan trọng
- Node.js bản thân — LTS vs Current
- Quản lý Node.js version với nvm / fnm / volta
- engines field trong package.json
- Publish package lên npm — Versioning workflow
- Monorepo và version — Turborepo / Nx / Lerna
- Các lệnh npm / pnpm hay dùng liên quan đến version
- Pitfalls thực tế
- Best Practices tổng kết
1. SemVer — Nền tảng
Semantic Versioning (SemVer) là chuẩn được npm áp dụng, định nghĩa version theo format:
MAJOR.MINOR.PATCH| Thành phần | Khi nào tăng | Ví dụ |
|---|---|---|
| MAJOR | Có breaking changes — API cũ không còn hoạt động | 4.0.0 → 5.0.0 |
| MINOR | Thêm tính năng mới, backward compatible | 5.1.0 → 5.2.0 |
| PATCH | Bug fix, không thay đổi API | 5.2.1 → 5.2.2 |
Pre-release tags
1.0.0-alpha ← early testing, API chưa ổn định
1.0.0-alpha.1
1.0.0-beta ← feature complete, đang fix bug
1.0.0-beta.2
1.0.0-rc.1 ← release candidate, gần như final
1.0.0 ← stable releasenpm hiểu thứ tự: 1.0.0-alpha < 1.0.0-beta < 1.0.0-rc.1 < 1.0.0
Build metadata (ít dùng)
1.0.0+build.20240601 ← bị npm bỏ qua khi so sánh versionVersion 0.x.x — Unstable phase
Khi major là 0, SemVer coi toàn bộ package là unstable:
0.1.0 → 0.2.0 ← được phép có breaking changes dù chỉ bump minorĐiều này ảnh hưởng trực tiếp đến cách ^ hoạt động (xem phần 2).
2. Version Ranges trong package.json
Các ký hiệu phổ biến
| Ký hiệu | Ví dụ | Cho phép range |
|---|---|---|
^ (caret) | ^5.46.1 | >=5.46.1 <6.0.0 |
~ (tilde) | ~5.46.1 | >=5.46.1 <5.47.0 |
| Exact | 5.46.1 | Chỉ đúng 5.46.1 |
>= | >=5.46.1 | Từ 5.46.1 trở lên, không giới hạn |
> | >5.46.1 | Lớn hơn 5.46.1 |
< | <6.0.0 | Nhỏ hơn 6.0.0 |
<= | <=5.46.1 | Nhỏ hơn hoặc bằng |
- (hyphen) | 5.0.0 - 5.46.1 | Inclusive range cả 2 đầu |
* hoặc "" | * | Bất kỳ version nào |
| ` | ` |
Caret ^ — Chi tiết đầy đủ
^ lock vào leftmost non-zero digit:
^5.46.1 → >=5.46.1 <6.0.0 ← lock major
^0.46.1 → >=0.46.1 <0.47.0 ← lock minor (major là 0)
^0.0.3 → >=0.0.3 <0.0.4 ← lock patch (cả major lẫn minor là 0)
^0.0.x → >=0.0.0 <0.1.0
^0.x → >=0.0.0 <1.0.0Tilde ~ — Chi tiết đầy đủ
~ lock vào patch level nếu minor được chỉ định, ngược lại lock minor:
~5.46.1 → >=5.46.1 <5.47.0 ← chỉ patch
~5.46 → >=5.46.0 <5.47.0 ← chỉ patch
~5 → >=5.0.0 <6.0.0 ← toàn bộ major 5
~0.46.1 → >=0.46.1 <0.47.0 ← chỉ patch (không có exception như ^)Khi nào dùng cái nào?
// Thư viện internal — dùng exact để đảm bảo
"my-internal-lib": "2.3.1"
// Thư viện stable, tin tưởng — dùng ^ (mặc định của npm install)
"express": "^4.18.2"
// Thư viện dễ breaking — dùng ~
"some-tricky-lib": "~3.2.1"
// Peer dependency — thường dùng range rộng
"react": ">=17.0.0 <20.0.0"dependencies vs devDependencies vs peerDependencies
{
"dependencies": {
// Runtime — được bundle hoặc cần ở production
"express": "^4.18.2"
},
"devDependencies": {
// Chỉ cần lúc develop/build/test
"typescript": "^5.0.0",
"vitest": "^1.0.0"
},
"peerDependencies": {
// Package này cần X, nhưng để host app tự install
// Thường thấy ở plugins, adapters
"react": ">=17.0.0"
},
"optionalDependencies": {
// Nếu install fail thì npm bỏ qua, không báo lỗi
"fsevents": "^2.3.2"
}
}3. npm vs pnpm vs yarn — Điểm khác biệt
Resolution algorithm
| Package Manager | Behavior mặc định |
|---|---|
| npm | Flat node_modules — hoist tất cả lên root |
| pnpm | Content-addressable store + symlink — strict, không hoist tự động |
| yarn classic (v1) | Tương tự npm flat |
| yarn berry (v2+) | Plug'n'Play (PnP) — không có node_modules |
pnpm và phantom dependencies
pnpm ngăn phantom dependencies — tức là bạn không thể import một package không có trong package.json của mình dù nó được install bởi dependency khác:
npm: có thể import 'lodash' dù chỉ là transitive dep → dễ gây bug ẩn
pnpm: import 'lodash' sẽ lỗi nếu không khai báo → safe hơnVersion resolution khi conflict
Giả sử app dùng lodash@^4.0.0, và một dep dùng lodash@^3.0.0:
- npm/yarn: Install cả 2 version vào các thư mục riêng (nested)
- pnpm: Tương tự, nhưng dùng hard link từ central store — không duplicate file thực sự
4. Lockfile — Tại sao quan trọng
Lockfile ghi lại version chính xác đã được resolved, đảm bảo mọi người trong team và CI đều cài đúng dependency tree.
| Package Manager | Lockfile |
|---|---|
| npm | package-lock.json |
| pnpm | pnpm-lock.yaml |
| yarn classic | yarn.lock |
| yarn berry | yarn.lock (format khác) |
Lockfile có gì?
# pnpm-lock.yaml — ví dụ rút gọn
express@4.18.2:
resolution: {integrity: sha512-...}
dependencies:
accepts: 1.3.8
body-parser: 1.20.1
...Nó record:
- Exact version đã resolve
- Integrity hash (sha512) để verify nội dung
- Toàn bộ dependency tree (transitive deps)
Quy tắc với lockfile
✅ Commit lockfile vào git — luôn luôn
✅ Dùng `npm ci` / `pnpm install --frozen-lockfile` trong CI
✅ Review lockfile changes trong MR khi upgrade deps
❌ Không `.gitignore` lockfile
❌ Không mix `npm install` và `pnpm install` trên cùng projectnpm install vs npm ci
npm install | npm ci | |
|---|---|---|
| Dùng khi | Development | CI/CD |
| Lockfile | Có thể update | Phải đúng, nếu sai → fail |
node_modules | Incremental | Xóa sạch rồi install lại |
| Tốc độ | Chậm hơn | Nhanh hơn (predictable) |
5. Node.js bản thân — LTS vs Current
Node.js có 2 release lines chính:
Current → tính năng mới nhất, odd major (21, 23, 25...)
LTS → stable, được maintain lâu dài, even major (18, 20, 22...)Release lifecycle
Current (6 tháng) → Active LTS (18 tháng) → Maintenance LTS (12 tháng) → EOLVí dụ timeline:
Node 18: EOL tháng 4/2025
Node 20: Active LTS đến tháng 4/2026
Node 22: Active LTS từ tháng 10/2024
Node 24: Current (2025)Khuyến nghị thực tế
- Production: Dùng Active LTS (ví dụ Node 22)
- Thử nghiệm tính năng mới: Current
- Tuyệt đối không: Dùng version đã EOL
6. Quản lý Node.js version với nvm / fnm / volta
.nvmrc / .node-version
File đặt ở root project để chỉ định Node version:
# .nvmrc
22.3.0
# hoặc
lts/jodnvm (Node Version Manager)
# Install version
nvm install 22
nvm install --lts
# Switch version
nvm use 22
nvm use --lts
# Set default
nvm alias default 22
# Tự động dùng version từ .nvmrc
nvm use # đọc .nvmrc trong thư mục hiện tạifnm (Fast Node Manager) — Rust-based, nhanh hơn nvm
# Install
fnm install 22
fnm install --lts
# Use
fnm use 22
# Auto-use từ .nvmrc hoặc .node-version
fnm use --version-file-strategy=recursivevolta — Pinning per-project
# Pin Node version cho project
volta pin node@22
volta pin npm@10
# Tự động apply khi cd vào thư mục có package.json với "volta" field// package.json
{
"volta": {
"node": "22.3.0",
"pnpm": "9.1.0"
}
}7. engines field trong package.json
Khai báo Node.js (và npm/pnpm) version tối thiểu project yêu cầu:
{
"engines": {
"node": ">=20.0.0",
"pnpm": ">=9.0.0"
}
}Mức độ enforcement
- npm: Chỉ warn nếu vi phạm (không block)
- npm với
--engine-strict: Sẽ fail - pnpm: Có thể config trong
.npmrc:
# .npmrc
engine-strict=trueKhi engine-strict=true, chạy sai Node version sẽ báo lỗi ngay khi pnpm install.
8. Publish package lên npm — Versioning workflow
npm version command
# Bump patch: 1.2.3 → 1.2.4
npm version patch
# Bump minor: 1.2.3 → 1.3.0
npm version minor
# Bump major: 1.2.3 → 2.0.0
npm version major
# Pre-release
npm version prerelease --preid=beta # 1.2.3 → 1.2.4-beta.0
npm version prepatch # 1.2.3 → 1.2.4-0
npm version preminor # 1.2.3 → 1.3.0-0
npm version premajor # 1.2.3 → 2.0.0-0
# Set cụ thể
npm version 2.0.0-rc.1Lệnh này tự động:
- Cập nhật
versiontrongpackage.json - Tạo git commit
v2.0.0 - Tạo git tag
v2.0.0
Publish với dist-tag
# Publish stable
npm publish
# Publish beta (người dùng không tự nhận được)
npm publish --tag beta
# Install beta
npm install my-package@beta
npm install my-package@2.0.0-beta.1dist-tags thường gặp
npm dist-tag ls my-package
# latest: 5.46.1 ← npm install my-package sẽ lấy cái này
# beta: 5.47.0-beta.2
# next: 6.0.0-rc.1
# legacy: 4.20.09. Monorepo và version — Turborepo / Nx / Lerna
2 chiến lược versioning trong monorepo
Fixed/Locked versioning — Tất cả packages cùng version:
packages/
ui/ → 2.1.0
api/ → 2.1.0 ← bump cùng nhau dù chỉ 1 package thay đổi
utils/ → 2.1.0Dùng bởi: Babel, Jest (trước đây)
Independent versioning — Mỗi package version riêng:
packages/
ui/ → 3.5.0
api/ → 1.2.1
utils/ → 5.0.0-beta.1Dùng bởi: hầu hết monorepo lớn
pnpm workspace
# pnpm-workspace.yaml
packages:
- 'packages/*'
- 'apps/*'// packages/ui/package.json — reference package nội bộ
{
"dependencies": {
"@myorg/utils": "workspace:*", // dùng local version
"@myorg/config": "workspace:^" // dùng local, range compatible
}
}workspace:* sẽ được replaced bằng exact version khi publish.
Changesets — Tool phổ biến để manage version trong monorepo
# Tạo changeset mô tả thay đổi
pnpm changeset
# Preview version sẽ bump
pnpm changeset version
# Publish tất cả packages đã bump
pnpm changeset publish10. Các lệnh npm / pnpm hay dùng liên quan đến version
Kiểm tra version
# Xem version hiện tại của package đã install
npm list lodash
pnpm list lodash
# Xem toàn bộ dep tree
npm list
pnpm list --depth=3
# Xem version mới nhất trên registry
npm view lodash version
npm view lodash versions --json # tất cả versions từng release
# Xem thông tin đầy đủ
npm info expressUpgrade packages
# Kiểm tra packages nào có thể update
npm outdated
pnpm outdated
# Update theo range trong package.json
npm update
pnpm update
# Update và cho phép vượt range (cẩn thận)
npm update lodash --save
pnpm update lodash --latest
# Interactive update (pnpm)
pnpm update --interactive
pnpm update --interactive --latestInstall theo version cụ thể
# Exact version
npm install lodash@4.17.21
pnpm add lodash@4.17.21
# Latest trong major
npm install lodash@^4
pnpm add lodash@^4
# Tag
npm install lodash@beta
pnpm add lodash@next
# Từ git (thường dùng khi fork fix bug)
npm install github:lodash/lodash#main
pnpm add github:lodash/lodash#v4.17.21Audit và security
# Kiểm tra CVE trong dependencies
npm audit
pnpm audit
# Tự động fix (patch/minor)
npm audit fix
# Fix kể cả breaking changes (cẩn thận)
npm audit fix --force11. Pitfalls thực tế
1. "SemVer in theory" vs "SemVer in practice"
Nhiều popular packages không tuân thủ SemVer nghiêm túc:
- Minor version bump nhưng có breaking changes
- Behavioral changes không được document
- Transitive dep update gây lỗi ngầm
Giải pháp: Luôn commit lockfile + đọc CHANGELOG trước khi update major.
2. ^ trong devDependencies của thư viện
Nếu bạn publish một library, devDependencies không ảnh hưởng consumer. Nhưng peerDependencies version range quá hẹp sẽ gây conflict.
3. Phantom dependencies
// package.json của bạn không có lodash
// nhưng một dep của bạn dùng lodash
import _ from 'lodash' // hoạt động với npm, fail với pnpm strictGiải pháp với pnpm: Dùng shamefully-hoist=true (quick fix) hoặc khai báo explicit (đúng cách).
4. Version conflict trong monorepo
app → lodash@^4
plugin-a → lodash@^4
plugin-b → lodash@^3 ← gây ra 2 bản lodash trong bundleGiải pháp: Dùng overrides (npm) hoặc pnpm.overrides để force single version.
// package.json (root)
{
"pnpm": {
"overrides": {
"lodash": "^4.17.21"
}
}
}5. node_modules không sync sau khi pull code
# Người khác thêm dep mới, bạn pull về nhưng không install lại
# → import error khó hiểu
# Giải pháp: luôn install sau pull
git pull && pnpm install6. Cài nhầm dependencies thay vì devDependencies
# Sai — typescript không cần ở runtime
pnpm add typescript
# Đúng
pnpm add -D typescript12. Best Practices tổng kết
Trong project thông thường
✅ Commit lockfile (package-lock.json / pnpm-lock.yaml)
✅ Dùng `^` cho dependencies thông thường (mặc định)
✅ Dùng exact version cho critical packages (database drivers, payment SDKs)
✅ Dùng npm ci / pnpm install --frozen-lockfile trong CI
✅ Khai báo engines field
✅ Chạy npm audit định kỳ
✅ Pin Node version qua .nvmrc hoặc voltaKhi publish package
✅ Tuân thủ SemVer nghiêm túc
✅ Viết CHANGELOG.md
✅ Dùng dist-tag cho pre-release (--tag beta)
✅ Khai báo peerDependencies đúng
✅ Không commit node_modulesKhi upgrade dependencies
✅ Đọc CHANGELOG / release notes trước khi bump major
✅ Upgrade từng package một, không upgrade tất cả cùng lúc
✅ Chạy test suite sau mỗi upgrade
✅ Review lockfile diff trong MR