使 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 | SwitchTabOptionsdeclare 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-pagesinterface 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 分支,相信很快就能在下个版本和大家见面。