vite-plugin-monkey
Version:
A vite plugin server and build your.user.js for userscript engine like Tampermonkey and Violentmonkey and Greasemonkey
1,556 lines (1,555 loc) • 71.1 kB
JavaScript
import { t as __exportAll } from "./chunk-CfYAbeIz.mjs";
import module from "node:module";
import * as acornWalk from "acorn-walk";
import { build, normalizePath } from "vite";
import { resolve } from "import-meta-resolve";
import fs from "node:fs/promises";
import path, { join } from "node:path";
import { pathToFileURL } from "node:url";
import { DomUtils, ElementType, parseDocument } from "htmlparser2";
import crypto from "node:crypto";
import MagicString from "magic-string";
import { exec } from "node:child_process";
import open from "open";
import spawn from "cross-spawn";
import colors from "picocolors";
//#region src/node/utils/gmApi.ts
const 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",
"GM_audio"
];
const gmMembers = [
"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",
"GM.audio"
];
const othersGrantNames = [
"unsafeWindow",
"window.close",
"window.focus",
"window.onurlchange"
];
const grantNames = [
...gmMembers,
...gmIdentifiers,
...othersGrantNames
];
//#endregion
//#region src/node/userscript/index.ts
const 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, "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]);
});
});
[...require, ...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 new Set([...Array.from(collectGrantSet.values()).flat(), ...grant]).forEach((s) => {
if (!s.trim()) return;
attrList.push(["grant", s]);
});
antifeature.forEach(({ description, type, tag }) => {
attrList.push([
tag ? `antifeature:${tag}` : "antifeature",
type,
description
]);
});
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 maxLen = Math.max(...subAttrList.map((s) => s[1].length));
subAttrList.forEach((s) => {
s[1] = s[1].padEnd(option.align + maxLen);
});
};
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
});
};
const 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;
};
const 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);
};
//#endregion
//#region src/node/utils/grant.ts
const collectGrant = (context, chunks, injectCssCode, minify) => {
const codes = /* @__PURE__ */ new Set();
if (injectCssCode) codes.add(injectCssCode);
for (const chunk of chunks) {
if (minify) Object.values(chunk.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;
}
matchMember(node.object.name + "." + node.property.name);
},
Identifier(node) {
matchIdentifier(node.name);
}
}, { ...acornWalk.base });
if (unusedMembers.size == 0 && unusedIdentifiers.size == 0) break;
}
return usedGm;
};
//#endregion
//#region src/node/utils/others.ts
const isFirstBoot = () => {
return (Reflect.get(globalThis, "__vite_start_time") ?? 0) < 1e3;
};
const compatResolve = (id) => {
return resolve(id, pathToFileURL(process.cwd() + "/any.js").href);
};
const existFile = async (path) => {
try {
return (await fs.stat(path)).isFile();
} catch {
return false;
}
};
const 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)));
}
}
const stringifyFunction = (fn, ...args) => {
return `;(${fn})(${args.map((v) => JSON.stringify(v)).join(",")});`;
};
const dataJsUrl = (code) => {
return "data:application/javascript," + encodeURIComponent(code);
};
function dataUrl(p0, ...args) {
if (typeof p0 == "string") return dataJsUrl(p0);
return dataJsUrl(stringifyFunction(p0, ...args));
}
const parserHtmlScriptResult = (html) => {
const doc = parseDocument(html);
return DomUtils.getElementsByTagType(ElementType.Script, doc).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
};
});
};
const simpleHash = (str) => {
return crypto.createHash("md5").update(str || "").digest("base64url").substring(0, 16);
};
const safeURL = (url, base) => {
if (!url) return void 0;
try {
return new URL(url, base);
} catch {}
};
const getSafeIdentifier = (prefix, code, others) => {
let n = 0;
let identifier = prefix;
while (code.includes(identifier) || others && others.some((c) => c.includes(identifier))) {
n++;
identifier = `${prefix}${n.toString(16)}`;
}
return identifier;
};
const getProgramImportNodes = (program) => {
const nodes = [];
acornWalk.simple(program, {
ImportDeclaration(node) {
const s = node.source;
if (s.type === "Literal") {
const value = s.value;
if (!value) return;
if (typeof value !== "string") return;
nodes.push({
node,
value
});
}
},
ImportExpression(node) {
const s = node.source;
if (s.type === "Literal") {
const value = s.value;
if (!value) return;
if (typeof value !== "string") return;
nodes.push({
node,
value
});
} else if (s.type === "TemplateLiteral") {
if (s.expressions.length) return;
if (s.quasis.length !== 1) return;
const value = s.quasis[0].value.cooked;
if (!value) return;
if (typeof value !== "string") return;
nodes.push({
node,
value
});
}
}
});
return nodes;
};
const nameReg = /[0-9a-zA-Z_]+/g;
const autoPreUnderline = (v) => {
if (!v) return "_";
return Number.isInteger(Number(v[0])) ? `_${v}` : v;
};
const getUpperCaseName = (value) => {
if (!value) return;
const list = value.match(nameReg);
if (!list?.length) return;
return list.map((v, i) => {
if (i === 0) return autoPreUnderline(v);
return v[0].toUpperCase() + v.substring(1);
}).join("");
};
const defaultCssSideEffects = (c) => {
if (typeof GM_addStyle === "function") GM_addStyle(c);
else (document.head || document.documentElement).appendChild(document.createElement("style")).append(c);
};
const getCssModuleCode = (f) => {
f ??= defaultCssSideEffects;
return `
const s = new Set;
export const _css = async (t) => {
if (s.has(t)) return;
s.add(t);
${`(${f})(t);`}
};
`.trimStart();
};
//#endregion
//#region src/node/utils/systemjs.ts
const _require = module.createRequire(import.meta.url);
const systemjsPkg = _require(`systemjs/package.json`);
const systemjsSubPaths = ["dist/system.min.js", "dist/extras/named-register.min.js"];
const customSystemInstanceCode = `;(typeof System!='undefined')&&(System=new System.constructor());`;
const systemjsAbsolutePaths = systemjsSubPaths.map((s) => {
return _require.resolve(`systemjs/` + s);
});
const getSystemjsTexts = async () => {
return Promise.all(systemjsAbsolutePaths.map((s) => fs.readFile(s, "utf-8").then((s) => s.trim().replace(/^\/\*[\s\S]*?\*\//, "").replace(/\/\/.*map$/, "").trim())).concat([Promise.resolve(customSystemInstanceCode)]));
};
const getSystemjsRequireUrls = (fn) => {
return systemjsSubPaths.map((p) => {
return fn(systemjsPkg.version, systemjsPkg.name, p, p);
}).concat([dataUrl(customSystemInstanceCode)]);
};
//#endregion
//#region src/node/utils/topLevelAwait.ts
const awaitOffset = `await`.length;
const initTlaIdentifier = `_TLA_`;
const 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;
};
const 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;
};
const includes = (str, start, end, substr) => {
const i = str.indexOf(substr, start);
return i >= 0 && i + substr.length < end;
};
const 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 = [];
acornWalk.simple(ast, {
AwaitExpression(node) {
tlaNodes.push(node);
},
ForOfStatement(node) {
if (node.await === true) tlaForOfNodes.push(node);
}
}, {
...acornWalk.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()
};
}
}
};
const 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 = [];
acornWalk.ancestor(ast, { CallExpression(node, _state, ancestors) {
if ("name" in node.callee) {
const { name, type } = node.callee;
if (type === `Identifier`) {
if (name === identifier) {
let needsParens = false;
if (ancestors.length >= 2) {
const parent = ancestors[ancestors.length - 2];
needsParens = parent.type === "MemberExpression" && parent.object === node || parent.type === "CallExpression" && parent.callee === node || parent.type === "TaggedTemplateExpression" && parent.tag === node;
}
tlaCallNodes.push({
node: {
...node,
callee: node.callee
},
needsParens
});
} else if (name === forIdentifier) forTlaCallNodes.push({
...node,
callee: node.callee
});
}
}
} }, {
...acornWalk.base,
Function: (node, state, callback) => {
if (topFnNodes.length == 0) topFnNodes.push(node);
if (includes(chunk.code, node.start, node.end, identifier)) return acornWalk.base.Function?.(node, state, callback);
}
});
if (tlaCallNodes.length > 0 || forTlaCallNodes.length > 0) {
const ms = new MagicString(chunk.code, {});
tlaCallNodes.forEach(({ node, needsParens }) => {
const callee = node.callee;
ms.update(callee.start, callee.end, "await");
if (needsParens) {
ms.appendLeft(node.start, "(");
ms.appendRight(node.end, ")");
}
});
forTlaCallNodes.forEach((node) => {
const arg0 = node.arguments[0];
ms.update(node.start, arg0.start, "await");
ms.update(arg0.end, node.end, "");
});
topFnNodes.forEach((node) => {
ms.appendLeft(node.start, `async\x20`);
});
chunk.code = ms.toString();
}
}
};
//#endregion
//#region src/node/plugins/css.ts
const cssModuleId = "virtual:monkey-css";
const virtualCssModuleId = "\0" + cssModuleId;
const styleExts = [
".css",
".less",
".sass",
".scss",
".styl",
".stylus",
".pcss",
".postcss",
".sss"
];
const exludeModuleCssExts = styleExts.map((v) => ".module" + v);
const appendInline = (value) => {
return value + "?inline";
};
const exlcudeChars = [
"\0",
"?",
"&"
];
const exlcudeModuleNames = ["uno.css"];
const filterAsync = async (arr, predicate) => {
const results = await Promise.all(arr.map(predicate));
return arr.filter((_, index) => results[index]);
};
const staticCssIdSuffix = "__monkey-css";
const staticCssTemplate = `
import {0} from '{1}';
import { _css } from '${cssModuleId}';
{0} && _css({0});
export default undefined;
`.trimStart();
const cssFactory = (getOption) => {
let option;
const isCssImport = async (context, importer, value) => {
if (!value) return false;
if (value.startsWith("virtual:")) return false;
if (exlcudeModuleNames.includes(value)) return false;
if (exlcudeChars.some((c) => value.includes(c))) return false;
if (exludeModuleCssExts.some((c) => value.endsWith(c))) return false;
if (option.build.externalResource[value]) return false;
const resolvedId = (await context.resolve(value, importer))?.id;
if (!resolvedId) return false;
if (exlcudeChars.some((c) => resolvedId.includes(c))) return false;
if (exludeModuleCssExts.some((c) => resolvedId.endsWith(c))) return false;
if (!styleExts.some((e) => resolvedId.endsWith(e))) return false;
return fs.access(resolvedId).then(() => true).catch(() => false);
};
return {
name: "monkey:css",
apply: "build",
enforce: "post",
async config() {
option = await getOption();
return { build: { rolldownOptions: { external: [cssModuleId] } } };
},
resolveId(source) {
if (source.endsWith(staticCssIdSuffix)) return source;
},
load(id) {
if (!id.endsWith(staticCssIdSuffix)) return;
const staticId = id.slice(0, -12);
return staticCssTemplate.replaceAll("{0}", getUpperCaseName(staticId.split("/").at(-1)) || "css").replaceAll("{1}", appendInline(staticId));
},
async transform(code, id) {
if (new URLSearchParams(id.split("?")[1] || "").has("inline")) return;
if (!code.includes("import")) return;
if (!styleExts.some((e) => code.includes(e))) return;
const importedCssNodes = await filterAsync(getProgramImportNodes(this.parse(code)).filter((n) => {
if (n.node.type === "ImportDeclaration" && n.node.specifiers.length) return false;
return true;
}), async (n) => isCssImport(this, id, n.value));
if (!importedCssNodes.length) return;
const ms = new MagicString(code);
const loadName = getSafeIdentifier("_css", code);
const importList = [`import {_css as ${loadName}} from '${cssModuleId}';`];
const nameCache = {};
for (const n of importedCssNodes) if (n.node.type === "ImportExpression") {
const inlineCssId = appendInline(n.value);
if (!nameCache[inlineCssId]) {
const cssName = getSafeIdentifier(getUpperCaseName(n.value) || "css", code, importList);
nameCache[inlineCssId] = cssName;
importList.push(`import ${cssName} from '${inlineCssId}';`);
}
ms.update(n.node.start, n.node.end, `${loadName}(${nameCache[inlineCssId]})`);
} else {
const resolved = (await this.resolve(n.value, id))?.id;
if (!resolved) continue;
const staticCssId = resolved + staticCssIdSuffix;
ms.update(n.node.start, n.node.end, `import '${staticCssId}';`);
}
ms.prepend(importList.join("\n"));
return {
code: ms.toString(),
map: ms.generateMap()
};
}
};
};
//#endregion
//#region src/node/plugins/buildBundle.ts
const __entry_name = `__monkey.entry.js`;
const cssModuleEntryId = `virtual:monkey-css-entry`;
const virtualCssModuleEntryId = "\0" + cssModuleEntryId;
const polyfillId = "\0vite/legacy-polyfills";
const systemJsImportMapPrefix = `user`;
const 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 cssCode = Object.entries(rawBundle).map(([k, v]) => {
if (v.type == "asset" && k.endsWith(".css")) {
delete rawBundle[k];
return v.source.toString();
}
}).filter(Boolean).join("").trim();
let cssJsCode = "";
const entryCode = (() => {
const e = Array.from(entryChunks);
const codes = [];
if (cssCode) {
if (e[0].facadeModuleId === polyfillId) {
codes.push(`import ${JSON.stringify(`./${e[0].fileName}`)};`);
e.shift();
}
codes.push(`import '${cssModuleEntryId}';`);
}
codes.push(...e.map((c) => `import './${c.fileName}';`));
return codes.join("\n");
})();
const hasDynamicImport = entryChunks.some((e) => {
if (e.dynamicImports.length) return true;
if (!e.code.includes("import")) return false;
let a = Boolean(false);
const ast = this.parse(e.code);
try {
acornWalk.simple(ast, { ImportExpression() {
a = true;
throw new Error("stop");
} });
} catch {}
return a;
});
const usedModules = /* @__PURE__ */ new Set();
const tlaIdentifier = getSafeTlaIdentifier(rawBundle);
let finalJsCode = ``;
const mockPlugin = {
name: "monkey:mock",
resolveId: {
order: "pre",
handler: (source, importer, options) => {
if (!importer && options.isEntry) return "\0" + source;
if (source === cssModuleEntryId) return virtualCssModuleEntryId;
if (source === "virtual:monkey-css") return virtualCssModuleId;
if (Object.values(rawBundle).find((chunk) => chunk.type == "chunk" && source.endsWith(chunk.fileName))) return "\0" + source;
}
},
load: {
order: "pre",
handler: (id) => {
if (!id.startsWith("\0")) return;
if (id === virtualCssModuleEntryId) return `import { _css } from '${cssModuleId}'; _css(${JSON.stringify(" " + cssCode + " ")});`;
if (id === "\0virtual:monkey-css") {
cssJsCode = getCssModuleCode(option.cssSideEffects);
return cssJsCode;
}
if (id.endsWith(__entry_name)) return entryCode;
const [k, chunk] = Object.entries(rawBundle).find(([_, chunk]) => id.endsWith(chunk.fileName)) ?? [];
if (chunk && chunk.type == "chunk" && k) {
usedModules.add(k);
if (!hasDynamicImport) {
const ch = transformTlaToIdentifier(this, chunk, tlaIdentifier);
if (ch) return ch.code;
}
return chunk.code;
}
}
}
};
const minify = viteConfig.build.minify ?? false;
if (hasDynamicImport) {
const { rollup } = await import("rollup");
const chunks = (await (await rollup({
logLevel: "silent",
external: Object.keys(option.globalsPkg2VarName),
input: __entry_name,
plugins: [mockPlugin]
})).generate({
globals: option.globalsPkg2VarName,
format: "systemjs",
sourcemap: false,
strict: true,
compact: true
})).output.flat();
const systemJsModules = [];
let entryName = "";
chunks.forEach((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.map((v) => v.trim()).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])});`),
finalJsCode
].filter((s) => s.trim()).join("\n");
if (typeof option.systemjs == "function") option.collectRequireUrls.push(...getSystemjsRequireUrls(option.systemjs));
else finalJsCode = (await getSystemjsTexts()).join("\n") + "\n" + finalJsCode;
} else {
const rolldownMinify = (viteConfig.build.minify === void 0 || viteConfig.build.minify === "oxc") && !Array.isArray(viteConfig.build.rolldownOptions?.output) ? viteConfig.build.rolldownOptions.output?.minify : void 0;
(await build({
logLevel: "error",
configFile: false,
build: {
write: false,
minify,
terserOptions: viteConfig.build.terserOptions,
target: "esnext",
modulePreload: false,
rolldownOptions: {
external: Object.keys(option.globalsPkg2VarName),
output: {
comments: false,
strict: false,
intro: `'use strict'`,
...viteConfig.build.rolldownOptions.output,
...rolldownMinify !== void 0 ? { minify: rolldownMinify } : {},
globals: option.globalsPkg2VarName
},
experimental: { attachDebugInfo: "none" }
},
lib: {
entry: __entry_name,
formats: ["iife"],
name: "__expose__",
fileName: () => "__entry.js"
}
},
plugins: [{
...mockPlugin,
generateBundle: {
order: "pre",
handler(_, iifeBundle) {
Object.entries(iifeBundle).forEach(([_, chunk]) => {
transformIdentifierToTla(this, chunk, tlaIdentifier);
});
}
}
}]
}))[0].output.flat().forEach((chunk) => {
if (chunk.type == "chunk" && chunk.isEntry) finalJsCode = chunk.code;
});
}
usedModules.forEach((k) => {
if (fristEntryChunk != rawBundle[k]) delete rawBundle[k];
});
let collectGrantSet;
if (option.build.autoGrant) collectGrantSet = collectGrant(this, chunks, cssJsCode, viteConfig.build.minify !== false);
else collectGrantSet = /* @__PURE__ */ new Set();
const mergedCode = [await finalMonkeyOptionToComment(option, collectGrantSet, "build"), finalJsCode].filter((s) => s).join(`\n\n`).trimEnd() + "\n";
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")
});
}
};
};
//#endregion
//#region src/node/plugins/config.ts
const configFactory = (getOption) => {
let option;
return {
name: "monkey:config",
async config(userConfig) {
option = await getOption();
return {
resolve: { alias: { [option.clientAlias]: "vite-plugin-monkey/dist/client" } },
build: {
assetsInlineLimit: Number.MAX_SAFE_INTEGER,
chunkSizeWarningLimit: Number.MAX_SAFE_INTEGER,
assetsDir: "./",
cssCodeSplit: false,
minify: userConfig.build?.minify ?? false,
cssMinify: userConfig.build?.cssMinify ?? true,
sourcemap: false,
rolldownOptions: {
input: option.entry,
onLog(level, log, defaultHandler) {
if (level === "warn" && log.code === "TOLERATED_TRANSFORM" && log.message.includes("Top-level await is not available")) return;
defaultHandler(level, log);
},
experimental: { attachDebugInfo: "none" }
}
}
};
}
};
};
//#endregion
//#region src/node/utils/pkg.ts
const getProjectPkg = async () => {
const rawPkg = await fs.readFile(path.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;
};
const isScopePkg = (name) => name.startsWith("@");
const 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;
}
};
const compatResolveModulePath = async (id) => {
try {
return compatResolve(id);
} catch (e) {
const r = await resolveModuleFromPath(id);
if (!r) throw e;
return r;
}
};
const 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;
}
})();
if (filePath === void 0 || !await existFile(filePath)) {
nameList.pop();
continue;
}
version = JSON.parse(await fs.readFile(filePath, "utf-8")).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
};
};
//#endregion
//#region src/node/plugins/externalGlobals.ts
const 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: { rolldownOptions: { external: Object.keys(option.globalsPkg2VarName) } } };
},
async generateBundle() {
const usedModIdSet = new Set(Array.from(this.getModuleIds()).map((s) => normalizePath(s)));
option.collectRequireUrls = option.requirePkgList.filter((p) => usedModIdSet.has(p.moduleName)).map((p) => p.url);
}
};
};
//#endregion
//#region src/node/plugins/externalResource.ts
const loaderModuleCode = `
import { GM_addStyle, GM_getResourceText, GM_getResourceURL } from 'vite-plugin-monkey/dist/client';
export const cssLoader = (name) => GM_addStyle(GM_getResourceText(name));
export const jsonLoader = (name) => JSON.parse(GM_getResourceText(name));
export const rawLoader = (name) => GM_getResourceText(name);
export const urlLoader = (name, type) => {
return GM_getResourceURL(name, false).replace(
/^data:application;base64,/,
'data:' + type + ';base64',
);
};
`;
const loaderModId = "virtual:monkey-loader";
const virtualloaderModId = "\0" + loaderModId;
const getExportModuleCode = (name, dynamic, params) => {
const valueLiteral = `(${name}(${params.map((v) => JSON.stringify(v)).join(",")}))`;
return [
`import {${name}} from '${loaderModId}'`,
dynamic ? `let cache; export const _ = () => cache ?? (cache = ${valueLiteral})` : `export const _ = ${valueLiteral}`,
`export default _`
].join(";");
};
const resPrefix = "virtual:monkey-resource-";
const resDynamicPrefix = "virtual:monkey-resource-dynamic-";
const isRawResId = (id) => {
return id.startsWith(resPrefix) || id.startsWith(resDynamicPrefix);
};
const getValueResId = (value, dynamic = false) => {
return (dynamic ? resDynamicPrefix : resPrefix) + encodeURIComponent(value);
};
const getVirtualResId = (id) => {
return "\0" + id + "\0";
};
const getResNameTuple = (id) => {
if (id.startsWith("\0") && id.endsWith("\0")) {
if (id.startsWith(resDynamicPrefix, 1)) return [decodeURIComponent(id.slice(33, -1)), true];
else if (id.startsWith(resPrefix, 1)) return [decodeURIComponent(id.slice(25, -1)), false];
}
};
const externalResourceFactory = (getOption) => {
let option;
let viteConfig;
let mrmime;
const resourceRecord = {};
let resKeys;
return {
name: "monkey:externalResource",
enforce: "post",
apply: "build",
async config() {
option = await getOption();
mrmime = await import("mrmime");
resKeys = Object.keys(option.build.externalResource);
},
configResolved(config) {
viteConfig = config;
},
resolveId(id) {
if (id === loaderModId) return virtualloaderModId;
if (isRawResId(id)) return getVirtualResId(id);
},
async transform(code) {
if (!code.includes("import")) return;
if (!resKeys.some((k) => code.includes(k))) return;
const nodes = getProgramImportNodes(this.parse(code)).filter((n) => resKeys.includes(n.value));
if (!nodes.length) return;
const ms = new MagicString(code);
const importCodes = [];
for (const { node, value } of nodes) if (node.type === "ImportDeclaration") ms.update(node.source.start, node.source.end, JSON.stringify(getValueResId(value)));
else {
const loadName = getSafeIdentifier(getUpperCaseName(value) || "r", code, importCodes);
importCodes.push(`import { _ as ${loadName}} from ${JSON.stringify(getValueResId(value, true))};`);
ms.update(node.start, node.end, `${loadName}()`);
}
ms.prepend(importCodes.join("\n"));
return {
code: ms.toString(),
map: ms.generateMap()
};
},
async load(id) {
if (id === virtualloaderModId) return loaderModuleCode;
const [importName, dynamic] = getResNameTuple(id) || [];
if (dynamic === void 0) return;
if (!importName) return;
const pkg = await getModuleRealInfo(importName);
const resOption = option.build.externalResource[importName];
const resourceName = await resOption.resourceName({
...pkg,
importName
});
const resourceUrl = await resOption.resourceUrl({
...pkg,
importName
});
resourceRecord[importName] = {
resourceName,
resourceUrl
};
const loaderParam = {
...pkg,
resourceName,
resourceUrl,
importName,
dynamic
};
if (resOption.nodeLoader) return resOption.nodeLoader(loaderParam);
else if (resOption.loader) {
const valueLiteral = `((${resOption.loader})(${JSON.stringify(loaderParam)}))`;
return (dynamic ? `let cache; export const _ async()=>cache??(cache=${valueLiteral})` : `export const _ ${valueLiteral}`) + `\nexport default _`;
}
const [resourcePath, query] = importName.split("?", 2);
const ext = resourcePath.split(".").at(-1) ?? "";
const mimeType = mrmime.lookup(ext) ?? "application/octet-stream";
const suffixSet = new URLSearchParams(query);
return (() => {
if (suffixSet.has("inline") && ext === "css") return getExportModuleCode("rawLoader", dynamic, [resourceName]);
else if (suffixSet.has("url") || suffixSet.has("inline")) return getExportModuleCode("urlLoader", dynamic, [resourceName, mimeType]);
else if (suffixSet.has("raw")) return getExportModuleCode("rawLoader", dynamic, [resourceName]);
else if (ext == "json") return getExportModuleCode("jsonLoader", dynamic, [resourceName]);
else if (ext == "css") return getExportModuleCode("cssLoader", dynamic, [resourceName]);
else if (viteConfig.assetsInclude(resourcePath)) return getExportModuleCode("urlLoader", dynamic, [resourceName, mimeType]);
else throw new Error(`module: ${importName} not found loader`);
})();
},
generateBundle() {
const usedModIdSet = /* @__PURE__ */ new Set();
Array.from(this.getModuleIds()).forEach((id) => {
const name = getResNameTuple(id)?.[0];
if (name) usedModIdSet.add(name);
});
const collectResource = {};
Object.entries(resourceRecord).forEach(([importName, { resourceName, resourceUrl }]) => {
if (usedModIdSet.has(importName)) collectResource[resourceName] = resourceUrl;
});
option.collectResource = collectResource;
}
};
};
//#endregion
//#region src/node/plugins/fixAssetUrl.ts
/**
* convert `export default "/src/assets/a.png"` to `export default new URL("/src/assets/a.png", import.meta['url']).href`
*/
const 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 defaultNode = this.parse(code).body[0];
if (defaultNode?.type == "ExportDefaultDeclaration") {
const childNode = defaultNode?.declaration;
if (childNode?.type == "Literal" && typeof childNode.value == "string" && childNode.value[0] === "/") return `export default new URL(${JSON.stringify(childNode.value)}, import.meta['url']).href`;
}
}
}
};
};
//#endregion
//#region src/node/plugins/fixClient.ts
const 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`);
}
};
};
//#endregion
//#region src/node/plugins/fixCssUrl.ts
const fixCssUrlFactory = () => {
return {
name: "monkey:fixCssUrl",
apply: "serve",
async config() {
const postUrl = (await import("postcss-url")).default;
return { css: { postcss: { plugins: [postUrl({ url: "inline" })] } } };
}
};
};
//#endregion
//#region src/node/plugins/fixWorker.ts
const isWorkerRequest = (id) => {
const queryIndex = id.indexOf("?");
if (queryIndex === -1) return false;
const query = new URLSearchParams(id.slice(queryIndex + 1));
return query.has("worker") && !query.has("url");
};
const workerWrapper = `
const dataUri = \`data:text/javascript;charset=utf-8,\${encodeURIComponent(
\`import \${JSON.stringify(
new URL('?worker_file&type=module', import.meta['url']).href,
)};\`,
)}\`;
export default function WorkerWrapper(options) {
return new Worker(dataUri, {
type: 'module',
name: options?.name,
});
}
`.trimStart();
const fixWorkerFactory = () => {
return {
name: "monkey:fixWorker",
enforce: "pre",
apply: "serve",
load(id) {
if (isWorkerRequest(id)) return workerWrapper;
}
};
};
//#endregion
//#region src/node/utils/template.ts
const htmlText = `
<!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();
const fcToHtml = (fn, ...args) => {
return htmlText.replace(`__CODE__`, stringifyFunction(fn, ...args));
};
const serverInjectFn = (entrySrc, key) => {
window.GM;
document[key] = window;
const script = document.createElement("script");
script.type = "module";
if (window.trustedTypes) script.src = window.trustedTypes.createPolicy(key, { createScriptURL: (input) => input }).createScriptURL(entrySrc);
else script.src = entrySrc;
(document.head || document.documentElement).append(script);
};
const mountGmApiFn = (key, apiNames) => {
const monkeyWindow = document[key];
if (!monkeyWindow) {
console.warn(`[vite-plugin-monkey] not found monkeyWindow`);
return;
}
window.unsafeWindow = window;
apiNames.push("GM");
apiNames.forEach((apiName) => {
if (monkeyWindow[apiName]) window[apiName] = monkeyWindow[apiName];
});
};
const 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 = `
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 = `
<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;
};
const 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 = `
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 = `
<div class="App">
<h1> There is no script to install </h1>
</div>
`.trim();
return;
} else {
document.body.innerHTML = `
<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);
});
}
};
//#endregion
//#region src/node/plugins/perview.ts
const 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 = path.join(process.cwd(), viteConfig.build.outDir);
const urls = [];
for await (const pathname of walk(distDirPath)) if (pathname.endsWith(".user.js")) {
const fileName = normalizePath(path.relative(distDirPath, pathname));
urls.push(`/` + fileName);
}
res.setHeader("content-type", "text/html; charset=utf-8");
res.end(fcToHtml(previewTemplate, urls));
return;
}
next();
});
}
};
};
//#endregion
//#region src/node/plugins/removePreload.ts
const removePreloadFactory = () => {
return {
name: "monkey:removeVitePreload",
apply: "build",
config() {
return { build: { modulePreload: false } };
},
configResolved(config) {
const plugin = config.plugins.find((p) => p.name === "native:import-analysis-build");
if (plugin) plugin.applyToEnvironment = void 0;
}
};
};
//#endregion
//#region src/node/plugins/redirectClient.ts
const clientSourceId = "vite-plugin-monkey/dist/client";
const clientId = "\0" + clientSourceId;
const 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") + `\nexport {${exportIdentifiers.map((v) => ` _${v} as ${v},`).join("\n")}};`;
}
}
};
};
//#endregion
//#region src/node/utils/openBrowser.ts
const VITE_PACKAGE_DIR = path.dirname(compatResolve("vite/package.json"));
/**
* Reads the BROWSER environment variable and decides what to do with it.
*/
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") startBrowserProcess(browser, process.env.BROWSER_ARGS ? process.env.BROWSER_ARGS.split(" ") : [], url);
}
function executeNodeScript(scriptPath, url) {
const extraArgs = process.argv.slice(2);
spawn(process.execPath, [
scriptPath,
...extraArgs,
url
], { stdio: "inherit" }).on("close", (code) => {
if (code !== 0) console.error("[plugin-monkey] " + colors.red(`\nThe script specified as BROWSER environment variable failed.\n\n${colors.cyan(scriptPath)} exited with code ${code}.`), { error: null });
});
}
const 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 preferredOSXBrows