Vite插件0.2.3重磅更新:路由生成更智能

版本:0.2.3 | 协议:MIT | 依赖:Vite >=5.0.0 <8.0.0


写在前面

v0.2.3 的主题是:降低插件开发门槛,让路由生成更智能

本次更新聚焦两个方面:一是将 BasePluginaddPluginHooks 从抽象方法改为提供空实现的非抽象方法,让简单插件无需再写空壳;二是重构 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 迁移到 onConfigResolveddestroy 生命周期方法,使代码结构更清晰:

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 重构了路由合并的核心算法,使用花括号匹配替代正则表达式,确保用户自定义属性(如 beforeEntercomponent 等)在重新生成时完整保留。

之前的问题

  • 使用 JSON.parse 解析整个路由数组,含函数属性时全部失败
  • 正则提取无法正确处理嵌套对象和注释

现在的方案

  1. 使用 scanText 花括号匹配算法,逐个提取路由对象的原始文本
  2. 对每个路由对象,通过 extractPropertyValueText 精确定位属性值
  3. 通过 replacePropertyValue 逐字段更新 path/name/meta
  4. 用户自定义属性(如 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 中的 readPagesJsongenerateDtsFile 提取为独立方法,提升代码可读性:

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.addPluginHooksabstract 改为非抽象方法,提供空实现
  • BasePlugin 新增 onConfigResolved 生命周期方法(已有基类实现,子类可重写)
  • BasePlugin 新增 destroy 生命周期方法(已有基类实现,子类可重写)
  • generateRouter 插件钩子注册从 addPluginHooks 迁移到 onConfigResolved/destroy