Изучение подключаемых модулей для инструментов Node CLI

Node.js
Изучение подключаемых модулей для инструментов Node CLI

Автор этой статьи:Сюй Чаоин

Как близкий и хороший партнер разработчиков, инструменты CLI сопровождают нас в нашей повседневной работе по разработке, в любую погоду. Как интерфейсный разработчик, вы должны сами разработать свой собственный гаджет CLI! Если нет, то эта статья не научит этому ~ В течение следующих пяти минут давайте поговорим о расширенном дизайне инструментов Node CLI и рассмотрим, как использовать сложные требования на стороне CLI в сценарии.Механизм плагиначтобы сделать такие гаджеты более гибкими и богатыми функциональными возможностями.

Преимущества плагина

На данный момент мы столкнулись с большим количеством подключаемых платформ, таких как koa, egg, webpack и т. д. Почему все эти фреймворки или инструменты выбирают для реализации набор подключаемых механизмов?

Во-первых, если не будет плагина, мы будем писать все большие и маленькие функции вместе, что приведет к тому, что проект будет слишком большим, а структура кода будет чрезвычайно длинной и сложной. отношение здорового проекта. Используя механизм подключаемых модулей для сокращения побочных функций, сохраняются только основные функции, и даже основные функции являются подключаемыми.При значительном упрощении проекта он также в основном добавляет в проект следующие функции:

  • гибкость, поскольку сам плагин и код ядра не зависят друг от друга, плагин можно свободно обновлять и изменять, не затрагивая код ядра и другие функции плагина, что повышает стабильность кода ядра до определенного степень.
  • Настройка функций, пользователи могут свободно комбинировать функции подключаемых модулей без установки избыточных функций.
  • Масштабируемость, что также является одной из самых важных особенностей механизма подключаемых модулей.Как сопровождающие проекта, так и сообщество могут легко добавлять подключаемые модули для удовлетворения различных потребностей, выходящих за рамки основных функций.

Можно сказать, что если ваш проект имеет сложную функциональную структуру или планирует в будущем требования к непрерывной итерации, вы можете рассмотреть возможность использования механизма подключаемых модулей для упрощения затрат на разработку и использование.

Поставьте маленькую цель

Говоря о наших гаджетах Node CLI, вообще говоря, гаджеты CLI легкие и простые в использовании, такие как команды инструментов, предоставляемые некоторыми скаффолдингами, которые мы можем часто использовать:

MyTool new aaa
MyTool delete bbb

И обычно их также легко установить:

npm install -g MyTool

Однако, как только у нас появляются новые функциональные требования, такие как добавление команды или параметра, мы должны выпустить пакет обновлений и найти способ предложить пользователям обновить наши инструменты, что очень неудобно и несвоевременно. Объединив название, мы знаем, что механизм подключаемых модулей можно использовать для решения проблемы итерации требований.

Итак, давайте сначала поставим перед собой небольшую цель: какими характеристиками в идеале должен обладать подключаемый инструмент CLI?

Во-первых, плагин лучшеЗаявление готово к использованию, установка не требуется вообще, Например:

MyTool start --featureA --featureB # 我们假设featureA、featureB是两个独立的插件

Таким образом, в процессе использования плагина пользователю не требуется загружать какой-либо плагин, и пользователь может использовать плагин после его объявления. Конечно, это отличается от того, как общая платформа плагинов использует плагины.Например, когда мы используем плагин webpack, нам нужно сначала изменить его.package.jsonФайл, эти плагины загружаются в локальный проектnode_modules, а затем объявите эти плагины в файле конфигурации, например:

webpack 插件

Использование плагинов, таких как webpack, действительно немного громоздко, поэтому в нашем плагине первое, что нужно сделать, — это избежать процесса установки плагина.

Поскольку плагин не нужно устанавливать, его не нужно обновлять или удалять. В общем, чтобы быть подключаемым, нам нужно предоставить нашим инструментам CLIВстроенный набор логики управления пакетами плагинов. Чтобы пользователи больше не заботились о каких-либо операциях, связанных с пакетами подключаемых модулей, не нужно было загружать и устанавливать, не нужно было обновлять подключаемые модули и не нужно было удалять подключаемые модули, все передается гаджету. обрабатывать.

Итак, чтобы реализовать такой набор логики управления пакетами плагинов, какие факторы и решения нам необходимо учитывать? Далее мы подробно рассмотрим механизм управления пакетами плагинов без установки.

Регистрация плагинов

Учитывая сценарии использования подключаемых модулей Node CLI и независимость функций подключаемых модулей, легко подумать об использованииnpmЧтобы зарегистрировать и опубликовать наши плагины: каждый плагин представляет собой отдельный пакет npm, если имя пакета плагина имеет определенные характеристики, мы можем легко найти соответствующий пакет по имени плагина. Например, соответствие между именами плагинов и именами пакетов:

имя плагина Имена пакетов
@{pluginName} myTool-plugin-@{pluginName}
@${scopeName}/${pluginName} @${scopeName}/myTool-plugin-@{pluginName}

Кроме того, нам также необходимо учитывать некоторые специальные пакеты, такие какscopedПакет также отличается характеристиками в названии, как показано в таблице выше.

Существует также пакет плагинов, опубликованный на частном npm, который требует, чтобы наша платформа плагинов сама добавлялаregistryПараметры используются для различения.Конечно, использование частного плагина также будет иметь на один параметр больше, чем обычный плагин, например:

MyTool --registry=http://my.npm.com --my-plugin # 使用一个名为 my-plugin 的私有插件

загрузка пакета

Поскольку все наши плагины представляют собой пакеты npm, нам нужно только подумать о том, как загрузить пакет npm. Изначально наша идея может быть представлена ​​в виде библиотекиnpm, а затем установите пакет плагина:

const npm = require('npm');
npm.install();

Но у этого есть большая проблема с производительностью,npmРазмер пакета составляет около 25 МБ, что недопустимо для инструмента командной строки.

Итак, мы подумали, что, поскольку мы хотим сделать инструмент Node CLI, пользователь должен иметь локальное окружение Node, можем ли мы использовать локальныйnpmскачать пакет плагинов? Ответ да:

const npm = require('global-npm');
npm.install();

Здесь мы можем использоватьglobal-npmили другие подобные пакеты, их рольНайти и загрузить локально на основе информации о переменной средыnpm. Таким образом, наш основной размер упаковки является идеальным «большим тонким».

место хранения

После загрузки пакета плагина также возникает проблема с местом хранения. дефолт,npmЗагруженный пакет будет сохранен в текущем каталогеnode_modulesсередина. В сценарии использования общих инструментов формирования шаблонов менеджер пакетов по умолчанию сохраняет файл пакета плагина в пользовательском проекте.node_modules, преимущество этого заключается в том, что пакет подключаемых модулей обеспечивает изоляцию инженерной детализации. Однако, поскольку пакет подключаемого модуля загружается нашим глобальным инструментом командной строки, и, конечно же, у нас не должно быть подключаемого модуля в качествеdevDependencyдобавлен в каталог проекта пользователяpackage.jsonфайл, который изменяет файл пользователя не так, как мы ожидали. Отсюда вытекает противоречие, а именноnode_modulesПакет плагинов есть, ноpackage.jsonТам нет пакета плагина.

На первый взгляд проблем нет, если пользователь установит все зависимости проекта и наши плагины, мы сможем запускать и запускать наши инструменты в обычном режиме. Но вот классный лакомый кусочек npm:node_modulesсуществует в, но не вpackage.jsonЗависимости, объявленные в, выполняется npminstallкоманда, обрезать их. Это оптимизация npm, т.е. если какие-то зависимости не объявлены заранее, то они будутinstallудаляются в процессе эксплуатации.

npm remove packages

Итак, как только пользователь снова запустит его в какой-то моментnpm install xxx, такие как добавление зависимости от проекта или добавление одного из наших подключаемых модулей (как упоминалось ранее, подключаемые модули также используютсяnpm installдля установки), зависимости некоторых ранее установленных плагинов будут удалены с помощью npm! Это привело к тому, что мы получили ошибку об отсутствующей зависимости при следующем запуске инструмента CLI и плагина.

Именно из-за этой особенности npm приходится отказываться от хранения пакетов плагинов в пользовательских проектах.node_modulesКаталог программ, в свою очередьПакеты подключаемых модулей глобального хранилища, поместите глобальный каталог, например~/.mytool/pluginsВ качестве адреса хранения пакета подключаемого модуля пакет подключаемого модуля внутри будет следовать${插件名}/${version}Хранилище пути, например:

# ~/.mytool/plugins
├── pluginA
│   └── 1.0.1
├── pluginB
│   └── 1.0.0
└── pluginC
    ├── 1.0.1
    └── 1.0.2

Таким образом, наш пакет плагинов избежал «случайной травмы» npm, однако из-за смены места хранения логика загрузки пакета плагинов также должна быть соответствующим образом скорректирована.

нагрузка

Учитывая версию, место хранения и другие проблемы, загрузка плагинов на самом деле немного сложна. Ниже приведен процесс загрузки плагина для локального сервера разработки, мы можем использовать эту упрощенную версию блок-схемы, чтобы лучше понять:

简化流程图

Во-первых, если у нас есть параметрpath, используется для указания пути для загрузки пакета плагина.Очевидно, что пользователь — Бог и всегда имеет наивысший приоритет (ручная собачья голова), поэтому мы сначалаpathоцениваются параметры. Если этот параметр присутствует, мы загружаем пакет плагина прямо по этому пути.

