Создание библиотеки компонентов React с помощью Storybook 6
Если у вас есть несколько проектов, которые используют одну и ту же систему дизайна (input, button, другие повторно используемые компоненты и т. д.), То у вас, вероятно, есть достаточно хороший вариант использования для создания общей библиотеки компонентов, которую можно публиковать и использовать напрямую всеми вашими проекты.
Еще одно преимущество заключается в том, что вы можете легко разрабатывать компоненты пользовательского интерфейса изолированно и напрямую отображать их различные состояния, без необходимости вмешиваться в бизнес-логику в своем стеке разработки с помощью Storybook.
В этом руководстве я расскажу о шагах по созданию и публикации библиотеки компонентов React (Storybook поддерживает бесчисленное множество других интерфейсных фреймворков) со следующими шагами:
1. Настройка проекта
2. Установка Storybook
3. Добавление историй и настройка файловой структуры
4. Компиляция библиотеки с используя Rollup
5. Публикация и использование библиотеки
Настройка проекта
Поскольку мы создаем библиотеку компонентов, которая будет опубликована в диспетчере пакетов, таком как NPM, было бы лучше, если бы мы настроили React с нуля, вместо того, чтобы использовать что-то вроде create-react-app, которое лучше подходит для веб-приложений.
Если у вас есть библиотека компонентов, использующая уже настроенную React, вы можете сразу перейти к следующему шагу. Нам просто нужна базовая настройка React, прежде чем мы сможем установить Storybook.
Для этого создайте новую папку с любым именем для вашей библиотеки компонентов. Я бы назвал свой my-awesome-component-library.
Затем запустите yarn init и git init соответственно в этой папке, указав соответствующие значения для запрошенных полей. Это инициализирует пустой проект NPM с помощью git. Также создайте файл gitignore.
Мы создаем библиотеку компонентов React, поэтому нам понадобится React для сборки наших компонентов. Также мы собираемся использовать TypeScript для создания нашей библиотеки. Давайте добавим и это.
yarn add --dev react react-dom @types/react typescript
Поскольку для react требуется, чтобы у нас была одна копия react-dom, мы добавим ее как peerDependency, чтобы наш пакет всегда использовал версию устанавливаемого клиента. Добавьте следующий фрагмент в свой package.json.
... "peerDependencies": { "react": "^16.8.0", "react-dom": "^16.8.0" }, ...
В качестве последнего шага для настройки проекта давайте также добавим tsconfig для компиляции нашего TypeScript. Создайте в корне файл с именем tsconfig.json и добавьте к нему следующее.
{ "compilerOptions": { "target": "es5", "outDir": "lib", "lib": [ "dom", "dom.iterable", "esnext" ], "declaration": true, "declarationDir": "lib", "allowJs": true, "skipLibCheck": true, "esModuleInterop": true, "allowSyntheticDefaultImports": true, "strict": true, "forceConsistentCasingInFileNames": true, "module": "esnext", "moduleResolution": "node", "resolveJsonModule": true, "isolatedModules": true, "noEmit": true, "jsx": "react" }, "include": [ "src" ], "exclude": [ "node_modules", "lib" ] }
Эти параметры помогают TypeScript игнорировать и применять определенные правила при компиляции нашего кода. Вы можете проверить все флаги, доступные в документации.
GitHub: код до этого шага
Установка Storybook
Теперь, когда у нас есть готовый шаблон React, мы можем установить Storybook, запустить следующую команду в корневой папке, чтобы добавить Storybook в свой проект.
npx sb init
Эта команда установит все основные devDependencies, добавит скрипты, настроит некоторые файлы конфигурации и создаст примеры историй, которые помогут вам начать работу с Storybook. На момент написания этой статьи я использовал Storybook версии 6.1.9.
Теперь вы можете запустить yarn storybook, и он должен загрузить для вас Storybook с примерами, которые они создали для вас.
Когда вы закончите играть, вы можете удалить папку историй с примерами.
Теперь откройте файл .storybook/main.js. Этот файл управляет поведением вашего сервера Storybook, задавая конфигурацию ваших историй.
Обновите ключ историй в файле до этого -
... "stories": [ "../src/**/*.stories.tsx" ], ...
Эта конфигурация будет запускать истории TypeScript, определенные в папке src, которую мы будем создавать на следующем шаге.
GitHub: код до этого шага
Добавление историй и настройка файловой структуры
Теперь, когда у нас есть настройка Storybook, мы можем начать создавать наши компоненты и писать для них истории.
Но прежде всего, что такое истории?
Рад, что вы спросили, из документации -
«История отражает состояние визуализации компонента пользовательского интерфейса. Разработчики пишут несколько историй для каждого компонента, которые описывают все« интересные »состояния, которые компонент может поддерживать».
Короче говоря, Stories позволяют отображать различные состояния вашего компонента пользовательского интерфейса и позволяют вам поиграть с разными состояниями с помощью чего-то, что называется Storybook Controls, к которому мы вернемся через минуту. Это файлы, предназначенные только для разработки, поэтому они не будут включены в окончательный комплект библиотеки.
Давайте создадим демонстрационный компонент, чтобы проверить, как работают истории и как вы можете извлечь из них максимальную пользу.
Наша файловая структура будет выглядеть примерно так -
.storybook/ main.js preview.js .gitignore package.json rollup.config.js tsconfig.json src/ components/ MyAwesomeComponent/ MyAwesomeComponent.tsx MyAwesomeComponent.css MyAwesomeComponent.stories.tsx index.ts index.ts
Мы будем использовать тот же компонент кнопки, который Storybook предоставил нам в демонстрации ранее для демонстрации.
Создайте папку src / components / Button и вставьте в нее файлы Button.tsx, button.css и index.ts.
Добавим истории ✨
Создайте src / components / Button / Button.stories.tsx
Теперь добавьте к нему следующий экспорт по умолчанию -
import React from "react"; import { Meta } from "@storybook/react/types-6-0"; import Button, { ButtonProps } from "./Button"; export default { title: "Components/Button", component: Button, } as Meta;
Экспорт по умолчанию в истории определяет метаинформацию, которая будет использоваться Storybook и его надстройками.
Чтобы определить историю, вам нужно создать именованный экспорт в файле, поэтому, например, мы можем создать историю для основного типа кнопки, подобного этой.
export const PrimaryButton = () => <Button label="Hello world" primary /> ;
Чтобы упростить написание нескольких историй, Storybook предоставляет возможность создавать истории, определяя главный шаблон и повторно используя этот шаблон для каждой истории. Итак, в нашем случае истории для кнопок основного и дополнительного типов могут быть созданы следующим образом:
import React from "react"; import { Meta } from "@storybook/react/types-6-0"; import { Story } from "@storybook/react"; import { Button, ButtonProps } from "./Button"; export default { title: "Components/Button", component: Button, } as Meta; // Создайте главный шаблон для сопоставления аргументов для рендеринга компонента Button const Template: Story <ButtonProps> = (args) => <Button { ...args } />; // Повторно используйте этот шаблон для создания разных историй export const Primary = Template.bind({}); Primary.args = { label: "Primary 😃", size: "large" }; export const Secondary = Template.bind({}); Secondary.args = { ...Primary.args, primary: false, label: "Secondary 😇" };
Если вы еще этого не сделали, вы можете перезапустить сервер Storybook, повторно запустив yarn Storybook, и вы должны увидеть следующее.
Обратите внимание, что Storybook автоматически сгенерировал для нас элементы управления в соответствии с реквизитами компонентов. Это благодаря react-docgen-typescript, который используется Storybook для вывода argTypes для компонента. Еще одна причина использовать TypeScript.
Помимо использования автоматически сгенерированных элементов управления, вы также можете определить пользовательские элементы управления для некоторых или всех свойств с помощью ключа argTypes. Например, давайте определим настраиваемый выбор цвета для свойства backgroundColor, заменим экспорт по умолчанию в файле историй на это -
export default { title: "Components/Button", component: Button, argTypes: { backgroundColor: { control: 'color' }, }, } as Meta;
Текущий предварительный просмотр истории также выглядит немного странно с кнопкой в одном углу предварительного просмотра. В качестве последнего шага добавьте ключ layout: 'centered' в файл .storybook / preview.js, чтобы центрировать предварительный просмотр. Этот файл позволяет вам контролировать, как ваша история будет отображаться в Storybook.
Если вы выполните вышеуказанные шаги, ваш окончательный предварительный просмотр истории будет выглядеть примерно так:
Компиляция библиотеки с помощью Rollup
Теперь, когда вы знаете, как создавать компоненты с помощью Storybook, пора перейти к следующему шагу - компиляции нашей библиотеки, чтобы наши конечные приложения могли ее использовать.
Если вы не знакомы с Rollup и задаетесь вопросом, почему мы используем его для компиляции нашей библиотеки вместо чего-то вроде webpack, это потому, что Rollup лучше всего подходит для объединения библиотек, тогда как webpack подходит для приложений.
Во-первых, нам нужно создать файл записи, который будет экспортировать все компоненты для нашей библиотеки компонентов. Создайте src / index.ts, и поскольку в нашей библиотеке компонентов сейчас только один компонент, он будет выглядеть примерно так:
import Button from "./components/Button"; export { Button };
Давайте добавим rollup, запустите следующее, чтобы установить накопительный пакет и его плагины, которые мы будем использовать для объединения библиотеки:
yarn add --dev rollup rollup-plugin-typescript2 @rollup/plugin-commonjs @rollup/plugin-node-resolve rollup-plugin-peer-deps-external rollup-plugin-postcss postcss
Теперь, прежде чем мы добавим конфигурацию rollup, есть несколько типов модулей JavaScript, о которых вам следует знать:
CommonJS - этот формат модуля чаще всего используется с Node с использованием функции require. Несмотря на то, что мы публикуем модуль React (который будет использоваться приложением, обычно написанным в формате ESM, а затем объединенным и скомпилированным такими инструментами, как webpack), мы должны учитывать, что он также может использоваться в среде рендеринга на стороне сервера, которая обычно использует Node и, следовательно, может потребовать аналог библиотеки CJS (модули ESM поддерживаются в среде Node начиная с версии 10 за экспериментальным флагом).
ESM - это современный формат модулей, который мы обычно используем в наших приложениях React, в котором модули определяются с помощью различных операторов импорта и экспорта. Основное преимущество доставки модулей ES заключается в том, что это делает вашу библиотеку древовидной. Это поддерживается такими инструментами, как Rollup и webpack 2+.
UMD - этот формат модуля не так популярен в наши дни. Это необходимо, когда пользователю требуется наш модуль с помощью тега скрипта.
Поэтому мы хотели бы поддерживать модули как ESM, так и CommonJS для нашей библиотеки компонентов, чтобы все виды инструментов поддержки могли использовать ее в конечном приложении, которое полагается на любой из типов модулей.
Для этого package.json позволяет добавлять точки входа как для модулей ESM, так и для CommonJS через модуль и основной ключ соответственно. Поэтому добавьте в ключи к вашему package.json следующее:
{ ... "main": "lib/index.js", "module": "lib/index.esm.js", "types": "lib/index.d.ts", ... }
Ключ типов будет указывать на статические типы, созданные для вашей библиотеки с помощью Rollup, что поможет с IntelliSense в редакторах кода, таких как VSCode.
Пришло время добавить файл конфигурации Rollup, создать файл с именем rollup.config.js в корневой папке и добавить к нему следующее:
import peerDepsExternal from "rollup-plugin-peer-deps-external"; import resolve from "@rollup/plugin-node-resolve"; import commonjs from "@rollup/plugin-commonjs"; import typescript from "rollup-plugin-typescript2"; 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: [ peerDepsExternal(), resolve(), commonjs(), typescript({ useTsconfigDeclarationDir: true }), postcss({ extensions: ['.css'] }) ] };
Давайте разберемся по порядку, чтобы понять, что здесь происходит.
Начнем с того, что клавиша ввода указывает input key для Rollup для нашей библиотеки компонентов, которым является только что созданный файл index.js, который содержит экспорты для всех наших компонентов.
Ключ вывода указывает, какие типы файлов вывода будут созданы в каком месте. Как упоминалось ранее, мы будем собирать пакеты ESM и CommonJS, и мы читаем выходные файлы для обоих пакетов из package.json.
Наконец, есть массив плагинов, с которым мы используем следующие плагины:
- rollup-plugin-peer-deps-external - этот плагин не позволяет нам связывать peerDependencies (в нашем случае response и react-dom) в окончательном пакете, поскольку они будут предоставлены нашим потребительским приложением.
- @rollup/plugin-node-resolve - Этот плагин включает сторонние внешние зависимости в наш окончательный пакет (у нас нет никаких зависимостей для этого руководства, но они вам обязательно понадобятся по мере роста вашей библиотеки).
- @rollup/plugin-commonjs - этот плагин позволяет преобразовать в CJS, чтобы их можно было включить в окончательный пакет.
- rollup-plugin-typescript2 - этот плагин компилирует код TypeScript в JavaScript для нашего окончательного пакета и генерирует объявления types для ключей типов в package.json. Параметр useTsconfigDeclarationDir выводит типы в каталог, указанный в файле tsconfig.json.
- rollup-plugin-postcss - этот плагин помогает включить CSS, который мы создали в виде отдельных файлов, в наш окончательный пакет. Для этого он генерирует минимизированный CSS из файлов * .css и включает их через тег <head> везде, где он используется в наших компонентах.
Теперь, в качестве последнего шага, давайте добавим скрипт для создания нашей библиотеки компонентов, добавим следующий скрипт в ваш файл package.json -
{ ... "scripts": { ... "build": "rollup -c" }, ... }
Запустите yarn build со своего терминала, и вы увидите созданную папку lib. Я бы рекомендовал изучить эту папку дальше, чтобы понять, как Rollup и его плагины генерируют соответствующие пакеты для модулей CommonJS и ESM с определениями типов.
Не забудьте добавить папку lib в .gitignore.
Публикация и использование библиотеки
Публикация библиотеки в NPM не может быть проще. Поскольку мы уже определили все обязательные поля в package.json, вам просто нужно запустить npm publish.
После публикации вы сможете импортировать свой компонент из своей библиотеки в потребительское приложение следующим образом:
import { Button } from "my-awesome-component-library";
Вы также можете сохранить свою библиотеку частной. Если у вас есть несколько проектов в монорепозитории и вы используете что-то вроде рабочих yarn workspaces, то вам на самом деле не нужно публиковать пакет где-либо.
Поместите папку библиотеки в свое монорепозиторий и добавьте ее в массив рабочих пространств в package.json в корневой папке -
// package.json { ... "workspaces": [ ... "my-awesome-component-library" ], ... }
Затем вы можете напрямую получить к нему доступ из любого другого пакета в своей рабочей области, просто добавив его как зависимость:
// my-awesome-frontend/package.json { ... "dependencies": { ... "my-awesome-component-library": 1.0.0, ... }, ... }
Следующие шаги
- Интегрируйте Netlify или какой-либо другой сервис для автоматического развертывания Storybook всякий раз, когда PR объединяется с master, и для создания предварительных просмотров при каждом открытии нового PR.
- Настройте тестовые случаи с помощью библиотеки React Testing и Jest.
- Добавьте разделение кода, чтобы пользовательское приложение могло импортировать только необходимые компоненты, а не всю библиотеку.