>_kejun
...

使 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 插件统一位置)

src/types.ts
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 分支,相信很快就能在下个版本和大家见面。