vite-plugin-monkey

vite-plugin-monkey

Vite插件助力用户脚本开发 支持多引擎及热更新

vite-plugin-monkey是一款为Tampermonkey、Violentmonkey等用户脚本引擎提供开发支持的Vite插件。该插件实现了自动注入用户脚本注释、热模块替换、外部CDN资源注入和GM API的ESM导入等功能。它能够智能收集使用的GM API并自动配置@grant注释,同时支持顶级await和动态导入。通过提供完整的TypeScript支持和Vite特性,vite-plugin-monkey简化了用户脚本的开发流程。

vite-plugin-monkey用户脚本Vite插件构建工具JavaScriptGithub开源项目

vite-plugin-monkey

<p> <a href="https://www.npmjs.com/package/vite-plugin-monkey"><img src="https://yellow-cdn.veclightyear.com/835a84d5/f109d1b9-87fd-47e9-ae0e-b47473d25aea.svg" alt="npm包"></a> <a href="https://github.com/lisonge/vite-plugin-monkey/releases/"><img src="https://yellow-cdn.veclightyear.com/835a84d5/9e97bedc-de41-4eba-ae04-361294800820.svg" alt="节点兼容性"></a> </p>

英文文档 | 中文文档

一个用于为用户脚本引擎(如TampermonkeyViolentmonkeyGreasemonkeyScriptCat)开发和构建你的user.js的vite插件。

特性

  • 支持Tampermonkey、Violentmonkey、Greasemonkey、ScriptCat等
  • 将用户脚本注释注入到构建包中
  • 当用户脚本发生变化时自动在默认浏览器中打开*.user.js
  • 将外部CDN URL注入到用户脚本的@require中
  • 将外部模块注入到用户脚本的@resource中
  • 通过ESM导入使用GM_api,并带有类型提示
  • 智能收集使用的GM_api并自动配置用户脚本的@grant注释
  • 在单个文件中支持"顶级await"和"动态导入"
  • 在vite预览时,自动打开浏览器安装dist.user.js
  • 完全支持TypeScript和vite特性

快速开始

就像使用vite创建一样

pnpm create monkey # npm create monkey # yarn create monkey

然后你可以选择以下模板

JavaScriptTypeScript
empty (仅js)empty-ts (仅ts)
vanilla (js + css)vanilla-ts (ts + css)
vuevue-ts
reactreact-ts
preactpreact-ts
sveltesvelte-ts
solidsolid-ts
<details open> <summary>示例:初始化模板</summary>

vue-ts

</details> <details open> <summary>示例:热模块替换</summary>

hmr

</details> <details open> <summary>示例:构建和预览</summary>

build&preview

</details>

安装

pnpm add -D vite-plugin-monkey # npm i -D vite-plugin-monkey # yarn add -D vite-plugin-monkey

注意:vite-plugin-monkey必须是插件列表中的最后一项

graph LR; A(你的代码) -- "其他插件/vite构建" -->B(esm) B -- "vite-plugin-monkey/vite构建库模式" --> C{有动态导入?} C -- --> D(systemjs) C -- --> E(iife)

配置

<!-- template-start-MonkeyOption -->

MonkeyOption

