Po co używać zależności równorzędnych w npm dla wtyczek?

218

Dlaczego na przykład wtyczka Grunt definiuje swoją zależność od chrząstki jako „ zależności równorzędne ”?

Dlaczego wtyczka nie może mieć Grunta jako własnej zależności w chrust-plug / node_modules ?

Zależności rówieśników opisano tutaj: https://nodejs.org/en/blog/npm/peer-dependencies/

Ale tak naprawdę nie rozumiem.

Przykład

Obecnie pracuję ze sterydami AppGyver, które wykorzystują zadania Grunt do budowania plików źródłowych w folderze / dist /, który ma być obsługiwany na urządzeniu lokalnym. Jestem całkiem nowy w npm i chrząkać, więc chcę w pełni zrozumieć, co się dzieje.

Do tej pory rozumiem:

[rootfolder] /package.json mówi npm, że programowanie zależy od grunt-steroidspakietu npm:

  "devDependencies": {
    "grunt-steroids": "0.x"
  },

W porządku. Uruchamianie npm install w [rootfolderze] wykrywa zależność i instaluje sterydy gruntu w [rootfolderze] / node_modules / grunt-sterydy .

Npm następnie odczytuje [rootfolder] /node_modules/grunt-steroids/package.json, aby mógł zainstalować grunt-steroidswłasne zależności:

"devDependencies": {
    "grunt-contrib-nodeunit": "0.3.0",
    "grunt": "0.4.4"
  },
"dependencies": {
    "wrench": "1.5.4",
    "chalk": "0.3.0",
    "xml2js": "0.4.1",
    "lodash": "2.4.1"
  },
"peerDependencies": {
    "grunt": "0.4.4",
    "grunt-contrib-copy": "0.5.0",
    "grunt-contrib-clean": "0.5.0",
    "grunt-contrib-concat": "0.4.0",
    "grunt-contrib-coffee": "0.10.1",
    "grunt-contrib-sass": "0.7.3",
    "grunt-extend-config": "0.9.2"
  },

Pakiety „ zależności ” są instalowane w [rootfolder] / node_modules / grunt-steroids / node_modules, co jest dla mnie logiczne.

DevDependencies ” nie są zainstalowane, co jestem pewien, że jest kontrolowane przez wykrywanie npm, którego próbuję użyć grunt-steroids, a nie rozwijam na nim.

Ale potem mamy „ peerDependencies ”.

Są one instalowane w [rootfolder] / node_modules , a ja nie rozumiem, dlaczego tam jest, a nie w [rootfolder] / node_modules / grunt-steroids / node_modules, aby uniknąć konfliktów z innymi wtyczkami (lub cokolwiek innego)?

Thomas Stock
źródło

Odpowiedzi:

421

TL; DR: [1] peerDependencies dotyczą zależności, które są narażone na (i oczekuje się, że będą używane) zużywającego kodu, w przeciwieństwie do zależności „prywatnych” , które nie są ujawnione, i są jedynie szczegółami implementacyjnymi.

Problemowe zależności równorzędne rozwiązują

System modułów NPM jest hierarchiczny. Jedną wielką zaletą w prostszych scenariuszach jest to, że po zainstalowaniu pakietu npm, pakiet ten ma swoje własne zależności, dzięki czemu będzie działać od razu po wyjęciu z pudełka.

Problemy pojawiają się jednak, gdy:

  • Zarówno twój projekt, jak i niektóre moduły, których używasz, zależą od innego modułu.
  • Trzy moduły muszą ze sobą rozmawiać.

W przykładzie

Powiedzmy, że budujesz YourCoolProjecti używasz zarówno, jak JacksModule 1.0i JillsModule 2.0. Załóżmy, że JacksModulezależy to również od JillsModule, powiedzmy, innej wersji 1.0. Dopóki te 2 wersje się nie spełniają, nie ma problemu. Fakt, że JacksModuleużywa się JillsModulepod powierzchnią, to tylko szczegół implementacji. Łączymy JillsModuledwa razy, ale to niewielka cena do zapłacenia, gdy otrzymamy stabilne oprogramowanie od razu po wyjęciu z pudełka.

Ale co teraz, jeśli w jakiś sposób JacksModuleujawnia swoją zależność JillsModule. Akceptuje na przykład wystąpienie JillsClass... Co się stanie, gdy utworzymy new JillsClassużywaną wersję 2.0biblioteki i przekażemy ją jacksFunction? Całe piekło rozpęta się! Proste rzeczy, takie jak jillsObject instanceof JillsClassnagle, powrócą, falseponieważ w jillsObjectrzeczywistości jest to przypadek innej JillsClass , 2.0wersji.

Jak rozwiązują to zależności rówieśników

Mówią npm

