Khi tìm hiểu về JavaScript với Node.js, một trong những khái niệm đầu tiên chúng ta gặp phải chính là "dependency" - các thư viện và những gói code đã được đóng gói và cung cấp miễn phí giúp cho việc phát triển ứng dụng một cách nhanh chóng và dễ dàng hơn. Để quản lý "mớ bòng bong" này, chúng ta cần đến các trình quản lý gói (package manager). Trong rất nhiều năm, npm (Node Package Manager) đã trở nên quen thuộc nhờ sự tiện lợi, đơn giản trong quá trình quản lý gói mỗi dự án JavaScript, đi kèm với mọi bản cài đặt Node.js.
Tuy nhiên, trong một vài năm gần đây, pnpm (performant npm) đang dần được các nhà phát triển tin dùng vì sự tối ưu cũng như giải quyết những vấn đề cố hữu liên quan tới hiệu suất và không gian lưu trữ của npm.
Mặc dù cùng chung mục đích, nhưng cách thức các công cụ này xử lý việc cài đặt gói, giải quyết phụ thuộc và quản lý không gian lưu trữ lại có những khác biệt đáng kể. Việc hiểu rõ những điểm khác biệt này có thể giúp chúng ta đưa ra lựa chọn sáng suốt cho dự án cụ thể của mình.
1. Tổng quan về npm và pnpm
Không giống như “người em” sinh sau đẻ muộn pnpm của mình, npm được ra mắt từ năm 2010 là công cụ quản lý gói chính thức đi kèm với Node.js, điều này có nghĩa là khi người dùng thực hiện cài đặt Node.js lên máy của mình thì phiên bản npm tương ứng cũng được cài đặt cùng lúc.

Tới với pnpm, để sử dụng cho dự án của mình chúng ta có thể cài đặt pnpm thông qua npm 😀, nghe thì như là “đối thủ” nhưng để sử dụng được thì pnpm vẫn cần npm như là một cách nhanh nhất để cài đặt và chạy được trên máy của chúng ta.

Được rồi, hãy đi vào tìm hiểu chi tiết từng công cụ để biết chúng hoạt động thế nào. Sự khác biệt cốt lõi của chúng trong việc quản lý thư mục node_modules ra sao.
npm: (Node Package Manager)
Kể từ khi phiên bản 3 được phát hành vào năm 2015, npm đã áp dụng một chiến lược tạo ra cây thư mục node_modules phẳng (flat). Điều này có nghĩa là tất cả các dependency, dù là trực tiếp hay gián tiếp, đều được "kéo" lên cấp cao nhất của node_modules.
Mô hình hoạt động:
- Ưu điểm:
- Giải quyết vấn đề cấu trúc phụ thuộc lồng nhau (nested dependency structure) của các phiên bản cũ, nơi các gói bị lồng vào nhau rất sâu và bị trùng lặp trong cùng một dự án.
- Cung cấp tính năng khóa phiên bản phụ thuộc (lockfile) thông qua tệp package-lock.json giúp đồng bộ các phụ thuộc sử dụng 1 phiên bản nhất quán giữa các môi trường khác nhau.
- Nhược điểm: Dẫn đến hai vấn đề lớn:
- Lãng phí dung lượng lưu trữ: Nếu bạn có 10 dự án cùng sử dụng dotenv, bạn sẽ có 10 bản sao của dotenv trên ổ cứng.
- Phantom Dependencies (Phụ thuộc "ma"): Code của bạn có thể truy cập vào các gói là dependency của dependency khác mà không hề được khai báo trong package.json. Điều này rất nguy hiểm vì khi một dependency được cập nhật và không còn phụ thuộc vào gói "ma" đó nữa, dự án của bạn sẽ bị lỗi mà không rõ nguyên nhân.
Cấu trúc cây thư mục dự án với npm:

pnpm: (Performant npm)
pnpm ra đời với một triết lý hoàn toàn khác: Nhanh và tiết kiệm dung lượng. Nó làm được điều này nhờ một kiến trúc sáng tạo sử dụng liên kết (links).
Mô hình hoạt động:
- Kho lưu trữ toàn cục (Global Store): Thay vì tải về các gói cho từng dự án, pnpm lưu mỗi phiên bản của một gói chỉ một lần duy nhất vào một kho lưu trữ chung trên máy tính của bạn (thường ở ~/.pnpm-store).
- Liên kết cứng (Hard Links): Khi bạn cài đặt một gói trong dự án, pnpm không sao chép file. Thay vào đó, nó tạo ra các liên kết cứng từ dự án của bạn đến kho lưu trữ toàn cục. Một liên kết cứng về cơ bản là một "tên gọi khác" cho cùng một file và không hề tốn thêm dung lượng.
- Liên kết tượng trưng (Symbolic Links): Để Node.js có thể tìm thấy các gói, pnpm sử dụng các liên kết tượng trưng (lối tắt) trong thư mục node_modules. Chỉ các dependency trực tiếp mới có "lối tắt" ở cấp cao nhất, giải quyết triệt để vấn đề "phantom dependencies".
- Lockfile: Tương tự như package-lock.json của npm, pnpm sử dụng tệp pnpm-lock.yaml để khóa các phiên bản phụ thuộc, đảm bảo tính nhất quán trong mọi môi trường.
Cấu trúc cây thư mục dự án với pnpm:

Quản lý phiên bản khác nhau cho từng dự án:
Vậy trong trường hợp có nhiều hơn một dự án cùng sử dụng chung một dependency, nhưng vấn đề ở đây là các phiên bản trên mỗi dự án lại khác nhau (ví dụ: dotenv), pnpm đưa ra cách giải quyết vấn đề này một cách hiệu quả thông qua việc lưu trữ tập trung và sử dụng symlinks (liên kết tượng trưng):
1. Lưu trữ Tập trung (~/.pnpm-store): pnpm sẽ lưu trữ cả dotenv@10.0.0 và dotenv@16.0.0 riêng biệt trong kho lưu trữ tập trung của nó (~/.pnpm-store). Điều này có nghĩa là các tệp thực tế của dotenv chỉ được lưu trữ một lần cho mỗi phiên bản, thay vì bị trùng lặp trong từng dự án.

2. Liên kết Tượng trưng (Symlinks) trong Dự án:
Khi Dự án A cài đặt dotenv@10.0.0, pnpm sẽ tạo một liên kết tượng trưng trong thư mục node_modules của Dự án A, trỏ trực tiếp đến dotenv@10.0.0 trong kho lưu trữ tập trung.

Tương tự, khi Dự án B cài đặt dotenv@16.0.0, pnpm sẽ tạo một liên kết tượng trưng trong thư mục node_modules của Dự án B, trỏ đến dotenv@16.0.0 trong kho lưu trữ tập trung.

