Как сделать свой UI Kit на Vue 3 + storybook и задеплоить его на npm

от автора

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

А зачем он вообще нужен?

Зачем?

Зачем?
  1. Это удобно и быстро, все компоненты лежат в одном месте.

  2. Несколько команд могут удобно его использовать, не прибегая к технике ctrl + c -> ctrl + v.

  3. Новые изменения сразу актуализируются во всех проектах.

  4. Можно прикрутить Storybook и показать менеджерам, как вы умеете xD.

  5. Это некий бренд, сделав крутой UI Kit и сделав под него базовую доку, ты можешь говорить о том, что у нас есть некоторые open source решения.

Мы поняли зачем — приступаем

Тут должна была быть Наталья Морская Пехота

Тут должна была быть Наталья Морская Пехота

Начнем с базы

С помощью vite заинитим проект на vue 3 с typescript — тык

npm create vite@latest ui-kit -- --template vue-ts

Проект есть, теперь чуть-чуть поколдуем с нашим package.json, сделаем базовые манипуляции, для того чтобы это было больше похоже на UI Kit.

Добавим vue в devDependencies и добавим peerDependecies с зависимостью vue >= 3. Делаем это для того, чтобы получить наименьшее количество кода на выходе, а значит лучшую производительность.

"peerDependencies": {     "vue": ">=3" }

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

Укажем экспорты для файлов нашей библиотеки:

Указываем что файлы нашей библиотеки будут в папке dist:

"files": [   "dist" ],

Настроим экспорты стилей и js бандлов для import/require(что за имена такие модные, вы поймете в следующем пункте, не переживайте).

"exports": {   ".": {     "import": "./dist/dragonekui.es.ts",     "require": "./dist/dragonekui.umd.ts"   },   "./styles": "./dist/style.css" },

Получим примерно вот такой package.json

package.json
{   "name": "dragonek-ui",   "version": "0.0.3",   "type": "module",   "main": "./dist/dragonekui.umd.ts",   "module": "./dist/dragonekui.es.ts",   "types": "./dist/components/index.d.ts",   "files": [     "dist"   ],   "exports": {     ".": {       "import": "./dist/dragonekui.es.ts",       "require": "./dist/dragonekui.umd.ts"     },     "./styles": "./dist/style.css"   },   "scripts": {     "dev": "vite",     "build": "vue-tsc && vite build",   },   "devDependencies": {     "@types/node": "^20.6.0",     "@vitejs/plugin-vue": "^4.2.3",     "typescript": "^5.0.2",     "vite": "^4.4.5",     "vue": "^3.3.4",     "vue-tsc": "^1.8.5"   },   "peerDependencies": {     "vue": ">=3"   } } 

Настроим vite

Далее нам нужно настроить vite, чтобы он правильно собирал наш проект и все было красиво!

В vite.config.ts, добавим options.lib, для того чтобы собирать проект как библиотеку, там укажем название нашей библиотеки, entry point — корневой файл для сборки либы, и filename на выходе(имя, которое будет использовать в папке dist при сборке проекта), по стандарту собирается два файлы библиотеки в формате es и umd их можно поменять в зависимости от ваших потребностей, с помощью свойства formats.

Почитать про форматы можно вот тут — тык

lib: {   entry: resolve(__dirname, 'src/components/index.ts'),   name: 'dragonekui',   fileName: (format) => `dragonekui.${format}.ts`, },

Я сделал entry point файл index.ts в компонентах, туда я буду импортировать все мои компоненты и в дальнейшем их экспортировать.

Далее в этом же файле нужно поднастроить rollup. Указываем в опции external — vue, показывая, что пакет является внешней зависимостью и прокидываем ее определение в том проекте, где мы будем использовать наш пакет, обычно там инстанс vue есть Vue.

rollupOptions: {   external: ['vue'],   output: {     globals: {       vue: 'Vue',     },   }, },

Также нужно добавить генерацию типов для нашего пакета, это мы сделаем с помощью специального плагина для vite — vite-plugin-dts.

npm i -D vite-plugin-dts
plugins: [   dts({     insertTypesEntry: true,   }), ],

Получим примерно вот такой vite.config.ts

