Ketergantungan JavaScript Road to Hell

Setiap proyek JavaScript dimulai dengan niat baik, yaitu bahwa pembuatnya berjanji untuk tidak menggunakan terlalu banyak paket NPM selama pengembangannya. Tetapi bahkan jika para pengembang berusaha keras untuk menepati janji ini, paket-paket NPM secara bertahap menembus proyek-proyek mereka. Ukuran file package.jsonbertambah seiring waktu. Dan dengan package-lock.jsonpemasangan dependensi, kengerian nyata terjadi, dinyatakan dalam penambahan dan penghapusan paket, terutama terlihat dengan PR berikutnya ... "Semuanya baik-baik saja," kata pemimpin tim. Anggota tim lainnya mengangguk setuju. Apa lagi yang harus dilakukan? Kita semua menikmati kenyataan bahwa ekosistem JavaScript hidup dan sehat. Kami tidak perlu menemukan kembali roda setiap saat dan mencoba memecahkan masalah yang telah dipecahkan oleh komunitas open source.





Misalkan Anda akan membuat blog dan ingin menggunakan Gatsby.js. Coba tambahkan penghasil situs ini berdasarkan proyek Anda. Sekarang, selamat. Proyek Anda baru saja memiliki 19.000 dependensi tambahan. Ini normal? Seberapa kompleks pohon ketergantungan JavaScript menjadi? Bagaimana pohon ketergantungan berubah menjadi neraka? Mari kita cari tahu.

Apa itu paket JavaScript?


NPM (Node Package Manager, Node Package Manager) menyimpan paket registri terbesar di dunia. Ini adalah paket JavaScript. NPM lebih dari gabungan RubyGems, PyPi, dan Maven. Kesimpulan ini dapat dibuat berdasarkan analisis data proyek Jumlah Modul , yang memantau jumlah paket di pendaftar populer.


Data tentang jumlah paket dalam pendaftar populer

Anda mungkin berpikir bahwa jumlah kode yang sangat besar terwakili pada grafik ini. Seperti itu. Untuk mengubah proyek menjadi paket NPM, paket ini harus memiliki filepackage.json. Paket semacam itu dapat dikirim ke registri NPM.

Apa itu package.json?


Berikut adalah tugas-tugas yang diselesaikan package.json:

  • Ini mencantumkan paket yang bergantung pada proyek Anda (ini adalah daftar dependensi proyek).
  • Di dalamnya, menggunakan aturan versi semantik, versi paket dependensi yang dapat digunakan proyek Anda ditetapkan.
  • Ini memungkinkan Anda untuk mereproduksi lingkungan yang diperlukan agar paket dapat berfungsi, dan, sebagai hasilnya, menyederhanakan transfer proyek ke pengembang lain.

File package.jsondapat dianggap sebagai file yang READMEdipompa dengan steroid. Di sini Anda dapat menggambarkan dependensi paket Anda, di sini Anda dapat menulis skrip yang dieksekusi selama perakitan dan pengujian proyek. File yang sama berisi informasi tentang versi proyek yang ditentukan oleh pengembangnya dan deskripsi proyek. Kami secara khusus tertarik pada kemungkinan package.jsonmenentukan ketergantungan proyek.

Mungkin fakta bahwa ketergantungan proyek ditunjukkan dalam file ini terlihat agak mengkhawatirkan. Bayangkan ada paket yang tergantung pada paket lain, dan paket lain ini tergantung paket lain. Rantai ketergantungan seperti itu bisa panjang dan sewenang-wenang. Untuk alasan ini, menginstal satu-satunya paket, Gatsby.js, berarti melengkapi proyek dengan 19.000 dependensi tambahan.

Ketergantungan tipe dalam package.json


Untuk lebih memahami bagaimana daftar ketergantungan proyek tumbuh dari waktu ke waktu, mari kita bicara tentang berbagai jenis ketergantungan yang mungkin dimiliki suatu proyek. Yaitu, bagian package.jsonberikut dapat ditemukan yang menggambarkan berbagai dependensi:

  • dependencies - ini adalah dependensi biasa, fungsi yang digunakan dalam proyek, dan yang diakses dari kodenya.
  • devDependencies- ini adalah dependensi pengembangan. Misalnya, perpustakaan yang lebih cantik yang digunakan untuk memformat kode.
  • peerDependencies - jika dependensi ditulis ke bagian ini, pengembang paket dengan demikian memberi tahu orang yang akan menginstalnya bahwa ia akan memerlukan versi spesifik dari paket yang ditentukan dalam bagian ini.
  • optionalDependencies - mereka mencantumkan dependensi opsional, seperti, ketidakmampuan untuk menginstal yang tidak akan melanggar proses instalasi paket.
  • bundledDependencies β€” , . , NPM, , .

package-lock.json


Kita semua tahu bahwa file tersebut package-lock.json, dalam proses pengerjaan proyek, terus mengalami perubahan. Sesuatu dihapus darinya, sesuatu ditambahkan padanya. Ini terutama terlihat ketika melihat PR yang berisi versi terbaru dari file ini. Kita sering menerima begitu saja. File package-lock.jsonsecara otomatis dihasilkan setiap kali file package.jsonatau folder berubah node_modules. Ini memungkinkan Anda untuk mempertahankan konten pohon dependensi persis seperti ketika Anda menginstal dependensi proyek. Ini memungkinkan, ketika menginstal proyek, untuk mereproduksi pohon dependensi. Ini memecahkan masalah memiliki versi berbeda dari paket yang sama dari pengembang yang berbeda.

Pertimbangkan sebuah proyek yang dependensinya termasuk Bereaksi. Entri yang sesuai tersedia di package.json. Jika Anda melihat filepackage-lock.json dari proyek ini, maka Anda dapat melihat sesuatu seperti berikut:

    "react": {
      "version": "16.13.0",
      "resolved": "https://registry.npmjs.org/react/-/react-16.13.0.tgz",
      "integrity": "sha512-TSavZz2iSLkq5/oiE7gnFzmURKZMltmi193rm5HEoUDAXpzT9Kzw6oNZnGoai/4+fUnm7FqS5dwgUL34TujcWQ==",
      "requires": {
        "loose-envify": "^1.1.0",
        "object-assign": "^4.1.1",
        "prop-types": "^15.6.2"
      }
    }

File package-lock.jsonadalah daftar besar dependensi proyek. Berikut adalah versi dependensi, jalur (URI) ke modul, hash yang digunakan untuk memverifikasi integritas modul dan paket yang dibutuhkan oleh modul ini. Jika Anda membaca file ini, Anda dapat menemukan catatan dari semua paket yang Bereaksi perlu. Di sinilah neraka ketergantungan yang sebenarnya berada. Segala yang dibutuhkan proyek dijelaskan di sini.

Memahami dependensi Gatsby.js


Bagaimana itu, setelah menetapkan hanya satu dependensi, kami menambahkan sebanyak 19.000 dependensi ke proyek? Ini semua tentang dependensi ketergantungan. Itulah sebabnya kami memiliki apa yang kami miliki:

$ npm install --save gatsby

...

+ gatsby@2.19.28
added 1 package from 1 contributor, removed 9 packages, updated 10 packages and audited 19001 packages in 40.382s

Jika Anda melihat package.json, di sana Anda hanya dapat menemukan satu ketergantungan. Tetapi jika Anda melihatnya package-lock.json, ternyata di depan kami ada monster berukuran hampir 14 kilobyte. Jawaban yang lebih terperinci tentang apa arti semua baris kode itu package-lock.json, dapat ditemukan dalam file package.jsondi repositori Gatsby.js . Ada banyak dependensi langsung, yaitu, menurut perhitungan npm , 132. Jika masing-masing dependensi ini memiliki setidaknya satu dependensi lagi, maka jumlah total dependensi proyek akan berlipat ganda - dan akan memiliki 264 dependensi. Tentu saja, di dunia nyata tidak demikian. Setiap ketergantungan proyek langsung memiliki lebih dari 1 ketergantungan bawaan. Akibatnya, daftar dependensi proyek sangat panjang.

