Vue Demi is a developing utility that allows Vue developers to write a universal Vue library for Vue 2 and 3. Honestly though, it's just much better to migrate all your Vue libraries and applications to the latest version now that Vue 3 seems stable. But for the sake of flexibility for your existing Vue libraries, Vue Demi is the answer to your prayers.
For this tutorial, I also used Tailwind CSS to do our component design faster. Tailwind CSS is a utility-first CSS framework packed with a variety of classes that can be composed to build any design, directly in your markup. I know lots of people are hating this CSS framework (I was one of them at first), but it happened to grow on me.
Anyways, let's get started!
Create base setup for the component library
Let's create the base setup with Vue 3 via create-vue.
npm create vue@3
I selected these configurations, but you can change it based on your preferences.
After selecting configurations, go to the directory of the created project, then install the packages via npm
.
cd vue-universal-library
npm install
Add Tailwind CSS configuration
For this example, I will be using Tailwind CSS for the component design.
Let's start by installing Tailwind CSS and its peer dependencies to the created vue project. Then, run the init command to automatically create tailwind and postcss configuration files.
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p
For reference on the later steps of this tutorial, here are the configurations inside my tailwind.config.js
file.
// tailwind.config.js
/** @type {import('tailwindcss').Config} */
module.exports = {
content: ['./index.html', './src/**/*.{vue,js,ts,jsx,tsx}'],
theme: {
extend: {
colors: {
primary: {
DEFAULT: '#f25f70',
light: '#f25f70e6',
dark: '#c14c59'
},
}
}
},
plugins: []
}
Lastly, you need to add the tailwind directives to your css file.
/* tailwind.css */
@tailwind base;
@tailwind components;
@tailwind utilities;
Please refer to the Tailwind CSS installation guide for more details.
Install Vue Demi
We will now install vue-demi to start with the creation of our universal component library.
npm i vue-demi
Update package.json
to add vue
and @vue/composition-api
as peer dependencies.
// package.son
{
...
"dependencies": {
"vue-demi": "^0.13.11"
},
"peerDependencies": {
"@vue/composition-api": "^1.0.0-rc.1",
"vue": "^2.0.0 || >=3.0.0"
},
"peerDependenciesMeta": {
"@vue/composition-api": {
"optional": true
}
},
"devDependencies": {
...
"vue": "^3.2.47",
...
}
}
After this, we can now create our component but instead of importing vue-related stuff from vue
, we import from vue-demi
.
Create your component
For this sample component library, I only created a button component based on Tailwind CSS design.
This is the snippet content of my TwButton.ts file which renders the button component based on props
and checks Vue version via isVue2
API of vue-demi
. When doing version-specific logic, it is best to check the migration guide for the breaking changes between Vue 2 and Vue 3.
// TwButton.ts
// import from 'vue-demi' instead of 'vue'
import { defineComponent, h, isVue2 } from 'vue-demi'
import '../assets/tailwind.css'
const TwButton = defineComponent({
name: 'TwButton',
props: { ... },
emits: ['click'],
setup(props, { emit, slots }) {
function renderV2() { ... }
function renderV3() {
return () =>
h(
'button',
{
disabled: props.disabled,
class: {
'btn': true,
'btn-flex': props.flex,
'opacity-50 cursor-not-allowed': props.disabled,
'!rounded-full': props.pill,
'border': props.bordered,
'border-dark': props.bordered && props.bg === 'clear',
'border-primary-dark': props.bordered && props.bg === 'fill',
'btn-clear': props.bg === 'clear',
'btn-fill': props.bg === 'fill'
},
onClick: (event: Event) => emit('click', event)
},
[
h('span', { class: 'btn-contents' }, [
props.icon
? h(
'span',
{
class: {
'icon material-icons': true,
'ml-[-2px] mr-1.5': slots.default
}
},
props.icon
)
: '',
h('span', slots.default ? slots.default() : '')
])
]
)
}
return isVue2 ? renderV2() : renderV3()
}
})
export default TwButton
For this example, I just made a separate function to render the component based on Vue version, but you can make your own wrapper to the h
function just like what this vue-demi-related GitHub discussion provided.
Build library and publish
Now that the component is created, let's build the library using rollupjs.
First, we update package.json
to add build:lib
script that removes dist
folder then builds the library via rollup.
// package.json
{
"name": "vue-universal-library",
...
"main": "dist/vue-universal-library.cjs.min.js",
"browser": "dist/vue-universal-library.esm.min.js",
"unpkg": "dist/vue-universal-library.global.min.js",
"scripts": {
...
"build:lib": "rimraf dist && rollup -c rollup.config.js --bundleConfigAsCjs",
...
},
...
}
Then, we create the rollup configuration. You can check here for the full contents of rollup.config.js
.
// rollup.config.js
...
const pkg = require('./package.json')
export default {
plugins: [...],
external: ['vue-demi'], // add vue-demi as external
input: ['src/index.ts'],
output: [
...
{
format: 'umd',
file: pkg.unpkg,
sourcemap: true,
name: 'VueUniversalLibrary',
globals: {
'vue-demi': 'VueDemi' // add vue-demi to output.globals
}
},
]
}
After implementing the rollup configuration, let's run the added build script. This will create dist
folder that contains the built library files.
npm run build:lib
If all goes well, and assuming you already own an account, we can try publishing our library to npm
.
npm publish
Install library to Vue application
In either Vue 2 or Vue 3 application, let's install vue-universal-library
via yarn
or npm
.
# using `yarn`
yarn add vue-universal-library
# using `npm`
npm install vue-universal-library
Once installed, let's import vue-universal-library.min.css
to our main file.
// main.ts
import 'vue-universal-library/dist/vue-universal-library.min.css'
Then, let's try using our button component inside App.vue
.
<!-- App.vue -->
<script setup lang="ts">
...
import { TwButton } from 'vue-universal-library'
function click() {
alert('test')
}
</script>
<template>
<div id="app">
...
<main>
...
<tw-button bg="fill" icon="download" @click="click" pill bordered flex>button</tw-button>
</main>
</div>
</template>
In order to be able to render the icon
props in our TwButton
component, we need to install Material Icons just by adding this line to our index.html
.
<!-- index.html -->
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
Also, if typescript returns a Cannot find module 'vue-universal-library'...
error, we just need to create a declaration file for it such as below:
// vue-universal-library.d.ts
declare module 'vue-universal-library'
After the setup, we can now run our Vue test application.
This is how our button looks like in Vue 2. (I did not change anything inside the pre-installed App.vue
so all other displays are still there.)
I installed it in Vue 3 application with bg=clear
and this is the result.
So that's it! You can find the sample Vue universal component library full code repo here. Feel free to comment any suggestions or comments regarding vue-demi
and this blog post. Have a Vue-tiful day! π
Top comments (0)