vite plugin server and build *.user.js for Tampermonkey and Violentmonkey and Greasemonkey
- support Tampermonkey and Violentmonkey and Greasemonkey
- inject userscript comment to build bundle
- auto open *.user.js in default browser when userscript change
- external cdn url inject to userscript @require
- use GM_api by ESM import with type hints
- when vite preview, auto open browser install dist.user.js
- full typescript support and vite feature
just like vite create
pnpm create monkey
# npm create monkey
# yarn create monkey
then you can choose the following template
JavaScript | TypeSript |
---|---|
empty (only js) | empty-ts (only ts) |
vanilla (js + css) | vanilla-ts (ts + css) |
vue | vue-ts |
react | react-ts |
preact | preact-ts |
svelte | svelte-ts |
solid | solid-ts |
pnpm add -D vite-plugin-monkey
# npm i -D vite-plugin-monkey
# yarn add -D vite-plugin-monkey
export type MonkeyOption = {
/**
* userscript entry file path
*/
entry: string;
userscript: MonkeyUserScript;
format?: Format;
/**
* alias of vite-plugin-monkey/dist/client
* @default '$'
* @example
* // vite.config.ts, plugin will auto modify config
* resolve: {
* alias: {
* [clientAlias]: 'vite-plugin-monkey/dist/client',
* },
* }
* @example
* // vite-env.d.ts, you must manual modify .env file
* declare module clientAlias {
* export * from 'vite-plugin-monkey/dist/client';
* }
*/
clientAlias?: string;
server?: {
/**
* auto open *.user.js in default browser when userscript comment change or vite server first start.
* if you don't want to open when vite server first start, just want to open when userscript comment change, you should set viteConfig.server.open=false
* @default true
*/
open?: boolean;
/**
* name prefix, distinguish server.user.js and build.user.js in monkey extension install list, if you not want prefix, set false
* @default 'dev:'
*/
prefix?: string | ((name: string) => string) | false;
/**
* mount GM_api to unsafeWindow, not recommend it, you should use GM_api by ESM import
* @default false
* @example
* // if set true, you can export all from vite-plugin-monkey/dist/client to global
* // vite-env.d.ts
* type MonkeyWindow = import('vite-plugin-monkey/dist/client').MonkeyWindow;
* declare const unsafeWindow: MonkeyWindow['unsafeWindow'];
* declare const GM_addStyle: MonkeyWindow['GM_addStyle'];
* declare const GM_addElement: MonkeyWindow['GM_addElement'];
* declare const GM_deleteValue: MonkeyWindow['GM_deleteValue'];
* declare const GM_listValues: MonkeyWindow['GM_listValues'];
* declare const GM_addValueChangeListener: MonkeyWindow['GM_addValueChangeListener'];
* declare const GM_removeValueChangeListener: MonkeyWindow['GM_removeValueChangeListener'];
* declare const GM_setValue: MonkeyWindow['GM_setValue'];
* declare const GM_getValue: MonkeyWindow['GM_getValue'];
* declare const GM_log: MonkeyWindow['GM_log'];
* declare const GM_getResourceText: MonkeyWindow['GM_getResourceText'];
* declare const GM_getResourceURL: MonkeyWindow['GM_getResourceURL'];
* declare const GM_registerMenuCommand: MonkeyWindow['GM_registerMenuCommand'];
* declare const GM_unregisterMenuCommand: MonkeyWindow['GM_unregisterMenuCommand'];
* declare const GM_openInTab: MonkeyWindow['GM_openInTab'];
* declare const GM_xmlhttpRequest: MonkeyWindow['GM_xmlhttpRequest'];
* declare const GM_download: MonkeyWindow['GM_download'];
* declare const GM_getTab: MonkeyWindow['GM_getTab'];
* declare const GM_saveTab: MonkeyWindow['GM_saveTab'];
* declare const GM_getTabs: MonkeyWindow['GM_getTabs'];
* declare const GM_notification: MonkeyWindow['GM_notification'];
* declare const GM_setClipboard: MonkeyWindow['GM_setClipboard'];
* declare const GM_info: MonkeyWindow['GM_info'];
* declare const GM_cookie: MonkeyWindow['GM_cookie'];
*/
mountGmApi?: boolean;
};
build?: {
/**
* build bundle userscript file name
*
* it should end with '.user.js'
* @default (package.json.name??'monkey')+'.user.js'
*/
fileName?: string;
/**
* build bundle userscript comment file name, this file is only include comment
*
* it can be used by userscript.updateURL, when checking for updates, just download this small file instead of downloading the entire script
*
* it should end with '.meta.js', if set false, will not generate this file
*
* if set true, will equal to fileName.replace(/\\.user\\.js$/,'.meta.js')
* @default false
*/
metaFileName?: string | boolean;
/**
* if value is string or function, it or its return value is exportVarName
*
* if value is Array, the first [item or its return value] is exportVarName, the items after it all are url that is [require url]
*
* @example
* {
* vue:'Vue',
* // youe need manually set userscript.require = ['https://unpkg.com/vue@3.0.0/dist/vue.global.js'], when command=='build'
*
* vuex:['Vuex', (version, name)=>`https://unpkg.com/${name}@${version}/dist/vuex.global.js`],
* // plugin will auto add this url to userscript.require
*
* 'prettier/parser-babel': [
* 'prettierPlugins.babel',
* (version, name, moduleName) => {
* // name == `prettier`
* // moduleName == `prettier/parser-babel`
* const subpath = `${moduleName.split('/').at(-1)}.js`;
* return `https://cdn.jsdelivr.net/npm/${name}@${version}/${subpath}`;
* },
* ],
* // sometimes moduleName deffers from package name
* }
*/
externalGlobals?: Record<
string,
string | [string, ...(string | Lib2Url)[]]
>;
/**
* according to final code bundle, auto inject GM_* or GM.* to userscript comment grant
*
* the judgment is based on String.prototype.includes
* @default true
*/
autoGrant?: boolean;
/**
* check all require urls for availability, just http code is 2xx, never check http.body
* @default false
*/
checkCDN?: boolean;
/**
* if you want minify all, just set viteConfig.build.minify=true
* @default true
*/
minifyCss?: boolean;
};
};
/**
* UserScript, merge metadata from Greasemonkey, Tampermonkey, Violentmonkey, Greasyfork
*/
export type MonkeyUserScript = GreasemonkeyUserScript &
TampermonkeyUserScript &
ViolentmonkeyUserScript &
GreasyforkUserScript &
MergemonkeyUserScript;
- GreasemonkeyUserScript
- TampermonkeyUserScript
- ViolentmonkeyUserScript
- GreasyforkUserScript
- MergemonkeyUserScript
/**
* format userscript comment
*/
export type Format = {
/**
* @description note font_width/font_family, suggest fixed-width font
* @default 2, true
*/
align?: number | boolean | AlignFunc;
};
export type AlignFunc = (
p0: [string, ...string[]][],
) => [string, ...string[]][];
// use example
import { cdn } from 'vite-plugin-monkey';
{
externalGlobals: {
'blueimp-md5': cdn.bytecdntp('md5', 'js/md5.min.js'),
},
}
there is the following cdn to use, full detail see cdn.ts
- jsdelivr https://www.jsdelivr.com/
- unpkg https://unpkg.com/
- bytecdntp https://cdn.bytedance.com/
- bootcdn https://www.bootcdn.cn/all/
- baomitu https://cdn.baomitu.com/
- staticfile https://staticfile.org/
- cdnjs https://cdnjs.com/libraries
- zhimg https://unpkg.zhimg.com/
if you want use other cdn, you can see external-scripts
we can use GM_api by esm module
import { GM_cookie, unsafeWindow, monkeyWindow, GM_addElement } from '$';
// $ is the alias of vite-plugin-monkey/dist/client, you can use others
// whatever it is serve or build mode, monkeyWindow is always the window of [UserScript Scope]
console.log(monkeyWindow);
GM_addElement(document.body, 'div', { innerHTML: 'hello' });
// whatever it is serve or build mode, unsafeWindow is always host window
if (unsafeWindow == window) {
console.log('scope->host, esm mode');
} else {
console.log('scope->monkey, iife mode');
}
GM_cookie.list({}, (cookies, error) => {
if (error) {
console.log(error);
} else {
const [cookie] = cookies;
if (cookie) {
console.log(cookie);
}
}
});
vite config is simple, see vite.config.ts, build file see example-project.user.js
and preact/react/svelte/vanilla/vue examples see create-monkey
you can use Tampermonkey then open extension://iikmkjmpaadaobahmlepeloendndfphd/options.html#nav=settings
at Security
, set Modify existing content security policy (CSP) headers
to Remove entirely (possibly unsecure)
full detail see issues/1
and if you use Violentmonkey
/Greasemonkey
, you can solve it in the following ways
- chrome - Disable Content-Security-Policy
- edge - Disable Content-Security-Policy
- firefox - disable
security.csp.enable
in theabout:config
menu
because of vite/issues/1639, now you can not use @vitejs/plugin-legacy
the following is a feasible solution by @require cdn
import { defineConfig } from 'vite';
import monkeyPlugin from 'vite-plugin-monkey';
export default defineConfig({
plugins: [
monkeyPlugin({
userscript: {
require: [
// polyfill all
'https://cdn.jsdelivr.net/npm/core-js-bundle@latest/minified.js',
// or use polyfill.io
// https://polyfill.io/v3/polyfill.min.js
],
},
}),
],
});