Enhancing Integration with Unplugin and Nuxt Module
Having a robust component library is a great start, but integrating it into the Vue.js ecosystem takes it to another level. In this ecosystem, two main tools stand out:
- Unplugin Vue Components: A bundler plugin for on-demand components that are auto-imported, fully typed, and tree-shakable.
- Nuxt: An open-source framework based on Vue.js that simplifies and empowers web development.
Although our component library can function without integration into these tools, I'm firmly convinced that integration will enhance its power and ease of use. I'm devoted to providing the best developer experience possible, and I'm sure you will find it invaluable.
Unplugin Vue Component Resolver
Before building the resolver, it's essential to understand how Unplugin Vue Components works. This will clarify what a resolver does and its functionality.
Tip
If you have any questions, please comment below, and I'll be happy to assist.
Consider this component:
<script lang="ts" setup>
</script>
<template>
<UButton label="Click me" />
</template>
Notice that there's no import statement for the UButton
component. Unplugin Vue Components scans the template and identifies non-standard HTML tags to find them in its component list. This list can be manually fed or automatically populated using a resolver.
A resolver is a function that receives a component name and returns its import path if it matches the resolver's internal list. This allows selective auto-import of components or adding prefixes to component names.
In our example, a resolver might resolve the UButton
component as:
return {
name: 'Button',
from: '@nuxt/ui'
}
Unplugin Vue Components would transform the component by injecting the import statement:
<script lang="ts" setup>
import { Button as UButton } from '@nuxt/ui'
</script>
<template>
<UButton label="Click me" />
</template>
Simple, right? Now, let's build our resolver.
Note
To deepen your understanding of Vite, try the vite-plugin-inspector, which lets you inspect modifications made by each plugin.
Building the Resolver
First, gather all your library components in a src/components.ts
file within the packages/huchet-vue
folder:
touch src/components.ts
Then export an array of all components:
export const components = [
'Button'
]
To ensure updates, create a test that verifies the exported components match those in the components
folder. Create a src/index.test.ts
file in packages/huchet-vue
:
touch src/index.test.ts
Add this test to verify component exports:
import { expect, it } from 'vitest'
import { components } from './components'
import * as Huchet from './index'
it('should expose the correct components', () => {
expect(Object.keys(Huchet)).toEqual(Object.values(components))
})
This test prevents missed updates when components change.
Note
Inspired by Radix Vue.
Next, create a src/resolver.ts
for the Unplugin Vue Components resolver and install it as a dev dependency:
touch src/resolver.ts && pnpm add -D unplugin-vue-components
The resolver function initiates in vite.config.ts
, allowing developers to pass options like a prefix for component names. It resolves component names and returns import paths if they match:
import type { ComponentResolver } from 'unplugin-vue-components'
import { components } from './components'
export interface ResolverOptions {
/**
* prefix for components used in templates
*
* @defaultValue ""
*/
prefix?: string
}
export default function (options: ResolverOptions = {}): ComponentResolver {
const { prefix = '' } = options
return {
type: 'component',
resolve: (name: string) => {
if (name.toLowerCase().startsWith(prefix.toLowerCase())) {
const componentName = name.substring(prefix.length)
if (components.includes(componentName)) {
return {
name: componentName,
from: '@barbapapazes/huchet-vue',
}
}
}
},
}
}
To ensure the resolver is built, add it as an entry point in vite.config.ts
:
import { resolve } from 'node:path'
import { defineConfig } from 'vite'
export default defineConfig({
build: {
lib: {
entry: {
index: resolve(__dirname, 'src/index.ts'),
resolver: resolve(__dirname, 'src/resolver.ts'),
},
},
},
})
Finally, export the resolver in package.json
:
{
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.mjs"
},
"./resolver": {
"types": "./dist/resolver.d.ts",
"import": "./dist/resolver.mjs"
}
},
"main": "./dist/index.mjs",
"types": "dist/index.d.ts"
}
Note
Rebuild the project to ensure the new entry point is accessible.
Using the Resolver
With the resolver ready, let's test it in our playground project:
- Navigate to the playground folder:
cd ../../playground/vue
- Install
unplugin-vue-components
:
pnpm add -D unplugin-vue-components
- Update
vite.config.ts
to use the resolver with aU
prefix:
import HuchetVueResolver from '@barbapapazes/huchet-vue/resolver'
import Vue from '@vitejs/plugin-vue'
import Components from 'unplugin-vue-components/vite'
import { defineConfig } from 'vite'
export default defineConfig({
plugins: [
Vue(),
Components({
dts: true,
resolvers: [
HuchetVueResolver({
prefix: 'U'
}),
],
})
],
})
- Add
components.d.ts
to.gitignore
:
echo "components.d.ts" >> .gitignore
Note
This file is generated by unplugin-vue-components
to help your IDE understand the components and provide auto-completion.
- Update
src/App.vue
to use theUButton
component without importing it:
<template>
<div class="flex gap-6 p-6">
<UButton label="Hello World" />
<UButton label="Hello World" variant="outline" />
</div>
</template>
That's it! The UButton
component is automatically imported for ease of use.
Nuxt Module
Nuxt transforms web development with Vue.js by providing an extended configuration and module system. Building a Nuxt module for our component library requires minimal effort and eliminates setup for developers. Let's dive into it.
Building the Module
Start by creating a nuxt.ts
file in the packages/huchet-vue
folder:
touch nuxt.ts
Install the necessary Nuxt packages and Tailwind CSS:
pnpm add -D @nuxt/kit @nuxt/schema && pnpm add tailwindcss@next @tailwindcss/vite@next @tailwindcss/postcss@next
Then, create the module:
import type { } from '@nuxt/schema' // Mandatory to avoid a bug when building
import { addComponent, addVitePlugin, defineNuxtModule } from '@nuxt/kit'
import { components } from './components'
export interface ModuleOptions {
prefix: string
}
export default defineNuxtModule<ModuleOptions>({
meta: {
name: '@barbapapazes/huchet-vue',
configKey: 'huchet',
compatibility: {
nuxt: '>=3.0.0',
},
},
defaults: {
prefix: '',
},
async setup(options, nuxt) {
if (nuxt.options.builder === '@nuxt/vite-builder') {
const Tailwind = await import('@tailwindcss/vite').then(r => r.default)
addVitePlugin(Tailwind())
}
else {
nuxt.options.postcss.plugins['@tailwindcss/postcss'] = {}
}
for (const component of components) {
addComponent({
name: `${options.prefix}${component}`,
export: component,
filePath: '@barbapapazes/huchet-vue',
})
}
},
})
This plugin simplifies Tailwind CSS setup, using either Vite or PostCSS depending on the Nuxt builder.
The second part auto-imports components into the Nuxt system using the Unimport framework, allowing prefix customization like the Unplugin Vue Components resolver.
Inspired by Radix Vue Nuxt module and Nuxt UI 3 module.
Add an entry in vite.config.ts
and export it in package.json
:
import { resolve } from 'node:path'
export default defineConfig({
build: {
lib: {
entry: {
index: resolve(__dirname, 'src/index.ts'),
resolver: resolve(__dirname, 'src/resolver.ts'),
nuxt: resolve(__dirname, 'src/nuxt.ts'),
},
},
rollupOptions: {
external: ['vue', '@nuxt/kit', '@tailwindcss/vite'],
},
},
})
Make sure to externalize @nuxt/kit
and @tailwindcss/vite
.
{
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.mjs"
},
"./resolver": {
"types": "./dist/resolver.d.ts",
"import": "./dist/resolver.mjs"
},
"./nuxt": {
"types": "./dist/nuxt.d.ts",
"import": "./dist/nuxt.mjs"
}
},
"main": "./dist/index.mjs",
"types": "dist/index.d.ts"
}
Using the Module
With the Nuxt module in place, let's set up a way to test its functionality. We'll create a new Nuxt playground, install the package, and configure it.
- Create a new Nuxt project from the root directory:
npx nuxi init ./playground/nuxt
Note
Modify the postinstall
script to dev:prepare
in Nuxt's package.json
to avoid errors after a package installation. Rename it since prepare
is reserved by npm.
- Install the component library:
cd ./playground/nuxt && pnpm add ../../packages/huchet-vue
- Duplicate the
App.vue
from the Vue playground to the Nuxt playground:
cp ../vue/src/App.vue ./app.vue
- Add the module in
nuxt.config.ts
:
export default defineNuxtConfig({
modules: ['@barbapapazes/huchet-vue/nuxt'],
css: ['~/assets/css/main.css'],
huchet: {
prefix: 'U',
},
compatibilityDate: '2024-11-01',
devtools: { enabled: true },
})
Note
Include the CSS file using the css
property for Tailwind CSS to function.
- Create the CSS file in
assets/css/main.css
:
mkdir -p assets/css && touch assets/css/main.css && echo "@import 'tailwindcss';" >> assets/css/main.css
- Launch the Nuxt project:
pnpm dev
Voila! You can now use the component library in a Nuxt project seamlessly, with auto-imported components and fully-functional Tailwind CSS.
The Nuxt developer experience, enriched by the module system, significantly simplifies developers' lives. I'm confident you will appreciate it too.
Thanks for reading! My name is Estéban, and I love to write about web development.
I've been coding for several years now, and I'm still learning new things every day. I enjoy sharing my knowledge with others, as I would have appreciated having access to such clear and complete resources when I first started learning programming.
If you have any questions or want to chat, feel free to comment below or reach out to me on Bluesky, X, and LinkedIn.
I hope you enjoyed this article and learned something new. Please consider sharing it with your friends or on social media, and feel free to leave a comment or a reaction below—it would mean a lot to me! If you'd like to support my work, you can sponsor me on GitHub!