<details open> <summary>MonkeyOption类型</summary>
export type MonkeyOption = { /** * 用户脚本入口文件路径 */ entry: string; userscript?: MonkeyUserScript; format?: Format; /** * vite-plugin-monkey/dist/client的别名 * @default '$' * @example * // vite-env.d.ts 用于类型提示 * * // 如果你使用默认值 `$` * /// <reference types="vite-plugin-monkey/client" /> * * // 如果你使用其他别名 * declare module other_alias { * export * from 'vite-plugin-monkey/dist/client'; * } */ clientAlias?: string; server?: { /** * 当用户脚本注释改变时,自动在默认浏览器中打开安装URL * * 并设置 `viteConfig.server.open ??= monkeyConfig.server.open` * @default * process.platform == 'win32' || process.platform == 'darwin' // 如果平台是Win/Mac */ open?: boolean; /** * 名称前缀,用于在猴子扩展安装列表中区分server.user.js和build.user.js,如果你不想要前缀,设置为false * @default 'server:' */ prefix?: string | ((name: string) => string) | false; /** * 将GM_api挂载到unsafeWindow,不推荐这样做,你应该通过ESM导入使用GM_api,或使用[unplugin-auto-import](https://github.com/antfu/unplugin-auto-import) * @default false * @example * // 如果设置为true,你可以使用`vite-plugin-monkey/global`进行类型提示 * // vite-env.d.ts * /// <reference types="vite-plugin-monkey/global" /> */ mountGmApi?: boolean; }; build?: { /** * 构建的用户脚本文件名 * * 应以'.user.js'结尾 * @default (package.json.name??'monkey')+'.user.js' */ fileName?: string; /** * 构建的用户脚本注释文件名,此文件仅包含注释 * * 可用于userscript.updateURL,检查更新时只需下载这个小文件,而不是下载整个脚本 * * 应以'.meta.js'结尾,如果设置为false,则不生成此文件 * * 如果设置为true,将等同于fileName.replace(/\\.user\\.js$/,'.meta.js') * * @default false */ metaFileName?: string | boolean | ((fileName: string) => string);

/**

  • 此配置可以是数组或对象,数组=Object.entries(对象)
  • 如果值是字符串或函数,它或其返回值就是exportVarName
  • 如果值是数组,第一个[项或其返回值]是exportVarName,之后的项都是[require url]
  • 如果模块未导入,插件不会将require url添加到userscript中
  • @example
  • { // 映射结构
  • vue:'Vue',
  • // 如果设置这个
  • // 当vite build时,你需要手动设置userscript.require = ['https://unpkg.com/vue@3.0.0/dist/vue.global.js']
  • vuex:['Vuex', (version, name)=>https://unpkg.com/${name}@${version}/dist/vuex.global.js],
  • // 插件会自动将此url添加到userscript.require中
  • 'prettier/parser-babel': [
  • 'prettierPlugins.babel',
  • (version, name, importName) => {
  •  // name == `prettier`
    
  •  // importName == `prettier/parser-babel`
    
  •  const subpath = `${importName.split('/').at(-1)}.js`;
    
  •  return `https://cdn.jsdelivr.net/npm/${name}@${version}/${subpath}`;
    
  • },
  • ],
  • // 有时importName与包名不同
  • }
  • @example
  • [ // 数组结构,此示例来自 playground/ex-vue-demi
  • [
  • 'vue',
    
  • cdn
    
  •   .jsdelivr('Vue', 'dist/vue.global.prod.js')
    
  •   .concat('https://unpkg.com/vue-demi@latest/lib/index.iife.js')
    
  •   .concat(
    
  •     await util.fn2dataUrl(() => {
    
  •       window.Vue = Vue;
    
  •     }),
    
  •   ),
    
  • ],
  • ['pinia', cdn.jsdelivr('Pinia', 'dist/pinia.iife.prod.js')],
  • [
  • 'element-plus',
    
  • cdn.jsdelivr('ElementPlus', 'dist/index.full.min.js'),
    
  • ],
  • ] */ externalGlobals?: ExternalGlobals;

/**

  • 根据最终代码包,自动注入GM_*或GM.*到userscript注释grant中
  • 对代码进行树摇,然后如果code.includes('GM_xxx'),就添加@grant GM_xxx到userscript中
  • @default true */ autoGrant?: boolean;

/**

/**

  • @example
  • { // resourceName默认值是pkg.importName
  • 'element-plus/dist/index.css': pkg=>https://unpkg.com/${pkg.name}@${pkg.version}/${pkg.resolveName},
  • 'element-plus/dist/index.css': {
  • resourceName: pkg=>pkg.importName,
    
  • resourceUrl: pkg=>`https://unpkg.com/${pkg.name}@${pkg.version}/${pkg.resolveName}`,
    
  • loader: pkg=>{ // 有默认加载器支持[css, json, vite支持的资源, ?url, ?raw]文件/名称后缀
    
  •    const css = GM_getResourceText(pkg.resourceName);
    
  •    GM_addStyle(css);
    
  •    return css;
    
  • },
    
  • nodeLoader: pkg=>{
    
  •    return [
    
  •      `export default (()=>{`,
    
  •      `const css = GM_getResourceText(${JSON.stringify(pkg.resourceName)});`,
    
  •      `GM_addStyle(css);`,
    
  •      `return css;`,
    
  •      `})();`
    
  •    ].join('');
    
  • },
    
  • },
  • 'element-plus/dist/index.css': [
  •  (version, name, importName, resolveName)=>importName,
    
  •  (version, name, importName, resolveName)=>`https://unpkg.com/${name}@${version}/${resolveName}`,
    
  •   // 为了兼容externalGlobals cdn函数,如果(version/name/importName/resolveName) == '',插件将使用它们自己的默认值
    
  • ],
  • 'element-plus/dist/index.css': cdn.jsdelivr(),
  • } */ externalResource?: ExternalResource;

/**

  • 使用动态导入时,插件将使用systemjs构建你的代码
  • cdn.jsdelivr()[1] 示例 -> dynamic-import.user.js
  • 'inline' 示例 -> test-v3.user.js
  • @default
  • cdn.jsdelivr()[1] */ systemjs?: 'inline' | ModuleToUrlFc;

/**

  • @default
  • const defaultFc = () => {
  • return (e: string) => {
  • if (typeof GM_addStyle == 'function') {
    
  •   GM_addStyle(e);
    
  •   return;
    
  • }
    
  • const o = document.createElement('style');
    
  • o.textContent = e;
    
  • document.head.append(o);
    
  • };
  • };
  • @example
  • const defaultFc1 = () => {
  • return (e: string) => {
  • const o = document.createElement('style');
    
  • o.textContent = e;
    
  • document.head.append(o);
    
  • };
  • };
  • const defaultFc2 = (css:string)=>{
  • const t = JSON.stringify(css)
  • return (e=>{const o=document.createElement("style");o.textContent=e,document.head.append(o)})(${t})
  • } */ cssSideEffects?: ( css: string, ) => IPromise<string | ((css: string) => void)>; }; };

