В этом руководстве вы пройдете через процесс создания и публикации собственной библиотеки компонентов React и размещения ее на Github.

В конце этого руководства вы сможете использовать компоненты похожие на показанный ниже во всех своих будущих проектах React:

npm install @my-github-account/my-cool-component-library
import MyCustomComponent from '@my-github-account/my-cool-component-library';
const MyApp = () => {
  return (
    <div>
    <MyCustomComponent />
    </div>
  )
}

Предварительные условия и установка

Этот проект предполагает, что вы знакомы и установили:

  • Редактор кода / IDE (в этом учебнике используется VS Code, но подойдет любая IDE).

  • NPM (NPM устанавливается, когда вы устанавливаете Node.js на свою машину).

  • Установка пакетов (предполагается, что вы знаете, как добавить пакеты в проект Javascript с помощью npm install).

  • Bash-терминал (или другой терминал, с которым вам удобно работать для выполнения команд).

  • Git (мы будем создавать git-репозиторий на нашей машине и публиковать его на Github, хотя все инструкции будут предоставлены, как делать это).

  • React (как создавать простые компоненты с помощью JSX).

  • Typescript (как создать интерфейс объекта с простыми свойствами).

Сначала мы инициализируем наш проект.

npm init

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

Далее добавляем инструменты, необходимые для создания наших компонентов.

npm install react typescript @types/react --save-dev

Создание компонентов

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

В корне вашего проекта создайте следующую структуру файлов:

.
├── src
│   ├── components
|   │   ├── Button
|   |   │   ├── Button.tsx
|   |   │   └── index.ts
|   │   └── index.ts
│   └── index.ts
├── package.json
└── package-lock.json

Убедитесь в том, что вы дважды проверили свою структуру. У вас должно быть три файла index.ts и файл Button.tsx внутри каталога Button. Если у вас есть предпочтительный способ структурирования компонентов React в проекте, вы, конечно, можете делать это так, как вам нравится, но в данном руководстве мы будем придерживаться именно этой структуры.

Начнём с создания файла Button.tsx:

src/components/Button/Button.tsx

import React from "react";
export interface ButtonProps {
label: string;
}
const Button = (props: ButtonProps) => {
return <button>{props.label}</button>;
};
export default Button;

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

После создания кнопки мы обновим index файл в каталоге Button:

src/components/Button/index.ts

export { default } from "./Button";

Затем экспортируем эту кнопку из директории components:

src/components/index.ts

export { default as Button } from "./Button";

И, наконец, мы экспортируем все наши компоненты из базового каталога src:

src/index.ts

export * from './components';

Добавление Typescript

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

Вы можете инициализировать конфигурацию по умолчанию, выполнив следующую команду:

npx tsc --init

Это создаст для нас файл tsconfig.json в корне нашего проекта, который содержит все параметры конфигурации по умолчанию для Typescript.

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

В зависимости от вашей IDE вы можете заметить, что сразу после инициализации в вашем проекте начинают появляться ошибки. На это есть две причины: первая - Typescript не настроен на понимание React по умолчанию, а вторая - мы еще не определили наш метод для работы с модулями: поэтому он может не понимать, как управлять всеми нашими экспортами.

Чтобы исправить это, мы добавим следующие значения в tsconfig.json:

{
  "compilerOptions": {
    // по умолчанию
    "target": "es5", 
    "esModuleInterop": true, 
    "forceConsistentCasingInFileNames": true,
    "strict": true, 
    "skipLibCheck": true,

    // добавленное
    "jsx": "react", 
    "module": "ESNext",  
    "declaration": true,
    "declarationDir": "types",
    "sourceMap": true,
    "outDir": "dist",
    "moduleResolution": "node",
    "allowSyntheticDefaultImports": true,
    "emitDeclarationOnly": true,
  }
}

Я разделил эти значения на несколько разных разделов, базируясь на стандартном файле tsconfig.json, созданном с помощью последней версии Typescript на момент написания статьи (4.4). Значения, прокомментированные по умолчанию, уже должны быть установлены для вас по умолчанию (однако конечно стоит дважды проверить и убедиться в этом).

