Na semana passada, os desenvolvedores do Yarn (o gerenciador de pacotes para Javascript) anunciaram um novo recurso - a instalação do Plug'n'Play. Esse recurso permite executar projetos Node.js. sem usar a pasta node_modules, na qual as dependências do projeto geralmente são instaladas antes de iniciar. A descrição do recurso declara que node_modules não será mais necessário - os módulos serão carregados a partir do cache geral do gerenciador de pacotes.
Ao mesmo tempo, os desenvolvedores do NPM também anunciaram sua solução semelhante para o problema.
Vamos examinar mais de perto essas soluções e tentar testá-las em projetos reais.
Histórico de problemas
Inicialmente, o sistema modular NodeJS era inteiramente baseado no sistema de arquivos. Qualquer chamada para require()
mapeada para o sistema de arquivos. Para organizar módulos de terceiros, a pasta node_modules foi inventada, na qual módulos e bibliotecas reutilizáveis devem ser baixados e instalados. Assim, cada projeto recebeu seu próprio conjunto separado de dependências, desperdiçando espaço em disco ineficientemente.
A instalação de dependência ocupa a maior parte do tempo de compilação nos sistemas de CI; portanto, acelerar esta etapa afetará favoravelmente o tempo de compilação como um todo.
Simplificada, a instalação dos módulos consiste nas seguintes etapas:
- A versão específica do módulo é calculada a partir do intervalo válido.
- Todos os módulos das versões necessárias são baixados do repositório e armazenados no cache local
- Módulos do cache local são copiados para a pasta node_modules do projeto
Se as duas primeiras etapas já estiverem otimizadas o suficiente e forem executadas rapidamente, quando você já tiver módulos em cache, a terceira etapa permanecerá quase inalterada em comparação com as primeiras versões do nó e do npm.
A nova abordagem propõe se livrar da terceira etapa e substituir a cópia real dos arquivos pela criação de uma tabela que mapeie os módulos solicitados para suas cópias no cache local.
Usando links simbólicos
Em vez de realmente copiar os módulos, você pode adicionar um link simbólico para sua localização no cache. Essa abordagem é implementada no PNPM , outro gerenciador de pacotes alternativo. A abordagem pode muito bem funcionar, mas com links simbólicos há muitos problemas associados à localização dupla do arquivo, à pesquisa de módulos adjacentes etc. Além disso, a criação de links simbólicos é uma operação de arquivo que eu gostaria de evitar da maneira ideal de trabalhar.
Tentando o fio PNP
Você pode ler mais sobre esse recurso na descrição oficial . Este parágrafo contém sua breve recontagem.
A versão do Yarn habilitada para PNP agora está no recurso-ramo yarn-pnp .
Clonamos o repositório localmente com o ramo desejado
git clone git@github.com:yarnpkg/yarn.git --branch yarn-pnp
A instrução de montagem do fio está aqui , o conjunto de etapas é muito trivial.
Após a conclusão da construção, adicione um alias à versão personalizada do yarn e comece a trabalhar com ele:
alias yarn-local="node $PWD/lib/cli/index.js"
O plug'n'play é ativado de duas maneiras: através do sinalizador: yarn --pnp
ou por configuração adicional em package.json
: "installConfig": {"pnp": true}
.
Como exemplo, os desenvolvedores de fios já prepararam um projeto de demonstração . Possui Webpack, Babel e outras ferramentas típicas de um front-end moderno. Vamos tentar estabelecer suas dependências de maneiras diferentes e obter os seguintes resultados:
- Instalação típica do
yarn
: 19s - Instalação via
yarn --pnp
: 3s
Antes da medição, uma instalação a frio era realizada para que todos os módulos necessários já estivessem no cache.
Vamos ver como isso funciona. Após uma instalação do pnp, um arquivo .pnp.js
adicional é criado na raiz do projeto, que contém uma substituição da lógica nativa na classe Module incorporada ao Node.js. Ao carregar esse arquivo em nosso código, damos à função require()
a capacidade de obter módulos do cache global e não olhar para node_modules
. Todos os comandos do yarn embutidos, como o yarn start
ou o yarn test
, pré-carregam esse arquivo por padrão, portanto, você não precisará fazer alterações no seu código se já tiver usado o Yarn antes.
Além dos módulos de mapeamento, o pnp.js executa validação de dependência adicional. Se você tentar chamar require('test')
, sem uma dependência declarada em package.json
, você receberá o seguinte erro: Error: You cannot require a package ("test") that is not declared in your dependencies
. Essa melhoria deve aumentar a confiabilidade e a previsibilidade do código.
Entre as deficiências da nova abordagem, vale ressaltar que será necessária integração adicional para ferramentas que trabalharam diretamente com o diretório node_modules sem os mecanismos internos do Node. Por exemplo, o Webpack e outros construtores de front-end precisarão de plug-ins adicionais para encontrar os arquivos necessários para o empacotamento.
No projeto de demonstração, há esboços de resolvedores para Eslint, Jest, Rollup e Webpack.
No meu experimento, ainda havia problemas com o Typescript, que está muito ligado à presença de node_modules e não há uma maneira fácil de substituir a estratégia de pesquisa do módulo.
Também haverá problemas com scripts pós-edição. Como o módulo permanece no cache, os scripts pós-instalação que alteram seu estado (por exemplo, fazem upload de arquivos adicionais) podem danificar o cache e interromper outros projetos que dependem dele. Os desenvolvedores de fios recomendam desativar a execução de scripts com o sinalizador --ignore-scripts
. Eles já haviam experimentado ativar esse sinalizador por padrão para todos os projetos dentro do Facebook e não encontraram nenhum problema sério. A longo prazo, abandonar os scripts pós-instalação parece ser um bom passo em vista de problemas de segurança conhecidos.
Experimentando o NPM
A equipe do NPM também anunciou sua solução alternativa. Sua nova ferramenta, tink, vem com um módulo independente e independente do NPM. Tink recebe o arquivo package-lock.json
como uma package-lock.json
, gerada automaticamente quando a npm install
executada. Com base no arquivo de bloqueio, o tink gera um node_modules/.package-map.json
, que armazena a projeção dos módulos locais em sua localização real no cache.
Ao contrário do Yarn, não há nenhum arquivo de gancho que possa ser pré-carregado no seu projeto para que o patch seja necessário. Em vez disso, é sugerido que você use o comando tink
vez do node
para obter o ambiente correto. Essa abordagem é menos ergonômica, pois exigirá modificações no seu código para fazê-lo funcionar. No entanto, como uma prova de conceito servirá.
Tentei comparar a velocidade de instalação dos módulos com os npm ci
e tink
, mas o tink foi ainda mais lento, por isso não apresentarei os resultados. Obviamente, este projeto é muito mais cru do que o Yarn e não é otimizado. Bem, vamos aguardar novos lançamentos.
Conclusão
Rejeitar o diretório node_modules é uma etapa lógica, dada a experiência de outros idiomas, em que essa abordagem não existia inicialmente. Isso afetará favoravelmente a velocidade de compilação nos sistemas de CI, onde é possível salvar o cache do pacote entre compilações. Além disso, se você transferir o cache do pacote e o arquivo .pnp.js
de um computador para outro, poderá reproduzir o ambiente sem sequer iniciar o Yarn. Isso pode ser útil em sistemas de construção de contêiner: monte o diretório com o cache, coloque o arquivo .pnp.js
e você poderá executar os testes imediatamente.
A nova abordagem parece incomum e quebra algumas práticas estabelecidas com base no fato de que todos os módulos estão sempre disponíveis em node_modules. Mas o arquivo .pnp.js
oferece uma API que permite abstrair da posição real dos arquivos e trabalhar com a árvore virtual. Além disso, como último recurso, há um comando yarn unplug --persist
que extrairá um módulo do cache e o colocará localmente em node_modules
.
De qualquer forma, nada foi finalizado ainda, mesmo que a solicitação de extração no Yarn ainda não tenha sido derramada, devemos esperar alterações. Mas foi interessante para mim experimentar a versão alfa do recurso na prática e testá-los em alguns dos meus projetos pessoais e garantir que essa abordagem realmente funcione, tornando a instalação mais rápida.
Referências