vite-tampermonkey 1.0.0 → 1.0.2
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/LICENSE +21 -0
- package/README.md +101 -0
- package/dist/index.cjs +316 -0
- package/dist/{types.d.ts → index.d.cts} +10 -11
- package/dist/index.d.ts +59 -4
- package/dist/index.js +276 -119
- package/package.json +8 -13
- package/dist/builder.d.ts +0 -34
- package/dist/builder.d.ts.map +0 -1
- package/dist/builder.js +0 -156
- package/dist/index.d.ts.map +0 -1
- package/dist/resovler.d.ts +0 -20
- package/dist/resovler.d.ts.map +0 -1
- package/dist/resovler.js +0 -45
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js +0 -1
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 koyori
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# 🚀 vite-plugin-tampermonkey
|
|
2
|
+
|
|
3
|
+
一个专为 Vite 设计的 Tampermonkey (油猴) 脚本构建插件。让你能以开发现代 Web 应用的体验(HMR、TypeScript、Tailwind CSS)来编写 Userscript。
|
|
4
|
+
|
|
5
|
+
## ✨ 功能特性
|
|
6
|
+
|
|
7
|
+
- **自动元数据生成**:根据配置自动生成脚本头 (`Userscript Header`)。
|
|
8
|
+
- **双模式开发驱动**:
|
|
9
|
+
- **Dev 模式**:自动生成 `user.dev.js` 指向本地服务,支持热更新(HMR)。
|
|
10
|
+
- **Build 模式**:生产环境构建,支持代码压缩与混淆。
|
|
11
|
+
- **智能资源处理**:
|
|
12
|
+
- **CDN 自动映射**:自动排除 `dependencies` 中的包并替换为远程 CDN 引用。
|
|
13
|
+
- **资源转换**:本地 `Icon` 图片自动转为 `base64` 嵌入。
|
|
14
|
+
- **样式深度集成**:完美支持 `tailwindcss`,自动压缩并注入到脚本中。
|
|
15
|
+
- **高度可定制**:灵活配置 `external` 模块、`globals` 全局变量及 `@resource` 资源。
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## 📦 安装
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
pnpm add -D vite-plugin-tampermonkey
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## 🛠️ 快速开始
|
|
26
|
+
|
|
27
|
+
1. 配置 package.json
|
|
28
|
+
|
|
29
|
+
为了配合插件的开发逻辑,建议在 `scripts` 中配置如下命令:
|
|
30
|
+
|
|
31
|
+
```json
|
|
32
|
+
"scripts": {
|
|
33
|
+
"dev": "vite build --mode development --watch",
|
|
34
|
+
"build": "vite build --mode production"
|
|
35
|
+
}
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
2. 配置 vite.config.ts
|
|
39
|
+
|
|
40
|
+
```typescript
|
|
41
|
+
import { defineConfig } from 'vite';
|
|
42
|
+
import react from '@vitejs/plugin-react';
|
|
43
|
+
import tailwindcss from '@theme-it/vite-plugin-tailwindcss';
|
|
44
|
+
import tampermonkeyPlugin from 'vite-plugin-tampermonkey';
|
|
45
|
+
|
|
46
|
+
export default defineConfig(({ mode }) => {
|
|
47
|
+
const isDev = mode === 'development';
|
|
48
|
+
|
|
49
|
+
return {
|
|
50
|
+
plugins: [
|
|
51
|
+
tailwindcss(),
|
|
52
|
+
react({
|
|
53
|
+
// 开发模式开启自动 Runtime,生产模式建议 classic 以减小体积
|
|
54
|
+
jsxRuntime: isDev ? 'automatic' : 'classic',
|
|
55
|
+
}),
|
|
56
|
+
tampermonkeyPlugin({
|
|
57
|
+
entry: 'src/main.tsx',
|
|
58
|
+
// 脚本元属性 (Userscript Header)
|
|
59
|
+
meta: {
|
|
60
|
+
name: 'My Awesome Script',
|
|
61
|
+
namespace: '[https://midorii.cc/](https://midorii.cc/)',
|
|
62
|
+
match: ['[https://www.google.com/](https://www.google.com/)*'],
|
|
63
|
+
grant: ['GM_addStyle', 'GM_xmlhttpRequest', 'GM_setValue', 'GM_getValue'],
|
|
64
|
+
},
|
|
65
|
+
external: {
|
|
66
|
+
// 排除包体,自动添加 CDN 映射
|
|
67
|
+
modules: {
|
|
68
|
+
'react': '[https://cdn.jsdelivr.net/npm/react@18/umd/react.production.min.js](https://cdn.jsdelivr.net/npm/react@18/umd/react.production.min.js)',
|
|
69
|
+
'react-dom': '[https://cdn.jsdelivr.net/npm/react-dom@18/umd/react-dom.production.min.js](https://cdn.jsdelivr.net/npm/react-dom@18/umd/react-dom.production.min.js)',
|
|
70
|
+
},
|
|
71
|
+
// 全局变量映射
|
|
72
|
+
globals: {
|
|
73
|
+
'react': 'React',
|
|
74
|
+
'react-dom': 'ReactDOM',
|
|
75
|
+
},
|
|
76
|
+
// 外部资源引用 (CSS等)
|
|
77
|
+
resources: {
|
|
78
|
+
'animate-css': '[https://cdn.bootcdn.net/ajax/libs/animate.css/4.1.1/animate.min.css](https://cdn.bootcdn.net/ajax/libs/animate.css/4.1.1/animate.min.css)'
|
|
79
|
+
}
|
|
80
|
+
},
|
|
81
|
+
}),
|
|
82
|
+
],
|
|
83
|
+
};
|
|
84
|
+
});
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## 💡 开发工作流
|
|
88
|
+
|
|
89
|
+
1. 启动本地服务:运行 `pnpm dev`。
|
|
90
|
+
2. 安装开发脚本:插件会在 dist 目录生成 `index.dev.user.js`。将其内容复制并粘贴到 Tampermonkey 编辑器中保存。
|
|
91
|
+
3. 实时调试:修改源码并保存后,受脚本控制的页面将自动应用更改或刷新,无需手动更新脚本内容。
|
|
92
|
+
|
|
93
|
+
## 📝 配置项说明 (Options)
|
|
94
|
+
|
|
95
|
+
| 属性 | 类型 | 描述 |
|
|
96
|
+
| :--- | :--- | :--- |
|
|
97
|
+
| **entry** | `string` | **必填**。脚本入口文件路径 (例如: `src/main.tsx`)。 |
|
|
98
|
+
| **meta** | `UserscriptMeta` | **必填**。油猴脚本头配置,支持所有标准的 `@match`, `@grant`, `@run-at` 等。 |
|
|
99
|
+
| **external.modules** | `Record<string, string>` | 需要从 Bundle 中排除的依赖及其对应的 CDN 链接。 |
|
|
100
|
+
| **external.globals** | `Record<string, string>` | 外部依赖对应的全局变量映射 (例如: `react` -> `React`)。 |
|
|
101
|
+
| **external.resources** | `Record<string, string>` | 使用 `@resource` 注入的外部资源列表,自动添加至脚本头。 |
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/index.ts
|
|
31
|
+
var index_exports = {};
|
|
32
|
+
__export(index_exports, {
|
|
33
|
+
tampermonkeyPlugin: () => tampermonkeyPlugin
|
|
34
|
+
});
|
|
35
|
+
module.exports = __toCommonJS(index_exports);
|
|
36
|
+
var import_node_fs = require("fs");
|
|
37
|
+
var import_node_path = require("path");
|
|
38
|
+
var import_node_url = require("url");
|
|
39
|
+
|
|
40
|
+
// src/builder.ts
|
|
41
|
+
var import_crypto = __toESM(require("crypto"), 1);
|
|
42
|
+
var import_fs = __toESM(require("fs"), 1);
|
|
43
|
+
var TampermonkeyMetaBuilder = class {
|
|
44
|
+
lines = ["// ==UserScript=="];
|
|
45
|
+
add(key, value) {
|
|
46
|
+
if (value == null) return this;
|
|
47
|
+
if (key === "icon") {
|
|
48
|
+
if (typeof value === "string" && !value.startsWith("http")) {
|
|
49
|
+
const iconBase64 = import_fs.default.readFileSync(value, "base64");
|
|
50
|
+
value = `data:image/png;base64,${iconBase64}`;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
if (Array.isArray(value)) {
|
|
54
|
+
const set = new Set(value);
|
|
55
|
+
set.forEach((v) => this.push(key, v));
|
|
56
|
+
} else {
|
|
57
|
+
this.push(key, value === true ? "" : String(value));
|
|
58
|
+
}
|
|
59
|
+
return this;
|
|
60
|
+
}
|
|
61
|
+
addRecord(record) {
|
|
62
|
+
if (!record) return this;
|
|
63
|
+
for (const [k, v] of Object.entries(record)) {
|
|
64
|
+
const key = k.replace(/([A-Z])/g, "-$1").toLowerCase();
|
|
65
|
+
if (Array.isArray(v)) {
|
|
66
|
+
v.forEach((val) => this.push(key, String(val)));
|
|
67
|
+
} else {
|
|
68
|
+
this.push(key, v === true ? "" : String(v));
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return this;
|
|
72
|
+
}
|
|
73
|
+
push(key, value) {
|
|
74
|
+
const spacing = " ".repeat(Math.max(1, 12 - key.length));
|
|
75
|
+
this.lines.push(`// @${key}${spacing}${value}`.trimEnd());
|
|
76
|
+
}
|
|
77
|
+
build() {
|
|
78
|
+
return [...this.lines, "// ==/UserScript==", ""].join("\n");
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
var TampermonkeyBuilder = class {
|
|
82
|
+
cssCode = "";
|
|
83
|
+
jsCode = "";
|
|
84
|
+
meta = {};
|
|
85
|
+
external;
|
|
86
|
+
externalResources = [];
|
|
87
|
+
constructor(meta, external) {
|
|
88
|
+
this.meta = meta;
|
|
89
|
+
this.external = external;
|
|
90
|
+
this.externalResources = this.getExternalResources();
|
|
91
|
+
this.addCode("var global = window;\n");
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* 添加CSS代码
|
|
95
|
+
* @param source 源码
|
|
96
|
+
*/
|
|
97
|
+
addCss(source) {
|
|
98
|
+
this.cssCode += typeof source === "string" ? source : Buffer.from(source).toString("utf8");
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* 添加JS代码
|
|
102
|
+
* @param code 代码
|
|
103
|
+
*/
|
|
104
|
+
addCode(code) {
|
|
105
|
+
this.jsCode += code;
|
|
106
|
+
}
|
|
107
|
+
getGrant() {
|
|
108
|
+
let grant = this.meta.grant ?? [];
|
|
109
|
+
if (typeof grant === "string") {
|
|
110
|
+
grant = [grant];
|
|
111
|
+
}
|
|
112
|
+
return grant;
|
|
113
|
+
}
|
|
114
|
+
getExternalResources() {
|
|
115
|
+
const externalResources = Array.from(
|
|
116
|
+
new Set(Object.values(this.external?.resources ?? {}))
|
|
117
|
+
);
|
|
118
|
+
return externalResources.map((resource) => {
|
|
119
|
+
const md5 = import_crypto.default.createHash("md5").update(resource).digest("hex").substring(0, 8);
|
|
120
|
+
return {
|
|
121
|
+
name: md5,
|
|
122
|
+
url: resource
|
|
123
|
+
};
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
buildHeader(isDev = false, filename) {
|
|
127
|
+
const name = isDev ? `${this.meta.name} (Dev)` : this.meta.name;
|
|
128
|
+
const grant = this.getGrant();
|
|
129
|
+
if (this.cssCode.trim().length > 0) {
|
|
130
|
+
grant.push("GM_addStyle");
|
|
131
|
+
}
|
|
132
|
+
if (this.externalResources.length > 0 && !isDev) {
|
|
133
|
+
grant.push("GM_getResourceText");
|
|
134
|
+
}
|
|
135
|
+
const requires = [
|
|
136
|
+
...this.meta.require ?? [],
|
|
137
|
+
...isDev ? [] : Object.values(this.external?.modules ?? {}),
|
|
138
|
+
...filename && isDev ? [filename] : []
|
|
139
|
+
];
|
|
140
|
+
const resources = [
|
|
141
|
+
...this.meta.resource ?? [],
|
|
142
|
+
...isDev && this.externalResources.length > 0 ? [] : this.externalResources.map(
|
|
143
|
+
(resource) => `${resource.name} ${resource.url}`
|
|
144
|
+
)
|
|
145
|
+
];
|
|
146
|
+
return new TampermonkeyMetaBuilder().add("name", name).add("namespace", this.meta.namespace).add("version", this.meta.version).add("description", this.meta.description).add("author", this.meta.author).add("match", this.meta.match).add("include", this.meta.include).add("grant", this.meta.grant).add("run-at", this.meta.runAt).add("icon", this.meta.icon).add("require", requires).add("resource", resources).addRecord(this.meta.extra).add("license", this.meta.license).add("downloadUrl", this.meta.downloadUrl).add("updateUrl", this.meta.updateUrl).build();
|
|
147
|
+
}
|
|
148
|
+
build(isDev = false) {
|
|
149
|
+
const header = !isDev ? this.buildHeader(isDev) : "";
|
|
150
|
+
if (!isDev) {
|
|
151
|
+
this.externalResources.forEach((resource) => {
|
|
152
|
+
if (resource.url.endsWith(".css")) {
|
|
153
|
+
this.jsCode = 'GM_addStyle(GM_getResourceText("' + resource.name + '"));\n\n' + this.jsCode;
|
|
154
|
+
}
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
const code = `${header}
|
|
158
|
+
GM_addStyle(${JSON.stringify(this.cssCode)});
|
|
159
|
+
${this.jsCode}`;
|
|
160
|
+
return code;
|
|
161
|
+
}
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
// src/resovler.ts
|
|
165
|
+
function resolveMeta(pkg, userMeta) {
|
|
166
|
+
const grants = new Set(userMeta.grant ?? []);
|
|
167
|
+
return {
|
|
168
|
+
name: pkg.name,
|
|
169
|
+
version: pkg.version,
|
|
170
|
+
description: pkg.description,
|
|
171
|
+
author: pkg.author,
|
|
172
|
+
...userMeta,
|
|
173
|
+
grant: Array.from(grants)
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
function createExternalResolver(external) {
|
|
177
|
+
if (!external)
|
|
178
|
+
return {
|
|
179
|
+
isModule: () => false,
|
|
180
|
+
getGlobal: () => "",
|
|
181
|
+
isResource: () => false
|
|
182
|
+
};
|
|
183
|
+
const modules = Object.keys(external.modules ?? {});
|
|
184
|
+
const globals = external.globals ?? {};
|
|
185
|
+
const resources = Object.keys(external.resources ?? {});
|
|
186
|
+
return {
|
|
187
|
+
isModule(id) {
|
|
188
|
+
return modules.some((name) => id === name || id.startsWith(`${name}/`));
|
|
189
|
+
},
|
|
190
|
+
getGlobal(id) {
|
|
191
|
+
return globals[id] ?? id.replace(/[^\w$]/g, "_");
|
|
192
|
+
},
|
|
193
|
+
isResource(id) {
|
|
194
|
+
return resources.some((name) => id.endsWith(name));
|
|
195
|
+
}
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// src/index.ts
|
|
200
|
+
var DEFAULT_META = {
|
|
201
|
+
namespace: "https://github.com/",
|
|
202
|
+
runAt: "document-idle",
|
|
203
|
+
grant: []
|
|
204
|
+
};
|
|
205
|
+
var DEFAULT_PKG_NAME = "userscript";
|
|
206
|
+
function tampermonkeyPlugin(options = {}) {
|
|
207
|
+
const {
|
|
208
|
+
root = process.cwd(),
|
|
209
|
+
entry = "src/main.tsx",
|
|
210
|
+
packageJson = (0, import_node_path.resolve)(root, "package.json"),
|
|
211
|
+
meta: userMeta = {}
|
|
212
|
+
} = options;
|
|
213
|
+
const pkg = JSON.parse((0, import_node_fs.readFileSync)(packageJson, "utf8"));
|
|
214
|
+
const pkgName = pkg.name || DEFAULT_PKG_NAME;
|
|
215
|
+
const baseName = pkgName.replace(/^@/, "").replace(/\//g, "-");
|
|
216
|
+
const externalResolver = createExternalResolver(options.external);
|
|
217
|
+
const meta = resolveMeta(pkg, { ...DEFAULT_META, ...userMeta });
|
|
218
|
+
const builder = new TampermonkeyBuilder(meta, options.external);
|
|
219
|
+
let isDev = false;
|
|
220
|
+
const fileName = `user.js`;
|
|
221
|
+
let outDirAbs = "";
|
|
222
|
+
return {
|
|
223
|
+
name: "vite-tampermonkey-plugin",
|
|
224
|
+
enforce: "post",
|
|
225
|
+
apply: "build",
|
|
226
|
+
/**
|
|
227
|
+
* 配置
|
|
228
|
+
* @param mode 模式
|
|
229
|
+
* @returns 配置
|
|
230
|
+
*/
|
|
231
|
+
config({ mode }) {
|
|
232
|
+
isDev = mode === "development";
|
|
233
|
+
return {
|
|
234
|
+
publicDir: false,
|
|
235
|
+
build: {
|
|
236
|
+
lib: {
|
|
237
|
+
entry: (0, import_node_path.resolve)(root, entry),
|
|
238
|
+
name: baseName.replace(/-/g, "_"),
|
|
239
|
+
fileName: () => fileName,
|
|
240
|
+
formats: ["iife"]
|
|
241
|
+
},
|
|
242
|
+
minify: false,
|
|
243
|
+
cssMinify: true,
|
|
244
|
+
sourcemap: false,
|
|
245
|
+
emptyOutDir: true,
|
|
246
|
+
cssCodeSplit: false,
|
|
247
|
+
rolldownOptions: isDev ? void 0 : {
|
|
248
|
+
external: externalResolver.isModule,
|
|
249
|
+
output: {
|
|
250
|
+
globals: externalResolver.getGlobal,
|
|
251
|
+
comments: false
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
},
|
|
255
|
+
define: {
|
|
256
|
+
"process.env.NODE_ENV": JSON.stringify(mode)
|
|
257
|
+
}
|
|
258
|
+
};
|
|
259
|
+
},
|
|
260
|
+
configResolved(config) {
|
|
261
|
+
outDirAbs = (0, import_node_path.resolve)(root, config.build.outDir);
|
|
262
|
+
},
|
|
263
|
+
/**
|
|
264
|
+
* 拦截外部资源,防止写入到输出文件,之后自定义代码注入到用户脚本中.
|
|
265
|
+
* @param code
|
|
266
|
+
* @param id
|
|
267
|
+
*/
|
|
268
|
+
load(id) {
|
|
269
|
+
if (externalResolver.isResource(id) && !isDev) {
|
|
270
|
+
return {
|
|
271
|
+
code: "",
|
|
272
|
+
map: null
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
return null;
|
|
276
|
+
},
|
|
277
|
+
/**
|
|
278
|
+
* 生成打包
|
|
279
|
+
* @param _options 选项
|
|
280
|
+
* @param bundle 打包
|
|
281
|
+
*/
|
|
282
|
+
generateBundle(_, bundle) {
|
|
283
|
+
for (const key of Object.keys(bundle)) {
|
|
284
|
+
const file = bundle[key];
|
|
285
|
+
if (file.type === "asset" && file.fileName.endsWith(".css")) {
|
|
286
|
+
builder.addCss(file.source);
|
|
287
|
+
delete bundle[key];
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
for (const key of Object.keys(bundle)) {
|
|
291
|
+
const file = bundle[key];
|
|
292
|
+
if (file.type !== "chunk" || !file.isEntry) continue;
|
|
293
|
+
builder.addCode(file.code);
|
|
294
|
+
if (isDev) {
|
|
295
|
+
const fileUrl = (0, import_node_url.pathToFileURL)((0, import_node_path.resolve)(outDirAbs, fileName)).href;
|
|
296
|
+
const header = builder.buildHeader(isDev, fileUrl);
|
|
297
|
+
this.emitFile({
|
|
298
|
+
type: "asset",
|
|
299
|
+
fileName: `user.dev.js`,
|
|
300
|
+
source: header
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
file.code = builder.build(isDev);
|
|
304
|
+
this.emitFile({
|
|
305
|
+
type: "asset",
|
|
306
|
+
fileName: `meta.json`,
|
|
307
|
+
source: JSON.stringify(meta, null, 2)
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
314
|
+
0 && (module.exports = {
|
|
315
|
+
tampermonkeyPlugin
|
|
316
|
+
});
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
import { Plugin } from 'vite';
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* 油猴元数据
|
|
3
5
|
*/
|
|
4
|
-
|
|
6
|
+
type TampermonkeyMeta = {
|
|
5
7
|
name?: string;
|
|
6
8
|
namespace?: string;
|
|
7
9
|
version?: string;
|
|
@@ -28,7 +30,7 @@ export type TampermonkeyMeta = {
|
|
|
28
30
|
/**
|
|
29
31
|
* 外部资源
|
|
30
32
|
*/
|
|
31
|
-
|
|
33
|
+
type ExternalResource = {
|
|
32
34
|
/** 外部资源(JS/CSS 等),用于生成 @require/@resource */
|
|
33
35
|
resources?: Record<string, string>;
|
|
34
36
|
/** 外部模块(用于 rolldown external + @require) */
|
|
@@ -39,7 +41,7 @@ export type ExternalResource = {
|
|
|
39
41
|
/**
|
|
40
42
|
* 油猴插件选项
|
|
41
43
|
*/
|
|
42
|
-
|
|
44
|
+
type TampermonkeyPluginOptions = {
|
|
43
45
|
/** 用户脚本入口,默认 `src/userscript.tsx`(相对 root) */
|
|
44
46
|
entry?: string;
|
|
45
47
|
/** 工程根目录,默认 `process.cwd()` */
|
|
@@ -51,13 +53,10 @@ export type TampermonkeyPluginOptions = {
|
|
|
51
53
|
/** 外部资源 */
|
|
52
54
|
external?: ExternalResource;
|
|
53
55
|
};
|
|
56
|
+
|
|
54
57
|
/**
|
|
55
|
-
*
|
|
58
|
+
* 油猴插件
|
|
56
59
|
*/
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
description?: string;
|
|
61
|
-
author?: string;
|
|
62
|
-
};
|
|
63
|
-
//# sourceMappingURL=types.d.ts.map
|
|
60
|
+
declare function tampermonkeyPlugin(options?: TampermonkeyPluginOptions): Plugin;
|
|
61
|
+
|
|
62
|
+
export { tampermonkeyPlugin };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,62 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
1
|
+
import { Plugin } from 'vite';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 油猴元数据
|
|
5
|
+
*/
|
|
6
|
+
type TampermonkeyMeta = {
|
|
7
|
+
name?: string;
|
|
8
|
+
namespace?: string;
|
|
9
|
+
version?: string;
|
|
10
|
+
description?: string;
|
|
11
|
+
author?: string;
|
|
12
|
+
/** 单条或多条 @match */
|
|
13
|
+
match?: string | string[];
|
|
14
|
+
/** 单条或多条 @include */
|
|
15
|
+
include?: string | string[];
|
|
16
|
+
/** 单条或多条 @grant */
|
|
17
|
+
grant?: string | string[];
|
|
18
|
+
runAt?: 'document-start' | 'document-end' | 'document-idle';
|
|
19
|
+
icon?: string;
|
|
20
|
+
exclude?: string | string[];
|
|
21
|
+
require?: string | string[];
|
|
22
|
+
connect?: string | string[];
|
|
23
|
+
resource?: string | string[];
|
|
24
|
+
/** 其它 // @key value,如 { noframes: '' } -> // @noframes */
|
|
25
|
+
extra?: Record<string, string | string[] | true>;
|
|
26
|
+
license?: string;
|
|
27
|
+
downloadUrl?: string;
|
|
28
|
+
updateUrl?: string;
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* 外部资源
|
|
32
|
+
*/
|
|
33
|
+
type ExternalResource = {
|
|
34
|
+
/** 外部资源(JS/CSS 等),用于生成 @require/@resource */
|
|
35
|
+
resources?: Record<string, string>;
|
|
36
|
+
/** 外部模块(用于 rolldown external + @require) */
|
|
37
|
+
modules?: Record<string, string>;
|
|
38
|
+
/** 外部全局变量映射 */
|
|
39
|
+
globals?: Record<string, string>;
|
|
40
|
+
};
|
|
41
|
+
/**
|
|
42
|
+
* 油猴插件选项
|
|
43
|
+
*/
|
|
44
|
+
type TampermonkeyPluginOptions = {
|
|
45
|
+
/** 用户脚本入口,默认 `src/userscript.tsx`(相对 root) */
|
|
46
|
+
entry?: string;
|
|
47
|
+
/** 工程根目录,默认 `process.cwd()` */
|
|
48
|
+
root?: string;
|
|
49
|
+
/** package.json 路径,默认 `<root>/package.json` */
|
|
50
|
+
packageJson?: string;
|
|
51
|
+
/** 覆盖 / 补充 UserScript 元数据 */
|
|
52
|
+
meta?: TampermonkeyMeta;
|
|
53
|
+
/** 外部资源 */
|
|
54
|
+
external?: ExternalResource;
|
|
55
|
+
};
|
|
56
|
+
|
|
3
57
|
/**
|
|
4
58
|
* 油猴插件
|
|
5
59
|
*/
|
|
6
|
-
|
|
7
|
-
|
|
60
|
+
declare function tampermonkeyPlugin(options?: TampermonkeyPluginOptions): Plugin;
|
|
61
|
+
|
|
62
|
+
export { tampermonkeyPlugin };
|
package/dist/index.js
CHANGED
|
@@ -1,124 +1,281 @@
|
|
|
1
|
-
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
1
|
+
// src/index.ts
|
|
2
|
+
import { readFileSync } from "fs";
|
|
3
|
+
import { resolve } from "path";
|
|
4
|
+
import { pathToFileURL } from "url";
|
|
5
|
+
|
|
6
|
+
// src/builder.ts
|
|
7
|
+
import crypto from "crypto";
|
|
8
|
+
import fs from "fs";
|
|
9
|
+
var TampermonkeyMetaBuilder = class {
|
|
10
|
+
lines = ["// ==UserScript=="];
|
|
11
|
+
add(key, value) {
|
|
12
|
+
if (value == null) return this;
|
|
13
|
+
if (key === "icon") {
|
|
14
|
+
if (typeof value === "string" && !value.startsWith("http")) {
|
|
15
|
+
const iconBase64 = fs.readFileSync(value, "base64");
|
|
16
|
+
value = `data:image/png;base64,${iconBase64}`;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
if (Array.isArray(value)) {
|
|
20
|
+
const set = new Set(value);
|
|
21
|
+
set.forEach((v) => this.push(key, v));
|
|
22
|
+
} else {
|
|
23
|
+
this.push(key, value === true ? "" : String(value));
|
|
24
|
+
}
|
|
25
|
+
return this;
|
|
26
|
+
}
|
|
27
|
+
addRecord(record) {
|
|
28
|
+
if (!record) return this;
|
|
29
|
+
for (const [k, v] of Object.entries(record)) {
|
|
30
|
+
const key = k.replace(/([A-Z])/g, "-$1").toLowerCase();
|
|
31
|
+
if (Array.isArray(v)) {
|
|
32
|
+
v.forEach((val) => this.push(key, String(val)));
|
|
33
|
+
} else {
|
|
34
|
+
this.push(key, v === true ? "" : String(v));
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return this;
|
|
38
|
+
}
|
|
39
|
+
push(key, value) {
|
|
40
|
+
const spacing = " ".repeat(Math.max(1, 12 - key.length));
|
|
41
|
+
this.lines.push(`// @${key}${spacing}${value}`.trimEnd());
|
|
42
|
+
}
|
|
43
|
+
build() {
|
|
44
|
+
return [...this.lines, "// ==/UserScript==", ""].join("\n");
|
|
45
|
+
}
|
|
10
46
|
};
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
47
|
+
var TampermonkeyBuilder = class {
|
|
48
|
+
cssCode = "";
|
|
49
|
+
jsCode = "";
|
|
50
|
+
meta = {};
|
|
51
|
+
external;
|
|
52
|
+
externalResources = [];
|
|
53
|
+
constructor(meta, external) {
|
|
54
|
+
this.meta = meta;
|
|
55
|
+
this.external = external;
|
|
56
|
+
this.externalResources = this.getExternalResources();
|
|
57
|
+
this.addCode("var global = window;\n");
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* 添加CSS代码
|
|
61
|
+
* @param source 源码
|
|
62
|
+
*/
|
|
63
|
+
addCss(source) {
|
|
64
|
+
this.cssCode += typeof source === "string" ? source : Buffer.from(source).toString("utf8");
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* 添加JS代码
|
|
68
|
+
* @param code 代码
|
|
69
|
+
*/
|
|
70
|
+
addCode(code) {
|
|
71
|
+
this.jsCode += code;
|
|
72
|
+
}
|
|
73
|
+
getGrant() {
|
|
74
|
+
let grant = this.meta.grant ?? [];
|
|
75
|
+
if (typeof grant === "string") {
|
|
76
|
+
grant = [grant];
|
|
77
|
+
}
|
|
78
|
+
return grant;
|
|
79
|
+
}
|
|
80
|
+
getExternalResources() {
|
|
81
|
+
const externalResources = Array.from(
|
|
82
|
+
new Set(Object.values(this.external?.resources ?? {}))
|
|
83
|
+
);
|
|
84
|
+
return externalResources.map((resource) => {
|
|
85
|
+
const md5 = crypto.createHash("md5").update(resource).digest("hex").substring(0, 8);
|
|
86
|
+
return {
|
|
87
|
+
name: md5,
|
|
88
|
+
url: resource
|
|
89
|
+
};
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
buildHeader(isDev = false, filename) {
|
|
93
|
+
const name = isDev ? `${this.meta.name} (Dev)` : this.meta.name;
|
|
94
|
+
const grant = this.getGrant();
|
|
95
|
+
if (this.cssCode.trim().length > 0) {
|
|
96
|
+
grant.push("GM_addStyle");
|
|
97
|
+
}
|
|
98
|
+
if (this.externalResources.length > 0 && !isDev) {
|
|
99
|
+
grant.push("GM_getResourceText");
|
|
100
|
+
}
|
|
101
|
+
const requires = [
|
|
102
|
+
...this.meta.require ?? [],
|
|
103
|
+
...isDev ? [] : Object.values(this.external?.modules ?? {}),
|
|
104
|
+
...filename && isDev ? [filename] : []
|
|
105
|
+
];
|
|
106
|
+
const resources = [
|
|
107
|
+
...this.meta.resource ?? [],
|
|
108
|
+
...isDev && this.externalResources.length > 0 ? [] : this.externalResources.map(
|
|
109
|
+
(resource) => `${resource.name} ${resource.url}`
|
|
110
|
+
)
|
|
111
|
+
];
|
|
112
|
+
return new TampermonkeyMetaBuilder().add("name", name).add("namespace", this.meta.namespace).add("version", this.meta.version).add("description", this.meta.description).add("author", this.meta.author).add("match", this.meta.match).add("include", this.meta.include).add("grant", this.meta.grant).add("run-at", this.meta.runAt).add("icon", this.meta.icon).add("require", requires).add("resource", resources).addRecord(this.meta.extra).add("license", this.meta.license).add("downloadUrl", this.meta.downloadUrl).add("updateUrl", this.meta.updateUrl).build();
|
|
113
|
+
}
|
|
114
|
+
build(isDev = false) {
|
|
115
|
+
const header = !isDev ? this.buildHeader(isDev) : "";
|
|
116
|
+
if (!isDev) {
|
|
117
|
+
this.externalResources.forEach((resource) => {
|
|
118
|
+
if (resource.url.endsWith(".css")) {
|
|
119
|
+
this.jsCode = 'GM_addStyle(GM_getResourceText("' + resource.name + '"));\n\n' + this.jsCode;
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
const code = `${header}
|
|
124
|
+
GM_addStyle(${JSON.stringify(this.cssCode)});
|
|
125
|
+
${this.jsCode}`;
|
|
126
|
+
return code;
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
// src/resovler.ts
|
|
131
|
+
function resolveMeta(pkg, userMeta) {
|
|
132
|
+
const grants = new Set(userMeta.grant ?? []);
|
|
133
|
+
return {
|
|
134
|
+
name: pkg.name,
|
|
135
|
+
version: pkg.version,
|
|
136
|
+
description: pkg.description,
|
|
137
|
+
author: pkg.author,
|
|
138
|
+
...userMeta,
|
|
139
|
+
grant: Array.from(grants)
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
function createExternalResolver(external) {
|
|
143
|
+
if (!external)
|
|
29
144
|
return {
|
|
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
|
-
|
|
56
|
-
|
|
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
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
continue;
|
|
104
|
-
builder.addCode(file.code);
|
|
105
|
-
if (isDev) {
|
|
106
|
-
const fileUrl = pathToFileURL(resolve(outDirAbs, fileName)).href;
|
|
107
|
-
const header = builder.buildHeader(isDev, fileUrl);
|
|
108
|
-
this.emitFile({
|
|
109
|
-
type: 'asset',
|
|
110
|
-
fileName: `user.dev.js`,
|
|
111
|
-
source: header,
|
|
112
|
-
});
|
|
113
|
-
}
|
|
114
|
-
file.code = builder.build(isDev);
|
|
115
|
-
// 产生一个meta.json文件
|
|
116
|
-
this.emitFile({
|
|
117
|
-
type: 'asset',
|
|
118
|
-
fileName: `meta.json`,
|
|
119
|
-
source: JSON.stringify(meta, null, 2),
|
|
120
|
-
});
|
|
145
|
+
isModule: () => false,
|
|
146
|
+
getGlobal: () => "",
|
|
147
|
+
isResource: () => false
|
|
148
|
+
};
|
|
149
|
+
const modules = Object.keys(external.modules ?? {});
|
|
150
|
+
const globals = external.globals ?? {};
|
|
151
|
+
const resources = Object.keys(external.resources ?? {});
|
|
152
|
+
return {
|
|
153
|
+
isModule(id) {
|
|
154
|
+
return modules.some((name) => id === name || id.startsWith(`${name}/`));
|
|
155
|
+
},
|
|
156
|
+
getGlobal(id) {
|
|
157
|
+
return globals[id] ?? id.replace(/[^\w$]/g, "_");
|
|
158
|
+
},
|
|
159
|
+
isResource(id) {
|
|
160
|
+
return resources.some((name) => id.endsWith(name));
|
|
161
|
+
}
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// src/index.ts
|
|
166
|
+
var DEFAULT_META = {
|
|
167
|
+
namespace: "https://github.com/",
|
|
168
|
+
runAt: "document-idle",
|
|
169
|
+
grant: []
|
|
170
|
+
};
|
|
171
|
+
var DEFAULT_PKG_NAME = "userscript";
|
|
172
|
+
function tampermonkeyPlugin(options = {}) {
|
|
173
|
+
const {
|
|
174
|
+
root = process.cwd(),
|
|
175
|
+
entry = "src/main.tsx",
|
|
176
|
+
packageJson = resolve(root, "package.json"),
|
|
177
|
+
meta: userMeta = {}
|
|
178
|
+
} = options;
|
|
179
|
+
const pkg = JSON.parse(readFileSync(packageJson, "utf8"));
|
|
180
|
+
const pkgName = pkg.name || DEFAULT_PKG_NAME;
|
|
181
|
+
const baseName = pkgName.replace(/^@/, "").replace(/\//g, "-");
|
|
182
|
+
const externalResolver = createExternalResolver(options.external);
|
|
183
|
+
const meta = resolveMeta(pkg, { ...DEFAULT_META, ...userMeta });
|
|
184
|
+
const builder = new TampermonkeyBuilder(meta, options.external);
|
|
185
|
+
let isDev = false;
|
|
186
|
+
const fileName = `user.js`;
|
|
187
|
+
let outDirAbs = "";
|
|
188
|
+
return {
|
|
189
|
+
name: "vite-tampermonkey-plugin",
|
|
190
|
+
enforce: "post",
|
|
191
|
+
apply: "build",
|
|
192
|
+
/**
|
|
193
|
+
* 配置
|
|
194
|
+
* @param mode 模式
|
|
195
|
+
* @returns 配置
|
|
196
|
+
*/
|
|
197
|
+
config({ mode }) {
|
|
198
|
+
isDev = mode === "development";
|
|
199
|
+
return {
|
|
200
|
+
publicDir: false,
|
|
201
|
+
build: {
|
|
202
|
+
lib: {
|
|
203
|
+
entry: resolve(root, entry),
|
|
204
|
+
name: baseName.replace(/-/g, "_"),
|
|
205
|
+
fileName: () => fileName,
|
|
206
|
+
formats: ["iife"]
|
|
207
|
+
},
|
|
208
|
+
minify: false,
|
|
209
|
+
cssMinify: true,
|
|
210
|
+
sourcemap: false,
|
|
211
|
+
emptyOutDir: true,
|
|
212
|
+
cssCodeSplit: false,
|
|
213
|
+
rolldownOptions: isDev ? void 0 : {
|
|
214
|
+
external: externalResolver.isModule,
|
|
215
|
+
output: {
|
|
216
|
+
globals: externalResolver.getGlobal,
|
|
217
|
+
comments: false
|
|
121
218
|
}
|
|
219
|
+
}
|
|
122
220
|
},
|
|
123
|
-
|
|
221
|
+
define: {
|
|
222
|
+
"process.env.NODE_ENV": JSON.stringify(mode)
|
|
223
|
+
}
|
|
224
|
+
};
|
|
225
|
+
},
|
|
226
|
+
configResolved(config) {
|
|
227
|
+
outDirAbs = resolve(root, config.build.outDir);
|
|
228
|
+
},
|
|
229
|
+
/**
|
|
230
|
+
* 拦截外部资源,防止写入到输出文件,之后自定义代码注入到用户脚本中.
|
|
231
|
+
* @param code
|
|
232
|
+
* @param id
|
|
233
|
+
*/
|
|
234
|
+
load(id) {
|
|
235
|
+
if (externalResolver.isResource(id) && !isDev) {
|
|
236
|
+
return {
|
|
237
|
+
code: "",
|
|
238
|
+
map: null
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
return null;
|
|
242
|
+
},
|
|
243
|
+
/**
|
|
244
|
+
* 生成打包
|
|
245
|
+
* @param _options 选项
|
|
246
|
+
* @param bundle 打包
|
|
247
|
+
*/
|
|
248
|
+
generateBundle(_, bundle) {
|
|
249
|
+
for (const key of Object.keys(bundle)) {
|
|
250
|
+
const file = bundle[key];
|
|
251
|
+
if (file.type === "asset" && file.fileName.endsWith(".css")) {
|
|
252
|
+
builder.addCss(file.source);
|
|
253
|
+
delete bundle[key];
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
for (const key of Object.keys(bundle)) {
|
|
257
|
+
const file = bundle[key];
|
|
258
|
+
if (file.type !== "chunk" || !file.isEntry) continue;
|
|
259
|
+
builder.addCode(file.code);
|
|
260
|
+
if (isDev) {
|
|
261
|
+
const fileUrl = pathToFileURL(resolve(outDirAbs, fileName)).href;
|
|
262
|
+
const header = builder.buildHeader(isDev, fileUrl);
|
|
263
|
+
this.emitFile({
|
|
264
|
+
type: "asset",
|
|
265
|
+
fileName: `user.dev.js`,
|
|
266
|
+
source: header
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
file.code = builder.build(isDev);
|
|
270
|
+
this.emitFile({
|
|
271
|
+
type: "asset",
|
|
272
|
+
fileName: `meta.json`,
|
|
273
|
+
source: JSON.stringify(meta, null, 2)
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
};
|
|
124
278
|
}
|
|
279
|
+
export {
|
|
280
|
+
tampermonkeyPlugin
|
|
281
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vite-tampermonkey",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"description": "Vite plugin for Tampermonkey / userscript builds",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -13,26 +13,21 @@
|
|
|
13
13
|
}
|
|
14
14
|
},
|
|
15
15
|
"files": [
|
|
16
|
-
"dist"
|
|
16
|
+
"dist",
|
|
17
|
+
"LICENSE"
|
|
17
18
|
],
|
|
18
19
|
"scripts": {
|
|
19
|
-
"build": "
|
|
20
|
-
"prepublishOnly": "npm run build"
|
|
21
|
-
"test": "echo \"Error: no test specified\" && exit 1"
|
|
20
|
+
"build": "tsup src/index.ts --format esm,cjs --dts --clean",
|
|
21
|
+
"prepublishOnly": "npm run build"
|
|
22
22
|
},
|
|
23
|
-
"keywords": [
|
|
24
|
-
"vite",
|
|
25
|
-
"vite-plugin",
|
|
26
|
-
"tampermonkey",
|
|
27
|
-
"userscript"
|
|
28
|
-
],
|
|
29
|
-
"author": "koyori",
|
|
30
|
-
"license": "MIT",
|
|
31
23
|
"peerDependencies": {
|
|
32
24
|
"vite": "^8.0.0"
|
|
33
25
|
},
|
|
26
|
+
"license": "MIT",
|
|
27
|
+
"author": "koyori",
|
|
34
28
|
"devDependencies": {
|
|
35
29
|
"@types/node": "^22.10.0",
|
|
30
|
+
"tsup": "^8.5.1",
|
|
36
31
|
"typescript": "^5.7.0",
|
|
37
32
|
"vite": "^8.0.0"
|
|
38
33
|
}
|
package/dist/builder.d.ts
DELETED
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
import type { ExternalResource, TampermonkeyMeta } from './types';
|
|
2
|
-
/**
|
|
3
|
-
* 元数据构造器
|
|
4
|
-
*/
|
|
5
|
-
export declare class TampermonkeyMetaBuilder {
|
|
6
|
-
private lines;
|
|
7
|
-
add(key: string, value?: string | string[] | boolean): this;
|
|
8
|
-
addRecord(record?: Record<string, string | string[] | true>): this;
|
|
9
|
-
private push;
|
|
10
|
-
build(): string;
|
|
11
|
-
}
|
|
12
|
-
export declare class TampermonkeyBuilder {
|
|
13
|
-
private cssCode;
|
|
14
|
-
private jsCode;
|
|
15
|
-
private meta;
|
|
16
|
-
private external?;
|
|
17
|
-
private externalResources;
|
|
18
|
-
constructor(meta: TampermonkeyMeta, external?: ExternalResource);
|
|
19
|
-
/**
|
|
20
|
-
* 添加CSS代码
|
|
21
|
-
* @param source 源码
|
|
22
|
-
*/
|
|
23
|
-
addCss(source: any): void;
|
|
24
|
-
/**
|
|
25
|
-
* 添加JS代码
|
|
26
|
-
* @param code 代码
|
|
27
|
-
*/
|
|
28
|
-
addCode(code: string): void;
|
|
29
|
-
private getGrant;
|
|
30
|
-
private getExternalResources;
|
|
31
|
-
buildHeader(isDev?: boolean, filename?: string): string;
|
|
32
|
-
build(isDev?: boolean): string;
|
|
33
|
-
}
|
|
34
|
-
//# sourceMappingURL=builder.d.ts.map
|
package/dist/builder.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"builder.d.ts","sourceRoot":"","sources":["../src/builder.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAClE;;GAEG;AACH,qBAAa,uBAAuB;IAClC,OAAO,CAAC,KAAK,CAAmC;IAEhD,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,OAAO;IAqBpD,SAAS,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,IAAI,CAAC;IAgB3D,OAAO,CAAC,IAAI;IAKZ,KAAK;CAIN;AAOD,qBAAa,mBAAmB;IAC9B,OAAO,CAAC,OAAO,CAAc;IAC7B,OAAO,CAAC,MAAM,CAAc;IAC5B,OAAO,CAAC,IAAI,CAAwB;IACpC,OAAO,CAAC,QAAQ,CAAC,CAAmB;IACpC,OAAO,CAAC,iBAAiB,CAA2B;gBAExC,IAAI,EAAE,gBAAgB,EAAE,QAAQ,CAAC,EAAE,gBAAgB;IAM/D;;;OAGG;IACH,MAAM,CAAC,MAAM,EAAE,GAAG;IAOlB;;;OAGG;IACH,OAAO,CAAC,IAAI,EAAE,MAAM;IAIpB,OAAO,CAAC,QAAQ;IAQhB,OAAO,CAAC,oBAAoB;IAkB5B,WAAW,CAAC,KAAK,GAAE,OAAe,EAAE,QAAQ,CAAC,EAAE,MAAM;IAgDrD,KAAK,CAAC,KAAK,GAAE,OAAe;CAkB7B"}
|
package/dist/builder.js
DELETED
|
@@ -1,156 +0,0 @@
|
|
|
1
|
-
import crypto from 'crypto';
|
|
2
|
-
import fs from 'fs';
|
|
3
|
-
/**
|
|
4
|
-
* 元数据构造器
|
|
5
|
-
*/
|
|
6
|
-
export class TampermonkeyMetaBuilder {
|
|
7
|
-
lines = ['// ==UserScript=='];
|
|
8
|
-
add(key, value) {
|
|
9
|
-
if (value == null)
|
|
10
|
-
return this;
|
|
11
|
-
if (key === 'icon') {
|
|
12
|
-
// 判断是否是本地文件
|
|
13
|
-
if (typeof value === 'string' && !value.startsWith('http')) {
|
|
14
|
-
const iconBase64 = fs.readFileSync(value, 'base64');
|
|
15
|
-
value = `data:image/png;base64,${iconBase64}`;
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
if (Array.isArray(value)) {
|
|
19
|
-
const set = new Set(value);
|
|
20
|
-
set.forEach((v) => this.push(key, v));
|
|
21
|
-
}
|
|
22
|
-
else {
|
|
23
|
-
this.push(key, value === true ? '' : String(value));
|
|
24
|
-
}
|
|
25
|
-
return this;
|
|
26
|
-
}
|
|
27
|
-
addRecord(record) {
|
|
28
|
-
if (!record)
|
|
29
|
-
return this;
|
|
30
|
-
for (const [k, v] of Object.entries(record)) {
|
|
31
|
-
const key = k.replace(/([A-Z])/g, '-$1').toLowerCase();
|
|
32
|
-
if (Array.isArray(v)) {
|
|
33
|
-
v.forEach((val) => this.push(key, String(val)));
|
|
34
|
-
}
|
|
35
|
-
else {
|
|
36
|
-
this.push(key, v === true ? '' : String(v));
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
return this;
|
|
40
|
-
}
|
|
41
|
-
push(key, value) {
|
|
42
|
-
const spacing = ' '.repeat(Math.max(1, 12 - key.length));
|
|
43
|
-
this.lines.push(`// @${key}${spacing}${value}`.trimEnd());
|
|
44
|
-
}
|
|
45
|
-
build() {
|
|
46
|
-
return [...this.lines, '// ==/UserScript==', ''].join('\n');
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
export class TampermonkeyBuilder {
|
|
50
|
-
cssCode = '';
|
|
51
|
-
jsCode = '';
|
|
52
|
-
meta = {};
|
|
53
|
-
external;
|
|
54
|
-
externalResources = [];
|
|
55
|
-
constructor(meta, external) {
|
|
56
|
-
this.meta = meta;
|
|
57
|
-
this.external = external;
|
|
58
|
-
this.externalResources = this.getExternalResources();
|
|
59
|
-
this.addCode('var global = window;\n');
|
|
60
|
-
}
|
|
61
|
-
/**
|
|
62
|
-
* 添加CSS代码
|
|
63
|
-
* @param source 源码
|
|
64
|
-
*/
|
|
65
|
-
addCss(source) {
|
|
66
|
-
this.cssCode +=
|
|
67
|
-
typeof source === 'string'
|
|
68
|
-
? source
|
|
69
|
-
: Buffer.from(source).toString('utf8');
|
|
70
|
-
}
|
|
71
|
-
/**
|
|
72
|
-
* 添加JS代码
|
|
73
|
-
* @param code 代码
|
|
74
|
-
*/
|
|
75
|
-
addCode(code) {
|
|
76
|
-
this.jsCode += code;
|
|
77
|
-
}
|
|
78
|
-
getGrant() {
|
|
79
|
-
let grant = this.meta.grant ?? [];
|
|
80
|
-
if (typeof grant === 'string') {
|
|
81
|
-
grant = [grant];
|
|
82
|
-
}
|
|
83
|
-
return grant;
|
|
84
|
-
}
|
|
85
|
-
getExternalResources() {
|
|
86
|
-
const externalResources = Array.from(new Set(Object.values(this.external?.resources ?? {})));
|
|
87
|
-
return externalResources.map((resource) => {
|
|
88
|
-
// md5(resource)
|
|
89
|
-
const md5 = crypto
|
|
90
|
-
.createHash('md5')
|
|
91
|
-
.update(resource)
|
|
92
|
-
.digest('hex')
|
|
93
|
-
.substring(0, 8);
|
|
94
|
-
return {
|
|
95
|
-
name: md5,
|
|
96
|
-
url: resource,
|
|
97
|
-
};
|
|
98
|
-
});
|
|
99
|
-
}
|
|
100
|
-
buildHeader(isDev = false, filename) {
|
|
101
|
-
const name = isDev ? `${this.meta.name} (Dev)` : this.meta.name;
|
|
102
|
-
const grant = this.getGrant();
|
|
103
|
-
if (this.cssCode.trim().length > 0) {
|
|
104
|
-
grant.push('GM_addStyle');
|
|
105
|
-
}
|
|
106
|
-
if (this.externalResources.length > 0 && !isDev) {
|
|
107
|
-
grant.push('GM_getResourceText');
|
|
108
|
-
}
|
|
109
|
-
const requires = [
|
|
110
|
-
...(this.meta.require ?? []),
|
|
111
|
-
...(isDev ? [] : Object.values(this.external?.modules ?? {})),
|
|
112
|
-
...(filename && isDev ? [filename] : []),
|
|
113
|
-
];
|
|
114
|
-
// 处理外部资源
|
|
115
|
-
const resources = [
|
|
116
|
-
...(this.meta.resource ?? []),
|
|
117
|
-
...(isDev && this.externalResources.length > 0
|
|
118
|
-
? []
|
|
119
|
-
: this.externalResources.map((resource) => `${resource.name} ${resource.url}`)),
|
|
120
|
-
];
|
|
121
|
-
return new TampermonkeyMetaBuilder()
|
|
122
|
-
.add('name', name)
|
|
123
|
-
.add('namespace', this.meta.namespace)
|
|
124
|
-
.add('version', this.meta.version)
|
|
125
|
-
.add('description', this.meta.description)
|
|
126
|
-
.add('author', this.meta.author)
|
|
127
|
-
.add('match', this.meta.match)
|
|
128
|
-
.add('include', this.meta.include)
|
|
129
|
-
.add('grant', this.meta.grant)
|
|
130
|
-
.add('run-at', this.meta.runAt)
|
|
131
|
-
.add('icon', this.meta.icon)
|
|
132
|
-
.add('require', requires)
|
|
133
|
-
.add('resource', resources)
|
|
134
|
-
.addRecord(this.meta.extra)
|
|
135
|
-
.add('license', this.meta.license)
|
|
136
|
-
.add('downloadUrl', this.meta.downloadUrl)
|
|
137
|
-
.add('updateUrl', this.meta.updateUrl)
|
|
138
|
-
.build();
|
|
139
|
-
}
|
|
140
|
-
build(isDev = false) {
|
|
141
|
-
const header = !isDev ? this.buildHeader(isDev) : '';
|
|
142
|
-
if (!isDev) {
|
|
143
|
-
this.externalResources.forEach((resource) => {
|
|
144
|
-
if (resource.url.endsWith('.css')) {
|
|
145
|
-
this.jsCode =
|
|
146
|
-
'GM_addStyle(GM_getResourceText("' +
|
|
147
|
-
resource.name +
|
|
148
|
-
'"));\n\n' +
|
|
149
|
-
this.jsCode;
|
|
150
|
-
}
|
|
151
|
-
});
|
|
152
|
-
}
|
|
153
|
-
const code = `${header}\nGM_addStyle(${JSON.stringify(this.cssCode)});\n${this.jsCode}`;
|
|
154
|
-
return code;
|
|
155
|
-
}
|
|
156
|
-
}
|
package/dist/index.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,MAAM,CAAC;AAGnC,OAAO,KAAK,EAAoB,yBAAyB,EAAE,MAAM,SAAS,CAAC;AAU3E;;GAEG;AACH,wBAAgB,kBAAkB,CAChC,OAAO,GAAE,yBAA8B,GACtC,MAAM,CA8HR"}
|
package/dist/resovler.d.ts
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import type { ExternalResource, PackageJson, TampermonkeyMeta } from './types';
|
|
2
|
-
/**
|
|
3
|
-
* 解析元数据
|
|
4
|
-
* @param pkg package.json
|
|
5
|
-
* @param userMeta 用户元数据
|
|
6
|
-
* @returns 元数据
|
|
7
|
-
*/
|
|
8
|
-
export declare function resolveMeta(pkg: PackageJson, userMeta: TampermonkeyMeta): TampermonkeyMeta;
|
|
9
|
-
/**
|
|
10
|
-
* 创建外部资源解析器
|
|
11
|
-
* @param externals 外部资源
|
|
12
|
-
* @param globals 全局变量
|
|
13
|
-
* @returns 外部资源解析器
|
|
14
|
-
*/
|
|
15
|
-
export declare function createExternalResolver(external?: ExternalResource): {
|
|
16
|
-
isModule(id: string): boolean;
|
|
17
|
-
getGlobal(id: string): string;
|
|
18
|
-
isResource(id: string): boolean;
|
|
19
|
-
};
|
|
20
|
-
//# sourceMappingURL=resovler.d.ts.map
|
package/dist/resovler.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"resovler.d.ts","sourceRoot":"","sources":["../src/resovler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAC/E;;;;;GAKG;AACH,wBAAgB,WAAW,CACzB,GAAG,EAAE,WAAW,EAChB,QAAQ,EAAE,gBAAgB,GACzB,gBAAgB,CAWlB;AAED;;;;;GAKG;AACH,wBAAgB,sBAAsB,CAAC,QAAQ,CAAC,EAAE,gBAAgB;iBAajD,MAAM;kBAIL,MAAM;mBAGL,MAAM;EAIxB"}
|
package/dist/resovler.js
DELETED
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 解析元数据
|
|
3
|
-
* @param pkg package.json
|
|
4
|
-
* @param userMeta 用户元数据
|
|
5
|
-
* @returns 元数据
|
|
6
|
-
*/
|
|
7
|
-
export function resolveMeta(pkg, userMeta) {
|
|
8
|
-
const grants = new Set(userMeta.grant ?? []);
|
|
9
|
-
return {
|
|
10
|
-
name: pkg.name,
|
|
11
|
-
version: pkg.version,
|
|
12
|
-
description: pkg.description,
|
|
13
|
-
author: pkg.author,
|
|
14
|
-
...userMeta,
|
|
15
|
-
grant: Array.from(grants),
|
|
16
|
-
};
|
|
17
|
-
}
|
|
18
|
-
/**
|
|
19
|
-
* 创建外部资源解析器
|
|
20
|
-
* @param externals 外部资源
|
|
21
|
-
* @param globals 全局变量
|
|
22
|
-
* @returns 外部资源解析器
|
|
23
|
-
*/
|
|
24
|
-
export function createExternalResolver(external) {
|
|
25
|
-
if (!external)
|
|
26
|
-
return {
|
|
27
|
-
isModule: () => false,
|
|
28
|
-
getGlobal: () => '',
|
|
29
|
-
isResource: () => false,
|
|
30
|
-
};
|
|
31
|
-
const modules = Object.keys(external.modules ?? {});
|
|
32
|
-
const globals = external.globals ?? {};
|
|
33
|
-
const resources = Object.keys(external.resources ?? {});
|
|
34
|
-
return {
|
|
35
|
-
isModule(id) {
|
|
36
|
-
return modules.some((name) => id === name || id.startsWith(`${name}/`));
|
|
37
|
-
},
|
|
38
|
-
getGlobal(id) {
|
|
39
|
-
return globals[id] ?? id.replace(/[^\w$]/g, '_');
|
|
40
|
-
},
|
|
41
|
-
isResource(id) {
|
|
42
|
-
return resources.some((name) => id.endsWith(name));
|
|
43
|
-
},
|
|
44
|
-
};
|
|
45
|
-
}
|
package/dist/types.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,MAAM,gBAAgB,GAAG;IAC7B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,mBAAmB;IACnB,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IAC1B,qBAAqB;IACrB,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IAC5B,mBAAmB;IACnB,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IAC1B,KAAK,CAAC,EAAE,gBAAgB,GAAG,cAAc,GAAG,eAAe,CAAC;IAC5D,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IAC5B,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IAC5B,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IAC5B,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IAC7B,0DAA0D;IAC1D,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,IAAI,CAAC,CAAC;IACjD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,gBAAgB,GAAG;IAC7B,6CAA6C;IAC7C,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACnC,4CAA4C;IAC5C,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,eAAe;IACf,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAClC,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,yBAAyB,GAAG;IACtC,8CAA8C;IAC9C,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,+BAA+B;IAC/B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,+CAA+C;IAC/C,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,6BAA6B;IAC7B,IAAI,CAAC,EAAE,gBAAgB,CAAC;IACxB,WAAW;IACX,QAAQ,CAAC,EAAE,gBAAgB,CAAC;CAC7B,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,WAAW,GAAG;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,CAAC"}
|
package/dist/types.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|