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 157 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/css.ts","../../src/node/plugins/config.ts","../../src/node/plugins/externalGlobals.ts","../../src/node/utils/pkg.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/style.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 getCssModuleCode,\n miniCode,\n moduleExportExpressionWrapper,\n removeComment,\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';\nimport { cssModuleId, virtualCssModuleId } from './css';\n\nconst __entry_name = `__monkey.entry.js`;\nconst cssModuleEntryId = cssModuleId + `-entry`;\nconst virtualCssModuleEntryId = '\\0' + cssModuleEntryId;\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 cssCode = Object.entries(rawBundle)\n .map(([k, v]) => {\n if (v.type == 'asset' && k.endsWith('.css')) {\n delete rawBundle[k];\n return v.source.toString();\n }\n })\n .filter(Boolean)\n .join('')\n .trim();\n\n let cssJsCode = '';\n const entryCode = (() => {\n const e = Array.from(entryChunks);\n const codes: string[] = [];\n if (cssCode) {\n if (e[0].facadeModuleId === polyfillId) {\n codes.push(`import ${JSON.stringify(`./${e[0].fileName}`)};`);\n e.shift();\n }\n codes.push(`import '${cssModuleEntryId}';`);\n }\n codes.push(...e.map((c) => `import './${c.fileName}';`));\n return codes.join('\\n');\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 if (source === cssModuleEntryId) return virtualCssModuleEntryId;\n if (source === cssModuleId) return virtualCssModuleId;\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 if (id === virtualCssModuleEntryId) {\n // use \\x20 compat unocss\n return miniCode(\n `import css from '${cssModuleId}'; css(${JSON.stringify('\\x20' + cssCode + '\\x20')});`,\n );\n }\n if (id === virtualCssModuleId) {\n cssJsCode = getCssModuleCode(option.cssSideEffects);\n return miniCode(cssJsCode);\n }\n if (id.endsWith(__entry_name)) {\n return entryCode;\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: null,\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: Object.keys(option.globalsPkg2VarName),\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 if (!viteConfig.build.minify) {\n finalJsCode = await removeComment(finalJsCode);\n }\n let collectGrantSet: Set<string>;\n if (option.build.autoGrant) {\n collectGrantSet = collectGrant(\n this,\n chunks,\n cssJsCode,\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, 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 'GM_audio',\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 'GM.audio',\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 * as acornWalk from 'acorn-walk';\nimport { resolve } from 'import-meta-resolve';\nimport fs from 'node:fs/promises';\nimport path from 'node:path';\nimport { pathToFileURL } from 'node:url';\nimport type { ProgramNode } from 'rollup';\nimport { transformWithEsbuild } from 'vite';\nimport type { Thenable } 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 (\n code: Thenable<string>,\n type: 'css' | 'js' = 'js',\n) => {\n return (\n await transformWithEsbuild(await 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 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 type { ImportDeclaration, ImportExpression } from 'acorn';\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\nexport const getSafeIdentifier = (\n prefix: string,\n code: string,\n others?: string[],\n): string => {\n let n = 0;\n let identifier = prefix;\n while (\n code.includes(identifier) ||\n (others && others.some((c) => c.includes(identifier)))\n ) {\n n++;\n identifier = `${prefix}${n.toString(16)}`;\n }\n return identifier;\n};\n\nexport interface ImportNodeItem {\n node: ImportExpression | ImportDeclaration;\n value: string;\n}\n\nexport const getProgramImportNodes = (\n program: ProgramNode,\n): ImportNodeItem[] => {\n const nodes: ImportNodeItem[] = [];\n acornWalk.simple(program, {\n ImportDeclaration(node) {\n const s = node.source;\n if (s.type === 'Literal') {\n const value = s.value;\n if (!value) return;\n if (typeof value !== 'string') return;\n nodes.push({\n node,\n value,\n });\n }\n },\n ImportExpression(node) {\n const s = node.source;\n if (s.type === 'Literal') {\n const value = s.value;\n if (!value) return;\n if (typeof value !== 'string') return;\n nodes.push({\n node,\n value,\n });\n } else if (s.type === 'TemplateLiteral') {\n if (s.expressions.length) return;\n if (s.quasis.length !== 1) return;\n const value = s.quasis[0].value.cooked;\n if (!value) return;\n if (typeof value !== 'string') return;\n nodes.push({\n node,\n value,\n });\n }\n },\n });\n return nodes;\n};\n\nconst nameReg = /[0-9a-zA-Z_]+/g;\nconst autoPreUnderline = (v: string): string => {\n if (!v) return '_';\n return Number.isInteger(Number(v[0])) ? `_${v}` : v;\n};\n\nexport const getUpperCaseName = (\n value: string | undefined,\n): string | undefined => {\n if (!value) return;\n const list = value.match(nameReg);\n if (!list?.length) return;\n return list\n .map((v, i) => {\n if (i === 0) return autoPreUnderline(v);\n return v[0].toUpperCase() + v.substring(1);\n })\n .join('');\n};\n\nconst defaultCssSideEffects = (css: string) => {\n // @ts-ignore\n if (typeof GM_addStyle === 'function') {\n // @ts-ignore\n GM_addStyle(css);\n } else {\n document.head.appendChild(document.createElement('style')).append(css);\n }\n};\n\nexport const getCssModuleCode = (\n f: string | ((css: string) => void) | undefined,\n): string => {\n f ??= defaultCssSideEffects;\n return `\nconst set = new Set();\nexport default async (css) => {\n if (set.has(css)) return;\n set.add(css);\n ${`(${f})(css);`}\n};\n`;\n};\n\nexport const removeComment = async (code: string): Promise<string> => {\n if (!(code.includes('/*') || code.includes('//'))) return code;\n const ranges: [number, number][] = [];\n (await import('acorn')).parse(code, {\n ecmaVersion: 'latest',\n sourceType: 'module',\n onComment(_isBlock, text, start, end) {\n // https://esbuild.github.io/api/#legal-comments\n // remove /*! #__NO_SIDE_EFFECTS__ */\n if (text.includes('@license')) return;\n if (text.includes('@preserve')) return;\n while (start > 0 && !code[start - 1].trim()) {\n start--;\n }\n while (end < code.length && !code[end].trim()) {\n end++;\n }\n ranges.push([start, end]);\n },\n });\n if (!ranges.length) return code;\n const getSeparator = (start: number, end: number): string => {\n while (start < end) {\n if (code[start] === '\\n') return '\\n';\n start++;\n }\n return '\\x20';\n };\n let newCode = '';\n let lastIndex = 0;\n for (const [start, end] of ranges) {\n newCode += code.slice(lastIndex, start) + getSeparator(start, end);\n lastIndex = end;\n }\n newCode += code.slice(lastIndex);\n return newCode;\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 MagicString from 'magic-string';\nimport fs from 'node:fs/promises';\nimport type { TransformPluginContext } from 'rollup';\nimport type { Plugin } from 'vite';\nimport {\n getProgramImportNodes,\n getSafeIdentifier,\n getUpperCaseName,\n} from '../utils/others';\nimport type { ResolvedMonkeyOption } from '../utils/types';\n\nexport const cssModuleId = 'virtual:monkey-css-side-effects';\nexport const virtualCssModuleId = '\\0' + cssModuleId;\n\n// https://github.com/vitejs/vite/blob/v7.1.2/packages/vite/src/node/constants.ts#L97\nconst styleExts = [\n '.css',\n '.less',\n '.sass',\n '.scss',\n '.styl',\n '.stylus',\n '.pcss',\n '.postcss',\n '.sss',\n];\nconst exludeModuleCssExts = styleExts.map((v) => '.module' + v);\n\nconst appendInline = (value: string): string => {\n return value + '?inline';\n};\nconst exlcudeChars = ['\\0', '?', '&'];\nconst exlcudeModuleNames = [\n // context.resolve() -> [plugin unocss:global:build:scan] [unocss] \"uno.css\" is being imported multiple times in different files\n 'uno.css',\n];\n\nconst filterAsync = async <T>(\n arr: T[],\n predicate: (value: T, index: number, array: T[]) => Promise<unknown>,\n): Promise<T[]> => {\n const results = await Promise.all(arr.map(predicate));\n return arr.filter((_, index) => results[index]);\n};\n\nconst staticCssIdSuffix = '__plugin-monkey-static-css';\n// https://github.com/lisonge/vite-plugin-monkey/issues/249\nconst staticCssTemplate = `\nimport {0} from '{1}';\nimport importCSS from '${cssModuleId}';\n{0} && importCSS({0});\nexport default undefined;\n`.trimStart();\n\nexport const cssFactory = (\n getOption: () => Promise<ResolvedMonkeyOption>,\n): Plugin => {\n let option: ResolvedMonkeyOption;\n const isCssImport = async (\n context: TransformPluginContext,\n importer: string,\n value: string,\n ): Promise<boolean> => {\n if (!value) return false;\n // exclude virtual modules, such as virtual:uno.css\n if (value.startsWith('virtual:')) return false;\n if (exlcudeModuleNames.includes(value)) return false;\n if (exlcudeChars.some((c) => value.includes(c))) return false;\n if (exludeModuleCssExts.some((c) => value.endsWith(c))) return false;\n if (option.build.externalResource[value]) return false;\n const resolvedId = (await context.resolve(value, importer))?.id;\n if (!resolvedId) return false;\n if (exlcudeChars.some((c) => resolvedId.includes(c))) return false;\n if (exludeModuleCssExts.some((c) => resolvedId.endsWith(c))) return false;\n if (!styleExts.some((e) => resolvedId.endsWith(e))) return false;\n // exclude not exist file, such as /project/src/__unocss.css\n return fs\n .access(resolvedId)\n .then(() => true)\n .catch(() => false);\n };\n return {\n name: 'monkey:css',\n apply: 'build',\n enforce: 'post',\n async config() {\n option = await getOption();\n return {\n build: {\n rollupOptions: {\n external: [cssModuleId],\n },\n },\n };\n },\n resolveId(source) {\n if (source.endsWith(staticCssIdSuffix)) return source;\n },\n load(id) {\n // handle static css\n if (!id.endsWith(staticCssIdSuffix)) return;\n const staticId = id.slice(0, -staticCssIdSuffix.length);\n return staticCssTemplate\n .replaceAll(\n '{0}',\n getUpperCaseName(staticId.split('/').at(-1)) || 'css',\n )\n .replaceAll('{1}', appendInline(staticId));\n },\n async transform(code, id) {\n if (new URLSearchParams(id.split('?')[1] || '').has('inline')) return;\n if (!code.includes('import')) return;\n if (!styleExts.some((e) => code.includes(e))) return;\n const importedCssNodes = await filterAsync(\n getProgramImportNodes(this.parse(code)).filter((n) => {\n if (n.node.type === 'ImportDeclaration' && n.node.specifiers.length) {\n return false;\n }\n return true;\n }),\n async (n) => isCssImport(this, id, n.value),\n );\n if (!importedCssNodes.length) return;\n const ms = new MagicString(code);\n const loadName = getSafeIdentifier('importCSS', code);\n const importList: string[] = [\n `import ${loadName} from '${cssModuleId}';`,\n ];\n const nameCache: Record<string, string> = {};\n for (const n of importedCssNodes) {\n if (n.node.type === 'ImportExpression') {\n const inlineCssId = appendInline(n.value);\n if (!nameCache[inlineCssId]) {\n const cssName = getSafeIdentifier(\n getUpperCaseName(n.value) || 'css',\n code,\n importList,\n );\n nameCache[inlineCssId] = cssName;\n importList.push(`import ${cssName} from '${inlineCssId}';`);\n }\n ms.update(\n n.node.start,\n n.node.end,\n `${loadName}(${nameCache[inlineCssId]})`,\n );\n } else {\n const resolved = (await this.resolve(n.value, id))?.id;\n if (!resolved) continue;\n const staticCssId = resolved + staticCssIdSuffix;\n ms.update(n.node.start, n.node.end, `import '${staticCssId}';`);\n }\n }\n ms.prepend(importList.join('\\n'));\n return {\n code: ms.toString(),\n map: ms.generateMap(),\n };\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