Skip to content

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

  1. SemVer — Nền tảng
  2. Version Ranges trong package.json
  3. npm vs pnpm vs yarn — Điểm khác biệt
  4. Lockfile — Tại sao quan trọng
  5. Node.js bản thân — LTS vs Current
  6. Quản lý Node.js version với nvm / fnm / volta
  7. engines field trong package.json
  8. Publish package lên npm — Versioning workflow
  9. Monorepo và version — Turborepo / Nx / Lerna
  10. Các lệnh npm / pnpm hay dùng liên quan đến version
  11. Pitfalls thực tế
  12. 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ầnKhi nào tăngVí dụ
MAJORCó breaking changes — API cũ không còn hoạt động4.0.0 → 5.0.0
MINORThêm tính năng mới, backward compatible5.1.0 → 5.2.0
PATCHBug fix, không thay đổi API5.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 release

npm 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 version

Version 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ệuVí 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
Exact5.46.1Chỉ đúng 5.46.1
>=>=5.46.1Từ 5.46.1 trở lên, không giới hạn
>>5.46.1Lớn hơn 5.46.1
<<6.0.0Nhỏ hơn 6.0.0
<=<=5.46.1Nhỏ hơn hoặc bằng
- (hyphen)5.0.0 - 5.46.1Inclusive 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.0

Tilde ~ — 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?

jsonc
// 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

jsonc
{
  "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 ManagerBehavior mặc định
npmFlat node_modules — hoist tất cả lên root
pnpmContent-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ơn

Version 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 ManagerLockfile
npmpackage-lock.json
pnpmpnpm-lock.yaml
yarn classicyarn.lock
yarn berryyarn.lock (format khác)

Lockfile có gì?

yaml
# 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 project

npm install vs npm ci

npm installnpm ci
Dùng khiDevelopmentCI/CD
LockfileCó thể updatePhải đúng, nếu sai → fail
node_modulesIncrementalXóa sạch rồi install lại
Tốc độChậm hơnNhanh 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) → EOL

Ví 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:

bash
# .nvmrc
22.3.0

# hoặc
lts/jod

nvm (Node Version Manager)

bash
# 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ại

fnm (Fast Node Manager) — Rust-based, nhanh hơn nvm

bash
# Install
fnm install 22
fnm install --lts

# Use
fnm use 22

# Auto-use từ .nvmrc hoặc .node-version
fnm use --version-file-strategy=recursive

volta — Pinning per-project

bash
# 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
jsonc
// 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:

jsonc
{
  "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:
ini
# .npmrc
engine-strict=true

Khi 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

bash
# 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.1

Lệnh này tự động:

  1. Cập nhật version trong package.json
  2. Tạo git commit v2.0.0
  3. Tạo git tag v2.0.0

Publish với dist-tag

bash
# 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.1

dist-tags thường gặp

bash
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.0

9. 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.0

Dù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.1

Dùng bởi: hầu hết monorepo lớn

pnpm workspace

yaml
# pnpm-workspace.yaml
packages:
  - 'packages/*'
  - 'apps/*'
jsonc
// 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

bash
# Tạo changeset mô tả thay đổi
pnpm changeset

# Preview version sẽ bump
pnpm changeset version

# Publish tất cả packages đã bump
pnpm changeset publish

10. Các lệnh npm / pnpm hay dùng liên quan đến version

Kiểm tra version

bash
# 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 express

Upgrade packages

bash
# 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 --latest

Install theo version cụ thể

bash
# 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.21

Audit và security

bash
# 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 --force

11. 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

javascript
// 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 strict

Giả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 bundle

Giải pháp: Dùng overrides (npm) hoặc pnpm.overrides để force single version.

jsonc
// package.json (root)
{
  "pnpm": {
    "overrides": {
      "lodash": "^4.17.21"
    }
  }
}

5. node_modules không sync sau khi pull code

bash
# 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 install

6. Cài nhầm dependencies thay vì devDependencies

bash
# Sai — typescript không cần ở runtime
pnpm add typescript

# Đúng
pnpm add -D typescript

12. 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 volta

Khi 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_modules

Khi 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

Tham khảo

Today I Learned