Potrzebuję tego pakietu, ale potrzebuję wersji, która jest częścią projektu, a nie wersji prywatnej dla mojego modułu.

Gdy npm zobaczy, że twój pakiet jest instalowany w projekcie, który nie ma tej zależności lub ma niezgodną wersję , ostrzeże użytkownika podczas procesu instalacji.

Kiedy należy używać zależności równorzędnych?

  • Kiedy budujesz bibliotekę do wykorzystania w innych projektach, i
  • Ta biblioteka korzysta z innej biblioteki i
  • Oczekujesz / potrzebujesz, aby użytkownik pracował również z tą inną biblioteką

Typowe scenariusze to wtyczki dla większych platform. Pomyśl o takich rzeczach jak Gulp, Grunt, Babel, Mocha itp. Jeśli piszesz wtyczkę Gulp, chcesz, aby ta wtyczka działała z tym samym Gulpem, z którego korzysta projekt użytkownika, a nie z twoją prywatną wersją Gulp.


Adnotacje

  1. Za długo; nie czytałem. Służy do wskazywania krótkiego podsumowania tekstu, który uznano za zbyt długi.
Stijn de Witt
źródło
2
Jedną ważną rzecz, którą zauważyłem i której nigdzie nie powiedziałem, kiedy budujemy wtyczkę, czy powinniśmy mieć duplikat zależności pakietów dla zależności równorzędnych? W przykładzie PO widzimy, że dotyczy "grunt": "0.4.4"to zarówno devDependencies, jak i peerDependencies, i sensowne jest dla mnie, aby mieć tam duplikat, ponieważ oznacza to zarówno, że potrzebuję tego gruntpakietu na własny użytek, ale także, że użytkownicy mojego biblioteka może korzystać z własnej wersji, o ile przestrzega blokady wersji peerDependencies. Czy to jest poprawne? A może przykład OP jest bardzo zły?
Vadorequest
4
Mogę sobie wyobrazić ludzi tworzących wtyczkę Grunt, którzy są fanami Grunta :) W związku z tym wydaje się naturalne, że używają Grunta do procesu kompilacji swojej wtyczki ... Ale dlaczego mieliby chcieć zablokować zakres wersji Grunt, aby ich wtyczka działała z procesem kompilacji, którego używają, aby go utworzyć? Dodanie go jako zależności deweloperów pozwala im to oddzielić. Zasadniczo istnieją 2 fazy: czas budowy i czas działania. Zależności deweloperów są potrzebne podczas kompilacji. Wymagane są regularne i równorzędne zależności w czasie wykonywania. Oczywiście przy zależnościach zależności wszystko szybko się myli :)
Stijn de Witt
1
Dziękuję za tę odpowiedź! Właśnie w celu wyjaśnienia, w przykładzie, jeśli JacksModulezależy JillsModule ^1.0.0z JillsModuleczym zależność rówieśnik JacksModulei YourCoolProjectużywaliśmy JacksModulei JillsModule ^2.0.0będziemy dostać ostrzeżenie o zależnościach typu peer przez KMP, który doradzi nam zainstalować JillsModule ^1.0.0również. Ale co się wtedy stanie? YourCoolProjectbędzie teraz mieć dwie wersje do JillsModuleimportowania import jillsModule from "..."? I jak mam pamiętać, że kiedy używam JacksModule, muszę przekazać to wystąpienie JillsModule v1.0.0?
tonix
1
@tonix Cóż, rzeczywiście będzie problem z niekompatybilnością wersji. peerDependencies nie rozwiązuje tego. Ale pomaga to wyjaśnić problem. Ponieważ wyraźnie pokaże niedopasowanie wersji zamiast cicho używać dwóch wersji. Deweloper aplikacji, który wybiera biblioteki, będzie musiał znaleźć rozwiązanie.
Stijn de Witt
2
@tonix Lub trzecia opcja: sklonuj JacksModulerepozytorium, uaktualnij go, aby polegać na nim JillsModule ^2.0.0i zaoferuj PR dla opiekuna projektu. Pomocne może być przesłanie błędu, który mówi, że ta zależność jest nieaktualna i chcesz ją zaktualizować. Jeśli zrobisz dobry PR, większość opiekunów bibliotecznych połączy go i ci za to podziękuje. Jeśli opiekunowie nie reagują, możesz opublikować swój widelec w przestrzeni nazw NPM pod swoim nazwiskiem i zamiast tego użyć widelca. W każdym razie istnieją rozwiązania, ale peerDependenciesnie rozwiązują go same.
Stijn de Witt
26

Polecam najpierw przeczytać ten artykuł. To trochę mylące, ale przykład z Winston-Mail pokazuje odpowiedź, dlaczego:

Na przykład, udawajmy, że [email protected]określono "winston": "0.5.x"w jego "dependencies"obiekcie, ponieważ jest to najnowsza wersja, z którą był testowany. Jako programista aplikacji, chcesz najnowsze i najlepsze rzeczy, więc trzeba sprawdzić najnowsze wersje winstoni winston-maili umieścić je w swoim package.json jako

{
  "dependencies": {  
    "winston": "0.6.2",  
    "winston-mail": "0.2.3"  
  }  
}

Ale teraz uruchomienie instalacji npm powoduje nieoczekiwany wykres zależności

├── winston@0.6.2  
└─┬ winston-mail@0.2.3                
  └── winston@0.5.11

W takim przypadku możliwe jest posiadanie wielu wersji pakietu, co spowodowałoby pewne problemy. Zależności równorzędne pozwalają programistom npm upewnić się, że użytkownik ma określony moduł (w folderze głównym). Ale masz rację z tym, że opis jednej konkretnej wersji pakietu doprowadziłby do problemów z innymi pakietami używającymi innych wersji. Ten artykuł dotyczy programistów npm, jak mówią artykuły

Jedna rada : wymagania dotyczące wzajemnej zależności, w przeciwieństwie do normalnych zależności, powinny być łagodne . Nie powinieneś blokować zależności równorzędnych do konkretnych wersji łatek.

Dlatego programiści powinni stosować semver do definiowania peerDependencies. Powinieneś otworzyć problem dotyczący pakietu sterydów na GitHub ...

Fer To
źródło
1
Mówisz tak, multiple versions of a package which would cause some issuesale czy nie o to chodzi w menedżerze pakietów? Dyskutują nawet o tym dalej w tym samym artykule, w którym w projekcie są 2 wersje tego samego pakietu: jedna dostarczona przez programistę, a druga dostarczona przez bibliotekę zewnętrzną.
Adam Beck
1
Wydaje mi się, że rozumiem sens zależności równorzędnej, ale w tym winstonprzykładzie nie mogę teraz korzystać z winston-mailbiblioteki, ponieważ moja wersja nie pasuje do zależności równorzędnej? Wolałbym tymczasowo obniżyć wersję z najnowszej i najlepszej dla biblioteki 1, niż w ogóle nie móc jej używać.
Adam Beck
1
dla twojego pierwszego komentarza, o ile go rozumiem i używam, ma to związek z testowaniem, np. jeśli masz pakiet, który został przetestowany przez ciebie dla konkretnego pakietu innej firmy, nie możesz być pewien, że jeśli taki zmian w zależnościach (poprawka błędów, aktualizacja głównych funkcji), że Twój pakiet będzie działał. Dlatego możesz określić konkretną wersję wtyczki i zapisać ją wraz z testami.
Fer To
1
W drugim komentarzu: dlatego w dokumentach powiedziano, że programiści powinni być łagodni w zakresie zależności między pakietami i powinni używać semver, np. Zamiast „0.2.1”, „~ 0.2.1” -> zezwalają na „0.2.x”, ale nie „0.3.x” lub „> = 0.2.1” -> wszystko od „0.2.x” do „1.x” lub „x.2.”. .. (ale tak naprawdę nie jest to zalecane dla pakietu npm pójdzie z ~
Fer To
15

peerDependencies wyjaśnione najprostszym możliwym przykładem:

{
  "name": "myPackage",
  "dependencies": {
    "foo": "^4.0.0",
    "react": "^15.0.0"
  }
}


{
  "name": "foo"
  "peerDependencies": {
    "react": "^16.0.0"
  }
}

uruchomienie npm install w myPackage wyśle ​​błąd, ponieważ próbuje zainstalować wersję React ^15.0.0ORAZ, fooktóra jest kompatybilna tylko z React ^16.0.0.

peerDependencies NIE są zainstalowane.

Christopher Tokar
źródło
dlaczego nie po prostu postawić reakcję 16 jako dep wewnątrz foo? w ten sposób dostępne będą zarówno 15, jak i 16, a foo może używać 16, a mackack może używać 15?
nitinsh99
React to środowisko ładowane podczas uruchamiania, aby zarówno React 15, jak i React 16 istniały na tej samej stronie, trzeba je jednocześnie uruchomić, co byłoby niezwykle ciężkie i problematyczne dla użytkownika końcowego. Jeśli foodziała zarówno z React 15, jak i React 16, może wymienić swoją peerDependency jako >=15 < 17.
Jens Bodal,
nitinsh99 moją odpowiedzią było wyjaśnienie celu peerDependencies najprostszym możliwym przykładem, a nie jak pozbyć się błędu zgłaszanego przez peerDependencies
Christopher Tokar
@ nitinsh99 dodanie reakcji w zależności od pakietu zapewni problem, taki jak Haki - wiele reakcji w pakiecie
Masood