Одно из десяти главных сожалений Райана по поводу node.js заключается в том, что он поддерживает node_modules.Хотя дизайн node_modules может соответствовать большинству сценариев, он все еще имеет различные дефекты, особенно в области разработки интерфейса, что вызвало множество проблем.Эта статья Кратко опишите некоторые из его проблем и возможных улучшений
период, термин
-
package: содержит package.json, пакет, определенный с помощью package.json, обычно соответствует модулю или может не содержать модуль, например сценарий оболочки, указанный в bin, или даже произвольный файл (используйте реестр как http-сервер, или Использовать unpkg как cdn), пакет может быть tar-пакетом, локальным файловым протоколом или даже адресом репозитория git.
-
модуль: Модуль, который может быть загружен с помощью require, называется модулем. Ниже приведены все модули. Только если модуль содержит package.json, его можно назвать пакетом.
-
Папка, содержащая package.json с основным полем
-
папка с index.js
-
любой JS-файл
Комплексный: модуль не обязательно является пакетом, а пакет не обязательно является модулем.
Dependency Hell
Теперь в проекте есть две зависимости A и C. A и C зависят от разных версий B соответственно, как с ними быть?
Здесь есть две проблемы
- Во-первых, сама B поддерживает сосуществование нескольких версий. Пока сама B не имеет побочных эффектов, это естественно. Однако для многих библиотек, таких как core-js, которые будут загрязнять глобальную среду, они не поддерживают мультиверсионность. сосуществование версий, поэтому нам нужно сообщать об ошибках как можно скорее (предупреждение о конфликте и проверка конфликта во время выполнения).
- Если сам B поддерживает сосуществование нескольких версий, то он должен убедиться, что A правильно загружен в B v1.0, а C правильно загружен в B v2.0.
Сосредоточимся на втором вопросе
npm-решение
Решение Node состоит в том, чтобы полагаться на алгоритм поиска путей модулей, загруженных узлом, и структуру каталогов node_modules для решения проблемы.
Как загрузить пакеты из node_modules
Суть в том, чтобы рекурсивно искать пакет в node_modules, если в'/home/ry/projects/foo.js'
файл называетсяrequire('bar.js')
, Node.js будет выглядеть в следующем порядке:
/home/ry/projects/node_modules/bar.js
/home/ry/node_modules/bar.js
/home/node_modules/bar.js
/node_modules/bar.js
Алгоритм имеет два ядра
- Сначала прочитайте зависимости ближайших node_modules
- Рекурсивный поиск зависимостей node_modules
Этот алгоритм не только упрощает решение ада зависимостей, но и приносит массу проблем.
Структура каталогов node_modules
nest mode
Используя требование сначала найти зависимые функции в ближайшем node_module, мы можем придумать очень простой способ напрямую поддерживать карту топологии исходного модуля в node_module.
Таким образом, в соответствии с mod-a используется ближайшая версия 1.0 mod-b, а ближайшая версия mod-c — это версия 2.0 mod-b.
Но это приносит еще одну проблему.Если мы полагаемся на другой mod-d в это время, mod-d также зависит от версии 2.0 mod-b.В это время node_modules становится следующим
Мы обнаружили, что здесь есть проблема, хотя mod-a и mod-d зависят от одной и той же версии mod-b, но mod-b устанавливается дважды, если ваше приложение использует много сторонних библиотек, а третьи- сторонние библиотеки Зависящие от некоторых очень простых сторонних библиотек, таких как lodash, вы обнаружите, что ваши node_modules полны различных дубликатов версий lodash, что приводит к огромной трате места и медленной установке npm, которая является не только печально известным node_modules ад
flat mode
Мы также можем использовать функцию рекурсивного поиска зависимостей, чтобы поместить некоторые общие зависимости в общий модуль node_module.
Согласно алгоритму поиска require
- A и D сначала перейдут к своему node_module, чтобы найти B, обнаружат, что B не существует, а затем рекурсивно проведут поиск вверх, в это время они найдут версию B версии 1.0, которая соответствует ожиданиям.
- C сначала найдет B v2.0 в своем собственном node_module, как и ожидалось.
На этот раз мы обнаружили, что удалось избежать как ада зависимостей, так и повторяющихся проблем с зависимостями, вызванных режимом гнезда npm2.
doppelgangers
Но проблема не решена.Если введенный D зависит от B v2.0, а введенный E зависит от B v1.0, мы находим, что независимо от того, поместите ли Bv2.0 или Bv1.0 на верхний уровень, это приведет к Любая другая версия будет иметь повторяющиеся проблемы, такие как повторяющаяся проблема B's v2.0 здесь
Есть ли проблема с дублированием версий?
Вы можете сказать, что дублирование версий — это пустая трата места, и это происходит только при наличии конфликта версий.Кажется, это не большая проблема, и это правда, но в некоторых случаях это все же может вызвать проблемы.
конфликт глобальных типов
Хотя код перед каждым пакетом не будет загрязнять друг друга, их типы все равно могут влиять друг на друга. Многие сторонние библиотеки изменяют определение глобального типа, обычно @types/react. Ниже приведена распространенная ошибка.
Причина ошибки в том, что глобальные типы образуют конфликт имен, поэтому дублирование версии может привести к ошибке глобального типа.
Общее решение состоит в том, чтобы контролировать, какие загруженные @types/xxx включены.
сломать одноэлементный шаблон
требуется механизм кэширования
Узел будет кэшировать загруженные модули. После загрузки модуля в первый раз результат будет кэширован, и последующие вызовы require вернут тот же результат. Однако кеширование запроса узла основано не на имени модуля, а на основе файл разрешения.Путь чувствителен к регистру, что означает, что даже если одна и та же версия одного и того же модуля загружена в ваш код, если проанализированные имена путей несовместимы, он будет рассматриваться как другой модуль, если тот же модуль загружен в то же время Выполнение побочных эффектов одновременно создает проблемы.
В качестве примера возьмем react-loadable, который используется как в браузере, так и в узлах.
использовать в браузере
использование уровня узла
Затем упакуйте и скомпилируйте браузер в bundle.js и загрузите скомпилированный код bundle.js на уровне узла.
Хотя и уровень узла, и браузер получают доступ к «реагируемому загружаемому», если компиляция веб-пакета включает перезапись пути, хотя версия реактивного загружаемого такая же, это приведет к тому, что узел и браузер загрузят не объект экспорта, загружаемый реакцией, к сожалению, react-loadable сильно зависит от узла и браузера, экспортирующих один и тот же объект. Поскольку уровень узла будет считывать READY_INITIALIZERS, установленный браузером, если узел и экспорт браузера не являются одним и тем же объектом, чтение завершится ошибкой.
Другая проблема заключается в использовании подмодуля git.Подмодуль Git может легко привести к сосуществованию нескольких версий в среде.Например, одновременно существует несколько версий реагирования, что с большей вероятностью вызовет проблемы.
Phantom dependency
Мы обнаружили, что плоский режим экономит много места по сравнению с вложенным режимом, но также создает проблему, фантомную зависимость, рассмотрите следующие пункты.
Пишем следующий код
Glob и brace-expansion здесь не находятся в наших зависимостях, но мы можем нормально разрабатывать и запускать (потому что это зависимость от rimraf) после выпуска библиотеки, потому что пользователь не будет устанавливать нашу библиотеку, когда она будет установлена. devDepdency библиотеки, из-за чего ошибка запускается на месте пользователя.
Мы называем библиотеку, использующую пакет, который не принадлежит ее зависимостям, как фантомные зависимости.Фантомные зависимости будут существовать не только в библиотеке, но когда мы используем монорепозиторий для управления проектом, проблема становится еще более серьезной.Пакет может не только ввести фантом, представленный DevDependency Dependency, он, скорее всего, приведет к введению зависимостей других пакетов, и могут возникнуть проблемы при развертывании или запуске проекта.
В структуре node_modules, основанной на пряже или npm, двойные и фантомные зависимости, похоже, не имеют хорошего решения. Суть в том, что npm и yarn симулируют исходный граф зависимостей через алгоритм разрешения узла и древовидную структуру node_modules.Есть ли лучший метод симуляции, чтобы избежать вышеуказанных проблем?
Semver, когда идеал встречается с реальностью
npm использует номер версии пакетаСемантическое управление версиями, Сам Semver также представляет собой решение, представленное для решения Depdency Hell.Если в вашем проекте появляется все больше и больше сторонних зависимостей, вы столкнетесь с дилеммой
- Если вы прописываете жестко закодированные зависимости для каждой из ваших версий, то при необходимости исправить или обновить определенную базовую зависимость вам будет сложно оценить масштаб влияния обновления, что может привести к каскадным реакциям, которые может зависнуть, в результате чего всей системе потребуется полный объем тестирования и регрессии.Конечным результатом, вероятно, будет полностью заблокированная версия всего приложения, и дальнейшие обновления производиться не будут.
Поэтому предложение semver в основном используется для контроля сферы влияния каждого пакета, что может обеспечить плавное обновление и переход системы.Каждая установка npm будет устанавливать последние ограниченные зависимости в соответствии с ограничениями semver.
Таким образом, каждая установка npm будет устанавливать последние зависимости, соответствующие ограничению «^4.0.0», что может быть версией 4.42.0.
Если все библиотеки могут идеально соответствовать semver, то мир во всем мире, но реальность такова, что многие библиотеки не соблюдают semver по разным причинам, в том числе
- Непредсказуемые ошибки, я думал, что определенная версия была просто исправлением ошибки, и выпустил версию с патчем, но патч внес неожиданное критическое изменение, которое привело к поломке semver
- Дизайн semver слишком идеален.На самом деле, даже самое маленькое исправление, если бизнес-сторона непреднамеренно опирается на эту ошибку, все равно приведет к критическим изменениям.Граница между ошибкой и критическими изменениями размыта.
- Я думаю, что semver не имеет особого смысла. Например, Typescript официально признает, что никогда не следовал семантике semver. На самом деле, typescript часто вносит различные критические изменения в минорных версиях.GitHub.com/Microsoft/T…
замок не эликсир
Итак, как справиться с такой проблемой в реальном мире, вы определенно не хотите, чтобы ваш код нормально работал локально, но зависал, когда вы выходите в интернет.
В промежутке между завершением теста и запуском бизнеса, если одна из ваших зависимостей не соответствует semver и происходит критическое изменение, вам, возможно, придется выйти в интернет посреди ночи, чтобы проверить наличие ошибок. Мы обнаружили, что корень проблемы кроется в том, как обеспечить полное соответствие кода при тестировании запускаемому коду.
Прям пишите мертвую версию
Естественная идея состоит в том, что я могу просто убить все свои сторонние версии зависимостей.
Однако проблема не так проста.Хотя вы заблокируете версию веб-пакета, зависимости веб-пакета не могут быть заблокированы.Если определенный метод зависимости веб-пакета не следует за критическим изменением semver, наше приложение все равно будет затронуто, если мы не гарантия Все сторонние и сторонние зависимости являются жестко запрограммированными версиями, а это значит, что все сообщество отказывается от semver, что заведомо невозможно.
yarn lock vs npm lock
Более надежным способом записи является блокировка зависимостей в проекте и зависимостей третьих лиц одновременно.И блокировка пряжи, и блокировка npm поддерживают эту функцию.Общий файл блокировки выглядит следующим образом
Если наш проект устанавливает экспресс-зависимости
Его файл блокировки выглядит следующим образом
Мы обнаружили, что все зависимости Express и версии их зависимостей заблокированы в файле блокировки, чтобы другой пользователь или среда могли использовать файл блокировки для воспроизведения версии каждой библиотеки в node_modules.
Тем не менее, все еще есть некоторые сценарии, в которых блокировка не может быть покрыта.Когда мы впервые устанавливаем и создаем проект или устанавливаем зависимость в первый раз, даже если сторонняя библиотека содержит файлы блокировки, npm install|(yarn install) не Он будет считывать блокировку, от которой зависит третья сторона, что приводит к тому, что пользователь вызывает ошибки при создании проекта в первый раз. Это очень часто встречается в сценарии установки глобального кли.Часто встречается, что когда глобальный кли был установлен в прошлый раз, это было нормально, но переустановка этой версии кли зависает, что, скорее всего, вызвано зависимостью вверх по течению версии cli.Имеется критическое изменение, так как в глобальной среде нет блокировки, поэтому на данный момент нет лучшего решения.
Резолюции начальника пожарной охраны
Если вы однажды установите новый webpack-cli, но обнаружите, что webpack-cli не работает должным образом, после некоторого позиционирования будет обнаружено, что одна из восходящих зависимостей cli имеет ошибку в последней версии portfinder, но у автора кли баг.В отпуске нет возможности вовремя исправить эту кли,а что делать если проект прет онлайн? пряжа предоставляет метод, называемыйclassic.yarn pk more.com/ru/docs/pervert…Механизм позволяет игнорировать ограничение зависимости и принудительно блокировать портфайндер до версии без ошибок для решения срочных задач.
Сам по себе npm не предоставляет механизма разрешения, но его можноnpm-froce-resolution
В этой библиотеке реализован аналогичный механизм
Должен ли Карри зафиксировать файл блокировки?
Как упоминалось ранее, npm и yarn не читают файлы блокировки в сторонних библиотеках во время установки, поэтому необходимо ли предоставлять файлы блокировки, когда мы пишем библиотеки?
Я не знаю, был ли у вас такой опыт.Однажды я нашел ошибку в сторонней библиотеке.Я с нетерпением скачал библиотеку и приготовился исправить ее и отправить mr.npm install && npm build
Операция была лютой как тигр, а потом я увидел кучу необъяснимых ошибок компиляции.Эти ошибки, вероятно, вызваны критическим изменением upstream-зависимости инструмента компиляции.После некоторого google + stackoverflow, это все еще не было исправлено, и в настоящее время оно в основном сломано.Чтобы избежать побуждения поднять mr, если разработчик библиотеки представит блокировку текущей среды компиляции, этой проблемы можно в значительной степени избежать.
determinism !!!
Детерминизм относится к топологии node_modules, которая будет одинаковой каждый раз, когда вы переустанавливаете под заданным package.json и файлом блокировки.
По сути, yarn гарантирует детерминированность только одной версии и не может гарантировать детерминированность разных версий, а npm гарантирует детерминированность разных версий.
Детерминированная версия !== детерминированная топология
Мы упоминали ранее, что yarn.lock гарантирует, что все сторонние библиотеки и их зависимые номера версий заблокированы.Хотя версия гарантирована, yarn.lock на самом деле не содержит никакой информации о топологии node_modules.
Как и в приведенном выше примере, файл блокировки гарантирует только версию has-flag и версию support-colors, но не гарантирует, появится ли has-flag на верхнем уровне или на support-color.Следующие две топологии являются разумными
Первый
второй
Напротив, информация о блокировке npm содержит информацию о топологии.
Приведенная выше структура показывает, что has-flag и support-color находятся на одном уровне.
В приведенном выше файле блокировки мы видим, что такие зависимости, как define-property и is-accessor-descritpor, помещены в node_modules в base.
Относительные пути вредны
топология имеет значение
В большинстве сценариев топологическая структура заблокированного номера версии + зависимость в принципе не проблема.Даже если топология node_modules непоследовательна, это не вызовет проблем.Однако в некоторых сценариях проблемы все же есть.
Приведенный ниже код на самом деле делает серьезные предположения о топологии ndoe_modules, и могут возникнуть проблемы с расположением @types.
Это также требует от нас не использовать какие-либо относительные пути при чтении сторонних зависимостей, а читать путь модуля через require.resolve, а затем искать на основе этого пути.
относительно какого каталога
Другая проблема с относительными путями заключается в том, что смысл неясен.
Возьмем, к примеру, Babel. Когда мы используем Babel для компиляции кода, обычно используются три каталога.
- Текущее рабочее пространство: т. е. возвращаемое значение process.cwd() здесь — это мой проект
- Местоположение текущего кода: например, my-project/build.js
- Расположение инструмента компиляции: например, xxx/node_modules/@babel/core
Вот проблема@babel/preset-env
Относительно кого позиция, все зависит от внутренней реализации в babel/core.
монорепозиторий: ссылка трудная
Если проблема зависимостей в сторонней библиотеке еще в какой-то степени управляема, то при входе в поле монорепозитория проблема удвоится. Когда мы используем один репозиторий для управления несколькими пакетами, возникают две серьезные проблемы.
- Проблема повторной установки сторонних зависимостей.Если используется одна и та же версия lodash и в пакете A, и в пакете B, без оптимизации оба пакета нужно устанавливать с одной и той же версией lodash повторно.
- Link hell: Если A зависит от B, а B зависит от C и D, каждый раз, когда мы разрабатываем, нам нужно связать C и D с B. Если диаграмма топологии очень сложная, недопустимо выполнять эти операции связи вручную.
Будь то лерна или пряжа, ядро рабочего механизма
- Попробуйте установить все зависимости пакетов в node_modules корневого уровня в плоском режиме, то есть hoist, чтобы избежать повторной установки сторонних зависимостей для каждого пакета, и установите конфликтующие зависимости в node_modules собственного пакета, чтобы решить проблему конфликта версий зависимостей
- Мягко свяжите каждый пакет с node_modules на корневом уровне, чтобы каждый пакет мог импортировать другие пакеты, используя механизм рекурсивного поиска узла, без необходимости самостоятельного связывания вручную.
- Свяжите бины node_modules в каждом пакете с node_modules на корневом уровне, чтобы убедиться, что сценарий npm каждого пакета может работать нормально.
Хотя этот метод решает две основные проблемы дублирования зависимостей и ссылочного ада, он создает и другие проблемы.
- packageA может легко импортировать packageB, даже если packageB не объявлен как зависимость в packageA, и даже packageA может легко импортировать сторонние зависимости packageB, что фактически усугубляет фантомную зависимость
- Зависимости в пакете A и сторонние зависимости пакета B, скорее всего, будут конфликтовать.Например, пакет A использует webpack3, а пакет B использует webpack4, который легко конфликтует, что фактически усугубляет проблему двойников.
подъемник небезопасен, рассмотрите следующую структуру
На самом деле html-webpack-plugin зависит от webpack при запуске
Перед подъемом реагирующие скрипты будут вызывать html-webpack-plugin, а затем вызывать webpack.Согласно алгоритму разрешения узла, сначала будет использоваться последняя версия webpack в node_modules, а здесь это webpack@2.
Но после подъема корневая версия веб-пакета, webpack@1, будет использоваться по принципу близости, что вызовет ошибки времени выполнения.
Для пряжи и npm сначала будет использоваться hoist, только когда локальная версия конфликтует с root, операция hoist не будет выполняться (даже вы не можете определить, какая версия будет поднята на корневой уровень, когда версий несколько). ).
Эта проблема не ограничивается веб-пакетом, eslint, jest, babel и т. д., она будет затронута до тех пор, пока задействованы ядро и его плагины.
Поэтому, чтобы решить эту проблему, официальный представитель react специально сделал предполетную проверку.GitHub.com/Facebook/взрослый…, который используется для проверки непротиворечивости версий babel и webpack в node_modules реагирующих скриптов текущего пользователя и его предков node_modules.Как только несоответствие версии обнаружено, он выдает предупреждение и выходит напрямую
Из-за некоторых дефектов самого подъемника это также является основной причиной, по которой React отказался от поддержки монорепозиториев.GitHub.com/Facebook/взрослый…, г-н объединен и возвращен
Yarn также имеет более агрессивный режим --flat. В этом режиме каждый пакет в node_modules может существовать только в одной версии за раз. есть через Указанный в разрешении принудительный контроль версий), что явно не работает в больших проектах, т.к. в сторонних библиотеках большое количество конфликтов версий (только в вебпаке 160+ конфликтов версий), что показывает серьезность doopelganges, принуждение всех версий указывать не решает проблему.
PNPM: Explicit is better than implicit.
Без учета циклических зависимостей наш фактический граф зависимостей на самом деле является своего рода направленным ациклическим графом (DAG), но то, что npm и yarn имитируют с помощью алгоритма разрешения каталогов файлов и узлов, на самом деле является одним из направленных ациклических графов Superset (множество связей между неправильные предки и братья и сестры), что вызывает много проблем, поэтому нам нужна симуляция, более похожая на DAG. pnpm использует именно это решение и решает проблему пряжи и прокси-сервера npm, более точно имитируя DAG.
phantom dependency
По сравнению с yarn, который максимально размещает пакет на корневом уровне, pnpm записывает только явно прописанные зависимости зависимостей в node_modules корневого уровня, что позволяет избежать проблемы некорректного введения неявных зависимостей в бизнес, то есть Resolved phantom зависимость
Возьмите следующий пример в качестве примера
Но мы можем использовать модуль отладки в нашем коде, потому что это модуль, введенный экспрессом, хотя мы сами не ввели его явно
// src/index.js
const debug = require('debug')
Если однажды экспресс решит заменить отладочный модуль наbetter-debug
module, то наш код зависнет.
Структура нпм
Структура pnpm
Мы обнаружили, что в node_modules верхнего уровня есть только экспресс-модуль, но нет модуля отладки, поэтому мы не можем ввести отладку в бизнес-код по ошибке, при этом каждая сторонняя библиотека имеет свою директорию node_modules, и каждая Каталог node_modules содержит собственную цепочку зависимостей.Soft, которая гарантирует корректную загрузку отладочной версии в экспресс.
doppelgangers
В то время как pnpm решает проблему фантомной зависимости, он также решает проблему дупелгангера на этой основе.
Проверьте следующий код
// package.json
{
"dependencies": {
"debug": "3",
"express": "4.0.0",
"koa": "^2.11.0"
}
}
После использования pnpm для установки связанных зависимостей мы обнаружили, что в проекте есть две версии отладки.
dependencies:
debug 3.1.0
express 4.0.0
├── debug 0.8.1
├─┬ send 0.2.0
│ └── debug 1.0.5
└─┬ serve-static 1.0.1
└─┬ send 0.1.4
└── debug 1.0.5
koa 2.11.0
└── debug 3.1.0%
Глядя на версию в node_modules, мы обнаружили, что, в отличие от пряжи, pnpm помещает разные версии в один и тот же слой и выбирает загруженную версию через мягкую цепочку, в то время как пряжа размещается в разных слоях и полагается на алгоритм рекурсивного поиска для выбора версия
Мы обнаружили, что node_modules pnpm содержит три версии, и к трем версиям подключены разные модули.
Таким образом, даже если есть конфликт версий, вам нужно только связать каждый модуль, и вам не нужно повторно устанавливать модуль для каждого модуля.
Мы можем обнаружить, что pnpm избегает прямой зависимости от рекурсивной природы node_modules для поиска зависимостей, но напрямую решает проблемы фантомных зависимостей и двойников с помощью программных ссылок. Это экономит много места и ускоряет установку, потому что полностью исключается проблема дублирования пакетов.
Возьмем в качестве примера проект монорепозитория.
Сравнивать
pnpm: размер node_modules 359M, установка занимает 20 секунд
пряжа: размер node_modules составляет 1,2 ГБ, установка занимает 173 секунды
Разница очень существенная
global store
pnpm может не только гарантировать, что каждая версия всех пакетов в проекте уникальна, но даже гарантировать, что вы можете совместно использовать уникальную версию между вашими различными проектами (нужно только совместно использовать хранилище), что может значительно улучшить экономию места на диске. Суть в том, что pnpm больше не полагается на алгоритм рекурсивного восходящего поиска node_modules узла, потому что алгоритм сильно зависит от физической топологии node_modules, что также является основной причиной сложности повторного использования node_modules в проектах разных проектов. (Существует также сухой метод, который заключается в записи номера версии зависимости, в которой используется код, что является сухим методом deno)
Cargo: система управления посылками для глобального магазина
На самом деле, за исключением npm узла, несколько других языков требуют, чтобы каждый проект поддерживал зависимость node_modules (вы слышали об адской проблеме node_modules в других языках), и другие языки редко имеют такие зависимости рекурсивного поиска. языки приняли глобальную систему управления магазином. Мы можем посмотреть, как rust управляет пакетами.
Создать новый проект rust легко, просто запустите
$ rust new hello-cargo // 创建项目,包含可执行的binary
$ rust new hello-lib --lib // 创建lib,
Сгенерированная структура каталогов выглядит следующим образом
.
├── Cargo.toml
└── src
└── main.rs
Функции Cargo.toml и package.json почти одинаковы (по сравнению с json, tom поддерживает комментарии), включая следующую информацию
[package]
name = "hello_cargo" // 包名
version = "0.1.0" // 版本号
authors = ["Your Name <you@example.com>"]
edition = "2018"
[dependencies]
Зависимости используются для хранения сторонних зависимостей
src/main.rs груза — основная точка входа в проект, аналогичная index.js.
// src/main.rs
fn main() {
println!("Hello, world!");
}
Cargo имеет встроенную функцию компиляции rust (по сравнению с богатыми инструментами в экосистеме js, преимущество встроенной компиляции rustc в cargo очевидно, всем сторонним библиотекам нужно только предоставить исходный код, груз сам завершает рекурсивную компиляцию работают собственные зависимости)
$ cargo build // 编译生成binary文件
$ cargo run // 执行binary文件
Давайте попробуем добавить стороннюю зависимость.Как и в случае с npm, зависимости от cargo также поддерживают git-протокол и файловый протокол.
[dependencies]
time = "0.1.12"
rand = { git = "https://github.com/rust-lang-nursery/rand.git" } // 支持git协议
Выполните сборку для установки зависимостей.На данный момент существует Cargo.lock, аналогичный файлу yarn.lock, который содержит детерминированную версию сторонней библиотеки и ее зависимости.
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
[[package]]
name = "hello_cargo"
version = "0.1.0"
dependencies = [
"time",
]
[[package]]
name = "libc"
version = "0.2.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "99e85c08494b21a9054e7fe1374a732aeadaff3980b6990b94bfd3a70f690005"
[[package]]
name = "time"
version = "0.1.43"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca8a50ef2360fbd1eeb0ecd46795a87a19024eb4b53c5dc916ca1fd95fe62438"
dependencies = [
"libc",
"winapi",
]
[[package]]
name = "winapi"
version = "0.3.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
Структура каталогов на данный момент выглядит следующим образом
.
├── Cargo.lock
├── Cargo.toml
├── src
└── target // 编译产物
Мы обнаружили, что в проекте нет такой вещи, как node_modules для хранения всех зависимостей проекта.
Так где же хранятся его зависимости?
cargo home
Cargo хранит весь сторонний зависимый код в каталоге с именем cargo home, который по умолчанию~/.cargo
, который содержит три основных каталога
/bin // 存放executable的bin文件
/git // 存放从git协议拉取的第三方库代码
/registry // 存放从registry拉取的第三方库代码
поддержка монорепозиториев
Груз сам по себе также обеспечивает поддержку монорепозитория.Подобно пряже, груз также поддерживает монорепозиторий через концепцию рабочего пространства.
// Cargo.tom
[workspace]
members = [
"adder",
"hardfist-add-one"
]
Это почти эквивалентно yarn.lock ниже
// package.json
{
"workspaces":["adder","hardfist-add-one"]
}
Давайте посмотрим на структуру каталогов рабочей области.
Подобно пряже, он использует файл Cargo.lock.
Мы можем связать каждую библиотеку через локальный файл
Предположим, что adder в монорепозитории зависит от hardfist-add-one
// adder/Cargo.toml
[package]
name = "hardfist-adder"
version = "0.1.0"
authors = ["hardfist <1562502418@qq.com>"]
edition = "2018"
description = "test publish"
license = "MIT"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
hardfist-add-one = { path = "../hardfist-add-one", version="0.1.0" }
rand="0.7"
Мы можем указать hardfist-add-one локально через протокол пути.
Когда нам нужно опубликовать сумматор, cargo не позволяет публиковать зависимости, которые содержат только путь path, поэтому нам нужно указать версию для жесткого кулака для публикации одновременно.
запретить неявные зависимости
Хотя в том же рабочем пространстве, если наш hardfist-add-one зависит от rand, а hardfist-adder зависит от hardfist-add-one, если сам hardfist-adder не объявляет rand в качестве своей зависимости, Cargo сообщит об ошибке.
Каждый раз, когда выполняется компиляция, Cargo автоматически рекурсивно компилирует все свои зависимости и не требует дополнительной поддержки инструментов.
Поскольку большинство грузовых приложений упакованы в виде двоичных файлов, в монорепозитории в узле нет поставщика, как описано ниже.
Cargo также поддерживает автономный режим, который поддерживает автономную установку зависимостей.
vendor: for serverless
Еще одна серьезная проблема в монорепозитории заключается в том, как развертывать каждый пакет отдельно, что более заметно в сценариях без обслуживания, поскольку обычно среды без обслуживания имеют большие ограничения на размер пользовательских ресурсов.
Когда мы используем монорепозиторий для управления приложением, возникают две проблемы с развертыванием.
- Сторонние зависимости устанавливаются на корневом уровне, так что node_modules в пакете не содержат всей информации о зависимостях.Вместо scm и других компонентов мы можем выбрать только упаковку всех узловых_модулей корневого уровня пакета. упаковать вместе
- Так как в каждом пакете реализована взаимная поддержка и импорт через софтчейны, даже если мы упаковываем node_modules, он все равно содержит только софтчейны, которые зависят от пакетов, и проблемы все равно будут.
В основном есть два решения этой проблемы
- Код упаковывается в пакет с помощью инструмента упаковки, а время выполнения не зависит от node_modules.
- Извлеките сторонние зависимости hoist и пакета ссылок в node_modules в пакете.
Самая большая проблема, с которой сталкиваются эти две схемы, — это неявная зависимость
bundle
Это распространено для пакетов интерфейсных приложений, но не характерно для пакетов приложений на стороне сервера.На самом деле, многие серверные языки используют пакетное решение, такое как deno, rust, go и т. д., все онлайн представляют собой пакетный файл, этот пакетный файл может быть в двоичном или другом формате.
На самом деле, даже на стороне сервера есть несколько зрелых пакетных решений в экосистеме узлов, таких какgithub.com/zeit/ncc, он разумно упакует код на стороне сервера в файл js или даже упакует среду выполнения вместе с бизнес-кодом в двоичный файл, напримерgithub.com/zeit/pkgстроить планы.
Самая большая проблема с пакетами на стороне сервера — это чтение и запись файлов и динамический импорт, потому что функция компиляции не может получить информацию, которая должна быть прочитана и записана | импортированные файлы во время компиляции, поэтому ее трудно применить к некоторым фреймворкам (таким как как яйцо и гулу), чьи условности больше, чем конфигурация., но если это экспресс и коа, которые нужно отобразить, проблем нет.