Введение
Прекрасная вещь в react — это то, что он позволяет вам разбивать пользовательский интерфейс на более мелкие компоненты и повторно использовать их в разных местах вашего проекта, но это еще не все, вы можете стандартизировать ваши компоненты и опубликовать их на npm или yarn и позволить другим людям использовать ваши компоненты в своих проектах.
В этом посте я покажу вам, как можно создать библиотеку компонентов для reactJS, используя Typescript, Storybook, SCSS и Rollup.
Не обязательно создавать большую библиотеку, вы можете создать библиотеку для одного компонента, например, Date Picker, или создать полноценную библиотеку, например, Material UI.
Библиотека компонентов пользовательского интерфейса очень важна для организаций, чтобы поддерживать согласованный пользовательский интерфейс в разных проектах. Однако вам не нужно создавать библиотеку компонентов только для одного проекта, поскольку все компоненты можно поддерживать в этом конкретном проекте, но если вы работаете над несколькими побочными проектами и у вас есть похожие компоненты, которые вы копируете-вставляете из одного проекта в другой, то лучше создать библиотеку для этих компонентов.
Итак, давайте приступим к созданию библиотеки
Настройка библиотеки
Начало работы
Перед началом этого проекта убедитесь, что в вашей системе глобально установлены node js и npm. Если нет, вы можете скачать node отсюда
Создайте папку и назовите ее как угодно, но для данной статьи назовем эту папку react-lib, а если вы планируете опубликовать эту библиотеку на npm, то проверьте, доступно ли имя пакета в реестре npm.
Откройте Терминал, перейдите в каталог проекта и выполните команду
npm init -y
Это инициализирует проект node в текущем каталоге. Флаг -y
при передаче команде NPM указывает генератору использовать значения по умолчанию вместо того, чтобы задавать вопросы, и файл package.json
создается в корне вашей директории
React & React DOM
Давайте установим react
и react-dom
в качестве dev-зависимостей.
npm i -D react react-dom
Мы создаем эту библиотеку для reactJS, поэтому требуется, чтобы проект, использующий эту библиотеку, имел react
и react-dom
в качестве зависимостей, поэтому давайте добавим react
и react-dom
в качестве зависимостей peer в наш файл package.json
.
Основная структура папок
Прежде чем добавить storybook и typescript и двигаться дальше, давайте создадим базовую структуру папок для нашего проекта. Вы можете использовать любую структуру папок, которая вам больше нравится.
Установка typescript
Сначала установите typescript глобально в вашей системе с помощью следующей команды
npm i typescript -g
Затем установите typescript
и @types/react
в качестве dev-зависимости в ваш проект.
npm i -D typescript @types/react
Затем создайте файл tsconfig.json
с помощью следующей команды.
tsc --init
Этот файл позволит вам настроить дальнейшую конфигурацию и настроить взаимодействие Typescript и компилятора tsc
.
Откройте tsconfig.json
и измените следующую конфигурацию.
...
"compilerOptions" : {
...
"jsx": "react",
...
"module": "es6",
...
"moduleResolution": "node",
....
"outDir": "./dist",
....
}
....
Получение Storybook
Storybook — это инструмент с открытым исходным кодом для создания изолированных компонентов пользовательского интерфейса и страниц. Он упрощает разработку пользовательского интерфейса, тестирование и документирование. Он работает с библиотекой javascript, такой как React, Vue, Angular и т.д.
Чтобы установить storybook в нашу библиотеку, выполните эту команду:
npx sb init
Теперь вы должны иметь возможность запустить Storybook локально, выполнив команду npm run storybook
или, если хотите, yarn storybook
.
Вот предварительный просмотр приложения Storybook:
Разбираемся в структуре папок
npx sb init
генерирует некоторые файлы и папки, давайте посмотрим на них.
Папка stories
.
Storybook достаточно умен и определяет, что проект использует Typescript и React, поэтому он создает некоторые примеры компонентов и страницы документации внутри папки stories
в src
. Вы можете удалить все из этой папки, но я рекомендую заглянуть внутрь, чтобы немного разобраться.
-
Introduction.stories.mdx
содержит документацию, используемую для создания страницы введения в приведенном выше превью сборника рассказов. Файлы с расширением.mdx
написаны с использованием MDX, который является слиянием JSX и Markdown. Это помогает писать истории компонентов вместе с их документацией в одном месте. -
Все файлы типа
<ComponentName>.tsx
являются компонентами react, созданными с помощью typescript, а файлы типа<ComponentName>.stories.tsx
используются для предварительного просмотра историй в сборнике историй и помогают нам разрабатывать компонент в изоляции.
Папка .storybook
.
Содержит файлы для настройки Storybook:
"stories": [
"../src/**/*.stories.mdx",
"../src/**/*.stories.@(js|jsx|ts|tsx)"
]
Аддоны для приложения Storybook также определяются в main.js.
.
"addons": [
"@storybook/addon-links",
"@storybook/addon-essentials"
]
export const parameters = {
actions: { argTypesRegex: "^on[A-Z].*" },
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/,
},
},
}
Файл package.json
Команда npx sb init
добавляет все dev-зависимости, необходимые сборнику рассказов, а также добавляет следующие скрипты в файл package.json
"scripts": {
"storybook": "start-storybook -p 6006",
"build-storybook": "build-storybook"
}
Добавление поддержки SASS
Storybook использует webpack для пакетирования всего кода, когда мы запускаем скрипт storybook
или build-storybook
.
По умолчанию Storybook не поставляется с поддержкой sass. Чтобы добавить sass в нашу библиотеку, нам нужно добавить некоторые пакеты и расширить конфигурацию webpack в .stoybook/main.js
.
Добавьте следующие зависимости dev для добавления поддержки sass
npm i -D sass style-loader css-loader sass-loader@10.2.0
Давайте разберемся в этих зависимостях
Для расширения конфигурации webpack добавим следующий код в .storybook/main.js
const path = require("path");
.....
addons: ["@storybook/addon-links", "@storybook/addon-essentials"],
framework: "@storybook/react",
webpackFinal: async (config, { configType }) => {
// Make whatever fine-grained changes you need
config.module.rules.push({
test: /.scss$/,
use: ["style-loader", "css-loader", "sass-loader"],
include: path.resolve(__dirname, "../"),
});
// Return the altered config
return config;
}
....
Добавив поддержку Sass, мы готовы к созданию наших компонентов.
Создание нашего первого компонента
Создание компонента кнопки
Вы можете создать любой компонент, который вам необходим, но для данного поста давайте создадим компонент Button, который можно будет использовать повторно.
Внутри папки components создайте файл Button.tsx
.
Сначала мы определим интерфейс реквизитов, которые необходимы кнопке.
Мы начнем с импорта react внутри нашего файла, а наш реквизит кнопки расширяет элемент кнопки HTML, поскольку мы можем передавать различные реквизиты, такие как onClick
или type
, которые являются родными для элемента кнопки HTML.
import React from "react";
export interface ButtonProps extends React.HTMLAttributes<HTMLButtonElement> {
children: React.ReactNode;
variant: "primary" | "danger";
shape?: "rounded";
}
Мы получаем следующие реквизиты
- у нас также есть необязательный реквизит
shape
.
Теперь давайте добавим наш компонент
export const Button: React.FC<ButtonProps> = ({
children,
variant,
shape,
...props
}) => {
const classNames = `btn btn-${variant} btn-${shape}`;
return (
<button className={classNames} {...props}>
{children}
</button>
);
};
Здесь мы возвращаем HTML элемент кнопки из нашей функции, и мы будем использовать реквизиты variant
и shape
для создания различных имен классов и добавим их в наш элемент <button>
, и мы можем нацелить эти определенные классы из нашего SCSS для придания различных стилей нашему компоненту.
Например, btn
является базовым классом, а когда мы передаем variant
, то у нас будет класс либо btn-primary
, либо btn-danger
в зависимости от переданного варианта, и мы можем добавить стили для различных вариантов.
Добавление стилей для компонента Button
Создайте файл global.scss
в папке scss
. Вы можете выбрать структуру папок по своему усмотрению и поместить SCSS код в разные файлы и импортировать их в этот файл global.scss
, но для простоты давайте добавим все наши стили в файл global.scss
.
/* base styling for our button */
.btn {
padding: 0.6rem 1rem;
background: transparent;
border: 1px solid #1e1e1e;
cursor: pointer;
}
/* styling for our variants */
.btn-primary {
border: none;
background-color: blue;
color: white;
&:hover {
background-color: blue;
}
}
.btn-danger {
border: none;
background-color: red;
color: white;
&:hover {
background-color: red;
}
}
/* styling for different shape*/
.btn-rounded {
border-radius: 0.4rem;
}
Либо у кнопки будет класс btn-primary
, либо btn-danger
вместе с другими классами, и соответствующие стили вступят в силу.
Теперь мы создали нашу кнопку и добавили некоторые стили, давайте воспользуемся этой кнопкой и посмотрим, как она выглядит в нашей книге историй.
Создание истории кнопки в storybook
Прежде чем создавать историю для нашей кнопки, давайте импортируем наш SCSS, чтобы его можно было использовать. Откройте файл preview.js
в папке .storybook
и импортируйте в него файл global.scss
.
Импортировав стили в сборник рассказов, давайте создадим рассказ о кнопке.
Откройте папку stories
и удалите все файлы, которые были автоматически сгенерированы сборником историй для нас внутри этой папки, создайте файл Button.stories.tsx
и напишите следующий код
import React from "react";
import { Story } from "@storybook/react";
import { Button, ButtonProps } from "../components/Button";
export default {
title: "Button",
component: Button,
};
const Template: Story<ButtonProps> = args => <Button {...args} />;
export const Primary = Template.bind({});
Primary.args = {
children: "Primary",
variant: "primary",
};
export const Danger = Template.bind({});
Danger.args = {
children: "Danger",
variant: "danger",
shape: "rounded",
};
Давайте разберемся в этом коде
Мы импортируем наши Button
и ButtonProps
из файла Button.stories.tsx
и начинаем с экспорта объекта по умолчанию, который содержит некоторые метаданные об этой истории, такие как title
и component
. title
— это фактическое название этой истории, а внутри компонента у нас есть компонент Button
.
Затем мы создаем функцию Template, которая принимает некоторые аргументы и возвращает наш компонент кнопки.
const Template: Story<ButtonProps> = args => <Button {...args} />;
Теперь вызовите Template.bind({})
для создания клона нашего шаблона кнопки. Этот Template.bind({})
вернет функцию, которую мы можем сохранить в переменной и экспортировать ее. Вы можете прочитать больше о .bind()
здесь
export const Primary = Template.bind({});
Теперь зададим некоторые args для нашей основной кнопки
Primary.args = {
children: "Primary",
variant: "primary",
};
Теперь выполните команду npm run storybook
для запуска сервера разработки, и вы увидите компоненты кнопки
В dev-сервере storybook у нас есть секция управления, которую можно использовать для изменения реквизитов и немедленно увидеть изменения, или мы можем написать больше версий или вариантов нашей кнопки, используя Template.bind({})
.
Таким образом, мы можем разрабатывать и тестировать несколько компонентов изолированно, используя storybook.
Комплектация с помощью Rollup
Rollup — это хороший инструмент пакетирования, если мы хотим упаковать библиотеку компонентов React и повторно использовать ее в других проектах.
Rollup нужна точка входа для создания пакета. Мы уже создали файл index.ts
в папке src
, который будет служить нашей точкой входа для Rollup.
Добавьте в этот файл index.ts
экспорт компонентов, которые будут использоваться другими, а также импортируйте сюда файл global.scss
, чтобы мы могли создать связку CSS.
Для того чтобы собрать нашу библиотеку, добавим следующие зависимости dev.
npm i -D rollup @rollup/plugin-babel rollup-plugin-peer-deps-external rollup-plugin-scss rollup-plugin-terser @babel/preset-react @rollup/plugin-node-resolve @rollup/plugin-typescript
Давайте разберемся в этих зависимостях:
Далее создадим файл rollup.config.js
и добавим в него следующее содержимое.
import { babel } from "@rollup/plugin-babel";
import external from "rollup-plugin-peer-deps-external";
import resolve from "@rollup/plugin-node-resolve";
import scss from "rollup-plugin-scss";
import typescript from "@rollup/plugin-typescript";
import { terser } from "rollup-plugin-terser";
export default [
{
input: "./src/index.ts",
output: [
{
file: "dist/index.js",
format: "cjs",
},
{
file: "dist/index.es.js",
format: "es",
exports: "named",
},
],
plugins: [
scss({
output: true,
failOnError: true,
outputStyle: "compressed",
}),
babel({
exclude: "node_modules/**",
presets: ["@babel/preset-react"],
}),
external(),
resolve(),
typescript(),
terser(),
],
},
];
Далее нам нужно обновить package.json. Библиотеки должны распространяться с использованием CommonJS и ES6. Мы указываем пути к выходным файлам с помощью свойств main
и module
. Мы также используем эти свойства в конфигурационном файле Rollup.
Затем мы добавляем скрипт build
, который использует rollup CLI с флагом -c
. Это означает, что Rollup будет искать конфигурационный файл с именем rollup.config.js, чтобы связать библиотеку компонентов.
...
"main": "dist/index.js",
"module": "dist/index.es.js",
...
"scripts": {
...
"build": "rollup -c",
}
...
Теперь, если вы выполните команду npm run build
, она соберет нашу библиотеку и создаст папку dist в корне каталога вашего проекта, которую можно будет развернуть с помощью npm.
Заключительные слова
Мы создали библиотеку компонентов react с нуля, используя typescript. Мы установили storybook для разработки компонентов пользовательского интерфейса в изоляции, настроили его для добавления поддержки SASS и, наконец, собрали все вместе с помощью Rollup.
Я решил написать эту статью после того, как создал библиотеку UI для себя в качестве побочного проекта, если вам интересно, вы можете ознакомиться с ней здесь.
Я надеюсь, что вы нашли это интересным и чему-то научились. Спасибо.
Per Aspera Ad Astra