vite.config.ts
import vue from '@vitejs/plugin-vue'; import { defineConfig } from 'vite'; import { resolve } from 'path'; import dts from 'vite-plugin-dts';  export default defineConfig({   build: {     lib: {       entry: resolve(__dirname, 'src/components/index.ts'),       name: 'dragonekui',       fileName: (format) => `dragonekui.${format}.ts`,     },     rollupOptions: {       external: ['vue'],       output: {         globals: {           vue: 'Vue',         },       },     },   },   plugins: [     vue(),     dts({       insertTypesEntry: true,     }),   ],   resolve: {     alias: {       '@': resolve(__dirname, 'src'),     },   }, }); 

Storybook

Устанавливаем storybook по их официальному гайду — тык

npx sb init

Получаем установленный storybook в проект и папочку с examples, ее можно удалить, она нам не понадобиться.

Обязательно в tsconfig.json, добавляем .stories.ts формат в exclude, чтобы нам не генерились типы для этих файлов:

"exclude": [   "src/**/*.stories.ts" ],

Приступим к написанию кода

Кот пишет код

Кот пишет код

Я покажу пример на кнопке.

Сделаем простенький компонент кнопки:

src/components/button/Button.vue

Button.vue
<script lang="ts" setup> import { defineProps, PropType } from 'vue';  const props = defineProps({   disabled: {     type: Boolean,     default: false,   },   size: {     type: String as PropType<'small' | 'medium' | 'large'>,     default: 'medium',   },   color: {     type: String as PropType<'primary' | 'secondary'>,     default: 'primary',   }, }); </script>  <template>   <button class="button" :class="[props.size, props.color]" :disabled="props.disabled">     <slot name="left-icon" />     <slot />     <slot name="right-icon" />   </button> </template>  <style scoped> .button {   color: #ffffff;   outline: none;   border: none;   padding: 0 16px; }  .small {   height: 32px;   border-radius: 8px; }  .medium {   height: 36px;   border-radius: 10px; }  .large {   height: 40px;   border-radius: 12px; }  .primary {   background: #535bf2; }  .secondary {   background: #a7a7a7; } </style> 

Далее напишем story для этой кнопки:

src/components/button/Button.stories.ts

Button.stories.ts
import { StoryFn, Meta } from '@storybook/vue3'; import Button from './Button.vue';  export default {   title: 'Button',   component: Button,   argTypes: {     disabled: {       control: { type: 'boolean' },       defaultValue: false,     },     size: {       control: { type: 'radio' },       options: ['small', 'medium', 'large'],       defaultValue: 'medium',     },     color: {       control: { type: 'select' },       options: ['primary', 'secondary'],       defaultValue: 'primary',     },   }, } as Meta<typeof Button>;  const Template: StoryFn<typeof Button> = (args) => ({   components: { Button },   setup() {     return { args };   },   template: `     <div style="display: flex; flex-direction: row; gap: 12px;">       <Button v-bind="args" size="large">Button</Button>       <Button v-bind="args" size="medium">Button</Button>       <Button v-bind="args" size="small">Button</Button>     </div>   `, });  export const DefaultButton: StoryFn<typeof Button> = (args) => ({   components: { Button },   setup() {     return { args };   },   template: '<Button v-bind="args">Button</Button>', });  export const PrimaryButton = Template.bind({}); PrimaryButton.args = { color: 'primary' };  export const SecondaryButton = Template.bind({}); SecondaryButton.args = { color: 'secondary' }; 

Запускаем:

npm run storybook

Получаем вот такой результат:

story с нашей кнопочкой

story с нашей кнопочкой

Заключительный этап

Теперь нам нужно сделать export нашей кнопки из ранее указанного нами entry point(src/components/index.ts)

import Button from './button/Button.vue';  export {   Button, };

Отлично, теперь можно попробовать сбилдить получившийся код и посмотреть, что будет в папке dist.

Билдим

npm run build

Получаем на выходе папку с нашим js bundle — dist/dragonekui.es.ts, dist/dragonekui.umd.ts, и файлы с типами в dist/components/*/*.vue.d.ts и файл с экспортом всех этих типов dist/components/index.d.ts

Полученный build

Полученный build

Финал. Деплой на npm

  • Для начала, нужно зарегистрироваться на npm — тык

  • После, нужно залогиниться

npm login
  • Далее билдим наш проект, и публикуем его на npm (не забывай каждый раз повысить версию пакета, пакеты с одинаковой версией не опубликуются)

npm run build && npm publish

Готово, теперь у тебя есть UI-KIT задеплоенный на npm.

UI-KIT на npm

UI-KIT на npm

Весь код можно найти в этом репо — тык

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


ссылка на оригинал статьи https://habr.com/ru/articles/761570/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *