vue-implant/vue-implant
GitHub: vue-implant/vue-implant
一个轻量级 Vue 3 组件注入框架,简化油猴脚本开发中的 DOM 挂载与生命周期管理。
Stars: 10 | Forks: 1
Vue-implant
一个轻量级的 Vue 组件注入框架
`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 开发, 声明式, 开源项目, 油猴脚本, 浏览器脚本, 组件注入, 自动化攻击, 轻量级