vite-userscript-plugin 1.10.0 → 1.11.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 CHANGED
@@ -6,9 +6,17 @@
6
6
 
7
7
  > ⚡️ A plugin for developing and building a Tampermonkey userscript based on [Vite](https://vitejs.dev).
8
8
 
9
+ ## Table of contents
10
+
11
+ - [Features](#features)
12
+ - [Install](#install)
13
+ - [Setup config](#setup-config)
14
+ - [Using style modules](#using-style-modules)
15
+ - [Plugin configuration](#plugin-configuration)
16
+
9
17
  ## Features
10
18
 
11
- - 🔥 Hot reloading after changing any files.
19
+ - 🔥 Reloading page after changing any files.
12
20
  - 🔧 Configure Tampermonkey's Userscript header.
13
21
  - 💨 Import all [`grant`](https://www.tampermonkey.net/documentation.php#_grant)'s to the header by default in development mode.
14
22
  - 📝 Automatic addition of used [`grant`](https://www.tampermonkey.net/documentation.php#_grant)'s in the code when building for production.
@@ -28,7 +36,7 @@ yarn add vite-userscript-plugin -D
28
36
  pnpm add vite-userscript-plugin -D
29
37
  ```
30
38
 
31
- ### Setup `vite.config.ts`
39
+ ### Setup config
32
40
 
33
41
  ```js
34
42
  import { defineConfig } from 'vite'
@@ -83,7 +91,19 @@ export default defineConfig((config) => {
83
91
  }
84
92
  ```
85
93
 
86
- ## Plugin Configuration
94
+ ### Using style modules
95
+
96
+ ```js
97
+ import style from './style.css?raw'
98
+
99
+ // inject style element
100
+ const styleElement = GM_addStyle(style)
101
+
102
+ // remove style element
103
+ styleElement.remove()
104
+ ```
105
+
106
+ ## Plugin configuration
87
107
 
88
108
  ```ts
89
109
  interface ServerConfig {
package/dist/index.d.ts CHANGED
@@ -5,8 +5,8 @@ declare const GMwindow: readonly ["unsafeWindow", "window.onurlchange", "window.
5
5
 
6
6
  type RunAt = 'document-start' | 'document-body' | 'document-end' | 'document-idle' | 'context-menu';
7
7
  type GMLiterals<T extends string> = [`GM_${T}` | `GM.${T}`];
8
- type GMWindow = typeof GMwindow[number];
9
- type Grants = GMWindow | GMLiterals<typeof GM[number]>[number];
8
+ type GMWindow = (typeof GMwindow)[number];
9
+ type Grants = GMWindow | GMLiterals<(typeof GM)[number]>[number];
10
10
  type HeaderConfig = {
11
11
  [property: string]: any;
12
12
  /**
@@ -184,4 +184,4 @@ interface UserscriptPluginConfig {
184
184
 
185
185
  declare function UserscriptPlugin(config: UserscriptPluginConfig): PluginOption;
186
186
 
187
- export { UserscriptPluginConfig, UserscriptPlugin as default };
187
+ export { type UserscriptPluginConfig, UserscriptPlugin as default };
package/dist/index.js CHANGED
@@ -1,5 +1,295 @@
1
- import{readFileSync as E,writeFileSync as y}from"fs";import{createServer as J}from"http";import{resolve as c}from"path";import Y from"get-port";import Q from"open";import C from"picocolors";import X from"serve-handler";import{createLogger as Z}from"vite";import{server as ee}from"websocket";var d=class{constructor(r){this.config=r;this.addHomepageMeta(),this.maxKeyLength=Math.max(...Object.keys(this.config).map(e=>e.length))+1}header=[];maxKeyLength;addHomepageMeta(){let r=this.config.homepage??this.config.homepageURL;r&&(this.config.updateURL=new URL(`${this.config.name}.meta.js`,r).href,this.config.downloadURL=new URL(`${this.config.name}.user.js`,r).href)}addSpaces(r){return" ".repeat(this.maxKeyLength-r.length)}addMetadata(r,e){e=Array.isArray(e)?e.join(" "):e===!0?"":e,this.header.push(`// @${r}${this.addSpaces(r)}${e}`)}generate(){for(let[r,e]of Object.entries(this.config))if(Array.isArray(e))e.forEach(s=>this.addMetadata(r,s));else{if(e===void 0)continue;this.addMetadata(r,e)}return["// ==UserScript==",...this.header,"// ==/UserScript=="].join(`
2
- `)}};import{dirname as q}from"path";import{fileURLToPath as z}from"url";var M=q(z(import.meta.url)),v="vite-userscript-plugin",P='console.warn("__STYLE__")',G=new RegExp(/\.(t|j)sx?$/),T=new RegExp(/\.(s[ac]|c|le)ss$/),B=["setValue","getValue","deleteValue","listValues","setClipboard","addStyle","addElement","addValueChangeListener","removeValueChangeListener","registerMenuCommand","unregisterMenuCommand","download","getTab","getTabs","saveTab","openInTab","notification","getResourceURL","getResourceText","xmlhttpRequest","log","info"],K=["unsafeWindow","window.onurlchange","window.focus","window.close"],g=B.map(t=>[`GM_${t}`,`GM.${t}`]).flat();g.push(...K);import{transformWithEsbuild as I}from"vite";function L(t){return[...new Set(Array.isArray(t)?t:t?[t]:[])]}async function h({minify:t,file:r,name:e,loader:s},a){let{code:f}=await I(r,e,{minify:t,loader:s,sourcemap:!1,legalComments:"none",...a});return f}function F(t){let r=[];for(let e of g)t.indexOf(e)!==-1&&r.push(e);return r}var R=class{styles=new Map;async add(r,e,s){let a=await h({name:r,file:e,minify:s,loader:"css"});return this.styles.set(r,a.replace(`
3
- `,"")),""}inject(){return this.styles.size?`GM_addStyle(\`${[...this.styles.values()].join("")}\`)`:void 0}merge(r){let e=[];for(let s of r){let a=this.styles.get(s);a&&e.push([s,a])}this.styles.clear(),e.forEach(s=>this.styles.set(...s))}},j=new R;function te(t){try{let r,e,s=null,a=t.fileName??t.header.name,f=Z("info",{prefix:`[${v}]`,allowClearScreen:!0}),w=J((n,o)=>X(n,o,{public:r.build.outDir})),_=ee;return new _({httpServer:w}).on("request",n=>{s=n.accept(null,n.origin)}),{name:v,apply:"build",config(){return{build:{target:"esnext",minify:!1,lib:{name:a,entry:t.entry,formats:["iife"],fileName:()=>`${a}.js`},rollupOptions:{output:{extend:!0}}}}},async configResolved(n){r=n,e=n.build.watch??!1,t.entry=c(n.root,t.entry),Array.from(["match","require","include","exclude","resource","connect"]).forEach(o=>{let i=t.header[o];t.header[o]=L(i)}),t.server={port:await Y(),open:!1,...t.server}},async transform(n,o){let i=n;return T.test(o)&&(i=await j.add(o,i,!e)),o.includes(t.entry)&&(i=n+P),{code:i,map:null}},generateBundle(n,o){for(let i of Object.values(o)){if(i.type==="asset")continue;let p=Object.keys(i.modules).filter(m=>T.test(m));p.length&&j.merge(p)}},async writeBundle(n,o){let{open:i,port:p}=t.server,m=n.sanitizeFileName(a),D=`${m}.user.js`,O=`${m}.proxy.user.js`,N=`${m}.meta.js`;for(let[u]of Object.entries(o))if(G.test(u)){let x=r.root,b=r.build.outDir,$=c(x,b,u),k=c(x,b,D),A=c(x,b,O),V=c(x,b,N),S=c(M,`ws-${m}.js`);try{let l=E($,"utf8");if(l=l.replace(P,`${j.inject()}`),l=await h({minify:!e,file:l,name:u,loader:"js"},t.esbuildTransformOptions),t.header.grant=L(e?g:[...F(l),...t.header.grant??[]]),e){let H=E(c(M,"ws.js"),"utf8"),W=await h({minify:!e,file:H.replace("__WS__",`ws://localhost:${p}`),name:S,loader:"js"},t.esbuildTransformOptions);y(S,W),y(A,new d({...t.header,require:[...t.header.require??[],"file://"+S,"file://"+$]}).generate())}let U=new d(t.header).generate();y($,l),y(V,U),y(k,`${U}
1
+ // src/index.ts
2
+ import { readFileSync, writeFileSync } from "node:fs";
3
+ import { createServer } from "node:http";
4
+ import { resolve } from "node:path";
5
+ import getPort from "get-port";
6
+ import openLink from "open";
7
+ import colors from "picocolors";
8
+ import serveHandler from "serve-handler";
9
+ import { createLogger } from "vite";
10
+ import { server } from "websocket";
4
11
 
5
- ${l}`)}catch(l){console.log(l)}}if(e&&!w.listening){let u=`http://localhost:${p}`;w.listen(p,()=>{f.clearScreen("info"),f.info(C.bold(`${C.cyan(">>> [vite-userscript-plugin]")} ${C.gray(u)}`))}),i&&await Q(`${u}/${O}`)}else e||(w.close(),process.exit(0))},buildEnd(){e&&(f.clearScreen("info"),s&&s.sendUTF(JSON.stringify({message:"reload"})))}}}catch(r){return console.error(r),{name:v}}}export{te as default};
12
+ // src/banner.ts
13
+ var Banner = class {
14
+ constructor(config) {
15
+ this.config = config;
16
+ this.addHomepageMeta();
17
+ this.maxKeyLength = Math.max(...Object.keys(this.config).map((key) => key.length)) + 1;
18
+ }
19
+ header = [];
20
+ maxKeyLength;
21
+ addHomepageMeta() {
22
+ const homePage = this.config.homepage ?? this.config.homepageURL;
23
+ if (homePage) {
24
+ this.config.updateURL = new URL(
25
+ `${this.config.name}.meta.js`,
26
+ homePage
27
+ ).href;
28
+ this.config.downloadURL = new URL(
29
+ `${this.config.name}.user.js`,
30
+ homePage
31
+ ).href;
32
+ }
33
+ }
34
+ addSpaces(str) {
35
+ return " ".repeat(this.maxKeyLength - str.length);
36
+ }
37
+ addMetadata(key, value) {
38
+ value = Array.isArray(value) ? value.join(" ") : value === true ? "" : value;
39
+ this.header.push(`// @${key}${this.addSpaces(key)}${value}`);
40
+ }
41
+ generate() {
42
+ for (const [key, value] of Object.entries(this.config)) {
43
+ if (Array.isArray(value)) {
44
+ value.forEach((value2) => this.addMetadata(key, value2));
45
+ } else {
46
+ if (value === void 0) continue;
47
+ this.addMetadata(key, value);
48
+ }
49
+ }
50
+ return [
51
+ "// ==UserScript==",
52
+ ...this.header,
53
+ "// ==/UserScript=="
54
+ ].join("\n");
55
+ }
56
+ };
57
+
58
+ // src/constants.ts
59
+ import { dirname } from "node:path";
60
+ import { fileURLToPath } from "node:url";
61
+ var pluginDir = dirname(fileURLToPath(import.meta.url));
62
+ var pluginName = "vite-userscript-plugin";
63
+ var regexpScripts = new RegExp(/\.(t|j)sx?$/);
64
+ var GM = [
65
+ "setValue",
66
+ "getValue",
67
+ "deleteValue",
68
+ "listValues",
69
+ "setClipboard",
70
+ "addStyle",
71
+ "addElement",
72
+ "addValueChangeListener",
73
+ "removeValueChangeListener",
74
+ "registerMenuCommand",
75
+ "unregisterMenuCommand",
76
+ "download",
77
+ "getTab",
78
+ "getTabs",
79
+ "saveTab",
80
+ "openInTab",
81
+ "notification",
82
+ "getResourceURL",
83
+ "getResourceText",
84
+ "xmlhttpRequest",
85
+ "log",
86
+ "info"
87
+ ];
88
+ var GMwindow = [
89
+ "unsafeWindow",
90
+ "window.onurlchange",
91
+ "window.focus",
92
+ "window.close"
93
+ ];
94
+ var grants = GM.map((grant) => [
95
+ `GM_${grant}`,
96
+ `GM.${grant}`
97
+ ]).flat();
98
+ grants.push(...GMwindow);
99
+
100
+ // src/helpers.ts
101
+ import { transformWithEsbuild } from "vite";
102
+ function removeDuplicates(arr) {
103
+ return [...new Set(Array.isArray(arr) ? arr : arr ? [arr] : [])];
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;
114
+ }
115
+ function defineGrants(code) {
116
+ const definedGrants = [];
117
+ for (const grant of grants) {
118
+ if (code.indexOf(grant) !== -1) {
119
+ definedGrants.push(grant);
120
+ }
121
+ }
122
+ return definedGrants;
123
+ }
124
+
125
+ // src/index.ts
126
+ function UserscriptPlugin(config) {
127
+ try {
128
+ let pluginConfig;
129
+ let isBuildWatch;
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
+ };
291
+ }
292
+ }
293
+ export {
294
+ UserscriptPlugin as default
295
+ };
package/dist/ws.js CHANGED
@@ -1 +1,14 @@
1
- function n(){let e=new WebSocket("__WS__");e.addEventListener("close",()=>{setTimeout(n,1e3)}),e.addEventListener("error",()=>{e.close()}),e.addEventListener("message",()=>{location.reload()})}n();
1
+ // src/ws.ts
2
+ function connection() {
3
+ const ws = new WebSocket("__WS__");
4
+ ws.addEventListener("close", () => {
5
+ setTimeout(connection, 1e3);
6
+ });
7
+ ws.addEventListener("error", () => {
8
+ ws.close();
9
+ });
10
+ ws.addEventListener("message", () => {
11
+ location.reload();
12
+ });
13
+ }
14
+ connection();
package/package.json CHANGED
@@ -1,27 +1,17 @@
1
1
  {
2
2
  "name": "vite-userscript-plugin",
3
- "version": "1.10.0",
3
+ "version": "1.11.0",
4
4
  "type": "module",
5
5
  "types": "./dist/index.d.ts",
6
- "main": "./dist/index.cjs",
7
- "module": "./dist/index.js",
8
6
  "exports": {
9
- "require": "./dist/index.cjs",
10
- "import": "./dist/index.js"
7
+ ".": "./dist/index.js"
11
8
  },
12
9
  "files": [
13
10
  "dist",
14
11
  "types"
15
12
  ],
16
- "scripts": {
17
- "dev": "tsup --watch",
18
- "build": "tsup",
19
- "test": "vitest",
20
- "test:ui": "vitest --ui --watch",
21
- "dev:examples": "turbo run dev --filter='./examples/*'",
22
- "build:examples": "turbo run build --filter='./examples/*'",
23
- "format": "prettier --write --ignore-unknown **",
24
- "prepublishOnly": "pnpm build"
13
+ "engines": {
14
+ "node": ">=20"
25
15
  },
26
16
  "repository": {
27
17
  "type": "git",
@@ -45,27 +35,36 @@
45
35
  "url": "https://github.com/crashmax-dev/vite-userscript-plugin/issues"
46
36
  },
47
37
  "dependencies": {
48
- "get-port": "7.0.0",
49
- "open": "9.1.0",
50
- "picocolors": "1.0.0",
38
+ "get-port": "7.1.0",
39
+ "open": "10.1.0",
40
+ "picocolors": "1.1.0",
51
41
  "serve-handler": "6.1.5",
52
- "websocket": "1.0.34"
42
+ "websocket": "1.0.35"
53
43
  },
54
44
  "devDependencies": {
55
- "@crashmax/prettier-config": "3.2.1",
56
- "@crashmax/tsconfig": "2.0.1",
57
- "@types/node": "18.15.3",
58
- "@types/serve-handler": "6.1.1",
59
- "@types/websocket": "1.0.5",
60
- "@vitest/ui": "0.33.0",
61
- "tsup": "7.1.0",
62
- "turbo": "1.10.7",
63
- "typescript": "5.1.6",
64
- "vite": "4.4.2",
65
- "vite-plugin-dts": "3.1.1",
66
- "vitest": "0.33.0"
45
+ "@crashmax/prettier-config": "5.0.2",
46
+ "@crashmax/tsconfig": "2.1.0",
47
+ "@types/node": "22.5.4",
48
+ "@types/serve-handler": "6.1.4",
49
+ "@types/websocket": "1.0.10",
50
+ "@vitest/ui": "2.0.5",
51
+ "tsup": "8.2.4",
52
+ "turbo": "2.1.1",
53
+ "typescript": "5.5.4",
54
+ "vite": "5.4.3",
55
+ "vite-plugin-dts": "4.1.1",
56
+ "vitest": "2.0.5"
67
57
  },
68
58
  "peerDependencies": {
69
59
  "vite": ">=3.0.0"
60
+ },
61
+ "scripts": {
62
+ "dev": "tsup --watch",
63
+ "build": "tsup",
64
+ "test": "vitest",
65
+ "test:ui": "vitest --ui --watch",
66
+ "dev:examples": "turbo run dev --filter=./examples/*",
67
+ "build:examples": "turbo run build --filter=./examples/*",
68
+ "format": "prettier --write --ignore-unknown **"
70
69
  }
71
- }
70
+ }