用于外部资源的CDN工具

import { defineConfig } from 'vite'; import monkey, { cdn } from 'vite-plugin-monkey'; export default defineConfig({ plugins: [ monkey({ build: { externalGlobals: { react: cdn.jsdelivr('React', 'umd/react.production.min.js'), }, externalResource: { 'element-plus/dist/index.css': cdn.jsdelivr(), }, }, }), ], });

可以使用以下CDN,完整详情见cdn.ts

如果你想使用其他CDN,可以参考external-scripts

压缩

由于greasyfork的代码规则

发布到Greasy Fork的代码不得被混淆或压缩

因此插件会将viteConfig.build.minify的默认值更改为false

如果你想启用压缩,只需设置viteConfig.build.minify=true

GM_api使用

ESM使用

我们可以通过esm模块使用GM_api

// main.ts import { GM_cookie, unsafeWindow, monkeyWindow, GM_addElement } from '$'; // $是vite-plugin-monkey/dist/client的默认别名 // 如果你想使用'others',设置monkeyConfig.clientAlias='others' // 无论是serve还是build模式,monkeyWindow始终是[UserScript Scope]的window console.log(monkeyWindow); GM_addElement(document.body, 'div', { innerHTML: 'hello' }); // 无论是serve还是build模式,unsafeWindow始终是宿主window if (unsafeWindow == window) { console.log('scope->host, host esm scope'); } else { console.log('scope->monkey, userscript scope'); } GM_cookie.list({}, (cookies, error) => { if (error) { console.log(error); } else { const [cookie] = cookies; if (cookie) { console.log(cookie); } } });

全局变量使用

设置 monkeyConfig.server.mountGmApi=true

// vite.config.ts import { defineConfig } from 'vite'; import monkey from 'vite-plugin-monkey'; export default defineConfig({ plugins: [ monkey({ // ... server: { mountGmApi: true }, }), ], });

GM_api 将挂载到 host window/globalThis 的属性上

// main.ts console.log(GM_cookie == globalThis.GM_cookie); console.log({ GM_cookie, unsafeWindow, monkeyWindow, GM_addElement });

自动导入用法

使用 unplugin-auto-import

// vite.config.ts import { defineConfig } from 'vite'; import monkey, { util } from 'vite-plugin-monkey'; import AutoImport from 'unplugin-auto-import/vite'; export default defineConfig({ plugins: [ AutoImport({ imports: [util.unimportPreset], }), monkey({ // ... }), ], });
// main.ts // 自动导入示例 console.log({ GM_cookie, unsafeWindow, monkeyWindow, GM_addElement });

示例

测试示例,请参见 /playground

以及 preact/react/svelte/vanilla/vue/solid 示例,请参见 create-monkey

一些注意事项

与其他插件一起使用

插件将通过 generateBundle 钩子重新构建你的代码

请确保插件的顺序是最后一个

CSP

vite serve 模式下,代码入口会作为脚本添加到目标主机的 document.head 中,代码需要在两个源之间工作

