Por que usar dependências de pares no npm para plugins?

217

Por que, por exemplo, um plug-in Grunt define sua dependência do grunt como " dependências de pares "?

Por que o plug-in não pode ter apenas o Grunt como sua própria dependência no grunt-plug / node_modules ?

As dependências entre pares são descritas aqui: https://nodejs.org/en/blog/npm/peer-dependencies/

Mas eu realmente não entendo.

Exemplo

No momento, estou trabalhando com o AppGyver Steroids, que usa as tarefas do Grunt para criar meus arquivos de origem em uma pasta / dist / para ser exibido em um dispositivo local. Eu sou muito novo na NPM e grunhido, então eu quero compreender completamente o que está acontecendo.

Até agora eu entendi:

[rootfolder] /package.json diz ao npm que depende do grunt-steroidspacote npm para desenvolvimento:

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

OK. A execução do npm install no [rootfolder] detecta a dependência e instala grunt-steroids em [rootfolder] / node_modules / grunt-steroids .

O Npm então lê [rootfolder] /node_modules/grunt-steroids/package.json para poder instalar suas grunt-steroidspróprias dependências .:

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

Os pacotes " dependências " são instalados em [rootfolder] / node_modules / grunt-steroids / node_modules, o que é lógico para mim.

O " devDependencies " não está instalado, o que eu tenho certeza que é controlado pelo npm detectando que estou apenas tentando usar grunt-steroids, e não desenvolvendo.

Mas temos o " peerDependencies ".

Eles estão instalados em [rootfolder] / node_modules , e não entendo por que existe e não em [rootfolder] / node_modules / grunt-steroids / node_modules para que conflitos com outros plugins de grunhido (ou o que seja) sejam evitados?

Thomas Stock
fonte

Respostas:

420

TL; DR: [1] peerDependencies são para dependências expostas (e que devem ser usadas pelo) código de consumo, em oposição a dependências "privadas" que não são expostas e são apenas um detalhe de implementação.

O problema que as dependências dos pares resolvem

O sistema de módulos do NPM é hierárquico. Uma grande vantagem para cenários mais simples é que, quando você instala um pacote npm, esse pacote traz suas próprias dependências para que ele funcione imediatamente.

Mas surgem problemas quando:

  • Seu projeto e algum módulo que você está usando dependem de outro módulo.
  • Os três módulos precisam se comunicar.

No exemplo

Digamos que você está construindo YourCoolProjecte você está usando ambos JacksModule 1.0e JillsModule 2.0. E vamos supor que isso JacksModuletambém dependa JillsModule, mas de uma versão diferente, digamos 1.0. Contanto que essas duas versões não atendam, não há problema. O fato de JacksModuleestar usando JillsModuleabaixo da superfície é apenas um detalhe de implementação. Estamos agrupando JillsModuleduas vezes, mas esse é um preço pequeno a pagar quando obtermos software estável imediatamente.

Mas agora, o que se JacksModuleexpõe de JillsModulealguma maneira sua dependência . Ele aceita uma instância de, JillsClasspor exemplo ... O que acontece quando criamos uma new JillsClassversão 2.0de uso da biblioteca e a passamos adiante jacksFunction? Todo o inferno vai se soltar! Coisas simples como jillsObject instanceof JillsClassretornarão repentinamente falseporque jillsObjectna verdade é uma instância de outra JillsClass , a 2.0versão.

Como as dependências dos pares resolvem isso

Eles dizem npm

Preciso deste pacote, mas preciso da versão que faz parte do projeto, não de uma versão privada ao meu módulo.

Quando o npm perceber que seu pacote está sendo instalado em um projeto que não possui essa dependência ou que possui uma versão incompatível , ele avisa o usuário durante o processo de instalação.

Quando você deve usar dependências entre pares?

  • Quando você estiver construindo uma biblioteca para ser usada por outros projetos, e
  • Esta biblioteca está usando alguma outra biblioteca e
  • Você espera / precisa que o usuário trabalhe com essa outra biblioteca também

Cenários comuns são plugins para estruturas maiores. Pense em coisas como Gulp, Grunt, Babel, Mocha, etc. Se você escrever um plug-in Gulp, deseja que ele funcione com o mesmo Gulp que o projeto do usuário está usando, e não com sua própria versão privada do Gulp.


Anotações

  1. Demasiado longo; não leu. Usado para indicar um breve resumo de um texto que consideramos muito longo.
