[Этап 3] Используйте lerna для управления общими библиотеками инструментов

NPM

На работе мы иногда пишем некоторые часто используемые библиотеки, такие как определение типа данных,cookieБиблиотека инструментов для хранения модулей и т. д., но в некоторых бизнес-сценариях требуются не все модули.

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

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

Можно себе представить, насколько громоздким станет этот процесс, когда будет больше библиотек.

Итак, каков хороший способ решить эту проблему?LernaОн подходит для таких сценариев применения.

что такое лерна

Lerna — это инструмент для управления проектами JavaScript с несколькими пакетами с использованиемmonorepo(Однократный репозиторий) Подход к управлению.

Все актуальноmodulerepo, каждыйmoduleНезависимый выпуск, напримерBabel,Reactа такжеjestИ т. Д.), Проблема и PR сосредоточены в репо.

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

Структура файла проекта Lerna:

├── lerna.json
├── package.json
└── packages
    ├── package-a
    │   ├── index.js
    │   └── package.json
    └── package-b
        ├── index.js
        └── package.json

что в основном делает lerna

  • пройти черезlerna bootstrapКоманда устанавливает зависимости и переносит кодовую базу вnpm link.
  • пройти черезlerna publishПубликация последних изменений в библиотеке

как использовать

Установить

npm install --global lerna
#or
yarn global add lerna

инициализировать проект

mkdir demo 
cd demo
lerna init  

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

├── lerna.json # lerna配置文件
├── package.json
└── packages # 包存放文件夹

В Lerna есть два режима управления проектами: фиксированный режим или автономный режим.

Фиксированный режим

Фиксированный режим является режимом по умолчанию, номер версии используетlerna.jsonв файлеversionАтрибуты. воплощать в жизньlerna publishКогда код обновляется, значение этого номера версии автоматически обновляется.

Автономный режим

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

Как использовать:

lerna init --independent

Исправлятьlerna.jsonсерединаversionзначениеindependent

{
  "npmClient": "yarn", // 执行命令所用的客户端,默认为npm
  "command": { // 命令相关配置
    "publish": { // 发布时配置
      "ignoreChanges": ["ignored-file", "*.md"], // 发布时忽略的文件
      "message": "chore(release): publish" // 发布时的自定义提示消息
    },
    "bootstrap": { // 安装依赖配置
      "ignore": "component-*", // 忽略项
      "npmClientArgs": ["--no-package-lock"] // 执行 lerna bootstrap命令时传的参数
    }
  },
  "packages": [ // 指定存放包的位置
    "packages/*"
  ],
  "version": "0.0.0" // 当前版本号
}

ОбщийdevDependencies

babel,eslintи другие модули, большинство из которых можно использовать совместно,

мы можем пройтиlerna link convertкоманда для автоматического размещения их в корневом каталогеpackage.jsonФайл.

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

Уведомление:НемногоnpmИсполняемые пакеты по-прежнему необходимо устанавливать в пакеты, которые используют модули для правильного выполнения, напримерjest.

Использование рабочих областей пряжи

Рабочие пространства — это новый способ настройки архитектуры пакетов, и их нужно запускать только один раз.yarn installВсе зависимости в указанном рабочем пространстве могут быть установлены все.

Преимущество

  • Зависимые пакеты могут быть связаны друг с другом, что означает, что ваши рабочие области могут зависеть друг от друга, всегда используя последний доступный код. Это такжеyarn linkЛучший механизм, потому что он влияет только на дерево зависимостей вашего рабочего пространства, а не на всю систему.
  • Все зависимости проекта будут установлены вместе, что позволит Yarn лучше их оптимизировать.
  • Yarn будет использовать один файл блокировки вместо одного для каждого пакета, что означает меньше конфликтов и более простую проверку кода.

как использовать

существуетpackage.jsonДобавьте следующее в файл:

package.json

{
  "private": true,
  "workspaces": ["packages/*"]
}

Уведомление:private: true

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

{
  "useWorkspaces": true
}

Создать модуль

lerna create package-a

Выполните приведенную выше команду, она будет вpackageСоздайте модули в папке и создайте соответствующие модули в соответствии с интерактивными подсказками.package.json.

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

