基于Vite与TypeScript的油猴脚本工程化开发实战
1. 项目概述:一个浏览器脚本的“瑞士军刀”启动器
如果你经常在浏览器里折腾,想给各种网页加上自己的“魔法”,比如让ChatGPT的网页版用起来更顺手,或者想自动化一些重复的网页操作,那你大概率听说过油猴脚本。但很多时候,我们找到的脚本要么功能太单一,要么配置起来很麻烦,想自己动手改改,又觉得从零开始写一个油猴脚本的架子太费劲。今天要聊的这个项目,KudoAI/chatgpt.js-greasemonkey-starter,就是来解决这个痛点的。它不是一个成品脚本,而是一个功能强大、开箱即用的油猴脚本开发模板,或者说是一个“启动器”。
简单来说,这个项目为你搭建好了一个现代化的油猴脚本开发环境。它内置了与ChatGPT网页版深度集成的核心库chatgpt.js,提供了实时热重载、TypeScript支持、代码风格检查、自动构建和发布等一整套开发工具链。你不再需要从零开始配置构建工具、处理模块依赖,或者手动管理脚本的元信息。这个启动器让你可以专注于脚本的核心逻辑,快速实现诸如“一键总结长文章”、“批量导出对话历史”、“自定义快捷指令”等功能,并且能享受到现代前端开发的流畅体验。
它适合谁呢?首先,是那些想为ChatGPT网页版开发增强功能,但又不想重复造轮子的开发者。其次,是任何希望快速启动一个高质量、可维护的油猴脚本项目的爱好者。即使你对构建工具不太熟悉,这个项目预设的配置也能让你轻松上手。接下来,我会带你深入拆解这个启动器的设计思路、核心功能,并分享如何用它从零开始打造一个属于自己的网页增强脚本。
2. 核心架构与设计思路拆解
2.1 为什么需要这样一个“启动器”?
传统的油猴脚本开发,通常是在油猴插件的编辑器里直接写一个巨大的.user.js文件。这种方式在脚本简单时没问题,但一旦功能复杂,就会暴露出很多问题:代码难以组织和维护、缺乏现代语法特性(如ES模块、TypeScript)、调试困难、没有版本管理和构建流程。chatgpt.js-greasemonkey-starter的出现,正是为了将油猴脚本开发带入工程化时代。
它的核心设计思路是“约定大于配置”和“开箱即用”。项目作者预判了开发一个与ChatGPT交互的脚本所需的常见需求:需要一个强大的底层API库来可靠地操作ChatGPT的DOM和状态(chatgpt.js),需要一个高效的本地开发环境(热重载),需要类型安全来提高开发效率(TypeScript),需要代码质量保障(ESLint/Prettier),以及需要一键打包发布的能力。这个启动器把这些分散的、需要手动集成的环节,通过一套预设的构建配置(基于Vite)完美地串联了起来。
注意:这里说的“工程化”,并不是要把它搞得很复杂,而是通过工具让开发变得更简单、更可靠。就像你用现成的面团做面包,总比自己从种小麦开始要快得多、好得多。
2.2 技术栈选型背后的逻辑
让我们看看这个项目都用了哪些技术,以及为什么选它们:
- Vite:作为构建工具。Vite以极快的冷启动和热更新速度著称,这对于需要频繁修改并实时在浏览器中查看效果的脚本开发来说,是至关重要的体验提升。它原生支持ES模块,能完美处理油猴脚本所需的依赖打包。
- TypeScript:作为开发语言。操作网页DOM,尤其是像ChatGPT这样结构可能变化的复杂应用,类型提示能极大减少错误,提高代码智能补全能力,让开发过程更顺畅。
- chatgpt.js:作为核心运行时库。这是同一个作者维护的另一个明星项目,它封装了与ChatGPT网页版交互的所有复杂细节,提供了稳定、高层的API。使用它,你不需要再去研究ChatGPT前端代码的DOM结构变化,直接调用诸如
sendMessage,getConversationHistory这样的方法即可,大大降低了开发门槛和脚本的脆弱性。 - ESLint + Prettier:代码检查和格式化工具。保证团队协作或个人长期维护时代码风格的一致性,提前发现潜在错误。
@grant和@require的自动化管理:油猴脚本头部的元数据(如@grant GM_xmlhttpRequest)和外部库依赖声明,在构建过程中会自动从代码分析和package.json中提取并注入到最终的用户脚本中,开发者无需手动维护,避免了遗漏。
这个技术栈组合,瞄准的就是“开发体验”和“脚本质量”这两个核心目标。它不是最炫技的堆砌,而是每一个选择都切实解决了油猴脚本开发中的一个具体痛点。
3. 环境准备与项目初始化实操
3.1 前置条件检查
在开始之前,你需要确保本地环境已经准备好:
- Node.js:版本建议在 16 或 18 以上。你可以通过在终端运行
node -v来检查。 - 包管理器:
npm或yarn或pnpm。项目通常使用npm,但pnpm因其速度快和磁盘效率高,是很多现代项目的推荐选择。你可以根据喜好安装。 - 浏览器与油猴插件:任何支持用户脚本的浏览器(Chrome, Firefox, Edge等),并安装好 Tampermonkey 或 Violentmonkey 插件。这是脚本最终运行的环境。
3.2 克隆与安装
打开你的终端,执行以下步骤:
# 1. 克隆项目到本地 git clone https://github.com/KudoAI/chatgpt.js-greasemonkey-starter.git # 或者使用 GitHub CLI # gh repo clone KudoAI/chatgpt.js-greasemonkey-starter # 2. 进入项目目录 cd chatgpt.js-greasemonkey-starter # 3. 安装项目依赖 # 使用 npm npm install # 或使用 pnpm (推荐,更快) pnpm install安装过程会下载 Vite、TypeScript、chatgpt.js 以及其他所有开发依赖。这个过程通常很快,取决于你的网络速度。
3.3 项目结构初探
安装完成后,用你喜欢的代码编辑器(如 VS Code)打开项目文件夹。你会看到类似如下的结构:
chatgpt.js-greasemonkey-starter/ ├── src/ │ ├── main.ts # 脚本的主入口文件,你的核心逻辑从这里开始写 │ └── vite-env.d.ts # Vite 环境类型声明 ├── dist/ # 构建后生成的目录,最终的 `.user.js` 文件在这里 ├── package.json # 项目配置和依赖声明 ├── vite.config.ts # Vite 构建配置文件 ├── tsconfig.json # TypeScript 配置文件 ├── .eslintrc.json # ESLint 代码检查规则 ├── .prettierrc # Prettier 代码格式化规则 └── README.md # 项目说明文档对于初学者,你最需要关注的是src/main.ts文件。这是你编写脚本功能的地方。vite.config.ts包含了构建为油猴脚本的所有魔法,初期一般不需要修改。
4. 核心开发流程详解
4.1 理解入口文件:src/main.ts
让我们打开src/main.ts,看看它的初始内容。它通常包含以下几个关键部分:
- 导入
chatgpt.js:import { ChatGPTAPI } from 'chatgpt.js';这行代码引入了核心库。 - 定义主函数:一个
main()函数,这是脚本执行的起点。 - 使用
chatgpt.jsAPI:在main()函数内,会实例化ChatGPTAPI,并调用其方法。初始模板可能会有一个简单的示例,比如等待ChatGPT界面加载完成,然后在某个位置添加一个按钮。 - 执行与错误处理:调用
main()函数,并用try...catch包裹进行错误处理。
一个典型的开发模式是:你在main()函数里,使用chatgpt.js提供的 API 来监听页面事件、查询DOM元素、插入你的自定义UI组件(如按钮、面板),并定义这些组件被点击或交互时要执行的操作(例如,调用api.sendMessage发送特定指令)。
4.2 启动开发服务器与热重载
这是本启动器最爽的功能之一。在项目根目录的终端中,运行:
npm run dev # 或 pnpm dev这个命令会启动 Vite 开发服务器,并开始监听src/目录下的文件变化。同时,它会在dist文件夹中生成一个*.user.js文件(例如chatgpt.js-greasemonkey-starter.user.js)。
接下来,你需要做一件至关重要的事情:在浏览器中打开 ChatGPT 的网页,然后打开油猴插件(Tampermonkey)的管理面板,点击“添加新脚本”。不要从头写,而是点击“文件”菜单或类似选项,选择“从磁盘导入...”,然后导航到你项目下的dist目录,选择那个刚刚生成的.user.js文件并导入。
导入成功后,确保该脚本是启用状态。现在,刷新 ChatGPT 页面。你的脚本应该已经生效了(初始模板可能会添加一个不起眼的测试按钮)。
魔法时刻:现在,回到你的代码编辑器,修改src/main.ts文件,比如改变一个按钮的文字。保存文件后,你会立刻在终端看到 Vite 重新构建的提示。无需手动刷新浏览器,只需切回 ChatGPT 页面,稍等一秒(或者看到页面轻微闪动),你的修改就已经实时生效了!这个热重载功能极大地提升了开发调试效率。
4.3 编写你的第一个功能:添加一个“总结”按钮
假设我们想实现一个经典功能:在ChatGPT输入框旁添加一个“总结”按钮,点击后自动向ChatGPT发送“请总结上面的对话内容”的指令。
首先,我们需要了解chatgpt.js的一些基本API。查看其文档或类型提示,我们会找到与输入框和发送消息相关的方法。
在src/main.ts的main()函数中,我们可以这样写:
import { ChatGPTAPI } from 'chatgpt.js'; async function main() { // 等待ChatGPT页面核心组件加载就绪 const api = new ChatGPTAPI(); await api.waitForReady(); // 获取聊天输入框的容器,通常我们可以在其附近添加按钮 const inputContainer = await api.getInputContainer(); if (!inputContainer) { console.warn('未找到输入框容器'); return; } // 创建我们的“总结”按钮 const summarizeButton = document.createElement('button'); summarizeButton.textContent = '📝 总结'; summarizeButton.style.marginLeft = '10px'; summarizeButton.style.padding = '8px 12px'; summarizeButton.style.borderRadius = '6px'; summarizeButton.style.border = '1px solid #ccc'; summarizeButton.style.background = '#f7f7f8'; summarizeButton.style.cursor = 'pointer'; // 为按钮添加点击事件 summarizeButton.addEventListener('click', async () => { // 在发送前,可以给用户一个反馈,比如禁用按钮或改变文字 summarizeButton.textContent = '总结中...'; summarizeButton.disabled = true; try { // 使用 chatgpt.js 的API发送一个预设的提示词 await api.sendMessage('请总结上面的对话内容。'); } catch (error) { console.error('发送总结请求失败:', error); // 可以在这里添加用户提示,例如一个Toast通知 } finally { // 无论成功失败,恢复按钮状态 summarizeButton.textContent = '📝 总结'; summarizeButton.disabled = false; } }); // 将按钮插入到输入框容器的后面(或其他你觉得合适的位置) inputContainer.parentNode?.insertBefore(summarizeButton, inputContainer.nextSibling); console.log('“总结”按钮已添加!'); } // 执行主函数,并捕获可能出现的错误 main().catch(console.error);保存文件。由于开发服务器正在运行,你的改动会立刻生效。刷新ChatGPT页面,你应该能在输入框附近看到一个崭新的“总结”按钮。点击它,ChatGPT就会收到你的指令并开始工作。
实操心得:在插入自定义UI元素时,CSS样式管理是个小挑战。直接写内联样式(
style.xxx)对于简单按钮可行,但组件复杂后会很乱。一个更好的实践是:在src/目录下创建一个style.css文件,编写你的样式,然后在main.ts中通过import './style.css';引入。Vite会自动处理CSS的打包和注入。这样代码更清晰,也便于维护。
5. 构建、发布与脚本管理
5.1 生产环境构建
当你完成了脚本功能的开发,并经过充分测试后,就需要构建一个用于分发的版本。运行:
npm run build # 或 pnpm build这个命令会启动 Vite 的生产模式构建。它与开发模式 (dev) 的关键区别在于:
- 代码压缩混淆:生成的
.user.js文件会被最小化和混淆,减小体积,保护源代码逻辑。 - 移除开发调试代码:一些仅在开发时使用的代码会被剔除。
- 生成Source Map(可选):如果配置了,会生成
.map文件,便于线上调试(但发布时通常不包含)。
构建完成后,最终的脚本文件同样位于dist/目录下。这个文件就是你可以分享给他人,或发布到 Greasy Fork、OpenUserJS 等脚本平台的成品。
5.2 脚本元信息与发布
油猴脚本文件头部有一段特殊的注释,称为“元数据块”,它定义了脚本的名称、版本、描述、匹配的网址、授权权限等。在这个启动器中,这部分信息主要由vite.config.ts中的GreasemonkeyPlugin配置来自动生成和管理。
关键的元信息通常在package.json和vite.config.ts中定义。例如,脚本的@name,@version,@description通常会从package.json的对应字段读取。而@match(指定脚本在哪些网址上运行) 则在vite.config.ts的插件配置里设置。
发布前检查清单:
- 更新版本号:在
package.json中修改version字段。遵循语义化版本控制(如从1.0.0到1.0.1)。 - 检查匹配规则:在
vite.config.ts中确认@match或@include规则是否正确覆盖了你的目标网站(例如https://chat.openai.com/*)。 - 完善描述:更新
package.json中的description和vite.config.ts中更详细的描述,让用户明白脚本的用途。 - 构建:运行
pnpm build生成最终文件。 - 手动验证:用文本编辑器打开
dist/下的.user.js文件,检查头部的元信息是否正确无误。 - 发布:将构建出的
.user.js文件内容复制,到 Greasy Fork 等平台创建/更新你的脚本。
5.3 版本控制与协作
由于这是一个标准的 Node.js 项目,你可以完美地使用 Git 进行版本控制。将node_modules和dist目录添加到.gitignore中,只提交源代码和配置文件。这样,你的团队可以协作开发,清晰地跟踪每一次功能添加和Bug修复。
6. 进阶技巧与深度定制
6.1 利用chatgpt.js的全部能力
chatgpt.js库非常强大,远不止发送消息。深入其文档,你可以发现更多宝藏功能:
- 对话历史管理:
api.getConversationHistory()可以获取当前会话的所有消息,用于本地分析或导出。 - 监听事件:库可能提供了消息接收、发送完成等事件监听器,让你能在更精确的时机执行操作。
- UI组件操作:除了输入框,还可以获取消息流容器、侧边栏等,用于创建更复杂的交互界面,比如一个常驻的侧边工具面板。
6.2 处理动态内容与页面导航
ChatGPT 是一个单页应用(SPA),在对话间切换或刷新时,URL可能不变,但DOM会更新。你的脚本需要能应对这种动态性。chatgpt.js的waitForReady()方法已经帮我们处理了初始加载。但对于后续的页面状态变化,你可能需要:
- 使用 MutationObserver:监听特定DOM子树的变化,当检测到输入框区域重新渲染时,重新执行你的UI注入逻辑。
- 合理设置脚本执行时机:油猴元数据中的
@run-at可以设置为document-idle,确保页面基本加载完毕后再执行你的脚本。
6.3 与后端服务通信
如果你的脚本需要从外部API获取数据(例如,从你自己的服务器获取配置,或者将处理后的对话内容发送到笔记软件),你需要使用油猴提供的GM_xmlhttpRequestAPI。这个启动器已经自动处理了@grant GM_xmlhttpRequest的声明。你可以在代码中直接使用:
// 注意:这是一个示例,实际使用需要更完善的错误处理和类型定义 GM_xmlhttpRequest({ method: 'POST', url: 'https://your-api.com/endpoint', data: JSON.stringify({ content: '要发送的数据' }), headers: { 'Content-Type': 'application/json' }, onload: function(response) { console.log('API响应:', response.responseText); // 处理响应数据 }, onerror: function(error) { console.error('API请求失败:', error); } });重要提示:由于跨域限制,普通网页脚本无法直接请求任意API。但油猴脚本通过
GM_xmlhttpRequest拥有更高的权限,可以跨域请求。但这同时也带来了安全风险,请确保你请求的是可信的端点。
6.4 配置化与用户设置
一个成熟的脚本应该允许用户进行一些自定义配置。你可以:
- 使用
GM_getValue/GM_setValue:油猴提供的存储API,适合保存简单的键值对配置(如开关状态、API密钥)。 - 创建配置面板:在脚本中注入一个设置图标,点击后弹出一个模态框(Modal),里面包含表单元素(输入框、下拉菜单、开关等),让用户进行配置。配置保存时,调用
GM_setValue存储。 - 提供默认值:在脚本初始化时,用
GM_getValue读取配置,如果没有则使用硬编码的默认值。
7. 常见问题排查与调试心得
即使有了强大的启动器,开发过程中仍会遇到各种问题。这里记录一些常见坑点和解决思路。
7.1 脚本未运行或按钮未出现
- 检查油猴插件是否启用:确保Tampermonkey图标是彩色的,并且你的脚本处于“已启用”状态。
- 检查匹配网址:确认脚本的
@match规则包含了当前浏览的ChatGPT网址。有时https://chat.openai.com/*和https://chat.openai.com/有细微差别。 - 查看浏览器控制台:按 F12 打开开发者工具,切换到“控制台”标签页。查看是否有红色的报错信息。启动器生成的脚本会将其
console.log输出到这里,这是最重要的调试信息源。常见的错误可能是chatgpt.js库未加载(检查网络),或你的代码有语法错误。 - 检查脚本安装方式:开发时务必使用“从磁盘导入”的方式安装
dist/下的脚本,而不是手动创建。确保你导入的是开发服务器生成的文件。
7.2 热重载失效
- 确认开发服务器在运行:终端里
pnpm dev的命令是否还在运行,且没有报错退出。 - 检查油猴脚本更新:有时油猴插件不会自动检测磁盘文件的变更。你可以尝试在油猴插件的管理面板中,找到你的脚本,点击其旁边的“检查更新”按钮。
- 手动刷新页面:如果热重载不生效,最直接的方式还是手动刷新浏览器页面。
7.3chatgpt.jsAPI 调用失败
- 等待页面就绪:确保在调用任何操作DOM的API前,已经
await api.waitForReady()。ChatGPT页面加载需要时间。 - API 变更:ChatGPT的网页前端结构可能会更新,导致
chatgpt.js的某些底层选择器失效。关注chatgpt.js库的更新日志,及时升级项目依赖 (pnpm update chatgpt.js)。 - 错误处理:用
try...catch包裹你的核心逻辑,并在catch块中给出友好的用户提示或记录详细的错误信息到控制台。
7.4 构建失败
- TypeScript 类型错误:运行
pnpm build前,可以先运行pnpm type-check(如果配置了)或tsc --noEmit来检查类型错误。 - 依赖缺失:删除
node_modules和package-lock.json(或pnpm-lock.yaml),重新运行pnpm install。 - 检查配置文件:对比项目原始仓库的
vite.config.ts,看是否有不小心改错的地方。
7.5 样式冲突或布局错乱
- CSS 作用域:你注入的样式可能会影响网页原有样式,或被网页样式覆盖。尽量使用特定的类名,并增加CSS选择器的特异性。例如,为你创建的按钮添加一个独特的前缀类名
.my-script-summarize-btn,然后所有样式都基于这个类名来写。 - 使用 Shadow DOM(高级):为了彻底隔离样式,可以考虑将你的UI组件封装到一个Shadow DOM中。但这会带来事件处理的复杂性,需酌情使用。
开发浏览器脚本,尤其是针对复杂SPA的脚本,耐心和细致的调试是关键。充分利用浏览器开发者工具的“元素检查器”和“控制台”,它们是你最好的朋友。这个启动器提供的现代化工具链,已经将开发过程中的大量摩擦点消除了,让你能更专注于创造有价值的功能本身。