Значения, отмеченные добавленные, - это новые значения, которые необходимы нам для нашего проекта. Мы кратко опишем, зачем они нам нужны:

  • "jsx": "react" -- Преобразование JSX в код React.

  • "module": "ESNext" -- Генерировать современные JS-модули для нашей библиотеки

  • "declaration": true -- Вывод файла .d.ts для типов нашей библиотеки

  • "declarationDir": "types" -- Куда помещать файлы .d.ts.

  • "sourceMap": true -- Сопоставление (маппинг) JS-кода с исходным текстом TS-файла для отладки.

  • "outDir": "dist" -- Каталог, где будет сгенерирован проект.

  • "moduleResolution": "node" -- Следовать правилам node.js для нахождения модулей.

  • "allowSyntheticDefaultImports": true -- Предполагает дефолтный экспорт, если он не указан вручную

  • "emitDeclarationOnly": true -- Не генерировать JS (это сделает rollup), только декларации экспортируемых типов.

После добавления этих значений в файл конфигурации TS вы должны увидеть, что ошибки в Button.tsx и других файлах немедленно исчезнут.

Добавление Rollup

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

Хотя оба инструмента могут достичь одной и той же цели в зависимости от конфигурации, обычно webpack используется для объединения приложений, в то время как rollup особенно подходит для объединения библиотек (таких как наша). Именно поэтому мы выбрали rollup.

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

Мы будем полагаться на четыре плагина для начальной настройки нашей библиотеки (другие будут добавлены позже):

Итак, теперь давайте установим rollup и наши плагины:

npm install rollup @rollup/plugin-node-resolve @rollup/plugin-typescript @rollup/plugin-commonjs rollup-plugin-dts --save-dev

Чтобы настроить, как rollup будет компоновать нашу библиотеку, нам нужно создать файл конфигурации в корне нашего проекта:

rollup.config.js

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

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

export default [
  {
    input: "src/index.ts",
    output: [
      {
        file: packageJson.main,
        format: "cjs",
        sourcemap: true,
      },
      {
        file: packageJson.module,
        format: "esm",
        sourcemap: true,
      },
    ],
    plugins: [
      resolve(),
      commonjs(),
      typescript({ tsconfig: "./tsconfig.json" }),
    ],
  },
  {
    input: "dist/esm/types/index.d.ts",
    output: [{ file: "dist/index.d.ts", format: "esm" }],
    plugins: [dts()],
  },
];

В этом файле мы импортируем наши четыре плагина, которые мы установили. Мы также импортируем наш файл package.json как CommonJS модуль в переменную под названием packageJson. Мы используем эту переменную для ссылки на значения main и module, которые мы определим в следующем разделе.

Точкой входа для нашей библиотеки (input) является файл index.ts в директории src, который экспортирует все наши компоненты. Мы будем генерировать модули ES6 и commonJS, чтобы потребители нашей библиотеки могли выбрать, какой тип им больше подходит. Мы также вызываем три из четырех плагинов на первом из двух объектов конфигурации в экспортированном массиве. Эта первая конфигурация определяет, как генерируется фактический Javascript-код нашей библиотеки.

Второй объект конфигурации определяет, как распределяются типы наших библиотек, и использует для этого плагин dts.

Последним шагом перед запуском нашего первого rollup является определение значений "main" и "module" в нашем файле package.json:

package.json

{
  "name": "template-react-component-library",
  "version": "0.0.1",
  "description": "A simple template for a custom React component library",
  "scripts": {
    "rollup": "rollup -c"
  },
  "author": "Alex Eagleson",
  "license": "ISC",
  "devDependencies": {
    "@rollup/plugin-commonjs": "^21.0.1",
    "@rollup/plugin-node-resolve": "^13.0.6",
    "@rollup/plugin-typescript": "^8.3.0",
    "@types/react": "^17.0.34",
    "react": "^17.0.2",
    "rollup": "^2.60.0",
    "rollup-plugin-dts": "^4.0.1",
    "typescript": "^4.4.4"
  },
  "main": "dist/cjs/index.js",
  "module": "dist/esm/index.js",
  "files": [
    "dist"
  ],
  "types": "dist/index.d.ts"
}

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

