В этой статье мы подробно разберем процесс настройки UI‑Kit на React — от установки зависимостей до сборки готового пакета. Мы настроим полный цикл разработки: сборку, тестирование, линтинг и документацию.

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

Также вы можете взять компоненты дизайн‑системы по ссылке.

? Начало работы: инициализация проекта

Шаг 1: Создаем проект и устанавливаем зависимости

bash
# Создаем директорию и инициализируем package.json
mkdir ui-kit
cd ui-kit
npm init -y

Здесь мы создаем основу нашего будущего UI‑Kit — папку проекта и файл package.json. Этот файл является сердцем любого Node.js проекта: в нем хранится информация о проекте, его зависимостях и скриптах. Ключ -y позволяет согласиться со значениями по умолчанию, чтобы быстро пройти инициализацию.

Шаг 2: Устанавливаем основные зависимости

bash
# React и TypeScript
npm install --save-dev react react-dom
npm install --save-peer react react-dom
npm install --save-dev typescript @types/react @types/react-dom

React и ReactDOM — это ядро, на котором будет построена наша библиотека. TypeScript добавляет статическую типизацию, что помогает предотвращать ошибки на этапе разработки и улучшает автодополнение. Мы устанавливаем React и как peerDependencies (чтобы избежать дублирования в финальном приложении), и как devDependencies (чтобы использовать их во время разработки и тестирования).

Шаг 3: Устанавливаем инструменты сборки

bash
# Rollup и плагины
npm install -D rollup
npm install -D @rollup/plugin-typescript
npm install -D @rollup/plugin-node-resolve
npm install -D @rollup/plugin-commonjs
npm install -D rollup-plugin-postcss
npm install -D postcss

Rollup — это современный сборщик модулей, идеально подходящий для библиотек. Мы настраиваем его с помощью плагинов:

  • @rollup/plugin-typescript для работы с TypeScript;

  • @rollup/plugin-node-resolve чтобы Rollup мог находить модули в node_modules;

  • @rollup/plugin-commonjs для преобразования модулей CommonJS в ES‑модули, которые понимает Rollup;

  • rollup-plugin-postcss для обработки CSS‑файлов, их минификации и извлечения в отдельный файл.

Шаг 4: Устанавливаем тестирование

bash
# Jest и Testing Library
npm install -D jest jest-environment-jsdom
npm install -D @testing-library/react @testing-library/jest-dom
npm install -D @testing-library/user-event @testing-library/dom
npm install -D @types/jest identity-obj-proxy

Jest — это мощный и популярный фреймворк для тестирования. Testing Library предоставляет набор утилит для тестирования React‑компонентов так, как это делают пользователи, фокусируясь на их доступности и поведении, а не на внутренней реализации. identity-obj-proxy помогает имитировать импорт CSS‑модулей в тестах.

Шаг 5: Устанавливаем Babel для транспиляции

bash
# Babel пресеты
npm install -D @babel/preset-env
npm install -D @babel/preset-react
npm install -D @babel/preset-typescript

Babel преобразует современный JavaScript и JSX/TSX‑код в версию, понятную старым браузерам и средам (например, Jest). Пресеты — это предустановленные наборы правил для преобразования определенных синтаксических конструкций (ES6+, React, TypeScript).

Шаг 6: Устанавливаем линтинг и форматирование

bash
# ESLint и Prettier
npm install -D eslint @eslint/js jiti
npm install -D typescript-eslint
npm install -D eslint-plugin-react
npm install -D prettier
npm install -D globals

ESLint и Prettier — незаменимые инструменты для поддержания качества и единообразия кода. ESLint находит и исправляет проблемные паттерны в коде, а Prettier автоматически форматирует код по заданным правилам, избавляя команду от споров о стиле.

Шаг 7: Устанавливаем Storybook для документации

bash
# Storybook
npm install -D storybook @storybook/react
npm install -D @storybook/react-vite

Storybook — это интерактивная среда для разработки и документирования компонентов в изоляции. Она позволяет просматривать компоненты в разных состояниях, писать для них документацию и тестировать их визуально.

⚙️ Настройка конфигурационных файлов

Шаг 8: Создаем структуру проекта

bash
# Создаем основную структуру
mkdir -p src/Button src/_internal/test src/types .storybook

Четкая структура папок — залог поддерживаемости кода. Мы заранее создаем папки для исходного кода (src), компонентов (src/Button), внутренних утилит (src/_internal), типов и конфигурации Storybook.

Шаг 9: Настраиваем TypeScript

