vite-userscript-plugin 1.11.0 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +161 -98
- package/dist/index.d.ts +264 -180
- package/dist/index.js +1032 -282
- package/package.json +51 -44
- package/types/greasemonkey.d.ts +267 -270
- package/types/tampermonkey.d.ts +1096 -954
- package/types/violentmonkey-ambient.d.ts +149 -0
- package/types/violentmonkey.d.ts +319 -186
- package/virtual.d.ts +7 -0
- package/dist/ws.js +0 -14
- package/types/README.md +0 -21
package/dist/index.js
CHANGED
|
@@ -1,295 +1,1045 @@
|
|
|
1
|
-
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import
|
|
1
|
+
import { basename, posix, relative, resolve, sep } from "node:path";
|
|
2
|
+
import { build } from "vite";
|
|
3
|
+
import { Buffer } from "node:buffer";
|
|
4
|
+
import { pathToFileURL } from "node:url";
|
|
5
|
+
import { existsSync } from "node:fs";
|
|
6
|
+
import { styleText } from "node:util";
|
|
6
7
|
import openLink from "open";
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
8
|
+
//#region src/names.ts
|
|
9
|
+
function sanitizeFileName(name) {
|
|
10
|
+
return name.replace(/[<>:"/\\|?*\u0000-\u001F]+/g, "-").replace(/\s+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "") || "userscript";
|
|
11
|
+
}
|
|
12
|
+
function toIdentifier(name) {
|
|
13
|
+
let id = name.replace(/[^\w$]/g, "_").replace(/_+/g, "_").replace(/^_|_$/g, "");
|
|
14
|
+
if (/^\d/.test(id)) id = `_${id}`;
|
|
15
|
+
return id || "userscript";
|
|
16
|
+
}
|
|
17
|
+
//#endregion
|
|
18
|
+
//#region src/header.ts
|
|
19
|
+
function ensureTrailingSlash(url) {
|
|
20
|
+
return url.endsWith("/") ? url : `${url}/`;
|
|
21
|
+
}
|
|
22
|
+
function resolveHomePage(header) {
|
|
23
|
+
const homePage = header.homepage ?? header.homepageURL ?? header.website ?? header.source;
|
|
24
|
+
if (typeof homePage !== "string") return;
|
|
25
|
+
const trimmed = homePage.trim();
|
|
26
|
+
return trimmed === "" ? void 0 : trimmed;
|
|
27
|
+
}
|
|
28
|
+
function resolvePublicFileUrl(header, fileName) {
|
|
29
|
+
const homePage = resolveHomePage(header);
|
|
30
|
+
if (!homePage) return;
|
|
31
|
+
try {
|
|
32
|
+
return new URL(fileName, ensureTrailingSlash(homePage)).href;
|
|
33
|
+
} catch {}
|
|
34
|
+
}
|
|
35
|
+
function applyAutoMetaUrls(header, fileName) {
|
|
36
|
+
const updateURL = header.updateURL ?? resolvePublicFileUrl(header, `${fileName}.meta.js`);
|
|
37
|
+
const downloadURL = header.downloadURL ?? resolvePublicFileUrl(header, `${fileName}.user.js`);
|
|
38
|
+
if (!updateURL && !downloadURL) return header;
|
|
39
|
+
return {
|
|
40
|
+
...header,
|
|
41
|
+
...updateURL ? { updateURL } : {},
|
|
42
|
+
...downloadURL ? { downloadURL } : {}
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
function sanitizeMetaText(text) {
|
|
46
|
+
return text.replace(/[\r\n\u2028\u2029]+/g, " ");
|
|
47
|
+
}
|
|
48
|
+
function formatValue(value) {
|
|
49
|
+
if (Array.isArray(value)) return sanitizeMetaText(value.join(" "));
|
|
50
|
+
if (value === true) return "";
|
|
51
|
+
if (typeof value === "object") return;
|
|
52
|
+
return sanitizeMetaText(String(value));
|
|
53
|
+
}
|
|
54
|
+
function generateHeader(config, options = {}) {
|
|
55
|
+
const fileName = options.fileName ?? sanitizeFileName(config.name);
|
|
56
|
+
const header = options.autoMetaUrls ? applyAutoMetaUrls({ ...config }, fileName) : { ...config };
|
|
57
|
+
const keys = Object.keys(header).filter((key) => {
|
|
58
|
+
const value = header[key];
|
|
59
|
+
return value !== void 0 && value !== null && value !== false;
|
|
60
|
+
});
|
|
61
|
+
const align = options.align;
|
|
62
|
+
const maxKeyLength = align === false ? 0 : Math.max(...keys.map((key) => key.length), 0) + (align ?? 1);
|
|
63
|
+
const pad = (key) => {
|
|
64
|
+
if (align === false) return " ";
|
|
65
|
+
return " ".repeat(Math.max(1, maxKeyLength - key.length));
|
|
66
|
+
};
|
|
67
|
+
const lines = [];
|
|
68
|
+
const addMetadata = (key, value) => {
|
|
69
|
+
const formatted = formatValue(value);
|
|
70
|
+
if (formatted === void 0) return;
|
|
71
|
+
const safeKey = key.replace(/[\r\n\u2028\u2029]+/g, "");
|
|
72
|
+
if (!safeKey) return;
|
|
73
|
+
lines.push(`// @${safeKey}${pad(key)}${formatted}`);
|
|
74
|
+
};
|
|
75
|
+
for (const key of keys) {
|
|
76
|
+
const value = header[key];
|
|
77
|
+
if (Array.isArray(value)) value.forEach((item) => addMetadata(key, item));
|
|
78
|
+
else addMetadata(key, value);
|
|
79
|
+
}
|
|
80
|
+
const userscript = [
|
|
81
|
+
"// ==UserScript==",
|
|
82
|
+
...lines,
|
|
83
|
+
"// ==/UserScript=="
|
|
84
|
+
].join("\n");
|
|
85
|
+
if (!options.generate) return userscript;
|
|
86
|
+
return options.generate({
|
|
87
|
+
userscript,
|
|
88
|
+
mode: options.mode ?? "build"
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
var Header = class {
|
|
92
|
+
config;
|
|
93
|
+
options;
|
|
94
|
+
constructor(config, options = {}) {
|
|
95
|
+
this.config = config;
|
|
96
|
+
this.options = options;
|
|
97
|
+
}
|
|
98
|
+
generate() {
|
|
99
|
+
return generateHeader(this.config, this.options);
|
|
100
|
+
}
|
|
56
101
|
};
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
102
|
+
//#endregion
|
|
103
|
+
//#region src/grants/catalog.ts
|
|
104
|
+
const GM = [
|
|
105
|
+
"setValue",
|
|
106
|
+
"getValue",
|
|
107
|
+
"deleteValue",
|
|
108
|
+
"listValues",
|
|
109
|
+
"setValues",
|
|
110
|
+
"getValues",
|
|
111
|
+
"deleteValues",
|
|
112
|
+
"setClipboard",
|
|
113
|
+
"addStyle",
|
|
114
|
+
"addElement",
|
|
115
|
+
"addValueChangeListener",
|
|
116
|
+
"removeValueChangeListener",
|
|
117
|
+
"registerMenuCommand",
|
|
118
|
+
"unregisterMenuCommand",
|
|
119
|
+
"download",
|
|
120
|
+
"getTab",
|
|
121
|
+
"getTabs",
|
|
122
|
+
"saveTab",
|
|
123
|
+
"openInTab",
|
|
124
|
+
"notification",
|
|
125
|
+
"getResourceURL",
|
|
126
|
+
"getResourceText",
|
|
127
|
+
"xmlhttpRequest",
|
|
128
|
+
"webRequest",
|
|
129
|
+
"cookie",
|
|
130
|
+
"audio",
|
|
131
|
+
"log",
|
|
132
|
+
"info"
|
|
87
133
|
];
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
134
|
+
const GMwindow = [
|
|
135
|
+
"unsafeWindow",
|
|
136
|
+
"window.onurlchange",
|
|
137
|
+
"window.focus",
|
|
138
|
+
"window.close"
|
|
93
139
|
];
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
140
|
+
const GM_DOT_ALIASES = ["GM.xmlHttpRequest", "GM.getResourceUrl"];
|
|
141
|
+
const grants = [
|
|
142
|
+
...GM.flatMap((grant) => [`GM_${grant}`, `GM.${grant}`]),
|
|
143
|
+
...GMwindow,
|
|
144
|
+
...GM_DOT_ALIASES
|
|
145
|
+
];
|
|
146
|
+
const gmIdentifiers = [
|
|
147
|
+
"GM",
|
|
148
|
+
"unsafeWindow",
|
|
149
|
+
...GM.map((grant) => `GM_${grant}`)
|
|
150
|
+
];
|
|
151
|
+
//#endregion
|
|
152
|
+
//#region src/grants/scan.ts
|
|
153
|
+
const grantMatchers = grants.map((grant) => ({
|
|
154
|
+
grant,
|
|
155
|
+
pattern: new RegExp(`\\b${grant.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\b`)
|
|
156
|
+
}));
|
|
102
157
|
function removeDuplicates(arr) {
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
async function transform({ minify, file, name, loader }, transformOptions) {
|
|
106
|
-
const { code } = await transformWithEsbuild(file, name, {
|
|
107
|
-
minify,
|
|
108
|
-
loader,
|
|
109
|
-
sourcemap: false,
|
|
110
|
-
legalComments: "none",
|
|
111
|
-
...transformOptions
|
|
112
|
-
});
|
|
113
|
-
return code;
|
|
158
|
+
if (Array.isArray(arr)) return [...new Set(arr)];
|
|
159
|
+
return arr ? [arr] : [];
|
|
114
160
|
}
|
|
115
161
|
function defineGrants(code) {
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
162
|
+
return grantMatchers.filter(({ pattern }) => pattern.test(code)).map(({ grant }) => grant);
|
|
163
|
+
}
|
|
164
|
+
//#endregion
|
|
165
|
+
//#region src/grants/policy.ts
|
|
166
|
+
function withServeGrants(header) {
|
|
167
|
+
if (header.grant === "none") return header;
|
|
168
|
+
return {
|
|
169
|
+
...header,
|
|
170
|
+
grant: [.../* @__PURE__ */ new Set([...header.grant ?? [], ...grants])]
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
function withBuildGrants(header, code, extraGrants = []) {
|
|
174
|
+
if (header.grant === "none") return header;
|
|
175
|
+
return {
|
|
176
|
+
...header,
|
|
177
|
+
grant: removeDuplicates([
|
|
178
|
+
...defineGrants(code),
|
|
179
|
+
...removeDuplicates(header.grant),
|
|
180
|
+
...extraGrants
|
|
181
|
+
])
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
//#endregion
|
|
185
|
+
//#region src/sourcemap.ts
|
|
186
|
+
function countHeaderLines(prefix) {
|
|
187
|
+
if (!prefix) return 0;
|
|
188
|
+
return prefix.endsWith("\n") ? prefix.slice(0, -1).split("\n").length : prefix.split("\n").length;
|
|
189
|
+
}
|
|
190
|
+
const VLQ_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
191
|
+
function encodeVlq(value) {
|
|
192
|
+
let vlq = value < 0 ? -value << 1 | 1 : value << 1;
|
|
193
|
+
let encoded = "";
|
|
194
|
+
do {
|
|
195
|
+
let digit = vlq & 31;
|
|
196
|
+
vlq >>>= 5;
|
|
197
|
+
if (vlq > 0) digit |= 32;
|
|
198
|
+
encoded += VLQ_ALPHABET[digit];
|
|
199
|
+
} while (vlq > 0);
|
|
200
|
+
return encoded;
|
|
201
|
+
}
|
|
202
|
+
function isTokenBoundary(line, index) {
|
|
203
|
+
if (index === 0) return true;
|
|
204
|
+
return /\w/.test(line[index] ?? "") !== /\w/.test(line[index - 1] ?? "");
|
|
205
|
+
}
|
|
206
|
+
function identitySourceMap(code, file) {
|
|
207
|
+
const lineCount = countHeaderLines(code);
|
|
208
|
+
const sourceLines = code.split("\n");
|
|
209
|
+
let previousOriginalLine = 0;
|
|
210
|
+
let previousOriginalColumn = 0;
|
|
211
|
+
return {
|
|
212
|
+
version: 3,
|
|
213
|
+
file,
|
|
214
|
+
mappings: Array.from({ length: lineCount }, (_, originalLine) => {
|
|
215
|
+
const line = sourceLines[originalLine] ?? "";
|
|
216
|
+
let previousGeneratedColumn = 0;
|
|
217
|
+
const segments = [];
|
|
218
|
+
const emit = (column) => {
|
|
219
|
+
segments.push(encodeVlq(column - previousGeneratedColumn) + encodeVlq(0) + encodeVlq(originalLine - previousOriginalLine) + encodeVlq(column - previousOriginalColumn));
|
|
220
|
+
previousGeneratedColumn = column;
|
|
221
|
+
previousOriginalLine = originalLine;
|
|
222
|
+
previousOriginalColumn = column;
|
|
223
|
+
};
|
|
224
|
+
emit(0);
|
|
225
|
+
for (let column = 1; column < line.length; column++) if (isTokenBoundary(line, column)) emit(column);
|
|
226
|
+
return segments.join(",");
|
|
227
|
+
}).join(";"),
|
|
228
|
+
names: [],
|
|
229
|
+
sources: file ? [file] : []
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
function offsetSourceMap(map, lineOffset, fileName) {
|
|
233
|
+
if (lineOffset <= 0) return fileName ? {
|
|
234
|
+
...map,
|
|
235
|
+
file: fileName
|
|
236
|
+
} : map;
|
|
237
|
+
return {
|
|
238
|
+
...map,
|
|
239
|
+
file: fileName ?? map.file,
|
|
240
|
+
mappings: `${";".repeat(lineOffset)}${map.mappings}`
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
function isAppSource(source) {
|
|
244
|
+
return !source.includes("node_modules") && !source.includes("\0") && !source.startsWith("virtual:");
|
|
245
|
+
}
|
|
246
|
+
function stripVendorSourcesContent(map) {
|
|
247
|
+
const sources = map.sources ?? [];
|
|
248
|
+
return {
|
|
249
|
+
...map,
|
|
250
|
+
sourcesContent: sources.map((source, index) => isAppSource(source ?? "") ? map.sourcesContent?.[index] ?? null : null)
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
function toInlineSourceMappingUrl(map) {
|
|
254
|
+
return `data:application/json;charset=utf-8;base64,${Buffer.from(JSON.stringify(map)).toString("base64")}`;
|
|
255
|
+
}
|
|
256
|
+
//#endregion
|
|
257
|
+
//#region src/build/bundle.ts
|
|
258
|
+
function isChunk(item) {
|
|
259
|
+
return item.type === "chunk";
|
|
260
|
+
}
|
|
261
|
+
function isAsset(item) {
|
|
262
|
+
return item.type === "asset";
|
|
263
|
+
}
|
|
264
|
+
//#endregion
|
|
265
|
+
//#region src/build/css.ts
|
|
266
|
+
const defaultCssInjector = `(function (css) {
|
|
267
|
+
if (typeof GM_addStyle === 'function') {
|
|
268
|
+
GM_addStyle(css)
|
|
269
|
+
} else {
|
|
270
|
+
var style = document.createElement('style')
|
|
271
|
+
style.textContent = css
|
|
272
|
+
;(document.head || document.documentElement).appendChild(style)
|
|
121
273
|
}
|
|
122
|
-
|
|
274
|
+
})`;
|
|
275
|
+
function collectImportedCss(chunk, bundle, seen = /* @__PURE__ */ new Set()) {
|
|
276
|
+
const files = [...chunk.viteMetadata?.importedCss ?? []];
|
|
277
|
+
for (const imported of chunk.imports) {
|
|
278
|
+
if (seen.has(imported)) continue;
|
|
279
|
+
const dep = bundle[imported];
|
|
280
|
+
if (!dep || !isChunk(dep) || dep.isEntry) continue;
|
|
281
|
+
seen.add(imported);
|
|
282
|
+
files.push(...collectImportedCss(dep, bundle, seen));
|
|
283
|
+
}
|
|
284
|
+
return files;
|
|
123
285
|
}
|
|
286
|
+
function collectCss(chunk, bundle) {
|
|
287
|
+
const files = [...new Set(collectImportedCss(chunk, bundle))];
|
|
288
|
+
if (!files.length) return {
|
|
289
|
+
css: "",
|
|
290
|
+
files
|
|
291
|
+
};
|
|
292
|
+
return {
|
|
293
|
+
css: files.map((file) => {
|
|
294
|
+
const asset = bundle[file];
|
|
295
|
+
return asset && isAsset(asset) ? String(asset.source) : "";
|
|
296
|
+
}).filter(Boolean).join("\n"),
|
|
297
|
+
files
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
function createCssInject(css, cssInject = "auto") {
|
|
301
|
+
const payload = JSON.stringify(css);
|
|
302
|
+
if (cssInject === "auto") return `${defaultCssInjector}(${payload});\n`;
|
|
303
|
+
if (typeof cssInject === "function") return `(${cssInject.toString()})(${payload});\n`;
|
|
304
|
+
return `(${cssInject})(${payload});\n`;
|
|
305
|
+
}
|
|
306
|
+
//#endregion
|
|
307
|
+
//#region src/build/iife.ts
|
|
308
|
+
const SOURCE_MAPPING_URL_RE = /\/\/[#@]\s*sourceMappingURL=\S+/g;
|
|
309
|
+
function stripSourceMappingUrl(code) {
|
|
310
|
+
return code.replace(SOURCE_MAPPING_URL_RE, "");
|
|
311
|
+
}
|
|
312
|
+
function stripImports(code) {
|
|
313
|
+
return code.replace(/(^|\n)import(?:\s+type)?(?:\s+[\s\S]*?\s+|\s+)from\s*["'][^"']+["']\s*;?/g, "$1").replace(/(^|\n)import\s*["'][^"']+["']\s*;?/g, "$1");
|
|
314
|
+
}
|
|
315
|
+
function stripExports(code) {
|
|
316
|
+
return code.replace(/^export\s+\{[\s\S]*?\}\s+from\s+["'][^"']+["']\s*;?\s*$/gm, "").replace(/^export\s+\*\s+from\s+["'][^"']+["']\s*;?\s*$/gm, "").replace(/^export\s+\{[\s\S]*?\};?\s*$/gm, "").replace(/^export\s+default\s+/gm, "").replace(/^export\s+async\s+function/gm, "async function").replace(/^export\s+function/gm, "function").replace(/^export\s+class/gm, "class").replace(/^export\s+(const|let|var)/gm, "$1");
|
|
317
|
+
}
|
|
318
|
+
function stripModuleSyntax(code) {
|
|
319
|
+
return stripExports(stripImports(code));
|
|
320
|
+
}
|
|
321
|
+
function isAlreadyIife(code) {
|
|
322
|
+
return !/^\s*(?:import|export)\s/m.test(code) && /\(\s*(?:async\s+)?function\b/.test(code);
|
|
323
|
+
}
|
|
324
|
+
function ensureIife(code) {
|
|
325
|
+
const withoutMap = stripSourceMappingUrl(code);
|
|
326
|
+
if (isAlreadyIife(withoutMap)) return withoutMap.endsWith("\n") ? withoutMap : `${withoutMap}\n`;
|
|
327
|
+
const stripped = stripModuleSyntax(withoutMap);
|
|
328
|
+
return `(${/\bawait\b/.test(stripped) ? "async function" : "function"} () {\n${stripped}\n})();\n`;
|
|
329
|
+
}
|
|
330
|
+
//#endregion
|
|
331
|
+
//#region src/build/proxy.ts
|
|
332
|
+
function toFileRequireUrl(absPath) {
|
|
333
|
+
return pathToFileURL(absPath).href;
|
|
334
|
+
}
|
|
335
|
+
function toRequireList(value) {
|
|
336
|
+
if (value == null) return [];
|
|
337
|
+
return Array.isArray(value) ? value.map(String) : [String(value)];
|
|
338
|
+
}
|
|
339
|
+
function createWatchProxyHeader(script, jsAbsPath) {
|
|
340
|
+
const header = withServeGrants({ ...script.header });
|
|
341
|
+
return {
|
|
342
|
+
...header,
|
|
343
|
+
require: [...toRequireList(header.require), toFileRequireUrl(jsAbsPath)]
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
function generateWatchProxy(script, jsAbsPath) {
|
|
347
|
+
return generateHeader(createWatchProxyHeader(script, jsAbsPath), {
|
|
348
|
+
align: script.align,
|
|
349
|
+
autoMetaUrls: false,
|
|
350
|
+
fileName: script.fileName,
|
|
351
|
+
generate: script.generate,
|
|
352
|
+
mode: "serve"
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
function toProxyFileName(fileName) {
|
|
356
|
+
return `${fileName}.proxy.user.js`;
|
|
357
|
+
}
|
|
358
|
+
function toRequireFileName(fileName) {
|
|
359
|
+
return `${fileName}.js`;
|
|
360
|
+
}
|
|
361
|
+
//#endregion
|
|
362
|
+
//#region src/build/apply.ts
|
|
363
|
+
function importedChunkIds(chunk) {
|
|
364
|
+
return [...chunk.imports, ...chunk.dynamicImports ?? []];
|
|
365
|
+
}
|
|
366
|
+
function inlineImportedChunks(chunk, bundle, seen = /* @__PURE__ */ new Set()) {
|
|
367
|
+
let prelude = "";
|
|
368
|
+
for (const imported of chunk.imports) {
|
|
369
|
+
if (seen.has(imported)) continue;
|
|
370
|
+
const dep = bundle[imported];
|
|
371
|
+
if (!dep || !isChunk(dep) || dep.isEntry) continue;
|
|
372
|
+
seen.add(imported);
|
|
373
|
+
prelude += inlineImportedChunks(dep, bundle, seen);
|
|
374
|
+
prelude += dep.code.endsWith("\n") ? dep.code : `${dep.code}\n`;
|
|
375
|
+
}
|
|
376
|
+
return prelude;
|
|
377
|
+
}
|
|
378
|
+
function collectImportedChunks(chunk, bundle) {
|
|
379
|
+
const files = /* @__PURE__ */ new Set();
|
|
380
|
+
const walk = (current) => {
|
|
381
|
+
for (const imported of importedChunkIds(current)) {
|
|
382
|
+
if (files.has(imported)) continue;
|
|
383
|
+
const dep = bundle[imported];
|
|
384
|
+
if (!dep || !isChunk(dep) || dep.isEntry) continue;
|
|
385
|
+
files.add(imported);
|
|
386
|
+
walk(dep);
|
|
387
|
+
}
|
|
388
|
+
};
|
|
389
|
+
walk(chunk);
|
|
390
|
+
return files;
|
|
391
|
+
}
|
|
392
|
+
function collectImportedCssFiles(fileNames, bundle) {
|
|
393
|
+
const files = /* @__PURE__ */ new Set();
|
|
394
|
+
for (const fileName of fileNames) {
|
|
395
|
+
const item = bundle[fileName];
|
|
396
|
+
if (!item || !isChunk(item)) continue;
|
|
397
|
+
for (const cssFile of item.viteMetadata?.importedCss ?? []) files.add(cssFile);
|
|
398
|
+
}
|
|
399
|
+
return files;
|
|
400
|
+
}
|
|
401
|
+
function withSourceMaps(fileNames) {
|
|
402
|
+
const files = [];
|
|
403
|
+
for (const fileName of fileNames) files.push(fileName, `${fileName}.map`);
|
|
404
|
+
return files;
|
|
405
|
+
}
|
|
406
|
+
function findScriptForChunk(chunk, fileName, scripts) {
|
|
407
|
+
return scripts.find((script) => chunk.name === script.fileName || fileName === `${script.fileName}.js` || fileName === `${script.fileName}.user.js`);
|
|
408
|
+
}
|
|
409
|
+
function deleteBundleFiles(bundle, fileNames) {
|
|
410
|
+
for (const fileName of fileNames) delete bundle[fileName];
|
|
411
|
+
}
|
|
412
|
+
function applyUserscriptBundle(bundle, config, context) {
|
|
413
|
+
const { emitFile } = context;
|
|
414
|
+
const userscriptEntries = [];
|
|
415
|
+
const otherEntryFiles = [];
|
|
416
|
+
for (const [fileName, item] of Object.entries(bundle)) {
|
|
417
|
+
if (!isChunk(item) || !item.isEntry) continue;
|
|
418
|
+
const script = findScriptForChunk(item, fileName, config.scripts);
|
|
419
|
+
if (script) userscriptEntries.push({
|
|
420
|
+
fileName,
|
|
421
|
+
chunk: item,
|
|
422
|
+
script
|
|
423
|
+
});
|
|
424
|
+
else otherEntryFiles.push(fileName);
|
|
425
|
+
}
|
|
426
|
+
const keptChunks = new Set(otherEntryFiles);
|
|
427
|
+
for (const fileName of otherEntryFiles) {
|
|
428
|
+
const chunk = bundle[fileName];
|
|
429
|
+
if (chunk && isChunk(chunk)) for (const dep of collectImportedChunks(chunk, bundle)) keptChunks.add(dep);
|
|
430
|
+
}
|
|
431
|
+
const keptCss = collectImportedCssFiles(keptChunks, bundle);
|
|
432
|
+
const leftoverChunks = [];
|
|
433
|
+
const leftoverAssets = [];
|
|
434
|
+
for (const { fileName, chunk, script } of userscriptEntries) {
|
|
435
|
+
const inlined = stripSourceMappingUrl(inlineImportedChunks(chunk, bundle));
|
|
436
|
+
const { css, files: cssFiles } = collectCss(chunk, bundle);
|
|
437
|
+
for (const cssFile of cssFiles) if (!keptCss.has(cssFile)) leftoverAssets.push(...withSourceMaps([cssFile]));
|
|
438
|
+
const cssPrelude = css ? createCssInject(css, script.cssInject) : "";
|
|
439
|
+
const body = `${inlined}${chunk.code}`;
|
|
440
|
+
const wrapped = ensureIife(body);
|
|
441
|
+
const extraGrants = css && script.cssInject === "auto" ? ["GM_addStyle"] : [];
|
|
442
|
+
const code = `${cssPrelude}${wrapped}`;
|
|
443
|
+
const emitFileProxy = Boolean(context.emitProxy && context.outDir && script.server.file);
|
|
444
|
+
leftoverAssets.push(`${fileName}.map`);
|
|
445
|
+
if (emitFileProxy) {
|
|
446
|
+
const requireName = toRequireFileName(script.fileName);
|
|
447
|
+
let nextCode = code.endsWith("\n") ? code : `${code}\n`;
|
|
448
|
+
if (chunk.map) {
|
|
449
|
+
const wrapOffset = isAlreadyIife(stripSourceMappingUrl(body)) ? 0 : 1;
|
|
450
|
+
const lineOffset = countHeaderLines(cssPrelude) + wrapOffset + countHeaderLines(inlined);
|
|
451
|
+
chunk.map = stripVendorSourcesContent(offsetSourceMap(chunk.map, lineOffset, requireName));
|
|
452
|
+
nextCode = `${stripSourceMappingUrl(nextCode).replace(/\n+$/g, "\n")}//# sourceMappingURL=${toInlineSourceMappingUrl(chunk.map)}\n`;
|
|
453
|
+
}
|
|
454
|
+
chunk.code = nextCode;
|
|
455
|
+
chunk.fileName = requireName;
|
|
456
|
+
emitFile(toProxyFileName(script.fileName), `${generateWatchProxy(script, resolve(context.outDir, requireName))}\n`);
|
|
457
|
+
continue;
|
|
458
|
+
}
|
|
459
|
+
const headerConfig = withBuildGrants(script.header, wrapped, extraGrants);
|
|
460
|
+
const prefix = `${generateHeader(headerConfig, {
|
|
461
|
+
align: script.align,
|
|
462
|
+
autoMetaUrls: script.autoMetaUrls,
|
|
463
|
+
fileName: script.fileName,
|
|
464
|
+
generate: script.generate,
|
|
465
|
+
mode: "build"
|
|
466
|
+
})}\n\n`;
|
|
467
|
+
const nextFileName = `${script.fileName}.user.js`;
|
|
468
|
+
let nextCode = `${prefix}${code}`;
|
|
469
|
+
if (chunk.map) {
|
|
470
|
+
const wrapOffset = isAlreadyIife(stripSourceMappingUrl(body)) ? 0 : 1;
|
|
471
|
+
const lineOffset = countHeaderLines(prefix) + countHeaderLines(cssPrelude) + wrapOffset + countHeaderLines(inlined);
|
|
472
|
+
chunk.map = stripVendorSourcesContent(offsetSourceMap(chunk.map, lineOffset, nextFileName));
|
|
473
|
+
nextCode = `${stripSourceMappingUrl(nextCode).replace(/\n+$/g, "\n")}//# sourceMappingURL=${toInlineSourceMappingUrl(chunk.map)}\n`;
|
|
474
|
+
}
|
|
475
|
+
chunk.code = nextCode;
|
|
476
|
+
chunk.fileName = nextFileName;
|
|
477
|
+
if (script.metaFile) emitFile(`${script.fileName}.meta.js`, generateHeader(headerConfig, {
|
|
478
|
+
align: script.align,
|
|
479
|
+
autoMetaUrls: script.autoMetaUrls,
|
|
480
|
+
fileName: script.fileName,
|
|
481
|
+
generate: script.generate,
|
|
482
|
+
mode: "meta"
|
|
483
|
+
}));
|
|
484
|
+
}
|
|
485
|
+
for (const [fileName, item] of Object.entries(bundle)) if (isChunk(item) && !item.isEntry && !keptChunks.has(fileName)) leftoverChunks.push(fileName);
|
|
486
|
+
leftoverAssets.push(...withSourceMaps(leftoverChunks));
|
|
487
|
+
deleteBundleFiles(bundle, leftoverChunks);
|
|
488
|
+
deleteBundleFiles(bundle, leftoverAssets);
|
|
489
|
+
}
|
|
490
|
+
//#endregion
|
|
491
|
+
//#region src/client.ts
|
|
492
|
+
const VIRTUAL_MODULE_ID = "virtual:vite-userscript-plugin";
|
|
493
|
+
const RESOLVED_VIRTUAL_MODULE_ID = `\0${VIRTUAL_MODULE_ID}`;
|
|
494
|
+
function createClientSnapshot(scripts, command) {
|
|
495
|
+
return scripts.map((script) => {
|
|
496
|
+
let suffix = ".dev.user.js";
|
|
497
|
+
if (command === "build") suffix = ".user.js";
|
|
498
|
+
else if (script.server.file) suffix = ".proxy.user.js";
|
|
499
|
+
return {
|
|
500
|
+
name: script.header.name,
|
|
501
|
+
version: script.header.version,
|
|
502
|
+
file: `${script.fileName}${suffix}`
|
|
503
|
+
};
|
|
504
|
+
});
|
|
505
|
+
}
|
|
506
|
+
function renderVirtualModule(scripts) {
|
|
507
|
+
return `export const scripts = ${JSON.stringify(scripts)}\n`;
|
|
508
|
+
}
|
|
509
|
+
//#endregion
|
|
510
|
+
//#region src/constants.ts
|
|
511
|
+
const PLUGIN_NAME = "vite-userscript-plugin";
|
|
512
|
+
const FAQ_URL = "https://github.com/greasify/vite-userscript-plugin#faq";
|
|
513
|
+
const GM_NAMESPACE = "__viteUserscriptGM__";
|
|
514
|
+
const VITE_CLIENT_FLAG = "__viteUserscriptViteClient__";
|
|
515
|
+
const REACT_PREAMBLE_PATH = `/${PLUGIN_NAME}/react-preamble.js`;
|
|
516
|
+
const REACT_BOOTSTRAP_PATH = `/${PLUGIN_NAME}/react-bootstrap.js`;
|
|
517
|
+
const REACT_REFRESH_PLUGIN_NAMES = /* @__PURE__ */ new Set(["vite:react-refresh", "vite:react-virtual-preamble"]);
|
|
518
|
+
//#endregion
|
|
519
|
+
//#region src/html.ts
|
|
520
|
+
function isHtmlPath(file) {
|
|
521
|
+
return /\.html?$/i.test(file.split("?")[0] ?? file);
|
|
522
|
+
}
|
|
523
|
+
function entryNameFromPath(file) {
|
|
524
|
+
return basename(file.split("?")[0] ?? file).replace(/\.html?$/i, "") || "index";
|
|
525
|
+
}
|
|
526
|
+
function readUserBuildInput(userConfig) {
|
|
527
|
+
return userConfig.build?.rolldownOptions?.input ?? userConfig.build?.rollupOptions?.input;
|
|
528
|
+
}
|
|
529
|
+
function normalizeInput(input) {
|
|
530
|
+
if (input == null) return {};
|
|
531
|
+
if (typeof input === "string") return { [entryNameFromPath(input)]: input };
|
|
532
|
+
if (Array.isArray(input)) {
|
|
533
|
+
const result = {};
|
|
534
|
+
for (const item of input) if (typeof item === "string") result[entryNameFromPath(item)] = item;
|
|
535
|
+
return result;
|
|
536
|
+
}
|
|
537
|
+
if (typeof input === "object") {
|
|
538
|
+
const result = {};
|
|
539
|
+
for (const [key, value] of Object.entries(input)) if (typeof value === "string") result[key] = value;
|
|
540
|
+
return result;
|
|
541
|
+
}
|
|
542
|
+
return {};
|
|
543
|
+
}
|
|
544
|
+
function collectHtmlEntries(root, userInput) {
|
|
545
|
+
const html = {};
|
|
546
|
+
for (const [key, value] of Object.entries(userInput)) if (isHtmlPath(value)) html[key] = value;
|
|
547
|
+
if (!Object.values(html).some((value) => basename(value.split("?")[0] ?? value) === "index.html") && existsSync(resolve(root, "index.html"))) html.index = "index.html";
|
|
548
|
+
return html;
|
|
549
|
+
}
|
|
550
|
+
function mergePluginInput(scripts, userInput, htmlEntries) {
|
|
551
|
+
const input = {
|
|
552
|
+
...userInput,
|
|
553
|
+
...htmlEntries
|
|
554
|
+
};
|
|
555
|
+
for (const script of scripts) {
|
|
556
|
+
const existing = input[script.fileName];
|
|
557
|
+
if (existing != null && existing !== script.entry) throw new Error(`[${PLUGIN_NAME}] HTML entry "${script.fileName}" collides with userscript fileName "${script.fileName}". Rename the userscript fileName.`);
|
|
558
|
+
input[script.fileName] = script.entry;
|
|
559
|
+
}
|
|
560
|
+
return input;
|
|
561
|
+
}
|
|
562
|
+
function resolvePluginBuildInput(userConfig, scripts) {
|
|
563
|
+
const root = resolve(userConfig.root ?? process.cwd());
|
|
564
|
+
const userInput = normalizeInput(readUserBuildInput(userConfig));
|
|
565
|
+
const htmlEntries = collectHtmlEntries(root, userInput);
|
|
566
|
+
return {
|
|
567
|
+
input: mergePluginInput(scripts, userInput, htmlEntries),
|
|
568
|
+
hasHtml: Object.keys(htmlEntries).length > 0,
|
|
569
|
+
root
|
|
570
|
+
};
|
|
571
|
+
}
|
|
572
|
+
//#endregion
|
|
573
|
+
//#region src/resolve.ts
|
|
574
|
+
function isEmptyHeaderField(value) {
|
|
575
|
+
if (value == null || value === "") return true;
|
|
576
|
+
return Array.isArray(value) && value.length === 0;
|
|
577
|
+
}
|
|
578
|
+
function assertHeader(header, label) {
|
|
579
|
+
for (const field of [
|
|
580
|
+
"name",
|
|
581
|
+
"version",
|
|
582
|
+
"match"
|
|
583
|
+
]) if (isEmptyHeaderField(header[field])) throw new Error(`[${PLUGIN_NAME}] ${label} is missing required header.${field}`);
|
|
584
|
+
}
|
|
585
|
+
function toResolvedScript(config) {
|
|
586
|
+
if (!config.entry) throw new Error(`[${PLUGIN_NAME}] Provide an "entry" for each userscript`);
|
|
587
|
+
assertHeader(config.header ?? {}, config.entry);
|
|
588
|
+
const fileName = sanitizeFileName(config.fileName ?? config.header.name);
|
|
589
|
+
return {
|
|
590
|
+
entry: config.entry,
|
|
591
|
+
fileName,
|
|
592
|
+
iifeName: toIdentifier(fileName),
|
|
593
|
+
header: config.header,
|
|
594
|
+
server: {
|
|
595
|
+
open: config.server?.open ?? false,
|
|
596
|
+
prefix: config.server?.prefix ?? "server:",
|
|
597
|
+
file: config.server?.file ?? false
|
|
598
|
+
},
|
|
599
|
+
cssInject: config.cssInject ?? "auto",
|
|
600
|
+
align: config.align ?? 1,
|
|
601
|
+
generate: config.generate,
|
|
602
|
+
autoMetaUrls: config.autoMetaUrls ?? false,
|
|
603
|
+
metaFile: config.metaFile ?? true
|
|
604
|
+
};
|
|
605
|
+
}
|
|
606
|
+
function collectAutoMetaUrlsWarnings(config) {
|
|
607
|
+
const warnings = [];
|
|
608
|
+
for (const script of config.scripts) {
|
|
609
|
+
if (!script.autoMetaUrls) continue;
|
|
610
|
+
if (!script.metaFile) warnings.push(`[${PLUGIN_NAME}] autoMetaUrls is enabled but metaFile is false for "${script.fileName}" — @updateURL points at a .meta.js that will not be emitted`);
|
|
611
|
+
if (!resolveHomePage(script.header)) warnings.push(`[${PLUGIN_NAME}] autoMetaUrls is enabled but "${script.fileName}" has no homepage, homepageURL, website, or source`);
|
|
612
|
+
}
|
|
613
|
+
return warnings;
|
|
614
|
+
}
|
|
615
|
+
function resolvePluginConfig(config) {
|
|
616
|
+
const items = Array.isArray(config) ? config : [config];
|
|
617
|
+
if (!items.length) throw new Error(`[${PLUGIN_NAME}] Provide a userscript config or a non-empty array`);
|
|
618
|
+
const scripts = items.map((item) => toResolvedScript(item));
|
|
619
|
+
const names = /* @__PURE__ */ new Set();
|
|
620
|
+
for (const script of scripts) {
|
|
621
|
+
if (names.has(script.fileName)) throw new Error(`[${PLUGIN_NAME}] Duplicate fileName "${script.fileName}"`);
|
|
622
|
+
names.add(script.fileName);
|
|
623
|
+
}
|
|
624
|
+
return { scripts };
|
|
625
|
+
}
|
|
626
|
+
//#endregion
|
|
627
|
+
//#region src/serve/gm-shim.ts
|
|
628
|
+
function shouldShimModule(id) {
|
|
629
|
+
const cleanId = id.split("\0").pop() ?? id;
|
|
630
|
+
if (cleanId.includes("node_modules")) return false;
|
|
631
|
+
if (/\.(?:css|scss|sass|less|styl|stylus|pcss)(?:$|\?)/i.test(cleanId)) return false;
|
|
632
|
+
if (/[?&](?:vue|svelte)&type=style/.test(cleanId)) return false;
|
|
633
|
+
if (/[?&](?:raw|url)(?:&|$)/.test(cleanId)) return false;
|
|
634
|
+
if (/\.(?:m|c)?[jt]sx?(?:$|\?)/.test(cleanId)) return true;
|
|
635
|
+
if (/[?&]vue&type=script/.test(cleanId) || cleanId.endsWith(".vue")) return true;
|
|
636
|
+
if (/[?&]svelte&type=script/.test(cleanId) || cleanId.endsWith(".svelte")) return true;
|
|
637
|
+
return false;
|
|
638
|
+
}
|
|
639
|
+
function createGmShimPrelude() {
|
|
640
|
+
return `const { ${gmIdentifiers.join(", ")} } = globalThis.${GM_NAMESPACE} ?? globalThis;\n`;
|
|
641
|
+
}
|
|
642
|
+
function shimModule(code, id) {
|
|
643
|
+
const prelude = createGmShimPrelude();
|
|
644
|
+
return {
|
|
645
|
+
code: `${prelude}${code}`,
|
|
646
|
+
map: offsetSourceMap(identitySourceMap(code, id), countHeaderLines(prelude))
|
|
647
|
+
};
|
|
648
|
+
}
|
|
649
|
+
//#endregion
|
|
650
|
+
//#region src/serve/logger.ts
|
|
651
|
+
function formatInstallLine(installUrl) {
|
|
652
|
+
const coloredUrl = styleText("cyan", installUrl.replace(/:(\d+)\//, (_match, port) => `:${styleText("bold", port)}/`));
|
|
653
|
+
return ` ${styleText("green", "➜")} ${styleText("bold", "Userscript")}: ${coloredUrl}`;
|
|
654
|
+
}
|
|
655
|
+
function formatRebuildLine(elapsedMs) {
|
|
656
|
+
return `${styleText("green", "Userscript rebuilt")} ${styleText("dim", `(${elapsedMs}ms)`)}`;
|
|
657
|
+
}
|
|
658
|
+
function formatFaqHint() {
|
|
659
|
+
return `${` ${styleText("green", "➜")} ${styleText("bold", "FAQ")}: `}${styleText("cyan", FAQ_URL)}\n`;
|
|
660
|
+
}
|
|
661
|
+
function stripAnsi(text) {
|
|
662
|
+
return text.replace(/\u001B\[[0-9;]*m/g, "");
|
|
663
|
+
}
|
|
664
|
+
function isViteLocalUrlLine(message) {
|
|
665
|
+
return /Local:\s/.test(stripAnsi(message));
|
|
666
|
+
}
|
|
667
|
+
function createAfterLocalLogger(info, localCount, onAfterLocal) {
|
|
668
|
+
let remaining = localCount;
|
|
669
|
+
let printed = false;
|
|
670
|
+
const flush = () => {
|
|
671
|
+
if (printed) return;
|
|
672
|
+
printed = true;
|
|
673
|
+
onAfterLocal();
|
|
674
|
+
};
|
|
675
|
+
return {
|
|
676
|
+
info: (msg, options) => {
|
|
677
|
+
info(msg, options);
|
|
678
|
+
if (remaining > 0 && isViteLocalUrlLine(String(msg))) {
|
|
679
|
+
remaining -= 1;
|
|
680
|
+
if (remaining === 0) flush();
|
|
681
|
+
}
|
|
682
|
+
},
|
|
683
|
+
flush
|
|
684
|
+
};
|
|
685
|
+
}
|
|
686
|
+
//#endregion
|
|
687
|
+
//#region src/serve/react.ts
|
|
688
|
+
function hasReactRefreshPlugin(plugins) {
|
|
689
|
+
return plugins.some((plugin) => REACT_REFRESH_PLUGIN_NAMES.has(plugin.name));
|
|
690
|
+
}
|
|
691
|
+
function matchReactPreamble(url) {
|
|
692
|
+
return (url.split("?")[0] ?? "") === REACT_PREAMBLE_PATH;
|
|
693
|
+
}
|
|
694
|
+
function matchReactBootstrap(url) {
|
|
695
|
+
return (url.split("?")[0] ?? "") === REACT_BOOTSTRAP_PATH;
|
|
696
|
+
}
|
|
697
|
+
function resolveBootstrapEntry(url) {
|
|
698
|
+
const query = url.includes("?") ? url.slice(url.indexOf("?") + 1) : "";
|
|
699
|
+
const entry = new URLSearchParams(query).get("entry");
|
|
700
|
+
if (!entry?.startsWith("/") || entry.startsWith("//")) return;
|
|
701
|
+
return entry;
|
|
702
|
+
}
|
|
703
|
+
const REACT_PREAMBLE_MODULE = `import { injectIntoGlobalHook } from "/@react-refresh";
|
|
704
|
+
injectIntoGlobalHook(window);
|
|
705
|
+
window.$RefreshReg$ = () => {};
|
|
706
|
+
window.$RefreshSig$ = () => (type) => type;
|
|
707
|
+
window.__vite_plugin_react_preamble_installed__ = true;
|
|
708
|
+
`;
|
|
709
|
+
function createReactBootstrapModule(entryPath) {
|
|
710
|
+
return `import "/@vite/client";
|
|
711
|
+
import ${JSON.stringify(REACT_PREAMBLE_PATH)};
|
|
712
|
+
import ${JSON.stringify(entryPath)};
|
|
713
|
+
`;
|
|
714
|
+
}
|
|
715
|
+
//#endregion
|
|
716
|
+
//#region src/serve/wrapper.ts
|
|
717
|
+
function matchDevUserscript(url, fileName) {
|
|
718
|
+
return (url.split("?")[0] ?? "") === `/${fileName}.dev.user.js`;
|
|
719
|
+
}
|
|
720
|
+
function matchProxyUserscript(url, fileName) {
|
|
721
|
+
return (url.split("?")[0] ?? "") === `/${toProxyFileName(fileName)}`;
|
|
722
|
+
}
|
|
723
|
+
function toInstallUrl(origin, fileName, file = false) {
|
|
724
|
+
const name = file ? toProxyFileName(fileName) : `${fileName}.dev.user.js`;
|
|
725
|
+
return `${origin.replace(/\/$/, "")}/${name}`;
|
|
726
|
+
}
|
|
727
|
+
function toServeEntryPath(root, entry) {
|
|
728
|
+
const absolute = resolve(root, entry);
|
|
729
|
+
return `/${relative(root, absolute).split(sep).join(posix.sep)}`;
|
|
730
|
+
}
|
|
731
|
+
function applyServeHeader(header, prefix) {
|
|
732
|
+
return withServeGrants({
|
|
733
|
+
...header,
|
|
734
|
+
name: prefix === false ? header.name : `${prefix}${header.name}`
|
|
735
|
+
});
|
|
736
|
+
}
|
|
737
|
+
function generateDevWrapper(options) {
|
|
738
|
+
const clientUrl = `${options.origin}/@vite/client`;
|
|
739
|
+
const entryUrl = `${options.origin}${options.entryPath}`;
|
|
740
|
+
const bootstrapUrl = `${options.origin}${REACT_BOOTSTRAP_PATH}?entry=${encodeURIComponent(options.entryPath)}`;
|
|
741
|
+
const copies = gmIdentifiers.map((id) => `if (typeof ${id} !== 'undefined') gm.${id} = ${id};`).join("\n ");
|
|
742
|
+
const injectTarget = options.reactPreamble ? bootstrapUrl : entryUrl;
|
|
743
|
+
const clientInject = options.reactPreamble ? "" : `
|
|
744
|
+
if (!root.${VITE_CLIENT_FLAG}) {
|
|
745
|
+
root.${VITE_CLIENT_FLAG} = true;
|
|
746
|
+
inject(${JSON.stringify(clientUrl)});
|
|
747
|
+
}`;
|
|
748
|
+
return `(function () {
|
|
749
|
+
var root = typeof unsafeWindow !== 'undefined' ? unsafeWindow : window;
|
|
750
|
+
var gm = root.${GM_NAMESPACE} || {};
|
|
751
|
+
${copies}
|
|
752
|
+
root.${GM_NAMESPACE} = gm;
|
|
124
753
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
let socketConnection = null;
|
|
131
|
-
const fileName = config.fileName ?? config.header.name;
|
|
132
|
-
const logger = createLogger("info", {
|
|
133
|
-
prefix: `[${pluginName}]`,
|
|
134
|
-
allowClearScreen: true
|
|
135
|
-
});
|
|
136
|
-
const httpServer = createServer((req, res) => {
|
|
137
|
-
return serveHandler(req, res, {
|
|
138
|
-
public: pluginConfig.build.outDir
|
|
139
|
-
});
|
|
140
|
-
});
|
|
141
|
-
const WebSocketServer = server;
|
|
142
|
-
const ws = new WebSocketServer({ httpServer });
|
|
143
|
-
ws.on("request", (request) => {
|
|
144
|
-
socketConnection = request.accept(null, request.origin);
|
|
145
|
-
});
|
|
146
|
-
return {
|
|
147
|
-
name: pluginName,
|
|
148
|
-
apply: "build",
|
|
149
|
-
config() {
|
|
150
|
-
return {
|
|
151
|
-
build: {
|
|
152
|
-
target: "esnext",
|
|
153
|
-
minify: false,
|
|
154
|
-
lib: {
|
|
155
|
-
name: fileName,
|
|
156
|
-
entry: config.entry,
|
|
157
|
-
formats: ["iife"],
|
|
158
|
-
fileName: () => `${fileName}.js`
|
|
159
|
-
},
|
|
160
|
-
rollupOptions: {
|
|
161
|
-
output: {
|
|
162
|
-
extend: true
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
};
|
|
167
|
-
},
|
|
168
|
-
async configResolved(userConfig) {
|
|
169
|
-
pluginConfig = userConfig;
|
|
170
|
-
isBuildWatch = userConfig.build.watch ?? false;
|
|
171
|
-
config.entry = resolve(userConfig.root, config.entry);
|
|
172
|
-
Array.from([
|
|
173
|
-
"match",
|
|
174
|
-
"require",
|
|
175
|
-
"include",
|
|
176
|
-
"exclude",
|
|
177
|
-
"resource",
|
|
178
|
-
"connect"
|
|
179
|
-
]).forEach((key) => {
|
|
180
|
-
const value = config.header[key];
|
|
181
|
-
config.header[key] = removeDuplicates(value);
|
|
182
|
-
});
|
|
183
|
-
config.server = {
|
|
184
|
-
port: await getPort(),
|
|
185
|
-
open: false,
|
|
186
|
-
...config.server
|
|
187
|
-
};
|
|
188
|
-
},
|
|
189
|
-
async writeBundle(output, bundle) {
|
|
190
|
-
const { open, port } = config.server;
|
|
191
|
-
const sanitizedFilename = output.sanitizeFileName(fileName);
|
|
192
|
-
const userFilename = `${sanitizedFilename}.user.js`;
|
|
193
|
-
const proxyFilename = `${sanitizedFilename}.proxy.user.js`;
|
|
194
|
-
const metaFilename = `${sanitizedFilename}.meta.js`;
|
|
195
|
-
for (const [fileName2] of Object.entries(bundle)) {
|
|
196
|
-
if (regexpScripts.test(fileName2)) {
|
|
197
|
-
const rootDir = pluginConfig.root;
|
|
198
|
-
const outDir = pluginConfig.build.outDir;
|
|
199
|
-
const outPath = resolve(rootDir, outDir, fileName2);
|
|
200
|
-
const userFilePath = resolve(rootDir, outDir, userFilename);
|
|
201
|
-
const proxyFilePath = resolve(rootDir, outDir, proxyFilename);
|
|
202
|
-
const metaFilePath = resolve(rootDir, outDir, metaFilename);
|
|
203
|
-
const wsPath = resolve(pluginDir, `ws-${sanitizedFilename}.js`);
|
|
204
|
-
try {
|
|
205
|
-
let source = readFileSync(outPath, "utf8");
|
|
206
|
-
source = await transform(
|
|
207
|
-
{
|
|
208
|
-
minify: !isBuildWatch,
|
|
209
|
-
file: source,
|
|
210
|
-
name: fileName2,
|
|
211
|
-
loader: "js"
|
|
212
|
-
},
|
|
213
|
-
config.esbuildTransformOptions
|
|
214
|
-
);
|
|
215
|
-
config.header.grant = removeDuplicates(
|
|
216
|
-
isBuildWatch ? grants : [...defineGrants(source), ...config.header.grant ?? []]
|
|
217
|
-
);
|
|
218
|
-
if (isBuildWatch) {
|
|
219
|
-
const wsFile = readFileSync(resolve(pluginDir, "ws.js"), "utf8");
|
|
220
|
-
const wsScript = await transform(
|
|
221
|
-
{
|
|
222
|
-
minify: !isBuildWatch,
|
|
223
|
-
file: wsFile.replace("__WS__", `ws://localhost:${port}`),
|
|
224
|
-
name: wsPath,
|
|
225
|
-
loader: "js"
|
|
226
|
-
},
|
|
227
|
-
config.esbuildTransformOptions
|
|
228
|
-
);
|
|
229
|
-
writeFileSync(wsPath, wsScript);
|
|
230
|
-
writeFileSync(
|
|
231
|
-
proxyFilePath,
|
|
232
|
-
new Banner({
|
|
233
|
-
...config.header,
|
|
234
|
-
require: [
|
|
235
|
-
...config.header.require ?? [],
|
|
236
|
-
"file://" + wsPath,
|
|
237
|
-
"file://" + outPath
|
|
238
|
-
]
|
|
239
|
-
}).generate()
|
|
240
|
-
);
|
|
241
|
-
}
|
|
242
|
-
const banner = new Banner(config.header).generate();
|
|
243
|
-
writeFileSync(outPath, source);
|
|
244
|
-
writeFileSync(metaFilePath, banner);
|
|
245
|
-
writeFileSync(userFilePath, `${banner}
|
|
246
|
-
|
|
247
|
-
${source}`);
|
|
248
|
-
} catch (err) {
|
|
249
|
-
console.log(err);
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
if (isBuildWatch && !httpServer.listening) {
|
|
254
|
-
const link = `http://localhost:${port}`;
|
|
255
|
-
httpServer.listen(port, () => {
|
|
256
|
-
logger.clearScreen("info");
|
|
257
|
-
logger.info(
|
|
258
|
-
colors.bold(
|
|
259
|
-
`${colors.cyan(">>> [vite-userscript-plugin]")} ${colors.gray(
|
|
260
|
-
link
|
|
261
|
-
)}`
|
|
262
|
-
)
|
|
263
|
-
);
|
|
264
|
-
});
|
|
265
|
-
if (open) {
|
|
266
|
-
await openLink(`${link}/${proxyFilename}`);
|
|
267
|
-
}
|
|
268
|
-
} else if (!isBuildWatch) {
|
|
269
|
-
httpServer.close();
|
|
270
|
-
process.exit(0);
|
|
271
|
-
}
|
|
272
|
-
},
|
|
273
|
-
buildEnd() {
|
|
274
|
-
if (isBuildWatch) {
|
|
275
|
-
logger.clearScreen("info");
|
|
276
|
-
if (socketConnection) {
|
|
277
|
-
socketConnection.sendUTF(
|
|
278
|
-
JSON.stringify({
|
|
279
|
-
message: "reload"
|
|
280
|
-
})
|
|
281
|
-
);
|
|
282
|
-
}
|
|
283
|
-
}
|
|
284
|
-
}
|
|
285
|
-
};
|
|
286
|
-
} catch (err) {
|
|
287
|
-
console.error(err);
|
|
288
|
-
return {
|
|
289
|
-
name: pluginName
|
|
290
|
-
};
|
|
754
|
+
function inject(src) {
|
|
755
|
+
var script = document.createElement('script');
|
|
756
|
+
script.type = 'module';
|
|
757
|
+
script.src = src;
|
|
758
|
+
(document.head || document.documentElement).appendChild(script);
|
|
291
759
|
}
|
|
760
|
+
${clientInject}
|
|
761
|
+
inject(${JSON.stringify(injectTarget)});
|
|
762
|
+
})();
|
|
763
|
+
`;
|
|
764
|
+
}
|
|
765
|
+
function generateDevUserscript(options) {
|
|
766
|
+
return `${generateHeader(applyServeHeader(options.script.header, options.prefix), {
|
|
767
|
+
...options.headerOptions,
|
|
768
|
+
fileName: options.script.fileName,
|
|
769
|
+
mode: "serve"
|
|
770
|
+
})}\n\n${generateDevWrapper({
|
|
771
|
+
origin: options.origin,
|
|
772
|
+
entryPath: toServeEntryPath(options.root, options.script.entry),
|
|
773
|
+
reactPreamble: options.reactPreamble
|
|
774
|
+
})}`;
|
|
775
|
+
}
|
|
776
|
+
function findDevScript(url, scripts) {
|
|
777
|
+
return scripts.find((script) => !script.server.file && matchDevUserscript(url, script.fileName));
|
|
778
|
+
}
|
|
779
|
+
function findProxyScript(url, scripts) {
|
|
780
|
+
return scripts.find((script) => script.server.file && matchProxyUserscript(url, script.fileName));
|
|
781
|
+
}
|
|
782
|
+
function createDevUserscript(options) {
|
|
783
|
+
return generateDevUserscript({
|
|
784
|
+
script: options.script,
|
|
785
|
+
origin: options.origin,
|
|
786
|
+
root: options.root,
|
|
787
|
+
prefix: options.script.server.prefix,
|
|
788
|
+
headerOptions: {
|
|
789
|
+
align: options.script.align,
|
|
790
|
+
autoMetaUrls: options.script.autoMetaUrls,
|
|
791
|
+
generate: options.script.generate
|
|
792
|
+
},
|
|
793
|
+
reactPreamble: options.reactPreamble
|
|
794
|
+
});
|
|
292
795
|
}
|
|
293
|
-
|
|
294
|
-
|
|
796
|
+
//#endregion
|
|
797
|
+
//#region src/serve/middleware.ts
|
|
798
|
+
const DEV_SCRIPT_HEADERS = {
|
|
799
|
+
"Content-Type": "text/javascript; charset=utf-8",
|
|
800
|
+
"Cache-Control": "no-store"
|
|
295
801
|
};
|
|
802
|
+
function resolveServerOrigin(urls) {
|
|
803
|
+
const url = urls?.local[0] ?? urls?.network[0];
|
|
804
|
+
return url ? url.replace(/\/$/, "") : "http://localhost:5173";
|
|
805
|
+
}
|
|
806
|
+
function writeScript(res, body) {
|
|
807
|
+
for (const [key, value] of Object.entries(DEV_SCRIPT_HEADERS)) res.setHeader(key, value);
|
|
808
|
+
res.end(body);
|
|
809
|
+
}
|
|
810
|
+
function configureDevServer(server, resolved, reactPreamble) {
|
|
811
|
+
server.middlewares.use((req, res, next) => {
|
|
812
|
+
const url = req.url ?? "";
|
|
813
|
+
if (matchReactPreamble(url)) {
|
|
814
|
+
writeScript(res, REACT_PREAMBLE_MODULE);
|
|
815
|
+
return;
|
|
816
|
+
}
|
|
817
|
+
if (matchReactBootstrap(url)) {
|
|
818
|
+
const entry = resolveBootstrapEntry(url);
|
|
819
|
+
if (!entry) {
|
|
820
|
+
res.statusCode = 400;
|
|
821
|
+
res.end();
|
|
822
|
+
return;
|
|
823
|
+
}
|
|
824
|
+
writeScript(res, createReactBootstrapModule(entry));
|
|
825
|
+
return;
|
|
826
|
+
}
|
|
827
|
+
const proxyScript = findProxyScript(url, resolved.scripts);
|
|
828
|
+
if (proxyScript) {
|
|
829
|
+
writeScript(res, `${generateWatchProxy(proxyScript, resolve(server.config.root, server.config.build.outDir, toRequireFileName(proxyScript.fileName)))}\n`);
|
|
830
|
+
return;
|
|
831
|
+
}
|
|
832
|
+
const script = findDevScript(url, resolved.scripts);
|
|
833
|
+
if (!script) {
|
|
834
|
+
next();
|
|
835
|
+
return;
|
|
836
|
+
}
|
|
837
|
+
writeScript(res, createDevUserscript({
|
|
838
|
+
origin: resolveServerOrigin(server.resolvedUrls),
|
|
839
|
+
root: server.config.root,
|
|
840
|
+
script,
|
|
841
|
+
reactPreamble
|
|
842
|
+
}));
|
|
843
|
+
});
|
|
844
|
+
const printUrls = server.printUrls.bind(server);
|
|
845
|
+
server.printUrls = () => {
|
|
846
|
+
const urls = server.resolvedUrls;
|
|
847
|
+
const info = server.config.logger.info.bind(server.config.logger);
|
|
848
|
+
let origins = [];
|
|
849
|
+
if (urls) origins = urls.local.length ? urls.local : urls.network;
|
|
850
|
+
const printInstall = () => {
|
|
851
|
+
for (const origin of origins) for (const script of resolved.scripts) info(formatInstallLine(toInstallUrl(origin, script.fileName, script.server.file)));
|
|
852
|
+
info(formatFaqHint());
|
|
853
|
+
};
|
|
854
|
+
const logger = createAfterLocalLogger(info, urls?.local.length ?? 0, printInstall);
|
|
855
|
+
const previousInfo = server.config.logger.info;
|
|
856
|
+
server.config.logger.info = logger.info;
|
|
857
|
+
try {
|
|
858
|
+
printUrls();
|
|
859
|
+
} finally {
|
|
860
|
+
server.config.logger.info = previousInfo;
|
|
861
|
+
logger.flush();
|
|
862
|
+
}
|
|
863
|
+
};
|
|
864
|
+
server.httpServer?.once("listening", () => {
|
|
865
|
+
const toOpen = resolved.scripts.filter((script) => script.server.open);
|
|
866
|
+
if (!toOpen.length) return;
|
|
867
|
+
queueMicrotask(() => {
|
|
868
|
+
const origin = resolveServerOrigin(server.resolvedUrls);
|
|
869
|
+
for (const script of toOpen) openLink(toInstallUrl(origin, script.fileName, script.server.file));
|
|
870
|
+
});
|
|
871
|
+
});
|
|
872
|
+
}
|
|
873
|
+
//#endregion
|
|
874
|
+
//#region src/plugin.ts
|
|
875
|
+
function absolutizeEntries(config, root) {
|
|
876
|
+
return { scripts: config.scripts.map((script) => ({
|
|
877
|
+
...script,
|
|
878
|
+
entry: resolve(root, script.entry)
|
|
879
|
+
})) };
|
|
880
|
+
}
|
|
881
|
+
function UserscriptPlugin(config) {
|
|
882
|
+
let resolved = resolvePluginConfig(config);
|
|
883
|
+
let reactPreamble = false;
|
|
884
|
+
let command = "serve";
|
|
885
|
+
let isWatch = false;
|
|
886
|
+
let mode = "production";
|
|
887
|
+
let outDir = "";
|
|
888
|
+
let fileWatchStarted = false;
|
|
889
|
+
const shouldEmitProxy = () => {
|
|
890
|
+
return isWatch || mode === "development";
|
|
891
|
+
};
|
|
892
|
+
const startFileWatchBuild = async (server) => {
|
|
893
|
+
if (command === "build" || fileWatchStarted) return;
|
|
894
|
+
fileWatchStarted = true;
|
|
895
|
+
const outDirAbs = resolve(server.config.root, server.config.build.outDir);
|
|
896
|
+
let firstBuild = true;
|
|
897
|
+
const run = async () => {
|
|
898
|
+
const isRebuild = !firstBuild;
|
|
899
|
+
const started = Date.now();
|
|
900
|
+
await build({
|
|
901
|
+
configFile: server.config.configFile ?? false,
|
|
902
|
+
root: server.config.root,
|
|
903
|
+
mode: server.config.mode,
|
|
904
|
+
logLevel: "silent",
|
|
905
|
+
clearScreen: false,
|
|
906
|
+
plugins: server.config.configFile ? void 0 : [UserscriptPlugin(config)],
|
|
907
|
+
build: {
|
|
908
|
+
outDir: server.config.build.outDir,
|
|
909
|
+
emptyOutDir: firstBuild,
|
|
910
|
+
minify: server.config.build.minify,
|
|
911
|
+
sourcemap: server.config.build.sourcemap,
|
|
912
|
+
write: true,
|
|
913
|
+
reportCompressedSize: false
|
|
914
|
+
}
|
|
915
|
+
});
|
|
916
|
+
firstBuild = false;
|
|
917
|
+
if (isRebuild) server.config.logger.info(formatRebuildLine(Date.now() - started), { timestamp: true });
|
|
918
|
+
};
|
|
919
|
+
try {
|
|
920
|
+
await run();
|
|
921
|
+
} catch (error) {
|
|
922
|
+
fileWatchStarted = false;
|
|
923
|
+
server.config.logger.error(`[${PLUGIN_NAME}] Failed to start file-mode watch build`);
|
|
924
|
+
server.config.logger.error(String(error));
|
|
925
|
+
return;
|
|
926
|
+
}
|
|
927
|
+
let timer;
|
|
928
|
+
const onChange = (file) => {
|
|
929
|
+
if (file.startsWith(outDirAbs)) return;
|
|
930
|
+
clearTimeout(timer);
|
|
931
|
+
timer = setTimeout(() => {
|
|
932
|
+
run().catch((error) => {
|
|
933
|
+
server.config.logger.error(String(error));
|
|
934
|
+
});
|
|
935
|
+
}, 80);
|
|
936
|
+
};
|
|
937
|
+
server.watcher.on("change", onChange);
|
|
938
|
+
server.watcher.on("add", onChange);
|
|
939
|
+
const closeServer = server.close.bind(server);
|
|
940
|
+
server.close = async () => {
|
|
941
|
+
server.watcher.off("change", onChange);
|
|
942
|
+
server.watcher.off("add", onChange);
|
|
943
|
+
clearTimeout(timer);
|
|
944
|
+
return closeServer();
|
|
945
|
+
};
|
|
946
|
+
};
|
|
947
|
+
return [
|
|
948
|
+
{
|
|
949
|
+
name: `${PLUGIN_NAME}:config`,
|
|
950
|
+
config(userConfig) {
|
|
951
|
+
const { input, hasHtml } = resolvePluginBuildInput(userConfig, resolved.scripts);
|
|
952
|
+
const scriptNames = new Set(resolved.scripts.map((script) => script.fileName));
|
|
953
|
+
userConfig.build ??= {};
|
|
954
|
+
userConfig.build.rolldownOptions ??= {};
|
|
955
|
+
userConfig.build.rolldownOptions.input = input;
|
|
956
|
+
const userOutput = userConfig.build.rolldownOptions.output;
|
|
957
|
+
const userEntryFileNames = userOutput && !Array.isArray(userOutput) ? userOutput.entryFileNames : void 0;
|
|
958
|
+
return {
|
|
959
|
+
appType: userConfig.appType ?? (hasHtml ? "spa" : "custom"),
|
|
960
|
+
optimizeDeps: {
|
|
961
|
+
entries: Object.values(input),
|
|
962
|
+
exclude: [VIRTUAL_MODULE_ID]
|
|
963
|
+
},
|
|
964
|
+
server: { cors: userConfig.server?.cors ?? true },
|
|
965
|
+
build: {
|
|
966
|
+
minify: userConfig.build?.minify ?? false,
|
|
967
|
+
assetsInlineLimit: userConfig.build?.assetsInlineLimit ?? Number.MAX_SAFE_INTEGER,
|
|
968
|
+
rolldownOptions: {
|
|
969
|
+
input,
|
|
970
|
+
output: {
|
|
971
|
+
format: "es",
|
|
972
|
+
entryFileNames: (chunkInfo) => {
|
|
973
|
+
if (scriptNames.has(chunkInfo.name)) return "[name].js";
|
|
974
|
+
if (typeof userEntryFileNames === "function") return userEntryFileNames(chunkInfo);
|
|
975
|
+
if (typeof userEntryFileNames === "string") return userEntryFileNames;
|
|
976
|
+
return "assets/[name]-[hash].js";
|
|
977
|
+
}
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
}
|
|
981
|
+
};
|
|
982
|
+
},
|
|
983
|
+
configResolved(viteConfig) {
|
|
984
|
+
command = viteConfig.command;
|
|
985
|
+
resolved = absolutizeEntries(resolved, viteConfig.root);
|
|
986
|
+
reactPreamble = hasReactRefreshPlugin(viteConfig.plugins);
|
|
987
|
+
for (const message of collectAutoMetaUrlsWarnings(resolved)) viteConfig.logger.warn(message);
|
|
988
|
+
}
|
|
989
|
+
},
|
|
990
|
+
{
|
|
991
|
+
name: `${PLUGIN_NAME}:virtual`,
|
|
992
|
+
resolveId: (id) => {
|
|
993
|
+
if (id === "virtual:vite-userscript-plugin") return RESOLVED_VIRTUAL_MODULE_ID;
|
|
994
|
+
},
|
|
995
|
+
load: (id) => {
|
|
996
|
+
if (id === RESOLVED_VIRTUAL_MODULE_ID) return renderVirtualModule(createClientSnapshot(resolved.scripts, command));
|
|
997
|
+
}
|
|
998
|
+
},
|
|
999
|
+
{
|
|
1000
|
+
name: `${PLUGIN_NAME}:serve`,
|
|
1001
|
+
apply: "serve",
|
|
1002
|
+
configureServer(server) {
|
|
1003
|
+
configureDevServer(server, resolved, reactPreamble);
|
|
1004
|
+
if (resolved.scripts.some((script) => script.server.file)) startFileWatchBuild(server);
|
|
1005
|
+
}
|
|
1006
|
+
},
|
|
1007
|
+
{
|
|
1008
|
+
name: `${PLUGIN_NAME}:gm-shim`,
|
|
1009
|
+
apply: "serve",
|
|
1010
|
+
transform: {
|
|
1011
|
+
filter: { id: { exclude: [/node_modules/] } },
|
|
1012
|
+
handler(code, id) {
|
|
1013
|
+
if (!shouldShimModule(id)) return null;
|
|
1014
|
+
return shimModule(code, id);
|
|
1015
|
+
}
|
|
1016
|
+
}
|
|
1017
|
+
},
|
|
1018
|
+
{
|
|
1019
|
+
name: `${PLUGIN_NAME}:build`,
|
|
1020
|
+
apply: "build",
|
|
1021
|
+
enforce: "post",
|
|
1022
|
+
configResolved(viteConfig) {
|
|
1023
|
+
isWatch = Boolean(viteConfig.build.watch);
|
|
1024
|
+
mode = viteConfig.mode;
|
|
1025
|
+
outDir = resolve(viteConfig.root, viteConfig.build.outDir);
|
|
1026
|
+
},
|
|
1027
|
+
generateBundle(_options, bundle) {
|
|
1028
|
+
isWatch = this.meta.watchMode || isWatch;
|
|
1029
|
+
applyUserscriptBundle(bundle, resolved, {
|
|
1030
|
+
emitFile: (fileName, source) => {
|
|
1031
|
+
this.emitFile({
|
|
1032
|
+
type: "asset",
|
|
1033
|
+
fileName,
|
|
1034
|
+
source
|
|
1035
|
+
});
|
|
1036
|
+
},
|
|
1037
|
+
emitProxy: shouldEmitProxy(),
|
|
1038
|
+
outDir
|
|
1039
|
+
});
|
|
1040
|
+
}
|
|
1041
|
+
}
|
|
1042
|
+
];
|
|
1043
|
+
}
|
|
1044
|
+
//#endregion
|
|
1045
|
+
export { Header, UserscriptPlugin as default, generateHeader, resolveHomePage, resolvePublicFileUrl };
|