Step‑by‑Step Guide to Building a Vite‑Based Vue Component Library with Custom Build and TypeScript Support
This article walks through creating a Vue component library using Vite, reorganizing the project structure, configuring aliases, writing reusable components with a global install helper, setting up routing, customizing Vite build outputs, generating TypeScript declarations, and documenting the library with VitePress.
The tutorial starts by initializing a Vite project (the scaffolding step is omitted) and lists the essential plugins and tools such as vue‑router, commitlint, stylelint, eslint, prettier, unplugin‑vue‑components, unplugin‑auto‑import, lint‑staged, Sentry, Pinia, and Husky.
It then shows the original directory tree and explains two major modifications:
Rename the src folder to packages and delete the views folder.
Update all alias paths that referenced src to point to packages in both vite.config.ts and tsconfig.json .
alias: [
{
find: "@",
replacement: resolve(__dirname, "../packages"),
},
], {
"paths": {
"@/*": [
"packages/*"
]
}
}Additional cleanup removes unnecessary folders (e.g., store ) and updates index.html to load the entry script from /packages/main.ts . A new theme-chalk folder is created under packages for style files.
Revised Directory Structure
├── README.md
├── commitlint.config.cjs
├── index.html
├── package.json
├── pnpm-lock.yaml
├── public
│ ├── version.txt
│ └── vite.svg
├── packages
│ ├── App.vue
│ ├── api
│ ├── assets
│ ├── auto-imports.d.ts
│ ├── components
│ ├── components.d.ts
│ ├── global.d.ts
│ ├── hooks
│ ├── intercept.ts
│ ├── main.ts
│ ├── router
│ ├── store
│ ├── utils
│ └── vite-env.d.ts
├── tsconfig.json
├── tsconfig.node.json
└── vite.config.tsComponents are placed under packages/components . Each component must define a name property to enable global registration via the withInstall helper.
<!-- Button.vue -->
<template>
<div class="bq-button">
<span>-测试按钮-6</span>
</div>
</template>
<script setup lang="ts">
defineOptions({
name: "BqButton",
});
</script>
<style lang="scss" scoped>
@import "@theme-chalk/button.scss";
</style> // index.ts
import Button from "./Button.vue";
import { withInstall } from "../../utils/tool";
export const BqButton = withInstall(Button);
export default BqButton; import type { App } from "vue";
export const withInstall = <T extends Component>(comp: T) => {
(comp as Record<string, unknown>).install = (app: App) => {
const compName = comp.name;
if (!compName) return;
app.component(compName, comp);
};
return comp;
};Routing is configured by adding a route entry that points to the component file:
{
path: "/",
name: "button",
meta: {
title: "login",
},
component: () => import(/* webpackChunkName: "button" */ "@/components/button/Button.vue"),
},Build Configuration
The Vite build is customized to output three bundles (ESM, ES modules with preserved structure, and CommonJS) and to split CSS into separate files.
export default defineConfig(() => {
return {
build: {
outDir: "build",
cssCodeSplit: true,
rollupOptions: {
external: ["three", "@ant-design/icons-vue", "ant-design-vue", "unplugin-vue-components", "unplugin-auto-import", "vue"],
output: [
{ format: "es", entryFileNames: "[name].js", exports: "named", name: "BqDesign", dir: "./build/dist" },
{ format: "es", entryFileNames: "[name].js", exports: "named", preserveModules: true, preserveModulesRoot: "packages", dir: "./build/es" },
{ format: "cjs", entryFileNames: "[name].js", exports: "named", preserveModules: true, preserveModulesRoot: "packages", dir: "./build/lib" },
],
},
lib: {
entry: resolve(__dirname, "./packages/index.ts"),
name: "BqDesign",
fileName: (format) => `bq-design.${format}.js`,
formats: ["es", "cjs"],
},
},
plugins: [vue(), dts({ tsconfigPath: "./tsconfig.prod.json", outDir: "build/lib" }), dts({ tsconfigPath: "./tsconfig.prod.json", outDir: "build/es" }), ...pluginsConfig],
resolve: resolveConfig,
};
});Explanation is given for why three output formats are needed (compatibility with ESM, CommonJS, and the library’s own dist folder) and why cssCodeSplit must be set to true to avoid bundling all component CSS into a single file.
Generating TypeScript Declarations
The vite-plugin-dts plugin is installed and configured twice (once for lib and once for es ) so that both bundles include proper .d.ts files. A dedicated tsconfig.prod.json limits the included files to the component source and excludes entry points that should not be emitted.
{
"extends": "./tsconfig.json",
"include": [
"packages/**/*.vue",
"packages/**/*.d.ts",
"packages/**/*.ts"
],
"exclude": [
"./packages/main.ts",
"node_modules",
"./packages/router/*"
]
}Documentation
For a professional documentation site, the guide recommends using VitePress , which integrates well with the Vite ecosystem and produces static docs under a docs folder.
Auto‑Import Support
An unplugin‑vue‑components resolver is provided to automatically import components whose names start with Bq and their associated CSS files.
const BqDesignResolver = () => {
return {
type: "component" as const,
resolve: (name) => {
if (name.startsWith("Bq")) {
const pathName = name.slice(2).toLowerCase();
return {
importName: name,
from: "bq-design",
path: `bq-design/es/components/${pathName}/index.js`,
sideEffects: `bq-design/es/components/${pathName}/${name.slice(2)}.css`,
};
}
},
};
};Manual Import and Tree‑Shaking
Although Vite’s tree‑shaking usually removes unused code, importing a component that depends on a third‑party library (e.g., three ) may still cause errors. The guide advises adding such libraries to optimizeDeps.exclude and shows both a full library import and a per‑component import pattern.
import {Button} from 'bq-design' import BqButton from "bq-design/es/components/button";Custom Vite Plugin for Import Transformation
A small Vite plugin is presented that rewrites named imports from bq-design into per‑component imports and automatically pulls in the component’s CSS.
export default function importPlugin() {
const regStr = /(?<!\/\/.*|\/\*[\s\S]*?\*\/\s*)import\s*{\s*([^{}]+)\s*}\s*from\s*['"]bq-design['"]/g;
return {
name: "vite-plugin-import",
enforce: "pre",
transform: (code: string, id: string) => {
if (id.endsWith(".vue")) {
const str = code.replaceAll(regStr, (match, imports) => {
const list = imports.split(",");
const newPath: string[] = [];
list.forEach((item: string) => {
item = item.trim();
const name = item.slice(2).charAt(0).toLowerCase() + item.slice(3);
const str = `import ${item.trim()} from 'bq-design/es/components/${name.trim()}';`;
import 'bq-design/es/components/${name.trim()}/${item.trim().slice(2)}.css';
newPath.push(str);
});
return newPath.join(";");
});
return str;
}
return code;
},
};
}Testing the Library Locally
The author suggests using yalc for local package publishing and provides the library’s demo URL and GitHub repository links.
Demo site: https://biuat.ibaiqiu.com/bq-design/
GitHub: https://github.com/Js-Man-H5/bq-deign
Rare Earth Juejin Tech Community
Juejin, a tech community that helps developers grow.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.