但是浏览器会根据 CSP 策略阻止这个脚本的执行

目前只能使用浏览器扩展 Disable-CSP

@require 中混合使用 IIFE 和 UMD

来自 iife-cdn 的通过 var 声明的变量在 monkeyWindow 作用域中不会成为 window 的属性,因为 monkeyWindow 作用域不是全局作用域

所以如果一个 umd 库依赖于一个 iife 库,比如 element-plus 依赖于 vueelement-plus cdn 将无法正常工作

详情请参见 issues/5greasyfork#1084

解决方案是在 iife-cdn 之后附加一个数据 URL 脚本,该脚本将 iife-变量设置为 window 的属性

import { defineConfig } from 'vite'; import monkey, { cdn, util } from 'vite-plugin-monkey'; export default defineConfig(async ({ command, mode }) => ({ plugins: [ monkey({ // ... build: { externalGlobals: { vue: cdn .jsdelivr('Vue', 'dist/vue.global.prod.js') .concat(util.dataUrl(';window.Vue=Vue;')), 'element-plus': cdn.jsdelivr('ElementPlus', 'dist/index.full.min.js'), }, }, }), ], }));

兼容性处理

当插件与 vite legacy 一起使用时,需要设置 renderLegacyChunks=false

// vite.config.ts import legacy from '@vitejs/plugin-legacy'; import { defineConfig } from 'vite'; import monkey from 'vite-plugin-monkey'; export default defineConfig({ plugins: [ legacy({ renderLegacyChunks: false, modernPolyfills: true, }), monkey({ entry: './src/main.ts', }), ], });

如何正确地使用 GM_api 构建库

如果你想封装 GM_api 来构建一个供他人使用的库

之前的做法通常是在库代码中直接将 GM_api 作为全局变量访问,然后在用户脚本中通过 @require 引用和加载。

然而,这种方法不允许我们通过 npm 或其他包管理器来管理这个依赖,也不兼容 vite-plugin-monkey 中 ESM GM_api 的使用方式。

现在,你只需要在库代码中正常从 vite-plugin-monkey/dist/client 导入 GM_api。修改你的构建配置,排除 vite-plugin-monkey/dist/client

这样,你就可以构建一个可以在 vite-plugin-monkey 中使用的库。这个库的使用者只需要通过 npm 安装它,然后正常使用 import 即可。

当然,如果你直接将 vite-plugin-monkey/dist/client 打包到构建产物中,那么这个库也可以直接通过 @require 引用。

但是为了让构建产物更加精简,建议你在构建时将 vite-plugin-monkey/dist/client 重定向到 vite-plugin-monkey/dist/native

下面是一个使用 tsup 同时打包 ESM 和 IIFE 格式的示例。ESM 提供给 vite-plugin-monkey 用户使用,IIFE 提供给想通过 @require 引用的用户使用。

另外,IIFE 格式还可以作为 vite-plugin-monkey 的 externalGlobals 的配置,以减小构建产物的大小。

// /src/index.ts import { GM_setValue } from 'vite-plugin-monkey/dist/client'; export const setValue = (name: string, value: unknown) => { console.log('你调用了 setValue', name, value); GM_setValue(name, value); };
// tsup.config.ts import { defineConfig } from 'tsup'; const outExtension = (ctx: { format: 'esm' | 'cjs' | 'iife' }) => ({ js: { esm: '.mjs', cjs: '.cjs', iife: '.iife.js' }[ctx.format], }); export default defineConfig([ { // 用于 vite 导入 entry: ['src/index.ts'], outDir: 'dist', sourcemap: true, platform: 'browser', outExtension, dts: true, format: ['esm'], external: ['vite-plugin-monkey/dist/client'], }, { // 用于用户脚本 @require entry: ['src/index.ts'], outDir: 'dist', sourcemap: true, platform: 'browser', outExtension, dts: false, format: ['iife'], minify: true, globalName: `GmExtra`, target: 'es2015', esbuildOptions: (options) => { options.alias = { 'vite-plugin-monkey/dist/client': 'vite-plugin-monkey/dist/native', }; }, }, ]);

贡献

请将你的更改提交到 dev 分支

编辑推荐精选

Trae

Trae

字节跳动发布的AI编程神器IDE

Trae是一种自适应的集成开发环境(IDE),通过自动化和多元协作改变开发流程。利用Trae,团队能够更快速、精确地编写和部署代码,从而提高编程效率和项目交付速度。Trae具备上下文感知和代码自动完成功能,是提升开发效率的理想工具。

热门AI工具生产力协作转型TraeAI IDE
问小白

问小白

全能AI智能助手,随时解答生活与工作的多样问题

问小白,由元石科技研发的AI智能助手,快速准确地解答各种生活和工作问题,包括但不限于搜索、规划和社交互动,帮助用户在日常生活中提高效率,轻松管理个人事务。

聊天机器人AI助手热门AI工具AI对话
Transly

Transly

实时语音翻译/同声传译工具

Transly是一个多场景的AI大语言模型驱动的同声传译、专业翻译助手,它拥有超精准的音频识别翻译能力,几乎零延迟的使用体验和支持多国语言可以让你带它走遍全球,无论你是留学生、商务人士、韩剧美剧爱好者,还是出国游玩、多国会议、跨国追星等等,都可以满足你所有需要同传的场景需求,线上线下通用,扫除语言障碍,让全世界的语言交流不再有国界。

讯飞智文

讯飞智文

一键生成PPT和Word,让学习生活更轻松

讯飞智文是一个利用 AI 技术的项目,能够帮助用户生成 PPT 以及各类文档。无论是商业领域的市场分析报告、年度目标制定,还是学生群体的职业生涯规划、实习避坑指南,亦或是活动策划、旅游攻略等内容,它都能提供支持,帮助用户精准表达,轻松呈现各种信息。

热门AI工具AI办公办公工具讯飞智文AI在线生成PPTAI撰写助手多语种文档生成AI自动配图
讯飞星火

讯飞星火

深度推理能力全新升级,全面对标OpenAI o1

科大讯飞的星火大模型,支持语言理解、知识问答和文本创作等多功能,适用于多种文件和业务场景,提升办公和日常生活的效率。讯飞星火是一个提供丰富智能服务的平台,涵盖科技资讯、图像创作、写作辅助、编程解答、科研文献解读等功能,能为不同需求的用户提供便捷高效的帮助,助力用户轻松获取信息、解决问题,满足多样化使用场景。

模型训练热门AI工具内容创作智能问答AI开发讯飞星火大模型多语种支持智慧生活
Spark-TTS

Spark-TTS

一种基于大语言模型的高效单流解耦语音令牌文本到语音合成模型

Spark-TTS 是一个基于 PyTorch 的开源文本到语音合成项目,由多个知名机构联合参与。该项目提供了高效的 LLM(大语言模型)驱动的语音合成方案,支持语音克隆和语音创建功能,可通过命令行界面(CLI)和 Web UI 两种方式使用。用户可以根据需求调整语音的性别、音高、速度等参数,生成高质量的语音。该项目适用于多种场景,如有声读物制作、智能语音助手开发等。

咔片PPT

咔片PPT

AI助力,做PPT更简单!

咔片是一款轻量化在线演示设计工具,借助 AI 技术,实现从内容生成到智能设计的一站式 PPT 制作服务。支持多种文档格式导入生成 PPT,提供海量模板、智能美化、素材替换等功能,适用于销售、教师、学生等各类人群,能高效制作出高品质 PPT,满足不同场景演示需求。

讯飞绘文

讯飞绘文

选题、配图、成文,一站式创作,让内容运营更高效

讯飞绘文,一个AI集成平台,支持写作、选题、配图、排版和发布。高效生成适用于各类媒体的定制内容,加速品牌传播,提升内容营销效果。

AI助手热门AI工具AI创作AI辅助写作讯飞绘文内容运营个性化文章多平台分发
材料星

材料星

专业的AI公文写作平台,公文写作神器

AI 材料星,专业的 AI 公文写作辅助平台,为体制内工作人员提供高效的公文写作解决方案。拥有海量公文文库、9 大核心 AI 功能,支持 30 + 文稿类型生成,助力快速完成领导讲话、工作总结、述职报告等材料,提升办公效率,是体制打工人的得力写作神器。

openai-agents-python

openai-agents-python

OpenAI Agents SDK,助力开发者便捷使用 OpenAI 相关功能。

openai-agents-python 是 OpenAI 推出的一款强大 Python SDK,它为开发者提供了与 OpenAI 模型交互的高效工具,支持工具调用、结果处理、追踪等功能,涵盖多种应用场景,如研究助手、财务研究等,能显著提升开发效率,让开发者更轻松地利用 OpenAI 的技术优势。

下拉加载更多