├── lerna.json
├── package.json
└── packages
    └── package-a
    		├── __tests__
    		│    └── name.test.js
    		├── lib
    		│    └── name.js
        ├── package.json
        └── README.md

добавить зависимости

модульpackage-aдобавить вpackage-bзависимость модуля

larna add package-a --scope=package-b

После завершения добавления будетpackage-bизpackage.jsonДобавьте следующие зависимости в

{
  "dependencies": {
    "package-a": "file:../package-a"
  }
}

использование зависимости пакетаfile:чтобы указать локальный путь к файлу

выпускать

При публикации необходимо предоставитьcommitкод, затем выполнитьlerna publish

Выберите здесьPatch1.0.1.

Затем выберите Подтвердить в соответствии с приглашением для успешной публикации.

также можно использоватьlerna publish -yВыбрать все по умолчаниюYes, и согласноcommitИнформация автоматически обновляет номер версии.

Lerna Changelog

lernaсгенерированныйChangelogфункция, ее нужно только сгенерировать с помощью простой конфигурацииCHANGELOG.mdдокумент.

Конфигурация выглядит следующим образом:

{
  "command": {
    "publish": {
      "allowBranch": "master", // 只在master分支执行publish
      "conventionalCommits": true, // 生成changelog文件
      "exact": true // 准确的依赖项
    }
  }
}

После настройки, когда мы выполняемlerna publishПосле этого он будет находиться в корневом каталоге проекта и каждыйpackagesупаковать, сгенерироватьCHANGELOG.md.

Уведомление:только встретитьсясоглашениеизcommitОтправить для правильной генерацииCHANGELOG.mdдокумент.

commitдляfixЭто будет автоматически обновленная версия;

еслиfeatАвтоматическое обновлениедополнительный номер версии;

Изменить, если есть критические измененияосновной номер версии.

Lerna интегрируется с Jest

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

  • Все пакеты поддерживают только один общий файл конфигурации jest.
  • Может запускать все модульные тесты в целом
  • Модульные тесты можно выполнять только для определенного пакета.

шутливая конфигурация

Настроен в корневом каталоге проектаjest.config.jsФайлы следующие:

const path = require('path')
module.exports = {
  collectCoverage: true, // 收集测试时的覆盖率信息
  coverageDirectory: path.resolve(__dirname, './coverage'), // 指定输出覆盖信息文件的目录
  collectCoverageFrom: [ // 指定收集覆盖率的目录文件,只收集每个包的lib目录,不收集打包后的dist目录
    '**/lib/**',
    '!**/dist/**'
  ],
  testURL: 'https://www.shuidichou.com/jd', // 设置jsdom环境的URL
  testMatch: [ // 测试文件匹配规则
    '**/__tests__/**/*.test.js'
  ],
  testPathIgnorePatterns: [ // 忽略测试路径
    '/node_modules/'
  ],
  coverageThreshold: { // 配置测试最低阈值
    global: {
      branches: 100,
      functions: 100,
      lines: 100,
      statements: 100
    }
  }
}

Пишите тестовые скрипты

новыйscriptsпапка, добавитьtest.jsдокумент:

const minimist = require('minimist')
const rawArgs = process.argv.slice(2)
const args = minimist(rawArgs)
const path = require('path')
let rootDir = path.resolve(__dirname, '../')
// 指定包测试
if (args.p) {
  rootDir = rootDir + '/packages/' + args.p
}
const jestArgs = [
  '--runInBand',
  '--rootDir', rootDir
]

console.log(`\n===> running: jest ${jestArgs.join(' ')}`)

require('jest').run(jestArgs)

Скрипт работает, анализируя аргументы командной строки-pДля определения выполнения тестовых случаев указанный пакет, если не указан-pПараметры выполняются, и все тестовые случаи выполняются.

Измените корневой каталогpackage.jsonизscriptДобавьте следующую команду:

{
  "scripts": {
    "ut": "node scripts/test.js"
  }
}

# 执行全部测试
yarn ut

# 执行某个包测试
yarn ut -p package-a

Интеграция Lerna с веб-пакетом

При отправке посылок вам нужно будет использоватьwebpackпровестиes6Транскодирование или сжатие, было бы обременительно поддерживать файл конфигурации для каждого из них; у нас естьjestТе же нужды:

  • только одна порцияwebpackконфигурационный файл
  • Все модули могут быть упакованы отдельно одновременно
  • Вы также можете упаковать определенные модули по отдельности.

конфигурация веб-пакета

Создал в корневом каталогеwebpack.config.jsФайл следующим образом:

var path = require('path')
const CleanWebpackPlugin = require('clean-webpack-plugin')

module.exports = (opt) => {
  return {
    mode: 'production',
    entry: path.resolve(opt.path, './lib/index.js'),
    output: {
      path: path.resolve(opt.path, './dist'),
      filename: `${opt.name}.min.js`,
      library: opt.name,
      libraryTarget: 'umd',
      umdNamedDefine: true
    },
    externals: opt.externals,
    plugins: [
      new CleanWebpackPlugin()
    ],
    module: {
      rules: [
        {
          test: /\.js$/,
          loader: 'babel-loader',
          include: [path.resolve(opt.path, './lib')],
          options: {
            // 指定babel配置文件
            configFile: path.resolve(__dirname, '.babelrc')
          }
        }
      ]
    },
    optimization: {
      minimize: true
    }
  }
}

Этот файл конфигурации представляет собой функцию, которая возвращает окончательное содержимое конфигурации, принимая объект параметра,

Пишите скрипты сборки

Идеи:

  1. читатьpackagesКаталог всех модулей, путь к модулю сбора
  2. package.json,ПолучатьnameЗависимость
  3. Путь через модуль, имя пакета иpackage.jsonсерединаdependencieswebpackнастроить
  4. пройти черезwebpackизNode APIВыполнить настройку, скомпилировать и упаковать
  5. По параметрам командной строки определить конфигурационный файл, который необходимо запаковать (упаковать отдельно)

Конкретная реализация выглядит следующим образом:

/scripts/build.js

const minimist = require('minimist')
const rawArgs = process.argv.slice(2)
const args = minimist(rawArgs)
const webpack = require('webpack')
const webpackConfig = require('../webpack.config')
const fs = require('fs')
const path = require('path')
const packages = fs.readdirSync(path.resolve(__dirname, '../packages/'))

// 获取外部依赖配置
function getExternals (dependencies) {
  let externals = {}
  if (dependencies) {
    Object.keys(dependencies).forEach(p => {
      externals[p] = `commonjs ${p}`
    })
    return externals
  }
}
const packageWebpackConfig = {}

// 遍历所有的包生成配置参数
packages.forEach(item => {
  let packagePath = path.resolve(__dirname, '../packages/', item)
  const { name, dependencies } = require(path.resolve(packagePath, 'package.json'))
  packageWebpackConfig[item] = {
    path: packagePath,
    name,
    externals: getExternals(dependencies)
  }
})

function build (configs) {
  // 遍历执行配置项
  configs.forEach(config => {
    webpack(webpackConfig(config), (err, stats) => {
      if (err) {
        console.error(err)
        return
      }

      console.log(stats.toString({
        chunks: false, // 使构建过程更静默无输出
        colors: true // 在控制台展示颜色
      }))
      if (stats.hasErrors()) {
        return
      }
      console.log(`${config.name} build successed!`)
    })
  })
}

console.log('\n===> running build')

// 根据 -p 参数获取执行对应的webpack配置项
if (args.p) {
  if (packageWebpackConfig[args.p]) {
    build([packageWebpackConfig[args.p]])
  } else {
    console.error(`${args.p} package is not find!`)
  }
} else {
  // 执行所有配置
  build(Object.values(packageWebpackConfig))
}

затем в корневой каталогpackage.jsonизscriptДобавьте следующую команду:

{
  "scripts": {
    "build": "node scripts/build.js"
  }
}

Запустите скрипт сборки:

# 全部打包
yarn build

# 指定打包
yarn build -p package-a

Слишком далеко,LernaПредставлен метод использования.


Front-end команда Shuidihuzhu набирает партнеров.Присылайте свое резюме на почтовый ящик: fed@shuidihuzhu.com