Наиболее важные изменения следующие:

  • "main" -- Мы определили путь вывода для модулей commonjs.

  • "module" -- Мы определили путь вывода для модулей es6.

  • "files" -- Мы определили выходной каталог для всей нашей библиотеки.

  • "types" -- Мы определили местоположение для типов нашей библиотеки.

  • "scripts" -- Мы определили новый скрипт под названием rollup. Он будет запускать пакет rollup с флагом -c, что означает "использовать файл конфигурации rollup". Если вы не знакомы с script в файле package.json, это просто сокращенные команды, которые можно запускать по имени с помощью npm run {SCRIPTNAME}. Таким образом, для запуска этой команды нужно выполнить npm run rollup.

Создание вашей библиотеки

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

.
├── src
│   ├── components
|   │   ├── Button
|   |   │   ├── Button.tsx
|   |   │   └── index.ts
|   │   └── index.ts
│   └── index.ts
├── package.json
├── package-lock.json
├── tsconfig.json
└── rollup.config.js

Содержимое каждого файла должно быть таким, как описано выше. Убедившись в этом, выполните следующую команду:

npm run rollup

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

(Если вы получили ошибку, обязательно внимательно прочитайте ее, чтобы попытаться определить проблему. Дважды проверьте, что каждый из ваших файлов в точности повторяет структуру примеров. В зависимости от количества времени, прошедшего с момента публикации этого руководства, потенциально могут быть опубликованы новые основные версии библиотек, содержащие изменения. Номера всех версий библиотек указаны выше в примере package.json, если вам нужно указать конкретную версию).

Публикация вашей библиотеки

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

Создайте новый репозиторий на Github. Я назвал свой template-react-component-library. Затем выполните шаги по инициализации вашего проекта как git-проекта, и сделайте push в ваш новый репозиторий.

Войдите в Github и создайте новый репозиторий под любым названием. Для этого примера я назвал его template-react-component-library, и он будет доступен для всех желающих клонировать и использовать публично. Вы можете сделать свою библиотеку приватной, если хотите, методы, описанные в этом руководстве, будут работать и для приватных пакетов (в случае, если вы создаете библиотеку для своей компании, например).

После создания репозитория нам нужно локально инициализировать git в нашем проекте. Выполните следующую команду:

git init

Затем создайте файл .gitignore в корне каталога (обратите особое внимание на ведущую точку, которая означает, что это скрытый файл):

.gitignore

dist
node_modules

В нашем файле .gitignore мы добавляем каталоги dist и node_modules. Причина в том, что обе эти директории являются автоматически создаваемыми директориями, которые мы создаем с помощью команд, поэтому нет необходимости включать их в наш репозиторий.

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

Этот репозиторий, который вы создали, вы будете клонировать и редактировать, когда захотите внести изменения и обновления в библиотеку компонентов. Это не сам пакет, который вы (как пользователь) будете устанавливать и использовать. Чтобы настроить в нашем проекте, куда должен быть опубликован наш пакет, нам нужно обновить package.json этой информацией:

package.json

{
  "name": "@YOUR_GITHUB_USERNAME/YOUR_REPOSITORY_NAME",
  "publishConfig": {
    "registry": "<https://npm.pkg.github.com/YOUR_GITHUB_USERNAME>"
  },
  ...
}

Вы обновите значение поля "name" и добавите новое поле "publishConfig". Обратите внимание, что значения выше, выделенные заглавными буквами, можно заменить на ваши собственные значения. Например, значение моего поля "name" будет @alexeagleson/template-react-component-library. Обратите внимание, что в поле "packageConfig" также содержится имя вашего аккаунта на Github, но это значение не сопровождается символом @.

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

Этот файл НЕ ЧАСТЬ НАШЕГО ПРОЕКТА. Это глобальный файл в централизованном месте. Для пользователей Mac/Linux он находится в вашей домашней директории ~/.npmrc.

Для пользователей Windows он также находится в вашем домашнем каталоге, хотя синтаксис будет отличаться. Что-то вроде C:\\\\Users\\\\{Ваш_WINDOWS_USERNAME}.

Для получения дополнительной информации об этом файле конфигурации читайте здесь.

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

~/.npmrc

registry=https://registry.npmjs.org/
@YOUR_GITHUB_USERNAME:registry=https://npm.pkg.github.com/
//npm.pkg.github.com/:_authToken=YOUR_AUTH_TOKEN

В примере выше есть два значения, которые нужно заменить. Первое - это YOUR_GITHUB_USERNAME. Не забудьте включить символ @.

Второе - это YOUR_AUTH_TOKEN, который мы еще не создали. Вернемся к Github!

Зайдите в свой профиль Github: Settings -> Developer Settings -> Personal access tokens. Или просто нажмите эту ссылку.

Нажмите Generate new token. Дайте ему имя, соответствующее проекту, который вы создаете. Укажите срок действия (Github рекомендует не создавать токены с бесконечным сроком действия из соображений безопасности, но это на ваше усмотрение).

Самое главное - выбрать значение доступа write:packages. Это даст вашему токену разрешение на чтение и запись пакетов в вашем аккаунте Github, что нам и нужно.

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

Основное место, куда вам нужно поместить этот токен, находится в файле ~/.npmrc, который вы создали, заменив значение YOUR_AUTH_TOKEN из примера выше.

Прежде чем продолжить, сделайте еще одну проверку на корректность, чтобы убедиться, что вы не создали файл .npmrc в корневом каталоге вашего реального проекта библиотеки. Технически это возможно, но причина, по которой вы должны быть осторожны, заключается в том, что вы можете случайно закоммитить его в свой репозиторий Github вместе с остальным кодом библиотеки и выложить свой токен в открытый доступ. Если ваш файл .npmrc находится в вашей домашней директории, риск этого сводится к минимуму.

На этом этапе, когда в файл ~/.npmrc добавлены ваше имя пользователя Github и токен доступа, вернитесь в каталог проекта и выполните следующую команду:

npm publish

(Если вам будет предложено ввести учетные данные, username - это ваше имя пользователя Github, а password - это сгенерированный вами токен доступа).

Поздравляем! Вы опубликовали версию 0.0.1 вашей библиотеки компонентов React! Вы можете просмотреть ее на своем аккаунте Github, перейдя на главную панель аккаунта и нажав на "packages" вверху справа от "repositories":

Использование вашей библиотеки

Теперь, когда ваша библиотека опубликована, вы захотите ее использовать!

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

Эти пользователи также должны добавить файл ~/.npmrc с той же информацией. Однако для большей безопасности вы можете предоставить этим пользователям токен доступа, который имеет привилегии только чтения, но не записи.

(С этого момента мы будем считать, что вы уже выполнили этот шаг или работаете с публичным репозиторием).

Поскольку мы создали библиотеку компонентов с использованием React и Typescript, мы предполагаем, что потребители нашей библиотеки также будут использовать эти инструменты. Технически все наши файлы типов (.d.ts) являются дополнительными: то есть они просто игнорируются при работе со стандартным Javascript, поэтому нет необходимости использовать Typescript для использования нашей библиотеки. Типы просто находятся там по желанию.

Однако для нашего примера мы будем использовать их, чтобы убедиться, что они работают правильно. Мы инициализируем приложение React, используя один из самых популярных и простых методов: Create React App.

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

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

npx create-react-app my-app --template typescript

Откройте новый созданный каталог my-app и выполните:

npm run start

Убедитесь, что вы можете открыть и загрузить экран приложения по умолчанию на localhost:3000 (или на любом другом порту, на котором он открывается).

Теперь приступим к проверке нашей библиотеки. Из корневого каталога вашего нового проекта my-app выполните следующую команду:

npm install @YOUR_GITHUB_USERNAME/YOUR_REPOSITORY_NAME

Так, например, для моего проекта это: npm install @alexeagleson/template-react-component-library

Предполагая, что ваши токены и конфигурация настроены правильно, все будет установлено корректно (если есть какие-либо проблемы, пересмотрите пример для конфигурации ~/.npmrc.).

