vite-userscript-plugin 2.1.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 +5 -5
- package/dist/index.d.ts +4 -4
- package/dist/index.js +73 -28
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -83,7 +83,7 @@ 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}.js` (
|
|
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
87
|
|
|
88
88
|
> [!IMPORTANT]
|
|
89
89
|
> Changing `@match`, `@grant`, or `@name` needs a reinstall.
|
|
@@ -135,9 +135,9 @@ See [examples/sourcemap](./examples/sourcemap).
|
|
|
135
135
|
| `entry` | — | Userscript entry. Required. |
|
|
136
136
|
| `header` | — | Metablock. Required: `name`, `version`, `match`. |
|
|
137
137
|
| `fileName` | sanitized `header.name` | Output base name (`{fileName}.user.js`). |
|
|
138
|
-
| `server.open` | `false` | Open the install target when Vite starts. HMR: `.dev.user.js`. `file`: `.
|
|
138
|
+
| `server.open` | `false` | Open the install target when Vite starts. HMR: `.dev.user.js`. `file`: `.user.js` URL. |
|
|
139
139
|
| `server.prefix` | `'server:'` | Prefix for `@name` in serve mode. `false` disables it. |
|
|
140
|
-
| `server.file` | `false` | Watch-build `{fileName}.js` + `{fileName}.proxy.user.js` (
|
|
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. |
|
|
141
141
|
| `cssInject` | `'auto'` | How production CSS is injected. `'auto'` uses `GM_addStyle` or a `<style>` node. |
|
|
142
142
|
| `align` | `1` | Extra spaces after the longest `@key`. `false` — one space. |
|
|
143
143
|
| `generate` | — | Rewrite the generated metablock. |
|
|
@@ -161,7 +161,7 @@ In serve mode the header lists every grant. In production the plugin scans the b
|
|
|
161
161
|
| [svelte](./examples/svelte) | SFC `<style>`. |
|
|
162
162
|
| [multiple-entries](./examples/multiple-entries) | Two scripts. |
|
|
163
163
|
| [sourcemap](./examples/sourcemap) | Inline map, HTML page, virtual module. |
|
|
164
|
-
| [serve-file](./examples/serve-file) | `server.file`, install the proxy from the printed
|
|
164
|
+
| [serve-file](./examples/serve-file) | `server.file`, install `.user.js` or the proxy from the printed URLs. |
|
|
165
165
|
|
|
166
166
|
## FAQ
|
|
167
167
|
|
|
@@ -190,7 +190,7 @@ In serve mode the header lists every grant. In production the plugin scans the b
|
|
|
190
190
|
### `file://` `@require` is blocked
|
|
191
191
|
|
|
192
192
|
> [!NOTE]
|
|
193
|
-
>
|
|
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.
|
|
194
194
|
|
|
195
195
|
## Migration from v1
|
|
196
196
|
|
package/dist/index.d.ts
CHANGED
|
@@ -147,7 +147,7 @@ type HeaderConfig = {
|
|
|
147
147
|
interface ServerConfig {
|
|
148
148
|
/**
|
|
149
149
|
* Open the install target when Vite starts.
|
|
150
|
-
* HMR: `.dev.user.js` URL. `file`: `{fileName}.
|
|
150
|
+
* HMR: `.dev.user.js` URL. `file`: `{fileName}.user.js` URL.
|
|
151
151
|
*
|
|
152
152
|
* @default false
|
|
153
153
|
*/
|
|
@@ -160,9 +160,9 @@ interface ServerConfig {
|
|
|
160
160
|
*/
|
|
161
161
|
prefix?: string | false;
|
|
162
162
|
/**
|
|
163
|
-
* Watch-build into `outDir`:
|
|
164
|
-
* with `@require file://` pointing at the IIFE.
|
|
165
|
-
* Install
|
|
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
166
|
*
|
|
167
167
|
* @default false
|
|
168
168
|
*/
|
package/dist/index.js
CHANGED
|
@@ -2,7 +2,7 @@ import { basename, posix, relative, resolve, sep } from "node:path";
|
|
|
2
2
|
import { build } from "vite";
|
|
3
3
|
import { Buffer } from "node:buffer";
|
|
4
4
|
import { pathToFileURL } from "node:url";
|
|
5
|
-
import { existsSync } from "node:fs";
|
|
5
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
6
6
|
import { styleText } from "node:util";
|
|
7
7
|
import openLink from "open";
|
|
8
8
|
//#region src/names.ts
|
|
@@ -406,6 +406,28 @@ function withSourceMaps(fileNames) {
|
|
|
406
406
|
function findScriptForChunk(chunk, fileName, scripts) {
|
|
407
407
|
return scripts.find((script) => chunk.name === script.fileName || fileName === `${script.fileName}.js` || fileName === `${script.fileName}.user.js`);
|
|
408
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
|
+
}
|
|
409
431
|
function deleteBundleFiles(bundle, fileNames) {
|
|
410
432
|
for (const fileName of fileNames) delete bundle[fileName];
|
|
411
433
|
}
|
|
@@ -442,6 +464,15 @@ function applyUserscriptBundle(bundle, config, context) {
|
|
|
442
464
|
const code = `${cssPrelude}${wrapped}`;
|
|
443
465
|
const emitFileProxy = Boolean(context.emitProxy && context.outDir && script.server.file);
|
|
444
466
|
leftoverAssets.push(`${fileName}.map`);
|
|
467
|
+
const headed = createHeadedUserscript(script, {
|
|
468
|
+
body,
|
|
469
|
+
code,
|
|
470
|
+
cssPrelude,
|
|
471
|
+
extraGrants,
|
|
472
|
+
inlined,
|
|
473
|
+
map: chunk.map,
|
|
474
|
+
wrapped
|
|
475
|
+
});
|
|
445
476
|
if (emitFileProxy) {
|
|
446
477
|
const requireName = toRequireFileName(script.fileName);
|
|
447
478
|
let nextCode = code.endsWith("\n") ? code : `${code}\n`;
|
|
@@ -454,27 +485,12 @@ function applyUserscriptBundle(bundle, config, context) {
|
|
|
454
485
|
chunk.code = nextCode;
|
|
455
486
|
chunk.fileName = requireName;
|
|
456
487
|
emitFile(toProxyFileName(script.fileName), `${generateWatchProxy(script, resolve(context.outDir, requireName))}\n`);
|
|
488
|
+
emitFile(`${script.fileName}.user.js`, headed.code);
|
|
457
489
|
continue;
|
|
458
490
|
}
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
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, {
|
|
491
|
+
chunk.code = headed.code;
|
|
492
|
+
chunk.fileName = `${script.fileName}.user.js`;
|
|
493
|
+
if (script.metaFile) emitFile(`${script.fileName}.meta.js`, generateHeader(headed.headerConfig, {
|
|
478
494
|
align: script.align,
|
|
479
495
|
autoMetaUrls: script.autoMetaUrls,
|
|
480
496
|
fileName: script.fileName,
|
|
@@ -495,7 +511,7 @@ function createClientSnapshot(scripts, command) {
|
|
|
495
511
|
return scripts.map((script) => {
|
|
496
512
|
let suffix = ".dev.user.js";
|
|
497
513
|
if (command === "build") suffix = ".user.js";
|
|
498
|
-
else if (script.server.file) suffix = ".
|
|
514
|
+
else if (script.server.file) suffix = ".user.js";
|
|
499
515
|
return {
|
|
500
516
|
name: script.header.name,
|
|
501
517
|
version: script.header.version,
|
|
@@ -648,9 +664,10 @@ function shimModule(code, id) {
|
|
|
648
664
|
}
|
|
649
665
|
//#endregion
|
|
650
666
|
//#region src/serve/logger.ts
|
|
651
|
-
function formatInstallLine(installUrl) {
|
|
667
|
+
function formatInstallLine(installUrl, label = "Userscript") {
|
|
652
668
|
const coloredUrl = styleText("cyan", installUrl.replace(/:(\d+)\//, (_match, port) => `:${styleText("bold", port)}/`));
|
|
653
|
-
|
|
669
|
+
const padded = label.padEnd(10);
|
|
670
|
+
return ` ${styleText("green", "➜")} ${styleText("bold", padded)}: ${coloredUrl}`;
|
|
654
671
|
}
|
|
655
672
|
function formatRebuildLine(elapsedMs) {
|
|
656
673
|
return `${styleText("green", "Userscript rebuilt")} ${styleText("dim", `(${elapsedMs}ms)`)}`;
|
|
@@ -720,9 +737,16 @@ function matchDevUserscript(url, fileName) {
|
|
|
720
737
|
function matchProxyUserscript(url, fileName) {
|
|
721
738
|
return (url.split("?")[0] ?? "") === `/${toProxyFileName(fileName)}`;
|
|
722
739
|
}
|
|
723
|
-
function
|
|
724
|
-
|
|
725
|
-
|
|
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]}`;
|
|
726
750
|
}
|
|
727
751
|
function toServeEntryPath(root, entry) {
|
|
728
752
|
const absolute = resolve(root, entry);
|
|
@@ -779,6 +803,9 @@ function findDevScript(url, scripts) {
|
|
|
779
803
|
function findProxyScript(url, scripts) {
|
|
780
804
|
return scripts.find((script) => script.server.file && matchProxyUserscript(url, script.fileName));
|
|
781
805
|
}
|
|
806
|
+
function findFileUserscript(url, scripts) {
|
|
807
|
+
return scripts.find((script) => script.server.file && matchFileUserscript(url, script.fileName));
|
|
808
|
+
}
|
|
782
809
|
function createDevUserscript(options) {
|
|
783
810
|
return generateDevUserscript({
|
|
784
811
|
script: options.script,
|
|
@@ -824,6 +851,17 @@ function configureDevServer(server, resolved, reactPreamble) {
|
|
|
824
851
|
writeScript(res, createReactBootstrapModule(entry));
|
|
825
852
|
return;
|
|
826
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
|
+
}
|
|
827
865
|
const proxyScript = findProxyScript(url, resolved.scripts);
|
|
828
866
|
if (proxyScript) {
|
|
829
867
|
writeScript(res, `${generateWatchProxy(proxyScript, resolve(server.config.root, server.config.build.outDir, toRequireFileName(proxyScript.fileName)))}\n`);
|
|
@@ -848,7 +886,14 @@ function configureDevServer(server, resolved, reactPreamble) {
|
|
|
848
886
|
let origins = [];
|
|
849
887
|
if (urls) origins = urls.local.length ? urls.local : urls.network;
|
|
850
888
|
const printInstall = () => {
|
|
851
|
-
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
|
+
}
|
|
852
897
|
info(formatFaqHint());
|
|
853
898
|
};
|
|
854
899
|
const logger = createAfterLocalLogger(info, urls?.local.length ?? 0, printInstall);
|
|
@@ -866,7 +911,7 @@ function configureDevServer(server, resolved, reactPreamble) {
|
|
|
866
911
|
if (!toOpen.length) return;
|
|
867
912
|
queueMicrotask(() => {
|
|
868
913
|
const origin = resolveServerOrigin(server.resolvedUrls);
|
|
869
|
-
for (const script of toOpen) openLink(toInstallUrl(origin, script.fileName, script.server.file));
|
|
914
|
+
for (const script of toOpen) openLink(toInstallUrl(origin, script.fileName, script.server.file ? "user" : "dev"));
|
|
870
915
|
});
|
|
871
916
|
});
|
|
872
917
|
}
|