Затем, если наше рабочее пространство зависит от проекта, мы также должны учитывать локальные зависимости проекта от плагинов: еслиnode_modulesЕсли пакет плагина существует в проекте (в основном, если пользователь устанавливает его вручную), то мы напрямую загружаем пакет плагина в этот проект.

Наконец, в предыдущем разделе мы упомянули, что все пакеты плагинов размещаются в глобальной папке, и можно сказать, что в 99% случаев наши плагины загружаются из этой папки. Внутренняя логика проста: запросите, существует ли плагин в папке,Загрузите, если есть, скачайте, если нетЛучшая (обычно последняя) версия плагина. Однако на самом деле здесь нужно учитывать одну деталь, то есть, если пользователь указывает номер версии плагина, нам также необходимо определить, существует ли соответствующая версия плагина в глобальной папке. нам нужно скачать версию.

Вышеупомянутое на самом деле является упрощенной версией процесса загрузки плагина.Сложная часть - если вы думаете об этом в то же время, вы можете смутно осознавать это - например, когда вы запрашиваете плагин в папка, это просто простое суждение о существовании файла или нет? Всегда ли по умолчанию загружается последняя версия плагина? Эти вопросы мы разобьем на следующие несколько подразделов, чтобы говорить о них медленно.

Проблема соответствия версий пакета плагина и основного пакета

Проблема, которая должна быть головной болью для каждой платформы подключаемых модулей, — это соответствие основного пакета, используемого пользователем (вообще говоря, самой платформы подключаемых модулей) и версии пакета подключаемых модулей. Иногда, когда основной пакет имеет серьезное обновление (BREAKING CHANGE), версия старого пакета подключаемого модуля может не совпадать, и наоборот. Таким образом, мы определенно надеемся, что при возникновении проблемы несоответствия версий пользователю будет предложено, и, подобно режиму управления без установки подключаемого модуля, который мы обсуждаем, он сможет автоматически сопоставлять и устанавливать соответствующий подключаемый модуль в соответствии с к версии основного пакета Теоретически пользователь вообще не воспринимает концепцию управления версиями в основных пакетах или пакетах плагинов.

Чтобы автоматически сопоставлять пакеты ядра и пакеты плагинов, нам сначала нужно найти способ связать их версии. Вы можете использовать метод, объявленный разработчиком плагина, например, разработчик плагина можетpackage.jsonсерединаenginesСреда основного пакета, необходимая для нормальной работы плагина, объявляется в поле, например:

{ "engines" : { "svrx" : "^1.0.0" } }

Это означает, что плагин может быть только^1.0.0интервалsvrxверсия работает. (svrxэто имя инструмента CLI)

из-заpackage.jsonПоля в могут быть доступны непосредственноnpm viewчтение команды,

npm view engines

Мы можем объединить использование текущего пользователяsvrxБазовая версия пакета легко определяет наиболее подходящую версию подключаемого модуля, а затем загружает эту версию. Соответствие версии здесь мы можем выбратьsemverсудить:

semver.satisfies('1.2.3',  '1.x || >=2.5.0 || 5.0.0 - 7.2.3')  // true

Итак, в отношении процесса загрузки плагина, когда пользователь не указывает конкретную версию, мы загружаем пакет плагина не обязательно с установленной датой (последней) версией плагина, носогласно сenginesНаиболее подходящая версия поля после проверки sever.

автоматическое обновление

Что ж, вернемся к вопросу, который мы задавали ранее, а что если пакет плагина обновится? На самом деле, это проблема любого механизма плагинов. Общее решение, такое как подключаемый модуль webpack, когда мы устанавливаем подключаемый модуль, мы записываем информацию о версии вpackage.jsonв, какhtml-webpack-plugin@^3.0.0, так что когдаv3.1.0После выпуска мы можем автоматически обновиться до последней версии при следующей «переустановке» этого пакета плагинов. Однако обратите внимание, что «переустановка в следующий раз» означает, что мы переустанавливаем пакет npm после удаления локальных зависимостей, однако в процессе фактического использования мы не часто обновляем зависимости в этих проектах, поэтому в большинстве случаев у нас нет способ насладиться последней версией плагина вовремя. Это проблема, с которой сталкиваются пользователи при самостоятельной установке плагинов.

Что, если это механизм установки без плагинов? Можем ли мы загружать последнюю версию по умолчанию каждый раз, когда загружаем ее? Конечно, потому что конкретная загруженная версия может быть определена внутренним механизмом загрузки. Однако у этого есть недостаток: если вам нужно проверять (просмотр npm), имеет ли плагин последнюю версию каждый раз, когда вы загружаете плагин, и если есть последняя версия, вам нужно загрузить (установить npm) пакет новой версии. , это пустая трата времени! После загрузки всех плагинов сервис запускается, а лилейник холодный!