Теперь откройте проект my-app в выбранной вами IDE (например, VS Code) и перейдите к файлу src/App.tsx.

Когда вы добавите компонент <Button />, если ваш редактор поддерживает функцию автоматического завершения импорта (ctrl/cmd + . для VS Code), то вы увидите, как он автоматически распознает благодаря Typescript, что наша библиотека экспортирует эту кнопку.

Давайте добавим ее! Самый простой пример обновления `src/App.tsx:

src/App.tsx

import React from "react";
import { Button } from "@alexeagleson/template-react-component-library";

function App() {
  return <Button label="Hello world!"/>;
}

export default App;

И когда мы снова запускаем npm run start, в углу появляется наша кнопка Hello world!.

Вот и все! Поздравляем! Теперь у вас есть все необходимые инструменты для создания и распространения библиотеки компонентов React с помощью Typescript! На этом этапе вы заканчиваете обучение и можете продолжить его самостоятельно, если хотите.

Если вы решите продолжить, мы рассмотрим, как расширить нашу библиотеку компонентов и включить в нее ряд чрезвычайно полезных функций, таких как:

  • CSS: Для экспорта компонентов со стилем.

  • Storybook: Для проверки наших компонентов в самой библиотеке по мере их разработки.

  • React Testing Library & Jest: Для тестирования наших компонентов

Добавление CSS

Прежде чем приступить к дополнительной настройке, мы начнем с создания CSS-файла, который будет применять некоторые стили к нашей кнопке. Внутри директории Button, где находится наш компонент, мы создадим файл под названием: Button.css:

src/components/Button/Button.css

button {
  font-size: 60px;
}

Это превратит нашу обычную кнопку Hello world! в РЕАЛЬНО БОЛЬШУЮ кнопку.

Далее мы укажем, что эти стили должны быть применены к нашему компоненту кнопки. Мы будем использовать специальный синтаксис, который не является родным для Javascript, но благодаря rollup и соответствующим плагинам мы можем его использовать. Обновите наш файл Button.tsx следующим образом:

src/components/Button/Button.tsx

import React from "react";
import "./Button.css";

export interface ButtonProps {
  label: string;
}

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

export default Button;

Обратите внимание на import './Button.css', который был добавлен.

Теперь нам нужно указать rollup, как обрабатывать этот синтаксис. Для этого мы используем плагин под названием rollup-plugin-postcss. Выполните следующую команду:

npm install rollup-plugin-postcss --save-dev

Далее нам нужно обновить конфигурацию rollup:

rollup.config.js

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

// НОВОЕ
import postcss from "rollup-plugin-postcss";

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

export default [
  {
    input: "src/index.ts",
    output: [
      {
        file: packageJson.main,
        format: "cjs",
        sourcemap: true,
      },
      {
        file: packageJson.module,
        format: "esm",
        sourcemap: true,
      },
    ],
    plugins: [
      resolve(),
      commonjs(),
      typescript({ tsconfig: "./tsconfig.json" }),

      // NEW
      postcss(),
    ],
  },
  {
    input: "dist/esm/types/index.d.ts",
    output: [{ file: "dist/index.d.ts", format: "esm" }],
    plugins: [dts()],

    // НОВОЕ
    external: [/\.css$/],
  },
];

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

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

package.json

{
  "version": "0.0.2",
  ...
}

Теперь выполните эти команды:

npm run rollup
npm publish

На стороне потребления библиотеки (my-app React app из нашего учебника) нам также необходимо обновить пакет, чтобы получить последнюю версию. Самый простой способ - увеличить номер версии в файле package.json приложения my-app. Он должен сейчас быть ^0.0.1. Увеличьте его до ^0.0.2 и затем вы можете обновить пакет с помощью команды npm install:

npm install
npm run start

И вас ждет огромный компонент кнопки из нашей библиотеки, который теперь поддерживает связывание с CSS!

Оптимизация

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

Второй - обновить некоторые из наших зависимостей сделав их peerDependencies. С помощью плагина peer dependencies от rollup мы можем сообщить проектам, использующим наши библиотеки, какие зависимости необходимы (например, React), но не упаковывать копию React с самой библиотекой. Если потребитель уже имеет React в своем проекте, он будет использовать его, в противном случае он будет установлен при запуске npm install.

Сначала мы установим эти два плагина:

npm install rollup-plugin-peer-deps-external rollup-plugin-terser --save-dev

Затем мы обновим нашу конфигурацию rollup:

rollup.config.js

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

// НОВОЕ
import { terser } from "rollup-plugin-terser";
import peerDepsExternal from 'rollup-plugin-peer-deps-external';

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

export default [
  {
    input: "src/index.ts",
    output: [
      {
        file: packageJson.main,
        format: "cjs",
        sourcemap: true,
      },
      {
        file: packageJson.module,
        format: "esm",
        sourcemap: true,
      },
    ],
    plugins: [
      // НОВОЕ
      peerDepsExternal(),

      resolve(),
      commonjs(),
      typescript({ tsconfig: "./tsconfig.json" }),
      postcss(),

      // НОВОЕ
      terser(),
    ],
  },
  {
    input: "dist/esm/types/index.d.ts",
    output: [{ file: "dist/index.d.ts", format: "esm" }],
    plugins: [dts()],
    external: [/\.css$/],
  },
];

Затем мы перемещаем React из devDependencies в peerDependencies в нашем файле package.json:

package.json

{
  "devDependencies": {
    "@rollup/plugin-commonjs": "^21.0.1",
    "@rollup/plugin-node-resolve": "^13.0.6",
    "@rollup/plugin-typescript": "^8.3.0",
    "@types/react": "^17.0.34",
    "rollup": "^2.60.0",
    "rollup-plugin-dts": "^4.0.1",
    "rollup-plugin-peer-deps-external": "^2.2.4",
    "rollup-plugin-postcss": "^4.0.1",
    "rollup-plugin-terser": "^7.0.2",
    "typescript": "^4.4.4"
  },
  "peerDependencies": {
    "react": "^17.0.2"
  },
  ...

Добавление тестов

Чтобы добавить тесты для наших компонентов, мы установим React Testing Library, а для запуска тестов установим Jest.

npm install @testing-library/react jest @types/jest --save-dev

Внутри каталога Button создайте новый файл Button.test.tsx.

src/components/Button/Button.test.tsx

import React from "react";
import { render } from "@testing-library/react";

import Button from "./Button";

describe("Button", () => {
  test("renders the Button component", () => {
    render(<Button label="Hello world!" />);
  });
});

Это позволит отобразить нашу кнопку в небраузерной реализации DOM и убедиться, что она правильно монтируется. Это очень простой тест, но он служит хорошим примером синтаксиса, который можно использовать для начала работы. Для более глубокого изучения читайте дальше в React Testing Library документация.

Перед запуском тестов нам нужно настроить jest и создать сценарий запуска тестов в нашем package.json. Начнем с конфигурации, создадим файл jest.config.js в корне проекта:

jest.config.js

module.exports = {
  testEnvironment: "jsdom",
};

Это указывает Jest на использование jsdom в качестве реализации DOM.

Далее обновите файл package.json:

package.json

{
  "scripts": {
    "rollup": "rollup -c",
    "test": "jest"
  },
  ...
}

Теперь мы можем запустить наши тесты:

npm run test

К сожалению, мы получим ошибку! Ошибка возникает, когда встречается наш JSX-код. Если вы помните, мы использовали Typescript для обработки JSX в нашей конфигурации rollup, а также плагин Typescript для rollup, чтобы научить его делать это. К сожалению, у нас нет такой настройки для Jest.

Нам потребуется установить Babel для обработки преобразований JSX. Нам также нужно установить плагин для Jest под названием babel-jest, который указывает Jest использовать Babel! Давайте установим их сейчас, вместе с плагинами Babel для обработки нашего кода Typescript и React. Общий набор всех плагинов выглядит следующим образом:

npm install @babel/core @babel/preset-env @babel/preset-react @babel/preset-typescript babel-jest --save-dev

Теперь мы создадим наш конфигурационный файл Babel в корне нашего проекта, который укажет Babel использовать все эти плагины, которые мы только что установили:

babel.config.js

module.exports = {
  presets: [
    "@babel/preset-env",
    "@babel/preset-react",
    "@babel/preset-typescript",
  ],
};

Теперь мы должны иметь возможность запускать наши тесты с помощью npm run test... но... есть еще одна проблема!

Вы получите ошибку, говорящую, что импорт файла .css не понят. Это имеет смысл, потому что, опять же, мы настроили плагин postcss для rollup, чтобы справиться с этим, но мы не сделали ничего подобного для Jest.

Последним шагом будет установка пакета под названием identity-obj-proxy. Он позволяет настроить Jest так, чтобы любой тип импорта рассматривался как просто общие объекты. Поэтому мы сделаем это с файлами CSS, чтобы не получать ошибку.

npm install identity-obj-proxy --save-dev

Нам нужно обновить конфигурацию Jest, чтобы включить проперти moduleNameMapper. Мы также добавили less и scss на всякий случай, если вы захотите расширить свой проект, чтобы использовать их:

jest.config.js

module.exports = {
  testEnvironment: "jsdom",
  moduleNameMapper: {
    ".(css|less|scss)$": "identity-obj-proxy",
  },
};

Теперь, наконец, если вы выполнили все шаги до этого момента, можете приступать:

npm run test

И вас ждет успешный тест!

Добавление Storybook

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

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

Инициализация Storybook очень проста. Для его автоматической установки и настройки достаточно выполнить следующую команду:

npx sb init --builder webpack5

(Обратите внимание, что на момент написания этой статьи Storybook все еще по умолчанию использует webpack 4, поэтому мы добавили дополнительный флаг builder. Предположительно, скоро по умолчанию будет использоваться 5, так что в будущем это может оказаться ненужным).

В отличие от некоторых других инструментов, которые мы добавляли до сих пор, Storybook больше похож на пакет "всё включено", который обрабатывает большинство начальных настроек за вас. Он даже автоматически добавит scripts для запуска в ваш файл package.json.

Вы также заметите, что он создает каталог stories в вашем каталоге src. Этот каталог полон предварительно созданных шаблонов, которые вы можете использовать в качестве примера того, как создавать свои собственные stories. Я рекомендую вам не удалять их, пока вы не освоитесь со Storybook и не научитесь писать свои собственные истории, иметь их под рукой будет очень удобно.

Теперь давайте создадим простую story для нашей кнопки. Создайте новый файл в директории Button под названием Button.stories.tsx:

src/components/Button/Button.stories.tsx

import React from "react";
import { ComponentStory, ComponentMeta } from "@storybook/react";
import Button from "./Button";

// больше про дефолтный экспорт: https://storybook.js.org/docs/react/writing-stories/introduction#default-export
export default {
  title: "ReactComponentLibrary/Button",
  component: Button,
} as ComponentMeta<typeof Button>;

// больше про шаблоны компонента: https://storybook.js.org/docs/react/writing-stories/introduction#using-args
const Template: ComponentStory<typeof Button> = (args) => <Button {...args} />;

export const HelloWorld = Template.bind({});
// больше про args: https://storybook.js.org/docs/react/writing-stories/args
HelloWorld.args = {
  label: "Hello world!",
};

export const ClickMe = Template.bind({});
ClickMe.args = {
  label: "Click me!",
};

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

  • default export определяет, где кнопка будет отображаться в Storybook. Я выбрал ReactComponentLibrary в качестве простого названия, чтобы сгруппировать наши кастомные компоненты отдельно от примеров.

  • Template определяет, какой компонент будет отображаться, и какие args/props по умолчанию следует применить к нему.

  • Объекты Template.bind являются экземплярами или примерами состояния компонента. Поэтому в реальном проекте у вас может быть что-то вроде "LargeButton" и "SmallButton". Поскольку наша кнопка всегда большая, я просто использовал пример тестирования кнопки с двумя разными надписями.

Если вы посмотрите на ваш файл package.json, то увидите, что Storybook уже добавил скрипт storybook и storybook-build. Первый будет размещать приложение Storybook локально для быстрого и легкого тестирования. Второй создаст статический HTML/JS-пакет, который можно легко разместить на удаленном сервере, чтобы все члены вашей команды могли попробовать ваши компоненты.

Пока что давайте просто выполним:

npm run storybook

Примечание: Возможно, вы столкнетесь с ошибками из-за отсутствия зависимостей. В этом случае есть несколько решений.

Первое - установить эти зависимости вручную. Например, react-dom. Это не идеальный вариант, поскольку ваш проект сам по себе не должен зависеть от этих библиотек, поэтому нет необходимости включать их, поскольку они включены в состав зависимостей Storybook, как пример здесь.

Если вы просто выполните свежую команду npm install, она установит все peerDependencies используемых вами библиотек. Перед выполнением этой команды вам может понадобиться удалить директории package-lock.json и node_modules. Они будут восстановлены автоматически после новой установки.

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


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

Есть еще много интересного о Storybook, обязательно прочитайте документацию.

Добавление SCSS

Благодаря rollup-plugin-postcss вы уже должны быть в состоянии просто переименовать ваш файл .css в .scss, а затем import 'Button.scss' и так далее. Запуск num run rollup скомпилирует все это просто отлично с текущей конфигурацией.

Однако запустить его в Storybook - это совсем другое дело. Обратите внимание, что именно по этой причине мы использовали флаг --builder webpack5 при установке в предыдущем разделе, вы, скорее всего, столкнетесь с большим количеством ошибок, пытаясь настроить Storybook на поддержку SCSS с webpack 4. В версии 5 это довольно просто, используя преднастройку SCSS.

(Если вы следовали предыдущей версии этого руководства, вы могли инициализировать Storybook с webpack 4 по умолчанию. Вы можете удалить все, что связано со Storybook, из вашего файла package.json. Затем удалите package-lock.json и директорию /node_modules/ и снова инициализируйте Storybook с флагом --builder webpack5).

npm install @storybook/preset-scss css-loader sass sass-loader style-loader --save-dev

Чтобы прочитать больше о различных видах поддержки CSS и Storybook, нажмите здесь.

(Если вы хотите больше узнать о разнице между этими разными загрузчиками, вот отличный ответ на Stack Overflow).

Затем все, что вам нужно сделать, это добавить @storybook/preset-scss в ваш основной конфиг Storybook:

.storybook/main.js

module.exports = {
  "stories": [
    "../src/**/*.stories.mdx",
    "../src/**/*.stories.@(js|jsx|ts|tsx)"
  ],
  "addons": [
    "@storybook/addon-links",
    "@storybook/addon-essentials",
    "@storybook/preset-scss"
  ],
  "core": {
    "builder": "webpack5"
  }
}

Теперь вы сможете запустить npm run storybook и увидеть все ваши стили SCSS.

(Последнее напоминание о том, что Storybook часто сталкивается с ошибками зависимостей. Прежде чем приступить к установке недостающих зависимостей, всегда пробуйте сначала удалить package-lock.json и node_modules, а затем снова запустить npm install. Это часто помогает решить проблему, не требуя добавления ненужных зависимостей в ваш проект.).

Подведение итогов

Теперь у вас должно быть хорошее понимание того, как создать свою собственную библиотеку компонентов React. Это может не только научить вас многому о том, как работает экосистема управления пакетами Javascript, но и стать отличным способом сделать код, который вы используете в нескольких проектах, легко доступным с помощью простой команды.

Ознакомьтесь с другими моими учебными пособиями. Не стесняйтесь оставлять комментарии или вопросы и делиться с другими, если вы нашли что-то из всего этого полезным:


Чтобы получить больше руководств, подобных этому, следуйте за мной @eagleson_alex в Twitter.

От переводчика: что касается SCSS, то нужно не забыть соответствующим образом упомянуть scss в external: [/\.(scss|css)$/] в rollup.config.js, иначе при сборке будет ошибка вида [!] Error: Could not resolve './blablabla.scss' from dist/esm/types/blabla/blablabla.d.ts

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


  1. Dreamte
    11.04.2022 20:57
    +1

    Спасибо за информацию, подчерпнул новенькое для себя)