Road to Hell JavaScript-Abhängigkeiten

Jedes JavaScript-Projekt beginnt mit guten Absichten, nämlich dass sich seine Entwickler versprechen, während seiner Entwicklung nicht zu viele NPM-Pakete zu verwenden. Aber selbst wenn die Entwickler erhebliche Anstrengungen unternehmen, um dieses Versprechen einzuhalten, dringen NPM-Pakete nach und nach in ihre Projekte ein. Die Dateigröße package.jsonwächst mit der Zeit. Und seit der package-lock.jsonInstallation von Abhängigkeiten tritt ein echtes Entsetzen auf, das sich in Hinzufügungen und Löschungen von Paketen äußert, was sich besonders bei der nächsten PR bemerkbar macht ... "Alles ist in Ordnung", sagt der Teamleiter. Der Rest des Teams nickt zustimmend. Was sonst zu tun? Wir alle genießen die Tatsache, dass das JavaScript-Ökosystem lebendig und gut ist. Wir müssen das Rad nicht jedes Mal neu erfinden und versuchen, die Probleme zu lösen, die bereits von der Open Source-Community gelöst wurden.





Angenommen, Sie erstellen ein Blog und möchten Gatsby.js verwenden. Versuchen Sie, diesen Site-Generator basierend auf Ihrem Projekt hinzuzufügen. Nun herzlichen Glückwunsch. Ihr Projekt hatte gerade 19.000 zusätzliche Abhängigkeiten. Es ist in Ordnung? Wie komplex kann ein JavaScript-Abhängigkeitsbaum werden? Wie wird ein Abhängigkeitsbaum zur Hölle? Lass es uns herausfinden.

Was ist ein JavaScript-Paket?


NPM (Node Package Manager, Node Package Manager) speichert die größte Paketregistrierung der Welt. Dies sind JavaScript-Pakete. NPM ist mehr als RubyGems, PyPi und Maven zusammen. Diese Schlussfolgerung kann auf der Grundlage der Analyse der Daten des Module Counts- Projekts gezogen werden , das die Anzahl der Pakete in populären Registern überwacht.


Daten zur Anzahl der Pakete in gängigen Registern

Sie könnten denken, dass in dieser Grafik sehr große Mengen an Code dargestellt werden. Wie es ist. Um ein Projekt in ein NPM-Paket umzuwandeln, muss dieses Paket eine Datei enthaltenpackage.json. Ein solches Paket kann an die NPM-Registrierung gesendet werden.

Was ist package.json?


Hier sind die Aufgaben, die es löst package.json:

  • Es listet die Pakete auf, von denen Ihr Projekt abhängt (dies ist eine Liste der Projektabhängigkeiten).
  • Darin werden unter Verwendung der Regeln der semantischen Versionierung Versionen von Abhängigkeitspaketen festgelegt, die Ihr Projekt verwenden kann.
  • Sie können die Umgebung reproduzieren, die für das Funktionieren des Pakets erforderlich ist, und vereinfachen dadurch die Übertragung des Projekts an andere Entwickler.

Eine Datei package.jsonkann als eine READMEmit Steroiden gepumpte Datei betrachtet werden . Hier können Sie die Abhängigkeiten Ihres Pakets beschreiben, hier können Sie Skripte schreiben, die während der Montage und des Testens des Projekts ausgeführt werden. Dieselbe Datei enthält Informationen zur vom Entwickler angegebenen Projektversion und eine Beschreibung des Projekts. Wir sind besonders an der Möglichkeit interessiert package.json, Projektabhängigkeiten anzugeben.

Vielleicht sieht die Tatsache, dass die Projektabhängigkeiten in dieser Datei angegeben sind, etwas alarmierend aus. Stellen Sie sich vor, es gibt ein Paket, das von einem anderen Paket abhängt, und dieses andere Paket hängt von einem anderen Paket ab. Eine solche Abhängigkeitskette kann beliebig lang sein. Aus diesem Grund bedeutet die Installation des einzigen Pakets, Gatsby.js, dass das Projekt mit 19.000 zusätzlichen Abhängigkeiten ausgestattet wird.

Abhängigkeitstypen in package.json


