Модуль 14. Сборка, публикация и CI/CD

Цель модуля: подготовить UI Kit к публикации как взрослую npm-библиотеку: exports-карта, sideEffects, size-limit, changesets, CI/CD pipeline.

К концу модуля у вас:

  • package.json с subpath exports (./box, ./button, ./theme) — tree-shaking на уровне entry.
  • size-limit на каждый компонент — CI падает при превышении.
  • Changesets — автоматический CHANGELOG и semver.
  • CI pipeline: lint → typecheck → test → build → size-check → publish.
  • Dry-run публикация на npm (без реального релиза).

Перед началом: Модули 1-13. Инструменты: tsup, changesets, GitHub Actions. Время: ~5 ч.


🔥 Инцидент. npm publish с локальной машины. Версия не обновлена, CHANGELOG пустой. Пользователи UI Kit получают ошибку: «export Button not found». Нет exports map в package.json. Нет size-limit — пакет весит 2 МБ. Ручная публикация не масштабируется.


📖 Теория

14.1 exports-карта (уже в boilerplate)

В boilerplate уже настроена multi-entry сборка. Проверим:

json
// ui-kit/package.json (фрагмент)
{
  "sideEffects": false,
  "exports": {
    ".":          { "import": "./dist/index.js",            "types": "./dist/index.d.ts" },
    "./box":      { "import": "./dist/components/Box/index.js",       "types": "./dist/components/Box/index.d.ts" },
    "./text":     { "import": "./dist/components/Text/index.js",      "types": "./dist/components/Text/index.d.ts" },
    "./button":   { "import": "./dist/components/Button/index.js",    "types": "./dist/components/Button/index.d.ts" },
    "./theme":    { "import": "./dist/theme/index.js",       "types": "./dist/theme/index.d.ts" },
    "./styles/reset.css": "./dist/reset.css"
  }
}

sideEffects: false

Сообщает bundler'ам (webpack, Vite, Rollup): «все файлы в этом пакете не имеют побочных эффектов, можно tree-shake». Это значит:

ts
// Если импортировать только Button:
import { Button } from '@myorg/ui-kit/button'
// → в бандле окажется ТОЛЬКО Button, не весь UI Kit

CSS — исключение: если CSS-файлы импортируются как side-effect (import './reset.css'), пометьте их: "sideEffects": ["*.css"]. Наш reset.css попадает в exports как явный путь — поэтому false безопасен.


14.2 size-limit — бюджет на размер

Установка:

bash
npm install -D size-limit @size-limit/file

Конфигурация в ui-kit/package.json:

json
{
  "size-limit": [
    { "name": "Box",      "path": "dist/components/Box/index.js",      "limit": "2 KB" },
    { "name": "Text",     "path": "dist/components/Text/index.js",     "limit": "2 KB" },
    { "name": "Button",   "path": "dist/components/Button/index.js",   "limit": "3 KB" },
    { "name": "Icon",     "path": "dist/components/Icon/index.js",     "limit": "3 KB" },
    { "name": "Modal",    "path": "dist/components/Modal/index.js",    "limit": "5 KB" },
    { "name": "Theme",    "path": "dist/theme/index.js",               "limit": "3 KB" },
    { "name": "Full barrel", "path": "dist/index.js",                  "limit": "20 KB" }
  ],
  "scripts": {
    "size": "size-limit"
  }
}

Запуск:

bash
npm run size --workspace ui-kit

# Вывод:
# Box:         0.8 KB ✓ (limit 2 KB)
# Text:        1.2 KB ✓ (limit 2 KB)
# Button:      1.5 KB ✓ (limit 3 KB)
# Full barrel: 12.3 KB ✓ (limit 20 KB)

Если компонент превышает лимит — exit code 1, CI падает.


14.3 Changesets — версионирование и CHANGELOG

Установка (уже в boilerplate):

bash
npm install -D @changesets/cli

Как работает

  1. Студент делает PR с новой фичей (например, добавил Reaction).
  2. Запускает npm run changeset:
    🦋  Which packages would you like to include?
    ◯ @myorg/ui-kit
    
    🦋  Which type of semver bump?
    ◯ minor (new feature)
    
    🦋  Summarize the change:
    Added Reaction component with pulsation animation
    
  3. Создаётся файл .changeset/reaction-pulsation.md:
    yaml
    ---
    "@myorg/ui-kit": minor
    ---
    Added Reaction component with pulsation animation
  4. При merge в main — Changesets bot автоматически:
    • Создаёт PR «Version Packages».
    • Bumps version (0.0.1 → 0.1.0).
    • Генерирует CHANGELOG.md:
      markdown
      # @myorg/ui-kit
      
      ## 0.1.0
      
      ### Minor Changes
      - Added Reaction component with pulsation animation
  5. При merge «Version Packages» PR — пакет публикуется на npm через CI.

Конфигурация (уже в boilerplate)

