@zzzzzzhaopu/vite-plugin-upload-sourcemap 1.0.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/LICENSE +21 -0
- package/README.md +392 -0
- package/dist/index.cjs +148 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +52 -0
- package/dist/index.d.ts +52 -0
- package/dist/index.js +138 -0
- package/dist/index.js.map +1 -0
- package/package.json +62 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 [Your Name]
|
|
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,392 @@
|
|
|
1
|
+
# @zzzzzzhaopu/vite-plugin-upload-sourcemap
|
|
2
|
+
|
|
3
|
+
> 🚀 A Vite plugin that automatically uploads SourceMap files to monitoring platforms after production build.
|
|
4
|
+
|
|
5
|
+
[English](#english) | [中文](#中文)
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## English
|
|
10
|
+
|
|
11
|
+
### Features
|
|
12
|
+
|
|
13
|
+
- ✨ **Automatic Upload**: Uploads SourceMap files after build completion
|
|
14
|
+
- 🗑️ **Auto Cleanup**: Removes .map files after upload (configurable)
|
|
15
|
+
- 🔌 **Easy Integration**: Simple Vite plugin configuration
|
|
16
|
+
- 🛠️ **Flexible**: Supports custom upload functions for different platforms
|
|
17
|
+
- 🎯 **Type Safe**: Full TypeScript support
|
|
18
|
+
- 📦 **Zero Config**: Works out of the box with sensible defaults
|
|
19
|
+
|
|
20
|
+
### Why Do You Need This?
|
|
21
|
+
|
|
22
|
+
**The Problem:**
|
|
23
|
+
1. Production code is minified and obfuscated for better performance
|
|
24
|
+
2. Error stack traces show minified code positions, making debugging difficult
|
|
25
|
+
3. SourceMaps can't be deployed to production (security risk - exposes source code)
|
|
26
|
+
|
|
27
|
+
**The Solution:**
|
|
28
|
+
1. Generate SourceMaps during build
|
|
29
|
+
2. Upload them to your monitoring platform (Sentry, Datadog, etc.)
|
|
30
|
+
3. Remove them from deployment bundle
|
|
31
|
+
4. Monitor platform uses them to restore readable stack traces
|
|
32
|
+
|
|
33
|
+
### Installation
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
npm install @zzzzzzhaopu/vite-plugin-upload-sourcemap -D
|
|
37
|
+
# 或
|
|
38
|
+
pnpm add @zzzzzzhaopu/vite-plugin-upload-sourcemap -D
|
|
39
|
+
# 或
|
|
40
|
+
yarn add @zzzzzzhaopu/vite-plugin-upload-sourcemap -D
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### Quick Start
|
|
44
|
+
|
|
45
|
+
```typescript
|
|
46
|
+
// vite.config.ts
|
|
47
|
+
import { defineConfig } from 'vite'
|
|
48
|
+
import { uploadSourceMapPlugin } from 'vite-plugin-upload-sourcemap'
|
|
49
|
+
|
|
50
|
+
export default defineConfig(({ mode }) => ({
|
|
51
|
+
plugins: [
|
|
52
|
+
uploadSourceMapPlugin({
|
|
53
|
+
enabled: mode === 'production',
|
|
54
|
+
uploadUrl: 'https://your-platform.com/api/sourcemap',
|
|
55
|
+
apiKey: process.env.SOURCEMAP_API_KEY,
|
|
56
|
+
projectName: 'my-project',
|
|
57
|
+
version: '1.0.0'
|
|
58
|
+
})
|
|
59
|
+
],
|
|
60
|
+
build: {
|
|
61
|
+
sourcemap: true // Enable sourcemap generation
|
|
62
|
+
}
|
|
63
|
+
}))
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### Configuration
|
|
67
|
+
|
|
68
|
+
```typescript
|
|
69
|
+
interface SourceMapUploadOptions {
|
|
70
|
+
/** Enable the plugin (default: production only) */
|
|
71
|
+
enabled?: boolean
|
|
72
|
+
|
|
73
|
+
/** Monitoring platform API URL */
|
|
74
|
+
uploadUrl?: string
|
|
75
|
+
|
|
76
|
+
/** API key for authentication */
|
|
77
|
+
apiKey?: string
|
|
78
|
+
|
|
79
|
+
/** Project name */
|
|
80
|
+
projectName?: string
|
|
81
|
+
|
|
82
|
+
/** Project version */
|
|
83
|
+
version?: string
|
|
84
|
+
|
|
85
|
+
/** Remove SourceMap files after upload (default: true) */
|
|
86
|
+
removeSourceMap?: boolean
|
|
87
|
+
|
|
88
|
+
/** Custom upload function (optional) */
|
|
89
|
+
uploadFn?: (
|
|
90
|
+
filePath: string,
|
|
91
|
+
options: {
|
|
92
|
+
uploadUrl: string
|
|
93
|
+
apiKey: string
|
|
94
|
+
projectName: string
|
|
95
|
+
version: string
|
|
96
|
+
}
|
|
97
|
+
) => Promise<boolean>
|
|
98
|
+
}
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### Platform Integration Examples
|
|
102
|
+
|
|
103
|
+
#### Sentry
|
|
104
|
+
|
|
105
|
+
```typescript
|
|
106
|
+
import { uploadSourceMapPlugin } from 'vite-plugin-upload-sourcemap'
|
|
107
|
+
|
|
108
|
+
uploadSourceMapPlugin({
|
|
109
|
+
enabled: mode === 'production',
|
|
110
|
+
uploadFn: async (filePath, config) => {
|
|
111
|
+
const fs = await import('fs')
|
|
112
|
+
const path = await import('path')
|
|
113
|
+
|
|
114
|
+
const formData = new FormData()
|
|
115
|
+
const content = fs.readFileSync(filePath, 'utf-8')
|
|
116
|
+
|
|
117
|
+
formData.append('file', new Blob([content]), path.basename(filePath))
|
|
118
|
+
formData.append('name', path.basename(filePath))
|
|
119
|
+
|
|
120
|
+
const response = await fetch(
|
|
121
|
+
`https://sentry.io/api/0/projects/${config.projectName}/releases/${config.version}/files/`,
|
|
122
|
+
{
|
|
123
|
+
method: 'POST',
|
|
124
|
+
headers: {
|
|
125
|
+
'Authorization': `Bearer ${config.apiKey}`
|
|
126
|
+
},
|
|
127
|
+
body: formData
|
|
128
|
+
}
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
return response.ok
|
|
132
|
+
}
|
|
133
|
+
})
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
#### Custom Platform
|
|
137
|
+
|
|
138
|
+
```typescript
|
|
139
|
+
uploadSourceMapPlugin({
|
|
140
|
+
uploadFn: async (filePath, config) => {
|
|
141
|
+
// Your custom upload logic here
|
|
142
|
+
const content = fs.readFileSync(filePath, 'utf-8')
|
|
143
|
+
|
|
144
|
+
const response = await fetch(config.uploadUrl, {
|
|
145
|
+
method: 'POST',
|
|
146
|
+
headers: {
|
|
147
|
+
'Content-Type': 'application/json',
|
|
148
|
+
'X-API-KEY': config.apiKey
|
|
149
|
+
},
|
|
150
|
+
body: JSON.stringify({
|
|
151
|
+
project: config.projectName,
|
|
152
|
+
version: config.version,
|
|
153
|
+
filename: path.basename(filePath),
|
|
154
|
+
content
|
|
155
|
+
})
|
|
156
|
+
})
|
|
157
|
+
|
|
158
|
+
return response.ok
|
|
159
|
+
}
|
|
160
|
+
})
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
### Environment Variables
|
|
164
|
+
|
|
165
|
+
You can use environment variables for configuration:
|
|
166
|
+
|
|
167
|
+
```bash
|
|
168
|
+
# .env.production
|
|
169
|
+
VITE_SOURCEMAP_UPLOAD_URL=https://your-platform.com/api/sourcemap
|
|
170
|
+
VITE_SOURCEMAP_API_KEY=your-secret-api-key
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
```typescript
|
|
174
|
+
// vite.config.ts
|
|
175
|
+
uploadSourceMapPlugin({
|
|
176
|
+
uploadUrl: process.env.VITE_SOURCEMAP_UPLOAD_URL,
|
|
177
|
+
apiKey: process.env.VITE_SOURCEMAP_API_KEY
|
|
178
|
+
})
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
### How It Works
|
|
182
|
+
|
|
183
|
+
```
|
|
184
|
+
Build Process
|
|
185
|
+
│
|
|
186
|
+
├─ Vite bundles your code
|
|
187
|
+
├─ Generate minified JS files
|
|
188
|
+
├─ Generate .map files
|
|
189
|
+
│
|
|
190
|
+
└─ Plugin executes (closeBundle hook)
|
|
191
|
+
├─ Find all .map files
|
|
192
|
+
├─ Upload to monitoring platform
|
|
193
|
+
└─ Remove .map files (optional)
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
### License
|
|
197
|
+
|
|
198
|
+
MIT
|
|
199
|
+
|
|
200
|
+
---
|
|
201
|
+
|
|
202
|
+
## 中文
|
|
203
|
+
|
|
204
|
+
### 特性
|
|
205
|
+
|
|
206
|
+
- ✨ **自动上传**:构建完成后自动上传 SourceMap 文件
|
|
207
|
+
- 🗑️ **自动清理**:上传后自动删除 .map 文件(可配置)
|
|
208
|
+
- 🔌 **轻松集成**:简单的 Vite 插件配置
|
|
209
|
+
- 🛠️ **灵活可扩展**:支持自定义上传函数以对接不同平台
|
|
210
|
+
- 🎯 **类型安全**:完整的 TypeScript 支持
|
|
211
|
+
- 📦 **零配置**:开箱即用的合理默认配置
|
|
212
|
+
|
|
213
|
+
### 为什么需要这个插件?
|
|
214
|
+
|
|
215
|
+
**问题背景:**
|
|
216
|
+
1. 生产环境代码会被压缩和混淆以提升性能
|
|
217
|
+
2. 错误堆栈显示的是压缩后的代码位置,难以调试
|
|
218
|
+
3. SourceMap 不能部署到生产环境(会泄露源码)
|
|
219
|
+
|
|
220
|
+
**解决方案:**
|
|
221
|
+
1. 构建时生成 SourceMap
|
|
222
|
+
2. 上传到监控平台(Sentry、阿里云 ARMS 等)
|
|
223
|
+
3. 从部署包中删除
|
|
224
|
+
4. 监控平台使用 SourceMap 还原可读的错误堆栈
|
|
225
|
+
|
|
226
|
+
### 安装
|
|
227
|
+
|
|
228
|
+
```bash
|
|
229
|
+
npm install @zzzzzzhaopu/vite-plugin-upload-sourcemap -D
|
|
230
|
+
# 或
|
|
231
|
+
pnpm add @zzzzzzhaopu/vite-plugin-upload-sourcemap -D
|
|
232
|
+
# 或
|
|
233
|
+
yarn add @zzzzzzhaopu/vite-plugin-upload-sourcemap -D
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
### 快速开始
|
|
237
|
+
|
|
238
|
+
```typescript
|
|
239
|
+
// vite.config.ts
|
|
240
|
+
import { defineConfig } from 'vite'
|
|
241
|
+
import { uploadSourceMapPlugin } from 'vite-plugin-upload-sourcemap'
|
|
242
|
+
|
|
243
|
+
export default defineConfig(({ mode }) => ({
|
|
244
|
+
plugins: [
|
|
245
|
+
uploadSourceMapPlugin({
|
|
246
|
+
enabled: mode === 'production',
|
|
247
|
+
uploadUrl: 'https://your-platform.com/api/sourcemap',
|
|
248
|
+
apiKey: process.env.SOURCEMAP_API_KEY,
|
|
249
|
+
projectName: 'my-project',
|
|
250
|
+
version: '1.0.0'
|
|
251
|
+
})
|
|
252
|
+
],
|
|
253
|
+
build: {
|
|
254
|
+
sourcemap: true // 启用 sourcemap 生成
|
|
255
|
+
}
|
|
256
|
+
}))
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
### 配置选项
|
|
260
|
+
|
|
261
|
+
```typescript
|
|
262
|
+
interface SourceMapUploadOptions {
|
|
263
|
+
/** 是否启用插件(默认:仅生产环境) */
|
|
264
|
+
enabled?: boolean
|
|
265
|
+
|
|
266
|
+
/** 监控平台 API 地址 */
|
|
267
|
+
uploadUrl?: string
|
|
268
|
+
|
|
269
|
+
/** API 密钥 */
|
|
270
|
+
apiKey?: string
|
|
271
|
+
|
|
272
|
+
/** 项目名称 */
|
|
273
|
+
projectName?: string
|
|
274
|
+
|
|
275
|
+
/** 项目版本 */
|
|
276
|
+
version?: string
|
|
277
|
+
|
|
278
|
+
/** 上传后删除 SourceMap 文件(默认:true) */
|
|
279
|
+
removeSourceMap?: boolean
|
|
280
|
+
|
|
281
|
+
/** 自定义上传函数(可选) */
|
|
282
|
+
uploadFn?: (
|
|
283
|
+
filePath: string,
|
|
284
|
+
options: {
|
|
285
|
+
uploadUrl: string
|
|
286
|
+
apiKey: string
|
|
287
|
+
projectName: string
|
|
288
|
+
version: string
|
|
289
|
+
}
|
|
290
|
+
) => Promise<boolean>
|
|
291
|
+
}
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
### 平台集成示例
|
|
295
|
+
|
|
296
|
+
#### Sentry
|
|
297
|
+
|
|
298
|
+
```typescript
|
|
299
|
+
import { uploadSourceMapPlugin } from 'vite-plugin-upload-sourcemap'
|
|
300
|
+
|
|
301
|
+
uploadSourceMapPlugin({
|
|
302
|
+
enabled: mode === 'production',
|
|
303
|
+
uploadFn: async (filePath, config) => {
|
|
304
|
+
const fs = await import('fs')
|
|
305
|
+
const path = await import('path')
|
|
306
|
+
|
|
307
|
+
const formData = new FormData()
|
|
308
|
+
const content = fs.readFileSync(filePath, 'utf-8')
|
|
309
|
+
|
|
310
|
+
formData.append('file', new Blob([content]), path.basename(filePath))
|
|
311
|
+
formData.append('name', path.basename(filePath))
|
|
312
|
+
|
|
313
|
+
const response = await fetch(
|
|
314
|
+
`https://sentry.io/api/0/projects/${config.projectName}/releases/${config.version}/files/`,
|
|
315
|
+
{
|
|
316
|
+
method: 'POST',
|
|
317
|
+
headers: {
|
|
318
|
+
'Authorization': `Bearer ${config.apiKey}`
|
|
319
|
+
},
|
|
320
|
+
body: formData
|
|
321
|
+
}
|
|
322
|
+
)
|
|
323
|
+
|
|
324
|
+
return response.ok
|
|
325
|
+
}
|
|
326
|
+
})
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
#### 阿里云 ARMS
|
|
330
|
+
|
|
331
|
+
```typescript
|
|
332
|
+
uploadSourceMapPlugin({
|
|
333
|
+
uploadFn: async (filePath, config) => {
|
|
334
|
+
const fs = await import('fs')
|
|
335
|
+
const path = await import('path')
|
|
336
|
+
const content = fs.readFileSync(filePath, 'utf-8')
|
|
337
|
+
|
|
338
|
+
const response = await fetch(config.uploadUrl, {
|
|
339
|
+
method: 'POST',
|
|
340
|
+
headers: {
|
|
341
|
+
'Content-Type': 'application/json',
|
|
342
|
+
'X-ARMS-API-KEY': config.apiKey
|
|
343
|
+
},
|
|
344
|
+
body: JSON.stringify({
|
|
345
|
+
project: config.projectName,
|
|
346
|
+
version: config.version,
|
|
347
|
+
filename: path.basename(filePath),
|
|
348
|
+
content
|
|
349
|
+
})
|
|
350
|
+
})
|
|
351
|
+
|
|
352
|
+
return response.ok
|
|
353
|
+
}
|
|
354
|
+
})
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
### 环境变量配置
|
|
358
|
+
|
|
359
|
+
可以使用环境变量来配置:
|
|
360
|
+
|
|
361
|
+
```bash
|
|
362
|
+
# .env.production
|
|
363
|
+
VITE_SOURCEMAP_UPLOAD_URL=https://your-platform.com/api/sourcemap
|
|
364
|
+
VITE_SOURCEMAP_API_KEY=your-secret-api-key
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
```typescript
|
|
368
|
+
// vite.config.ts
|
|
369
|
+
uploadSourceMapPlugin({
|
|
370
|
+
uploadUrl: process.env.VITE_SOURCEMAP_UPLOAD_URL,
|
|
371
|
+
apiKey: process.env.VITE_SOURCEMAP_API_KEY
|
|
372
|
+
})
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
### 工作原理
|
|
376
|
+
|
|
377
|
+
```
|
|
378
|
+
构建流程
|
|
379
|
+
│
|
|
380
|
+
├─ Vite 打包代码
|
|
381
|
+
├─ 生成压缩后的 JS 文件
|
|
382
|
+
├─ 生成 .map 文件
|
|
383
|
+
│
|
|
384
|
+
└─ 插件执行 (closeBundle 钩子)
|
|
385
|
+
├─ 查找所有 .map 文件
|
|
386
|
+
├─ 上传到监控平台
|
|
387
|
+
└─ 删除 .map 文件(可选)
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
### 许可证
|
|
391
|
+
|
|
392
|
+
MIT
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
|
+
|
|
5
|
+
var fs = require('fs');
|
|
6
|
+
var path = require('path');
|
|
7
|
+
|
|
8
|
+
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
9
|
+
|
|
10
|
+
var fs__default = /*#__PURE__*/_interopDefault(fs);
|
|
11
|
+
var path__default = /*#__PURE__*/_interopDefault(path);
|
|
12
|
+
|
|
13
|
+
// src/index.ts
|
|
14
|
+
function uploadSourceMapPlugin(options = {}) {
|
|
15
|
+
const config = {
|
|
16
|
+
enabled: options.enabled ?? process.env.NODE_ENV === "production",
|
|
17
|
+
uploadUrl: options.uploadUrl || process.env.VITE_SOURCEMAP_UPLOAD_URL || "",
|
|
18
|
+
apiKey: options.apiKey || process.env.VITE_SOURCEMAP_API_KEY || "",
|
|
19
|
+
projectName: options.projectName || process.env.npm_package_name || "unknown-project",
|
|
20
|
+
version: options.version || process.env.npm_package_version || "1.0.0",
|
|
21
|
+
removeSourceMap: options.removeSourceMap ?? true,
|
|
22
|
+
uploadFn: options.uploadFn
|
|
23
|
+
};
|
|
24
|
+
let outDir = "dist";
|
|
25
|
+
const sourceMapFiles = [];
|
|
26
|
+
return {
|
|
27
|
+
// 插件名称
|
|
28
|
+
name: "vite-plugin-upload-sourcemap",
|
|
29
|
+
// 仅在构建时应用
|
|
30
|
+
apply: "build",
|
|
31
|
+
/**
|
|
32
|
+
* 在 Vite 配置解析完成后调用
|
|
33
|
+
* 用于获取构建配置信息(如输出目录)
|
|
34
|
+
*/
|
|
35
|
+
configResolved(resolvedConfig) {
|
|
36
|
+
outDir = resolvedConfig.build.outDir;
|
|
37
|
+
},
|
|
38
|
+
/**
|
|
39
|
+
* 在生成产物时调用(产物还在内存中,未写入磁盘)
|
|
40
|
+
* 这个钩子可以获取到所有生成的文件信息,包括 SourceMap 文件
|
|
41
|
+
*
|
|
42
|
+
* @description
|
|
43
|
+
* 在这个阶段我们可以知道哪些文件是 .map 文件,提前记录下来
|
|
44
|
+
* 避免后续在 closeBundle 中遍历文件系统查找
|
|
45
|
+
*/
|
|
46
|
+
generateBundle(_options, bundle) {
|
|
47
|
+
Object.keys(bundle).forEach((fileName) => {
|
|
48
|
+
if (fileName.endsWith(".map")) {
|
|
49
|
+
const fullPath = path__default.default.resolve(outDir, fileName);
|
|
50
|
+
sourceMapFiles.push(fullPath);
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
},
|
|
54
|
+
/**
|
|
55
|
+
* 在打包完成后调用(所有文件都已写入磁盘)
|
|
56
|
+
* 这是执行 SourceMap 上传的最佳时机
|
|
57
|
+
*/
|
|
58
|
+
async closeBundle() {
|
|
59
|
+
if (!config.enabled) {
|
|
60
|
+
console.log("\u23ED\uFE0F SourceMap \u4E0A\u4F20\u63D2\u4EF6\u5DF2\u7981\u7528");
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
if (!config.uploadUrl || !config.apiKey) {
|
|
64
|
+
console.warn("\u26A0\uFE0F SourceMap \u4E0A\u4F20\u914D\u7F6E\u4E0D\u5B8C\u6574\uFF0C\u8DF3\u8FC7\u4E0A\u4F20");
|
|
65
|
+
console.warn(" \u8BF7\u914D\u7F6E uploadUrl \u548C apiKey");
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
console.log("\n\u{1F680} \u5F00\u59CB\u5904\u7406 SourceMap \u6587\u4EF6...\n");
|
|
69
|
+
try {
|
|
70
|
+
if (sourceMapFiles.length === 0) {
|
|
71
|
+
console.log("\u26A0\uFE0F \u672A\u627E\u5230 SourceMap \u6587\u4EF6");
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
console.log(`\u2705 \u627E\u5230 ${sourceMapFiles.length} \u4E2A SourceMap \u6587\u4EF6
|
|
75
|
+
`);
|
|
76
|
+
console.log("\u{1F4E4} \u5F00\u59CB\u4E0A\u4F20 SourceMap...");
|
|
77
|
+
const uploadResults = await Promise.all(
|
|
78
|
+
sourceMapFiles.map((file) => {
|
|
79
|
+
if (config.uploadFn) {
|
|
80
|
+
return config.uploadFn(file, {
|
|
81
|
+
uploadUrl: config.uploadUrl,
|
|
82
|
+
apiKey: config.apiKey,
|
|
83
|
+
projectName: config.projectName,
|
|
84
|
+
version: config.version
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
return uploadSourceMap(file, {
|
|
88
|
+
uploadUrl: config.uploadUrl,
|
|
89
|
+
apiKey: config.apiKey,
|
|
90
|
+
projectName: config.projectName,
|
|
91
|
+
version: config.version
|
|
92
|
+
});
|
|
93
|
+
})
|
|
94
|
+
);
|
|
95
|
+
const successCount = uploadResults.filter(Boolean).length;
|
|
96
|
+
console.log(`
|
|
97
|
+
\u2705 \u4E0A\u4F20\u5B8C\u6210: ${successCount}/${sourceMapFiles.length} \u6210\u529F`);
|
|
98
|
+
if (config.removeSourceMap) {
|
|
99
|
+
console.log("\n\u{1F5D1}\uFE0F \u6B63\u5728\u5220\u9664 SourceMap \u6587\u4EF6...");
|
|
100
|
+
sourceMapFiles.forEach((file) => {
|
|
101
|
+
try {
|
|
102
|
+
fs__default.default.unlinkSync(file);
|
|
103
|
+
console.log(` \u2705 \u5DF2\u5220\u9664: ${path__default.default.basename(file)}`);
|
|
104
|
+
} catch (error) {
|
|
105
|
+
console.error(` \u274C \u5220\u9664\u5931\u8D25: ${path__default.default.basename(file)}`, error);
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
console.log("\n\u{1F389} SourceMap \u5904\u7406\u5B8C\u6210!");
|
|
110
|
+
} catch (error) {
|
|
111
|
+
console.error("\n\u274C SourceMap \u5904\u7406\u5931\u8D25:", error);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
async function uploadSourceMap(filePath, config) {
|
|
117
|
+
console.log(`\u{1F4E4} \u6B63\u5728\u4E0A\u4F20: ${path__default.default.basename(filePath)}`);
|
|
118
|
+
try {
|
|
119
|
+
const content = fs__default.default.readFileSync(filePath, "utf-8");
|
|
120
|
+
const response = await fetch(config.uploadUrl, {
|
|
121
|
+
method: "POST",
|
|
122
|
+
headers: {
|
|
123
|
+
"Content-Type": "application/json",
|
|
124
|
+
"Authorization": `Bearer ${config.apiKey}`
|
|
125
|
+
},
|
|
126
|
+
body: JSON.stringify({
|
|
127
|
+
project: config.projectName,
|
|
128
|
+
version: config.version,
|
|
129
|
+
filename: path__default.default.basename(filePath),
|
|
130
|
+
content
|
|
131
|
+
})
|
|
132
|
+
});
|
|
133
|
+
if (!response.ok) {
|
|
134
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
135
|
+
}
|
|
136
|
+
console.log(`\u2705 \u4E0A\u4F20\u6210\u529F: ${path__default.default.basename(filePath)}`);
|
|
137
|
+
return true;
|
|
138
|
+
} catch (error) {
|
|
139
|
+
console.error(`\u274C \u4E0A\u4F20\u5931\u8D25: ${path__default.default.basename(filePath)}`, error instanceof Error ? error.message : error);
|
|
140
|
+
return false;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
var index_default = uploadSourceMapPlugin;
|
|
144
|
+
|
|
145
|
+
exports.default = index_default;
|
|
146
|
+
exports.uploadSourceMapPlugin = uploadSourceMapPlugin;
|
|
147
|
+
//# sourceMappingURL=index.cjs.map
|
|
148
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"names":["path","fs"],"mappings":"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","file":"index.cjs","sourcesContent":["import fs from 'fs'\nimport path from 'path'\nimport type { Plugin } from 'vite'\n\n/**\n * SourceMap 上传插件配置项\n */\nexport interface SourceMapUploadOptions {\n /** 是否启用插件(默认仅在生产环境启用) */\n enabled?: boolean\n /** 监控平台 API 地址 */\n uploadUrl?: string\n /** API 密钥 */\n apiKey?: string\n /** 项目名称 */\n projectName?: string\n /** 项目版本 */\n version?: string\n /** 上传完成后是否删除 SourceMap 文件(默认 true) */\n removeSourceMap?: boolean\n /** 自定义上传函数(可选,用于对接特定监控平台) */\n uploadFn?: (filePath: string, options: Required<Pick<SourceMapUploadOptions, 'uploadUrl' | 'apiKey' | 'projectName' | 'version'>>) => Promise<boolean>\n}\n\n/**\n * Vite 插件:上传 SourceMap 到监控平台\n * \n * @description\n * 这个插件会在打包完成后自动执行以下操作:\n * 1. 查找 dist 目录下所有的 .map 文件\n * 2. 上传到指定的监控平台(如 Sentry、阿里云 ARMS 等)\n * 3. 上传完成后自动删除 .map 文件(可配置)\n * \n * @example\n * ```ts\n * // vite.config.ts\n * import { uploadSourceMapPlugin } from 'vite-plugin-upload-sourcemap'\n * \n * export default defineConfig({\n * plugins: [\n * uploadSourceMapPlugin({\n * enabled: mode === 'production',\n * uploadUrl: 'https://your-platform.com/api/sourcemap',\n * apiKey: process.env.SOURCEMAP_API_KEY,\n * projectName: 'my-project',\n * version: '1.0.0',\n * removeSourceMap: true\n * })\n * ]\n * })\n * ```\n */\nexport function uploadSourceMapPlugin(options: SourceMapUploadOptions = {}): Plugin {\n // 默认配置\n const config = {\n enabled: options.enabled ?? process.env.NODE_ENV === 'production',\n uploadUrl: options.uploadUrl || process.env.VITE_SOURCEMAP_UPLOAD_URL || '',\n apiKey: options.apiKey || process.env.VITE_SOURCEMAP_API_KEY || '',\n projectName: options.projectName || process.env.npm_package_name || 'unknown-project',\n version: options.version || process.env.npm_package_version || '1.0.0',\n removeSourceMap: options.removeSourceMap ?? true,\n uploadFn: options.uploadFn\n }\n\n // 用于存储输出目录路径\n let outDir = 'dist'\n \n // 用于存储在构建过程中生成的 SourceMap 文件路径\n // 在 generateBundle 钩子中收集,在 closeBundle 钩子中使用\n const sourceMapFiles: string[] = []\n\n return {\n // 插件名称\n name: 'vite-plugin-upload-sourcemap',\n \n // 仅在构建时应用\n apply: 'build',\n \n /**\n * 在 Vite 配置解析完成后调用\n * 用于获取构建配置信息(如输出目录)\n */\n configResolved(resolvedConfig) {\n // 获取实际的输出目录\n outDir = resolvedConfig.build.outDir\n },\n \n /**\n * 在生成产物时调用(产物还在内存中,未写入磁盘)\n * 这个钩子可以获取到所有生成的文件信息,包括 SourceMap 文件\n * \n * @description\n * 在这个阶段我们可以知道哪些文件是 .map 文件,提前记录下来\n * 避免后续在 closeBundle 中遍历文件系统查找\n */\n generateBundle(_options, bundle) {\n // 遍历所有生成的文件\n Object.keys(bundle).forEach(fileName => {\n // 如果是 .map 文件,记录其完整路径\n if (fileName.endsWith('.map')) {\n const fullPath = path.resolve(outDir, fileName)\n sourceMapFiles.push(fullPath)\n }\n })\n },\n \n /**\n * 在打包完成后调用(所有文件都已写入磁盘)\n * 这是执行 SourceMap 上传的最佳时机\n */\n async closeBundle() {\n // 如果插件未启用,直接返回\n if (!config.enabled) {\n console.log('⏭️ SourceMap 上传插件已禁用')\n return\n }\n\n // 验证必要的配置\n if (!config.uploadUrl || !config.apiKey) {\n console.warn('⚠️ SourceMap 上传配置不完整,跳过上传')\n console.warn(' 请配置 uploadUrl 和 apiKey')\n return\n }\n\n console.log('\\n🚀 开始处理 SourceMap 文件...\\n')\n\n try {\n // 1. 检查是否有 SourceMap 文件(已在 generateBundle 中收集)\n if (sourceMapFiles.length === 0) {\n console.log('⚠️ 未找到 SourceMap 文件')\n return\n }\n\n console.log(`✅ 找到 ${sourceMapFiles.length} 个 SourceMap 文件\\n`)\n\n // 2. 上传所有 SourceMap 文件\n console.log('📤 开始上传 SourceMap...')\n const uploadResults = await Promise.all(\n sourceMapFiles.map(file => {\n // 如果提供了自定义上传函数,使用自定义函数\n if (config.uploadFn) {\n return config.uploadFn(file, {\n uploadUrl: config.uploadUrl,\n apiKey: config.apiKey,\n projectName: config.projectName,\n version: config.version\n })\n }\n // 否则使用默认上传函数\n return uploadSourceMap(file, {\n uploadUrl: config.uploadUrl,\n apiKey: config.apiKey,\n projectName: config.projectName,\n version: config.version\n })\n })\n )\n\n const successCount = uploadResults.filter(Boolean).length\n console.log(`\\n✅ 上传完成: ${successCount}/${sourceMapFiles.length} 成功`)\n\n // 3. 删除 SourceMap 文件(如果配置了)\n if (config.removeSourceMap) {\n console.log('\\n🗑️ 正在删除 SourceMap 文件...')\n sourceMapFiles.forEach(file => {\n try {\n fs.unlinkSync(file)\n console.log(` ✅ 已删除: ${path.basename(file)}`)\n } catch (error) {\n console.error(` ❌ 删除失败: ${path.basename(file)}`, error)\n }\n })\n }\n\n console.log('\\n🎉 SourceMap 处理完成!')\n } catch (error) {\n console.error('\\n❌ SourceMap 处理失败:', error)\n // 不中断构建流程\n }\n }\n }\n}\n\n/**\n * 上传单个 SourceMap 文件到监控平台(默认实现)\n * \n * @param filePath - SourceMap 文件的绝对路径\n * @param config - 上传配置\n * @returns 上传是否成功\n * \n * @description\n * 这是一个示例实现,实际使用时建议通过 uploadFn 参数传入自定义上传函数\n * 以对接具体的监控平台(如 Sentry、阿里云 ARMS 等)\n */\nasync function uploadSourceMap(\n filePath: string, \n config: Required<Pick<SourceMapUploadOptions, 'uploadUrl' | 'apiKey' | 'projectName' | 'version'>>\n): Promise<boolean> {\n console.log(`📤 正在上传: ${path.basename(filePath)}`)\n \n try {\n // 读取文件内容\n const content = fs.readFileSync(filePath, 'utf-8')\n \n /* \n * ==============================================\n * 🔧 默认实现:基础的 HTTP POST 请求\n * ==============================================\n * \n * 建议通过 uploadFn 参数传入自定义上传函数以对接具体平台\n * \n * 不同监控平台的 API 接口差异较大,这里提供一个通用的实现\n * 实际使用时请根据平台文档修改\n */\n \n const response = await fetch(config.uploadUrl, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${config.apiKey}`,\n },\n body: JSON.stringify({\n project: config.projectName,\n version: config.version,\n filename: path.basename(filePath),\n content: content\n })\n })\n \n if (!response.ok) {\n throw new Error(`HTTP ${response.status}: ${response.statusText}`)\n }\n \n console.log(`✅ 上传成功: ${path.basename(filePath)}`)\n return true\n } catch (error) {\n console.error(`❌ 上传失败: ${path.basename(filePath)}`, error instanceof Error ? error.message : error)\n return false\n }\n}\n\n// 默认导出(支持两种导入方式)\nexport default uploadSourceMapPlugin\n\n"]}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { Plugin } from 'vite';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* SourceMap 上传插件配置项
|
|
5
|
+
*/
|
|
6
|
+
interface SourceMapUploadOptions {
|
|
7
|
+
/** 是否启用插件(默认仅在生产环境启用) */
|
|
8
|
+
enabled?: boolean;
|
|
9
|
+
/** 监控平台 API 地址 */
|
|
10
|
+
uploadUrl?: string;
|
|
11
|
+
/** API 密钥 */
|
|
12
|
+
apiKey?: string;
|
|
13
|
+
/** 项目名称 */
|
|
14
|
+
projectName?: string;
|
|
15
|
+
/** 项目版本 */
|
|
16
|
+
version?: string;
|
|
17
|
+
/** 上传完成后是否删除 SourceMap 文件(默认 true) */
|
|
18
|
+
removeSourceMap?: boolean;
|
|
19
|
+
/** 自定义上传函数(可选,用于对接特定监控平台) */
|
|
20
|
+
uploadFn?: (filePath: string, options: Required<Pick<SourceMapUploadOptions, 'uploadUrl' | 'apiKey' | 'projectName' | 'version'>>) => Promise<boolean>;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Vite 插件:上传 SourceMap 到监控平台
|
|
24
|
+
*
|
|
25
|
+
* @description
|
|
26
|
+
* 这个插件会在打包完成后自动执行以下操作:
|
|
27
|
+
* 1. 查找 dist 目录下所有的 .map 文件
|
|
28
|
+
* 2. 上传到指定的监控平台(如 Sentry、阿里云 ARMS 等)
|
|
29
|
+
* 3. 上传完成后自动删除 .map 文件(可配置)
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* ```ts
|
|
33
|
+
* // vite.config.ts
|
|
34
|
+
* import { uploadSourceMapPlugin } from 'vite-plugin-upload-sourcemap'
|
|
35
|
+
*
|
|
36
|
+
* export default defineConfig({
|
|
37
|
+
* plugins: [
|
|
38
|
+
* uploadSourceMapPlugin({
|
|
39
|
+
* enabled: mode === 'production',
|
|
40
|
+
* uploadUrl: 'https://your-platform.com/api/sourcemap',
|
|
41
|
+
* apiKey: process.env.SOURCEMAP_API_KEY,
|
|
42
|
+
* projectName: 'my-project',
|
|
43
|
+
* version: '1.0.0',
|
|
44
|
+
* removeSourceMap: true
|
|
45
|
+
* })
|
|
46
|
+
* ]
|
|
47
|
+
* })
|
|
48
|
+
* ```
|
|
49
|
+
*/
|
|
50
|
+
declare function uploadSourceMapPlugin(options?: SourceMapUploadOptions): Plugin;
|
|
51
|
+
|
|
52
|
+
export { type SourceMapUploadOptions, uploadSourceMapPlugin as default, uploadSourceMapPlugin };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { Plugin } from 'vite';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* SourceMap 上传插件配置项
|
|
5
|
+
*/
|
|
6
|
+
interface SourceMapUploadOptions {
|
|
7
|
+
/** 是否启用插件(默认仅在生产环境启用) */
|
|
8
|
+
enabled?: boolean;
|
|
9
|
+
/** 监控平台 API 地址 */
|
|
10
|
+
uploadUrl?: string;
|
|
11
|
+
/** API 密钥 */
|
|
12
|
+
apiKey?: string;
|
|
13
|
+
/** 项目名称 */
|
|
14
|
+
projectName?: string;
|
|
15
|
+
/** 项目版本 */
|
|
16
|
+
version?: string;
|
|
17
|
+
/** 上传完成后是否删除 SourceMap 文件(默认 true) */
|
|
18
|
+
removeSourceMap?: boolean;
|
|
19
|
+
/** 自定义上传函数(可选,用于对接特定监控平台) */
|
|
20
|
+
uploadFn?: (filePath: string, options: Required<Pick<SourceMapUploadOptions, 'uploadUrl' | 'apiKey' | 'projectName' | 'version'>>) => Promise<boolean>;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Vite 插件:上传 SourceMap 到监控平台
|
|
24
|
+
*
|
|
25
|
+
* @description
|
|
26
|
+
* 这个插件会在打包完成后自动执行以下操作:
|
|
27
|
+
* 1. 查找 dist 目录下所有的 .map 文件
|
|
28
|
+
* 2. 上传到指定的监控平台(如 Sentry、阿里云 ARMS 等)
|
|
29
|
+
* 3. 上传完成后自动删除 .map 文件(可配置)
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* ```ts
|
|
33
|
+
* // vite.config.ts
|
|
34
|
+
* import { uploadSourceMapPlugin } from 'vite-plugin-upload-sourcemap'
|
|
35
|
+
*
|
|
36
|
+
* export default defineConfig({
|
|
37
|
+
* plugins: [
|
|
38
|
+
* uploadSourceMapPlugin({
|
|
39
|
+
* enabled: mode === 'production',
|
|
40
|
+
* uploadUrl: 'https://your-platform.com/api/sourcemap',
|
|
41
|
+
* apiKey: process.env.SOURCEMAP_API_KEY,
|
|
42
|
+
* projectName: 'my-project',
|
|
43
|
+
* version: '1.0.0',
|
|
44
|
+
* removeSourceMap: true
|
|
45
|
+
* })
|
|
46
|
+
* ]
|
|
47
|
+
* })
|
|
48
|
+
* ```
|
|
49
|
+
*/
|
|
50
|
+
declare function uploadSourceMapPlugin(options?: SourceMapUploadOptions): Plugin;
|
|
51
|
+
|
|
52
|
+
export { type SourceMapUploadOptions, uploadSourceMapPlugin as default, uploadSourceMapPlugin };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
|
|
4
|
+
// src/index.ts
|
|
5
|
+
function uploadSourceMapPlugin(options = {}) {
|
|
6
|
+
const config = {
|
|
7
|
+
enabled: options.enabled ?? process.env.NODE_ENV === "production",
|
|
8
|
+
uploadUrl: options.uploadUrl || process.env.VITE_SOURCEMAP_UPLOAD_URL || "",
|
|
9
|
+
apiKey: options.apiKey || process.env.VITE_SOURCEMAP_API_KEY || "",
|
|
10
|
+
projectName: options.projectName || process.env.npm_package_name || "unknown-project",
|
|
11
|
+
version: options.version || process.env.npm_package_version || "1.0.0",
|
|
12
|
+
removeSourceMap: options.removeSourceMap ?? true,
|
|
13
|
+
uploadFn: options.uploadFn
|
|
14
|
+
};
|
|
15
|
+
let outDir = "dist";
|
|
16
|
+
const sourceMapFiles = [];
|
|
17
|
+
return {
|
|
18
|
+
// 插件名称
|
|
19
|
+
name: "vite-plugin-upload-sourcemap",
|
|
20
|
+
// 仅在构建时应用
|
|
21
|
+
apply: "build",
|
|
22
|
+
/**
|
|
23
|
+
* 在 Vite 配置解析完成后调用
|
|
24
|
+
* 用于获取构建配置信息(如输出目录)
|
|
25
|
+
*/
|
|
26
|
+
configResolved(resolvedConfig) {
|
|
27
|
+
outDir = resolvedConfig.build.outDir;
|
|
28
|
+
},
|
|
29
|
+
/**
|
|
30
|
+
* 在生成产物时调用(产物还在内存中,未写入磁盘)
|
|
31
|
+
* 这个钩子可以获取到所有生成的文件信息,包括 SourceMap 文件
|
|
32
|
+
*
|
|
33
|
+
* @description
|
|
34
|
+
* 在这个阶段我们可以知道哪些文件是 .map 文件,提前记录下来
|
|
35
|
+
* 避免后续在 closeBundle 中遍历文件系统查找
|
|
36
|
+
*/
|
|
37
|
+
generateBundle(_options, bundle) {
|
|
38
|
+
Object.keys(bundle).forEach((fileName) => {
|
|
39
|
+
if (fileName.endsWith(".map")) {
|
|
40
|
+
const fullPath = path.resolve(outDir, fileName);
|
|
41
|
+
sourceMapFiles.push(fullPath);
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
},
|
|
45
|
+
/**
|
|
46
|
+
* 在打包完成后调用(所有文件都已写入磁盘)
|
|
47
|
+
* 这是执行 SourceMap 上传的最佳时机
|
|
48
|
+
*/
|
|
49
|
+
async closeBundle() {
|
|
50
|
+
if (!config.enabled) {
|
|
51
|
+
console.log("\u23ED\uFE0F SourceMap \u4E0A\u4F20\u63D2\u4EF6\u5DF2\u7981\u7528");
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
if (!config.uploadUrl || !config.apiKey) {
|
|
55
|
+
console.warn("\u26A0\uFE0F SourceMap \u4E0A\u4F20\u914D\u7F6E\u4E0D\u5B8C\u6574\uFF0C\u8DF3\u8FC7\u4E0A\u4F20");
|
|
56
|
+
console.warn(" \u8BF7\u914D\u7F6E uploadUrl \u548C apiKey");
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
console.log("\n\u{1F680} \u5F00\u59CB\u5904\u7406 SourceMap \u6587\u4EF6...\n");
|
|
60
|
+
try {
|
|
61
|
+
if (sourceMapFiles.length === 0) {
|
|
62
|
+
console.log("\u26A0\uFE0F \u672A\u627E\u5230 SourceMap \u6587\u4EF6");
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
console.log(`\u2705 \u627E\u5230 ${sourceMapFiles.length} \u4E2A SourceMap \u6587\u4EF6
|
|
66
|
+
`);
|
|
67
|
+
console.log("\u{1F4E4} \u5F00\u59CB\u4E0A\u4F20 SourceMap...");
|
|
68
|
+
const uploadResults = await Promise.all(
|
|
69
|
+
sourceMapFiles.map((file) => {
|
|
70
|
+
if (config.uploadFn) {
|
|
71
|
+
return config.uploadFn(file, {
|
|
72
|
+
uploadUrl: config.uploadUrl,
|
|
73
|
+
apiKey: config.apiKey,
|
|
74
|
+
projectName: config.projectName,
|
|
75
|
+
version: config.version
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
return uploadSourceMap(file, {
|
|
79
|
+
uploadUrl: config.uploadUrl,
|
|
80
|
+
apiKey: config.apiKey,
|
|
81
|
+
projectName: config.projectName,
|
|
82
|
+
version: config.version
|
|
83
|
+
});
|
|
84
|
+
})
|
|
85
|
+
);
|
|
86
|
+
const successCount = uploadResults.filter(Boolean).length;
|
|
87
|
+
console.log(`
|
|
88
|
+
\u2705 \u4E0A\u4F20\u5B8C\u6210: ${successCount}/${sourceMapFiles.length} \u6210\u529F`);
|
|
89
|
+
if (config.removeSourceMap) {
|
|
90
|
+
console.log("\n\u{1F5D1}\uFE0F \u6B63\u5728\u5220\u9664 SourceMap \u6587\u4EF6...");
|
|
91
|
+
sourceMapFiles.forEach((file) => {
|
|
92
|
+
try {
|
|
93
|
+
fs.unlinkSync(file);
|
|
94
|
+
console.log(` \u2705 \u5DF2\u5220\u9664: ${path.basename(file)}`);
|
|
95
|
+
} catch (error) {
|
|
96
|
+
console.error(` \u274C \u5220\u9664\u5931\u8D25: ${path.basename(file)}`, error);
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
console.log("\n\u{1F389} SourceMap \u5904\u7406\u5B8C\u6210!");
|
|
101
|
+
} catch (error) {
|
|
102
|
+
console.error("\n\u274C SourceMap \u5904\u7406\u5931\u8D25:", error);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
async function uploadSourceMap(filePath, config) {
|
|
108
|
+
console.log(`\u{1F4E4} \u6B63\u5728\u4E0A\u4F20: ${path.basename(filePath)}`);
|
|
109
|
+
try {
|
|
110
|
+
const content = fs.readFileSync(filePath, "utf-8");
|
|
111
|
+
const response = await fetch(config.uploadUrl, {
|
|
112
|
+
method: "POST",
|
|
113
|
+
headers: {
|
|
114
|
+
"Content-Type": "application/json",
|
|
115
|
+
"Authorization": `Bearer ${config.apiKey}`
|
|
116
|
+
},
|
|
117
|
+
body: JSON.stringify({
|
|
118
|
+
project: config.projectName,
|
|
119
|
+
version: config.version,
|
|
120
|
+
filename: path.basename(filePath),
|
|
121
|
+
content
|
|
122
|
+
})
|
|
123
|
+
});
|
|
124
|
+
if (!response.ok) {
|
|
125
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
126
|
+
}
|
|
127
|
+
console.log(`\u2705 \u4E0A\u4F20\u6210\u529F: ${path.basename(filePath)}`);
|
|
128
|
+
return true;
|
|
129
|
+
} catch (error) {
|
|
130
|
+
console.error(`\u274C \u4E0A\u4F20\u5931\u8D25: ${path.basename(filePath)}`, error instanceof Error ? error.message : error);
|
|
131
|
+
return false;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
var index_default = uploadSourceMapPlugin;
|
|
135
|
+
|
|
136
|
+
export { index_default as default, uploadSourceMapPlugin };
|
|
137
|
+
//# sourceMappingURL=index.js.map
|
|
138
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import fs from 'fs'\nimport path from 'path'\nimport type { Plugin } from 'vite'\n\n/**\n * SourceMap 上传插件配置项\n */\nexport interface SourceMapUploadOptions {\n /** 是否启用插件(默认仅在生产环境启用) */\n enabled?: boolean\n /** 监控平台 API 地址 */\n uploadUrl?: string\n /** API 密钥 */\n apiKey?: string\n /** 项目名称 */\n projectName?: string\n /** 项目版本 */\n version?: string\n /** 上传完成后是否删除 SourceMap 文件(默认 true) */\n removeSourceMap?: boolean\n /** 自定义上传函数(可选,用于对接特定监控平台) */\n uploadFn?: (filePath: string, options: Required<Pick<SourceMapUploadOptions, 'uploadUrl' | 'apiKey' | 'projectName' | 'version'>>) => Promise<boolean>\n}\n\n/**\n * Vite 插件:上传 SourceMap 到监控平台\n * \n * @description\n * 这个插件会在打包完成后自动执行以下操作:\n * 1. 查找 dist 目录下所有的 .map 文件\n * 2. 上传到指定的监控平台(如 Sentry、阿里云 ARMS 等)\n * 3. 上传完成后自动删除 .map 文件(可配置)\n * \n * @example\n * ```ts\n * // vite.config.ts\n * import { uploadSourceMapPlugin } from 'vite-plugin-upload-sourcemap'\n * \n * export default defineConfig({\n * plugins: [\n * uploadSourceMapPlugin({\n * enabled: mode === 'production',\n * uploadUrl: 'https://your-platform.com/api/sourcemap',\n * apiKey: process.env.SOURCEMAP_API_KEY,\n * projectName: 'my-project',\n * version: '1.0.0',\n * removeSourceMap: true\n * })\n * ]\n * })\n * ```\n */\nexport function uploadSourceMapPlugin(options: SourceMapUploadOptions = {}): Plugin {\n // 默认配置\n const config = {\n enabled: options.enabled ?? process.env.NODE_ENV === 'production',\n uploadUrl: options.uploadUrl || process.env.VITE_SOURCEMAP_UPLOAD_URL || '',\n apiKey: options.apiKey || process.env.VITE_SOURCEMAP_API_KEY || '',\n projectName: options.projectName || process.env.npm_package_name || 'unknown-project',\n version: options.version || process.env.npm_package_version || '1.0.0',\n removeSourceMap: options.removeSourceMap ?? true,\n uploadFn: options.uploadFn\n }\n\n // 用于存储输出目录路径\n let outDir = 'dist'\n \n // 用于存储在构建过程中生成的 SourceMap 文件路径\n // 在 generateBundle 钩子中收集,在 closeBundle 钩子中使用\n const sourceMapFiles: string[] = []\n\n return {\n // 插件名称\n name: 'vite-plugin-upload-sourcemap',\n \n // 仅在构建时应用\n apply: 'build',\n \n /**\n * 在 Vite 配置解析完成后调用\n * 用于获取构建配置信息(如输出目录)\n */\n configResolved(resolvedConfig) {\n // 获取实际的输出目录\n outDir = resolvedConfig.build.outDir\n },\n \n /**\n * 在生成产物时调用(产物还在内存中,未写入磁盘)\n * 这个钩子可以获取到所有生成的文件信息,包括 SourceMap 文件\n * \n * @description\n * 在这个阶段我们可以知道哪些文件是 .map 文件,提前记录下来\n * 避免后续在 closeBundle 中遍历文件系统查找\n */\n generateBundle(_options, bundle) {\n // 遍历所有生成的文件\n Object.keys(bundle).forEach(fileName => {\n // 如果是 .map 文件,记录其完整路径\n if (fileName.endsWith('.map')) {\n const fullPath = path.resolve(outDir, fileName)\n sourceMapFiles.push(fullPath)\n }\n })\n },\n \n /**\n * 在打包完成后调用(所有文件都已写入磁盘)\n * 这是执行 SourceMap 上传的最佳时机\n */\n async closeBundle() {\n // 如果插件未启用,直接返回\n if (!config.enabled) {\n console.log('⏭️ SourceMap 上传插件已禁用')\n return\n }\n\n // 验证必要的配置\n if (!config.uploadUrl || !config.apiKey) {\n console.warn('⚠️ SourceMap 上传配置不完整,跳过上传')\n console.warn(' 请配置 uploadUrl 和 apiKey')\n return\n }\n\n console.log('\\n🚀 开始处理 SourceMap 文件...\\n')\n\n try {\n // 1. 检查是否有 SourceMap 文件(已在 generateBundle 中收集)\n if (sourceMapFiles.length === 0) {\n console.log('⚠️ 未找到 SourceMap 文件')\n return\n }\n\n console.log(`✅ 找到 ${sourceMapFiles.length} 个 SourceMap 文件\\n`)\n\n // 2. 上传所有 SourceMap 文件\n console.log('📤 开始上传 SourceMap...')\n const uploadResults = await Promise.all(\n sourceMapFiles.map(file => {\n // 如果提供了自定义上传函数,使用自定义函数\n if (config.uploadFn) {\n return config.uploadFn(file, {\n uploadUrl: config.uploadUrl,\n apiKey: config.apiKey,\n projectName: config.projectName,\n version: config.version\n })\n }\n // 否则使用默认上传函数\n return uploadSourceMap(file, {\n uploadUrl: config.uploadUrl,\n apiKey: config.apiKey,\n projectName: config.projectName,\n version: config.version\n })\n })\n )\n\n const successCount = uploadResults.filter(Boolean).length\n console.log(`\\n✅ 上传完成: ${successCount}/${sourceMapFiles.length} 成功`)\n\n // 3. 删除 SourceMap 文件(如果配置了)\n if (config.removeSourceMap) {\n console.log('\\n🗑️ 正在删除 SourceMap 文件...')\n sourceMapFiles.forEach(file => {\n try {\n fs.unlinkSync(file)\n console.log(` ✅ 已删除: ${path.basename(file)}`)\n } catch (error) {\n console.error(` ❌ 删除失败: ${path.basename(file)}`, error)\n }\n })\n }\n\n console.log('\\n🎉 SourceMap 处理完成!')\n } catch (error) {\n console.error('\\n❌ SourceMap 处理失败:', error)\n // 不中断构建流程\n }\n }\n }\n}\n\n/**\n * 上传单个 SourceMap 文件到监控平台(默认实现)\n * \n * @param filePath - SourceMap 文件的绝对路径\n * @param config - 上传配置\n * @returns 上传是否成功\n * \n * @description\n * 这是一个示例实现,实际使用时建议通过 uploadFn 参数传入自定义上传函数\n * 以对接具体的监控平台(如 Sentry、阿里云 ARMS 等)\n */\nasync function uploadSourceMap(\n filePath: string, \n config: Required<Pick<SourceMapUploadOptions, 'uploadUrl' | 'apiKey' | 'projectName' | 'version'>>\n): Promise<boolean> {\n console.log(`📤 正在上传: ${path.basename(filePath)}`)\n \n try {\n // 读取文件内容\n const content = fs.readFileSync(filePath, 'utf-8')\n \n /* \n * ==============================================\n * 🔧 默认实现:基础的 HTTP POST 请求\n * ==============================================\n * \n * 建议通过 uploadFn 参数传入自定义上传函数以对接具体平台\n * \n * 不同监控平台的 API 接口差异较大,这里提供一个通用的实现\n * 实际使用时请根据平台文档修改\n */\n \n const response = await fetch(config.uploadUrl, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${config.apiKey}`,\n },\n body: JSON.stringify({\n project: config.projectName,\n version: config.version,\n filename: path.basename(filePath),\n content: content\n })\n })\n \n if (!response.ok) {\n throw new Error(`HTTP ${response.status}: ${response.statusText}`)\n }\n \n console.log(`✅ 上传成功: ${path.basename(filePath)}`)\n return true\n } catch (error) {\n console.error(`❌ 上传失败: ${path.basename(filePath)}`, error instanceof Error ? error.message : error)\n return false\n }\n}\n\n// 默认导出(支持两种导入方式)\nexport default uploadSourceMapPlugin\n\n"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@zzzzzzhaopu/vite-plugin-upload-sourcemap",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "A Vite plugin to automatically upload SourceMap files to monitoring platforms after production build",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"vite",
|
|
7
|
+
"vite-plugin",
|
|
8
|
+
"sourcemap",
|
|
9
|
+
"source-map",
|
|
10
|
+
"upload",
|
|
11
|
+
"sentry",
|
|
12
|
+
"monitoring",
|
|
13
|
+
"error-tracking"
|
|
14
|
+
],
|
|
15
|
+
"type": "module",
|
|
16
|
+
"main": "./dist/index.js",
|
|
17
|
+
"module": "./dist/index.js",
|
|
18
|
+
"types": "./dist/index.d.ts",
|
|
19
|
+
"exports": {
|
|
20
|
+
".": {
|
|
21
|
+
"types": "./dist/index.d.ts",
|
|
22
|
+
"import": "./dist/index.js",
|
|
23
|
+
"require": "./dist/index.cjs"
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
"files": [
|
|
27
|
+
"dist",
|
|
28
|
+
"README.md",
|
|
29
|
+
"LICENSE"
|
|
30
|
+
],
|
|
31
|
+
"scripts": {
|
|
32
|
+
"build": "tsup",
|
|
33
|
+
"dev": "tsup --watch",
|
|
34
|
+
"prepublishOnly": "pnpm build"
|
|
35
|
+
},
|
|
36
|
+
"peerDependencies": {
|
|
37
|
+
"vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0"
|
|
38
|
+
},
|
|
39
|
+
"devDependencies": {
|
|
40
|
+
"@types/node": "^22.0.0",
|
|
41
|
+
"tsup": "^8.0.0",
|
|
42
|
+
"typescript": "^5.0.0",
|
|
43
|
+
"vite": "^7.0.0"
|
|
44
|
+
},
|
|
45
|
+
"engines": {
|
|
46
|
+
"node": ">=18.0.0"
|
|
47
|
+
},
|
|
48
|
+
"repository": {
|
|
49
|
+
"type": "git",
|
|
50
|
+
"url": "https://github.com/your-username/vite-plugin-upload-sourcemap.git"
|
|
51
|
+
},
|
|
52
|
+
"bugs": {
|
|
53
|
+
"url": "https://github.com/your-username/vite-plugin-upload-sourcemap/issues"
|
|
54
|
+
},
|
|
55
|
+
"homepage": "https://github.com/your-username/vite-plugin-upload-sourcemap#readme",
|
|
56
|
+
"author": "Your Name <your.email@example.com>",
|
|
57
|
+
"license": "MIT",
|
|
58
|
+
"publishConfig": {
|
|
59
|
+
"access": "public",
|
|
60
|
+
"registry": "https://registry.npmjs.org/"
|
|
61
|
+
}
|
|
62
|
+
}
|