Um besser zu verstehen, wie Projektabhängigkeitslisten im Laufe der Zeit wachsen, sprechen wir über die verschiedenen Arten von Abhängigkeiten, die ein Projekt haben kann. Es package.jsonkönnen nämlich die folgenden Abschnitte gefunden werden, die verschiedene Abhängigkeiten beschreiben:

  • dependencies - Dies sind gewöhnliche Abhängigkeiten, deren Funktionalität im Projekt verwendet wird und auf die über den Code zugegriffen wird.
  • devDependencies- Dies sind Entwicklungsabhängigkeiten. Zum Beispiel die schönere Bibliothek, die zum Formatieren von Code verwendet wird.
  • peerDependencies - Wenn Abhängigkeiten in diesen Abschnitt geschrieben werden, informiert der Paketentwickler den Installateur darüber, dass er eine bestimmte Version des in diesem Abschnitt angegebenen Pakets benötigt.
  • optionalDependencies - Sie listen optionale Abhängigkeiten auf, z. B. die Unfähigkeit zur Installation, die den Installationsprozess des Pakets nicht verletzt.
  • bundledDependencies — , . , NPM, , .

package-lock.json


Wir alle wissen, dass die Datei package-lock.jsonim Verlauf der Projektarbeit ständig geändert wird. Etwas wird daraus entfernt, etwas wird hinzugefügt. Dies macht sich insbesondere beim Anzeigen von PR mit einer aktualisierten Version dieser Datei bemerkbar. Wir halten es oft für selbstverständlich. Eine Datei wird package-lock.jsonautomatisch generiert, wenn sich eine Datei package.jsonoder ein Ordner ändert node_modules. Auf diese Weise können Sie den Inhalt des Abhängigkeitsbaums genau so verwalten, wie er bei der Installation der Projektabhängigkeiten war. Auf diese Weise kann bei der Installation des Projekts der Abhängigkeitsbaum reproduziert werden. Dies löst das Problem, unterschiedliche Versionen desselben Pakets von unterschiedlichen Entwicklern zu haben.

Stellen Sie sich ein Projekt vor, dessen Abhängigkeiten React enthalten. Der entsprechende Eintrag ist verfügbar unter package.json. Wenn Sie sich die Datei ansehenpackage-lock.json Von diesem Projekt können Sie dann Folgendes sehen:

    "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"
      }
    }

Eine Datei package-lock.jsonist eine große Liste von Projektabhängigkeiten. Hier finden Sie die Abhängigkeitsversionen, Pfade (URIs) zu den Modulen, Hashes, mit denen die Integrität des Moduls überprüft wird, und die von diesem Modul benötigten Pakete. Wenn Sie diese Datei lesen, finden Sie Aufzeichnungen aller Pakete, die React benötigt. Hier liegt die wahre Hölle der Abhängigkeiten. Hier wird alles beschrieben, was das Projekt benötigt.

Grundlegendes zu Gatsby.js-Abhängigkeiten


Wie kommt es, dass wir, nachdem wir nur eine Abhängigkeit hergestellt haben, dem Projekt bis zu 19.000 Abhängigkeiten hinzufügen? Es geht um Abhängigkeiten. Deshalb haben wir, was wir haben:

$ 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

Wenn Sie nachsehen package.json, finden Sie dort nur eine Abhängigkeit. Aber wenn Sie es sich ansehen package-lock.json, stellt sich heraus, dass vor uns ein fast 14-Kilobyte-Monster steht. Eine detailliertere Antwort darauf, was all diese Codezeilen bedeuten package-lock.json, finden Sie in der Datei package.jsonim Repository von Gatsby.js . Es gibt viele direkte Abhängigkeiten, nämlich nach npm-Berechnungen 132. Wenn jede dieser Abhängigkeiten mindestens eine weitere Abhängigkeit aufweist, verdoppelt sich die Gesamtzahl der Projektabhängigkeiten - und es gibt 264 Abhängigkeiten. In der realen Welt ist das natürlich nicht so. Jede direkte Projektabhängigkeit hat mehr als eine inhärente Abhängigkeit. Infolgedessen ist die Liste der Projektabhängigkeiten sehr lang.

Wir interessieren uns beispielsweise dafür, wie oft die lodash- Bibliothek als Abhängigkeit für andere Pakete verwendet wird :

$ 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
  ...

Glücklicherweise werden die meisten dieser Abhängigkeiten durch dieselbe Version von lodash dargestellt. Bei diesem Ansatz gibt node_moduleses nur einen Ordner für die lodash-Bibliothek. Dies ist normalerweise nicht ganz der Fall. Manchmal benötigen unterschiedliche Pakete unterschiedliche Versionen desselben Pakets. Aus diesem Grund tauchten viele Witze über die enorme Größe des Ordners auf node_modules. In unserem Fall ist jedoch nicht alles so schlecht:

$ du -sh node_modules
200M    node_modules

200 Megabyte sind nicht so schlecht. Ich habe gesehen, wie leicht die Größe dieses Ordners 700 MB erreicht. Wenn Sie erfahren möchten, welche Module am meisten Platz beanspruchen, können Sie den folgenden Befehl ausführen:

$ 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
...

Ja, rxjs ist ein heimtückisches Paket.

Hier ist ein einfacher Befehl, mit dem Sie die Größe des Ordners reduzieren node_modulesund seine Struktur vereinfachen können:

$ 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

Während der Deduplizierung versucht npm, die Struktur des Abhängigkeitsbaums zu vereinfachen, indem die von anderen Abhängigkeiten verwendeten Abhängigkeiten ermittelt und verschoben werden, damit sie gemeinsam genutzt werden können. Dies gilt für unser lodash-Beispiel. Viele Pakete werden daher verwendet lodash @4.17.15, um ihre Funktionsfähigkeit sicherzustellen. Es reicht aus, diese Version der Bibliothek nur einmal zu installieren. Dies ist natürlich die Situation, in die wir von Anfang an geraten, nur indem wir Abhängigkeiten herstellen. Wenn Sie während der Arbeit an einem Projekt package.jsonneue Abhängigkeiten hinzufügen, wird manchmal empfohlen, sich an das Team zu erinnern npm dedup. Wenn Sie den Garnpaket- Manager verwenden, sieht ein ähnlicher Befehl wie Garn-Deduplizierung aus. Tatsächlich besteht jedoch keine Notwendigkeit dafür, da die Abhängigkeitsoptimierung automatisch ausgeführt wird, wenn der Befehl ausgeführt wird yarn install.

Abhängigkeitsvisualisierung


Interessiert an einer grafischen Darstellung der Abhängigkeiten Ihres Projekts? In diesem Fall können Sie eine solche Präsentation mit speziellen Tools erstellen. Betrachten wir einige davon.

Das folgende Ergebnis der Abhängigkeitsvisualisierung wurde mit npm.anvaka.com/ erhalten .


Abhängigkeitsvisualisierung mit npm.anvaka.com.

Hier sehen Sie die Abhängigkeiten der Abhängigkeiten des Gatsby.js-Projektpakets. Das Ergebnis ähnelt einem riesigen Web. Das Gatsby.js-Projekt hat so viele Abhängigkeiten, dass dieses "Web" meinen Browser fast "hängen" ließ. Nun , wenn Sie interessiert sind, einen Link zu diesem Diagramm. Es kann in 3D-Form dargestellt werden.

Hier ist eine Visualisierung, die mit npm.broofa.com erstellt wurde .


Ein Fragment einer Abhängigkeitsvisualisierung, die mit npm.broofa.com erstellt wurde.

Dies ähnelt einem Flussdiagramm. Für Gatsby.js stellt sich heraus, dass sie sehr kompliziert ist. Sie können es hier ansehen. Schaltungselemente können basierend auf Schätzungen von npms.io eingefärbt werden . Sie können Ihre eigene Datei auf die Site hochladenpackage.json.

Mit dem Tool " Package Phobia" können Sie herausfinden, wie viel Speicherplatz benötigt wird, bevor Sie ein Paket installieren.


Mit Package Phobia empfangene Paketinformationen

Hier erfahren Sie, wie groß das veröffentlichte Paket ist und wie viel Speicherplatz nach der Installation benötigt wird.

Fazit: Mit großer Kraft geht große Verantwortung einher


Am Ende möchte ich sagen, dass JavaScript und NPM großartige Werkzeuge sind. Das Gute ist, dass moderne Entwickler die Möglichkeit haben, eine Vielzahl von Abhängigkeiten zu nutzen. Das Ausführen des Befehls npm install, um sich das Schreiben einiger Codezeilen zu ersparen, ist so einfach, dass wir manchmal die Konsequenzen vergessen.

Nachdem Sie bis zu diesem Punkt gelesen haben, sollten Sie die Funktionen der Abhängigkeitsbaumstruktur des npm-Projekts besser verstehen. Wenn Sie dem Projekt eine Bibliothek hinzufügen, die sehr groß ist, oder wenn Sie nur die Abhängigkeiten Ihres Projekts untersuchen, können Sie immer das nutzen, was wir hier besprochen haben, und die Abhängigkeiten analysieren.

Liebe Leser! Möchten Sie in Ihren npm-Projekten so wenig Abhängigkeiten wie möglich verwenden?


All Articles