Создаем tsconfig.json:

json
{
  "compilerOptions": {
    "target": "ES2022",
    "lib": ["DOM", "ES2022"],
    "allowJs": true,
    "skipLibCheck": true,
    "esModuleInterop": true,
    "allowSyntheticDefaultImports": true,
    "strict": true,
    "forceConsistentCasingInFileNames": true,
    "moduleResolution": "bundler",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "noEmit": true,
    "jsx": "react-jsx",
    "declaration": true,
    "outDir": "lib",
    "rootDir": "src",
    "module": "ESNext",
    "types": ["jest", "@testing-library/jest-dom"]
  },
  "include": ["src", "*.js", "*.ts"],
  "exclude": ["lib", "node_modules", "**/*.stories.tsx", "**/*.test.tsx"]
}

Файл tsconfig.json сообщает TypeScript, как компилировать наш проект. Мы настраиваем его для работы с современным JavaScript (ES2022), строгой типизацией (strict: true), JSX и генерацией файлов с объявлениями типов (declaration: true), которые необходимы для использования нашей библиотеки в других TypeScript‑проектах.

Шаг 10: Настраиваем Rollup для сборки

Создаем rollup.config.js:

javascript
import commonjs from "@rollup/plugin-commonjs";
import resolve from "@rollup/plugin-node-resolve";
import typescript from "@rollup/plugin-typescript";
import postcss from "rollup-plugin-postcss";

const pkg = require("./package.json");

const external = [
  ...(pkg.dependencies ? Object.keys(pkg.dependencies) : []),
  ...(pkg.devDependencies ? Object.keys(pkg.devDependencies) : []),
  ...(pkg.peerDependencies ? Object.keys(pkg.peerDependencies) : []),
];

const baseOutput = {
  dir: "lib",
  sourcemap: true,
  exports: "named",
};

const plugins = [
  postcss({
    modules: true,
    extract: true,
    minimize: true,
    inject: false,
  }),
  resolve({
    extensions: [".ts", ".tsx", ".js", ".jsx"],
  }),
  commonjs(),
  typescript({
    outDir: "lib",
    declarationDir: "lib",
    declaration: true,
    rootDir: "src",
  }),
];

export default [
  {
    input: ["src/index.ts"],
    output: [
      {
        ...baseOutput,
        format: "esm",
      },
      {
        ...baseOutput,
        format: "cjs",
        entryFileNames: "[name].cjs",
      },
    ],
    external,
    plugins,
  },
];

Это основная конфигурация нашего сборщика. Мы указываем точку входа, настраиваем плагины для обработки разных типов файлов и задаем сборку в двух форматах — ESM (для современных сборщиков) и CommonJS (для Node.js и некоторых других окружений). Важно отметить внешние зависимости (external), чтобы они не попадали в бандл нашей библиотеки.

Шаг 11: Настраиваем Jest для тестирования

Создаем jest.config.json:

json
{
  "clearMocks": true,
  "logHeapUsage": true,
  "passWithNoTests": true,
  "testEnvironment": "jsdom",
  "transform": {
    "^.+\\.(ts|tsx|js|jsx)$": "babel-jest"
  },
  "transformIgnorePatterns": ["node_modules/(?!(.*\\.css$))"],
  "moduleFileExtensions": ["ts", "tsx", "js", "jsx", "json"],
  "setupFilesAfterEnv": ["<rootDir>/src/_internal/test/setupTest.ts"],
  "moduleNameMapper": {
    "\\.(css|less|scss|sass)$": "identity-obj-proxy"
  }
}
```

Конфигурация Jest подключает окружение для тестирования DOM (`jsdom`), настраивает Babel для транспиляции тестов, добавляет маппинг для CSS-модулей (чтобы Jest их понимал) и указывает файл с дополнительной настройкой тестового окружения.

Шаг 12: Настраиваем Babel

Создаем `.babelrc.json`:

```json
{
  "presets": [
    ["@babel/preset-env", { "targets": { "node": "current" } }],
    ["@babel/preset-react", { "runtime": "automatic" }],
    "@babel/preset-typescript"
  ]
}

Babel использует пресеты, которые мы установили ранее, чтобы преобразовывать наш код. Мы настраиваем его для поддержки последних версий JavaScript, React с новой JSX‑трансформацией (runtime: automatic) и TypeScript.

Шаг 13: Настраиваем ESLint

Создаем eslint.config.ts:

typescript
import js from "@eslint/js";
import globals from "globals";
import tseslint from "typescript-eslint";
import pluginReact from "eslint-plugin-react";
import { defineConfig } from "eslint/config";

export default defineConfig([
  {
    ignores: ["lib/**", "dist/**", "build/**"],
  },
  {
    files: ["**/*.{js,mjs,cjs,ts,mts,cts,jsx,tsx}"],
    plugins: { js },
    extends: ["js/recommended"],
    languageOptions: {
      globals: {
        ...globals.browser,
        ...globals.node,
        require: "readonly",
      },
    },
  },
  tseslint.configs.recommended,
  {
    ...pluginReact.configs.flat.recommended,
    settings: {
      react: {
        version: "detect",
      },
    },
  },
  {
    files: ["**/*.config.js", "**/*.config.ts", "rollup.config.js"],
    rules: {
      "@typescript-eslint/no-require-imports": "off",
    },
  },
  {
    files: ["**/*.{jsx,tsx}"],
    rules: {
      "react/react-in-jsx-scope": "off",
    },
  },
]);

Новая плоская конфигурация ESLint позволяет гибко настраивать правила. Мы подключаем рекомендованные конфигурации для JavaScript, TypeScript и React, а также задаем глобальные переменные для браузера и Node.js. Отключаем некоторые правила для конфигурационных файлов, где использование require является нормой.

Шаг 14: Настраиваем Storybook

UI-kit t2. Насчитывает 64 компонента и 10 хуков
UI-kit t2. Насчитывает 64 компонента и 10 хуков

Создаем .storybook/main.js:

javascript
/** @type { import('@storybook/react-vite').StorybookConfig } */
const config = {
  stories: ["../src/**/*.stories.@(js|jsx|mjs|ts|tsx)"],
  addons: [],
  framework: {
    name: "@storybook/react-vite",
    options: {},
  },
};
export default config;

Создаем .storybook/preview.js:

javascript
/** @type { import('@storybook/react-vite').Preview } */
const preview = {
  parameters: {
    controls: {
      matchers: {
        color: /(background|color)$/i,
        date: /Date$/i,
      },
    },
  },
};

export default preview;

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

?️ Создаем файлы компонентов

Шаг 15: Создаем тип для CSS Modules

src/types/css-modules.d.ts:

typescript
declare module "*.module.css" {
  const classes: { [key: string]: string };
  export default classes;
}

TypeScript по умолчанию не знает о формате CSS Modules. Этот файл объявляет модуль, сообщая TypeScript, что при импорте *.module.css мы получаем объект, где ключи — это названия классов, а значения — строки. Это избавляет от ошибок типизации при обращении к styles.button.

Шаг 16: Создаем настройку тестов

src/_internal/test/setupTest.ts:

typescript
import "@testing-library/jest-dom";

Этот файл выполняется перед каждым тестовым прогоном. Здесь мы подключаем матчеры из @testing-library/jest-dom (например, toBeInTheDocument()), которые значительно расширяют стандартные возможности утверждений (assertions) в Jest.

Шаг 17: Создаем компонент Button

src/Button/Button.tsx:

typescript
import { ButtonHTMLAttributes, DetailedHTMLProps, FC } from "react";
import styles from "./styles.module.css";

type ButtonProps = DetailedHTMLProps<
  ButtonHTMLAttributes<HTMLButtonElement>,
  HTMLButtonElement
>;

const Button: FC<ButtonProps> = (props) => {
  return <button {...props} className={styles.button} />;
};

export type { ButtonProps };
export default Button;

src/Button/styles.module.css:

css
.button {
  background-color: #4caf50;
  border: none;
  color: white;
  padding: 12px 24px;
  text-align: center;
  text-decoration: none;
  display: inline-block;
  font-size: 16px;
  margin: 4px 2px;
  cursor: pointer;
  border-radius: 8px;
  transition: background-color 0.3s;

  &:hover {
    background-color: #45a049;
  }

  &:active {
    background-color: #3d8b40;
    transform: translateY(1px);
  }
}

Мы создаем наш первый компонент — кнопку. Это функциональный компонент, который принимает все стандартные свойства HTML‑элемента button. Мы используем CSS Modules для стилизации, что обеспечивает изоляцию стилей и избегает конфликтов имен.

Шаг 18: Создаем тесты для компонента

src/Button/Button.test.tsx:

typescript
import { render, screen, fireEvent } from "@testing-library/react";
import Button from "./Button";