Как мы можем обновить плагин автоматически, не замедляя скорость загрузки? Мы можем принять компромиссное решение, то есть после запуска каждого сервиса выполняется проверка обновления версии и загрузка новой версии для всех используемых плагинов, и все это делается.в подпроцессе, никогда не будет блокировать нормальную работу службы. Таким образом, при следующем запуске Server-X все плагины будут обновлены.

Однако есть еще некоторые детали, на которые необходимо обратить внимание в такой схеме, например, что делать, если при загрузке пакета возникает ошибка? Что, если пользователь вдруг прервет программу в процессе загрузки? В этих особых случаях загруженный файл пакета подключаемого модуля будет неполным и недоступным. Для этого мы можем попробоватьИспользуйте временную папку в качестве промежуточной области для загрузки пакетов плагинов., и после подтверждения того, что пакет подключаемого модуля успешно загружен, переместите файлы из временной папки в целевую папку (~/.myTool/plugins), так:

const tmp = require('tmp');
const tmpPath = tmp.dirSync().name;   // 生成一个随机临时文件夹目录
const result = await npm.install({
    name: packageName,
	path: tmpPath,  // npm 下载到临时文件夹 
	global: true,
	// ...
});  
const tmpFolder = libPath.join(tmpPath, 'node_modules', packageName);  
const destFolder = libPath.join(root, result.version);  
  
// 复制到目标文件夹  
fs.copySync(tmpFolder, destFolder, {  
  dereference: true, // ensure linked folder is copied too  
});

Поэтому, если загрузка плагина не удалась, пакет плагина не будет существовать в нашей целевой папке, поэтому мы попытаемся снова загрузить плагин при следующем запуске. Некоторые из пакетов плагинов, которые не удалось загрузить, находятся во временной папке и будут регулярно очищаться нашей системой, поэтому не нужно беспокоиться об остатках мусора, это очень экологично!

Очистка пакета с истекшим сроком действия

Фактически, остаточные файлы, о которых нам действительно нужно позаботиться, — это те устаревшие пакеты плагинов, которые уже существуют в папке плагинов. Из-за существования механизма автоматического обновления мы будем загружать новую версию и хранить ее в папке плагина после каждого обновления плагина, а в следующий раз напрямую запускать новую версию плагина, поэтому пакет плагина старой версии бесполезен. его нельзя вовремя очистить, он может занимать место для хранения пользователя.

Конкретная логика очистки также очень проста, то есть при выполнении шага автоматического обновленияУзнать, что версия в каталоге хранилища плагинов не самая последняя и не та версия, которую указал текущий пользовательи удалять папки массово. Кроме того, учитывая чистоплотность инженеров, я лично считаю, что и сам CLI-инструмент тоже должен иметьсамоочищениеПо логике, если пользователь удалит инструмент, инструмент должен автоматически очистить все локально сохраненные конфигурации, основные пакеты и пакеты плагинов, чтобы добиться нулевого загрязнения.

Суммировать

Выше мы обсуждаем некоторые решения и детали дизайна для управления пакетами подключаемых модулей Node CLI. Если вы посмотрите только на заголовок и жирный и черный текст, то обнаружите, что нормально не читать другой текст? (хе-хе-хе~)

Основное содержание и материалы этой статьи получены из подключаемого локального сервера разработки, созданного Netease Cloud Music Front-end Group——Server-Xи процесс проектирования и разработки его подключаемого механизма, чтобы обобщить, текст намеренно скрывает правильныйServer-XЕсли вы все еще заинтересованы или хотите узнать конкретный код механизма плагина, вы можете открыть ссылку ниже для дальнейшего чтения.

В целом, начиная со загрузки, хранения, загрузки и управления версиями пакетов подключаемых модулей, на самом деле существуют некоторые проблемы, которые мы, возможно, не рассматривали до разработки.Если вы планируете создать платформу подключаемых модулей или столкнулись с Подобные проблемы Сценарий, я надеюсь, что этот не требующий установки механизм управления пакетами плагинов Server-X может вам помочь.

Links

Можете ли вы поддержать это?

Небольшое примечание: прилагательные 996-й серии в начале статьи не имеют ничего общего с командой разработчиков внешнего интерфейса NetEase Cloud Music!

Эта статья была опубликована сКоманда внешнего интерфейса NetEase Cloud Music, может быть свободно воспроизведено, пожалуйста, указывайте перепечатку в названии и сохраняйте источник на видном месте. Мы всегда нанимаем, если вы готовы сменить работу и вам нравится облачная музыка, тоПрисоединяйтесь к нам!