版本:0.2.3 | 协议:MIT | 依赖:Vite >=5.0.0 <8.0.0
写在前面
v0.2.3 的主题是:降低插件开发门槛,让路由生成更智能。
本次更新聚焦两个方面:一是将 BasePlugin 的 addPluginHooks 从抽象方法改为提供空实现的非抽象方法,让简单插件无需再写空壳;二是重构 generateRouter
的钩子注册方式,统一使用生命周期方法(onConfigResolved/destroy),同时新增 pages.json 页面级 meta 字段支持,以及基于花括号匹配的原始文本保留算法,让用户自定义属性(如 beforeEnter)在重新生成时完整保留。
本版重点:
| 能力 | 一句话说明 | 你需要做什么 |
|---|---|---|
addPluginHooks 非抽象化 |
简单插件无需再为 addPluginHooks 提供空实现 |
自动生效 |
onConfigResolved 生命周期 |
子类通过重写 onConfigResolved 注册初始化逻辑,替代手动钩子 |
自动生效 |
destroy 生命周期 |
子类通过重写 destroy 注册清理逻辑,替代手动钩子 |
自动生效 |
页面级 meta 字段支持 |
pages.json 中可直接为页面配置 meta,优先级高于 metaMapping |
在 pages.json 中配置 |
| 原始文本保留算法 | 重新生成时保留用户自定义属性的原始文本(如 beforeEnter) |
自动生效 |
| meta 逐字段更新 | 仅更新/添加新 meta 字段,不删除用户自定义的 meta 字段 | 自动生效 |
升级方式 :修改 devDependencies 中版本号为 ^0.2.3。无 Breaking Changes,完全向后兼容。
一、5 分钟快速上手
1.1 安装与升级
json
{
"devDependencies": {
"@meng-xi/vite-plugin": "^0.2.3"
}
}
1.2 在 pages.json 中直接配置 meta
json
{
"pages": [
{
"path": "pages/index/index",
"style": {
"navigationBarTitleText": "首页"
},
"meta": {
"requireAuth": false,
"customFlag": true
}
},
{
"path": "pages/user/profile",
"style": {
"navigationBarTitleText": "个人中心"
},
"meta": {
"requireAuth": true
}
}
]
}
生成的路由配置:
typescript
import type { RouteConfig } from '@meng-xi/uni-router'
export const routes: RouteConfig[] = [
{
path: '/pages/index/index',
name: 'pagesIndexIndex',
meta: { title: '首页', requireAuth: false, customFlag: true, isTab: true }
},
{
path: '/pages/user/profile',
name: 'pagesUserProfile',
meta: { title: '个人中心', requireAuth: true }
}
]
export default routes
1.3 用户自定义属性保留
在 router.config.ts 中添加 beforeEnter 守卫后,重新生成时完整保留:
typescript
// 用户添加了 beforeEnter 守卫
{
path: '/pages/user/profile',
name: 'pagesUserProfile',
meta: { title: '个人中心', requireAuth: true },
beforeEnter: (to, from, next) => {
if (!isLoggedIn()) next('/pages/login/index')
else next()
}
}
// pages.json 新增页面后重新生成,beforeEnter 完整保留
{
path: '/pages/user/profile',
name: 'pagesUserProfile',
meta: { title: '个人中心', requireAuth: true },
beforeEnter: (to, from, next) => {
if (!isLoggedIn()) next('/pages/login/index')
else next()
}
},
{
path: '/pages/settings/index', // 新增页面自动生成
name: 'pagesSettingsIndex',
meta: { title: '设置' }
}
二、BasePlugin 增强
2.1 addPluginHooks 非抽象化
v0.2.3 之前,addPluginHooks 是抽象方法,所有子类必须实现。对于不需要注册 Vite 钩子的简单插件,这要求写一个空方法。
v0.2.3 将 addPluginHooks 改为提供空实现的非抽象方法,简单插件可直接省略:
typescript
// v0.2.2:必须实现抽象方法
class SimplePlugin extends BasePlugin<SimplePluginOptions> {
protected getPluginName() {
return 'simple-plugin'
}
protected addPluginHooks(plugin: Plugin): void {
// 空实现,仅为了满足抽象方法要求
}
}
// v0.2.3:无需实现,直接省略
class SimplePlugin extends BasePlugin<SimplePluginOptions> {
protected getPluginName() {
return 'simple-plugin'
}
// addPluginHooks 已有默认空实现,无需重写
}
2.2 生命周期方法统一
v0.2.3 将 generateRouter 的钩子注册从 addPluginHooks 迁移到 onConfigResolved 和 destroy 生命周期方法,使代码结构更清晰:
typescript
// v0.2.2:在 addPluginHooks 中手动注册 configResolved 钩子
protected addPluginHooks(plugin: Plugin): void {
plugin.configResolved = async config => {
this.projectRoot = config.root
await this.safeExecute(() => this.generateRouterConfig(), '生成路由配置')
if (config.command === 'serve') {
this.startWatching()
}
}
}
// v0.2.3:重写生命周期方法,基类自动处理钩子注册
protected onConfigResolved(config: ResolvedConfig): void {
super.onConfigResolved(config) // 调用基类,记录 viteConfig 和日志
this.projectRoot = config.root
this.safeExecute(() => this.generateRouterConfig(), '生成路由配置')
if (config.command === 'serve') {
this.startWatching()
}
}
protected destroy(): void {
super.destroy() // 调用基类,注销日志配置
this.stopWatching()
}
生命周期方法说明:
| 方法 | 调用时机 | 基类行为 | 子类重写时需调用 super |
|---|---|---|---|
onConfigResolved |
Vite 配置解析完成 | 存储 viteConfig,记录日志 |
是 |
destroy |
构建结束(closeBundle) |
注销日志配置 | 是 |
三、generateRouter 增强
3.1 页面级 meta 字段
v0.2.3 为 UniAppPageConfig 新增 meta 字段,支持在 pages.json 中直接配置页面元信息。
优先级 :pageConfig.meta > metaMapping 映射 > tabBar 推断
typescript
// pages.json
{
"path": "pages/user/profile",
"style": { "navigationBarTitleText": "个人中心" },
"meta": { "requireAuth": true, "role": "admin" }
}
// 生成的路由配置
{
path: '/pages/user/profile',
name: 'pagesUserProfile',
meta: { title: '个人中心', requireAuth: true, role: 'admin' }
}
| meta 来源 | 优先级 | 说明 |
|---|---|---|
pageConfig.meta |
最高 | pages.json 中页面对象的 meta 字段 |
metaMapping 映射 |
中 | 从 style 中映射提取的字段 |
tabBar 推断 |
最低 | 自动从 tabBar 配置推断 isTab 字段 |
3.2 原始文本保留算法
v0.2.3 重构了路由合并的核心算法,使用花括号匹配替代正则表达式,确保用户自定义属性(如 beforeEnter、component 等)在重新生成时完整保留。
之前的问题:
- 使用
JSON.parse解析整个路由数组,含函数属性时全部失败 - 正则提取无法正确处理嵌套对象和注释
现在的方案:
- 使用
scanText花括号匹配算法,逐个提取路由对象的原始文本 - 对每个路由对象,通过
extractPropertyValueText精确定位属性值 - 通过
replacePropertyValue逐字段更新path/name/meta - 用户自定义属性(如
beforeEnter)的原始文本完全保留
typescript
// 保留前:用户添加了 beforeEnter
{
path: '/pages/user/profile',
name: 'pagesUserProfile',
meta: { title: '个人中心', requireAuth: true },
beforeEnter: (to, from, next) => {
if (!isLoggedIn()) next('/pages/login/index')
else next()
}
}
// 重新生成后:path/name/meta 更新,beforeEnter 原样保留
{
path: '/pages/user/profile',
name: 'pagesUserProfile',
meta: { title: '个人中心', requireAuth: true },
beforeEnter: (to, from, next) => {
if (!isLoggedIn()) next('/pages/login/index')
else next()
}
}
3.3 meta 逐字段更新
重新生成路由时,meta 字段采用逐字段更新策略:
- 新 meta 中的字段会添加或更新
- 用户自定义的 meta 字段不会被删除
- 用户修改的 meta 值不会被覆盖
| 场景 | 行为 |
|---|---|
| pages.json 新增 meta 字段 | 自动添加到生成的路由中 |
| pages.json 修改 meta 字段 | 更新对应字段值 |
| 用户添加自定义 meta 字段 | 保留,不会被删除 |
| 用户修改 meta 字段值 | 保留用户修改的值 |
四、代码重构细节
4.1 generateRouter 钩子注册迁移
将插件钩子注册从 addPluginHooks 中的手动 plugin.configResolved 改为重写 onConfigResolved 生命周期方法:
typescript
// 之前:手动注册钩子
protected addPluginHooks(plugin: Plugin): void {
plugin.configResolved = async config => {
this.projectRoot = config.root
await this.safeExecute(() => this.generateRouterConfig(), '生成路由配置')
if (config.command === 'serve') {
this.startWatching()
}
}
}
// 现在:重写生命周期方法
protected onConfigResolved(config: ResolvedConfig): void {
super.onConfigResolved(config)
this.projectRoot = config.root
this.safeExecute(() => this.generateRouterConfig(), '生成路由配置')
if (config.command === 'serve') {
this.startWatching()
}
}
protected destroy(): void {
super.destroy()
this.stopWatching()
}
4.2 方法提取与职责分离
将 generateRouterConfig 中的 readPagesJson 和 generateDtsFile 提取为独立方法,提升代码可读性:
typescript
// 之前:所有逻辑混在 generateRouterConfig 中
private async generateRouterConfig(): Promise<void> {
const pagesJsonPath = resolve(this.projectRoot, this.options.pagesJsonPath!)
if (!existsSync(pagesJsonPath)) { ... }
try {
const content = await fsp.readFile(pagesJsonPath, 'utf-8')
var pagesJson = JSON.parse(stripJsonComments(content))
} catch (error) { ... }
// ... 生成逻辑 ...
// ... dts 生成逻辑 ...
}
// 现在:职责分离
private async generateRouterConfig(): Promise<void> {
const pagesJson = await this.readPagesJson()
if (!pagesJson) return
// ... 核心生成逻辑 ...
await this.generateDtsFile(routes)
}
private async readPagesJson(): Promise<UniAppPagesJson | null> { ... }
private async generateDtsFile(routes: RouteConfig[]): Promise<void> { ... }
五、完整配置项
typescript
interface GenerateRouterOptions extends BasePluginOptions {
pagesJsonPath?: string // pages.json 路径,默认 'src/pages.json'
outputPath?: string // 输出文件路径,默认 'src/router.config.ts'
outputFormat?: 'ts' | 'js' // 输出格式,默认 'ts'
nameStrategy?: NameStrategy // 命名策略,默认 'camelCase'
customNameGenerator?: (path: string) => string // 自定义命名函数
includeSubPackages?: boolean // 包含子包,默认 true
watch?: boolean // 监听变化,默认 true
metaMapping?: Record<string, string> // meta 字段映射
exportTypes?: boolean // 导出类型,默认 true
preserveRouteChanges?: boolean // 保留用户修改,默认 true
dts?: string | boolean // 类型声明文件,默认 false
fileHeader?: boolean // 文件注释头,默认 false
}
六、实战场景
6.1 页面级 meta + 自定义守卫
typescript
// vite.config.ts
import { generateRouter } from '@meng-xi/vite-plugin'
export default defineConfig({
plugins: [
generateRouter({
fileHeader: true,
preserveRouteChanges: true,
metaMapping: {
navigationBarTitleText: 'title',
requireAuth: 'requireAuth'
},
dts: true
})
]
})
json
// pages.json --- 使用页面级 meta 配置权限
{
"pages": [
{
"path": "pages/index/index",
"style": { "navigationBarTitleText": "首页" }
},
{
"path": "pages/admin/dashboard",
"style": { "navigationBarTitleText": "管理后台" },
"meta": { "requireAuth": true, "role": "admin" }
}
]
}
6.2 开发自定义插件(简化版)
typescript
import { BasePlugin, createPluginFactory } from '@meng-xi/vite-plugin/factory'
import type { Plugin } from 'vite'
interface HelloOptions {
message?: string
}
class HelloPlugin extends BasePlugin<HelloOptions> {
protected getPluginName() {
return 'hello-plugin'
}
protected getDefaultOptions() {
return { message: 'Hello from plugin!' }
}
// v0.2.3:无需重写 addPluginHooks,直接使用生命周期方法
protected onConfigResolved() {
this.logger.info(this.options.message!)
}
}
export const helloPlugin = createPluginFactory(HelloPlugin)
七、内置插件全景
v0.2.3 共包含 15 个实用插件,覆盖构建优化的各个方面:
| 插件 | enforce | 描述 |
|---|---|---|
assetManifest |
post | 构建后生成资源映射清单,支持 Vite/Webpack/自定义格式、按入口分组和运行时注入 |
autoImport |
pre | 自动导入,支持预设映射、通配符('*')、目录扫描、Vue 模板自动导入和类型声明生成 |
buildProgress |
- | 终端实时构建进度条,支持 bar / spinner / minimal |
bundleAnalyzer |
post | 构建产物体积分析,支持 JSON/HTML 报告、gzip 计算和阈值告警 |
compressAssets |
post | 构建产物压缩,支持 gzip / brotli / both,并发压缩和统计报告 |
copyFile |
post | 构建完成后复制文件或目录,支持增量复制 |
envGuard |
post | 环境变量校验,支持类型检查、范围验证、自定义规则和运行时守卫 |
faviconManager |
post | 管理网站图标链接注入和文件复制 |
generateRouter |
post | 根据 pages.json 自动生成路由配置与类型声明(uni-app) |
generateVersion |
post | 自动生成版本号,支持文件输出和全局变量注入 |
htmlInject |
post | HTML 内容注入,支持多种位置、选择器定位、条件注入和安全过滤 |
imageOptimizer |
post | 图片优化压缩与格式转换,支持 WebP/AVIF 转换、SVG 优化、并发处理 |
loadingManager |
post | 全局 Loading 状态管理,支持请求拦截、防抖、过渡动画 |
proxyManager |
- | 开发代理管理,支持环境切换、规则文件、请求日志、延迟模拟和响应修改 |
versionUpdateChecker |
post | 运行时版本更新检查,支持多种提示样式和自定义回调 |
八、子路径导出变更
新增
UniAppPageConfig类型新增meta可选字段,支持页面级元信息配置
变更
BasePlugin.addPluginHooks从abstract改为非抽象方法,提供空实现BasePlugin新增onConfigResolved生命周期方法(已有基类实现,子类可重写)BasePlugin新增destroy生命周期方法(已有基类实现,子类可重写)generateRouter插件钩子注册从addPluginHooks迁移到onConfigResolved/destroy