describe("Button Component", () => {
  it("renders with correct text", () => {
    render(<Button>Click me</Button>);
    expect(
      screen.getByRole("button", { name: /click me/i }),
    ).toBeInTheDocument();
  });

  it("calls onClick when clicked", () => {
    const handleClick = jest.fn();
    render(<Button onClick={handleClick}>Click me</Button>);

    fireEvent.click(screen.getByRole("button"));
    expect(handleClick).toHaveBeenCalledTimes(1);
  });

  it("matches snapshot", () => {
    const { container } = render(<Button>Save</Button>);
    expect(container.firstChild).toMatchSnapshot();
  });
});

Пишем модульные тесты, которые проверяют:

  • Рендерится ли кнопка с переданным текстом;

  • Вызывается ли переданный обработчик onClick при клике;

  • Соответствует ли вывод компонента сохраненному снимку (snapshot), что помогает быстро обнаружить незапланированные изменения в вёрстке.

Шаг 19: Создаем Storybook stories

src/Button/Button.stories.tsx:

typescript
import type { Meta, StoryObj } from "@storybook/react";
import { Button } from "./index";

const meta: Meta<typeof Button> = {
  component: Button,
};

export default meta;

type Story = StoryObj<typeof Button>;

export const Basic: Story = {
  args: {
    children: "Apply",
  },
};

Story — это, по сути, изолированный пример использования компонента. В Storybook мы можем визуально представлять наши компоненты, менять их свойства (props) через панель управления и документировать ожидаемое поведение для команды разработки и дизайнеров.

Шаг 20: Настраиваем экспорты

src/Button/index.ts:

typescript
export * from "./Button";

src/index.ts:

typescript
export * from "./Button";

Файлы index.ts — это точка входа в наши модули. Они реэкспортируют наружу только то, что должно быть публичным API нашего компонента и библиотеки в целом. Это позволяет импортировать компоненты удобным способом: import { Button } from 'ui-kit';.

? Запуск и использование

Шаг 21: Добавляем скрипты в package.json

Обновляем package.json:

json
{
  "name": "ui-kit",
  "version": "1.0.0",
  "main": "index.cjs",
  "module": "index.js",
  "typings": "index.d.ts",
  "style": "index.css",
  "files": ["*.js", "*.cjs", "*.d.ts", "*.css", "*.map"],
  "scripts": {
    "clean": "rm -rf dist lib",
    "build": "npm run clean && rollup -c --bundleConfigAsCjs",
    "pack": "npm run build && mkdir -p dist && cp -r lib/* dist/ && cp package.json dist/ && cd dist && npm pack && mv *.tgz ../ && cd .. && rm -rf dist",
    "storybook": "storybook dev -p 6006",
    "build-storybook": "storybook build",
    "lint": "eslint",
    "format": "prettier . --write",
    "test": "jest"
  },
  "peerDependencies": {
    "react": "^19.2.0",
    "react-dom": "^19.2.0"
  }
}

Мы добавляем в package.json удобные npm‑скрипты, которые автоматизируют рутинные задачи: сборку, очистку, запуск дев‑сервера, тестирование, линтинг и упаковку библиотеки в .tgz архив, который можно установить в другом проекте для тестирования.

Шаг 22: Запускаем инструменты разработки

bash
# Сборка библиотеки
npm run build

# Запуск Storybook
npm run storybook

# Запуск тестов
npm run test

# Линтинг
npm run lint

# Форматирование кода
npm run format

# Создание npm пакета
npm run pack

Теперь, когда всё настроено, мы можем пользоваться плодами нашего труда. Эти команды запускают различные процессы разработки, позволяя собирать библиотеку, просматривать и разрабатывать компоненты в Storybook, проверять код и запускать тесты.

? Что мы получили в результате

После выполнения всех шагов у нас есть:

Современная система сборки с Rollup

Поддержка TypeScript с генерацией declaration files

CSS Modules с минификацией и извлечением стилей

Полная система тестирования с Jest и Testing Library

Интерактивная документация со Storybook

Линтинг и форматирование с ESLint и Prettier

Поддержка ESM и CommonJS для широкой совместимости

? Заключение

Мы настроили полнофункциональную среду разработки UI-Kit, которая включает все современные инструменты фронтенд-разработки. Такой подход позволяет:

  • Быстро разрабатывать новые компоненты

  • Обеспечивать качество кода через тесты и линтинг

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

  • Легко поддерживать и расширять библиотеку

Какие инструменты вы используете в своих UI‑Kit? Делитесь опытом в комментариях!

Комментарии (0)