UNPKG

vite-plugin-monkey

Version:

A vite plugin server and build your.user.js for userscript engine like Tampermonkey and Violentmonkey and Greasemonkey

1 lines 141 kB
{"version":3,"sources":["../../src/node/plugins/buildBundle.ts","../../src/node/utils/gmApi.ts","../../src/node/userscript/index.ts","../../src/node/utils/grant.ts","../../src/node/utils/others.ts","../../src/node/utils/systemjs.ts","../../src/node/utils/topLevelAwait.ts","../../src/node/plugins/config.ts","../../src/node/plugins/externalGlobals.ts","../../src/node/utils/pkg.ts","../../src/node/plugins/externalLoader.ts","../../src/node/plugins/externalResource.ts","../../src/node/plugins/fixAssetUrl.ts","../../src/node/plugins/fixClient.ts","../../src/node/plugins/fixCssUrl.ts","../../src/node/plugins/perview.ts","../../src/node/utils/template.ts","../../src/node/plugins/redirectClient.ts","../../src/node/plugins/server.ts","../../src/node/utils/openBrowser.ts","../../src/node/plugins/virtualHtml.ts","../../src/node/plugins/index.ts","../../src/node/cdn.ts","../../src/node/utils/option.ts","../../src/node/index.ts"],"sourcesContent":["import type { OutputChunk, RollupOutput } from 'rollup';\nimport type { Plugin, ResolvedConfig } from 'vite';\nimport { build } from 'vite';\nimport { finalMonkeyOptionToComment } from '../userscript';\nimport { collectGrant } from '../utils/grant';\nimport {\n getInjectCssCode,\n moduleExportExpressionWrapper,\n} from '../utils/others';\nimport { getSystemjsRequireUrls, getSystemjsTexts } from '../utils/systemjs';\nimport {\n getSafeTlaIdentifier,\n transformIdentifierToTla,\n transformTlaToIdentifier,\n} from '../utils/topLevelAwait';\nimport type { ResolvedMonkeyOption } from '../utils/types';\n\nconst __entry_name = `__monkey.entry.js`;\n\n// https://github.com/vitejs/vite/blob/main/packages/plugin-legacy/src/index.ts\nconst polyfillId = '\\0vite/legacy-polyfills';\n\nconst systemJsImportMapPrefix = `user`;\n\nexport const buildBundleFactory = (\n getOption: () => Promise<ResolvedMonkeyOption>,\n): Plugin => {\n let option: ResolvedMonkeyOption;\n let viteConfig: ResolvedConfig;\n return {\n name: 'monkey:buildBundle',\n apply: 'build',\n enforce: 'post',\n async config() {\n option = await getOption();\n },\n async configResolved(resolvedConfig) {\n viteConfig = resolvedConfig;\n },\n async generateBundle(_, rawBundle) {\n const entryChunks: OutputChunk[] = [];\n const chunks: OutputChunk[] = [];\n Object.values(rawBundle).forEach((chunk) => {\n if (chunk.type == 'chunk') {\n if (chunk.facadeModuleId != polyfillId) {\n chunks.push(chunk);\n }\n if (chunk.isEntry) {\n if (chunk.facadeModuleId == polyfillId) {\n entryChunks.unshift(chunk);\n } else {\n entryChunks.push(chunk);\n }\n }\n }\n });\n\n const fristEntryChunk = entryChunks.find(\n (s) => s.facadeModuleId != polyfillId,\n );\n\n const hasDynamicImport = entryChunks.some(\n (e) => e.dynamicImports.length > 0,\n );\n\n const usedModules = new Set<string>();\n\n const tlaIdentifier = getSafeTlaIdentifier(rawBundle);\n\n const buildResult = (await build({\n logLevel: 'error',\n configFile: false,\n esbuild: false,\n plugins: [\n {\n name: 'monkey:mock',\n enforce: 'pre',\n resolveId(source, importer, options) {\n if (!importer && options.isEntry) {\n return '\\0' + source;\n }\n const chunk = Object.values(rawBundle).find(\n (chunk) =>\n chunk.type == 'chunk' && source.endsWith(chunk.fileName),\n ) as OutputChunk | undefined;\n if (chunk) {\n return '\\0' + source;\n }\n },\n async load(id) {\n if (!id.startsWith('\\0')) return;\n\n if (id.endsWith(__entry_name)) {\n return entryChunks\n .map((a) => `import ${JSON.stringify(`./${a.fileName}`)};`)\n .join('\\n');\n }\n const [k, chunk] =\n Object.entries(rawBundle).find(([_, chunk]) =>\n id.endsWith(chunk.fileName),\n ) ?? [];\n if (chunk && chunk.type == 'chunk' && k) {\n usedModules.add(k);\n if (!hasDynamicImport) {\n const ch = transformTlaToIdentifier(\n this,\n chunk,\n tlaIdentifier,\n );\n if (ch) return ch;\n }\n return {\n code: chunk.code,\n map: chunk.map,\n };\n }\n },\n generateBundle(_, iifeBundle) {\n if (hasDynamicImport) {\n return;\n }\n Object.entries(iifeBundle).forEach(([_, chunk]) => {\n transformIdentifierToTla(this, chunk, tlaIdentifier);\n });\n },\n },\n ],\n build: {\n write: false,\n minify: false,\n target: 'esnext',\n rollupOptions: {\n external(source) {\n return source in option.globalsPkg2VarName;\n },\n output: {\n globals: option.globalsPkg2VarName,\n },\n },\n lib: {\n entry: __entry_name,\n formats: [hasDynamicImport ? 'system' : 'iife'] as any,\n name: hasDynamicImport ? undefined : '__expose__',\n fileName: () => `__entry.js`,\n },\n },\n })) as RollupOutput[];\n usedModules.forEach((k) => {\n if (fristEntryChunk != rawBundle[k]) {\n delete rawBundle[k];\n }\n });\n\n const buildBundle = buildResult[0].output.flat();\n let finalJsCode = ``;\n if (hasDynamicImport) {\n const systemJsModules: string[] = [];\n let entryName = '';\n Object.entries(buildBundle).forEach(([_, chunk]) => {\n if (chunk.type == 'chunk') {\n const name = JSON.stringify(`./` + chunk.fileName);\n systemJsModules.push(\n chunk.code\n .trimStart()\n .replace(/^System\\.register\\(/, `System.register(${name}, `),\n );\n if (chunk.isEntry) {\n entryName = name;\n }\n }\n });\n systemJsModules.push(`System.import(${entryName}, \"./\");`);\n finalJsCode = systemJsModules.join('\\n');\n const usedModuleIds = Array.from(this.getModuleIds()).filter(\n (d) => d in option.globalsPkg2VarName,\n );\n // {vue:'xxx:vue'}\n const importsMap = usedModuleIds.reduce(\n (p: Record<string, string>, c) => {\n p[c] = `${systemJsImportMapPrefix}:${c}`;\n return p;\n },\n {},\n );\n // inject SystemJs external globals\n finalJsCode = [\n Object.keys(importsMap).length > 0\n ? `System.addImportMap({ imports: ${JSON.stringify(importsMap)} });`\n : ``,\n ...usedModuleIds.map(\n (id) =>\n `System.set(${JSON.stringify(\n `${systemJsImportMapPrefix}:${id}`,\n )}, ${moduleExportExpressionWrapper(\n option.globalsPkg2VarName[id],\n )});`,\n ),\n '\\n' + finalJsCode,\n ]\n .filter((s) => s)\n .join('\\n');\n\n if (typeof option.systemjs == 'function') {\n option.collectRequireUrls.push(\n ...getSystemjsRequireUrls(option.systemjs),\n );\n } else {\n finalJsCode =\n (await getSystemjsTexts()).join('\\n') + '\\n' + finalJsCode;\n }\n } else {\n // use iife\n Object.entries(buildBundle).forEach(([_, chunk]) => {\n if (chunk.type == 'chunk' && chunk.isEntry) {\n finalJsCode = chunk.code;\n }\n });\n }\n\n const injectCssCode = await getInjectCssCode(option, rawBundle);\n\n let collectGrantSet: Set<string>;\n if (option.build.autoGrant) {\n collectGrantSet = collectGrant(\n this,\n chunks,\n injectCssCode,\n viteConfig.build.minify !== false,\n );\n } else {\n collectGrantSet = new Set<string>();\n }\n\n const comment = await finalMonkeyOptionToComment(\n option,\n collectGrantSet,\n 'build',\n );\n\n const mergedCode = [comment, injectCssCode, finalJsCode]\n .filter((s) => s)\n .join(`\\n\\n`)\n .trimEnd();\n if (fristEntryChunk) {\n fristEntryChunk.fileName = option.build.fileName;\n fristEntryChunk.code = mergedCode;\n } else {\n this.emitFile({\n type: 'asset',\n fileName: option.build.fileName,\n source: mergedCode,\n });\n }\n\n if (option.build.metaFileName) {\n this.emitFile({\n type: 'asset',\n fileName: option.build.metaFileName(),\n source: await finalMonkeyOptionToComment(\n option,\n collectGrantSet,\n 'meta',\n ),\n });\n }\n },\n };\n};\n","export const gmIdentifiers = [\n 'GM_addElement',\n 'GM_addStyle',\n 'GM_addValueChangeListener',\n 'GM_cookie',\n 'GM_deleteValue',\n 'GM_deleteValues',\n 'GM_download',\n 'GM_getResourceText',\n 'GM_getResourceURL',\n 'GM_getTab',\n 'GM_getTabs',\n 'GM_getValue',\n 'GM_getValues',\n 'GM_info',\n 'GM_listValues',\n 'GM_log',\n 'GM_notification',\n 'GM_openInTab',\n 'GM_registerMenuCommand',\n 'GM_removeValueChangeListener',\n 'GM_saveTab',\n 'GM_setClipboard',\n 'GM_setValue',\n 'GM_setValues',\n 'GM_unregisterMenuCommand',\n 'GM_webRequest',\n 'GM_xmlhttpRequest',\n] as const;\n\nconst gmMembers = [\n 'GM.addElement',\n 'GM.addStyle',\n 'GM.addValueChangeListener',\n 'GM.cookie',\n 'GM.deleteValue',\n 'GM.deleteValues',\n 'GM.download',\n 'GM.getResourceText',\n // https://www.tampermonkey.net/documentation.php#api:GM_getResourceURL\n 'GM.getResourceUrl',\n 'GM.getTab',\n 'GM.getTabs',\n 'GM.getValue',\n 'GM.getValues',\n 'GM.info',\n 'GM.listValues',\n 'GM.log',\n 'GM.notification',\n 'GM.openInTab',\n 'GM.registerMenuCommand',\n 'GM.removeValueChangeListener',\n 'GM.saveTab',\n 'GM.setClipboard',\n 'GM.setValue',\n 'GM.setValues',\n 'GM.unregisterMenuCommand',\n 'GM.webRequest',\n 'GM.xmlHttpRequest',\n] as const;\n\nconst othersGrantNames = [\n 'unsafeWindow',\n 'window.close',\n 'window.focus',\n 'window.onurlchange',\n] as const;\n\nexport type GrantType =\n | (typeof gmIdentifiers)[number]\n | (typeof gmMembers)[number]\n | (typeof othersGrantNames)[number];\n\nexport const grantNames = [...gmMembers, ...gmIdentifiers, ...othersGrantNames];\n","import { grantNames, type GrantType } from '../utils/gmApi';\nimport type { ResolvedMonkeyOption, IArray, LocaleType } from '../utils/types';\nimport type { GreasemonkeyUserScript, GreaseRunAt } from './greasemonkey';\nimport type {\n AntifeatureType,\n TampermonkeyUserScript,\n TamperRunAt,\n} from './tampermonkey';\nimport type { ViolentmonkeyUserScript, ViolentRunAt } from './violentmonkey';\nimport type { ViolentInjectInto } from './violentmonkey';\n\nexport type {\n GreasemonkeyUserScript,\n TampermonkeyUserScript,\n ViolentmonkeyUserScript,\n};\n\n/**\n * @see https://greasyfork.org/help/meta-keys\n */\ninterface GreasyforkUserScript {\n /**\n * @see https://greasyfork.org/help/meta-keys\n * @default package.json.license\n */\n license?: string;\n\n /**\n * @see https://greasyfork.org/help/meta-keys\n */\n contributionURL?: string;\n\n /**\n * @see https://greasyfork.org/help/meta-keys\n */\n contributionAmount?: string;\n\n /**\n * @see https://greasyfork.org/help/meta-keys\n */\n compatible?: string;\n\n /**\n * @see https://greasyfork.org/help/meta-keys\n */\n incompatible?: string;\n}\n\ninterface MergemonkeyUserScript {\n /**\n * @default package.json.name??'monkey'\n * @default {...{'':package.json.name??'monkey'},...name} // if name is object\n */\n name?: string | LocaleType<string>;\n\n namespace?: string;\n\n /**\n * @default package.json.version\n */\n version?: string;\n\n /**\n * @default package.json.description\n * @default {...{'':package.json.description},...description} // if description is object\n */\n description?: string | LocaleType<string>;\n\n /**\n * @default package.json.author\n */\n author?: string;\n\n /**\n * @default package.json.homepage\n */\n homepage?: string;\n\n /**\n * @default package.json.homepage\n */\n homepageURL?: string;\n\n /**\n * @default package.json.repository\n */\n source?: string;\n\n /**\n * @default package.json.bugs\n */\n supportURL?: string;\n\n /**\n * @see https://wiki.greasespot.net/Metadata_Block#.40run-at\n *\n * @see https://www.tampermonkey.net/documentation.php#meta:run_at\n *\n * @see https://violentmonkey.github.io/api/metadata-block/#run-at\n *\n */\n 'run-at'?: GreaseRunAt | TamperRunAt | ViolentRunAt;\n\n /**\n * @see https://wiki.greasespot.net/Metadata_Block#.40grant\n *\n * @see https://www.tampermonkey.net/documentation.php#meta:grant\n *\n * @see https://violentmonkey.github.io/api/metadata-block/#grant\n *\n * if set '\\*', will add all GM_* to UserScript\n */\n grant?: IArray<GrantType> | 'none' | '*';\n\n /**\n * custom extra meta\n * @example\n * [['antifeature', ['miner', 'hello233']]]\n * // -->\n * // \\@antifeature miner hello233\n */\n $extra?: [string, IArray<string>][] | Record<string, IArray<string>>;\n}\n\n/**\n * UserScript, merge metadata from Greasemonkey, Tampermonkey, Violentmonkey, Greasyfork\n */\nexport type MonkeyUserScript = GreasemonkeyUserScript &\n TampermonkeyUserScript &\n ViolentmonkeyUserScript &\n GreasyforkUserScript &\n MergemonkeyUserScript;\n\nexport interface FinalUserScript extends GreasyforkUserScript {\n name: LocaleType<string>;\n namespace?: string;\n version?: string;\n description: LocaleType<string>;\n icon?: string;\n include: string[];\n match: string[];\n exclude: string[];\n require: string[];\n resource: Record<string, string>;\n noframes: boolean;\n unwrap: boolean;\n webRequest: string[];\n\n author?: string;\n copyright?: string;\n homepage?: string;\n homepageURL?: string;\n website?: string;\n source?: string;\n iconURL?: string;\n defaulticon?: string;\n icon64?: string;\n icon64URL?: string;\n updateURL?: string;\n downloadURL?: string;\n supportURL?: string;\n connect: string[];\n sandbox?: string;\n tag: string[];\n antifeature: AntifeatureType[];\n\n 'exclude-match': string[];\n 'inject-into'?: ViolentInjectInto;\n 'run-at'?: GreaseRunAt | TamperRunAt | ViolentRunAt;\n grant: Set<string>;\n $extra: [string, ...string[]][];\n}\n\nexport const finalMonkeyOptionToComment = async (\n option: ResolvedMonkeyOption,\n collectGrantSet: Set<string>,\n mode: `serve` | `build` | `meta`,\n): Promise<string> => {\n const { userscript, collectRequireUrls, collectResource } = option;\n let attrList: [string, ...string[]][] = [];\n const {\n name,\n namespace,\n version,\n author,\n description,\n license,\n copyright,\n\n icon,\n iconURL,\n icon64,\n icon64URL,\n defaulticon,\n\n homepage,\n homepageURL,\n website,\n source,\n\n supportURL,\n downloadURL,\n updateURL,\n\n include,\n match,\n exclude,\n require,\n 'exclude-match': excludeMatch,\n\n 'inject-into': injectInto,\n 'run-at': runAt,\n\n compatible,\n incompatible,\n\n antifeature,\n contributionAmount,\n contributionURL,\n\n connect,\n sandbox,\n tag,\n resource,\n grant,\n noframes,\n unwrap,\n webRequest,\n $extra,\n } = userscript;\n Object.entries({\n namespace,\n version,\n author,\n license,\n copyright,\n icon,\n iconURL,\n icon64,\n icon64URL,\n defaulticon,\n homepage,\n homepageURL,\n website,\n source,\n supportURL,\n downloadURL,\n updateURL,\n 'inject-into': injectInto,\n 'run-at': runAt,\n compatible,\n incompatible,\n contributionAmount,\n contributionURL,\n sandbox,\n }).forEach(([k, v]) => {\n if (typeof v == 'string') {\n attrList.push([k, v]);\n }\n });\n Object.entries(name).forEach(([k, v]) => {\n if (k == '') {\n attrList.push(['name', v]);\n } else {\n attrList.push(['name:' + k, v]);\n }\n });\n Object.entries(description).forEach(([k, v]) => {\n if (k == '') {\n attrList.push(['description', v]);\n } else {\n attrList.push(['description:' + k, v]);\n }\n });\n\n Object.entries({\n include,\n match,\n exclude,\n 'exclude-match': excludeMatch,\n }).forEach(([k, v]) => {\n v.forEach((v2) => {\n attrList.push([k, v2]);\n });\n });\n\n [...require, ...collectRequireUrls].forEach((s) => {\n attrList.push(['require', s]);\n });\n\n Object.entries({ ...resource, ...collectResource }).forEach(([k, v]) => {\n attrList.push(['resource', k, v]);\n });\n\n connect.forEach((s) => {\n attrList.push(['connect', s]);\n });\n tag.forEach((s) => {\n attrList.push(['tag', s]);\n });\n\n webRequest.forEach((s) => {\n attrList.push(['webRequest', s]);\n });\n\n if (grant.has('none')) {\n attrList.push(['grant', 'none']);\n } else if (grant.has('*')) {\n grantNames.forEach((s) => {\n attrList.push(['grant', s]);\n });\n } else {\n new Set([...Array.from(collectGrantSet.values()).flat(), ...grant]).forEach(\n (s) => {\n if (!s.trim()) return;\n attrList.push(['grant', s]);\n },\n );\n }\n antifeature.forEach(({ description, type, tag }) => {\n attrList.push([\n tag ? `antifeature:${tag}` : 'antifeature',\n type,\n description,\n ]);\n });\n if (noframes) {\n attrList.push(['noframes']);\n }\n if (unwrap) {\n attrList.push(['unwrap']);\n }\n attrList.push(...$extra);\n\n attrList = defaultSortFormat(attrList);\n\n // format\n if (option.align >= 1) {\n const formatKey = (subAttrList: [string, ...string[]][]) => {\n if (subAttrList.length == 0) return;\n const maxLen = Math.max(...subAttrList.map((s) => s[1].length));\n subAttrList.forEach((s) => {\n s[1] = s[1].padEnd(option.align + maxLen);\n });\n };\n\n formatKey(attrList.filter((s) => s[0] == 'resource'));\n formatKey(\n attrList.filter(\n (s) => s[0] == 'antifeature' || s[0].startsWith('antifeature:'),\n ),\n );\n // format all\n const maxLen = Math.max(...attrList.map((s) => s[0].length));\n attrList.forEach((s) => {\n s[0] = s[0].padEnd(option.align + maxLen);\n });\n }\n\n const uString = [\n '==UserScript==',\n ...attrList.map(\n (attr) =>\n '@' +\n attr\n .map((v) => {\n return v.endsWith('\\x20') ? v : v + '\\x20';\n })\n .join('')\n .trimEnd(),\n ),\n '==/UserScript==',\n ]\n .map((s) => '//\\x20' + s)\n .join('\\n');\n\n return option.generate({ userscript: uString, mode });\n};\n\nconst stringSort = (a: [string, ...string[]], b: [string, ...string[]]) => {\n const minLen = Math.min(a.length, b.length);\n for (let i = 0; i < minLen; i++) {\n if (a[i] > b[i]) {\n return 1;\n } else if (a[i] < b[i]) {\n return -1;\n }\n }\n if (a.length > b.length) {\n return 1;\n } else if (a.length < b.length) {\n return -1;\n }\n return 0;\n};\n\nconst defaultSortFormat = (p0: [string, ...string[]][]) => {\n const filter = (\n predicate: (value: [string, ...string[]], index: number) => boolean,\n ): [string, ...string[]][] => {\n const notMatchList: [string, ...string[]][] = [];\n const matchList: [string, ...string[]][] = [];\n p0.forEach((value, index) => {\n if (!predicate(value, index)) {\n notMatchList.push(value);\n } else {\n matchList.push(value);\n }\n });\n p0 = notMatchList;\n return matchList;\n };\n return [\n filter(([k]) => k == 'name'),\n filter(([k]) => k.startsWith('name:')),\n filter(([k]) => k == 'namespace'),\n filter(([k]) => k == 'version'),\n filter(([k]) => k == 'author'),\n filter(([k]) => k == 'description'),\n filter(([k]) => k.startsWith('description:')),\n filter(([k]) => k == 'license'),\n filter(([k]) => k == 'copyright'),\n\n filter(([k]) => k == 'icon'),\n filter(([k]) => k == 'iconURL'),\n filter(([k]) => k == 'icon64'),\n filter(([k]) => k == 'icon64URL'),\n filter(([k]) => k == 'defaulticon'),\n\n filter(([k]) => k == 'homepage'),\n filter(([k]) => k == 'homepageURL'),\n filter(([k]) => k == 'website'),\n filter(([k]) => k == 'source'),\n\n filter(([k]) => k == 'supportURL'),\n filter(([k]) => k == 'downloadURL'),\n filter(([k]) => k == 'updateURL'),\n\n filter(([k]) => k == 'include'),\n filter(([k]) => k == 'match'),\n filter(([k]) => k == 'exclude'),\n filter(([k]) => k == 'exclude-match'),\n filter(([k]) => k == 'webRequest'),\n\n filter(([k]) => k == 'require'),\n\n filter(([k]) => k == 'resource').sort(stringSort),\n\n filter(([k]) => k == 'sandbox'),\n filter(([k]) => k == 'tag'),\n\n filter(([k]) => k == 'connect'),\n\n filter(([k]) => k == 'grant').sort(stringSort),\n\n filter(([k]) => k == 'inject-into'),\n filter(([k]) => k == 'run-at'),\n\n filter(([k]) => k == 'compatible'),\n filter(([k]) => k == 'incompatible'),\n filter(([k]) => k == 'antifeature').sort(stringSort),\n filter(([k]) => k.startsWith('antifeature:')).sort(stringSort),\n filter(([k]) => k == 'contributionAmount'),\n filter(([k]) => k == 'contributionURL'),\n filter(([k]) => k == 'noframes'),\n filter(([k]) => k == 'unwrap'),\n p0,\n ].flat(1);\n};\n","import * as acornWalk from 'acorn-walk';\nimport type { OutputChunk, PluginContext } from 'rollup';\nimport { grantNames } from './gmApi';\n\nexport const collectGrant = (\n context: PluginContext,\n chunks: OutputChunk[],\n injectCssCode: string | undefined,\n minify: boolean,\n): Set<string> => {\n const codes = new Set<string>();\n if (injectCssCode) {\n codes.add(injectCssCode);\n }\n for (const chunk of chunks) {\n if (minify) {\n // https://github.com/lisonge/vite-plugin-monkey/issues/166\n const modules = Object.values(chunk.modules);\n modules.forEach((m) => {\n const code = m.code;\n if (code) {\n codes.add(code);\n }\n });\n }\n codes.add(chunk.code);\n }\n const unusedMembers = new Set<string>(\n grantNames.filter((s) => s.includes(`.`)),\n );\n const endsWithWin = (a: string, b: string): boolean => {\n if (a.endsWith(b)) {\n return a === 'monkeyWindow.' + b || a === '_monkeyWindow.' + b;\n }\n return false;\n };\n const memberHandleMap = Object.fromEntries(\n grantNames\n .filter((s) => s.startsWith('window.'))\n .map((name) => [name, (v: string) => endsWithWin(v, name.split('.')[1])]),\n );\n const unusedIdentifiers = new Set<string>(\n grantNames.filter((s) => !s.includes(`.`)),\n );\n const usedGm = new Set<string>();\n const matchIdentifier = (name: string): boolean => {\n if (unusedIdentifiers.has(name)) {\n usedGm.add(name);\n unusedIdentifiers.delete(name);\n return true;\n }\n return false;\n };\n const matchMember = (name: string): boolean => {\n for (const unusedName of unusedMembers.values()) {\n if (name.endsWith(unusedName) || memberHandleMap[unusedName]?.(name)) {\n usedGm.add(unusedName);\n unusedMembers.delete(unusedName);\n return true;\n }\n }\n return false;\n };\n for (const code of codes) {\n if (!code.trim()) continue;\n const ast = context.parse(code);\n acornWalk.simple(\n ast,\n {\n MemberExpression(node) {\n if (unusedMembers.size === 0) return;\n if (\n node.computed ||\n node.object.type !== 'Identifier' ||\n node.property.type !== 'Identifier'\n ) {\n return;\n }\n if (\n node.object.name === 'monkeyWindow' ||\n node.object.name === '_monkeyWindow'\n ) {\n if (matchIdentifier(node.property.name)) {\n return;\n }\n }\n const name = node.object.name + '.' + node.property.name;\n matchMember(name);\n },\n Identifier(node) {\n // only one layer\n matchIdentifier(node.name);\n },\n },\n { ...acornWalk.base },\n );\n if (unusedMembers.size == 0 && unusedIdentifiers.size == 0) {\n break;\n }\n }\n return usedGm;\n};\n","import { resolve } from 'import-meta-resolve';\nimport fs from 'node:fs/promises';\nimport path from 'node:path';\nimport { pathToFileURL } from 'node:url';\nimport type { OutputBundle } from 'rollup';\nimport { transformWithEsbuild } from 'vite';\nimport type { ResolvedMonkeyOption } from './types';\n\nexport const isFirstBoot = (): boolean => {\n return (Reflect.get(globalThis, '__vite_start_time') ?? 0) < 1000;\n};\n\nexport const compatResolve = (id: string) => {\n return resolve(id, pathToFileURL(process.cwd() + '/any.js').href);\n};\n\nexport const existFile = async (path: string) => {\n try {\n return (await fs.stat(path)).isFile();\n } catch {\n return false;\n }\n};\n\nexport const miniCode = async (code: string, type: 'css' | 'js' = 'js') => {\n return (\n await transformWithEsbuild(code, 'any_name.' + type, {\n minify: true,\n sourcemap: false,\n legalComments: 'none',\n })\n ).code.trimEnd();\n};\n\nexport const moduleExportExpressionWrapper = (expression: string) => {\n let n = 0;\n let identifier = ``;\n while (expression.includes(identifier)) {\n identifier = `_${(n || ``).toString(16)}`;\n n++;\n }\n // https://github.com/lisonge/vite-plugin-monkey/issues/70\n return `(()=>{const ${identifier}=${expression};('default' in ${identifier})||(${identifier}.default=${identifier});return ${identifier}})()`;\n};\n\nexport async function* walk(dirPath: string) {\n const pathnames = (await fs.readdir(dirPath)).map((s) =>\n path.join(dirPath, s),\n );\n while (pathnames.length > 0) {\n const pathname = pathnames.pop()!;\n const state = await fs.lstat(pathname);\n if (state.isFile()) {\n yield pathname;\n } else if (state.isDirectory()) {\n pathnames.push(\n ...(await fs.readdir(pathname)).map((s) => path.join(pathname, s)),\n );\n }\n }\n}\n\nexport const getInjectCssCode = async (\n option: ResolvedMonkeyOption,\n bundle: OutputBundle,\n) => {\n const cssTexts: string[] = [];\n Object.entries(bundle).forEach(([k, v]) => {\n if (v.type == 'asset' && k.endsWith('.css')) {\n cssTexts.push(v.source.toString());\n delete bundle[k];\n }\n });\n const css = cssTexts.join('').trim();\n if (css) {\n // use \\x20 to compat unocss, see https://github.com/lisonge/vite-plugin-monkey/issues/45\n return await option.cssSideEffects(`\\x20` + css + `\\x20`);\n }\n};\n\nexport const stringifyFunction = <T extends (...args: any[]) => any>(\n fn: T,\n ...args: Parameters<T>\n) => {\n return `;(${fn})(${args.map((v) => JSON.stringify(v)).join(',')});`;\n};\n\nexport const dataJsUrl = (code: string): string => {\n return 'data:application/javascript,' + encodeURIComponent(code);\n};\n\n/**\n * string -> javascript data url\n * @example\n * dataUrl('console.log(\"hello world\")')\n * // => data:application/javascript,console.log(%22hello%20world%22)\n */\nexport function dataUrl(code: string): string;\n/**\n * function and it parameters -> iife -> mini_iife -> javascript data url\n *\n * @example\n * dataUrl((a)=>{console.log(a)}, 'world')\n * // => data:application/javascript,((z)%3D%3E%7Bconsole.log(a)%7D)('world')\n */\nexport function dataUrl<T extends (...args: any[]) => any>(\n fn: T,\n ...args: Parameters<T>\n): Promise<string>;\nexport function dataUrl(p0: any, ...args: any[]): string | Promise<string> {\n if (typeof p0 == 'string') {\n return dataJsUrl(p0);\n }\n return miniCode(stringifyFunction(p0, ...args)).then(dataJsUrl);\n}\n\nimport { DomUtils, ElementType, parseDocument } from 'htmlparser2';\n\ninterface ScriptResult {\n src: string;\n text: string;\n}\n\nexport const parserHtmlScriptResult = (html: string): ScriptResult[] => {\n const doc = parseDocument(html);\n type Element =\n ReturnType<typeof DomUtils.findAll> extends Array<infer T> ? T : never;\n const scripts = DomUtils.getElementsByTagType(\n ElementType.Script,\n doc,\n ) as Element[];\n return scripts.map<ScriptResult>((p) => {\n const src = p.attribs.src ?? '';\n const textNode = p.firstChild;\n let text = '';\n if (textNode?.type == ElementType.Text) {\n text = textNode.data ?? '';\n }\n if (src) {\n return { src, text };\n } else {\n return {\n src: '',\n text,\n };\n }\n });\n};\n\nimport crypto from 'node:crypto';\n\nexport const simpleHash = (str = ''): string => {\n return crypto\n .createHash('md5')\n .update(str || '')\n .digest('base64url')\n .substring(0, 8);\n};\n\nexport const safeURL = (\n url: string | URL | undefined | null,\n base?: string | URL,\n): URL | undefined => {\n if (!url) return undefined;\n try {\n return new URL(url, base);\n } catch {}\n};\n","import fs from 'node:fs/promises';\nimport module from 'node:module';\nimport type systemjsPkgT from 'systemjs/package.json';\nimport { dataUrl } from './others';\nimport type { ModuleToUrlFc } from './types';\n\nconst _require = module.createRequire(import.meta.url);\n\nconst systemjsPkg: typeof systemjsPkgT = _require(`systemjs/package.json`);\n\nconst systemjsSubPaths = [\n 'dist/system.min.js',\n 'dist/extras/named-register.min.js',\n];\n\n// https://github.com/systemjs/systemjs/blob/main/docs/api.md#systemconstructor\nconst customSystemInstanceCode = `;(typeof System!='undefined')&&(System=new System.constructor());`;\n\nconst systemjsAbsolutePaths = systemjsSubPaths.map((s) => {\n return _require.resolve(`systemjs/` + s);\n});\n\nexport const getSystemjsTexts = async (): Promise<string[]> => {\n return Promise.all(\n systemjsAbsolutePaths\n .map((s) =>\n fs.readFile(s, 'utf-8').then((s) =>\n s\n .trim()\n .replace(/^\\/\\*[\\s\\S]*?\\*\\//, '')\n .replace(/\\/\\/.*map$/, '')\n .trim(),\n ),\n )\n .concat([Promise.resolve(customSystemInstanceCode)]),\n );\n};\n\nexport const getSystemjsRequireUrls = (fn: ModuleToUrlFc) => {\n return systemjsSubPaths\n .map((p) => {\n return fn(systemjsPkg.version, systemjsPkg.name, p, p);\n })\n .concat([dataUrl(customSystemInstanceCode)]);\n};\n","import type * as acorn from 'acorn';\nimport * as acornWalk from 'acorn-walk';\nimport MagicString from 'magic-string';\nimport type {\n OutputAsset,\n OutputBundle,\n OutputChunk,\n PluginContext,\n} from 'rollup';\n\ninterface AwaitCallExpression extends acorn.CallExpression {\n callee: acorn.Identifier;\n}\n\nconst awaitOffset = `await`.length;\nconst initTlaIdentifier = `_TLA_`;\n\nexport const getSafeTlaIdentifier = (rawBundle: OutputBundle) => {\n const codes: string[] = [];\n for (const chunk of Object.values(rawBundle)) {\n if (chunk.type == 'chunk') {\n codes.push(chunk.code);\n }\n }\n let x = 0;\n let identifier = initTlaIdentifier;\n while (codes.some((code) => code.includes(identifier))) {\n x++;\n identifier = initTlaIdentifier + x.toString(36);\n }\n return identifier;\n};\n\nconst startWith = (\n text: string,\n searchString: string,\n position = 0,\n ignoreString: string,\n) => {\n for (let i = position; i < text.length; i++) {\n if (ignoreString.includes(text[i])) {\n continue;\n }\n return text.startsWith(searchString, i);\n }\n return false;\n};\n\nconst includes = (\n str: string,\n start: number,\n end: number,\n substr: string,\n): boolean => {\n const i = str.indexOf(substr, start);\n return i >= 0 && i + substr.length < end;\n};\n\nexport const transformTlaToIdentifier = (\n context: PluginContext,\n chunk: OutputAsset | OutputChunk,\n identifier: string,\n) => {\n if (chunk.type == 'chunk') {\n const code = chunk.code;\n if (!code.includes(`await`)) {\n return;\n }\n const ast = context.parse(code);\n const tlaNodes: acorn.AwaitExpression[] = [];\n const tlaForOfNodes: acorn.ForOfStatement[] = [];\n acornWalk.simple(\n ast,\n {\n AwaitExpression(node) {\n // top level await\n tlaNodes.push(node);\n },\n ForOfStatement(node) {\n if (node.await === true) {\n tlaForOfNodes.push(node);\n }\n },\n },\n { ...acornWalk.base, Function: () => {} },\n );\n if (tlaNodes.length > 0 || tlaForOfNodes.length > 0) {\n const ms = new MagicString(code);\n tlaNodes.forEach((node) => {\n if (\n !startWith(chunk.code, '(', node.start + awaitOffset, '\\x20\\t\\r\\n')\n ) {\n // await xxx -> await (xxx)\n ms.appendLeft(node.start + awaitOffset, `(`);\n ms.appendRight(node.end, `)`);\n }\n\n // await (xxx) -> __topLevelAwait__ (xxx)\n ms.update(node.start, node.start + awaitOffset, identifier);\n });\n tlaForOfNodes.forEach((node) => {\n // for await(const x of xxx){} -> __topLevelAwait_FOR ((async()=>{ /*start*/for await(const x of xxx){}/*end*/ })());\n ms.appendLeft(node.start, `${identifier + `FOR`}((async()=>{`);\n ms.appendRight(node.end, `})());`);\n });\n return {\n code: ms.toString(),\n map: ms.generateMap(),\n };\n }\n }\n};\n\nexport const transformIdentifierToTla = (\n context: PluginContext,\n chunk: OutputAsset | OutputChunk,\n identifier: string,\n) => {\n if (chunk.type == 'chunk') {\n if (!chunk.code.includes(identifier)) {\n return;\n }\n\n const forIdentifier = identifier + `FOR`;\n\n const ast = context.parse(chunk.code);\n const tlaCallNodes: AwaitCallExpression[] = [];\n const forTlaCallNodes: AwaitCallExpression[] = [];\n const topFnNodes: acorn.Function[] = [];\n acornWalk.simple(\n ast,\n {\n CallExpression(node) {\n if ('name' in node.callee) {\n const { name, type } = node.callee;\n if (type === `Identifier`) {\n if (name === identifier) {\n // top level await\n tlaCallNodes.push({ ...node, callee: node.callee });\n } else if (name === forIdentifier) {\n // top level for await\n forTlaCallNodes.push({ ...node, callee: node.callee });\n }\n }\n }\n },\n },\n {\n ...acornWalk.base,\n Function: (node, state, callback) => {\n if (topFnNodes.length == 0) {\n topFnNodes.push(node);\n }\n if (includes(chunk.code, node.start, node.end, identifier)) {\n return acornWalk.base.Function?.(node, state, callback);\n }\n },\n },\n );\n if (tlaCallNodes.length > 0 || forTlaCallNodes.length > 0) {\n const ms = new MagicString(chunk.code, {});\n tlaCallNodes.forEach((node) => {\n const callee = node.callee;\n // __topLevelAwait__ (xxx) -> await (xxx)\n ms.update(callee.start, callee.end, 'await');\n });\n forTlaCallNodes.forEach((node) => {\n // __topLevelAwait_FOR ((async()=>{ /*start*/for await(const x of xxx){}/*end*/ })()); -> for await(const x of xxx){}\n // @ts-ignore\n const forOfNode = node.arguments?.[0]?.callee?.body\n ?.body?.[0] as acorn.ForOfStatement;\n ms.update(node.start, forOfNode.start, '');\n ms.update(forOfNode.end, node.end, '');\n });\n topFnNodes.forEach((node) => {\n ms.appendLeft(node.start, `async\\x20`);\n });\n chunk.code = ms.toString();\n }\n }\n};\n","import type { Plugin } from 'vite';\nimport type { ResolvedMonkeyOption } from '../utils/types';\n\nexport const configFactory = (\n getOption: () => Promise<ResolvedMonkeyOption>,\n): Plugin => {\n let option: ResolvedMonkeyOption;\n return {\n name: 'monkey:config',\n async config(userConfig) {\n option = await getOption();\n return {\n resolve: {\n alias: {\n [option.clientAlias]: 'vite-plugin-monkey/dist/client',\n },\n },\n esbuild: {\n supported: {\n 'top-level-await': true,\n },\n },\n build: {\n assetsInlineLimit: Number.MAX_SAFE_INTEGER,\n chunkSizeWarningLimit: Number.MAX_SAFE_INTEGER,\n modulePreload: false,\n assetsDir: './',\n cssCodeSplit: false,\n minify: userConfig.build?.minify ?? false,\n cssMinify: userConfig.build?.cssMinify ?? true,\n sourcemap: false,\n rollupOptions: {\n input: option.entry,\n },\n },\n };\n },\n };\n};\n","import { normalizePath } from 'vite';\nimport type { ResolvedMonkeyOption } from '../utils/types';\nimport { getModuleRealInfo } from '../utils/pkg';\nimport type { Plugin } from 'vite';\n\nexport const externalGlobalsFactory = (\n getOption: () => Promise<ResolvedMonkeyOption>,\n): Plugin => {\n let option: ResolvedMonkeyOption;\n return {\n name: 'monkey:externalGlobals',\n enforce: 'pre',\n apply: 'build',\n async config() {\n option = await getOption();\n for (const [moduleName, varName2LibUrl] of option.build.externalGlobals) {\n const { name, version } = await getModuleRealInfo(moduleName);\n if (typeof varName2LibUrl == 'string') {\n option.globalsPkg2VarName[moduleName] = varName2LibUrl;\n } else if (typeof varName2LibUrl == 'function') {\n option.globalsPkg2VarName[moduleName] = await varName2LibUrl(\n version,\n name,\n moduleName,\n );\n } else if (varName2LibUrl instanceof Array) {\n const [varName, ...libUrlList] = varName2LibUrl;\n if (typeof varName == 'string') {\n option.globalsPkg2VarName[moduleName] = varName;\n } else if (typeof varName == 'function') {\n option.globalsPkg2VarName[moduleName] = await varName(\n version,\n name,\n moduleName,\n );\n }\n for (const libUrl of libUrlList) {\n // keep add order\n if (typeof libUrl == 'string') {\n option.requirePkgList.push({ url: libUrl, moduleName });\n } else if (typeof libUrl == 'function') {\n option.requirePkgList.push({\n url: await libUrl(version, name, moduleName),\n moduleName,\n });\n }\n }\n }\n }\n return {\n build: {\n rollupOptions: {\n external(source, _importer, _isResolved) {\n return source in option.globalsPkg2VarName;\n },\n },\n },\n };\n },\n async generateBundle() {\n const usedModIdSet = new Set(\n Array.from(this.getModuleIds()).map((s) => normalizePath(s)),\n );\n option.collectRequireUrls = option.requirePkgList\n .filter((p) => usedModIdSet.has(p.moduleName))\n .map((p) => p.url);\n },\n };\n};\n","import fs from 'node:fs/promises';\nimport path from 'node:path';\nimport { normalizePath } from 'vite';\nimport { compatResolve, existFile } from './others';\n\ninterface RawPackageJson {\n name?: string;\n version?: string;\n description?: string;\n license?: string;\n author?: string | { name: string };\n homepage?: string;\n repository?: string | { url?: string };\n bugs?: string | { url?: string };\n}\n\ninterface PackageJson {\n name?: string;\n version?: string;\n description?: string;\n license?: string;\n author?: string;\n homepage?: string;\n repository?: string;\n bugs?: string;\n}\n\nexport const getProjectPkg = async (): Promise<PackageJson> => {\n const rawPkg: RawPackageJson | undefined = await fs\n .readFile(path.resolve(process.cwd(), 'package.json'), 'utf-8')\n .then(JSON.parse)\n .catch(() => {});\n\n const pkg: PackageJson = {};\n if (!rawPkg) return pkg;\n Object.entries(rawPkg).forEach(([k, v]) => {\n if (typeof v == 'string') {\n Reflect.set(pkg, k, v);\n }\n });\n if (\n typeof rawPkg.author === 'object' &&\n typeof rawPkg.author?.name == 'string'\n ) {\n pkg.author = rawPkg.author.name;\n }\n if (typeof rawPkg.bugs === 'object' && typeof rawPkg.bugs?.url == 'string') {\n pkg.bugs = rawPkg.bugs.url;\n }\n if (\n typeof rawPkg.repository === 'object' &&\n typeof rawPkg.repository?.url == 'string'\n ) {\n const { url } = rawPkg.repository;\n if (url.startsWith('http')) {\n pkg.repository = url;\n } else if (url.startsWith('git+http')) {\n pkg.repository = url.substring(4);\n }\n }\n return pkg;\n};\n\nconst isScopePkg = (name: string): boolean => name.startsWith('@');\nconst resolveModuleFromPath = async (\n subpath: string,\n): Promise<string | undefined> => {\n const p = normalizePath(process.cwd()).split('/');\n for (let i = p.length; i > 0; i--) {\n const p2 = `${p.slice(0, i).join('/')}/node_modules/${subpath}`;\n if (await existFile(p2)) {\n return p2;\n }\n }\n};\n\nconst compatResolveModulePath = async (id: string): Promise<string> => {\n try {\n return compatResolve(id);\n } catch (e) {\n // not defined in pkg/package.json but exist in pkg/subpath\n // https://github.com/lisonge/vite-plugin-monkey/issues/169\n const r = await resolveModuleFromPath(id);\n if (!r) {\n throw e;\n }\n return r;\n }\n};\n\nexport const getModuleRealInfo = async (importName: string) => {\n const nameNoQuery = normalizePath(importName.split('?')[0]);\n const resolveName = await (async () => {\n const n = normalizePath(await compatResolveModulePath(nameNoQuery)).replace(\n /.*\\/node_modules\\/[^/]+\\//,\n '',\n );\n if (isScopePkg(importName)) {\n return n.split('/').slice(1).join('/');\n }\n return n;\n })();\n let version: string | undefined = undefined;\n const nameList = nameNoQuery.split('/');\n let name = nameNoQuery;\n while (nameList.length > 0) {\n name = nameList.join('/');\n const filePath = await (async () => {\n const p = await resolveModuleFromPath(`${name}/package.json`);\n if (p) {\n return p;\n }\n try {\n return compatResolve(`${name}/package.json`);\n } catch {\n return undefined;\n }\n })();\n if (filePath === undefined || !(await existFile(filePath))) {\n nameList.pop();\n continue;\n }\n const modulePack: PackageJson = JSON.parse(\n await fs.readFile(filePath, 'utf-8'),\n );\n version = modulePack.version;\n break;\n }\n if (version === undefined) {\n console.warn(\n `[plugin-monkey] not found module ${nameNoQuery} version, use ${nameNoQuery}@latest`,\n );\n name = nameNoQuery;\n version = 'latest';\n }\n return { version, name, resolveName };\n};\n","import type { Plugin } from 'vite';\nimport { miniCode } from '../utils/others';\n\nconst cssLoader = (name: string) => {\n // @ts-ignore\n const css = GM_getResourceText(name);\n // @ts-ignore\n GM_addStyle(css);\n return css;\n};\n\nconst jsonLoader = (name: string): unknown =>\n // @ts-ignore\n JSON.parse(GM_getResourceText(name));\n\nconst urlLoader = (name: string, type: string) =>\n // @ts-ignore\n GM_getResourceURL(name, false).replace(\n /^data:application;base64,/,\n `data:${type};base64,`,\n );\n\nconst rawLoader = (name: string) =>\n // @ts-ignore\n GM_getResourceText(name);\n\nconst loaderCode = [\n `export const cssLoader = ${cssLoader}`,\n `export const jsonLoader = ${jsonLoader}`,\n `export const urlLoader = ${urlLoader}`,\n `export const rawLoader = ${rawLoader}`,\n].join(';');\n\nexport const externalLoaderFactory = (): Plugin => {\n return {\n name: 'monkey:externalLoader',\n apply: 'build',\n async resolveId(id) {\n if (id == 'virtual:plugin-monkey-loader') {\n return '\\0' + id;\n }\n },\n async load(id) {\n if (id == '\\0virtual:plugin-monkey-loader') {\n return miniCode(loaderCode, 'js');\n }\n },\n };\n};\n","import { normalizePath } from 'vite';\nimport { miniCode } from '../utils/others';\nimport { getModuleRealInfo } from '../utils/pkg';\nimport type { PkgOptions, ResolvedMonkeyOption } from '../utils/types';\nimport type { Plugin, ResolvedConfig } from 'vite';\n\nconst resourceImportPrefix = '\\0monkey-resource-import:';\n\nexport const externalResourcePlugin = (\n getOption: () => Promise<ResolvedMonkeyOption>,\n): Plugin => {\n let option: ResolvedMonkeyOption;\n let viteConfig: ResolvedConfig;\n let mrmime: typeof import('mrmime');\n const resourceRecord: Record<\n string,\n { resourceName: string; resourceUrl: string }\n > = {};\n return {\n name: 'monkey:externalResource',\n enforce: 'pre',\n apply: 'build',\n async config() {\n option = await getOption();\n mrmime = await import('mrmime');\n },\n configResolved(config) {\n viteConfig = config;\n },\n async resolveId(id) {\n const { externalResource } = option.build;\n if (id in externalResource) {\n return resourceImportPrefix + id + '\\0';\n }\n // see https://github.com/vitejs/vite/blob/5d56e421625b408879672a1dd4e774bae3df674f/packages/vite/src/node/plugins/css.ts#L431-L434\n const [resource, query] = id.split('?', 2);\n if (resource.endsWith('.css') && query) {\n const id2 = [\n resource,\n '?',\n query\n .split('&')\n .filter((e) => e != 'used')\n .join(`&`),\n ].join('');\n if (id2 in externalResource) {\n return resourceImportPrefix + id2 + '\\0';\n }\n }\n },\n async load(id) {\n if (id.startsWith(resourceImportPrefix) && id.endsWith('\\0')) {\n const { externalResource } = option.build;\n const importName = id.substring(\n resourceImportPrefix.length,\n id.length - 1,\n );\n if (!(importName in externalResource)) {\n return;\n }\n const pkg = await getModuleRealInfo(importName);\n const {\n resourceName: resourceNameFn,\n resourceUrl: resourceUrlFn,\n loader,\n nodeLoader,\n } = externalResource[importName];\n const resourceName = await resourceNameFn({ ...pkg, importName });\n const resourceUrl = await resourceUrlFn({ ...pkg, importName });\n resourceRecord[importName] = {\n resourceName,\n resourceUrl,\n };\n\n if (nodeLoader) {\n return miniCode(\n await nodeLoader({\n ...pkg,\n resourceName,\n resourceUrl,\n importName,\n }),\n );\n } else if (loader) {\n let fnText: string;\n if (\n loader.prototype && // not arrow function\n loader.name.length > 0 &&\n loader.name != 'function' // not anonymous function\n ) {\n if (Reflect.get(loader, Symbol.toStringTag) == 'AsyncFunction') {\n fnText = loader\n .toString()\n .replace(/^[\\s\\S]+?\\(/, 'async function(');\n } else {\n fnText = loader.toString().replace(/^[\\s\\S]+?\\(/, 'function(');\n }\n } else {\n fnText = loader.toString();\n }\n return miniCode(\n `export default (${fnText})(${JSON.stringify({\n resourceUrl,\n importName,\n ...pkg,\n } as PkgOptions)})`,\n );\n }\n\n let moduleCode: string | undefined = undefined;\n const [resource, query] = importName.split('?', 2);\n const ext = resource.split('.').pop()!;\n const mimeType = mrmime.lookup(ext) ?? 'application/octet-stream';\n const suffixSet = new URLSearchParams(query);\n if (suffixSet.has('url') || suffixSet.has('inline')) {\n moduleCode = [\n `import {urlLoader as loader} from 'virtual:plugin-monkey-loader'`,\n `export default loader(...${JSON.stringify([\n resourceName,\n mimeType,\n ])})`,\n ].join(';');\n } else if (suffixSet.has('raw')) {\n moduleCode = [\n `import {rawLoader as loader} from 'virtual:plugin-monkey-loader'`,\n `export default loader(...${JSON.stringify([resourceName])})`,\n ].join(';');\n } else if (ext == 'json') {\n // export name will bring side effect\n moduleCode = [\n `import {jsonLoader as loader} from 'virtual:plugin-monkey-loader'`,\n `export default loader(...${