FlowingInk/vue-implant
GitHub: FlowingInk/vue-implant
vue-implant 是一个为油猴脚本和浏览器扩展设计的 Vue 3 组件注入框架,提供生命周期管理、DOM 检测和自动重注入能力。
Stars: 4 | Forks: 0
# vue-implant
[](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 开发, 内容脚本, 前端开发工具, 动态渲染, 安全插件, 数据可视化, 暗色界面, 油猴脚本, 浏览器扩展开发, 浏览器插件, 生命周期管理, 用户脚本, 第三方页面增强, 组件注入, 自动化攻击, 自动重注入, 页面劫持