vite-plugin-monkey
Version:
A vite plugin server and build your.user.js for userscript engine like Tampermonkey and Violentmonkey and Greasemonkey
1,739 lines (1,713 loc) • 68.7 kB
JavaScript
var __defProp = Object.defineProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
// src/node/plugins/buildBundle.ts
import { build } from "vite";
// src/node/utils/gmApi.ts
var gmIdentifiers = [
"GM_addElement",
"GM_addStyle",
"GM_addValueChangeListener",
"GM_cookie",
"GM_deleteValue",
"GM_deleteValues",
"GM_download",
"GM_getResourceText",
"GM_getResourceURL",
"GM_getTab",
"GM_getTabs",
"GM_getValue",
"GM_getValues",
"GM_info",
"GM_listValues",
"GM_log",
"GM_notification",
"GM_openInTab",
"GM_registerMenuCommand",
"GM_removeValueChangeListener",
"GM_saveTab",
"GM_setClipboard",
"GM_setValue",
"GM_setValues",
"GM_unregisterMenuCommand",
"GM_webRequest",
"GM_xmlhttpRequest"
];
var gmMembers = [
"GM.addElement",
"GM.addStyle",
"GM.addValueChangeListener",
"GM.cookie",
"GM.deleteValue",
"GM.deleteValues",
"GM.download",
"GM.getResourceText",
// https://www.tampermonkey.net/documentation.php#api:GM_getResourceURL
"GM.getResourceUrl",
"GM.getTab",
"GM.getTabs",
"GM.getValue",
"GM.getValues",
"GM.info",
"GM.listValues",
"GM.log",
"GM.notification",
"GM.openInTab",
"GM.registerMenuCommand",
"GM.removeValueChangeListener",
"GM.saveTab",
"GM.setClipboard",
"GM.setValue",
"GM.setValues",
"GM.unregisterMenuCommand",
"GM.webRequest",
"GM.xmlHttpRequest"
];
var othersGrantNames = [
"unsafeWindow",
"window.close",
"window.focus",
"window.onurlchange"
];
var grantNames = [...gmMembers, ...gmIdentifiers, ...othersGrantNames];
// src/node/userscript/index.ts
var finalMonkeyOptionToComment = async (option, collectGrantSet, mode) => {
const { userscript, collectRequireUrls, collectResource } = option;
let attrList = [];
const {
name,
namespace,
version,
author,
description,
license,
copyright,
icon,
iconURL,
icon64,
icon64URL,
defaulticon,
homepage,
homepageURL,
website,
source,
supportURL,
downloadURL,
updateURL,
include,
match,
exclude,
require: require2,
"exclude-match": excludeMatch,
"inject-into": injectInto,
"run-at": runAt,
compatible,
incompatible,
antifeature,
contributionAmount,
contributionURL,
connect,
sandbox,
tag,
resource,
grant,
noframes,
unwrap,
webRequest,
$extra
} = userscript;
Object.entries({
namespace,
version,
author,
license,
copyright,
icon,
iconURL,
icon64,
icon64URL,
defaulticon,
homepage,
homepageURL,
website,
source,
supportURL,
downloadURL,
updateURL,
"inject-into": injectInto,
"run-at": runAt,
compatible,
incompatible,
contributionAmount,
contributionURL,
sandbox
}).forEach(([k, v]) => {
if (typeof v == "string") {
attrList.push([k, v]);
}
});
Object.entries(name).forEach(([k, v]) => {
if (k == "") {
attrList.push(["name", v]);
} else {
attrList.push(["name:" + k, v]);
}
});
Object.entries(description).forEach(([k, v]) => {
if (k == "") {
attrList.push(["description", v]);
} else {
attrList.push(["description:" + k, v]);
}
});
Object.entries({
include,
match,
exclude,
"exclude-match": excludeMatch
}).forEach(([k, v]) => {
v.forEach((v2) => {
attrList.push([k, v2]);
});
});
[...require2, ...collectRequireUrls].forEach((s) => {
attrList.push(["require", s]);
});
Object.entries({ ...resource, ...collectResource }).forEach(([k, v]) => {
attrList.push(["resource", k, v]);
});
connect.forEach((s) => {
attrList.push(["connect", s]);
});
tag.forEach((s) => {
attrList.push(["tag", s]);
});
webRequest.forEach((s) => {
attrList.push(["webRequest", s]);
});
if (grant.has("none")) {
attrList.push(["grant", "none"]);
} else if (grant.has("*")) {
grantNames.forEach((s) => {
attrList.push(["grant", s]);
});
} else {
(/* @__PURE__ */ new Set([...Array.from(collectGrantSet.values()).flat(), ...grant])).forEach(
(s) => {
if (!s.trim()) return;
attrList.push(["grant", s]);
}
);
}
antifeature.forEach(({ description: description2, type, tag: tag2 }) => {
attrList.push([
tag2 ? `antifeature:${tag2}` : "antifeature",
type,
description2
]);
});
if (noframes) {
attrList.push(["noframes"]);
}
if (unwrap) {
attrList.push(["unwrap"]);
}
attrList.push(...$extra);
attrList = defaultSortFormat(attrList);
if (option.align >= 1) {
const formatKey = (subAttrList) => {
if (subAttrList.length == 0) return;
const maxLen2 = Math.max(...subAttrList.map((s) => s[1].length));
subAttrList.forEach((s) => {
s[1] = s[1].padEnd(option.align + maxLen2);
});
};
formatKey(attrList.filter((s) => s[0] == "resource"));
formatKey(
attrList.filter(
(s) => s[0] == "antifeature" || s[0].startsWith("antifeature:")
)
);
const maxLen = Math.max(...attrList.map((s) => s[0].length));
attrList.forEach((s) => {
s[0] = s[0].padEnd(option.align + maxLen);
});
}
const uString = [
"==UserScript==",
...attrList.map(
(attr) => "@" + attr.map((v) => {
return v.endsWith(" ") ? v : v + " ";
}).join("").trimEnd()
),
"==/UserScript=="
].map((s) => "// " + s).join("\n");
return option.generate({ userscript: uString, mode });
};
var stringSort = (a, b) => {
const minLen = Math.min(a.length, b.length);
for (let i = 0; i < minLen; i++) {
if (a[i] > b[i]) {
return 1;
} else if (a[i] < b[i]) {
return -1;
}
}
if (a.length > b.length) {
return 1;
} else if (a.length < b.length) {
return -1;
}
return 0;
};
var defaultSortFormat = (p0) => {
const filter = (predicate) => {
const notMatchList = [];
const matchList = [];
p0.forEach((value, index) => {
if (!predicate(value, index)) {
notMatchList.push(value);
} else {
matchList.push(value);
}
});
p0 = notMatchList;
return matchList;
};
return [
filter(([k]) => k == "name"),
filter(([k]) => k.startsWith("name:")),
filter(([k]) => k == "namespace"),
filter(([k]) => k == "version"),
filter(([k]) => k == "author"),
filter(([k]) => k == "description"),
filter(([k]) => k.startsWith("description:")),
filter(([k]) => k == "license"),
filter(([k]) => k == "copyright"),
filter(([k]) => k == "icon"),
filter(([k]) => k == "iconURL"),
filter(([k]) => k == "icon64"),
filter(([k]) => k == "icon64URL"),
filter(([k]) => k == "defaulticon"),
filter(([k]) => k == "homepage"),
filter(([k]) => k == "homepageURL"),
filter(([k]) => k == "website"),
filter(([k]) => k == "source"),
filter(([k]) => k == "supportURL"),
filter(([k]) => k == "downloadURL"),
filter(([k]) => k == "updateURL"),
filter(([k]) => k == "include"),
filter(([k]) => k == "match"),
filter(([k]) => k == "exclude"),
filter(([k]) => k == "exclude-match"),
filter(([k]) => k == "webRequest"),
filter(([k]) => k == "require"),
filter(([k]) => k == "resource").sort(stringSort),
filter(([k]) => k == "sandbox"),
filter(([k]) => k == "tag"),
filter(([k]) => k == "connect"),
filter(([k]) => k == "grant").sort(stringSort),
filter(([k]) => k == "inject-into"),
filter(([k]) => k == "run-at"),
filter(([k]) => k == "compatible"),
filter(([k]) => k == "incompatible"),
filter(([k]) => k == "antifeature").sort(stringSort),
filter(([k]) => k.startsWith("antifeature:")).sort(stringSort),
filter(([k]) => k == "contributionAmount"),
filter(([k]) => k == "contributionURL"),
filter(([k]) => k == "noframes"),
filter(([k]) => k == "unwrap"),
p0
].flat(1);
};
// src/node/utils/grant.ts
import * as acornWalk from "acorn-walk";
var collectGrant = (context, chunks, injectCssCode, minify) => {
const codes = /* @__PURE__ */ new Set();
if (injectCssCode) {
codes.add(injectCssCode);
}
for (const chunk of chunks) {
if (minify) {
const modules = Object.values(chunk.modules);
modules.forEach((m) => {
const code = m.code;
if (code) {
codes.add(code);
}
});
}
codes.add(chunk.code);
}
const unusedMembers = new Set(
grantNames.filter((s) => s.includes(`.`))
);
const endsWithWin = (a, b) => {
if (a.endsWith(b)) {
return a === "monkeyWindow." + b || a === "_monkeyWindow." + b;
}
return false;
};
const memberHandleMap = Object.fromEntries(
grantNames.filter((s) => s.startsWith("window.")).map((name) => [name, (v) => endsWithWin(v, name.split(".")[1])])
);
const unusedIdentifiers = new Set(
grantNames.filter((s) => !s.includes(`.`))
);
const usedGm = /* @__PURE__ */ new Set();
const matchIdentifier = (name) => {
if (unusedIdentifiers.has(name)) {
usedGm.add(name);
unusedIdentifiers.delete(name);
return true;
}
return false;
};
const matchMember = (name) => {
for (const unusedName of unusedMembers.values()) {
if (name.endsWith(unusedName) || memberHandleMap[unusedName]?.(name)) {
usedGm.add(unusedName);
unusedMembers.delete(unusedName);
return true;
}
}
return false;
};
for (const code of codes) {
if (!code.trim()) continue;
const ast = context.parse(code);
acornWalk.simple(
ast,
{
MemberExpression(node) {
if (unusedMembers.size === 0) return;
if (node.computed || node.object.type !== "Identifier" || node.property.type !== "Identifier") {
return;
}
if (node.object.name === "monkeyWindow" || node.object.name === "_monkeyWindow") {
if (matchIdentifier(node.property.name)) {
return;
}
}
const name = node.object.name + "." + node.property.name;
matchMember(name);
},
Identifier(node) {
matchIdentifier(node.name);
}
},
{ ...acornWalk.base }
);
if (unusedMembers.size == 0 && unusedIdentifiers.size == 0) {
break;
}
}
return usedGm;
};
// src/node/utils/others.ts
import { resolve } from "import-meta-resolve";
import fs from "fs/promises";
import path from "path";
import { pathToFileURL } from "url";
import { transformWithEsbuild } from "vite";
import { DomUtils, ElementType, parseDocument } from "htmlparser2";
import crypto from "crypto";
var isFirstBoot = () => {
return (Reflect.get(globalThis, "__vite_start_time") ?? 0) < 1e3;
};
var compatResolve = (id) => {
return resolve(id, pathToFileURL(process.cwd() + "/any.js").href);
};
var existFile = async (path6) => {
try {
return (await fs.stat(path6)).isFile();
} catch {
return false;
}
};
var miniCode = async (code, type = "js") => {
return (await transformWithEsbuild(code, "any_name." + type, {
minify: true,
sourcemap: false,
legalComments: "none"
})).code.trimEnd();
};
var moduleExportExpressionWrapper = (expression) => {
let n = 0;
let identifier = ``;
while (expression.includes(identifier)) {
identifier = `_${(n || ``).toString(16)}`;
n++;
}
return `(()=>{const ${identifier}=${expression};('default' in ${identifier})||(${identifier}.default=${identifier});return ${identifier}})()`;
};
async function* walk(dirPath) {
const pathnames = (await fs.readdir(dirPath)).map(
(s) => path.join(dirPath, s)
);
while (pathnames.length > 0) {
const pathname = pathnames.pop();
const state = await fs.lstat(pathname);
if (state.isFile()) {
yield pathname;
} else if (state.isDirectory()) {
pathnames.push(
...(await fs.readdir(pathname)).map((s) => path.join(pathname, s))
);
}
}
}
var getInjectCssCode = async (option, bundle) => {
const cssTexts = [];
Object.entries(bundle).forEach(([k, v]) => {
if (v.type == "asset" && k.endsWith(".css")) {
cssTexts.push(v.source.toString());
delete bundle[k];
}
});
const css = cssTexts.join("").trim();
if (css) {
return await option.cssSideEffects(` ` + css + ` `);
}
};
var stringifyFunction = (fn, ...args) => {
return `;(${fn})(${args.map((v) => JSON.stringify(v)).join(",")});`;
};
var dataJsUrl = (code) => {
return "data:application/javascript," + encodeURIComponent(code);
};
function dataUrl(p0, ...args) {
if (typeof p0 == "string") {
return dataJsUrl(p0);
}
return miniCode(stringifyFunction(p0, ...args)).then(dataJsUrl);
}
var parserHtmlScriptResult = (html) => {
const doc = parseDocument(html);
const scripts = DomUtils.getElementsByTagType(
ElementType.Script,
doc
);
return scripts.map((p) => {
const src = p.attribs.src ?? "";
const textNode = p.firstChild;
let text = "";
if (textNode?.type == ElementType.Text) {
text = textNode.data ?? "";
}
if (src) {
return { src, text };
} else {
return {
src: "",
text
};
}
});
};
var simpleHash = (str = "") => {
return crypto.createHash("md5").update(str || "").digest("base64url").substring(0, 8);
};
var safeURL = (url, base3) => {
if (!url) return void 0;
try {
return new URL(url, base3);
} catch {
}
};
// src/node/utils/systemjs.ts
import fs2 from "fs/promises";
import module from "module";
var _require = module.createRequire(import.meta.url);
var systemjsPkg = _require(`systemjs/package.json`);
var systemjsSubPaths = [
"dist/system.min.js",
"dist/extras/named-register.min.js"
];
var customSystemInstanceCode = `;(typeof System!='undefined')&&(System=new System.constructor());`;
var systemjsAbsolutePaths = systemjsSubPaths.map((s) => {
return _require.resolve(`systemjs/` + s);
});
var getSystemjsTexts = async () => {
return Promise.all(
systemjsAbsolutePaths.map(
(s) => fs2.readFile(s, "utf-8").then(
(s2) => s2.trim().replace(/^\/\*[\s\S]*?\*\//, "").replace(/\/\/.*map$/, "").trim()
)
).concat([Promise.resolve(customSystemInstanceCode)])
);
};
var getSystemjsRequireUrls = (fn) => {
return systemjsSubPaths.map((p) => {
return fn(systemjsPkg.version, systemjsPkg.name, p, p);
}).concat([dataUrl(customSystemInstanceCode)]);
};
// src/node/utils/topLevelAwait.ts
import * as acornWalk2 from "acorn-walk";
import MagicString from "magic-string";
var awaitOffset = `await`.length;
var initTlaIdentifier = `_TLA_`;
var getSafeTlaIdentifier = (rawBundle) => {
const codes = [];
for (const chunk of Object.values(rawBundle)) {
if (chunk.type == "chunk") {
codes.push(chunk.code);
}
}
let x = 0;
let identifier = initTlaIdentifier;
while (codes.some((code) => code.includes(identifier))) {
x++;
identifier = initTlaIdentifier + x.toString(36);
}
return identifier;
};
var startWith = (text, searchString, position = 0, ignoreString) => {
for (let i = position; i < text.length; i++) {
if (ignoreString.includes(text[i])) {
continue;
}
return text.startsWith(searchString, i);
}
return false;
};
var includes = (str, start, end, substr) => {
const i = str.indexOf(substr, start);
return i >= 0 && i + substr.length < end;
};
var transformTlaToIdentifier = (context, chunk, identifier) => {
if (chunk.type == "chunk") {
const code = chunk.code;
if (!code.includes(`await`)) {
return;
}
const ast = context.parse(code);
const tlaNodes = [];
const tlaForOfNodes = [];
acornWalk2.simple(
ast,
{
AwaitExpression(node) {
tlaNodes.push(node);
},
ForOfStatement(node) {
if (node.await === true) {
tlaForOfNodes.push(node);
}
}
},
{ ...acornWalk2.base, Function: () => {
} }
);
if (tlaNodes.length > 0 || tlaForOfNodes.length > 0) {
const ms = new MagicString(code);
tlaNodes.forEach((node) => {
if (!startWith(chunk.code, "(", node.start + awaitOffset, " \r\n")) {
ms.appendLeft(node.start + awaitOffset, `(`);
ms.appendRight(node.end, `)`);
}
ms.update(node.start, node.start + awaitOffset, identifier);
});
tlaForOfNodes.forEach((node) => {
ms.appendLeft(node.start, `${identifier + `FOR`}((async()=>{`);
ms.appendRight(node.end, `})());`);
});
return {
code: ms.toString(),
map: ms.generateMap()
};
}
}
};
var transformIdentifierToTla = (context, chunk, identifier) => {
if (chunk.type == "chunk") {
if (!chunk.code.includes(identifier)) {
return;
}
const forIdentifier = identifier + `FOR`;
const ast = context.parse(chunk.code);
const tlaCallNodes = [];
const forTlaCallNodes = [];
const topFnNodes = [];
acornWalk2.simple(
ast,
{
CallExpression(node) {
if ("name" in node.callee) {
const { name, type } = node.callee;
if (type === `Identifier`) {
if (name === identifier) {
tlaCallNodes.push({ ...node, callee: node.callee });
} else if (name === forIdentifier) {
forTlaCallNodes.push({ ...node, callee: node.callee });
}
}
}
}
},
{
...acornWalk2.base,
Function: (node, state, callback) => {
if (topFnNodes.length == 0) {
topFnNodes.push(node);
}
if (includes(chunk.code, node.start, node.end, identifier)) {
return acornWalk2.base.Function?.(node, state, callback);
}
}
}
);
if (tlaCallNodes.length > 0 || forTlaCallNodes.length > 0) {
const ms = new MagicString(chunk.code, {});
tlaCallNodes.forEach((node) => {
const callee = node.callee;
ms.update(callee.start, callee.end, "await");
});
forTlaCallNodes.forEach((node) => {
const forOfNode = node.arguments?.[0]?.callee?.body?.body?.[0];
ms.update(node.start, forOfNode.start, "");
ms.update(forOfNode.end, node.end, "");
});
topFnNodes.forEach((node) => {
ms.appendLeft(node.start, `async `);
});
chunk.code = ms.toString();
}
}
};
// src/node/plugins/buildBundle.ts
var __entry_name = `__monkey.entry.js`;
var polyfillId = "\0vite/legacy-polyfills";
var systemJsImportMapPrefix = `user`;
var buildBundleFactory = (getOption) => {
let option;
let viteConfig;
return {
name: "monkey:buildBundle",
apply: "build",
enforce: "post",
async config() {
option = await getOption();
},
async configResolved(resolvedConfig) {
viteConfig = resolvedConfig;
},
async generateBundle(_, rawBundle) {
const entryChunks = [];
const chunks = [];
Object.values(rawBundle).forEach((chunk) => {
if (chunk.type == "chunk") {
if (chunk.facadeModuleId != polyfillId) {
chunks.push(chunk);
}
if (chunk.isEntry) {
if (chunk.facadeModuleId == polyfillId) {
entryChunks.unshift(chunk);
} else {
entryChunks.push(chunk);
}
}
}
});
const fristEntryChunk = entryChunks.find(
(s) => s.facadeModuleId != polyfillId
);
const hasDynamicImport = entryChunks.some(
(e) => e.dynamicImports.length > 0
);
const usedModules = /* @__PURE__ */ new Set();
const tlaIdentifier = getSafeTlaIdentifier(rawBundle);
const buildResult = await build({
logLevel: "error",
configFile: false,
esbuild: false,
plugins: [
{
name: "monkey:mock",
enforce: "pre",
resolveId(source, importer, options) {
if (!importer && options.isEntry) {
return "\0" + source;
}
const chunk = Object.values(rawBundle).find(
(chunk2) => chunk2.type == "chunk" && source.endsWith(chunk2.fileName)
);
if (chunk) {
return "\0" + source;
}
},
async load(id) {
if (!id.startsWith("\0")) return;
if (id.endsWith(__entry_name)) {
return entryChunks.map((a) => `import ${JSON.stringify(`./${a.fileName}`)};`).join("\n");
}
const [k, chunk] = Object.entries(rawBundle).find(
([_2, chunk2]) => id.endsWith(chunk2.fileName)
) ?? [];
if (chunk && chunk.type == "chunk" && k) {
usedModules.add(k);
if (!hasDynamicImport) {
const ch = transformTlaToIdentifier(
this,
chunk,
tlaIdentifier
);
if (ch) return ch;
}
return {
code: chunk.code,
map: chunk.map
};
}
},
generateBundle(_2, iifeBundle) {
if (hasDynamicImport) {
return;
}
Object.entries(iifeBundle).forEach(([_3, chunk]) => {
transformIdentifierToTla(this, chunk, tlaIdentifier);
});
}
}
],
build: {
write: false,
minify: false,
target: "esnext",
rollupOptions: {
external(source) {
return source in option.globalsPkg2VarName;
},
output: {
globals: option.globalsPkg2VarName
}
},
lib: {
entry: __entry_name,
formats: [hasDynamicImport ? "system" : "iife"],
name: hasDynamicImport ? void 0 : "__expose__",
fileName: () => `__entry.js`
}
}
});
usedModules.forEach((k) => {
if (fristEntryChunk != rawBundle[k]) {
delete rawBundle[k];
}
});
const buildBundle = buildResult[0].output.flat();
let finalJsCode = ``;
if (hasDynamicImport) {
const systemJsModules = [];
let entryName = "";
Object.entries(buildBundle).forEach(([_2, chunk]) => {
if (chunk.type == "chunk") {
const name = JSON.stringify(`./` + chunk.fileName);
systemJsModules.push(
chunk.code.trimStart().replace(/^System\.register\(/, `System.register(${name}, `)
);
if (chunk.isEntry) {
entryName = name;
}
}
});
systemJsModules.push(`System.import(${entryName}, "./");`);
finalJsCode = systemJsModules.join("\n");
const usedModuleIds = Array.from(this.getModuleIds()).filter(
(d) => d in option.globalsPkg2VarName
);
const importsMap = usedModuleIds.reduce(
(p, c) => {
p[c] = `${systemJsImportMapPrefix}:${c}`;
return p;
},
{}
);
finalJsCode = [
Object.keys(importsMap).length > 0 ? `System.addImportMap({ imports: ${JSON.stringify(importsMap)} });` : ``,
...usedModuleIds.map(
(id) => `System.set(${JSON.stringify(
`${systemJsImportMapPrefix}:${id}`
)}, ${moduleExportExpressionWrapper(
option.globalsPkg2VarName[id]
)});`
),
"\n" + finalJsCode
].filter((s) => s).join("\n");
if (typeof option.systemjs == "function") {
option.collectRequireUrls.push(
...getSystemjsRequireUrls(option.systemjs)
);
} else {
finalJsCode = (await getSystemjsTexts()).join("\n") + "\n" + finalJsCode;
}
} else {
Object.entries(buildBundle).forEach(([_2, chunk]) => {
if (chunk.type == "chunk" && chunk.isEntry) {
finalJsCode = chunk.code;
}
});
}
const injectCssCode = await getInjectCssCode(option, rawBundle);
let collectGrantSet;
if (option.build.autoGrant) {
collectGrantSet = collectGrant(
this,
chunks,
injectCssCode,
viteConfig.build.minify !== false
);
} else {
collectGrantSet = /* @__PURE__ */ new Set();
}
const comment = await finalMonkeyOptionToComment(
option,
collectGrantSet,
"build"
);
const mergedCode = [comment, injectCssCode, finalJsCode].filter((s) => s).join(`
`).trimEnd();
if (fristEntryChunk) {
fristEntryChunk.fileName = option.build.fileName;
fristEntryChunk.code = mergedCode;
} else {
this.emitFile({
type: "asset",
fileName: option.build.fileName,
source: mergedCode
});
}
if (option.build.metaFileName) {
this.emitFile({
type: "asset",
fileName: option.build.metaFileName(),
source: await finalMonkeyOptionToComment(
option,
collectGrantSet,
"meta"
)
});
}
}
};
};
// src/node/plugins/config.ts
var configFactory = (getOption) => {
let option;
return {
name: "monkey:config",
async config(userConfig) {
option = await getOption();
return {
resolve: {
alias: {
[option.clientAlias]: "vite-plugin-monkey/dist/client"
}
},
esbuild: {
supported: {
"top-level-await": true
}
},
build: {
assetsInlineLimit: Number.MAX_SAFE_INTEGER,
chunkSizeWarningLimit: Number.MAX_SAFE_INTEGER,
modulePreload: false,
assetsDir: "./",
cssCodeSplit: false,
minify: userConfig.build?.minify ?? false,
cssMinify: userConfig.build?.cssMinify ?? true,
sourcemap: false,
rollupOptions: {
input: option.entry
}
}
};
}
};
};
// src/node/plugins/externalGlobals.ts
import { normalizePath as normalizePath2 } from "vite";
// src/node/utils/pkg.ts
import fs3 from "fs/promises";
import path2 from "path";
import { normalizePath } from "vite";
var getProjectPkg = async () => {
const rawPkg = await fs3.readFile(path2.resolve(process.cwd(), "package.json"), "utf-8").then(JSON.parse).catch(() => {
});
const pkg = {};
if (!rawPkg) return pkg;
Object.entries(rawPkg).forEach(([k, v]) => {
if (typeof v == "string") {
Reflect.set(pkg, k, v);
}
});
if (typeof rawPkg.author === "object" && typeof rawPkg.author?.name == "string") {
pkg.author = rawPkg.author.name;
}
if (typeof rawPkg.bugs === "object" && typeof rawPkg.bugs?.url == "string") {
pkg.bugs = rawPkg.bugs.url;
}
if (typeof rawPkg.repository === "object" && typeof rawPkg.repository?.url == "string") {
const { url } = rawPkg.repository;
if (url.startsWith("http")) {
pkg.repository = url;
} else if (url.startsWith("git+http")) {
pkg.repository = url.substring(4);
}
}
return pkg;
};
var isScopePkg = (name) => name.startsWith("@");
var resolveModuleFromPath = async (subpath) => {
const p = normalizePath(process.cwd()).split("/");
for (let i = p.length; i > 0; i--) {
const p2 = `${p.slice(0, i).join("/")}/node_modules/${subpath}`;
if (await existFile(p2)) {
return p2;
}
}
};
var compatResolveModulePath = async (id) => {
try {
return compatResolve(id);
} catch (e) {
const r = await resolveModuleFromPath(id);
if (!r) {
throw e;
}
return r;
}
};
var getModuleRealInfo = async (importName) => {
const nameNoQuery = normalizePath(importName.split("?")[0]);
const resolveName = await (async () => {
const n = normalizePath(await compatResolveModulePath(nameNoQuery)).replace(
/.*\/node_modules\/[^/]+\//,
""
);
if (isScopePkg(importName)) {
return n.split("/").slice(1).join("/");
}
return n;
})();
let version = void 0;
const nameList = nameNoQuery.split("/");
let name = nameNoQuery;
while (nameList.length > 0) {
name = nameList.join("/");
const filePath = await (async () => {
const p = await resolveModuleFromPath(`${name}/package.json`);
if (p) {
return p;
}
try {
return compatResolve(`${name}/package.json`);
} catch {
return void 0;
}
})();
if (filePath === void 0 || !await existFile(filePath)) {
nameList.pop();
continue;
}
const modulePack = JSON.parse(
await fs3.readFile(filePath, "utf-8")
);
version = modulePack.version;
break;
}
if (version === void 0) {
console.warn(
`[plugin-monkey] not found module ${nameNoQuery} version, use ${nameNoQuery}@latest`
);
name = nameNoQuery;
version = "latest";
}
return { version, name, resolveName };
};
// src/node/plugins/externalGlobals.ts
var externalGlobalsFactory = (getOption) => {
let option;
return {
name: "monkey:externalGlobals",
enforce: "pre",
apply: "build",
async config() {
option = await getOption();
for (const [moduleName, varName2LibUrl] of option.build.externalGlobals) {
const { name, version } = await getModuleRealInfo(moduleName);
if (typeof varName2LibUrl == "string") {
option.globalsPkg2VarName[moduleName] = varName2LibUrl;
} else if (typeof varName2LibUrl == "function") {
option.globalsPkg2VarName[moduleName] = await varName2LibUrl(
version,
name,
moduleName
);
} else if (varName2LibUrl instanceof Array) {
const [varName, ...libUrlList] = varName2LibUrl;
if (typeof varName == "string") {
option.globalsPkg2VarName[moduleName] = varName;
} else if (typeof varName == "function") {
option.globalsPkg2VarName[moduleName] = await varName(
version,
name,
moduleName
);
}
for (const libUrl of libUrlList) {
if (typeof libUrl == "string") {
option.requirePkgList.push({ url: libUrl, moduleName });
} else if (typeof libUrl == "function") {
option.requirePkgList.push({
url: await libUrl(version, name, moduleName),
moduleName
});
}
}
}
}
return {
build: {
rollupOptions: {
external(source, _importer, _isResolved) {
return source in option.globalsPkg2VarName;
}
}
}
};
},
async generateBundle() {
const usedModIdSet = new Set(
Array.from(this.getModuleIds()).map((s) => normalizePath2(s))
);
option.collectRequireUrls = option.requirePkgList.filter((p) => usedModIdSet.has(p.moduleName)).map((p) => p.url);
}
};
};
// src/node/plugins/externalLoader.ts
var cssLoader = (name) => {
const css = GM_getResourceText(name);
GM_addStyle(css);
return css;
};
var jsonLoader = (name) => (
// @ts-ignore
JSON.parse(GM_getResourceText(name))
);
var urlLoader = (name, type) => (
// @ts-ignore
GM_getResourceURL(name, false).replace(
/^data:application;base64,/,
`data:${type};base64,`
)
);
var rawLoader = (name) => (
// @ts-ignore
GM_getResourceText(name)
);
var loaderCode = [
`export const cssLoader = ${cssLoader}`,
`export const jsonLoader = ${jsonLoader}`,
`export const urlLoader = ${urlLoader}`,
`export const rawLoader = ${rawLoader}`
].join(";");
var externalLoaderFactory = () => {
return {
name: "monkey:externalLoader",
apply: "build",
async resolveId(id) {
if (id == "virtual:plugin-monkey-loader") {
return "\0" + id;
}
},
async load(id) {
if (id == "\0virtual:plugin-monkey-loader") {
return miniCode(loaderCode, "js");
}
}
};
};
// src/node/plugins/externalResource.ts
import { normalizePath as normalizePath3 } from "vite";
var resourceImportPrefix = "\0monkey-resource-import:";
var externalResourcePlugin = (getOption) => {
let option;
let viteConfig;
let mrmime;
const resourceRecord = {};
return {
name: "monkey:externalResource",
enforce: "pre",
apply: "build",
async config() {
option = await getOption();
mrmime = await import("mrmime");
},
configResolved(config) {
viteConfig = config;
},
async resolveId(id) {
const { externalResource } = option.build;
if (id in externalResource) {
return resourceImportPrefix + id + "\0";
}
const [resource, query] = id.split("?", 2);
if (resource.endsWith(".css") && query) {
const id2 = [
resource,
"?",
query.split("&").filter((e) => e != "used").join(`&`)
].join("");
if (id2 in externalResource) {
return resourceImportPrefix + id2 + "\0";
}
}
},
async load(id) {
if (id.startsWith(resourceImportPrefix) && id.endsWith("\0")) {
const { externalResource } = option.build;
const importName = id.substring(
resourceImportPrefix.length,
id.length - 1
);
if (!(importName in externalResource)) {
return;
}
const pkg = await getModuleRealInfo(importName);
const {
resourceName: resourceNameFn,
resourceUrl: resourceUrlFn,
loader,
nodeLoader
} = externalResource[importName];
const resourceName = await resourceNameFn({ ...pkg, importName });
const resourceUrl = await resourceUrlFn({ ...pkg, importName });
resourceRecord[importName] = {
resourceName,
resourceUrl
};
if (nodeLoader) {
return miniCode(
await nodeLoader({
...pkg,
resourceName,
resourceUrl,
importName
})
);
} else if (loader) {
let fnText;
if (loader.prototype && // not arrow function
loader.name.length > 0 && loader.name != "function") {
if (Reflect.get(loader, Symbol.toStringTag) == "AsyncFunction") {
fnText = loader.toString().replace(/^[\s\S]+?\(/, "async function(");
} else {
fnText = loader.toString().replace(/^[\s\S]+?\(/, "function(");
}
} else {
fnText = loader.toString();
}
return miniCode(
`export default (${fnText})(${JSON.stringify({
resourceUrl,
importName,
...pkg
})})`
);
}
let moduleCode = void 0;
const [resource, query] = importName.split("?", 2);
const ext = resource.split(".").pop();
const mimeType = mrmime.lookup(ext) ?? "application/octet-stream";
const suffixSet = new URLSearchParams(query);
if (suffixSet.has("url") || suffixSet.has("inline")) {
moduleCode = [
`import {urlLoader as loader} from 'virtual:plugin-monkey-loader'`,
`export default loader(...${JSON.stringify([
resourceName,
mimeType
])})`
].join(";");
} else if (suffixSet.has("raw")) {
moduleCode = [
`import {rawLoader as loader} from 'virtual:plugin-monkey-loader'`,
`export default loader(...${JSON.stringify([resourceName])})`
].join(";");
} else if (ext == "json") {
moduleCode = [
`import {jsonLoader as loader} from 'virtual:plugin-monkey-loader'`,
`export default loader(...${JSON.stringify([resourceName])})`
].join(";");
} else if (ext == "css") {
moduleCode = [
`import {cssLoader as loader} from 'virtual:plugin-monkey-loader'`,
`export default loader(...${JSON.stringify([resourceName])})`
].join(";");
} else if (viteConfig.assetsInclude(importName.split("?", 1)[0])) {
const mediaType = mrmime.mimes[ext];
moduleCode = [
`import {urlLoader as loader} from 'virtual:plugin-monkey-loader'`,
`export default loader(...${JSON.stringify([
resourceName,
mediaType
])})`
].join(";");
}
if (moduleCode) {
if (moduleCode.includes("rawLoader") || moduleCode.includes("jsonLoader") || moduleCode.includes("cssLoader")) {
option.userscript.grant.add("GM_getResourceText");
} else if (moduleCode.includes("urlLoader")) {
option.userscript.grant.add("GM_getResourceURL");
}
return miniCode(moduleCode);
}
throw new Error(`module: ${importName} not found loader`);
}
},
generateBundle() {
const usedModIdSet = new Set(
Array.from(this.getModuleIds()).map((s) => normalizePath3(s))
);
Array.from(usedModIdSet).forEach((id) => {
if (id.startsWith(resourceImportPrefix) && id.endsWith("\0")) {
usedModIdSet.add(
id.substring(resourceImportPrefix.length, id.length - 1)
);
}
});
const collectResource = {};
Object.entries(resourceRecord).forEach(
([importName, { resourceName, resourceUrl }]) => {
if (usedModIdSet.has(importName)) {
collectResource[resourceName] = resourceUrl;
}
}
);
option.collectResource = collectResource;
}
};
};
// src/node/plugins/fixAssetUrl.ts
var fixAssetUrlFactory = () => {
let viteConfig;
return {
name: "monkey:fixAssetUrl",
apply: "serve",
async configResolved(resolvedConfig) {
viteConfig = resolvedConfig;
},
async transform(code, id) {
const [_, query = "url"] = id.split("?", 2);
if ((query.split("&").includes("url") || viteConfig.assetsInclude(id)) && code.match(/^\s*export\s+default/)) {
const ast = this.parse(code);
const defaultNode = ast.body[0];
if (defaultNode?.type == "ExportDefaultDeclaration") {
const childNode = defaultNode?.declaration;
if (childNode?.type == "Literal" && typeof childNode.value == "string" && childNode.value[0] === "/") {
const p0 = JSON.stringify(childNode.value);
return `export default new URL(${p0}, import.meta['url']).href`;
}
}
}
}
};
};
// src/node/plugins/fixClient.ts
var fixClientFactory = () => {
return {
name: "monkey:fixClient",
apply: "serve",
async transform(code, id) {
if (id.endsWith("node_modules/vite/dist/client/client.mjs")) {
return code.replaceAll(
"__BASE__",
`new URL(__BASE__ || '/', import.meta['url']).href`
);
}
}
};
};
// src/node/plugins/fixCssUrl.ts
var fixCssUrlFactory = () => {
return {
name: "monkey:fixCssUrl",
apply: "serve",
async config() {
const postUrl = (await import("postcss-url")).default;
return {
css: {
postcss: {
plugins: [postUrl({ url: "inline" })]
}
}
};
}
};
};
// src/node/plugins/perview.ts
import path3 from "path";
import { normalizePath as normalizePath4 } from "vite";
// src/node/utils/template.ts
var htmlText = (
/* html */
`
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="https://vite.dev/logo.svg" />
<title>Vite</title>
</head>
<script type="module" data-source="vite-plugin-monkey">
__CODE__
</script>
</html>
`.trimStart()
);
var fcToHtml = (fn, ...args) => {
return htmlText.replace(`__CODE__`, stringifyFunction(fn, ...args));
};
var serverInjectFn = (entrySrc) => {
window.GM;
const key = `__monkeyWindow-` + new URL(entrySrc).origin;
document[key] = window;
console.log(`[vite-plugin-monkey] mount monkeyWindow to document`);
if (typeof GM_addElement === "function") {
GM_addElement(document.head, "script", {
type: "module",
src: entrySrc
});
} else {
const script = document.createElement("script");
script.type = "module";
if (window.trustedTypes) {
const policy = window.trustedTypes.createPolicy(key, {
createScriptURL: (input) => input
});
const trustedScriptURL = policy.createScriptURL(entrySrc);
script.src = trustedScriptURL;
} else {
script.src = entrySrc;
}
document.head.append(script);
}
console.log(`[vite-plugin-monkey] mount entry module to document.head`);
};
var mountGmApiFn = (meta, apiNames = []) => {
const key = `__monkeyWindow-` + new URL(meta.url).origin;
const monkeyWindow = document[key];
if (!monkeyWindow) {
console.log(`[vite-plugin-monkey] not found monkeyWindow`);
return;
}
window.unsafeWindow = window;
console.log(`[vite-plugin-monkey] mount unsafeWindow to unsafeWindow`);
apiNames.push("GM");
let mountedApiSize = 0;
apiNames.forEach((apiName) => {
const fn = monkeyWindow[apiName];
if (fn) {
window[apiName] = monkeyWindow[apiName];
mountedApiSize++;
}
});
console.log(
`[vite-plugin-monkey] mount ${mountedApiSize}/${apiNames.length} GM api to unsafeWindow`
);
};
var virtualHtmlTemplate = async (url) => {
const delay = (n = 0) => new Promise((res) => setTimeout(res, n));
await delay();
const u = new URL(url, location.origin);
u.searchParams.set("origin", u.origin);
if (window == window.parent) {
location.href = u.href;
await delay(500);
window.close();
return;
}
const style = document.createElement("style");
document.head.append(style);
style.innerText = /* css */
`
body {
font-family: Arial, sans-serif;
margin: 0;
}
.App {
margin: 25px;
}
p {
font-size: 1.5em;
}
a {
color: blue;
text-decoration: none;
font-size: 1.5em;
}
a:hover {
text-decoration: underline;
}
`.trim();
document.body.innerHTML = /* html */
`
<div class="App">
<h1>PREVIEW PAGE</h1>
<p>Click the links below to install userscripts:</p>
<a target="_blank"></a></th>
</div>
`.trim();
await delay();
const a = document.querySelector("a");
a.href = location.href;
a.text = location.href;
};
var previewTemplate = async (urls) => {
const delay = (n = 0) => new Promise((res) => setTimeout(res, n));
await delay();
const style = document.createElement("style");
document.head.append(style);
style.innerText = /* css */
`
body {
font-family: Arial, sans-serif;
margin: 0;
}
.App {
margin: 25px;
}
p {
font-size: 1.5em;
}
table {
width: 100%;
border-collapse: collapse;
font-size: 1.5em;
}
th, td {
border: 1px solid black;
padding: 8px;
text-align: left;
}
th {
background-color: #f2f2f2;
}
a {
color: blue;
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
`.trim();
if (window == window.parent && urls.length == 1) {
const u = new URL(urls[0], location.origin);
location.href = u.href;
await delay(500);
window.close();
return;
} else if (urls.length == 0) {
document.body.innerHTML = /* html */
`
<div class="App">
<h1> There is no script to install </h1>
</div>
`.trim();
return;
} else {
document.body.innerHTML = /* html */
`
<div class="App">
<h1>PREVIEW PAGE</h1>
<p>Click the links below to install userscripts:</p>
<table>
<tr>
<th>No.</th>
<th>Install Link</th>
</tr>
</table>
</div>
`.trim();
await delay();
const table = document.querySelector(`table`);
urls.sort().forEach((u, index) => {
const tr = document.createElement("tr");
const td1 = document.createElement("td");
const td2 = document.createElement("td");
const a = document.createElement("a");
td1.innerText = `${index + 1}`;
if (window != window.parent) {
a.target = "_blank";
}
a.href = u;
a.textContent = new URL(u, location.origin).href;
td2.append(a);
tr.append(td1);
tr.append(td2);
table.append(tr);
});
}
};
// src/node/plugins/perview.ts
var perviewFactory = () => {
let viteConfig;
return {
name: "monkey:perview",
apply: "serve",
configResolved(config) {
viteConfig = config;
},
async configurePreviewServer(server) {
server.middlewares.use(async (req, res, next) => {
if (["/", "/index.html"].includes((req.url ?? "").split("?")[0])) {
const distDirPath = path3.join(process.cwd(), viteConfig.build.outDir);
const urls = [];
for await (const pathname of walk(distDirPath)) {
if (pathname.endsWith(".user.js")) {
const fileName = normalizePath4(
path3.relative(distDirPath, pathname)
);
urls.push(`/` + fileName);
}
}
res.setHeader("content-type", "text/html; charset=utf-8");
res.end(fcToHtml(previewTemplate, urls));
return;
}
next();
});
}
};
};
// src/node/plugins/redirectClient.ts
var clientSourceId = "vite-plugin-monkey/dist/client";
var clientId = "\0" + clientSourceId;
var redirectClientFactory = () => {
return {
name: "monkey:redirectClient",
enforce: "pre",
apply: "build",
resolveId(source) {
if (source === clientSourceId) {
return clientId;
}
},
load(id) {
if (id == clientId) {
const identifiers = ["GM", ...gmIdentifiers, "unsafeWindow"];
const declarations = identifiers.map((v) => {
return `var _${v} = /* @__PURE__ */ (() => typeof ${v} != "undefined" ? ${v} : undefined)();`;
}).concat("var _monkeyWindow = /* @__PURE__ */ (() => window)();");
const exportIdentifiers = identifiers.concat("monkeyWindow");
return declarations.join("\n") + `
export {${exportIdentifiers.map((v) => ` _${v} as ${v},`).join("\n")}};`;
}
}
};
};
// src/node/plugins/server.ts
import fs4 from "fs/promises";
import path5 from "path";
import { normalizePath as normalizePath5 } from "vite";
// src/node/utils/openBrowser.ts
import path4, { join } from "path";
import { exec } from "child_process";
import open from "open";
import spawn from "cross-spawn";
import colors from "picocolors";
var VITE_PACKAGE_DIR = path4.dirname(compatResolve("vite/package.json"));
function openBrowser(url, opt) {
const browser = typeof opt === "string" ? opt : process.env.BROWSER || "";
if (browser.toLowerCase().endsWith(".js")) {
executeNodeScript(browser, url);
} else if (browser.toLowerCase() !== "none") {
const browserArgs = process.env.BROWSER_ARGS ? process.env.BROWSER_ARGS.split(" ") : [];
startBrowserProcess(browser, browserArgs, url);
}
}
function executeNodeScript(scriptPath, url) {
const extraArgs = process.argv.slice(2);
const child = spawn(process.execPath, [scriptPath, ...extraArgs, url], {
stdio: "inherit"
});
child.on("close", (code) => {
if (code !== 0) {
console.error(
"[plugin-monkey] " + colors.red(
`
The script specified as BROWSER environment variable failed.
${colors.cyan(
scriptPath
)} exited with code ${code}.`
),
{ error: null }
);
}
});
}
var supportedChromiumBrowsers = [
"Google Chrome Canary",
"Google Chrome Dev",
"Google Chrome Beta",
"Google Chrome",
"Microsoft Edge",
"Brave Browser",
"Vivaldi",
"Chromium"
];
async function startBrowserProcess(browser, browserArgs, url) {
const preferredOSXBrowser = browser === "google chrome" ? "Google Chrome" : browser;
const shouldTryOpenChromeWithAppleScript = process.platform === "darwin" && (!preferredOSXBrowser || supportedChromiumBrowsers.includes(preferredOSXBrowser));
if (shouldTryOpenChromeWithAppleScript) {
try {
const ps = await execAsync("ps cax");
const openedBrowser = preferredOSXBrowser && ps.includes(preferredOSXBrowser) ? preferredOSXBrowser : supportedChromiumBrowsers.find((b) => ps.includes(b));
if (openedBrowser) {
await execAsync(
`osascript openChrome.applescript "${url}" "${openedBrowser}"`,
{
cwd: join(VITE_PACKAGE_DIR, "bin")
}
);
return true;
}
} catch {
}
}
if (process.platform === "darwin" && browser === "open") {
browser = void 0;
}
try {
const options = browser ? { app: { name: browser, arguments: browserArgs } } : {};
new Promise((_, reject) => {
open(url, options).then((subprocess) => {
subprocess.on("error", reject);
}).catch(reject);
}).catch((err) => {
console.error("[plugin-monkey] " + (err.stack || err.message));
});
return true;
} catch {
return false;
}
}
function execAsync(command, options) {
return new Promise((resolve2, reject) => {
exec(command, options, (error, stdout) => {
if (error) {
reject(error);
} else {
resolve2(stdout.toString());
}
});
});
}
// src/node/plugins/server.ts
var urlPrefix = "/__vite-plugin-monkey.";
var installUserPath = urlPrefix + "install.user.js";
var gmApiPath = urlPrefix + "gm.api.js";
var entryPath = urlPrefix + "entr