vite-plugin-glob-eager-auto-declare
TypeScript icon, indicating that this package has built-in type declarations

0.0.10 • Public • Published

🚀 vite-plugin-glob-eager-auto-declare

🔥 vite插件 - 自动扫描指定文件生成声明文件挂载到vue ComponentCustomProperties全局属性扩展 (通熟的讲就是你搞了一些自动化注册,挂载了很多全局属性在实例上,又懒的自己去写这些声明,又想编辑器 有代码提示,这个插件就是帮你做这些的)

一、插件的背景和目标


  • 背景 - 开发业务时,虽然在用vue3,但是习惯vue2的this写法,将全局相关的配置挂载到实例上 可以很方便的使用。但存在一个问题是,编辑器无法推导代码提示,很头疼这个问题,开发 体验大大降低。vue3大大增强了typescript的支持,是可以实现全局属性扩展推导的, 那编辑器就可以依赖ts的插件来实现代码提示

  • 目标 - 实现业务中常用的全局配置声明的自动生成,挂载到vue实例,实现代码提示

  • 原理 - 插件会扫描指定注册的文件,监听到文件更新,就会执行tsc去编译文件生成声明文件,声 名文件通过扩展vue的ComponentCustomProperties全局属性,进而使编辑器的ts插件进行 推导,实现代码提示

  • 声明 - 该插件旨在减化自己工作中重复工作的工具,实现原理简单,自身能力有限,使用者勿喷, 如果有好的想法和建议也可在issue中提出

二、插件的注册


Install:

$ npm install vite-plugin-glob-eager-auto-declare

Use in vite.config.ts for register plugin:

import GlobEagerAutoDeclarePlugin from 'vite-plugin-glob-eager-auto-declare'

export default defineConfig({
  plugins: [
    GlobEagerAutoDeclarePlugin(
      // 注册扫描的文件目录
      [
        path.resolve(__dirname, 'src/service/constant'),
        path.resolve(__dirname, 'src/common/properties'),
      ],
      // 生成声明文件输出路径
      path.resolve(__dirname, 'types')
    )
  ]
})

enable your ts plugin in tsconfig.json:

  {
      // tsconfig.json的include字段包含生成声明文件的路径,告诉ts需要引用这些声明
      "include": [
        "types/**/*.ts",
        "types/**/*.d.ts"  
      ]
  }

三、组件支持选项


GlobEagerAutoDeclarePlugin(
  // 第一个参数是数组,注册扫描的文件,包含你需要扫描的文件夹路径
  [
    // 可以直接给路径,挂载的属性名默认取文件夹名  appInstance.$constant
    path.resolve(__dirname, 'src/service/constant'),
    // 可以加入mountedName选项,挂载的属性名优先取mountedName字段  appInstance.$store
    {
      path: path.resolve(__dirname, 'src/service/store/modules'),
      mountedName: 'store'
    },
    // 可以加入exclude选项,exclude表示排除该文件夹下文哪些文件不参与编译
    // 以下就是文件夹src/service/store/modules下排除index.ts文件不编译
    {
      path: path.resolve(__dirname, 'src/service/store/modules'),
      exclude: ['index.ts']
    },
  ],
  // 第二个参数是字符串,生成声明文件输出路径
  path.resolve(__dirname, 'types'),
  // 第三个参数是object,插件相关注入选项
  {
    // 插件编译的目标
    // 此处暂时只支持vue、uni
    // 'vue', 'uni', ['uni', 'vue']都是合法值
    target?: 'vue', 
    // 是否打开编译日志 - 用于调试查看编译异常很有帮助
    keepLog?: true,
    // 是否保留编译声明中间文件 - 用于调试查看tsc编译后的原始声明
    keepCompile?: false,
    // 编译需要参加的额外依赖 - 详细解释请看下方更新记录v0.0.9
    // 请使用绝对路径,path.resolve(__dirname, 'xxx')
    include?: []
  }
)

四、注意事项

1. 很重要! 很重要! 很重要! 这是一个实验性质的项目,对于开发体验很好,还是很实用,
   但当前插件版本并非稳定版本,请谨慎使用。主要原因还是在tsc编译的声明文件,和最终
   挂载到vue ComponentCustomProperties需要的声明内容并不是完全一致的,插件内部
   写了大量的替换、过滤来实现声明文件到挂载属性的转换,这部分功能不是很稳定!!!
   
