Лучшие практики для проектов Monorepo на основе пакетов управления Lerna

Node.js JavaScript NPM

Эта статья была впервые опубликована в публичном аккаунте vivo Internet Technology WeChat.Tickets.WeChat.QQ.com/Yes/NL на7 и 0i…
Автор: Конг Чуйлян

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

задний план

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

В корневом каталоге репозитория проекта есть три папки подмодулей, соответствующие трем пакетам соответственно После знакомства с процессом сборки и выпуска это немного глупо. Рабочий процесс показан на рисунке:

  1. Скомпилируйте src pkg-a для dist, используя webpack, babel и uglifyjs

  2. Скомпилируйте src pkg-b в dist, используя webpack, babel и uglifyjs

  3. Скомпилируйте src pkg-main в dist, используя webpack, babel и uglifyjs

  4. Наконец, используйте метод копирования файлов, чтобы собрать скомпилированные файлы в pkg-main, pkg-a и pkg-b в pkg-npm и, наконец, использовать их для публикации в npm.

Болевые точки

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

  2. Зависимости пакетов не ясны.pkg-a и pkg-b просто не имеют управления версиями, они больше похожи на уровень исходного кода, но логика относительно независима. В конечном итоге package.json в pkg-main будет скопирован в pkg-npm, но это зависит от некоторых пакетов в pkg-a и pkg-b, поэтому зависимости в pkg-a и pkg-b следует объединить в pkg-in. основной. Package.json pkg-main и pkg-npm связаны вместе, в результате чего некоторые зависимости разработки, которые изначально были проектами, также будут выпущены в npm и станут зависимостями от pkg-npm.

  3. Зависимые пакеты являются избыточными.Видно, что pkg-a, pkg-b и pkg-main нужно компилировать отдельно, и все они зависят от babel, webpack и т. д., и для установки зависимостей необходимо перейти в каждый каталог.

  4. Публикация требует ручной проверки номера версии.Потому что в итоге был выпущен только один пакет, но реальная логика требовала, чтобы этот пакет был установлен и глобально, и локально, и дело не разобрали, в результате чего получилось две установки. В совокупности даже использование npm link может затруднить отладку,

  5. выпуск нетCHANGELOG.md.Поскольку pkg-a и pkg-b на самом деле не управляют версиями, не существует идеального CHANGELOG для записи изменений с момента выпуска последней версии.

Весь проект похож на неуправляемый монорепозиторий. Так что же такое монорепо?

Monorepo vs Multirepo

Полное название Monorepo — монолитный репозиторий, то есть монолитный репозиторий, который соответствует Multirepo (несколько репозиториев), где «один» и «несколько» относятся к количеству модулей, управляемых в каждом репозитории.

Мультирепо — более традиционный подход, то есть каждый пакет управляется отдельным репозиторием. Например: Свернуть, ...

Monorep управляет всеми связанными пакетами в одном репозитории.Каждый пакет выпускается независимо. Например: React, Angular, Babel, Jest, Umijs, Vue...

Одна картинка стоит тысячи слов:

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

Хотя разделение подрепозиториев и пакетов под-npm является естественным решением для изоляции проекта, когда содержимое репозитория связано, нет более эффективного метода отладки, чем объединение исходного кода.

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

Идеальную среду разработки можно представить следующим образом:

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

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

Инструменты, связанные с использованием, упомянутые на картинке выше, — это сегодняшняя главная героиня Lerna!Lerna — самый известный инструмент управления Monorepo в отрасли с полным набором функций.

Lerna

1. Что такое Лерна

A tool for managing JavaScript projects with multiple packages.

Lerna is a tool that optimizes the workflow around managing multi-package repositories with git and npm.

Lerna — это инструмент для управления несколькими модулями npm, это проект, который Babel использует для поддержки собственного монорепозитория и его открытого исходного кода. Оптимизируйте и поддерживайте рабочий процесс нескольких пакетов, решите проблему, связанную с тем, что несколько пакетов зависят друг от друга, а выпуск требует ручного обслуживания нескольких пакетов.

Lerna сейчас используется многими известными проектными организациями, такими как: Babel, React, Vue, Angular, Ember, Meteor, Jest.