Stijn de Witt
fonte
2
Uma coisa importante que notei e não foi dita em nenhum lugar, quando estamos construindo um plug-in, devemos ter uma duplicação de dependências de pacotes, para as dependências de pares? No exemplo do OP, podemos ver que isso "grunt": "0.4.4"é tanto em devDependencies quanto em peerDependencies, e faz sentido ter uma duplicata lá, porque significa tanto que eu preciso desse gruntpacote para meu próprio uso, mas também que os usuários do meu A biblioteca pode usar sua própria versão, desde que respeite o bloqueio de versão peerDependencies. Isso está correto? Ou o exemplo do OP é muito ruim?
Vadorequest
4
Eu posso imaginar pessoas criando um plug-in do Grunt como fãs do Grunt :) Como tal, parece natural que eles usem o Grunt para o processo de construção de seu plug-in ... Mas por que eles desejam bloquear a faixa de versões do Grunt, o plug-in funciona? com o processo de construção que eles usam para criá-lo? Adicioná-lo como uma dependência de desenvolvimento permite que eles desacoplem isso. Basicamente, existem duas fases: tempo de construção e tempo de execução. Dependências de desenvolvimento são necessárias durante o tempo de construção. Dependências regulares e de pares são necessárias em tempo de execução. Claro que com dependências de dependências tudo se torna confuso rápido :)
Stijn de Witt
1
Obrigado por esta resposta! Só para esclarecer, no seu exemplo, se JacksModuledepende JillsModule ^1.0.0de JillsModuleser uma dependência de pares de JacksModulee YourCoolProjectestavam usando JacksModulee JillsModule ^2.0.0, vamos receber o aviso dependência de pares por NPM, o que nos aconselham a instalar JillsModule ^1.0.0também. Mas o que acontece então? YourCoolProjectagora terá duas versões JillsModuleimportáveis ​​através import jillsModule from "..."? E como me lembro que, quando uso JacksModule, preciso passar uma instância de JillsModule v1.0.0?
tonix 25/02
1
Tonix @ Bem, de fato, será um problema que você tenha uma incompatibilidade de versão. peerDependencies não resolve isso. Mas ajuda a tornar o problema explícito. Porque mostrará claramente a incompatibilidade de versão em vez de usar duas versões silenciosamente. O desenvolvedor do aplicativo que está selecionando as bibliotecas precisará encontrar uma solução.
Stijn de Witt
2
@tonix Ou a terceira opção: clonar o JacksModulerepositório, atualizá-lo para depender JillsModule ^2.0.0e oferecer um PR ao mantenedor do projeto. Pode ser útil enviar um bug primeiro, dizendo que essa dependência está desatualizada e você gostaria de ajudar a atualizá-la. Se você faz um bom RP, a maioria dos mantenedores de bibliotecas o mescla e agradece por isso. Se os mantenedores não responderem, você pode publicar sua bifurcação no NPM com espaço de nome sob seu nome e, em vez disso, usar sua bifurcação. De qualquer forma, existem soluções, mas peerDependenciesnão as resolve por si só.
Stijn de Witt
26

Eu recomendo que você leia o artigo novamente primeiro. É um pouco confuso, mas o exemplo com o winston-mail mostra a resposta por que:

Por exemplo, vamos fingir que isso foi [email protected]especificado "winston": "0.5.x"em seu "dependencies"objeto, porque essa é a versão mais recente contra a qual foi testada. Como desenvolvedor de aplicativos, você deseja as melhores e mais recentes coisas, procura as versões mais recentes de winstone de winston-maile as coloca em seu package.json como

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

Mas agora, a instalação do npm resulta no inesperado gráfico de dependência de

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

Nesse caso, é possível ter várias versões de um pacote, o que causaria alguns problemas. As dependências entre pares permitem que os desenvolvedores do npm garantam que o usuário tenha o módulo específico (na pasta raiz). Mas você está correto ao afirmar que a descrição de uma versão específica de um pacote levaria a problemas com outros pacotes usando outras versões. Esse problema tem a ver com desenvolvedores npm, como os artigos afirmam

Um conselho : os requisitos de dependência de pares, diferentemente dos requisitos de dependências regulares, devem ser tolerantes . Você não deve bloquear suas dependências entre pares para versões específicas de patches.

Portanto, os desenvolvedores devem seguir sempre para definir peerDependencies. Você deve abrir um problema para o pacote grunt-esteróides no GitHub ...

Fer para
fonte
1
Você diz isso, multiple versions of a package which would cause some issuesmas não é esse o objetivo de um gerenciador de pacotes? Eles discutem isso ainda mais no mesmo artigo, onde há duas versões do mesmo pacote no projeto: uma fornecida pelo desenvolvedor e outra fornecida por uma biblioteca de terceiros.
Adam Beck
1
Acho que entendo o ponto da dependência de colegas, mas no winstonexemplo agora não consigo usar a winston-mailbiblioteca porque minha versão não corresponde à dependência de colegas? Eu preferiria ter esse downgrade temporário do mais recente e melhor para a biblioteca 1 do que não poder usá-lo.
Adam Beck
1
para o seu primeiro comentário, tanto quanto eu o entendo e uso, isso tem a ver com testes, por exemplo, se você tiver um pacote que foi testado por você para um pacote específico de terceiros, não poderá ter certeza de que, se um de suas dependências mudam (correção de bugs, atualização dos principais recursos) que seu pacote funcionará. Portanto, você pode especificar uma versão específica do plug-in e salvar com seus testes.
Fer até
1
Em seu segundo comentário: é por isso que eles dizem nos documentos que os desenvolvedores devem ser tolerantes com suas dependências de pacotes e devem usar sempre, por exemplo, em vez de "0.2.1", "~ 0.2.1" -> permite "0.2.x", mas não "0.3.x" ou "> = 0.2.1" -> tudo, de "0.2.x" a "1.x" ou "x.2.". .. (mas não é realmente preferível para um pacote npm iria com ~
Fer To
15

peerDependencies explicado com o exemplo mais simples possível:

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


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

executar a instalação do npm no myPackage gerará um erro porque está tentando instalar a versão React ^15.0.0E fooque é compatível apenas com o React ^16.0.0.

peerDependencies NÃO estão instalados.

Christopher Tokar
fonte
por que não colocar react 16 como um dep dentro de foo? dessa forma, tanto 15 quanto 16 estarão disponíveis e foo pode usar 16 e mypackage pode usar 15?
Nitinsh99 17/08/19
React é uma estrutura que é autoinicializada no tempo de execução, para que o React 15 e o React 16 existam na mesma página, é necessário que ambos sejam inicializados simultaneamente, o que seria extremamente pesado e problemático para o usuário final. Se foofuncionar com o React 15 e o React 16, ele poderá listar sua Dependência par como >=15 < 17.
Jens Bodal
nitinsh99 minha resposta era explicar o propósito de peerDependencies com o exemplo mais simples possível, não como se livrar do erro lançada por peerDependencies
Christopher Tokar
@ nitinsh99 adicionar reagir dentro da dependência do pacote fornecerá problemas como Hooks - Vários reagem em um pacote
Masood