UNPKG

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
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