json
// .changeset/config.json
{
  "changelog": "@changesets/cli/changelog",
  "commit": false,
  "fixed": [],
  "linked": [],
  "access": "public",
  "baseBranch": "main",
  "updateInternalDependencies": ["patch", "minor"],
  "ignore": ["@myorg/app"]
}

14.4 CI/CD pipeline (обновляем существующий)

Полный pipeline с размером бандла и changesets:

yaml
# .github/workflows/ci.yml
name: CI

on:
  push:
    branches: [main]
  pull_request:

jobs:
  quality:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '22'
          cache: 'npm'
      - run: npm ci

      - name: Lint
        run: npm run lint

      - name: Typecheck
        run: npm run typecheck

      - name: Unit tests + coverage
        run: npm run test:coverage

      - name: Build UI Kit
        run: npm run build:ui-kit

      - name: Size check
        run: npm run size --workspace ui-kit

      - name: Build App
        run: npm run build:app

      - name: Upload coverage
        if: always()
        uses: actions/upload-artifact@v4
        with:
          name: coverage
          path: coverage/

  e2e:
    runs-on: ubuntu-latest
    needs: quality
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with: { node-version: '22', cache: 'npm' }
      - run: npm ci
      - run: npx playwright install --with-deps chromium
      - run: npm run build:ui-kit && npm run build:app && npx playwright test --project=chromium

  release:
    runs-on: ubuntu-latest
    needs: [quality, e2e]
    if: github.ref == 'refs/heads/main'
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '22'
          cache: 'npm'
          registry-url: 'https://registry.npmjs.org'
      - run: npm ci
      - name: Build UI Kit
        run: npm run build:ui-kit
      - name: Create release PR or publish
        uses: changesets/action@v1
        with:
          publish: npm run publish:ui-kit
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

14.5 Публикация на npm (dry-run)

Студент не публикует реально, но проверяет что может:

bash
# Собрать
npm run build:ui-kit

# Проверить содержимое пакета (что попадёт в npm)
npm pack --workspace ui-kit --dry-run

# Вывод:
# npm notice === Tarball Contents ===
# npm notice 1.2kB  dist/index.js
# npm notice 0.8kB  dist/components/Box/index.js
# npm notice 1.5kB  dist/components/Button/index.js
# npm notice ...
# npm notice 5.0kB  dist/theme/index.js
# npm notice 0.5kB  package.json
# npm notice 3.0kB  README.md

Если в выводе есть node_modules/, .storybook/, src/ — files поле в package.json настроено неправильно. Должны быть только dist/ и README.md.


14.6 Антипаттерны

АнтипаттернКак правильно
peerDependencies не declaredReact/react-dom в peerDependencies, не в dependencies
sideEffects: false без проверки CSSЕсли есть side-effect CSS — "sideEffects": ["*.css"]
Публикация без size-limitsize-limit в CI — overflow = fail
semver вручнуюchangesets: автоматический bump + CHANGELOG
Нет files в package.jsonЯвно: "files": ["dist", "README.md"]
latest версии в depsЯвные ^ semver диапазоны

Компромиссы (Trade-offs)

РешениеПлюсыМинусы
exports map (subpath)Tree-shaking на entry, чистый импортНужно поддерживать каждый путь
sideEffects: falseBundler tree-shake'ит всёCSS-импорты могут потеряться
size-limit в CIЛовит рост бандла в PRЛимиты нужно калибровать
ChangesetsАвто-bump версии + CHANGELOGНужен бот и NPM_TOKEN
npm publish из CIВоспроизводимо, без человекаСложнее откатить

💻 14.7 Практика

  1. Установите size-limit и @size-limit/file.
  2. Настройте size-limit в ui-kit/package.json (§14.2).
  3. Создайте changeset: npm run changeset → .changeset/example.md.
  4. Обновите CI workflow (§14.4).
  5. Запустите npm pack --dry-run — проверьте что в пакете только dist/.
  6. Запустите npm run size — все компоненты в пределах лимитов.
bash
npm run verify
npm run size --workspace ui-kit
npm pack --workspace ui-kit --dry-run

14.8 Артефакты сдачи М14

АртефактКритерий
size-limit выводВсе компоненты в пределах лимитов
npm pack --dry-runВ пакете только dist/ + README.md + package.json
.changeset/*.mdМинимум один changeset (например, «Added Modal component»)
CI workflowjob release с changesets/action настроен

Вперёд → Модуль 15: итоговое приложение, Go-мост и ревью.


🧩 Проверь себя

1. Build и publish UI Kit lib — как и куда?
2. changesets для UI Kit versioning — как работает?
3. CI/CD для UI Kit lib — обязательный шаг?
4. Semver: breaking-change — что делать?
5. npm publish из CI — зачем NPM_TOKEN (secret)?

🔗 Мостик к Модулю 15

UI Kit опубликован как npm-пакет. Теперь свяжем фронтенд с Go-бэкендом через OpenAPI-контракт и проведём финальное ревью архитектуры.