Tại sao package-lock.json tồn tại và cách nó hoạt động

Tại sao package-lock.json tồn tại và cách nó hoạt động

package-lock.json là gì? Tại sao lại có file package-lock.json này trong khi chúng ta đã có package.json?

Đây có lẽ là thắc mắc của rất nhiều bạn (trong đó có mình ngày xưa), 2 file này rất dễ gây nhầm lẫn luôn. Đừng lo, đọc xong bài này bạn sẽ được "thông não".

Đối với công dụng yarn.lock cũng tương tự như package-lock.json nhé, chỉ là format nó khác thôi

Khi sử dụng nodejs hay npm để quản lý các thư viện javascript, chúng ta sẽ có file package.json chứa version các dependency và devDepedency (hay còn gọi là package) mà chúng ta dùng trong dự án.

Nếu chúng ta xây dựng một RESTful API bằng Express, chúng ta thực hiện câu lệnh npm install express

Sau đó, file package.json của chúng ta sẽ thêm có express với version là 4.0.0 trong mục dependencies.

{
  "name": "my-app",
  "version": "1.0.0",
  "main": "index.js",
  "dependency": {
    "express": "4.0.0"
  }
}

Ứng dụng bạn cài express

Ứng dụng bạn cài `express`

Khi chúng ta cài đặt thư viện Express, cũng có nghĩa là chúng ta cài những thư viện mà Express dùng đến. Ví dụ Express dùng thư viện send để thực hiện chức năng static file server.

express dùng send nên ứng dụng của bạn cũng tải và cài send

`express` dùng `send` nên ứng dụng của bạn cũng tải và cài `send`

Thêm nữa, package send lại cần một package khác để hoạt động, đó là mime-types. Vì thế npm sẽ cài mime-types.

package send lại dùng mime-types, vậy là trong folder node_modules có thêm folder mime-types

package `send` lại dùng `mime-types`, vậy là trong folder node_modules có thêm folder mime-types

Cuối cùng khi cài đặt mime-types, npm nhận ra rằng mime-types cần mime-db, và nó cũng tải luôn mime-db

npm tải package mime-db vì nó là dependency của những dependency khác

npm tải package `mime-db` vì nó là dependency của những dependency khác

Nó giống như một quá trình đệ quy vậy, nó sẽ tải xuống các dependency của bạn và cả dependency của dependency. Đó là lý do folder node_modules của bạn rất nặng.

Ngay cả việc bạn chỉ cài một dependency express thôi, nếu bạn kiểm tra thư mục node_modules - thư mục mà npm lưu trữ những dependency của bạn - bạn sẽ thấy nhiều package tại đây.

▾ node_modules/
  ▸ accepts/
  ▸ array-flatten/
  ▸ body-parser/
  ▸ bytes/
  ▸ content-disposition/
  ▸ content-type/
  ▸ cookie-signature/
  ▸ cookie/
  ▸ debug/
  ▸ depd/
  ▸ destroy/
  ▸ ee-first/
  ▸ encodeurl/
  ▸ escape-html/
  ▸ etag/
  ▸ express/
  ▸ finalhandler/
  ▸ forwarded/
  ▸ fresh/
  ▸ http-errors/
  ▸ iconv-lite/
  ▸ inherits/
  ▸ ipaddr.js/
  ▸ media-typer/
  ▸ merge-descriptors/
  ▸ methods/
  ▸ mime-db/
  ▸ mime-types/
  ▸ mime/
  ▸ ms/
  ▸ negotiator/
  ▸ on-finished/
  ▸ parseurl/
  ▸ path-to-regexp/
  ▸ proxy-addr/
  ▸ qs/
  ▸ range-parser/
  ▸ raw-body/
  ▸ safe-buffer/
  ▸ safer-buffer/
  ▸ send/
  ▸ serve-static/
  ▸ setprototypeof/
  ▸ statuses/
  ▸ toidentifier/
  ▸ type-is/
  ▸ unpipe/
  ▸ utils-merge/
  ▸ vary/
  package-lock.json
  package.json

Giả sử nếu không có file package-lock.json và bạn chạy npm install vào buổi chiều, bạn có thể nhận được những dependency khác so với buổi sáng, dù cho bạn không thay đổi verson express trong package.json

Câu lệnh npm install có thể cài những package khác version quy định trong file package.json nếu không có file package-lock.json


🥇 Tại sao cùng một package.json nhưng khi npm install lại tải về những dependency khác nhau?

Nếu bạn để ý trong file package.json, version của các dependency sẽ có những tiền tố là ^ hoặc ~. Những tiền tố này nói cho NPM rằng không nhất thiết phải tải về chính xác version như đã quy định. Thay vào đó hãy tải về version "phù hợp" với dependency trong package.json

Ví dụ chúng ta không có file package-lock.json, trong package.json chúng ta có "express": "^4.0.0".

Trong trường hợp này khi chạy npm install thì chúng ta có thể nhận lại express version là 4.0.1 hoặc 4.1.0 (tất nhiên với điều kiện là express đã xuất bản version 4.0.1 hoặc 4.1.0)

Để lý giải cho điều này thì chúng ta cần hiểu về semantic version, mình có thể tóm gọn như thế này

Cấu trúc version có dạng MAJOR.MINOR.PATCH

  • Số version MAJOR đại diện cho những thay đổi lớn, không tương thích với phiên bản trước

  • Số version MINOR đại diện cho những chức năng mới được thêm vào, vẫn tương thích với phiên bản trước

  • Số version PATCH đại diện cho những bản vá lỗi, vẫn tương thích ngược với phiên bản trước

Ngoài ra còn một số prefix (tiền tố) khi những version này được ghi lại trong file package.json đó là

  • ^: Có thể cài version MINOR và PATCH. Ví dụ ^0.13.0 thì có thể cài 0.13.1, 0.14.0

  • ~: Có thể cài version PATCH. Ví dụ ~0.13.0 thì có thể cài 0.13.1 nhưng không thể cài 0.14.0

Oke. Đến đây bạn đã có câu trả lời cho câu hỏi "Tại sao cùng một package.json nhưng khi npm install lại tải về những dependency khác nhau?" rồi đúng không. Đó là do mấy cái tiền tố trước version.

Tại sao package-lock.json tồn tại và cách nó hoạt động

Tiếp tục nào!!!!

Nếu không dùng đến file package-lock.json, sử dụng ^~ có thể không xảy ra vấn đề gì. Vì theo semantic version mà mình đã nói ở trên, các version update sau minor và patch có khả năng tương thích ngược - nghĩa là không có bất kỳ "breaking change" nào với code bạn cả.

Tuy nhiên, là con người ai mà chả có lỗi lầm. Ví dụ, người tạo ra package chỉ sửa một lỗi nhỏ thôi, họ note rằng đây chỉ là một bản PATCH, trong khi đó có thể nó gây ra một sự thay đổi lớn trong code của bạn.

Ví dụ Chai từng đánh số version bị sai dẫn đến hàng triệu bản build trên khắp thế giới bị lỗi.

Vậy nghĩa là không sử dụng ^ hoặc ~ thì npm install sẽ luôn cài đúng phiên bản package đúng không?

Không, không hề 🙃

Ngay cả khi bạn không dùng ^~ trong file package.json của bạn thì những package của bạn nó cũng dùng ^ hoặc ~ để setting version các dependency mà nó phụ thuộc. Vậy nên nếu chạy npm install vẫn có thể tải về những version khác nhau.


🥇 Cách package-lock.json giải quyết vấn đề version không ổn định

Khi bạn cài bất cứ package nào bằng câu lệnh npm install <packagename>, chúng ta sẽ có thêm file package-lock.json.

File package-lock.json sẽ liệt kê hết tất cả những package trong ứng dụng của bạn, bao gồm package của package,... Đó là lý do tại sao file package-lock.json nó lại dài hơn package.json.

  • Nếu bạn chạy chạy câu lệnh npm install mà có cả 2 file package.jsonpackage-lock.json thì bạn yên tâm rằng bạn luôn tải đúng version như đã ghi trong file package-lock.json. File package-lock.json cũng sẽ không bao giờ bị thay đổi

  • Nếu bạn thay đổi bằng tay version hoặc package được ghi trong file package.json và chạy npm install thì lúc này package-lock.json sẽ được cập nhật theo để tương thích với file package.json

Vậy nên nếu có file package-lock.json, bạn sẽ luôn nhận được chính xác version các package của bạn, cho dù đó là hàng ngàn năm sau.

Ngoài npm install, chúng ta còn có một câu lệnh khá là tương đồng là npm ci

npm ci thường được dùng trong các script tự động hóa CI CD.

Sự khác biệt giữa npm installnpm ci là:

  • Nếu những dependency trong package-lock.json không khớp với package.json thì npm ci sẽ ngừng lại và quăng ra lỗi

  • npm ci chỉ có thể cài tất cả các dependency trong project cùng một lúc, không thể cài từng cái như npm install được

  • Nếu node_modules tồn tại, nó sẽ bị xóa trước khi cài npm ci

  • npm ci sẽ không bao giờ viết lên file package.json hoặc package-lock.json

Ví dụ dưới đây sẽ cho bạn thấy công dụng thực tế của npm ci

Nếu có bạn thay đổi bằng tay file package.json ở dưới local, nhưng quên chạy lại câu lệnh npm install, điều này dẫn đến package-lock.json không tương thích với package.json. Bây giờ nếu trên server của bạn có flow là

  1. git pull để pull code mới từ Github về

  2. Sau đó chạy npm install

Nếu với flow này thì sau khi npm install, server bạn sẽ cập nhật file package-lock.json trên server, lúc này package-lock.json ở server khác với trên Github, lần sau bạn sẽ không thể thực hiện theo flow như trên được nữa vì git pull sẽ bị chặn do sự khác nhau giữa package-lock.json ở server và Github

Bạn thấy đấy, dùng npm install trong trường hợp này không hay cho lắm, nếu chúng ta dùng npm ci thì chúng ta sẽ không bao giờ sợ rằng file package-lock.json bị thay đổi, và luôn đảm bảo rằng package-lock.json luôn được đồng bộ với package.json


🥇 Tóm lại

  • File package-lock.json sẽ giúp bạn cố định version khi bạn chạy npm install.

  • Bạn nên đưa package-lock.json vào git, đừng ignore nó để tránh những trường hợp lỗi code do update version.

  • Nếu ở local, khi cài package mới hay tải tất cả package trong project thì dùng npm install.

  • Nếu trên server thì nên dùng npm ci để không change bất cứ thứ gì.

Oke, bài viết đến đây là hết rồi đó, mong rằng bài viết có thể giúp bạn hiểu hơn về công dụng của package-lock.json cũng như là package.json

Trong bài viết mình có tham khảo từ

Nguồn bài viết: https://duthanhduoc.com

Phạm Hồng Đức
Phạm Hồng Đức
Một developer thích nghiên cứu và chia sẻ kiến thức lập trình, phần mềm mã nguồn mở (open-source), và cuộc sống.