1. CommonJS vs ES Modules
| Features | CommonJS (CJS) | ES Modules (ESM) |
|---|---|---|
| Syntax | require() / module.exports | import / export |
| Loading | Synchronous | Asynchronous |
| Top-level await | No | Yes |
| __dirname | Yes | import.meta.dirname (Node 21+) |
| JSON import | require('./data.json') | import data from './data.json' with { type: 'json' } |
| Tree-shaking | No | Yes (static analysis) |
// ESM — recommended
import { readFile } from 'node:fs/promises'
import path from 'node:path'
// Dynamic import
const module = await import('./utils.js')
// import.meta
console.log(import.meta.url) // file:///path/to/file.js
console.log(import.meta.dirname) // /path/to (Node.js 21+)
console.log(import.meta.filename) // /path/to/file.js
2. Package.json exports Field
{
"name": "my-lib",
"type": "module",
"exports": {
".": {
"import": "./dist/index.mjs",
"require": "./dist/index.cjs",
"types": "./dist/index.d.ts"
},
"./utils": {
"import": "./dist/utils.mjs",
"require": "./dist/utils.cjs"
}
},
"files": ["dist"],
"engines": {
"node": ">=22"
}
}
3. pnpm Workspace
# pnpm-workspace.yaml
packages:
- 'packages/*'
- 'apps/*'
# Thêm dependency cho workspace
pnpm add lodash --filter @myorg/server
pnpm add @myorg/shared --filter @myorg/server --workspace
# Chạy script cho tất cả packages
pnpm -r run build
pnpm -r --parallel run dev
4. Permission Model (Node.js 22+)
# Chạy với permission giới hạn
node --experimental-permission \
--allow-fs-read=/app/data \
--allow-fs-write=/app/logs \
--allow-child-process \
app.js
# Kiểm tra permission trong code
if (process.permission.has('fs.read', '/etc/passwd')) {
console.log('Has read permission')
}
5. Built-in Test Runner
import { describe, it, before, after, mock } from 'node:test'
import assert from 'node:assert/strict'
describe('Calculator', () => {
it('adds two numbers', () => {
assert.strictEqual(1 + 2, 3)
})
it('handles async', async () => {
const result = await Promise.resolve(42)
assert.strictEqual(result, 42)
})
it('supports mocking', () => {
const fn = mock.fn((a: number, b: number) => a + b)
fn(1, 2)
assert.strictEqual(fn.mock.calls.length, 1)
assert.deepStrictEqual(fn.mock.calls[0].arguments, [1, 2])
})
})
node --test --test-reporter spec
Next article: TypeScript & Modern JavaScript — tsx, ts-node, type stripping.