How to make your UI Kit on Vue 3+ storybook and deploy it on npm
Now it’s a very popular story to create your own UI Kit and tell everywhere how cool it is and how it accelerated development, so I decided to write a small guide on how to have your own UI Kit.
Contents
And why is it needed at all?
-
It is convenient and fast, all components lie in one place.
-
Several commands can conveniently use it without resorting to the ctrl + c -> ctrl + v technique.
-
New changes are immediately updated in all projects.
-
You can screw up Storybook and show the managers what you can do xD.
-
This is some brand, by making a cool UI Kit and making a basic dock under it, you can say that we have some open source solutions.
We understand why – let’s get started
Let’s start with the base
Using vite, a busy project on vue 3 with typescript – tick
npm create vite@latest ui-kit -- --template vue-ts
The project is there, now let’s play a little with our package.json, make basic manipulations to make it more like UI Kit.
Let’s add vue to devDependencies and add peerDependencies with dependency vue >= 3. We do this in order to get the least amount of code at the output, which means better performance.
"peerDependencies": {
"vue": ">=3"
}
You can read what your dependencies, devDependencies and peerDependencies are here – in short:
dependencies – dependencies that will go into the final bundle.
devDependencies – dependencies that are needed only for development and are not needed in the final bundle.
peerDependencies – dependencies that the user must have installed in their project to use your library
Let’s specify the exports for our library files:
We indicate that the files of our library will be in the folder district:
"files": [
"dist"
],
We will set up exports of styles and js bundles for import/require (what kind of names are so fashionable, you will understand in the next point, don’t worry).
"exports": {
".": {
"import": "./dist/dragonekui.es.ts",
"require": "./dist/dragonekui.umd.ts"
},
"./styles": "./dist/style.css"
},
We will get something like this 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"
}
}
Let’s configure vite
Next we need to configure vite to build our project correctly and everything is beautiful!
In vite.config.ts, we will add options.lib in order to compile the project as a library, there we will specify the name of our library, entry point – the root file for compiling the lib, and filename at the output (the name that will be used in the dist folder when compiling the project), according to the standard, two library files in the format are collected es and umd they can be changed depending on your needs, using the quality of the formats.
You can read about the formats here – tick
lib: {
entry: resolve(__dirname, 'src/components/index.ts'),
name: 'dragonekui',
fileName: (format) => `dragonekui.${format}.ts`,
},
I made an entry point file index.ts in the components, there I will import all my components and in the future I will export them.
Next, in this file, you need to configure rollup. We indicate in the option external – vue, indicating that the package is an external dependency and we drop its definition in the project where we will use our package, usually there the instance of vue is Vue.
rollupOptions: {
external: ['vue'],
output: {
globals: {
vue: 'Vue',
},
},
},
We also need to add type generation for our package, we will do this using a special plugin for vite – vite-plugin-dts.
npm i -D vite-plugin-dts
plugins: [
dts({
insertTypesEntry: true,
}),
],
We will get something like this 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
We install storybook according to their official guide – tick
npx sb init
We receive the installed storybook in the project and the father with examples, it can be deleted, we will not need it.
Be sure to tsconfig.json, we add .stories.ts format in exclude so that we don’t generate types for these files:
"exclude": [
"src/**/*.stories.ts"
],
Let’s start writing the code
I will show an example on a button.
Let’s make a simple button component:
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>
Next, we will write a story for this button:
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' };
Let’s start:
npm run storybook
We get the following result:
The final stage
Now we need to export our button from the previously specified entry point (src/components/index.ts)
import Button from './button/Button.vue';
export {
Button,
};
Excellent, now you can try to compile the resulting code and see what will be in the dist folder.
build me
npm run build
We get a folder with our js bundle at the output – dist/dragonekui.es.ts, dist/dragonekui.umd.ts, and files with types in dist/components/*/*.vue.d.ts and a file with the export of all these types dist/components/index.d.ts
Final. Deploy on npm
-
To get started, you need to register for npm – tick
-
After, you need to log in
npm login
-
Next, we build our project and publish it on npm (do not forget to upgrade the package version every time, packages with the same version will not be published)
npm run build && npm publish
Done, now you have UI-KIT deployed on npm.
All code can be found in this repo – you to
If the article seemed interesting to you, then I have it Tg channelwhere I write about new technologies at the front, share good books and interesting articles by other authors.