Базовая структура репозитория, управляемого Lerna, выглядит следующим образом:

Установить

Рекомендуется глобальная установка, поскольку часто используется команда lerna.

npm i -g lerna

сборка проекта

1. Инициализация

lerna init

Подробнее о команде init см. в lerna init.

Где package.json и lerna.json выглядят следующим образом:

// package.json
{
  "name": "root",
  "private": true, // 私有的,不会被发布,是管理整个项目,与要发布到npm的解耦
  "devDependencies": {
    "lerna": "^3.15.0"
  }
}
 
// lerna.json
{
  "packages": [
    "packages/*"
  ],
  "version": "0.0.0"
}

2. Добавьте два пакета

lerna create @mo-demo/cli
lerna create @mo-demo/cli-shared-utils

Подробности о команде create см. в lerna create.

3. Добавить зависимые модули в соответствующие пакеты соответственно

lerna add chalk                                           // 为所有 package 增加 chalk 模块
lerna add semver --scope @mo-demo/cli-shared-utils        // 为 @mo-demo/cli-shared-utils 增加 semver 模块
lerna add @mo-demo/cli-shared-utils --scope @mo-demo/cli  // 增加内部模块之间的依赖

Подробную информацию о команде add см. в lerna add.

4. Опубликовать

lerna publish

Подробную информацию о команде публикации см. в lerna publish.

Ниже приведена ситуация с выпуском, lerna позволит вам выбрать номер версии для выпуска, я отправил версию @0.0.1-alpha.0.

Для публикации пакетов npm требуется вход в учетную запись npm.

5. Установите зависимости и очистите зависимости

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

Мы также обнаружили на шаге 4 lerna add, что пакет, установленный для пакета, помещается в каталог node_modules внутри каталога пакета. Таким образом, пакеты, от которых зависит несколько пакетов, будут установлены несколько раз несколькими пакетами, а node_modules будут поддерживаться в каждом пакете, который не обновляется. Поэтому мы используем --hoist для продвижения зависимостей каждого пакета в корневую директорию проекта, чтобы снизить стоимость установки и управления.

lerna bootstrap --hoist

Подробнее о команде bootstrap см. в lerna bootstrap.

Чтобы каждый раз не вводить параметр --hoist, его можно настроить в lerna.json:

{
  "packages": [
    "packages/*"
  ],
  "command": {
    "bootstrap": {
      "hoist": true
    }
  },
  "version": "0.0.1-alpha.0"
}

После настройки, в случае, если предыдущие пакеты зависимостей были установлены под каждым пакетом, нам нужно только очистить установленные зависимости:

lerna clean

Затем запустите lerna bootstrap, чтобы увидеть, что зависимости пакетов установлены в node_modules в корневом каталоге.

Лучшие практики для Лерна

Lerna не отвечает за такие задачи, как сборка и тестирование.. Он предлагает модель каталогов для централизованного управления пакетами и предоставляет набор автоматизированных процедур управления, так что разработчикам не нужно копаться в конкретных компонентах для обслуживания контента, и они могут быть глобально доступны. в корневом каталоге проекта.Управление, основанное на сценариях npm, позволяет пользователям выполнять построение компонентов, форматирование кода и другие операции. Далее давайте рассмотрим лучшие практики создания проекта Monorepo на основе Lerna и его сочетания с другими инструментами.

1. Элегантная подача

1.commitizen && cz-lerna-changelog

Сообщение commitizen git commit используется для форматирования инструмента, оно предоставляет способ получить требуемую информацию о запросе типа.

cz-lerna-changelog — это спецификация отправки, специально разработанная для проекта Lerna.В процессе запроса выбор пакетов будет затронут аналогичным образом. следующим образом:

Мы используем commitizen и cz-lerna-changelog для стандартизации коммитов при подготовке к автоматическому созданию журнала позже.

Поскольку это зависит от разработки всего проекта, установите его в корневой каталог:

npm i -D commitizen
npm i -D cz-lerna-changelog

После завершения установки добавьте поле конфигурации в package.json и настройте cz-lerna-changelog для фиксации. В то же время, поскольку commitizen не является глобально безопасным, вам нужно добавить скрипты для выполнения git-cz

{
  "name": "root",
  "private": true,
  "scripts": {
    "c": "git-cz"
  },
  "config": {
    "commitizen": {
      "path": "./node_modules/cz-lerna-changelog"
    }
  },
  "devDependencies": {
    "commitizen": "^3.1.1",
    "cz-lerna-changelog": "^2.0.2",
    "lerna": "^3.15.0"
  }
}

Затем при обычной разработке вы можете использовать npm run c для пошагового ввода в соответствии с подсказками для завершения отправки кода.

2.commitlint && husky

Выше мы использовали commitizen для стандартизации отправки, но это зависит от сознательного использования npm run c разработчиками. Что, если вы забудете об этом или отправите его напрямую с помощью git commit? Ответ заключается в проверке представленной информации при подаче, и если она не соответствует требованиям, она не будет подана, а будет дана подсказка. Работу по проверке выполняет commitlint, а сроки проверки уточняет хаски. Husky наследует все хуки от Git. Когда хук срабатывает, хаски может предотвратить незаконные коммиты, пуши и т. д.

// 安装 commitlint 以及要遵守的规范
npm i -D @commitlint/cli @commitlint/config-conventional

// 在工程根目录为 commitlint 增加配置文件 commitlint.config.js 为commitlint 指定相应的规范
module.exports = { extends: ['@commitlint/config-conventional'] }

// 安装 husky
npm i -D husky

// 在 package.json 中增加如下配置
"husky": {
  "hooks": {
    "commit-msg": "commitlint -E HUSKY_GIT_PARAMS"
  }
}

«commit-msg» — это хук для проверки информации о коммите, когда git фиксирует, и он будет использовать commitlit для проверки, когда он запускается. После завершения установки и настройки, когда вы хотите отправить через git commit или другие сторонние инструменты, вы не можете отправить, пока информация об отправке не соответствует спецификациям. Это вынуждает разработчиков использовать npm run c для отправки.

3.standardjs && lint-staged

В дополнение к стандартизации информации о представлении сам код также должен меньше полагаться на стандарт для унификации стиля.

Standardjs — это полный набор спецификаций кода JavaScript с собственным линтером и автоматическим исправлением кода. Он не требует настройки, автоматически форматирует и исправляет код, а также заранее обнаруживает проблемы со стилем и программированием.

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

// 安装
npm i -D standard lint-staged

// package.json
{
  "name": "root",
  "private": true,
  "scripts": {
    "c": "git-cz"
  },
  "config": {
    "commitizen": {
      "path": "./node_modules/cz-lerna-changelog"
    }
  },
  "husky": {
    "hooks": {
      "pre-commit": "lint-staged",
      "commit-msg": "commitlint -E HUSKY_GIT_PARAMS"
    }
  },
  "lint-staged": {
    "*.js": [
      "standard --fix",
      "git add"
    ]
  },
  "devDependencies": {
    "@commitlint/cli": "^8.1.0",
    "@commitlint/config-conventional": "^8.1.0",
    "commitizen": "^3.1.1",
    "cz-lerna-changelog": "^2.0.2",
    "husky": "^3.0.0",
    "lerna": "^3.15.0",
    "lint-staged": "^9.2.0",
    "standard": "^13.0.2"
  }
}

После завершения установки добавьте подготовленную lint-конфигурацию в package.json, как показано выше, что означает, что стандартное --fix будет выполнено для js-файлов в промежуточной области и автоматически восстановлено. Затем, когда мы будем проверять, мы будем использовать хаски, установленный выше.Хук pre-commit добавляется в конфигурацию хаски для выполнения поэтапной проверки, как показано выше.

Когда файл js будет отправлен в это время, ошибки будут автоматически исправлены и проверены. Это необходимо для обеспечения единообразия стиля кода, а также для улучшения качества кода.

2. Автоматическая генерация журнала

При отправке предыдущей спецификации автоматическая генерация журнала является само собой разумеющимся. Давайте подробнее рассмотрим, что делает lerna publish:

1. Вызвать версию lerna

  • Найдите пакеты, которые изменились с момента последнего выпуска

  • Предложите разработчику определить номер версии для выпуска

  • Обновите поле версии package.json во всех обновленных пакетах.

  • Обновите номер версии зависимости в пакете, который зависит от обновленного пакета.

  • Обновите поле версии в lerna.json

  • Отправьте указанную выше модификацию и нажмите на тег

  • отправить в репозиторий git

2. Используйте npm publish, чтобы отправить новую версию в npm.

CHANGELOG явно соответствует версии один в один, поэтому нужно искать путь в версии lerna.Посмотрев подробное описание команды версии lerna, вы увидите параметр конфигурации --conventional-commits. Верно, пока мы отправляем в соответствии со спецификацией, CHANGELOG текущей версии будет автоматически сгенерирован во время процесса версии lerna. Для удобства вместо того, чтобы каждый раз вводить параметры, можно настроить в lerna.json так:

{
  "packages": [
    "packages/*"
  ],
  "command": {
    "bootstrap": {
      "hoist": true
    },
    "version": {
      "conventionalCommits": true
    }
  },
  "ignoreChanges": [
    "**/*.md"
  ],
  "version": "0.0.1-alpha.1"
}

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

Фактическая версия lerna редко используется напрямую, потому что она включена в lerna publish, просто используйте lerna publish напрямую.

Lerna предоставляет два режима для выбора Fixed или Independent при управлении номером версии пакета. По умолчанию установлено значение «Исправлено». Для получения более подробной информации об игровом процессе Lerna см. официальную документацию веб-сайта:

3. Компилировать, сжимать, отлаживать

Для проектов со структурой Monorepo структура каждого пакета должна быть единообразной.

В соответствии с текущим статусом проекта, дизайн выглядит следующим образом:

  1. Запись каждого пакета унифицирована как index.js

  2. Запись исходного кода каждого пакета унифицирована как src/index.js.

  3. Запись компиляции каждого пакета унифицирована как dist/index.js.

  4. Каждый пакет единообразно использует синтаксис ES6, использует Babel для компиляции, сжатия и вывода в дистрибутив.

  5. При выпуске каждого пакета освобождается только каталог dist, а каталог src не освобождается.

  6. Переменная среды LOCAL_DEBUG вводится в каждый пакет, а среда отладки или среда выпуска различаются в index.js.Среда отладки ruquire(./src/index.js) гарантирует, что весь исходный код можно отлаживать. Среда выпуска ruquire(./dist/index.js) гарантирует, что весь исходный код не будет выпущен.

Поскольку dist — это каталог, скомпилированный Babel, мы не хотим искать его содержимое при поиске, поэтому каталог dist исключен из области поиска в настройках проекта.

Далее мы строим структуру пакета в соответствии с приведенной выше спецификацией.

Сначала установите зависимости

npm i -D @babel/cli @babel/core @babel/preset-env  // 使用 Babel 必备 详见官网用法
npm i -D @babel/node                               // 用于调试 因为用了 import&export 等 ES6 的语法
npm i -D babel-preset-minify                       // 用于压缩代码

Из-за унифицированной структуры каждого пакета такие инструменты, как Babel, могут быть установлены только в корневом каталоге, и их не нужно устанавливать в каждом пакете, который просто обновляется.

Добавить конфигурацию Babel

// 根目录新建 babel.config.js
module.exports = function (api) {
  api.cache(true)
 
  const presets = [
    [
      '@babel/env',
      {
        targets: {
          node: '8.9'
        }
      }
    ]
  ]
 
  // 非本地调试模式才压缩代码,不然调试看不到实际变量名
  if (!process.env['LOCAL_DEBUG']) {
    presets.push([
      'minify'
    ])
  }
 
  const plugins = []
 
  return {
    presets,
    plugins,
    ignore: ['node_modules']
  }
}

Изменить код каждого пакета

// @mo-demo/cli/index.js
if (process.env.LOCAL_DEBUG) {
  require('./src/index')                        // 如果是调试模式,加载src中的源码
} else {
  require('./dist/index')                       // dist会发到npm
}
 
// @mo-demo/cli/src/index.js
import { log } from '@mo-demo/cli-shared-utils'  // 从 utils 模块引入依赖并使用 log 函数
log('cli/index.js as cli entry exec!')
 
// @mo-demo/cli/package.json
{
  "main": "index.js",
  "files": [
    "dist"                                       // 发布 dist
  ]
}
 
 
// @mo-demo/cli-shared-utils/index.js
if (process.env.LOCAL_DEBUG) {
  module.exports = require('./src/index')        // 如果是调试模式,加载src中的源码
} else {
  module.exports = require('./dist/index')       // dist会发到npm
}
 
// @mo-demo/cli-shared-utils/src/index.js
const log = function (str) {
  console.log(str)
}
export {                                         //导出 log 接口
  log
}
 
// @mo-demo/cli-shared-utils/package.json
{
  "main": "index.js",
  "files": [
    "dist"
  ]
}

Изменить опубликованный скрипт

npm run b используется для компиляции babel для каждого пакета, вывода каталога dist из каталога src и использования файла конфигурации babel.config.js в корневом каталоге.

npm run p используется для замены lerna publish, выполнить npm run b для компиляции перед публикацией.

Другие часто используемые команды lerna также добавляются в скрипты для простоты использования.

// 工程根目录 package.json
 "scripts": {
   "c": "git-cz",
   "i": "lerna bootstrap",
   "u": "lerna clean",
   "p": "npm run b && lerna publish",
   "b": "lerna exec -- babel src -d dist --config-file ../../babel.config.js"
 }

отладка

Мы используем функцию отладки, которая поставляется с vscode для отладки, или мы можем использовать Node + Chrome для отладки, в зависимости от привычек разработчика.

Возьмем в качестве примера vscode, см.

.

Добавьте следующие файлы конфигурации отладки:

// .vscode/launch.json
{
    // 使用 IntelliSense 了解相关属性。
    // 悬停以查看现有属性的描述。
    // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
    "version": "0.2.0",
    "configurations": [
        {
            "type": "node",
            "request": "launch",
            "name": "debug cli",
            "runtimeExecutable": "${workspaceRoot}/node_modules/.bin/babel-node",
            "runtimeArgs": [
                "${workspaceRoot}/packages/cli/src/index.js"
            ],
            "env": {
                "LOCAL_DEBUG": "true"
            },
            "console": "integratedTerminal"
        }
    ]
}

Поскольку код src — ES6, поэтому используйте babel-node для запуска отладки, @babel/node уже был установлен ранее.

** Лучше всего, можно использовать в качестве одноэтапной отладки, передаваемой в модуль-зависимую, ** выше, мы хотим выполнить модуль журнала метода @ MO-DEMO / CLI-MODULE-UTILS, в шаге, он будет прыгать Непосредственно @ Mo-Demo / CLI-Shared-Utils SRC Исходный код для выполнения. Как показано ниже

Эпилог

На данный момент лучшие практики для создания проекта Monorepo на основе пакетов управления Lerna в основном созданы.Все функции:

  • Полный рабочий процесс

  • Плавная отладка

  • Единое кодирование

  • Механизм публикации в один клик

  • идеальный журнал изменений

  • ...

Конечно, у Lerna есть много других функций, которые ждут вас, и есть много инструментов, которые можно использовать вместе с Lerna. Чтобы построить полноценный механизм управления складом, его преимущества могут быть неизмеримы некоторыми количественными показателями, и нет прямого выхода ценности, ноЭто может значительно повысить эффективность работы в повседневной работе, повысить производительность и сэкономить много трудозатрат.

--- использованная литература ---

  1. Научу вас играть в Лерну

  2. Интенсивное чтение «Преимущества монорепо»

  3. Изящно управляйте несколькими пакетами с помощью lerna

  4. Создавайте супергладкие рабочие процессы проверки кода с помощью husky и lint-staged.

Больше контента, пожалуйста, обратите вниманиеживые интернет-технологииПубличный аккаунт WeChat

Примечание. Чтобы перепечатать статью, сначала свяжитесь с учетной записью WeChat:labs2020соединять.