Nhờ cơ chế này, pnpm tiết kiệm không gian đĩa đáng kể và đảm bảo tính nhất quán giữa các dự án mà không cần phải tải xuống hoặc sao chép cùng một gói nhiều lần, ngay cả khi chúng là các phiên bản khác nhau.
2. Ưu và Nhược điểm
Tiêu chí | npm | pnpm |
Dung lượng lưu trữ | (Nhược điểm) Cao. Mỗi dự án là một bản sao riêng của các dependency, gây chiếm dụng ổ cứng lưu trữ lớn khi có nhiều dự án. | (Ưu điểm) Rất thấp. Các gói được chia sẻ trên toàn bộ máy tính thông qua một kho lưu trữ duy nhất. Tiết kiệm hàng GB dung lượng ổ cứng. |
Tốc độ cài đặt | (Nhược điểm) Chậm tới trung bình. Phải sao chép rất nhiều file từ cache vào node_modules của từng dự án. | (Ưu điểm) Rất nhanh. Tốc độ nhanh hơn npm từ 2-3 lần. Việc tạo liên kết nhanh hơn rất nhiều so với việc sao chép file. |
Quản lý Dependency | (Nhược điểm) Cấu trúc phẳng, dễ gây ra "phantom dependencies". Dự án có thể bị phụ thuộc vào các gói không được khai báo. | (Ưu điểm) Cấu trúc liên kết chặt chẽ. Giải quyết triệt để "phantom dependencies", giúp code sạch hơn và đáng tin cậy hơn. |
Hệ sinh thái | (Ưu điểm) Lớn nhất. Là mặc định, có cộng đồng hỗ trợ khổng lồ và tương thích với gần như mọi công cụ. | (Bình thường) Tương thích 100% với npm registry. Các câu lệnh cũng tương tự, dễ dàng chuyển đổi. Cộng đồng đang phát triển rất mạnh. |
3. Nên sử dụng công cụ nào cho dự án của mình
Hãy tưởng tượng bạn làm việc trên 3 dự án microservice: Services X, Services Y, và Services Z. Cả 3 dự án này đều cần sử dụng các thư viện phổ biến như express, dotenv, và axios ở các phiên bản gần giống nhau.
- Nếu dùng npm: Ổ cứng của bạn sẽ phải chứa 3 bản sao đầy đủ của express, dotenv, và axios. Khi bạn chạy npm install trên cả 3 dự án, thời gian chờ đợi sẽ nhân lên 3 lần. Tổng dung lượng của node_modules có thể lên đến hàng GB.
- Nếu dùng pnpm: Cả 3 thư viện trên sẽ chỉ được tải về một lần duy nhất và lưu vào kho toàn cục. Cả 3 dự án sẽ chỉ tạo các liên kết nhẹ đến kho này.
- Dẫn chứng cụ thể: Quá trình pnpm install lần đầu có thể tương đương npm, nhưng từ dự án thứ hai trở đi, tốc độ sẽ cực nhanh vì không cần tải lại bất cứ thứ gì. Dung lượng node_modules của 3 dự án cộng lại sẽ chỉ nhỉnh hơn một chút so với 1 dự án.
Đề xuất:
- Đối với các dự án mới, các hệ thống microservices, hoặc nếu bạn là lập trình viên làm nhiều dự án cùng lúc: Hãy chọn pnpm. Những lợi ích về tốc độ, tiết kiệm dung lượng và sự chặt chẽ trong quản lý dependency là không thể bàn cãi. Nó sẽ giúp bạn tiết kiệm thời gian và hàng GB tài nguyên về lâu về dài.
- Đối với các dự án cũ (legacy) đang hoạt động ổn định hoặc trong môi trường doanh nghiệp có quy định chặt chẽ về công cụ: Bạn có thể tiếp tục gắn bó với npm. Tuy nhiên, việc chuyển đổi sang sử dụng pnpm cũng rất dễ dàng với lệnh pnpm import để chuyển từ package-lock.json sang pnpm-lock.yaml.
4. Kết luận
Trong nội dung lần này, chúng ta đã cùng nhau tìm hiểu về thành phần cũng như luồng hoạt động của 2 công cụ quản lý gói trong hệ sinh thái Node.js. Có thể coi npm giống như một con đường quốc lộ quen thuộc, đáng tin cậy mà ai cũng biết đi. Công cụ này đã phục vụ cộng đồng rất tốt trong nhiều năm cho tới hiện tại và cả tương lai. Tuy nhiên, pnpm xuất hiện như một tuyến đường cao tốc hiện đại, cho phép bạn đi đến đích nhanh hơn, mượt mà hơn và tốn ít "nhiên liệu" (dung lượng ổ cứng) hơn.
Cuối cùng, giữ npm và pnpm, mỗi trình quản lý gói với những cơ chế xử lý dependency và tối ưu hóa hiệu năng riêng, đều đưa ra những lợi ích và sự đánh đổi khác nhau. Việc hiểu rõ kiến trúc nền tảng của chúng sẽ là chìa khóa để đưa ra quyết định kỹ thuật phù hợp nhất với vòng đời và yêu cầu của từng dự án.