FlowingInk/vue-implant

GitHub: FlowingInk/vue-implant

vue-implant 是一个为油猴脚本和浏览器扩展设计的 Vue 3 组件注入框架,提供生命周期管理、DOM 检测和自动重注入能力。

Stars: 4 | Forks: 0

# vue-implant [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE) [🇨🇳 中文文档](README.CN.md) `vue-implant` 是一个专为不可控页面环境(如 userscript 和浏览器扩展)设计的 Vue 组件注入框架。 它提供组件生命周期管理、DOM 等待与目标检测,以及重新注入能力,帮助你可靠地增强第三方页面,而无需频繁处理复杂的原生 DOM API。 它适用于页面结构不稳定、异步渲染频繁且需要长期运行注入的场景。 ## 目录 📚 - [演示](#demo) - [安装](#installation) - [快速开始](#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 ``` ## 快速开始 ⚡ ``` 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` - 运行环境:现代浏览器页面环境(例如 userscript、浏览器扩展 content script) - iframe:目前不支持 ## API 🧩 ### `new Injector(config?: Partial)` 🏗️ 创建一个 `Injector` 实例。 ``` type InjectionConfig = { alive?: boolean; scope?: 'local' | 'global'; timeout?: number; }; ``` | 属性 | 类型 | 描述 | 默认值 | | --- | --- | --- | --- | | `alive` | `boolean` | 是否启用全局重新注入。 | `false` | | `scope` | `'local' \| 'global'` | `local` 将监听器绑定到目标元素的父级;`global` 将监听器挂载到 `body`,因此在局部 DOM 重建时监听器仍可保持活动。 | `'local'` | | `timeout` | `number` | 初始注入和重新注入的超时阈值(毫秒)。不建议将其显式设置为 `undefined`。 | `5000` | ### `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.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 observer。 - 调用一次 context 级别的完整重置,以清理每个任务的运行时字段。 - 在 context 中保留任务注册和任务 ID。 ### `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 会立即返回并发出警告。 ## 路线图 🛣️ - [ ] **重构并解耦 injector 逻辑:** 将注入流程拆分为职责更清晰的更小模块。 - [ ] **实现单 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 ``` **运行演示应用:** ``` npm run demo:dev ``` **测试:** ``` npm run test ``` **格式化:** ``` npm run lint:fix ``` ## 贡献 🤝 欢迎提交 Issue 和 PR。让我们一起完善 `vue-implant`。 ## 许可证 📄 本项目基于 MIT 许可证授权。详情请参见 [LICENSE](LICENSE)。
标签:DOM 检测, DOM 监听, JavaScript 库, SPA 注入, TypeScript, Vue 3, Web 开发, 内容脚本, 前端开发工具, 动态渲染, 安全插件, 数据可视化, 暗色界面, 油猴脚本, 浏览器扩展开发, 浏览器插件, 生命周期管理, 用户脚本, 第三方页面增强, 组件注入, 自动化攻击, 自动重注入, 页面劫持