Tệp kê khai của một dự án Node
Mọi dự án Node.js đều có một tệp package.json ở thư mục gốc. Đây là một tệp JSON thông thường mô tả dự án: tên, phiên bản, các thứ nó phụ thuộc vào, những lệnh nó cung cấp. Và đây là tệp mà npm đọc mỗi khi làm bất cứ việc gì. Xóa tệp này đi, npm sẽ không biết dự án của bạn là gì.
Cách nhanh nhất để tạo tệp này là npm init:
npm init -y
Cờ -y bỏ qua các câu hỏi và chấp nhận giá trị mặc định. Bạn sẽ nhận được một tệp giống thế này:
{
"name": "my-app",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC"
}
Đó là bộ khung tối thiểu. Phần lớn các trường trên tự chúng không làm được gì nhiều: chúng chỉ trở nên hữu ích khi bạn thêm dependency và script.
dependencies và devDependencies
Hai trường đảm nhận gần như toàn bộ công việc chính: dependencies và devDependencies. Cả hai đều ánh xạ tên package tới khoảng phiên bản.
Việc tách riêng quan trọng vì một lý do: dependencies là các package mà code của bạn cần để chạy. devDependencies là các package bạn chỉ cần trong lúc phát triển: công cụ chạy test, linter, công cụ build, trình kiểm tra kiểu. Khi ai đó cài package của bạn làm dependency cho dự án của họ, npm tải dependencies của bạn và bỏ qua devDependencies.
npm tự động cập nhật các trường này. npm install express thêm một dòng vào dependencies. npm install --save-dev vitest thêm một dòng vào devDependencies. Bạn hiếm khi phải sửa chúng bằng tay.
Khoảng phiên bản: ^, ~ và phiên bản chính xác
Những chuỗi phiên bản như ^4.19.0 không phải là phiên bản chính xác mà là khoảng phiên bản. npm tuân theo semver, chia phiên bản thành MAJOR.MINOR.PATCH:
- Tăng MAJOR nghĩa là phá vỡ khả năng tương thích ngược.
- Tăng MINOR nghĩa là thêm tính năng nhưng không làm hỏng gì.
- Tăng PATCH nghĩa là sửa bug.
Hai toán tử bạn sẽ thấy ở khắp nơi:
"express": "^4.19.0" // >= 4.19.0 and < 5.0.0 (any 4.x.x at or above 4.19.0)
"express": "~4.19.0" // >= 4.19.0 and < 4.20.0 (any 4.19.x at or above 4.19.0)
"express": "4.19.0" // exactly 4.19.0
^ là mặc định mà npm dùng khi bạn cài một package. Nó tin rằng các bản tăng minor và patch vẫn tương thích. ~ thận trọng hơn: chỉ cho phép cập nhật patch. Một phiên bản không có ký hiệu thì được cố định chính xác.
Vấn đề là: "thứ tôi vừa cài" và "thứ mà khoảng phiên bản cho phép" không giống nhau. Nếu hôm nay bạn cài express@4.19.0 và một tháng sau đồng đội cài dự án của bạn, ^4.19.0 có thể được phân giải thành 4.19.5. Đó là lúc package-lock.json vào cuộc: tệp này ghi lại phiên bản chính xác đã được phân giải, để ai cũng nhận được cùng một cây dependency. Hãy commit tệp này.
Scripts: bộ lệnh của dự án
Trường scripts là nơi bạn định nghĩa lối tắt cho các lệnh thường dùng. Bất cứ thứ gì đặt ở đây đều chạy được bằng npm run <name>:
Vài điều cần biết về scripts:
npm start,npm testvà một số tên khác chạy được mà không cần từ khóarun. Mọi script khác cầnnpm run <name>.- Script được thực thi trong một shell có
node_modules/.binnằm trongPATH, nên bạn có thể gọi trực tiếp các tệp thực thi từ package đã cài."test": "vitest"vẫn chạy dùvitestkhông được cài toàn cục. - Bạn có thể nối các script:
"build": "npm run lint && npm run compile". Dùng&&cho "chạy lần lượt, dừng lại khi có lỗi". - Các script
pre<name>vàpost<name>chạy tự động. Nếu cóprebuild, nó sẽ chạy trướcbuildmà không cần cấu hình thêm.
Scripts là bộ lệnh của dự án. Một tệp package.json tốt giúp người mới tham gia có thể clone dự án, chạy npm install, rồi npm run dev / npm test mà không phải đọc wiki.
Điểm vào: main, exports, type
Các trường này cho Node (và các bundler) biết cách tải package của bạn.
typequyết định cách đọc các tệp.js."module"nghĩa là ESM (import/export). Bỏ trường này hoặc đặt"commonjs"để dùng CommonJS (require). Xem bài CommonJS và ESM để biết đầy đủ.mainlà điểm vào kiểu cũ: thứ màrequire("my-lib")phân giải tới. Các công cụ cũ vẫn tôn trọng trường này.exportslà phiên bản thay thế hiện đại và chặt chẽ hơn. Nó định nghĩa chính xác những tệp nào người dùng package có thể import và dưới đường dẫn con nào. Nếu một tệp không được liệt kê ở đây, import tệp đó sẽ thất bại, và đó là tính năng chứ không phải bug. Bạn kiểm soát API công khai.
Nếu bạn chỉ đang xây dựng một ứng dụng (không xuất bản package), type có lẽ là trường duy nhất trong số này mà bạn cần quan tâm.
Một package.json thực tế
Ghép tất cả lại, package.json của một ứng dụng Node nhỏ trong thực tế trông đại khái như sau:
Hãy để ý engines.node. Trường này mang tính khuyến nghị: npm sẽ cảnh báo (hoặc báo lỗi nếu bật engine-strict) khi phiên bản Node của người dùng không khớp. Một thói quen tốt cho mọi thứ bạn xuất bản.
Những trường nên biết
Thêm vài trường bạn sẽ gặp:
private: true: ngăn bạn vô tình xuất bản package lên npm. Hãy đặt trường này cho mọi dự án không dùng để xuất bản.license: mã định danh SPDX như"MIT"hoặc"ISC". Quan trọng với mọi thứ công khai.repository,bugs,homepage: hiển thị trên trang của package trong registry npm.bin: nếu package của bạn có kèm một CLI, hãy ánh xạ tên lệnh tới các tệp script ở đây. Sau khi cài đặt, các lệnh đó sẽ chạy được.workspaces: dành cho monorepo; báo cho npm coi các thư mục con là các package được liên kết với nhau.
Bạn không cần tất cả các trường này. Bạn cần đúng những trường phù hợp với việc mình đang làm.
Những lỗi thường gặp
Vài điều khiến nhiều người vấp:
- Commit
node_modules. Đừng làm vậy. Hãy thêm nó vào.gitignore.package.jsoncùngpackage-lock.jsonlà đủ để bất kỳ ai dựng lại nó bằngnpm install. - Không commit
package-lock.json. Hãy commit tệp này. Thiếu lockfile, chuyện "máy tôi chạy được" sẽ thực sự xảy ra, vì khoảng phiên bản semver có thể phân giải thành các phiên bản khác nhau theo thời gian. - Đặt dependency cần khi chạy vào
devDependencies. Ứng dụng có thể chạy được trên máy bạn vì dev dependency đã được cài, rồi hỏng trên môi trường production nơi chúng bị bỏ qua. Nếu code bạn đưa lên production dùng tới package nào, package đó thuộc vềdependencies. - Sửa phiên bản bằng tay mà không cài lại. Hãy đổi phiên bản trong
package.jsonrồi chạynpm install, nếu khôngnode_modulesvà lockfile sẽ lệch nhau.
Tiếp theo: môi trường chạy Node
package.json cho Node biết dự án của bạn là gì. Môi trường chạy Node quyết định nó chạy như thế nào: cách phân giải module, các module có sẵn, biến toàn cục, vòng lặp sự kiện bên dưới. Đó là nội dung của trang tiếp theo.
Câu hỏi thường gặp
package.json dùng để làm gì?
Đây là tệp kê khai (manifest) của một dự án Node.js. Tệp ghi lại tên và phiên bản dự án, các package mà dự án phụ thuộc, những script bạn có thể chạy bằng npm run, cùng các siêu dữ liệu như điểm vào và kiểu module. npm install đọc tệp này để quyết định cần tải những gì.
dependencies và devDependencies khác nhau thế nào?
dependencies là các package mà code của bạn cần khi chạy, chẳng hạn express hay react. devDependencies chỉ cần trong lúc phát triển hoặc build: công cụ chạy test, bundler, linter. Khi ai đó cài package của bạn làm dependency cho dự án của họ, npm sẽ bỏ qua devDependencies của bạn.
Dấu ^ và ~ trong phiên bản ở package.json nghĩa là gì?
Đó là các toán tử khoảng phiên bản của semver. ^1.2.3 cho phép mọi phiên bản 1.x.x từ 1.2.3 trở lên (cùng major). ~1.2.3 chặt chẽ hơn: cho phép 1.2.x từ 1.2.3 trở lên (cùng minor). Một phiên bản không có ký hiệu như 1.2.3 sẽ cố định chính xác phiên bản đó. package-lock.json ghi lại phiên bản chính xác đã được chọn để các lần cài đặt luôn cho kết quả giống nhau.
Làm sao tạo tệp package.json?
Chạy npm init trong một thư mục trống và trả lời các câu hỏi, hoặc chạy npm init -y để chấp nhận giá trị mặc định và có ngay một tệp. Bạn cũng có thể tự viết tay, vì nó chỉ là JSON. Hai trường thực sự bắt buộc duy nhất là name và version.