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 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` (headerless IIFE) and `{fileName}.proxy.user.js` with `@require file://` to that IIFE. Install the printed `/{fileName}.proxy.user.js` URL. See [examples/serve-file](./examples/serve-file).
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`: `.proxy.user.js` URL. |
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` (`@require file://`). Install from the printed URL. No HMR. |
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 URL. |
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
- > Tampermonkey must allow local file access (`@require` from `file://`). Violentmonkey polls the required file after you install `{fileName}.proxy.user.js` from the printed URL. HTTP `@require` and page auto-reload are not part of this mode.
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}.proxy.user.js` URL.
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`: headerless `{fileName}.js` and `{fileName}.proxy.user.js`
164
- * with `@require file://` pointing at the IIFE.
165
- * Install the proxy from the printed `/{fileName}.proxy.user.js` URL. No HMR for this script.
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
- 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, {
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 = ".proxy.user.js";
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
- return ` ${styleText("green", "➜")} ${styleText("bold", "Userscript")}: ${coloredUrl}`;
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 toInstallUrl(origin, fileName, file = false) {
724
- const name = file ? toProxyFileName(fileName) : `${fileName}.dev.user.js`;
725
- return `${origin.replace(/\/$/, "")}/${name}`;
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) info(formatInstallLine(toInstallUrl(origin, script.fileName, script.server.file)));
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
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "vite-userscript-plugin",
3
3
  "type": "module",
4
- "version": "2.1.0",
4
+ "version": "2.1.1",
5
5
  "author": {
6
6
  "name": "Vitalij Ryndin",
7
7
  "url": "https://github.com/crashmax-dev"