2. 关于插件的效率,由于插件原理是通过tsc来编译声明文件,还需要转换声明文件为挂载属性
   这部分还是需要耗时的,自测时间在几秒内完成,不算很长。关于效率问题做了以下优化
   第一版本:插件在项目启动时才去编译,编译整个项目的声明。
     问题: a. 写代码时全局配置随时都在修改,只有重启项目声明才能重新编译
           b. 编译整个项目声明,耗时很长
   第二版本:借vite插件的热更新手段,当注册文件发生变化时,就重新编译,并指定tsc只
            编译注册的文件来提高效率

五、使用demo(以下是我运用此插件来生成声明代码提示的一些场景)

以下示例只是展示了使用示例,模块的自动化注册是需要自己编写的,不是本插件管控功能, 此插件只是生成声明代码提示,没有涉及模块自动化注册,以下是一个api模块的自动化 注册,仅供参考

    /* api接口集中管理自动注册
     *    规则: a. 自动检测当前目录下的脚本文件
     *         b. 脚本文件必须export导出(因为一个模块可能会有多个导出接口)
     *         c. 脚本会自动注册一个以 $api.[fileName] 的全局属性提供外部调用
     *              eg:$api.auth.xxx
     */
    import type {App} from 'vue'
    
    const install = (app: App) => {
        // api集合
        const apiList: Record<string, any> = {}
        
        // 查找文件
        const modules = import.meta.glob('./modules/**/*.ts', { eager: true })
        
        // api接口分模块注入
        for (const fileName in modules) {
            const splitFileName = fileName.split('/')
            const apiKey: string = splitFileName[splitFileName.length - 1].slice(0, -3)
            apiList[apiKey] = modules[fileName]
        }
    
        // 将api集合挂载到$api属性
        app.config.globalProperties.$api = apiList as any
    }
    
    export default install
  1. pinia声明,别问我为什么不是vuex,已经被我抛弃了
   目录结构如下:
   src
    ├─ store
      ├─ modules        
        ├─ xxx.ts           
        └─ example.ts
    
    -----------------------------------------------------------------
    // vite.config.ts
    GlobEagerAutoDeclarePlugin(
      // 第一个参数是数组,注册扫描的文件,包含你需要扫描的文件夹路径
      [
        {
          path: path.resolve(__dirname, 'src/store/modules'),
          mountedName: 'store'
        }
      ],
      path.resolve(__dirname, 'types'),
    )
    
    -----------------------------------------------------------------
    // src/store/modules/example.ts    
    import { defineStore } from 'pinia';

    interface ExampleState {
      count: number;
    }
    
    export const useExampleStore = defineStore({
      id: 'example',
      
      state: (): ExampleState => ({
        count: 0
      }),
      
      getters: {
        getDoubleCount(): number {
          return this.count * 2
        }	
      },
      actions: {
        setCount(): void {
          this.count = this.count + 1
        }
      }
    })
    ----------------------------------------------------------------
    // xxx.vue
    <script setup>
       // vue实例
       const appInstance = useAppContext()
       
       // 调用store(实例.$store.模块名.具体属性)
       appInstance.$store.example.count
       appInstance.$store.example.getDoubleCount()
       appInstance.$store.example.setCount()
    </script>
  1. api接口声明
   目录结构如下:
   src
    ├─ api
      ├─ modules        
        ├─ xxx.ts           
        └─ auth.ts
    
    -----------------------------------------------------------------
    // vite.config.ts
    GlobEagerAutoDeclarePlugin(
      // 第一个参数是数组,注册扫描的文件,包含你需要扫描的文件夹路径
      [
        {
          path: path.resolve(__dirname, 'src/api/modules'),
          mountedName: 'api'
        }
      ],
      path.resolve(__dirname, 'types'),
    )
    
    -----------------------------------------------------------------
    // src/api/modules/auth.ts    
    import { request } from '@/common/plugins/requestPlugin'

    // 登录
    export const login = 
        (loginVO: API.ILoginVO): API.ILoginDTO => request({
          url: '/user/login',
          data: loginVO
        })
    
    // 登出
    export const logout = 
        (logoutVO: API.ILogoutVO): API.ILogoutDTO => request({
          url: '/user/logout',
          data: logoutVO
        })

    ----------------------------------------------------------------
    // xxx.vue
    <script setup>
       // vue实例
       const appInstance = useAppContext()
       
       // 调用api(实例.$api.模块名.具体属性)
       appInstance.$api.auth.login(loginVO)
       appInstance.$api.auth.logout(logoutVO)
    </script>
  1. 全局属性声明
   目录结构如下:
   src
    ├─ properties
       ├─ helper.ts           
       └─ require.ts
    
    -----------------------------------------------------------------
    // vite.config.ts
    GlobEagerAutoDeclarePlugin(
      // 第一个参数是数组,注册扫描的文件,包含你需要扫描的文件夹路径
      [
        {
          path: path.resolve(__dirname, 'src/properties'),
        }
      ],
      path.resolve(__dirname, 'types'),
    )
    
    -----------------------------------------------------------------
    // src/properties/helper.ts (多对象导出)   
   
    export const appSetting = {
      title: 'xxx应用名称',
      subTitle: 'xxx应用名称'  
    }
    
    export const setAppName = (name: string) => {
      console.log(name)
    }
    
    -----------------------------------------------------------------
    // src/properties/require.ts (默认导出)   
   
    const require = (path: string) => {
      if (path.startsWith('@')) {
        const filepath = path.replace(/@/, '/src')
        return new URL(import.meta.url).origin + filepath
      } else {
        return new URL(path, import.meta.url).href
      }
    }

    export default require

    ----------------------------------------------------------------
    // xxx.vue
    <script setup>
       // vue实例
       const appInstance = useAppContext()
       
       // 调用properties(实例.$properties.模块名.具体属性)
       appInstance.$properties.helper.appSetting
       appInstance.$properties.helper.setAppName('xxx应用名称')
       appInstance.$properties.require('xxxxxx')
    </script>
  1. 全局常量、枚举
   目录结构如下:
   src
    ├─ constant
       ├─ const.ts      
       ├─ dict.ts        
       └─ enum.ts
    
    -----------------------------------------------------------------
    // vite.config.ts
    GlobEagerAutoDeclarePlugin(
      // 第一个参数是数组,注册扫描的文件,包含你需要扫描的文件夹路径
      [
        {
          path: path.resolve(__dirname, 'src/constant'),
        }
      ],
      path.resolve(__dirname, 'types'),
    )
    
    -----------------------------------------------------------------
    // src/constant/const.ts  
   
    export const STORAGE_KEY_AUTH_TOKEN = 'auth_token'
    
    export const STORAGE_KEY_AUTH_USERINFO = 'auth_userinfo'
    
    -----------------------------------------------------------------
    // src/constant/dict.ts  
   
    // 性别
    export const SEX = [
      { label: '男', value: 'man' },
      { label: '女', value: 'woman' },
    ]
    
    // 支付方式
    export const CONFISCATE_WAY_TYPE = [
      { label: '微信', value: '微信' },
      { label: '支付宝', value: '支付宝' },
      { label: '现金', value: '现金' },
      { label: '其它', value: '其它' }
    ]
    
    -----------------------------------------------------------------
    // src/properties/enum.ts
   
    export enum ACCOUNT_LEVEL {
      // 市级账号
      CITY = 1,
      // 区县账号
      COUNTRY = 2,
      // 乡镇账号
      TOWN = 3
    }

    ----------------------------------------------------------------
    // xxx.vue
    <script setup>
       // vue实例
       const appInstance = useAppContext()
       
       // 调用constant(实例.$constant.模块名.具体属性)
       appInstance.$constant.const.STORAGE_KEY_AUTH_TOKEN
       appInstance.$constant.dict.SEX[0].label
       appInstance.$constant.enum.ACCOUNT_LEVEL.CITY
    </script>