Misalnya, kami akan tertarik pada berapa kali perpustakaan lodash digunakan sebagai ketergantungan untuk paket lain :

$ npm ls lodash
example-js-package@1.0.0
└─┬ gatsby@2.19.28
  β”œβ”€β”¬ @babel/core@7.8.6
  β”‚ β”œβ”€β”¬ @babel/generator@7.8.6
  β”‚ β”‚ └── lodash@4.17.15  deduped
  β”‚ β”œβ”€β”¬ @babel/types@7.8.6
  β”‚ β”‚ └── lodash@4.17.15  deduped
  β”‚ └── lodash@4.17.15  deduped
  β”œβ”€β”¬ @babel/traverse@7.8.6
  β”‚ └── lodash@4.17.15  deduped
  β”œβ”€β”¬ @typescript-eslint/parser@2.22.0
  β”‚ └─┬ @typescript-eslint/typescript-estree@2.22.0
  β”‚   └── lodash@4.17.15  deduped
  β”œβ”€β”¬ babel-preset-gatsby@0.2.29
  β”‚ └─┬ @babel/preset-env@7.8.6
  β”‚   β”œβ”€β”¬ @babel/plugin-transform-block-scoping@7.8.3
  β”‚   β”‚ └── lodash@4.17.15  deduped
  β”‚   β”œβ”€β”¬ @babel/plugin-transform-classes@7.8.6
  β”‚   β”‚ └─┬ @babel/helper-define-map@7.8.3
  β”‚   β”‚   └── lodash@4.17.15  deduped
  β”‚   β”œβ”€β”¬ @babel/plugin-transform-modules-amd@7.8.3
  β”‚   β”‚ └─┬ @babel/helper-module-transforms@7.8.6
  β”‚   β”‚   └── lodash@4.17.15  deduped
  β”‚   └─┬ @babel/plugin-transform-sticky-regex@7.8.3
  β”‚     └─┬ @babel/helper-regex@7.8.3
  β”‚       └── lodash@4.17.15  deduped
  ...

Untungnya, sebagian besar dependensi ini diwakili oleh versi yang sama dari lodash. Dan dengan pendekatan ini, hanya node_modulesakan ada satu folder perpustakaan lodash. Benar, ini biasanya tidak sepenuhnya benar. Terkadang paket yang berbeda memerlukan versi berbeda dari paket yang sama. Itu sebabnya banyak lelucon muncul tentang ukuran besar folder node_modules. Namun, dalam kasus kami, semuanya tidak terlalu buruk:

$ du -sh node_modules
200M    node_modules

200 megabita tidak terlalu buruk. Saya melihat bagaimana ukuran folder ini dengan mudah mencapai 700 MB. Jika Anda tertarik mempelajari modul mana yang paling memakan ruang, Anda dapat menjalankan perintah berikut:

$ du -sh ./node_modules/* | sort -nr | grep '\dM.*'
 17M    ./node_modules/rxjs
8.4M    ./node_modules/@types
7.4M    ./node_modules/core-js
6.8M    ./node_modules/@babel
5.4M    ./node_modules/gatsby
5.2M    ./node_modules/eslint
4.8M    ./node_modules/lodash
3.6M    ./node_modules/graphql-compose
3.6M    ./node_modules/@typescript-eslint
3.5M    ./node_modules/webpack
3.4M    ./node_modules/moment
3.3M    ./node_modules/webpack-dev-server
3.2M    ./node_modules/caniuse-lite
3.1M    ./node_modules/graphql
...

Ya, rxjs adalah paket berbahaya.

Ini adalah perintah sederhana yang membantu mengurangi ukuran folder node_modulesdan menyederhanakan strukturnya:

$ npm dedup
moved 1 package and audited 18701 packages in 4.622s

51 packages are looking for funding
  run `npm fund` for details

found 0 vulnerabilities

Selama deduplikasi, npm mencoba menyederhanakan struktur pohon dependensi dengan menemukan dependensi yang digunakan oleh dependensi lain dan memindahkannya sehingga mereka dapat dibagikan. Ini berlaku untuk contoh lodash kami. Banyak paket digunakan lodash @4.17.15, sebagai hasilnya, untuk memastikan operabilitasnya, cukup untuk menginstal versi perpustakaan ini hanya sekali. Tentu saja, ini adalah situasi yang kita hadapi sejak awal, hanya dengan membangun ketergantungan. Jika dalam proses mengerjakan proyek package.jsonmenambah ketergantungan baru, kadang-kadang disarankan untuk mengingat tim npm dedup. Jika Anda menggunakan manajer paket benang, ada perintah yang mirip seperti dedupe benang. Tetapi, pada kenyataannya, tidak perlu untuk itu, karena optimasi ketergantungan dilakukan secara otomatis ketika perintah dijalankan yarn install.

Visualisasi Ketergantungan


Tertarik pada representasi grafis dari dependensi proyek Anda? Jika demikian, Anda dapat membuat presentasi seperti itu menggunakan alat khusus. Mari kita pertimbangkan beberapa di antaranya.

Berikut ini adalah hasil visualisasi ketergantungan yang diperoleh menggunakan npm.anvaka.com/ .


Visualisasi ketergantungan menggunakan npm.anvaka.com.

Di sini Anda dapat melihat dependensi paket proyek Gatsby.js. Hasilnya mirip dengan web besar. Proyek Gatsby.js memiliki banyak dependensi sehingga "web" ini hampir "menggantung" browser saya. Sekarang , jika tertarik, tautan ke diagram ini. Itu bisa disajikan dalam bentuk 3D.

Berikut ini visualisasi yang dibuat menggunakan npm.broofa.com .


Sebuah fragmen dari visualisasi ketergantungan yang dilakukan menggunakan npm.broofa.com

Ini mirip dengan diagram alur. Dia, bagi Gatsby.js, ternyata sangat rumit. Anda dapat melihatnya di sini . Elemen sirkuit dapat diwarnai berdasarkan perkiraan dari npms.io . Anda dapat mengunggah file Anda sendiri ke situspackage.json.

Alat Paket Phobia memungkinkan Anda untuk mengetahui berapa banyak ruang yang dibutuhkan sebelum menginstal paket.


Informasi Paket yang Diterima Menggunakan Paket Phobia

Di sini Anda dapat mengetahui tentang ukuran paket yang dipublikasikan, dan berapa banyak ruang disk yang diperlukan setelah instalasi.

Intinya: dengan kekuatan besar datang tanggung jawab besar


Pada akhirnya, saya ingin mengatakan bahwa JavaScript dan NPM adalah alat yang hebat. Hal yang baik adalah bahwa pengembang modern memiliki kesempatan untuk menggunakan serangkaian besar dependensi. Menjalankan perintah npm installuntuk menyelamatkan diri dari menulis beberapa baris kode sangat mudah sehingga kadang-kadang kita melupakan konsekuensinya.

Sekarang Anda telah membaca hingga titik ini, Anda harus memiliki pemahaman yang lebih lengkap tentang fitur-fitur struktur pohon dependensi npm-proyek. Jika Anda menambahkan perpustakaan ke proyek yang sangat besar, atau jika Anda hanya menjelajahi dependensi proyek Anda, Anda selalu dapat mengambil keuntungan dari apa yang telah kita bahas di sini dan menganalisis dependensi.

Pembaca yang budiman! Apakah Anda ingin menggunakan dependensi sesedikit mungkin dalam proyek npm Anda?


All Articles