vite-userscript-plugin 2.0.0 → 2.1.1
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 +14 -5
- package/dist/index.d.ts +11 -1
- package/dist/index.js +229 -42
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -83,10 +83,12 @@ Details: [types/README.md](./types/README.md).
|
|
|
83
83
|
|
|
84
84
|
`vite` prints `/{fileName}.dev.user.js` — install that URL once. HMR covers code and styles.
|
|
85
85
|
|
|
86
|
+
`server.file: true` skips HMR. The same `vite` watch-builds `{fileName}.user.js` (headed IIFE), `{fileName}.js`, and `{fileName}.proxy.user.js` (`@require file://` to the IIFE). Install the printed `/{fileName}.user.js` URL (Firefox-safe). The `Proxy` line is for managers that can poll `file://`. See [examples/serve-file](./examples/serve-file).
|
|
87
|
+
|
|
86
88
|
> [!IMPORTANT]
|
|
87
89
|
> Changing `@match`, `@grant`, or `@name` needs a reinstall.
|
|
88
90
|
|
|
89
|
-
`vite build` writes `{fileName}.user.js` to `dist/`.
|
|
91
|
+
`vite build` writes `{fileName}.user.js` to `dist/`. One-shot builds do not emit the proxy.
|
|
90
92
|
|
|
91
93
|
## Multiple scripts
|
|
92
94
|
|
|
@@ -133,8 +135,9 @@ See [examples/sourcemap](./examples/sourcemap).
|
|
|
133
135
|
| `entry` | — | Userscript entry. Required. |
|
|
134
136
|
| `header` | — | Metablock. Required: `name`, `version`, `match`. |
|
|
135
137
|
| `fileName` | sanitized `header.name` | Output base name (`{fileName}.user.js`). |
|
|
136
|
-
| `server.open` | `false` | Open the `.dev.user.js`
|
|
138
|
+
| `server.open` | `false` | Open the install target when Vite starts. HMR: `.dev.user.js`. `file`: `.user.js` URL. |
|
|
137
139
|
| `server.prefix` | `'server:'` | Prefix for `@name` in serve mode. `false` disables it. |
|
|
140
|
+
| `server.file` | `false` | Watch-build `{fileName}.user.js` + `{fileName}.js` + `{fileName}.proxy.user.js`. Install `.user.js` (or the proxy if `file://` `@require` works). No HMR. |
|
|
138
141
|
| `cssInject` | `'auto'` | How production CSS is injected. `'auto'` uses `GM_addStyle` or a `<style>` node. |
|
|
139
142
|
| `align` | `1` | Extra spaces after the longest `@key`. `false` — one space. |
|
|
140
143
|
| `generate` | — | Rewrite the generated metablock. |
|
|
@@ -158,6 +161,7 @@ In serve mode the header lists every grant. In production the plugin scans the b
|
|
|
158
161
|
| [svelte](./examples/svelte) | SFC `<style>`. |
|
|
159
162
|
| [multiple-entries](./examples/multiple-entries) | Two scripts. |
|
|
160
163
|
| [sourcemap](./examples/sourcemap) | Inline map, HTML page, virtual module. |
|
|
164
|
+
| [serve-file](./examples/serve-file) | `server.file`, install `.user.js` or the proxy from the printed URLs. |
|
|
161
165
|
|
|
162
166
|
## FAQ
|
|
163
167
|
|
|
@@ -181,17 +185,22 @@ In serve mode the header lists every grant. In production the plugin scans the b
|
|
|
181
185
|
### `@run-at document-start` feels late in dev
|
|
182
186
|
|
|
183
187
|
> [!NOTE]
|
|
184
|
-
> Serve injects `type="module"` (async). Production is a synchronous IIFE unless you use top-level `await`.
|
|
188
|
+
> Serve injects `type="module"` (async). Production is a synchronous IIFE unless you use top-level `await`. `server.file` uses that IIFE in dev too.
|
|
189
|
+
|
|
190
|
+
### `file://` `@require` is blocked
|
|
191
|
+
|
|
192
|
+
> [!NOTE]
|
|
193
|
+
> Firefox extensions cannot read `file://`. Install the printed `{fileName}.user.js` URL instead of the proxy. Tampermonkey on Chromium must allow local file access for the proxy `@require`. Violentmonkey can poll the local IIFE after you install the proxy.
|
|
185
194
|
|
|
186
195
|
## Migration from v1
|
|
187
196
|
|
|
188
197
|
| v1 | v2 |
|
|
189
198
|
| --- | --- |
|
|
190
|
-
| `vite build --watch` | `vite` |
|
|
199
|
+
| `vite build --watch` | `vite` (HMR) or `vite` + `server.file` |
|
|
191
200
|
| `esbuildTransformOptions` | removed |
|
|
192
201
|
| `server.port` | Vite `server.port` |
|
|
193
202
|
| minify on by default | off; set `build.minify` |
|
|
194
|
-
| `*.proxy.user.js` + `file://` |
|
|
203
|
+
| `*.proxy.user.js` + `file://` | `server.file: true`, or HMR `.dev.user.js` |
|
|
195
204
|
| Vite 3–7 | Vite 8 |
|
|
196
205
|
| `scripts` + shared `header` | `userscript([config, config, …])` |
|
|
197
206
|
| `ScriptOptions` | removed |
|
package/dist/index.d.ts
CHANGED
|
@@ -146,7 +146,8 @@ type HeaderConfig = {
|
|
|
146
146
|
};
|
|
147
147
|
interface ServerConfig {
|
|
148
148
|
/**
|
|
149
|
-
* Open the
|
|
149
|
+
* Open the install target when Vite starts.
|
|
150
|
+
* HMR: `.dev.user.js` URL. `file`: `{fileName}.user.js` URL.
|
|
150
151
|
*
|
|
151
152
|
* @default false
|
|
152
153
|
*/
|
|
@@ -158,6 +159,14 @@ interface ServerConfig {
|
|
|
158
159
|
* @default 'server:'
|
|
159
160
|
*/
|
|
160
161
|
prefix?: string | false;
|
|
162
|
+
/**
|
|
163
|
+
* Watch-build into `outDir`: `{fileName}.user.js`, headerless `{fileName}.js`,
|
|
164
|
+
* and `{fileName}.proxy.user.js` with `@require file://` pointing at the IIFE.
|
|
165
|
+
* Install `/{fileName}.user.js` (Firefox-safe) or the proxy URL. No HMR for this script.
|
|
166
|
+
*
|
|
167
|
+
* @default false
|
|
168
|
+
*/
|
|
169
|
+
file?: boolean;
|
|
161
170
|
}
|
|
162
171
|
type HeaderMode = 'serve' | 'build' | 'meta';
|
|
163
172
|
interface HeaderGenerateContext {
|
|
@@ -229,6 +238,7 @@ interface ResolvedScript {
|
|
|
229
238
|
server: {
|
|
230
239
|
open: boolean;
|
|
231
240
|
prefix: string | false;
|
|
241
|
+
file: boolean;
|
|
232
242
|
};
|
|
233
243
|
cssInject: CssInject;
|
|
234
244
|
align: number | false;
|
package/dist/index.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { basename, posix, relative, resolve, sep } from "node:path";
|
|
2
|
+
import { build } from "vite";
|
|
2
3
|
import { Buffer } from "node:buffer";
|
|
3
|
-
import {
|
|
4
|
-
import
|
|
4
|
+
import { pathToFileURL } from "node:url";
|
|
5
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
5
6
|
import { styleText } from "node:util";
|
|
7
|
+
import openLink from "open";
|
|
6
8
|
//#region src/names.ts
|
|
7
9
|
function sanitizeFileName(name) {
|
|
8
10
|
return name.replace(/[<>:"/\\|?*\u0000-\u001F]+/g, "-").replace(/\s+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "") || "userscript";
|
|
@@ -326,6 +328,37 @@ function ensureIife(code) {
|
|
|
326
328
|
return `(${/\bawait\b/.test(stripped) ? "async function" : "function"} () {\n${stripped}\n})();\n`;
|
|
327
329
|
}
|
|
328
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
|
|
329
362
|
//#region src/build/apply.ts
|
|
330
363
|
function importedChunkIds(chunk) {
|
|
331
364
|
return [...chunk.imports, ...chunk.dynamicImports ?? []];
|
|
@@ -373,10 +406,33 @@ function withSourceMaps(fileNames) {
|
|
|
373
406
|
function findScriptForChunk(chunk, fileName, scripts) {
|
|
374
407
|
return scripts.find((script) => chunk.name === script.fileName || fileName === `${script.fileName}.js` || fileName === `${script.fileName}.user.js`);
|
|
375
408
|
}
|
|
409
|
+
function createHeadedUserscript(script, options) {
|
|
410
|
+
const headerConfig = withBuildGrants(script.header, options.wrapped, options.extraGrants);
|
|
411
|
+
const prefix = `${generateHeader(headerConfig, {
|
|
412
|
+
align: script.align,
|
|
413
|
+
autoMetaUrls: script.autoMetaUrls,
|
|
414
|
+
fileName: script.fileName,
|
|
415
|
+
generate: script.generate,
|
|
416
|
+
mode: "build"
|
|
417
|
+
})}\n\n`;
|
|
418
|
+
const nextFileName = `${script.fileName}.user.js`;
|
|
419
|
+
let nextCode = `${prefix}${options.code}`;
|
|
420
|
+
if (options.map) {
|
|
421
|
+
const wrapOffset = isAlreadyIife(stripSourceMappingUrl(options.body)) ? 0 : 1;
|
|
422
|
+
const lineOffset = countHeaderLines(prefix) + countHeaderLines(options.cssPrelude) + wrapOffset + countHeaderLines(options.inlined);
|
|
423
|
+
const map = stripVendorSourcesContent(offsetSourceMap(options.map, lineOffset, nextFileName));
|
|
424
|
+
nextCode = `${stripSourceMappingUrl(nextCode).replace(/\n+$/g, "\n")}//# sourceMappingURL=${toInlineSourceMappingUrl(map)}\n`;
|
|
425
|
+
}
|
|
426
|
+
return {
|
|
427
|
+
headerConfig,
|
|
428
|
+
code: nextCode.endsWith("\n") ? nextCode : `${nextCode}\n`
|
|
429
|
+
};
|
|
430
|
+
}
|
|
376
431
|
function deleteBundleFiles(bundle, fileNames) {
|
|
377
432
|
for (const fileName of fileNames) delete bundle[fileName];
|
|
378
433
|
}
|
|
379
|
-
function applyUserscriptBundle(bundle, config,
|
|
434
|
+
function applyUserscriptBundle(bundle, config, context) {
|
|
435
|
+
const { emitFile } = context;
|
|
380
436
|
const userscriptEntries = [];
|
|
381
437
|
const otherEntryFiles = [];
|
|
382
438
|
for (const [fileName, item] of Object.entries(bundle)) {
|
|
@@ -405,27 +461,36 @@ function applyUserscriptBundle(bundle, config, emitMeta) {
|
|
|
405
461
|
const body = `${inlined}${chunk.code}`;
|
|
406
462
|
const wrapped = ensureIife(body);
|
|
407
463
|
const extraGrants = css && script.cssInject === "auto" ? ["GM_addStyle"] : [];
|
|
408
|
-
const headerConfig = withBuildGrants(script.header, wrapped, extraGrants);
|
|
409
464
|
const code = `${cssPrelude}${wrapped}`;
|
|
410
|
-
const
|
|
411
|
-
align: script.align,
|
|
412
|
-
autoMetaUrls: script.autoMetaUrls,
|
|
413
|
-
fileName: script.fileName,
|
|
414
|
-
generate: script.generate,
|
|
415
|
-
mode: "build"
|
|
416
|
-
})}\n\n`;
|
|
417
|
-
const nextFileName = `${script.fileName}.user.js`;
|
|
418
|
-
let nextCode = `${prefix}${code}`;
|
|
419
|
-
if (chunk.map) {
|
|
420
|
-
const wrapOffset = isAlreadyIife(stripSourceMappingUrl(body)) ? 0 : 1;
|
|
421
|
-
const lineOffset = countHeaderLines(prefix) + countHeaderLines(cssPrelude) + wrapOffset + countHeaderLines(inlined);
|
|
422
|
-
chunk.map = stripVendorSourcesContent(offsetSourceMap(chunk.map, lineOffset, nextFileName));
|
|
423
|
-
nextCode = `${stripSourceMappingUrl(nextCode).replace(/\n+$/g, "\n")}//# sourceMappingURL=${toInlineSourceMappingUrl(chunk.map)}\n`;
|
|
424
|
-
}
|
|
465
|
+
const emitFileProxy = Boolean(context.emitProxy && context.outDir && script.server.file);
|
|
425
466
|
leftoverAssets.push(`${fileName}.map`);
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
467
|
+
const headed = createHeadedUserscript(script, {
|
|
468
|
+
body,
|
|
469
|
+
code,
|
|
470
|
+
cssPrelude,
|
|
471
|
+
extraGrants,
|
|
472
|
+
inlined,
|
|
473
|
+
map: chunk.map,
|
|
474
|
+
wrapped
|
|
475
|
+
});
|
|
476
|
+
if (emitFileProxy) {
|
|
477
|
+
const requireName = toRequireFileName(script.fileName);
|
|
478
|
+
let nextCode = code.endsWith("\n") ? code : `${code}\n`;
|
|
479
|
+
if (chunk.map) {
|
|
480
|
+
const wrapOffset = isAlreadyIife(stripSourceMappingUrl(body)) ? 0 : 1;
|
|
481
|
+
const lineOffset = countHeaderLines(cssPrelude) + wrapOffset + countHeaderLines(inlined);
|
|
482
|
+
chunk.map = stripVendorSourcesContent(offsetSourceMap(chunk.map, lineOffset, requireName));
|
|
483
|
+
nextCode = `${stripSourceMappingUrl(nextCode).replace(/\n+$/g, "\n")}//# sourceMappingURL=${toInlineSourceMappingUrl(chunk.map)}\n`;
|
|
484
|
+
}
|
|
485
|
+
chunk.code = nextCode;
|
|
486
|
+
chunk.fileName = requireName;
|
|
487
|
+
emitFile(toProxyFileName(script.fileName), `${generateWatchProxy(script, resolve(context.outDir, requireName))}\n`);
|
|
488
|
+
emitFile(`${script.fileName}.user.js`, headed.code);
|
|
489
|
+
continue;
|
|
490
|
+
}
|
|
491
|
+
chunk.code = headed.code;
|
|
492
|
+
chunk.fileName = `${script.fileName}.user.js`;
|
|
493
|
+
if (script.metaFile) emitFile(`${script.fileName}.meta.js`, generateHeader(headed.headerConfig, {
|
|
429
494
|
align: script.align,
|
|
430
495
|
autoMetaUrls: script.autoMetaUrls,
|
|
431
496
|
fileName: script.fileName,
|
|
@@ -443,12 +508,16 @@ function applyUserscriptBundle(bundle, config, emitMeta) {
|
|
|
443
508
|
const VIRTUAL_MODULE_ID = "virtual:vite-userscript-plugin";
|
|
444
509
|
const RESOLVED_VIRTUAL_MODULE_ID = `\0${VIRTUAL_MODULE_ID}`;
|
|
445
510
|
function createClientSnapshot(scripts, command) {
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
511
|
+
return scripts.map((script) => {
|
|
512
|
+
let suffix = ".dev.user.js";
|
|
513
|
+
if (command === "build") suffix = ".user.js";
|
|
514
|
+
else if (script.server.file) suffix = ".user.js";
|
|
515
|
+
return {
|
|
516
|
+
name: script.header.name,
|
|
517
|
+
version: script.header.version,
|
|
518
|
+
file: `${script.fileName}${suffix}`
|
|
519
|
+
};
|
|
520
|
+
});
|
|
452
521
|
}
|
|
453
522
|
function renderVirtualModule(scripts) {
|
|
454
523
|
return `export const scripts = ${JSON.stringify(scripts)}\n`;
|
|
@@ -540,7 +609,8 @@ function toResolvedScript(config) {
|
|
|
540
609
|
header: config.header,
|
|
541
610
|
server: {
|
|
542
611
|
open: config.server?.open ?? false,
|
|
543
|
-
prefix: config.server?.prefix ?? "server:"
|
|
612
|
+
prefix: config.server?.prefix ?? "server:",
|
|
613
|
+
file: config.server?.file ?? false
|
|
544
614
|
},
|
|
545
615
|
cssInject: config.cssInject ?? "auto",
|
|
546
616
|
align: config.align ?? 1,
|
|
@@ -594,9 +664,13 @@ function shimModule(code, id) {
|
|
|
594
664
|
}
|
|
595
665
|
//#endregion
|
|
596
666
|
//#region src/serve/logger.ts
|
|
597
|
-
function formatInstallLine(installUrl) {
|
|
667
|
+
function formatInstallLine(installUrl, label = "Userscript") {
|
|
598
668
|
const coloredUrl = styleText("cyan", installUrl.replace(/:(\d+)\//, (_match, port) => `:${styleText("bold", port)}/`));
|
|
599
|
-
|
|
669
|
+
const padded = label.padEnd(10);
|
|
670
|
+
return ` ${styleText("green", "➜")} ${styleText("bold", padded)}: ${coloredUrl}`;
|
|
671
|
+
}
|
|
672
|
+
function formatRebuildLine(elapsedMs) {
|
|
673
|
+
return `${styleText("green", "Userscript rebuilt")} ${styleText("dim", `(${elapsedMs}ms)`)}`;
|
|
600
674
|
}
|
|
601
675
|
function formatFaqHint() {
|
|
602
676
|
return `${` ${styleText("green", "➜")} ${styleText("bold", "FAQ")}: `}${styleText("cyan", FAQ_URL)}\n`;
|
|
@@ -660,8 +734,19 @@ import ${JSON.stringify(entryPath)};
|
|
|
660
734
|
function matchDevUserscript(url, fileName) {
|
|
661
735
|
return (url.split("?")[0] ?? "") === `/${fileName}.dev.user.js`;
|
|
662
736
|
}
|
|
663
|
-
function
|
|
664
|
-
return
|
|
737
|
+
function matchProxyUserscript(url, fileName) {
|
|
738
|
+
return (url.split("?")[0] ?? "") === `/${toProxyFileName(fileName)}`;
|
|
739
|
+
}
|
|
740
|
+
function matchFileUserscript(url, fileName) {
|
|
741
|
+
return (url.split("?")[0] ?? "") === `/${fileName}.user.js`;
|
|
742
|
+
}
|
|
743
|
+
function toInstallUrl(origin, fileName, kind = "dev") {
|
|
744
|
+
const names = {
|
|
745
|
+
dev: `${fileName}.dev.user.js`,
|
|
746
|
+
proxy: toProxyFileName(fileName),
|
|
747
|
+
user: `${fileName}.user.js`
|
|
748
|
+
};
|
|
749
|
+
return `${origin.replace(/\/$/, "")}/${names[kind]}`;
|
|
665
750
|
}
|
|
666
751
|
function toServeEntryPath(root, entry) {
|
|
667
752
|
const absolute = resolve(root, entry);
|
|
@@ -713,7 +798,13 @@ function generateDevUserscript(options) {
|
|
|
713
798
|
})}`;
|
|
714
799
|
}
|
|
715
800
|
function findDevScript(url, scripts) {
|
|
716
|
-
return scripts.find((script) => matchDevUserscript(url, script.fileName));
|
|
801
|
+
return scripts.find((script) => !script.server.file && matchDevUserscript(url, script.fileName));
|
|
802
|
+
}
|
|
803
|
+
function findProxyScript(url, scripts) {
|
|
804
|
+
return scripts.find((script) => script.server.file && matchProxyUserscript(url, script.fileName));
|
|
805
|
+
}
|
|
806
|
+
function findFileUserscript(url, scripts) {
|
|
807
|
+
return scripts.find((script) => script.server.file && matchFileUserscript(url, script.fileName));
|
|
717
808
|
}
|
|
718
809
|
function createDevUserscript(options) {
|
|
719
810
|
return generateDevUserscript({
|
|
@@ -760,6 +851,22 @@ function configureDevServer(server, resolved, reactPreamble) {
|
|
|
760
851
|
writeScript(res, createReactBootstrapModule(entry));
|
|
761
852
|
return;
|
|
762
853
|
}
|
|
854
|
+
const fileScript = findFileUserscript(url, resolved.scripts);
|
|
855
|
+
if (fileScript) {
|
|
856
|
+
const userJsPath = resolve(server.config.root, server.config.build.outDir, `${fileScript.fileName}.user.js`);
|
|
857
|
+
try {
|
|
858
|
+
writeScript(res, readFileSync(userJsPath, "utf8"));
|
|
859
|
+
} catch {
|
|
860
|
+
res.statusCode = 404;
|
|
861
|
+
res.end();
|
|
862
|
+
}
|
|
863
|
+
return;
|
|
864
|
+
}
|
|
865
|
+
const proxyScript = findProxyScript(url, resolved.scripts);
|
|
866
|
+
if (proxyScript) {
|
|
867
|
+
writeScript(res, `${generateWatchProxy(proxyScript, resolve(server.config.root, server.config.build.outDir, toRequireFileName(proxyScript.fileName)))}\n`);
|
|
868
|
+
return;
|
|
869
|
+
}
|
|
763
870
|
const script = findDevScript(url, resolved.scripts);
|
|
764
871
|
if (!script) {
|
|
765
872
|
next();
|
|
@@ -779,7 +886,14 @@ function configureDevServer(server, resolved, reactPreamble) {
|
|
|
779
886
|
let origins = [];
|
|
780
887
|
if (urls) origins = urls.local.length ? urls.local : urls.network;
|
|
781
888
|
const printInstall = () => {
|
|
782
|
-
for (const origin of origins) for (const script of resolved.scripts)
|
|
889
|
+
for (const origin of origins) for (const script of resolved.scripts) {
|
|
890
|
+
if (script.server.file) {
|
|
891
|
+
info(formatInstallLine(toInstallUrl(origin, script.fileName, "user")));
|
|
892
|
+
info(formatInstallLine(toInstallUrl(origin, script.fileName, "proxy"), "Proxy"));
|
|
893
|
+
continue;
|
|
894
|
+
}
|
|
895
|
+
info(formatInstallLine(toInstallUrl(origin, script.fileName)));
|
|
896
|
+
}
|
|
783
897
|
info(formatFaqHint());
|
|
784
898
|
};
|
|
785
899
|
const logger = createAfterLocalLogger(info, urls?.local.length ?? 0, printInstall);
|
|
@@ -797,7 +911,7 @@ function configureDevServer(server, resolved, reactPreamble) {
|
|
|
797
911
|
if (!toOpen.length) return;
|
|
798
912
|
queueMicrotask(() => {
|
|
799
913
|
const origin = resolveServerOrigin(server.resolvedUrls);
|
|
800
|
-
for (const script of toOpen) openLink(toInstallUrl(origin, script.fileName));
|
|
914
|
+
for (const script of toOpen) openLink(toInstallUrl(origin, script.fileName, script.server.file ? "user" : "dev"));
|
|
801
915
|
});
|
|
802
916
|
});
|
|
803
917
|
}
|
|
@@ -813,6 +927,68 @@ function UserscriptPlugin(config) {
|
|
|
813
927
|
let resolved = resolvePluginConfig(config);
|
|
814
928
|
let reactPreamble = false;
|
|
815
929
|
let command = "serve";
|
|
930
|
+
let isWatch = false;
|
|
931
|
+
let mode = "production";
|
|
932
|
+
let outDir = "";
|
|
933
|
+
let fileWatchStarted = false;
|
|
934
|
+
const shouldEmitProxy = () => {
|
|
935
|
+
return isWatch || mode === "development";
|
|
936
|
+
};
|
|
937
|
+
const startFileWatchBuild = async (server) => {
|
|
938
|
+
if (command === "build" || fileWatchStarted) return;
|
|
939
|
+
fileWatchStarted = true;
|
|
940
|
+
const outDirAbs = resolve(server.config.root, server.config.build.outDir);
|
|
941
|
+
let firstBuild = true;
|
|
942
|
+
const run = async () => {
|
|
943
|
+
const isRebuild = !firstBuild;
|
|
944
|
+
const started = Date.now();
|
|
945
|
+
await build({
|
|
946
|
+
configFile: server.config.configFile ?? false,
|
|
947
|
+
root: server.config.root,
|
|
948
|
+
mode: server.config.mode,
|
|
949
|
+
logLevel: "silent",
|
|
950
|
+
clearScreen: false,
|
|
951
|
+
plugins: server.config.configFile ? void 0 : [UserscriptPlugin(config)],
|
|
952
|
+
build: {
|
|
953
|
+
outDir: server.config.build.outDir,
|
|
954
|
+
emptyOutDir: firstBuild,
|
|
955
|
+
minify: server.config.build.minify,
|
|
956
|
+
sourcemap: server.config.build.sourcemap,
|
|
957
|
+
write: true,
|
|
958
|
+
reportCompressedSize: false
|
|
959
|
+
}
|
|
960
|
+
});
|
|
961
|
+
firstBuild = false;
|
|
962
|
+
if (isRebuild) server.config.logger.info(formatRebuildLine(Date.now() - started), { timestamp: true });
|
|
963
|
+
};
|
|
964
|
+
try {
|
|
965
|
+
await run();
|
|
966
|
+
} catch (error) {
|
|
967
|
+
fileWatchStarted = false;
|
|
968
|
+
server.config.logger.error(`[${PLUGIN_NAME}] Failed to start file-mode watch build`);
|
|
969
|
+
server.config.logger.error(String(error));
|
|
970
|
+
return;
|
|
971
|
+
}
|
|
972
|
+
let timer;
|
|
973
|
+
const onChange = (file) => {
|
|
974
|
+
if (file.startsWith(outDirAbs)) return;
|
|
975
|
+
clearTimeout(timer);
|
|
976
|
+
timer = setTimeout(() => {
|
|
977
|
+
run().catch((error) => {
|
|
978
|
+
server.config.logger.error(String(error));
|
|
979
|
+
});
|
|
980
|
+
}, 80);
|
|
981
|
+
};
|
|
982
|
+
server.watcher.on("change", onChange);
|
|
983
|
+
server.watcher.on("add", onChange);
|
|
984
|
+
const closeServer = server.close.bind(server);
|
|
985
|
+
server.close = async () => {
|
|
986
|
+
server.watcher.off("change", onChange);
|
|
987
|
+
server.watcher.off("add", onChange);
|
|
988
|
+
clearTimeout(timer);
|
|
989
|
+
return closeServer();
|
|
990
|
+
};
|
|
991
|
+
};
|
|
816
992
|
return [
|
|
817
993
|
{
|
|
818
994
|
name: `${PLUGIN_NAME}:config`,
|
|
@@ -870,6 +1046,7 @@ function UserscriptPlugin(config) {
|
|
|
870
1046
|
apply: "serve",
|
|
871
1047
|
configureServer(server) {
|
|
872
1048
|
configureDevServer(server, resolved, reactPreamble);
|
|
1049
|
+
if (resolved.scripts.some((script) => script.server.file)) startFileWatchBuild(server);
|
|
873
1050
|
}
|
|
874
1051
|
},
|
|
875
1052
|
{
|
|
@@ -887,13 +1064,23 @@ function UserscriptPlugin(config) {
|
|
|
887
1064
|
name: `${PLUGIN_NAME}:build`,
|
|
888
1065
|
apply: "build",
|
|
889
1066
|
enforce: "post",
|
|
1067
|
+
configResolved(viteConfig) {
|
|
1068
|
+
isWatch = Boolean(viteConfig.build.watch);
|
|
1069
|
+
mode = viteConfig.mode;
|
|
1070
|
+
outDir = resolve(viteConfig.root, viteConfig.build.outDir);
|
|
1071
|
+
},
|
|
890
1072
|
generateBundle(_options, bundle) {
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
1073
|
+
isWatch = this.meta.watchMode || isWatch;
|
|
1074
|
+
applyUserscriptBundle(bundle, resolved, {
|
|
1075
|
+
emitFile: (fileName, source) => {
|
|
1076
|
+
this.emitFile({
|
|
1077
|
+
type: "asset",
|
|
1078
|
+
fileName,
|
|
1079
|
+
source
|
|
1080
|
+
});
|
|
1081
|
+
},
|
|
1082
|
+
emitProxy: shouldEmitProxy(),
|
|
1083
|
+
outDir
|
|
897
1084
|
});
|
|
898
1085
|
}
|
|
899
1086
|
}
|