vue-implant/vue-implant

GitHub: vue-implant/vue-implant

一个轻量级 Vue 3 组件注入框架,简化油猴脚本开发中的 DOM 挂载与生命周期管理。

Stars: 10 | Forks: 1

Addfox

Vue-implant

一个轻量级的 Vue 组件注入框架

GitHub Repo stars NPM Version NPM Downloads NPM Downloads
English | 中文
`vue-implant` 是一个 Vue 组件注入框架,主要为 Greasemonkey 脚本开发场景设计。 它简化了 Userscript 开发中的组件注入,消除了繁琐的底层 DOM 操作。通过提供声明式的注入机制,它使开发者能够轻松构建高性能、可维护的脚本应用程序。 我们强烈建议配合使用 [vite-plugin-monkey](https://github.com/lisonge/vite-plugin-monkey)。通过将 `vue-implant` 的组件注入与 Vite 的现代构建管道相结合,您可以享受无缝、高性能的 Userscript 开发工作流。 ## 目录 📚 - [演示](#demo) - [安装](#installation) - [最佳实践](#best-practice) - [快速开始](#quick-start) - [兼容性](#compatibility) - [API](#api) - [限制](#limitations) - [常见问题](#faq) - [路线图](#roadmap) - [开发](#development) - [贡献](#contributing) - [许可证](#license) ## 演示 🎬 公开演示站点:https://flowingink.github.io/vue-implant/ ## 安装 📦 支持 `npm`、`pnpm` 和 `yarn`: ``` npm install vue-implant ``` ``` pnpm add vue-implant ``` ``` yarn add vue-implant ``` ## 最佳实践 ✅ 对于 Greasemonkey/TemperMonkey 项目,推荐的技术栈是:`vite-plugin-monkey + vue-implant`。 - `vite-plugin-monkey`:处理 userscript 构建流程、元数据、本地开发和发布流程。 - `vue-implant`:处理组件挂载、DOM 目标等待、重新注入以及动态页面上的任务生命周期。 这种搭配保持了职责清晰:一个工具专注于 userscript 工程,另一个专注于可靠的页面增强。 ## 快速开始 ⚡ ``` import { Injector } from 'vue-implant'; import TestAppComponent from './TestAppComponent.vue'; const injector = new Injector(); injector.register('#app', TestAppComponent, { alive: true, scope: 'global' }); injector.run(); ``` ## 兼容性 ✅ - Vue:`3.x` - 运行环境:现代浏览器页面环境(例如 userscripts、浏览器扩展 content scripts) - iframe:目前不支持 ## API 🧩 ### `new Injector(config?: Partial)` 🏗️ 创建一个 `Injector` 实例。 ``` type InjectionConfig = { alive?: boolean; scope?: 'local' | 'global'; timeout?: number; logger?: ILogger; }; ``` | 属性 | 类型 | 描述 | 默认值 | | --- | --- | --- | --- | | `alive` | `boolean` | 是否启用全局重新注入。 | `false` | | `scope` | `'local' \| 'global'` | `local` 将监听器绑定到目标元素的父级;`global` 将监听器挂载到 `body`,因此当本地 DOM 重建时监听器仍可保持活动状态。 | `'local'` | | `timeout` | `number` | 初始注入和重新注入的超时阈值(毫秒)。不建议将其显式设置为 `undefined`。 | `5000` | | `logger` | `ILogger` | 自定义 logger 实现。省略时使用内置 logger。 | 内置 logger | ### `Injector.run(): void` 启动注入过程并处理已注册的任务。 ### `Injector.register(injectAt: string, component: Component, option?: ComponentOptions): RegisterResult` 注册一个组件注入任务。 参数描述: - `injectAt`:组件应注入的目标选择器。 - `component`:要注入的 Vue 组件。 - `option`:可选配置。 `option` 结构: | 属性 | 必填 | 类型 | 描述 | | --- | --- | --- | --- | | `alive` | 否 | `boolean` | 是否启用重新注入(省略时使用全局配置)。 | | `scope` | 否 | `'local' \| 'global'` | 重新注入观察范围(省略时使用全局配置)。 | | `on` | 否 | `object` | 外部事件绑定配置。 | | `on.listenAt` | 是 | `string` | 事件目标元素的选择器。 | | `on.type` | 是 | `string` | 事件类型。 | | `on.callback` | 是 | `EventListener` | 事件回调。 | | `on.activitySignal` | 否 | `() => Ref` | 控制监听器激活的外部信号。 | 返回值: | 属性 | 类型 | 描述 | 默认值 | | --- | --- | --- | --- | | `taskId` | `string` | 唯一任务标识符。 | `[ComponentName]@[CSSSelector]` | | `isSuccess` | `boolean` | 注册是否成功。 | 成功 `true`,失败 `false` | | `enableAlive` | `() => void` | 手动启用重新注入;注册失败时为空函数。 | 回调函数 | | `disableAlive` | `() => void` | 手动禁用重新注入;注册失败时为空函数。 | 回调函数 | ### `Injector.registerListener(listenAt: string, event: string, callback: EventListener, activitySignal?: () => Ref): ListenerRegisterResult` 注册一个纯监听器任务(无组件注入)。 参数描述: - `listenAt`:监听目标元素的选择器。 - `event`:监听事件类型。 - `callback`:事件回调。 - `activitySignal`:可选,返回 `Ref` 以动态控制监听器激活。 返回值: | 属性 | 类型 | 描述 | | --- | --- | --- | | `taskId` | `string` | 唯一监听器任务标识符。 | | `isSuccess` | `boolean` | 注册是否成功。 | ### `Injector.use(plugin: Plugin): this` 为当前 `Injector` 创建的每个注入 app 注册一个共享 Vue 插件。 **最小示例:** ``` import { createPinia } from 'pinia'; import { Injector } from 'vue-implant'; const injector = new Injector(); injector.use(createPinia()); ``` ### `Injector.usePlugins(...plugins: Plugin[]): this` 按照安装顺序注册多个共享 Vue 插件。 **最小示例:** ``` import { createPinia } from 'pinia'; import { Injector } from 'vue-implant'; const injector = new Injector(); const pinia = createPinia(); const analyticsPlugin = { install() { // custom plugin setup } }; injector.usePlugins(pinia, analyticsPlugin); ``` ### `Injector.getPlugins(): Plugin[]` 返回 injector 上当前已注册的共享插件。 ### `Injector.setPinia(pinia: Plugin): void` 基于 Pinia 设置的旧版兼容别名。它仍然有效,并在内部将 Pinia 注册为共享插件。 ### `Injector.getPinia(): Plugin | undefined` 返回先前通过 `setPinia()` 设置的 Pinia 实例。 ### 日志 `vue-implant` 通过统一的 logger 写入内部运行时日志,而不是在每个模块中直接使用 `console`。 - 默认日志格式:`[Vue Implant][LEVEL][ISO_TIMESTAMP] message` **最小示例:** ``` import { Injector, type ILogger } from 'vue-implant'; const logger: ILogger = { info: (message, ...args) => console.info(`[My App] ${message}`, ...args), warn: (message, ...args) => console.warn(`[My App] ${message}`, ...args), error: (message, ...args) => console.error(`[My App] ${message}`, ...args), debug: (message, ...args) => console.debug(`[My App] ${message}`, ...args) }; const injector = new Injector({ logger }); ``` ### `Injector.enableAlive(taskId: string): void` 为组件任务启用重新注入。 参数描述: - `taskId`:用于启用重新注入的任务 ID。 **最小示例:** ``` import { Injector } from 'vue-implant'; import TestAppComponent from './TestAppComponent.vue'; const injector = new Injector(); const { taskId } = injector.register('#app', TestAppComponent); injector.enableAlive(taskId); injector.run(); ``` ### `Injector.disableAlive(taskId: string): void` 为组件任务禁用重新注入。 参数描述: - `taskId`:用于禁用重新注入的任务 ID。 **最小示例:** ``` import { Injector } from 'vue-implant'; import TestAppComponent from './TestAppComponent.vue'; const injector = new Injector(); const { taskId } = injector.register('#app', TestAppComponent); injector.enableAlive(taskId); injector.disableAlive(taskId); injector.run(); ``` ### `Injector.destroy(taskId: string): void` 销毁特定任务并释放关联的监听器、组件实例和状态。 参数描述: - `taskId`:要销毁的任务 ID。 **最小示例:** ``` import { Injector } from 'vue-implant'; import TestAppComponent from './TestAppComponent.vue'; const injector = new Injector(); const { taskId } = injector.register('#app', TestAppComponent); injector.run(); injector.destroy(taskId); ``` ### `Injector.destroyAll(): void` 销毁当前 `Injector` 中注册的所有任务。 **最小示例:** ``` import { Injector } from 'vue-implant'; import TestAppComponent from './TestAppComponent.vue'; const injector = new Injector(); injector.register('#app', TestAppComponent); injector.registerListener('#btn', 'click', () => console.log('clicked')); injector.run(); injector.destroyAll(); ``` ### `Injector.reset(taskId: string): void` 将特定任务重置为可复用的初始运行时状态,同时保留注册元数据。 参数描述: - `taskId`:要重置的任务 ID。 行为摘要: - 当任务处于 alive 模式时,首先停止 alive observer。 - 卸载已挂载的组件实例并移除注入的根元素。 - 中止监听器并停止 watcher。 - 在 context 中保留任务条目,以便任务可以被复用。 ### `Injector.resetAll(): void` 将所有已注册的任务重置为可复用的初始运行时状态。 行为摘要: - 首先停止所有 alive 任务的 alive observers。 - 调用一次 context 级别的完全重置,以清理每个任务的运行时字段。 - 在 context 中保留任务注册和 taskId。 ### `Injector.bindListenerSignal(taskId: string, source: WatchSource): boolean` 将外部响应式信号绑定到监听器激活:当为 `true` 时打开监听器,当为 `false` 时关闭。 参数描述: - `taskId`:任务 ID(必须是配置了 `on` 的任务)。 - `source`:`WatchSource`,通常是 `ref`。 **最小示例:** ``` import { ref } from 'vue'; import { Injector } from 'vue-implant'; import TestAppComponent from './TestAppComponent.vue'; const injector = new Injector(); const enabled = ref(true); const { taskId } = injector.register('#app', TestAppComponent, { on: { listenAt: '#btn', type: 'click', callback: () => console.log('clicked') } }); injector.bindListenerSignal(taskId, enabled); injector.run(); ``` ### `Injector.controlListener(taskId: string, event: ActionEvent): boolean` 手动控制已注册任务的外部监听器状态:`Action.OPEN` 启用,`Action.CLOSE` 禁用。 参数描述: - `taskId`:任务 ID(必须是配置了事件监听的任务)。 - `event`:动作类型,`Action.OPEN` 或 `Action.CLOSE`。 **最小示例:** ``` import { Action, Injector } from 'vue-implant'; import TestAppComponent from './TestAppComponent.vue'; const injector = new Injector(); const { taskId } = injector.register('#app', TestAppComponent, { on: { listenAt: '#btn', type: 'click', callback: () => console.log('clicked') } }); injector.controlListener(taskId, Action.OPEN); injector.controlListener(taskId, Action.CLOSE); ``` ## 限制 ⚠️ - 目前不支持 `iframe` 注入。在当前架构中,`iframe` 内部的样式注入和生命周期管理尚未完全处理。 - 从性能角度来看,每个注入的组件都会创建一个独立的 Vue 实例。在大批量注入场景中,响应式系统和虚拟 DOM 可能会引入额外的开销。 ## 常见问题 ❓ ### 1) 我可以在 `run()` 之后调用 `register` / `registerListener` 吗? 可以。`run()` 之后允许新的注册。再次调用 `run()` 以激活新注册的任务。现有的活动/待处理任务将被跳过。 ### 2) 重复的 `registerListener` 会抛出错误吗? 不会。重新注册相同的 `listenAt + event` 会发出警告并返回相同的 `taskId`。 ### 3) `scope` 应该使用 `local` 还是 `global`? - `local`:观察范围更小,副作用更低,推荐默认使用。 - `global`:对于本地 DOM 重建场景更稳健,但观察范围更大,性能开销更高。 ### 4) `enableAlive`/`disableAlive` 可以用于纯监听器任务吗? 不可以。对纯监听器任务调用这些 API 会立即返回并发出警告。 ### 5) Pinia 应该使用 `use()` 还是 `setPinia()`? 新代码首选 `use(createPinia())`。`setPinia()` 在 `1.x` 中仍作为兼容性别名受支持,因此现有集成无需立即迁移。 ## 路线图 🛣️ - [x] **重构并解耦 injector 逻辑:** 将注入流程拆分为职责更清晰的小模块。 - [x] **实现简单的日志系统:** 替换模块内的多个 `console` 调用,并通过内置或外部集成的日志模块统一输出日志 - [ ] **实现单 Vue 实例注入模式:** 减少多任务场景中的实例开销,同时允许用户在多实例和单实例模式之间进行选择。 ## 开发 🛠️ **构建:** ``` git clone https://github.com/FlowingInk/vue-implant.git cd vue-implant git switch -c feat/your-feature-name npm install npm run build ``` **运行演示 app:** ``` npm run demo:dev ``` **测试:** ``` npm run test ``` **格式化:** ``` npm run lint:fix ``` ## 贡献 🤝 欢迎提交 Issues 和 PRs。让我们一起改进 `vue-implant`。 ## 许可证 📄 本项目基于 MIT 许可证授权。详见 [LICENSE](LICENSE)。
标签:CMS安全, DNS解析, DOM 操作, Greasemonkey, JavaScript, LangChain, NPM 包, Tampermonkey, Userscript, Vite, vite-plugin-monkey, Vue 3, Web 开发, 声明式, 开源项目, 油猴脚本, 浏览器脚本, 组件注入, 自动化攻击, 轻量级