六、更新记录

v0.0.7:
  1. 支持uni-app中使用, 将编译的声明挂载到uni对象上,而不是vue实例  
     GlobEagerAutoDeclarePlugin(
       // 注册编译文件路径
       ['xxx'] 
       // 声明输出路径
       'xxx',
       // 插件选项
       {
         type: 'uni'
       }  
      )
v0.0.8:
  1. 解决编译中缺少全局声明导致编译结果不正确问题 
    原因:这个问题是在使用pinia-plugin-persistedstate这个包时发现的,
    pinia-plugin-persistedstate是通过declare module 'pinia'
    来扩展pinia的声明,它是个全局的声明,不需要在文件在引入。问题
    在于该插件由于效率问题采用的方案是只编译用户指定注册的文件的声
    明,如果每次都编译整个工程效率会很低。这些全局声明通常是不会显
    式被引入的,这会导致tsc编译出来的声明简化、缺失甚至异常。
    
    解决方案:插件配置项中添加了include选项,由用户自定义配置哪些
    声明需要参与到编译过程。
       GlobEagerAutoDeclarePlugin(
        // 注册编译文件路径
        ['xxx'] 
        // 声明输出路径
        'xxx',
        // 插件选项
        {
           include: [
             path.resolve(
              __dirname, 
              'node_modules/pinia-plugin-persistedstate'
             )
           ]
        }
      )

Package Sidebar

Install

npm i vite-plugin-glob-eager-auto-declare

Weekly Downloads

149

Version

0.0.10

License

MIT

Unpacked Size

64.5 kB

Total Files

8

Last publish

Collaborators

  • achengyu