使用 Vant CLI 搭建 UI 组件库:从 H5 到小程序跨平台
本文详细介绍如何使用 Vant CLI 搭建两种不同类型的 UI 组件库:Vue 3 移动端 H5 组件库 和微信/支付宝小程序跨平台组件库,涵盖完整的搭建过程、目录结构、关键配置。
目录
- [一、Vant CLI 简介](#一、Vant CLI 简介 "#%E4%B8%80vant-cli-%E7%AE%80%E4%BB%8B")
- [二、项目一:Axi-UI --- Vue 3 移动端组件库](#二、项目一:Axi-UI — Vue 3 移动端组件库 "#%E4%BA%8C%E9%A1%B9%E7%9B%AE%E4%B8%80axi-ui--vue-3-%E7%A7%BB%E5%8A%A8%E7%AB%AF%E7%BB%84%E4%BB%B6%E5%BA%93")
- [三、项目二:miniapp-ui --- 微信/支付宝小程序跨平台组件库](#三、项目二:miniapp-ui — 微信/支付宝小程序跨平台组件库 "#%E4%B8%89%E9%A1%B9%E7%9B%AE%E4%BA%8Cminiapp-ui--%E5%BE%AE%E4%BF%A1%E6%94%AF%E4%BB%98%E5%AE%9D%E5%B0%8F%E7%A8%8B%E5%BA%8F%E8%B7%A8%E5%B9%B3%E5%8F%B0%E7%BB%84%E4%BB%B6%E5%BA%93")
- 四、技术对比总结
一、Vant CLI 简介
Vant CLI 是 Vant 官方提供的组件库开发工具,核心能力包括:
- 项目脚手架:约定式目录结构,自动发现组件
- 文档站点:基于 README.md + demo/index.vue 自动生成文档站点
- 开发服务 :
vant-cli dev启动文档预览 - 代码规范:内置 ESLint + Prettier 配置
- 构建打包 :
vant-cli build构建组件库(输出 es/lib 目录)
关键认知:Vant CLI 是脚手架工具,构建目标(H5 还是小程序)由你的源码写法和构建配置决定。
二、项目一:Axi-UI --- Vue 3 移动端组件库
2.1 项目定位
基于 Vue 3 的移动端 H5 UI 组件库,标准 Vant CLI 使用方式。
2.2 搭建步骤
第一步:环境准备
bash
# Node.js >= 16.0.0
node -v
第二步:创建项目并安装依赖
bash
mkdir Axi-UI && cd Axi-UI
npm init -y
# 安装核心依赖
npm i @vant/cli @vant/eslint-config eslint prettier vue@3 sass -D
npm i @vant/cli -D # 如果上面没装好,单独执行
第三步:配置 package.json
json
{
"name": "Axi-UI",
"version": "1.0.0",
"main": "lib/Axi-UI.js",
"module": "es/index.js",
"style": "lib/index.css",
"typings": "lib/index.d.ts",
"files": ["lib", "es"],
"scripts": {
"dev": "vant-cli dev",
"lint": "vant-cli lint",
"build": "vant-cli build",
"build:site": "vant-cli build-site"
},
"peerDependencies": {
"vue": "^3.3.4"
},
"devDependencies": {
"@vant/cli": "^7.0.0",
"vue": "^3.3.4",
"sass": "^1.49.7"
},
"nano-staged": {
"*.md": "prettier --write",
"*.{ts,tsx,js,vue,less,scss}": "prettier --write",
"*.{ts,tsx,js,vue}": "eslint --fix"
},
"eslintConfig": {
"root": true,
"extends": ["@vant"]
},
"prettier": {
"singleQuote": true
},
"browserslist": ["Chrome >= 51", "iOS >= 10"]
}
第四步:创建 vant.config.mjs(⚠️ 必须用 .mjs 后缀)
js
export default {
name: 'Axi-UI',
build: {
css: { preprocessor: 'sass' },
site: { publicPath: '/Axi-UI/' },
},
site: {
title: 'Axi-UI',
logo: 'https://fastly.jsdelivr.net/npm/@vant/assets/logo.png',
nav: [
{
title: '开发指南',
items: [
{ path: 'home', title: '介绍' },
{ path: 'quickstart', title: '快速上手' },
],
},
{
title: '基础组件',
items: [
{ path: 'demo-button', title: 'DemoButton 按钮' },
],
},
],
},
};
第五步:创建目录和文件
bash
Axi-UI/
├── vant.config.mjs # Vant CLI 配置
├── package.json # 项目配置
├── docs/ # 文档目录
│ ├── home.md # 介绍页
│ └── quickstart.md # 快速上手
└── src/ # 组件源码
└── demo-button/ # 组件目录
├── index.vue # 组件实现(模板+脚本+样式)
├── README.md # 组件文档
├── demo/
│ └── index.vue # 组件演示(文档站点右栏渲染)
└── test/
└── index.spec.js # 单元测试
组件源码 (src/demo-button/index.vue):
vue
<template>
<button class="demo-button">
<slot />
</button>
</template>
<script>
export default {
name: 'DemoButton',
props: {
color: String,
type: {
type: String,
default: 'primary',
},
},
};
</script>
<style lang="scss">
.demo-button {
min-width: 120px;
color: #fff;
font-size: 16px;
line-height: 36px;
background-color: #f44;
border: none;
border-radius: 30px;
}
</style>
组件演示 (src/demo-button/demo/index.vue):
vue
<template>
<demo-block title="基础用法">
<demo-button type="primary" style="margin-left: 15px">按钮</demo-button>
</demo-block>
<demo-block title="自定义颜色">
<demo-button color="#03a9f4" style="margin-left: 15px">按钮</demo-button>
</demo-block>
</template>
<script setup>
import DemoButton from '../index.vue';
</script>
关键 :
<demo-block>是 Vant CLI 提供的内置组件,用于文档站点的示例区块展示。
组件文档 (src/demo-button/README.md):
markdown
# DemoButton 按钮
### 介绍
DemoButton 是一个示例按钮组件
### 引入
import { DemoButton } from 'Axi-UI';
Vue.use(DemoButton);
## 代码演示
### 基础用法
<demo-button type="primary" />
## API
### Props
| 参数 | 说明 | 类型 | 默认值 |
|------|------|------|--------|
| type | 按钮类型 | _string_ | `primary` |
| color | 按钮颜色 | _string_ | - |
### Events
| 事件名 | 说明 | 回调参数 |
|--------|------|----------|
| click | 点击时触发 | _event: MouseEvent_ |
### Slots
| 名称 | 说明 |
|------|------|
| default | 默认插槽 |
第六步:启动
bash
npm run dev
# 访问 http://localhost:3000/
2.3 最终目录结构
bash
Axi-UI/
├── vant.config.mjs
├── package.json
├── .eslintignore
├── .gitignore
├── .husky/
│ ├── commit-msg
│ └── pre-commit
├── docs/
│ ├── home.md
│ └── quickstart.md
├── src/
│ └── demo-button/
│ ├── index.vue
│ ├── README.md
│ ├── demo/
│ │ └── index.vue
│ └── test/
│ ├── index.spec.js
│ └── __snapshots__/
│ └── index.spec.js.snap
├── es/ # 构建产物(ES Module)
├── lib/ # 构建产物(CommonJS)
└── site-dist/ # 文档站点构建产物
2.4 技术栈
| 层级 | 技术 |
|---|---|
| 框架 | Vue 3 |
| 构建工具 | @vant/cli v7 |
| CSS 预处理 | Sass (SCSS) |
| 代码规范 | ESLint + Prettier |
| Git 钩子 | Husky + nano-staged |
| 测试 | Jest + @vue/test-utils |
| 输出格式 | CommonJS + ESM 双格式 |
三、项目二:miniapp-ui --- 微信/支付宝小程序跨平台组件库
3.1 项目定位
基于 Vant CLI + Gulp 的微信/支付宝小程序跨平台 UI 组件库,一套源码,双平台输出。
3.2 核心设计思路
css
src/weapp/button/ ──→ 构建(PLATFORM=weapp) ──→ dist/weapp/button/
src/alipay/button/ ──→ 构建(PLATFORM=alipay) ──→ dist/alipay/button/
src/common/ ──→ 被两边 import 复用 ──→ 公共逻辑共享
- 组件按平台分目录 :
src/weapp/和src/alipay/各自独立 - 公共逻辑复用 :
src/common/存放双平台共享代码 - 构建按平台分离 :通过
cross-env PLATFORM=weapp|alipay切换构建目标
3.3 搭建步骤
第一步:创建项目并安装依赖
bash
mkdir miniapp-ui && cd miniapp-ui
npm init -y
# 安装核心依赖
npm i @vant/cli @vant/eslint-config eslint prettier vue@3 sass -D
# 安装构建相关
npm i cross-env gulp gulp-rename gulp-sass gulp-typescript miniprogram-api-typings -D
第二步:配置 package.json
json
{
"name": "miniapp-ui",
"version": "1.0.0",
"description": "微信/支付宝小程序跨平台UI组件库",
"main": "dist/weapp/index.js",
"files": ["dist"],
"scripts": {
"dev": "vant-cli dev",
"lint": "vant-cli lint",
"build:weapp": "cross-env PLATFORM=weapp gulp -f build/compiler.js",
"build:alipay": "cross-env PLATFORM=alipay gulp -f build/compiler.js",
"build": "npm run build:weapp && npm run build:alipay",
"dev:weapp": "cross-env PLATFORM=weapp gulp -f build/compiler.js && echo '请在微信开发者工具中打开 dist/weapp/'",
"dev:alipay": "cross-env PLATFORM=alipay gulp -f build/compiler.js && echo '请在支付宝IDE中打开 dist/alipay/'",
"watch:weapp": "cross-env PLATFORM=weapp gulp watch -f build/compiler.js",
"watch:alipay": "cross-env PLATFORM=alipay gulp watch -f build/compiler.js"
},
"devDependencies": {
"@vant/cli": "^7.1.0",
"@vant/eslint-config": "^4.0.0",
"cross-env": "^10.1.0",
"eslint": "^8.57.1",
"gulp": "^5.0.1",
"gulp-rename": "^2.1.0",
"gulp-sass": "^6.0.1",
"gulp-typescript": "^6.0.0-alpha.1",
"miniprogram-api-typings": "^5.2.1",
"prettier": "^3.9.5",
"sass": "^1.101.0",
"vue": "^3.5.40"
},
"eslintConfig": {
"root": true,
"extends": ["@vant"]
},
"prettier": {
"singleQuote": true
}
}
第三步:配置 vant.config.mjs
js
export default {
name: 'miniapp-ui',
build: {
css: { preprocessor: 'sass' },
site: { publicPath: '/' },
srcDir: 'src/weapp',
},
site: {
defaultLang: 'zh-CN',
locales: {
'zh-CN': {
title: 'miniapp-ui',
description: '微信/支付宝小程序组件库',
logo: 'https://fastly.jsdelivr.net/npm/@vant/assets/logo.png',
langLabel: '中文',
nav: [
{
title: '开发指南',
items: [
{ path: 'home', title: '介绍' },
{ path: 'quickstart', title: '快速上手' },
],
},
{
title: '基础组件',
items: [
{ path: 'button', title: 'Button 按钮' },
{ path: 'badge', title: 'Badge 徽标' },
{ path: 'swiper', title: 'Swiper 轮播' },
],
},
],
},
},
versions: [],
},
};
⚠️ 注意:
nav必须放在locales['zh-CN']内部,不能放在顶层site下。
第四步:配置 tsconfig.json
json
{
"compilerOptions": {
"target": "ES2020",
"module": "commonjs",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"declaration": true,
"outDir": "./dist",
"rootDir": "./src",
"lib": ["ES2020"],
"types": ["miniprogram-api-typings"]
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}
第五步:创建 Gulp 构建脚本 build/compiler.js
js
const gulp = require('gulp');
const rename = require('gulp-rename');
const sass = require('gulp-sass')(require('sass'));
const ts = require('gulp-typescript');
const path = require('path');
const fs = require('fs');
const PLATFORM = process.env.PLATFORM; // 'weapp' | 'alipay'
const ROOT = path.resolve(__dirname, '..');
const tsProject = ts.createProject(path.join(ROOT, 'tsconfig.json'));
const platformConfig = {
weapp: { templateExt: '.wxml', styleExt: '.wxss' },
alipay: { templateExt: '.axml', styleExt: '.acss' },
};
const config = platformConfig[PLATFORM];
if (!config) {
throw new Error(`未知平台: ${PLATFORM}`);
}
const srcDir = path.join(ROOT, 'src', PLATFORM);
const commonDir = path.join(ROOT, 'src', 'common');
const distDir = path.join(ROOT, 'dist', PLATFORM);
// 清理
gulp.task('clean', (done) => {
if (fs.existsSync(distDir)) {
fs.rmSync(distDir, { recursive: true, force: true });
}
done();
});
// 编译 TypeScript
gulp.task('compile-ts', () => {
return gulp
.src([`${srcDir}/**/*.ts`, `${commonDir}/**/*.ts`])
.pipe(tsProject())
.pipe(gulp.dest(distDir));
});
// 拷贝模板文件
gulp.task('copy-template', () => {
return gulp.src(`${srcDir}/**/*${config.templateExt}`).pipe(gulp.dest(distDir));
});
// 拷贝 json 配置
gulp.task('copy-json', () => {
return gulp.src(`${srcDir}/**/*.json`).pipe(gulp.dest(distDir));
});
// 编译 scss → wxss/acss
gulp.task('compile-scss', () => {
return gulp
.src(`${srcDir}/**/*.scss`)
.pipe(sass().on('error', sass.logError))
.pipe(rename({ extname: config.styleExt }))
.pipe(gulp.dest(distDir));
});
// 拷贝 README
gulp.task('copy-readme', () => {
return gulp.src(`${srcDir}/**/README.md`).pipe(gulp.dest(distDir));
});
// 拷贝静态资源
gulp.task('copy-assets', () => {
return gulp.src(`${srcDir}/**/*.{png,jpg,jpeg,gif,svg}`).pipe(gulp.dest(distDir));
});
// Watch 监听
gulp.task('watch', () => {
const watcher = gulp.watch(
[`${srcDir}/**/*`, `${commonDir}/**/*`],
gulp.series('build')
);
watcher.on('change', (filePath) => {
console.log(`[${PLATFORM}] 文件变更: ${filePath}`);
});
console.log(`[${PLATFORM}] 开始监听文件变更...`);
console.log(`[${PLATFORM}] 请在对应开发者工具中打开: dist/${PLATFORM}/`);
});
// 构建
gulp.task(
'build',
gulp.series(
'clean',
gulp.parallel('compile-ts', 'copy-template', 'copy-json', 'compile-scss', 'copy-readme', 'copy-assets')
)
);
gulp.task('default', gulp.series('build'));
第六步:创建微信小程序组件(以 Button 为例)
模板 src/weapp/button/index.wxml:
html
<button
class="custom-class {{type === 'primary' ? 'button--primary' : ''}} {{disabled ? 'button--disabled' : ''}}"
disabled="{{disabled}}"
bindtap="onClick"
>
<view wx:if="{{loading}}" class="button__loading"></view>
<slot></slot>
</button>
逻辑 src/weapp/button/index.ts:
ts
Component({
properties: {
type: { type: String, value: 'default' },
size: { type: String, value: 'default' },
disabled: { type: Boolean, value: false },
loading: { type: Boolean, value: false },
},
methods: {
onClick(event: WechatMiniprogram.TouchEvent) {
if (!this.data.disabled && !this.data.loading) {
this.triggerEvent('click', event);
}
},
},
});
组件配置 src/weapp/button/index.json:
json
{
"component": true,
"usingComponents": {}
}
样式 src/weapp/button/index.scss:
scss
.button {
position: relative;
display: inline-flex;
align-items: center;
justify-content: center;
padding: 0 16px;
height: 44px;
font-size: 16px;
border-radius: 4px;
color: #323233;
background-color: #f7f8fa;
border: 1px solid #ebedf0;
&--primary { color: #fff; background-color: #1989fa; border-color: #1989fa; }
&--warn { color: #fff; background-color: #ee0a24; border-color: #ee0a24; }
&--disabled { opacity: 0.5; }
&--mini { padding: 0 8px; height: 32px; font-size: 13px; }
}
第七步:创建支付宝小程序组件
模板 src/alipay/button/index.axml(注意语法差异):
html
<button
class="custom-class {{type === 'primary' ? 'button--primary' : ''}} {{disabled ? 'button--disabled' : ''}}"
disabled="{{disabled}}"
onTap="onClick"
>
<view a:if="{{loading}}" class="button__loading"></view>
<slot></slot>
</button>
逻辑 src/alipay/button/index.ts(API 差异):
ts
Component({
props: {
type: 'default' as 'default' | 'primary' | 'warn',
size: 'default' as 'default' | 'mini',
disabled: false,
loading: false,
},
methods: {
onClick() {
if (!this.props.disabled && !this.props.loading) {
this.props.onClick?.();
}
},
},
});
第八步:启动
bash
# 启动文档站点
npm run dev
# 构建微信小程序
npm run dev:weapp
# → 用微信开发者工具打开 dist/weapp/
# 构建支付宝小程序
npm run dev:alipay
# → 用支付宝 IDE 打开 dist/alipay/
# Watch 模式(修改源码自动构建)
npm run watch:weapp
npm run watch:alipay
3.4 最终目录结构
bash
miniapp-ui/
├── vant.config.mjs
├── package.json
├── tsconfig.json
├── build/
│ └── compiler.js # Gulp 构建脚本
├── src/
│ ├── weapp/ # 微信小程序组件
│ │ ├── button/
│ │ │ ├── index.wxml # 模板
│ │ │ ├── index.scss # 样式 → .wxss
│ │ │ ├── index.ts # 逻辑
│ │ │ ├── index.json # 组件配置
│ │ │ ├── README.md # 组件文档
│ │ │ └── demo/
│ │ │ └── index.vue # 文档站点演示
│ │ ├── badge/
│ │ │ └── ... (同上结构)
│ │ └── swiper/
│ │ └── ... (同上结构)
│ ├── alipay/ # 支付宝小程序组件
│ │ ├── button/
│ │ │ ├── index.axml # 模板(支付宝语法)
│ │ │ ├── index.scss # 样式 → .acss
│ │ │ ├── index.ts # 逻辑(支付宝 API)
│ │ │ ├── index.json
│ │ │ └── README.md
│ │ ├── badge/
│ │ └── swiper/
│ └── common/
│ └── utils.ts # 公共工具函数
├── dist/ # 构建产物
│ ├── weapp/
│ └── alipay/
└── docs/
└── zh-CN/
├── home.md
└── quickstart.md
3.5 微信 vs 支付宝关键差异
| 项目 | 微信小程序 | 支付宝小程序 |
|---|---|---|
| 模板后缀 | .wxml |
.axml |
| 样式后缀 | .wxss |
.acss |
| 事件绑定 | bindtap="onXxx" |
onTap="onXxx" |
| 条件渲染 | wx:if |
a:if |
| 列表渲染 | wx:for |
a:for |
| 组件属性定义 | properties: {} |
props: {} |
| 属性访问 | this.properties.xxx |
this.props.xxx |
| 数据访问 | this.data.xxx |
this.data.xxx |
| 触发事件 | this.triggerEvent('click', e) |
this.props.onXxx?.() |
| 生命周期 | lifetimes.attached() |
didMount() |
四、技术对比总结
| 维度 | Axi-UI | miniapp-ui |
|---|---|---|
| 目标平台 | H5 移动端 | 微信 + 支付宝小程序 |
| 框架 | Vue 3 | 小程序原生 + TypeScript |
| 组件格式 | .vue 单文件 |
.wxml/.axml + .scss + .ts |
| 构建工具 | Vant CLI 内置 Vite | Gulp 自定义构建 |
| CSS 预处理 | Sass | Sass |
| 输出产物 | es/ + lib/ |
dist/weapp/ + dist/alipay/ |
| 开发命令 | npm run dev |
npm run watch:weapp |
| 预览方式 | 浏览器 | 小程序开发者工具 |
| 文档站点 | ✅ 支持 | ✅ 支持(demo 用 .vue 模拟) |
| 单元测试 | Jest | 待扩展 |
结语
Vant CLI 是一个优秀的组件库开发工具,既可以搭建标准的 Vue 3 H5 组件库,也可以通过自定义 Gulp 构建流程扩展为小程序跨平台组件库。两个项目(Axi-UI 和 miniapp-ui)展示了这两种典型场景的完整搭建过程。
希望本文能帮助到正在搭建组件库的开发者,欢迎交流讨论!