使 vite-plugin-uni-pages 支持类型安全
2023-08-28 18:07
支持类型安全 · Issue #105 · uni-helper/vite-plugin-uni-pages (github.com)
原理
正如 issue 中所说,生成 .d.ts
,覆写 uni-app 提供的类型接口,我们所熟知的 unplugin-vue-component、unplugin-auto-import 等插件均采用这种方式实现。
实现
vite-plugin-uni-pages 目前的 Context 类中已经有 pages 和 subPages 的所有数据,所以只要实现配置、生成、和在合适的时机写 dts 文件即可。
配置
新增一个配置项 dts
,使其接受 string 或者 boolean,默认为 true,当其为 string 时,作为相对路径生成文件,为 flase 时不生成文件,为 true 时生成到 ./uni-pages.d.ts
(与其他 vite 插件统一位置)
export interface Options {
...
dts?: boolean | string
...
}
生成
生成也很简单,关键是要生成什么,首先当然是老三套注释,禁用 eslint、prettier、tsc
/* eslint-disable */
/* prettier-ignore */
// @ts-nocheck
// Generated by vite-plugin-uni-pages
然后重载全局接口 Uni 的四个路由跳转函数,当然这意味着你必须在 tsconfig.json 中配置 "types": ["@dcloudio/types"]
其中比较关键的是我们都知道的 tabBar 页面必须使用 switchTab 函数去跳转。其他页面可以用任意跳转函数。
interface NavigateToOptions {
url: '/pages/login' | '...'
}
interface RedirectToOptions extends NavigateToOptions {}
interface SwitchTabOptions {
url: '/pages/index' | '...'
}
type ReLaunchOptions = NavigateToOptions | SwitchTabOptions
declare interface Uni {
navigateTo(options: UniNamespace.NavigateToOptions & NavigateToOptions): void
redirectTo(options: UniNamespace.RedirectToOptions & RedirectToOptions): void
switchTab(options: UniNamespace.SwitchTabOptions & SwitchTabOptions): void
reLaunch(options: UniNamespace.ReLaunchOptions & ReLaunchOptions): void
}
有了目标就简单多了,我们只要获取到所有普通页面和所有 tabBar 页面,使用模板生成即可
export function getDeclaration(ctx: PageContext) {
const subPagesPath = ctx.subPageMetaData.map((sub) => {
return sub.pages.map(v => (`"${normalizePath(join(sub.root, v.path))}"`))
}).flat()
const tabsPagesPath = ctx.pagesGlobConfig?.tabBar?.list?.map((v) => {
return `"${v.pagePath}"`
}) ?? []
const allPagesPath = [...ctx.pageMetaData.filter(page => !tabsPagesPath.includes(page.path)).map(v => `"${v.path}"`), ...subPagesPath]
const code = `/* eslint-disable */
/* prettier-ignore */
// @ts-nocheck
// Generated by vite-plugin-uni-pages
interface NavigateToOptions {
url: ${allPagesPath.join(' |\n ')};
}
interface RedirectToOptions extends NavigateToOptions {}
interface SwitchTabOptions {
${tabsPagesPath.length ? `url: ${tabsPagesPath.join(' | ')}` : ''}
}
type ReLaunchOptions = NavigateToOptions | SwitchTabOptions;
declare interface Uni {
navigateTo(options: UniNamespace.NavigateToOptions & NavigateToOptions): void;
redirectTo(options: UniNamespace.RedirectToOptions & RedirectToOptions): void;
switchTab(options: UniNamespace.SwitchTabOptions & SwitchTabOptions): void;
reLaunch(options: UniNamespace.ReLaunchOptions & ReLaunchOptions): void;
}
`
return code
}
最后是写入函数
async function writeFile(filePath: string, content: string) {
await mkdir(dirname(filePath), { recursive: true })
return await writeFile_(filePath, content, 'utf-8')
}
export async function writeDeclaration(ctx: PageContext, filepath: string) {
const originalContent = existsSync(filepath) ? await readFile(filepath, 'utf-8') : ''
const code = getDeclaration(ctx)
if (!code)
return
if (code !== originalContent)
await writeFile(filepath, code)
}
合适的时机
最后只需在 updatePagesJSON
函数最后获取到最新的pages后,调用即可。
总结
从插件本身出发,这个功能还是很好实现的,进一步增强了 VS Code 下开发 uni-app 的体验。
目前代码已经合并到 main 分支,相信很快就能在下个版本和大家见面。