@tourmind-frontend/monitor-plugin-webpack 1.2.0 → 1.6.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/dist/index.cjs +41 -9
- package/dist/index.d.ts +15 -2
- package/dist/index.js +41 -9
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -33,6 +33,7 @@ __export(src_exports, {
|
|
|
33
33
|
default: () => UploadSourceMapPlugin
|
|
34
34
|
});
|
|
35
35
|
module.exports = __toCommonJS(src_exports);
|
|
36
|
+
var import_zlib = require("zlib");
|
|
36
37
|
var import_axios = __toESM(require("axios"), 1);
|
|
37
38
|
var import_form_data = __toESM(require("form-data"), 1);
|
|
38
39
|
var LOG_PREFIX = "[frontend-monitor]";
|
|
@@ -43,6 +44,11 @@ function defaultLogger(level, msg, extra) {
|
|
|
43
44
|
else if (level === "warn") console.warn(line, tail);
|
|
44
45
|
else console.error(line, tail);
|
|
45
46
|
}
|
|
47
|
+
function formatSize(bytes) {
|
|
48
|
+
if (bytes >= 1024 * 1024) return `${(bytes / 1024 / 1024).toFixed(1)}MB`;
|
|
49
|
+
if (bytes >= 1024) return `${(bytes / 1024).toFixed(1)}KB`;
|
|
50
|
+
return `${bytes}B`;
|
|
51
|
+
}
|
|
46
52
|
function extractSourceMaps(compilation) {
|
|
47
53
|
const result = [];
|
|
48
54
|
const assets = compilation.assets;
|
|
@@ -65,10 +71,16 @@ function extractSourceMaps(compilation) {
|
|
|
65
71
|
}
|
|
66
72
|
async function uploadFiles({ url, query, files }) {
|
|
67
73
|
const form = new import_form_data.default();
|
|
74
|
+
let rawBytes = 0;
|
|
75
|
+
let gzipBytes = 0;
|
|
68
76
|
for (const [filename, content] of files) {
|
|
69
|
-
|
|
77
|
+
const raw = Buffer.from(content, "utf-8");
|
|
78
|
+
const gz = (0, import_zlib.gzipSync)(raw);
|
|
79
|
+
rawBytes += raw.byteLength;
|
|
80
|
+
gzipBytes += gz.byteLength;
|
|
81
|
+
form.append("file", gz, { filename });
|
|
70
82
|
}
|
|
71
|
-
const qs = new URLSearchParams(query).toString();
|
|
83
|
+
const qs = new URLSearchParams({ ...query, encoding: "gzip" }).toString();
|
|
72
84
|
const fullUrl = `${url}${url.includes("?") ? "&" : "?"}${qs}`;
|
|
73
85
|
await (0, import_axios.default)({
|
|
74
86
|
method: "POST",
|
|
@@ -78,14 +90,28 @@ async function uploadFiles({ url, query, files }) {
|
|
|
78
90
|
maxBodyLength: Infinity,
|
|
79
91
|
maxContentLength: Infinity
|
|
80
92
|
});
|
|
93
|
+
return { rawBytes, gzipBytes };
|
|
81
94
|
}
|
|
82
95
|
var UploadSourceMapPlugin = class {
|
|
83
96
|
constructor(options) {
|
|
97
|
+
// emit 阶段 fire-and-forget 启动的上传任务;用数组而非单值,兼容同一实例被挂到
|
|
98
|
+
// 多个 compiler(如 Nuxt 2 modern 模式 client + modern 双客户端构建)的情况。
|
|
99
|
+
this.pendingUploads = [];
|
|
84
100
|
if (!options.url) throw new Error(`${LOG_PREFIX} "url" is required`);
|
|
85
|
-
if (!options.
|
|
101
|
+
if (!options.authToken) throw new Error(`${LOG_PREFIX} "authToken" is required`);
|
|
102
|
+
if (!options.authToken.startsWith("fm_at_")) {
|
|
103
|
+
throw new Error(`${LOG_PREFIX} "authToken" must start with "fm_at_"; do not use the client key here`);
|
|
104
|
+
}
|
|
86
105
|
if (!options.commit) throw new Error(`${LOG_PREFIX} "commit" is required`);
|
|
87
106
|
this.options = options;
|
|
88
107
|
}
|
|
108
|
+
/**
|
|
109
|
+
* 等待所有已启动的上传完成(上传失败已被内部 catch 吞掉,不会 reject)。
|
|
110
|
+
* deferWait 模式下由调用方在构建尾声调用,确保进程退出前上传完成。
|
|
111
|
+
*/
|
|
112
|
+
waitForUpload() {
|
|
113
|
+
return Promise.all(this.pendingUploads).then(() => void 0);
|
|
114
|
+
}
|
|
89
115
|
apply(compiler) {
|
|
90
116
|
var _a;
|
|
91
117
|
const options = this.options;
|
|
@@ -93,7 +119,6 @@ var UploadSourceMapPlugin = class {
|
|
|
93
119
|
if (compiler.options.mode !== "production") return;
|
|
94
120
|
const uploadUrl = `${options.url.replace(/\/+$/, "")}/api/upload`;
|
|
95
121
|
compiler.options.devtool = "hidden-source-map";
|
|
96
|
-
let uploadPromise = null;
|
|
97
122
|
compiler.hooks.emit.tap("UploadSourceMapPlugin", (compilation) => {
|
|
98
123
|
var _a2;
|
|
99
124
|
const files = extractSourceMaps(compilation);
|
|
@@ -104,14 +129,21 @@ var UploadSourceMapPlugin = class {
|
|
|
104
129
|
const { commit } = options;
|
|
105
130
|
const timestamp = (_a2 = options.timestamp) != null ? _a2 : Date.now();
|
|
106
131
|
const query = {
|
|
107
|
-
token: options.
|
|
132
|
+
token: options.authToken,
|
|
108
133
|
timestamp: String(timestamp)
|
|
109
134
|
};
|
|
110
135
|
query.commit = commit;
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
136
|
+
this.pendingUploads.push(
|
|
137
|
+
uploadFiles({ url: uploadUrl, query, files }).then(
|
|
138
|
+
({ rawBytes, gzipBytes }) => log(
|
|
139
|
+
"info",
|
|
140
|
+
`uploaded ${files.length} sourcemap file(s) (commit=${commit}, ${formatSize(rawBytes)} -> ${formatSize(gzipBytes)} gzipped)`
|
|
141
|
+
)
|
|
142
|
+
).catch((err) => log("error", "upload failed", err instanceof Error ? err.message : err))
|
|
143
|
+
);
|
|
115
144
|
});
|
|
145
|
+
if (!options.deferWait) {
|
|
146
|
+
compiler.hooks.done.tapPromise("UploadSourceMapPlugin", () => this.waitForUpload());
|
|
147
|
+
}
|
|
116
148
|
}
|
|
117
149
|
};
|
package/dist/index.d.ts
CHANGED
|
@@ -2,17 +2,30 @@ import { Compiler } from "webpack";
|
|
|
2
2
|
export interface UploadSourceMapOptions {
|
|
3
3
|
/** monitor 服务的 base URL,例如 `https://monitor.example.com`。内部拼接 `/api/upload`。 */
|
|
4
4
|
url: string;
|
|
5
|
-
/** monitor 项目 token
|
|
6
|
-
|
|
5
|
+
/** monitor 项目 auth token(前缀 `fm_at_`),仅供 CI / 构建机使用,从 web 控制台「项目」页获取。 */
|
|
6
|
+
authToken: string;
|
|
7
7
|
/** Commit hash,由调用方自行解析(如 `git rev-parse HEAD`)。 */
|
|
8
8
|
commit: string;
|
|
9
9
|
/** 构建时间戳(毫秒),未设置时取上传时刻的 `Date.now()`。 */
|
|
10
10
|
timestamp?: number;
|
|
11
11
|
/** 自定义 logger,默认走 `console.log` / `console.warn` / `console.error`。 */
|
|
12
12
|
logger?: (level: "info" | "warn" | "error", msg: string, extra?: unknown) => void;
|
|
13
|
+
/**
|
|
14
|
+
* 为 true 时本插件不在 compiler 的 done 阶段等待上传完成,由调用方在更晚的时机
|
|
15
|
+
* (如 Nuxt 2 的 `build:done`,所有 compiler 都结束后)await `waitForUpload()`,
|
|
16
|
+
* 让上传与后续其它 compiler 的编译并行。
|
|
17
|
+
* 注意:调用方必须在构建进程退出前 await,否则 CI 退出会掐断未完成的上传。
|
|
18
|
+
*/
|
|
19
|
+
deferWait?: boolean;
|
|
13
20
|
}
|
|
14
21
|
export default class UploadSourceMapPlugin {
|
|
15
22
|
options: UploadSourceMapOptions;
|
|
23
|
+
private pendingUploads;
|
|
16
24
|
constructor(options: UploadSourceMapOptions);
|
|
25
|
+
/**
|
|
26
|
+
* 等待所有已启动的上传完成(上传失败已被内部 catch 吞掉,不会 reject)。
|
|
27
|
+
* deferWait 模式下由调用方在构建尾声调用,确保进程退出前上传完成。
|
|
28
|
+
*/
|
|
29
|
+
waitForUpload(): Promise<void>;
|
|
17
30
|
apply(compiler: Compiler): void;
|
|
18
31
|
}
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
// extensions/plugin-webpack/src/index.ts
|
|
2
|
+
import { gzipSync } from "zlib";
|
|
2
3
|
import axios from "axios";
|
|
3
4
|
import FormData from "form-data";
|
|
4
5
|
var LOG_PREFIX = "[frontend-monitor]";
|
|
@@ -9,6 +10,11 @@ function defaultLogger(level, msg, extra) {
|
|
|
9
10
|
else if (level === "warn") console.warn(line, tail);
|
|
10
11
|
else console.error(line, tail);
|
|
11
12
|
}
|
|
13
|
+
function formatSize(bytes) {
|
|
14
|
+
if (bytes >= 1024 * 1024) return `${(bytes / 1024 / 1024).toFixed(1)}MB`;
|
|
15
|
+
if (bytes >= 1024) return `${(bytes / 1024).toFixed(1)}KB`;
|
|
16
|
+
return `${bytes}B`;
|
|
17
|
+
}
|
|
12
18
|
function extractSourceMaps(compilation) {
|
|
13
19
|
const result = [];
|
|
14
20
|
const assets = compilation.assets;
|
|
@@ -31,10 +37,16 @@ function extractSourceMaps(compilation) {
|
|
|
31
37
|
}
|
|
32
38
|
async function uploadFiles({ url, query, files }) {
|
|
33
39
|
const form = new FormData();
|
|
40
|
+
let rawBytes = 0;
|
|
41
|
+
let gzipBytes = 0;
|
|
34
42
|
for (const [filename, content] of files) {
|
|
35
|
-
|
|
43
|
+
const raw = Buffer.from(content, "utf-8");
|
|
44
|
+
const gz = gzipSync(raw);
|
|
45
|
+
rawBytes += raw.byteLength;
|
|
46
|
+
gzipBytes += gz.byteLength;
|
|
47
|
+
form.append("file", gz, { filename });
|
|
36
48
|
}
|
|
37
|
-
const qs = new URLSearchParams(query).toString();
|
|
49
|
+
const qs = new URLSearchParams({ ...query, encoding: "gzip" }).toString();
|
|
38
50
|
const fullUrl = `${url}${url.includes("?") ? "&" : "?"}${qs}`;
|
|
39
51
|
await axios({
|
|
40
52
|
method: "POST",
|
|
@@ -44,14 +56,28 @@ async function uploadFiles({ url, query, files }) {
|
|
|
44
56
|
maxBodyLength: Infinity,
|
|
45
57
|
maxContentLength: Infinity
|
|
46
58
|
});
|
|
59
|
+
return { rawBytes, gzipBytes };
|
|
47
60
|
}
|
|
48
61
|
var UploadSourceMapPlugin = class {
|
|
49
62
|
constructor(options) {
|
|
63
|
+
// emit 阶段 fire-and-forget 启动的上传任务;用数组而非单值,兼容同一实例被挂到
|
|
64
|
+
// 多个 compiler(如 Nuxt 2 modern 模式 client + modern 双客户端构建)的情况。
|
|
65
|
+
this.pendingUploads = [];
|
|
50
66
|
if (!options.url) throw new Error(`${LOG_PREFIX} "url" is required`);
|
|
51
|
-
if (!options.
|
|
67
|
+
if (!options.authToken) throw new Error(`${LOG_PREFIX} "authToken" is required`);
|
|
68
|
+
if (!options.authToken.startsWith("fm_at_")) {
|
|
69
|
+
throw new Error(`${LOG_PREFIX} "authToken" must start with "fm_at_"; do not use the client key here`);
|
|
70
|
+
}
|
|
52
71
|
if (!options.commit) throw new Error(`${LOG_PREFIX} "commit" is required`);
|
|
53
72
|
this.options = options;
|
|
54
73
|
}
|
|
74
|
+
/**
|
|
75
|
+
* 等待所有已启动的上传完成(上传失败已被内部 catch 吞掉,不会 reject)。
|
|
76
|
+
* deferWait 模式下由调用方在构建尾声调用,确保进程退出前上传完成。
|
|
77
|
+
*/
|
|
78
|
+
waitForUpload() {
|
|
79
|
+
return Promise.all(this.pendingUploads).then(() => void 0);
|
|
80
|
+
}
|
|
55
81
|
apply(compiler) {
|
|
56
82
|
var _a;
|
|
57
83
|
const options = this.options;
|
|
@@ -59,7 +85,6 @@ var UploadSourceMapPlugin = class {
|
|
|
59
85
|
if (compiler.options.mode !== "production") return;
|
|
60
86
|
const uploadUrl = `${options.url.replace(/\/+$/, "")}/api/upload`;
|
|
61
87
|
compiler.options.devtool = "hidden-source-map";
|
|
62
|
-
let uploadPromise = null;
|
|
63
88
|
compiler.hooks.emit.tap("UploadSourceMapPlugin", (compilation) => {
|
|
64
89
|
var _a2;
|
|
65
90
|
const files = extractSourceMaps(compilation);
|
|
@@ -70,15 +95,22 @@ var UploadSourceMapPlugin = class {
|
|
|
70
95
|
const { commit } = options;
|
|
71
96
|
const timestamp = (_a2 = options.timestamp) != null ? _a2 : Date.now();
|
|
72
97
|
const query = {
|
|
73
|
-
token: options.
|
|
98
|
+
token: options.authToken,
|
|
74
99
|
timestamp: String(timestamp)
|
|
75
100
|
};
|
|
76
101
|
query.commit = commit;
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
102
|
+
this.pendingUploads.push(
|
|
103
|
+
uploadFiles({ url: uploadUrl, query, files }).then(
|
|
104
|
+
({ rawBytes, gzipBytes }) => log(
|
|
105
|
+
"info",
|
|
106
|
+
`uploaded ${files.length} sourcemap file(s) (commit=${commit}, ${formatSize(rawBytes)} -> ${formatSize(gzipBytes)} gzipped)`
|
|
107
|
+
)
|
|
108
|
+
).catch((err) => log("error", "upload failed", err instanceof Error ? err.message : err))
|
|
109
|
+
);
|
|
81
110
|
});
|
|
111
|
+
if (!options.deferWait) {
|
|
112
|
+
compiler.hooks.done.tapPromise("UploadSourceMapPlugin", () => this.waitForUpload());
|
|
113
|
+
}
|
|
82
114
|
}
|
|
83
115
|
};
|
|
84
116
|
export {
|
package/package.json
CHANGED