Quasar App Extension 实战:如何向宿主应用注入 Quasar Plugin
Quasar App Extension 实战如何向宿主应用注入 Quasar Plugin【免费下载链接】quasarQuasar Framework - Build high-performance VueJS user interfaces in record time项目地址: https://gitcode.com/gh_mirrors/qu/quasar当你开发一个 Quasar App Extension 时常常会遇到这样的需求你的扩展依赖某个 Quasar Plugin如Notify、Dialog、Loading、AppVisibility等才能正常工作但宿主应用hosting app不一定安装了它。此时若直接调用$q.notify(...)或从quasar包导入对应插件 API很可能会得到插件未安装的运行时错误。本篇技术指南将围绕 注入 Quasar Plugin 这一官方文档讲解如何在 App Extension 的 Index 脚本中通过 Index API 自动改写宿主应用的quasar.config文件、把所需插件注入进去并解决你的代码与宿主应用各自加载了一份 Quasar这一经典双实例陷阱。读完本文你将能够写出开箱即用、无需宿主开发者手动配置的 App Extension。适用场景为什么需要主动注入 Plugin官方指南开宗明义本指南适用于你希望确保某个 Quasar Plugin 会被注入到宿主应用中的场景因为你的 App Extension 依赖它才能工作。这种需求非常普遍。例如你的扩展在运行时调用Notify.create(...)弹出通知但宿主应用的quasar.config中framework.plugins没有配置Notify你的扩展提供基于Dialog的交互组件你的扩展监听AppVisibility来判断应用前后台状态。如果只依赖宿主开发者手动在quasar.config里加上这些插件体验就会大打折扣也容易出现装了扩展但功能不生效的困惑。更好的做法是由 App Extension 自己完成注入。文档明确指出完成这件事只需要改动 Index 脚本Index script即可因为可以借助 Index API 直接配置宿主应用的quasar.config文件。第一步通过 extendQuasarConf 注入 PluginApp Extension 的核心是src/index.js或.ts文件。在这个文件里我们使用defineIndexScript包装一个接收api对象的函数。defineIndexScript从#q-app导入其类型定义位于 app-vite/types/app-wrappers.d.ts它把回调函数的类型标注为IndexAPICallback为编写 Index 脚本提供完整的 TypeScript 类型提示。官方给出的注入示例位于ae/src/index.js或.tsimport { defineIndexScript } from #q-app export default defineIndexScript(api { // ... // Here we extend quasar.config file, so we can add // a boot file which registers our new Vue directive; // extendConf will be defined below (keep reading the tutorial) api.extendQuasarConf((conf, api) { // Lets play nice and add it only if its not defined already if (!conf.framework.plugins.includes(AppVisibility)) { conf.framework.plugins.push(AppVisibility) } }) })这段代码做了三件事通过api.extendQuasarConf(fn)注册一个配置扩展钩子钩子回调接收宿主应用合并后的完整配置对象conf检查conf.framework.plugins数组中是否已包含AppVisibility仅在未包含时才 push 进去做到友好地play nice幂等注入避免重复。从源码看 extendQuasarConf 的执行时机extendQuasarConf并非虚构的 API。在 app-vite/lib/app-extension/api-classes/IndexAPI.js 中它被实现为一个向内部钩子列表注册回调的方法/** * Extend quasar.config file * * param {function} fn * (cfg: Object, ctx: Object) undefined */ extendQuasarConf(fn) { this.#addHook(extendQuasarConf, fn) }IndexAPI内部维护了#hooks对象见 IndexAPI.js其中extendQuasarConf: []作为钩子队列存在#addHook会把{ fn, api, packageDir }压入队列packageDir用于后续解析扩展包内的资源路径。真正执行这些钩子的是配置组装阶段。在 app-vite/lib/quasar-config-file.js 中Quasar 在完成默认配置与用户配置的合并之后、产出最终quasarConf之前会遍历所有已安装 App Extension 注册的extendQuasarConf钩子await this.#ctx.appExt.runAppExtensionHook( extendQuasarConf, async hook { hook.api.logger.log(Extending quasar.config file configuration...) const tildeAssetCounts getTildeAssetCounts(rawQuasarConf) const overrides await hook.fn(rawQuasarConf, hook.api) if (Object(overrides) overrides) { rawQuasarConf merge(rawQuasarConf, overrides) } resolveAppExtensionAssets( rawQuasarConf, tildeAssetCounts, hook.packageDir ) } )这段实现透露了几个关键细节回调既可以原地修改conf对象如文档示例中的conf.framework.plugins.push(...)也可以返回一个覆盖对象该对象会被深合并merge进rawQuasarConf执行时机在quasar dev/quasar build的配置解析阶段早于 Vite 配置生成与编译流程因此对插件列表、boot 文件、build 配置的修改都会完整生效钩子内会记录扩展前的~波浪号资源数量并在执行后用resolveAppExtensionAssets重新解析扩展包内的~资源引用确保扩展注入的静态资源路径被正确指向扩展包目录一旦某个扩展的钩子抛出异常Quasar 会在开发模式下警告warn(One of your installed App Extensions failed to run.)并停止使用该配置而在构建等应严格失败的场景则直接fatal终止。钩子机制的单元测试也印证了这一点app-vite/lib/app-extension/api-classes/IndexAPI.test.js 遍历包括extendQuasarConf在内的全部钩子方法验证它们都会把{ fn, api, packageDir }正确注册进钩子列表。framework.plugins 是什么conf.framework.plugins对应宿主应用quasar.config中framework块下的插件数组。从 app-vite/types/configuration/framework-conf.d.ts 的类型定义可以看到QuasarFrameworkConfiguration包含plugins等字段该文件还定义了iconSet、lang、cssAddon、autoImportComponentCase等关联选项插件名以字符串形式出现例如AppVisibility、Notify、Dialog、Loading。第二步从自己的代码中安全地使用插件注入插件让宿主应用安装了它但这只是问题的一半。官方文档特别指出从 App Extension 自己的运行时代码里 import 插件还需要额外一步。双实例陷阱Notify.create is not a function原因在于你的 App Extension 包npm 包安装在宿主应用的node_modules里Vite 默认会对其做依赖预打包pre-bundle即optimizeDeps。预打包会把你的import { Notify } from quasar链接到另一份 Quasar 副本——而宿主应用从未在这份副本上安装任何插件。于是插件在你的代码视角里是未安装的典型报错就是Notify.create is not a function也就是说宿主应用代码里的Notify是有插件的实例而你扩展代码里的Notify是另一个没有安装插件的实例。用 optimizeDeps.exclude 绕开预打包解决办法是告诉 Vite把你的扩展包排除在预打包之外让它直接走模块图module graph这样它内部的quasarimport 就会解析到与宿主应用代码相同的模块即已安装插件的那个 Quasar 实例。官方给出的 Index 脚本写法位于ae/src/index.js或.tsimport { defineIndexScript } from #q-app export default defineIndexScript(api { // ... api.extendViteConf(() { // gets deeply merged into the host apps Vite config return { optimizeDeps: { exclude: [quasar-app-extension-my-ext] } } }) })注意两点这里返回的配置对象会被**深合并deeply merged**进宿主应用的 Vite 配置因此你只需返回增量片段exclude数组里填的是你的扩展包名npm 包名请按实际替换quasar-app-extension-my-ext。从源码看 extendViteConf 的合并语义extendViteConf同样定义于 IndexAPI.js/** * Extend Vite config * * param {function} fn * (cfg: Object, invoke: Object {isClient, isServer}, api) undefined */ extendViteConf(fn) { this.#addHook(extendViteConf, fn) }回调签名是(cfg, invoke, api)其中invoke携带{ isClient, isServer }可用于区分客户端与服务端构建SSR/SSG 场景下 Vite 配置会分别生成见 app-vite/lib/modes/ssr/ssr-config.js 中客户端与服务端两条extendViteConfig调用路径。Vite 配置的组装发生在 app-vite/lib/config-tools.js 的extendViteConfig函数中export async function extendViteConfig(viteConf, quasarConf, invokeParams) { const opts { isClient: false, isServer: false, ...invokeParams } if (typeof quasarConf.build.extendViteConf function) { const overrides await quasarConf.build.extendViteConf(viteConf, opts) if (Object(overrides) overrides) { viteConf mergeConfig(viteConf, overrides) } } await quasarConf.ctx.appExt.runAppExtensionHook( extendViteConf, async hook { hook.api.logger.log(Extending Vite config) const overrides await hook.fn(viteConf, opts, hook.api) if (Object(overrides) overrides) { viteConf mergeConfig(viteConf, overrides) } } ) return viteConf }值得注意的执行顺序宿主应用自身quasar.config里build.extendViteConf的回调先执行之后才轮到所有 App Extension 注册的extendViteConf钩子因此扩展的 Vite 覆盖可以叠加在宿主应用之上。返回的覆盖对象通过 Vite 的mergeConfig合并进viteConf——这与文档所说的deeply merged一致。该函数被 SPA、PWA、SSR、SSG、Electron、Capacitor、Cordova、Bex 等各模式共用如 app-vite/lib/modes/spa/spa-config.js 传入{ isClient: true }调用说明这条注入路径对所有 Quasar 模式通用。扩展到任何依赖 quasar 的 npm 包官方文档强调这条规则适用于任何从quasar包导入的 npm 包无论是不是 App Extension。区别只在于由谁来配置如果你的包是 App Extension可以在 Index 脚本里通过api.extendViteConf替宿主应用完成配置如上文如果是一个普通 npm 包无法配置宿主应用则需要宿主应用开发者自己在quasar.config的build extendViteConf里写等效配置。宿主应用侧的等效写法参见 Handling Vite 文档其核心是把optimizeDeps.exclude加到 Vite 配置中例如// quasar.config 文件 build: { extendViteConf(viteConf) { viteConf.optimizeDeps viteConf.optimizeDeps || {} viteConf.optimizeDeps.exclude viteConf.optimizeDeps.exclude || [] viteConf.optimizeDeps.exclude.push(your-package-name) } }例外情况Vue 渲染作用域内的代码无需任何处理官方文档最后给出一条重要提示Code that runs in Vue render scope does not need any of this.useQuasar()returns the host apps own$q, so$q.notify(...)from a composable or component always reaches the installed plugin.翻译过来即是运行在 Vue 渲染作用域render scope内的代码完全不需要上述处理。因为useQuasar()返回的是宿主应用自己的$q实例——它天然关联着宿主应用已安装的插件。因此在组件、composable 里通过useQuasar()拿到的$q.notify(...)、$q.dialog(...)等始终命中已安装的插件无需optimizeDeps.exclude双实例问题主要影响的是在 Vue 渲染作用域之外直接import { Notify } from quasar并调用插件静态 API的代码路径例如普通工具模块、非组件逻辑、某些需要在模块顶层执行的代码等。这条边界值得在实现时明确区分能走useQuasar()的尽量走useQuasar()只有不得不直接 import 插件静态 API 的场景才需要optimizeDeps.exclude配置。完整实战清单综合官方文档与仓库实现一个注入 Quasar Plugin的 App Extension Index 脚本可以按如下模板组织ae/src/index.js或.tsimport { defineIndexScript } from #q-app export default defineIndexScript(api { // 1. 注入插件到宿主应用的 quasar.config api.extendQuasarConf(conf { const { plugins } conf.framework for (const plugin of [Notify, Dialog, AppVisibility]) { if (plugins.includes(plugin) false) { plugins.push(plugin) } } }) // 2. 若扩展的运行时代码在渲染作用域外直接 import 了 quasar 插件 // 需排除预打包确保与宿主应用共享同一 Quasar 实例 api.extendViteConf(() ({ optimizeDeps: { exclude: [quasar-app-extension-my-ext] } })) // 其余 Index 脚本逻辑生命周期钩子、命令注册等... })配套的检查清单确认插件名正确与quasar包导出的插件名一致如AppVisibility、Notify、Dialog、Loading幂等注入先includes判断再push避免重复安装与插件重复注册识别代码作用域渲染作用域内用useQuasar()取$q无需额外配置作用域外的静态 import 才需要optimizeDeps.exclude替换包名optimizeDeps.exclude里务必填扩展的实际 npm 包名善用文档涉及扩展配置更多能力时可参考 Index API 开发指南若你的扩展同时提供 UI 组件等宿主资源可参考 Provide UI elements 一文。小结向宿主应用注入 Quasar Plugin 只需两步用api.extendQuasarConf修改conf.framework.plugins让插件被安装用api.extendViteConf把扩展包加入optimizeDeps.exclude让扩展代码与宿主应用共享同一个 Quasar 实例。前者解决插件装没装后者解决装了但你的代码看不到。而 Vue 渲染作用域内的useQuasar()则始终指向宿主应用的$q无需任何额外处理。这套模式在 quasar-config-file.js 与 config-tools.js 中有完整的执行链路支撑适用于 SPA、PWA、SSR、SSG、Electron、Capacitor、Cordova 与 Bex 全模式是编写高质量、零配置依赖型 App Extension 的必备技巧。【免费下载链接】quasarQuasar Framework - Build high-performance VueJS user interfaces in record time项目地址: https://gitcode.com/gh_mirrors/qu/quasar创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
上一篇/下一篇内容由系统自动关联
返回资讯列表 →