@xysfe/vite-plugin-dev-proxy 1.0.2 → 1.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +511 -145
- package/dist/index.cjs +390 -163
- package/dist/index.d.cts +178 -4
- package/dist/index.d.mts +178 -4
- package/dist/index.d.ts +178 -4
- package/dist/index.mjs +385 -163
- package/package.json +2 -3
package/dist/index.mjs
CHANGED
|
@@ -2,205 +2,305 @@ import zlib from 'zlib';
|
|
|
2
2
|
import { resolve } from 'path';
|
|
3
3
|
import fs from 'fs';
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
const SCRIPT_LINK_REGEX = /<(?:script[^>]*>.*?<\/script>|link[^>]*>)/g;
|
|
6
|
+
const ASSET_REGEX = /\.(js|mjs|ts|tsx|jsx|css|scss|sass|less|vue|json|woff2?|ttf|eot|ico|png|jpe?g|gif|svg|webp)(\?.*)?$/i;
|
|
7
|
+
const STATIC_PATH_REGEX = /^\/(static|assets|public|images|css|js)\//i;
|
|
8
|
+
const BYPASS_REGEX = /\.(vue|js|mjs|ts|tsx|jsx|css|scss|sass|less|json|png|jpe?g|gif|svg|webp|ico|woff2?|ttf|eot)$/i;
|
|
9
|
+
const REDIRECT_STATUS_MIN = 300;
|
|
10
|
+
const REDIRECT_STATUS_MAX = 400;
|
|
11
|
+
const DEFAULT_APP_DIV_REGEX = /<div[^>]*id=["']app["'][^>]*><\/div>/g;
|
|
12
|
+
const HTTPS_TO_HTTP_REGEX = /https:\/\/(localhost|127\.0\.0\.1|192\.168\.\d{1,3}\.\d{1,3}|10\.\d{1,3}\.\d{1,3}\.\d{1,3}|172\.(?:1[6-9]|2[0-9]|3[0-1])\.\d{1,3}\.\d{1,3})(:\d+)?/gi;
|
|
13
|
+
function createLogger(debug) {
|
|
14
|
+
return {
|
|
15
|
+
log: debug ? console.log.bind(console) : () => {
|
|
16
|
+
},
|
|
17
|
+
logError: debug ? console.error.bind(console) : () => {
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
function normalizePath(path) {
|
|
22
|
+
return path.endsWith("/") ? path.slice(0, -1) : path;
|
|
23
|
+
}
|
|
24
|
+
function generateEntryScript(entry, staticPrefix) {
|
|
25
|
+
if (Array.isArray(entry)) {
|
|
26
|
+
return entry.map(
|
|
27
|
+
(e) => `<script crossorigin type="module" src="${staticPrefix + e}"><\/script>`
|
|
28
|
+
).join("\n");
|
|
29
|
+
}
|
|
30
|
+
return `<script crossorigin type="module" src="${staticPrefix + entry}"><\/script>`;
|
|
31
|
+
}
|
|
32
|
+
function rewriteCookies(headers, log2) {
|
|
33
|
+
const setCookie = headers["set-cookie"];
|
|
34
|
+
if (setCookie) {
|
|
35
|
+
headers["set-cookie"] = setCookie.map((cookie) => {
|
|
36
|
+
const rewrittenCookie = cookie.replace(/;\s*secure\s*(;|$)/gi, "$1").replace(/;\s*domain\s*=[^;]+(;|$)/gi, "$1").replace(/;\s*samesite\s*=[^;]+(;|$)/gi, "$1").replace(/;+/g, ";").replace(/;\s*$/g, "");
|
|
37
|
+
log2("[rewrittenCookie]", rewrittenCookie);
|
|
38
|
+
return rewrittenCookie;
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
return headers;
|
|
42
|
+
}
|
|
43
|
+
function decompressBuffer(buffer, encoding) {
|
|
44
|
+
if (encoding === "gzip") {
|
|
45
|
+
return zlib.gunzipSync(buffer);
|
|
46
|
+
} else if (encoding === "deflate") {
|
|
47
|
+
return zlib.inflateSync(buffer);
|
|
48
|
+
} else if (encoding === "br") {
|
|
49
|
+
return zlib.brotliDecompressSync(buffer);
|
|
50
|
+
}
|
|
51
|
+
return buffer;
|
|
52
|
+
}
|
|
53
|
+
function shouldClearScriptCss(match, clearRule) {
|
|
54
|
+
const srcMatch = match.match(/src="([^"]+)"/i);
|
|
55
|
+
const hrefMatch = match.match(/href="([^"]+)"/i);
|
|
56
|
+
const srcOrHref = srcMatch ? srcMatch[1] : hrefMatch ? hrefMatch[1] : null;
|
|
57
|
+
if (clearRule === "") {
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
if (typeof clearRule === "string") {
|
|
61
|
+
return srcOrHref?.startsWith(clearRule) ?? false;
|
|
62
|
+
}
|
|
63
|
+
if (Array.isArray(clearRule)) {
|
|
64
|
+
return clearRule.some((prefix) => srcOrHref?.startsWith(prefix));
|
|
65
|
+
}
|
|
66
|
+
if (clearRule instanceof RegExp) {
|
|
67
|
+
return clearRule.test(match);
|
|
68
|
+
}
|
|
69
|
+
if (typeof clearRule === "function") {
|
|
70
|
+
return clearRule(match);
|
|
71
|
+
}
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
function injectEntryScript(html, fullEntry, developmentAgentOccupancy) {
|
|
75
|
+
if (developmentAgentOccupancy) {
|
|
76
|
+
return html.replace(developmentAgentOccupancy, fullEntry);
|
|
77
|
+
}
|
|
78
|
+
return html.replace(DEFAULT_APP_DIV_REGEX, (match) => `${match}${fullEntry}`);
|
|
79
|
+
}
|
|
80
|
+
function clearScriptCssTags(html, clearRule, log2) {
|
|
81
|
+
return html.replace(SCRIPT_LINK_REGEX, (match) => {
|
|
82
|
+
const isClear = shouldClearScriptCss(match, clearRule);
|
|
83
|
+
if (isClear) {
|
|
84
|
+
log2?.(`[clearScriptCssTags]: ${match}`);
|
|
85
|
+
}
|
|
86
|
+
return isClear ? "" : match;
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
function isRedirectResponse(proxyRes) {
|
|
90
|
+
return proxyRes.statusCode >= REDIRECT_STATUS_MIN && proxyRes.statusCode < REDIRECT_STATUS_MAX && !!proxyRes.headers.location;
|
|
91
|
+
}
|
|
92
|
+
function shouldProcessAsHtml(contentType, acceptHeader, requestUrl, isRedirect) {
|
|
93
|
+
const isNavigationRequest = acceptHeader.includes("text/html");
|
|
94
|
+
const isAssetRequest = ASSET_REGEX.test(requestUrl);
|
|
95
|
+
const isStaticPath = STATIC_PATH_REGEX.test(requestUrl);
|
|
96
|
+
return contentType.includes("text/html") && isNavigationRequest && !isAssetRequest && !isStaticPath && !isRedirect;
|
|
97
|
+
}
|
|
98
|
+
function matchesRemoteResource(url, remoteRule) {
|
|
99
|
+
if (typeof remoteRule === "string") {
|
|
100
|
+
return url.startsWith(remoteRule);
|
|
101
|
+
}
|
|
102
|
+
if (Array.isArray(remoteRule)) {
|
|
103
|
+
return remoteRule.some((prefix) => url.startsWith(prefix));
|
|
104
|
+
}
|
|
105
|
+
if (remoteRule instanceof RegExp) {
|
|
106
|
+
return remoteRule.test(url);
|
|
107
|
+
}
|
|
108
|
+
if (typeof remoteRule === "function") {
|
|
109
|
+
return remoteRule(url);
|
|
110
|
+
}
|
|
111
|
+
return false;
|
|
112
|
+
}
|
|
113
|
+
function shouldUseLocal(url, normalizedStaticPrefix, remotePrefixes) {
|
|
114
|
+
const pathname = url.split("?")[0];
|
|
115
|
+
const isLocalResource = normalizedStaticPrefix && url.startsWith(normalizedStaticPrefix) || url.startsWith("/@") || url.startsWith("/src") || url.startsWith("/node_modules") || url.includes(".hot-update.") || url.startsWith("/sockjs-node") || // Vue CLI/Webpack 热更新 WebSocket (3.x)
|
|
116
|
+
url.startsWith("/ws") || // Vue CLI/Webpack 热更新 WebSocket (4.x+/Vite)
|
|
117
|
+
url === "/" || BYPASS_REGEX.test(pathname);
|
|
118
|
+
const isRemoteResource = matchesRemoteResource(url, remotePrefixes);
|
|
119
|
+
return isLocalResource && !isRemoteResource;
|
|
120
|
+
}
|
|
121
|
+
function handleRedirect(proxyRes, req, res, appHost, log2, startTime) {
|
|
122
|
+
const redirectUrl = proxyRes.headers.location;
|
|
123
|
+
const host = req.headers.host;
|
|
124
|
+
const regex = new RegExp(appHost, "gi");
|
|
125
|
+
let location = redirectUrl.replace(regex, host || "");
|
|
126
|
+
location = location.replace(HTTPS_TO_HTTP_REGEX, "http://$1$2");
|
|
127
|
+
const headers = rewriteCookies({ ...proxyRes.headers }, log2);
|
|
128
|
+
headers.location = location;
|
|
129
|
+
res.writeHead(proxyRes.statusCode, headers);
|
|
130
|
+
res.end();
|
|
131
|
+
log2(
|
|
132
|
+
`Redirect handled: ${redirectUrl} -> ${location} (${Date.now() - startTime}ms)`
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
function handleLibModeHtml(localIndexHtml, res, log2, logError, startTime) {
|
|
136
|
+
try {
|
|
137
|
+
const indexHtml = fs.readFileSync(
|
|
138
|
+
resolve(__dirname, localIndexHtml),
|
|
139
|
+
"utf-8"
|
|
140
|
+
);
|
|
141
|
+
res.writeHead(200, {
|
|
142
|
+
"Content-Type": "text/html; charset=utf-8"
|
|
143
|
+
});
|
|
144
|
+
res.end(indexHtml);
|
|
145
|
+
log2(`Local HTML served: ${localIndexHtml} (${Date.now() - startTime}ms)`);
|
|
146
|
+
} catch (err) {
|
|
147
|
+
logError("Failed to read local HTML:", err);
|
|
148
|
+
res.writeHead(500);
|
|
149
|
+
res.end("Failed to read local HTML");
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
function handleHtmlResponse(proxyRes, req, res, context) {
|
|
153
|
+
const encoding = proxyRes.headers["content-encoding"];
|
|
154
|
+
const requestUrl = req.url || "";
|
|
155
|
+
const chunks = [];
|
|
156
|
+
proxyRes.on("data", (chunk) => {
|
|
157
|
+
if (chunk) {
|
|
158
|
+
chunks.push(chunk);
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
proxyRes.on("end", () => {
|
|
162
|
+
try {
|
|
163
|
+
const buffer = Buffer.concat(chunks);
|
|
164
|
+
const decompressed = decompressBuffer(buffer, encoding);
|
|
165
|
+
let html = decompressed.toString("utf-8");
|
|
166
|
+
html = injectEntryScript(
|
|
167
|
+
html,
|
|
168
|
+
context.fullEntry,
|
|
169
|
+
context.developmentAgentOccupancy
|
|
170
|
+
);
|
|
171
|
+
html = clearScriptCssTags(
|
|
172
|
+
html,
|
|
173
|
+
context.clearScriptCssPrefixes,
|
|
174
|
+
context.log
|
|
175
|
+
);
|
|
176
|
+
const headers = rewriteCookies({ ...proxyRes.headers }, context.log);
|
|
177
|
+
headers["content-type"] = "text/html; charset=utf-8";
|
|
178
|
+
delete headers["content-encoding"];
|
|
179
|
+
delete headers["content-length"];
|
|
180
|
+
res.writeHead(200, headers);
|
|
181
|
+
res.end(html);
|
|
182
|
+
context.log(
|
|
183
|
+
`[HTML processed]: ${requestUrl} (${Date.now() - context.startTime}ms)`
|
|
184
|
+
);
|
|
185
|
+
} catch (err) {
|
|
186
|
+
context.logError("Decompress error:", err);
|
|
187
|
+
context.logError("Request URL:", requestUrl);
|
|
188
|
+
context.logError("Response headers:", proxyRes.headers);
|
|
189
|
+
res.writeHead(500);
|
|
190
|
+
res.end("Decompress error");
|
|
191
|
+
}
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
function validateOptions(options, pluginName) {
|
|
195
|
+
const { appHost } = options;
|
|
196
|
+
if (!appHost) {
|
|
197
|
+
throw new Error(`${pluginName}: appHost is required`);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
function processOptions(options, isVite) {
|
|
201
|
+
let {
|
|
7
202
|
https = true,
|
|
8
203
|
appHost = "",
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
bypassPrefixes = ["/static"],
|
|
13
|
-
// scriptCssPrefix = "",
|
|
204
|
+
localIndexHtml = "",
|
|
205
|
+
staticPrefix = "/dev/static",
|
|
206
|
+
remotePrefixes = ["/static/component"],
|
|
14
207
|
developmentAgentOccupancy = "",
|
|
15
208
|
clearScriptCssPrefixes = "",
|
|
16
|
-
entry = "/src/main.js",
|
|
209
|
+
entry = isVite ? "/src/main.js" : ["/js/chunk-vendors.js", "/js/app.js"],
|
|
17
210
|
debug = false
|
|
18
211
|
} = options;
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
212
|
+
appHost = normalizePath(appHost);
|
|
213
|
+
const normalizedStaticPrefix = normalizePath(staticPrefix);
|
|
214
|
+
const fullEntry = generateEntryScript(entry, normalizedStaticPrefix);
|
|
215
|
+
const { log: log2, logError } = createLogger(debug);
|
|
216
|
+
return {
|
|
217
|
+
https,
|
|
218
|
+
appHost,
|
|
219
|
+
localIndexHtml,
|
|
220
|
+
normalizedStaticPrefix,
|
|
221
|
+
remotePrefixes,
|
|
222
|
+
developmentAgentOccupancy,
|
|
223
|
+
clearScriptCssPrefixes,
|
|
224
|
+
fullEntry,
|
|
225
|
+
log: log2,
|
|
226
|
+
logError
|
|
28
227
|
};
|
|
29
|
-
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
function createProxyConfig(options) {
|
|
231
|
+
validateOptions(options, "vite-plugin-dev-proxy");
|
|
232
|
+
const {
|
|
233
|
+
https,
|
|
234
|
+
appHost,
|
|
235
|
+
localIndexHtml,
|
|
236
|
+
normalizedStaticPrefix,
|
|
237
|
+
remotePrefixes,
|
|
238
|
+
developmentAgentOccupancy,
|
|
239
|
+
clearScriptCssPrefixes,
|
|
240
|
+
fullEntry,
|
|
241
|
+
log,
|
|
242
|
+
logError
|
|
243
|
+
} = processOptions(options, true);
|
|
30
244
|
log("vite-plugin-dev-proxy: staticPrefix", normalizedStaticPrefix);
|
|
31
|
-
const
|
|
32
|
-
const scriptLinkRegex = /<(?:script[^>]*>.*?<\/script>|link[^>]*>)/g;
|
|
33
|
-
const assetRegex = /\.(js|mjs|ts|tsx|jsx|css|scss|sass|less|vue|json|woff2?|ttf|eot|ico|png|jpe?g|gif|svg|webp)(\?.*)?$/i;
|
|
34
|
-
const staticPathRegex = /^\/(static|assets|public|images|css|js)\//i;
|
|
35
|
-
const bypassRegex = /\.(vue|js|mjs|ts|tsx|jsx|css|scss|sass|less|json|png|jpe?g|gif|svg|webp|ico|woff2?|ttf|eot)$/i;
|
|
245
|
+
const protocol = https ? "https://" : "http://";
|
|
36
246
|
return {
|
|
37
247
|
"/": {
|
|
38
|
-
target: `${
|
|
248
|
+
target: `${protocol}${appHost}`,
|
|
39
249
|
changeOrigin: true,
|
|
40
250
|
secure: false,
|
|
41
251
|
cookieDomainRewrite: { "*": "localhost" },
|
|
42
252
|
selfHandleResponse: true,
|
|
43
|
-
configure: (proxy,
|
|
44
|
-
const rewriteCookies = (headers) => {
|
|
45
|
-
const setCookie = headers["set-cookie"];
|
|
46
|
-
if (setCookie) {
|
|
47
|
-
headers["set-cookie"] = setCookie.map((cookie) => {
|
|
48
|
-
let rewrittenCookie = cookie.replace(/;\s*secure\s*(;|$)/gi, "$1").replace(/;\s*domain\s*=[^;]+(;|$)/gi, "$1").replace(/;\s*samesite\s*=[^;]+(;|$)/gi, "$1").replace(/;+/g, ";").replace(/;\s*$/g, "");
|
|
49
|
-
log("vite-plugin-dev-proxy: rewrittenCookie", rewrittenCookie);
|
|
50
|
-
return rewrittenCookie;
|
|
51
|
-
});
|
|
52
|
-
}
|
|
53
|
-
return headers;
|
|
54
|
-
};
|
|
253
|
+
configure: (proxy, _options) => {
|
|
55
254
|
proxy.on(
|
|
56
255
|
"proxyRes",
|
|
57
256
|
(proxyRes, req, res) => {
|
|
58
257
|
const startTime = Date.now();
|
|
59
258
|
const contentType = proxyRes.headers["content-type"] || "";
|
|
60
|
-
const redirectUrl = proxyRes.headers.location;
|
|
61
259
|
const requestUrl = req.url || "";
|
|
62
260
|
const acceptHeader = req.headers.accept || "";
|
|
63
|
-
const isRedirect = proxyRes
|
|
261
|
+
const isRedirect = isRedirectResponse(proxyRes);
|
|
64
262
|
if (isRedirect) {
|
|
65
|
-
|
|
66
|
-
const regex = new RegExp(appHost, "gi");
|
|
67
|
-
let location = redirectUrl.replace(regex, host || "");
|
|
68
|
-
location = location.replace(
|
|
69
|
-
/https:\/\/(localhost|127\.0\.0\.1)(:\d+)?/gi,
|
|
70
|
-
"http://$1$2"
|
|
71
|
-
);
|
|
72
|
-
const headers2 = rewriteCookies({ ...proxyRes.headers });
|
|
73
|
-
headers2.location = location;
|
|
74
|
-
res.writeHead(proxyRes.statusCode, headers2);
|
|
75
|
-
res.end();
|
|
76
|
-
log(
|
|
77
|
-
`Redirect handled: ${redirectUrl} -> ${location} (${Date.now() - startTime}ms)`
|
|
78
|
-
);
|
|
263
|
+
handleRedirect(proxyRes, req, res, appHost, log, startTime);
|
|
79
264
|
return;
|
|
80
265
|
}
|
|
81
|
-
const
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
266
|
+
const shouldProcessHtml = shouldProcessAsHtml(
|
|
267
|
+
contentType,
|
|
268
|
+
acceptHeader,
|
|
269
|
+
requestUrl,
|
|
270
|
+
isRedirect
|
|
271
|
+
);
|
|
85
272
|
if (shouldProcessHtml) {
|
|
86
|
-
if (
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
});
|
|
95
|
-
res.end(indexHtml);
|
|
96
|
-
log(
|
|
97
|
-
`Local HTML served: ${localIndexHtml}\uFF09 (${Date.now() - startTime}ms)`
|
|
98
|
-
);
|
|
99
|
-
} catch (err) {
|
|
100
|
-
logError("Failed to read local HTML:", err);
|
|
101
|
-
res.writeHead(500);
|
|
102
|
-
res.end("Failed to read local HTML");
|
|
103
|
-
}
|
|
273
|
+
if (localIndexHtml) {
|
|
274
|
+
handleLibModeHtml(
|
|
275
|
+
localIndexHtml,
|
|
276
|
+
res,
|
|
277
|
+
log,
|
|
278
|
+
logError,
|
|
279
|
+
startTime
|
|
280
|
+
);
|
|
104
281
|
return;
|
|
105
282
|
}
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
);
|
|
116
|
-
proxyRes.on("end", () => {
|
|
117
|
-
try {
|
|
118
|
-
let buffer = Buffer.concat(chunks);
|
|
119
|
-
const decompress = () => {
|
|
120
|
-
if (encoding === "gzip") {
|
|
121
|
-
return zlib.gunzipSync(buffer);
|
|
122
|
-
} else if (encoding === "deflate") {
|
|
123
|
-
return zlib.inflateSync(buffer);
|
|
124
|
-
} else if (encoding === "br") {
|
|
125
|
-
return zlib.brotliDecompressSync(buffer);
|
|
126
|
-
}
|
|
127
|
-
return buffer;
|
|
128
|
-
};
|
|
129
|
-
const decompressed = decompress();
|
|
130
|
-
let html = decompressed.toString("utf-8");
|
|
131
|
-
if (developmentAgentOccupancy) {
|
|
132
|
-
html = html.replace(
|
|
133
|
-
developmentAgentOccupancy,
|
|
134
|
-
`<script crossorigin type="module" src="${fullEntry}"><\/script>`
|
|
135
|
-
);
|
|
136
|
-
} else {
|
|
137
|
-
html = html.replace(
|
|
138
|
-
/<div[^>]*id=["']app["'][^>]*><\/div>/g,
|
|
139
|
-
(match) => `${match}<script crossorigin type="module" src="${fullEntry}"><\/script>`
|
|
140
|
-
);
|
|
141
|
-
}
|
|
142
|
-
clearScriptCssPrefixes;
|
|
143
|
-
html = html.replace(scriptLinkRegex, (match) => {
|
|
144
|
-
const srcMatch = match.match(/src="([^"]+)"/i);
|
|
145
|
-
const hrefMatch = match.match(/href="([^"]+)"/i);
|
|
146
|
-
const srcOrHref = srcMatch ? srcMatch[1] : hrefMatch ? hrefMatch[1] : null;
|
|
147
|
-
if (typeof clearScriptCssPrefixes === "string") {
|
|
148
|
-
if (srcOrHref?.startsWith(clearScriptCssPrefixes)) {
|
|
149
|
-
return "";
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
if (Array.isArray(clearScriptCssPrefixes)) {
|
|
153
|
-
if (clearScriptCssPrefixes.some(
|
|
154
|
-
(prefix) => srcOrHref?.startsWith(prefix)
|
|
155
|
-
)) {
|
|
156
|
-
return "";
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
if (clearScriptCssPrefixes instanceof RegExp) {
|
|
160
|
-
return clearScriptCssPrefixes.test(match) ? "" : match;
|
|
161
|
-
}
|
|
162
|
-
if (typeof clearScriptCssPrefixes === "function") {
|
|
163
|
-
return clearScriptCssPrefixes(match) ? "" : match;
|
|
164
|
-
}
|
|
165
|
-
return match;
|
|
166
|
-
});
|
|
167
|
-
if (html.indexOf(fullEntry) === -1) {
|
|
168
|
-
html = html.replace(
|
|
169
|
-
/<!--\sS 公共组件 提示信息\s-->/g,
|
|
170
|
-
`<script crossorigin type="module" src="${fullEntry}"><\/script>`
|
|
171
|
-
);
|
|
172
|
-
}
|
|
173
|
-
const headers2 = rewriteCookies({ ...proxyRes.headers });
|
|
174
|
-
headers2["content-type"] = "text/html; charset=utf-8";
|
|
175
|
-
delete headers2["content-encoding"];
|
|
176
|
-
delete headers2["content-length"];
|
|
177
|
-
res.writeHead(200, headers2);
|
|
178
|
-
res.end(html);
|
|
179
|
-
log(
|
|
180
|
-
`HTML processed: ${requestUrl} (${Date.now() - startTime}ms)`
|
|
181
|
-
);
|
|
182
|
-
} catch (err) {
|
|
183
|
-
logError("Decompress error:", err);
|
|
184
|
-
logError("Request URL:", requestUrl);
|
|
185
|
-
logError("Response headers:", proxyRes.headers);
|
|
186
|
-
res.writeHead(500);
|
|
187
|
-
res.end("Decompress error");
|
|
188
|
-
}
|
|
283
|
+
handleHtmlResponse(proxyRes, req, res, {
|
|
284
|
+
fullEntry,
|
|
285
|
+
developmentAgentOccupancy,
|
|
286
|
+
clearScriptCssPrefixes,
|
|
287
|
+
log,
|
|
288
|
+
logError,
|
|
289
|
+
startTime
|
|
189
290
|
});
|
|
190
291
|
return;
|
|
191
292
|
}
|
|
192
|
-
const headers = rewriteCookies({ ...proxyRes.headers });
|
|
293
|
+
const headers = rewriteCookies({ ...proxyRes.headers }, log);
|
|
193
294
|
res.writeHead(proxyRes.statusCode, headers);
|
|
194
295
|
proxyRes.pipe(res);
|
|
195
|
-
log(`Proxy request
|
|
296
|
+
log(`[Proxy request] ${requestUrl} (${Date.now() - startTime}ms)`);
|
|
196
297
|
}
|
|
197
298
|
);
|
|
198
299
|
},
|
|
199
300
|
bypass: (req) => {
|
|
200
301
|
const url = req.url || "";
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
log(`Bypass proxy: ${url}`);
|
|
302
|
+
if (shouldUseLocal(url, normalizedStaticPrefix, remotePrefixes)) {
|
|
303
|
+
log(`shouldUseLocal: ${url}`);
|
|
204
304
|
return url;
|
|
205
305
|
}
|
|
206
306
|
}
|
|
@@ -229,4 +329,126 @@ function viteDevProxy(options = {}) {
|
|
|
229
329
|
};
|
|
230
330
|
}
|
|
231
331
|
|
|
232
|
-
|
|
332
|
+
function createVueCliProxyConfig(options) {
|
|
333
|
+
validateOptions(options, "vue-cli-plugin-dev-proxy");
|
|
334
|
+
const {
|
|
335
|
+
https,
|
|
336
|
+
appHost,
|
|
337
|
+
localIndexHtml,
|
|
338
|
+
normalizedStaticPrefix,
|
|
339
|
+
remotePrefixes,
|
|
340
|
+
developmentAgentOccupancy,
|
|
341
|
+
clearScriptCssPrefixes,
|
|
342
|
+
fullEntry,
|
|
343
|
+
log,
|
|
344
|
+
logError
|
|
345
|
+
} = processOptions(options);
|
|
346
|
+
log("vue-cli-plugin-dev-proxy: staticPrefix", normalizedStaticPrefix);
|
|
347
|
+
const protocol = https ? "https://" : "http://";
|
|
348
|
+
return {
|
|
349
|
+
"/": {
|
|
350
|
+
target: `${protocol}${appHost}`,
|
|
351
|
+
changeOrigin: true,
|
|
352
|
+
secure: false,
|
|
353
|
+
ws: false,
|
|
354
|
+
//5.x 版本需要设置,否则会报错 Invalid frame header,4.x 版本不需要设置
|
|
355
|
+
cookieDomainRewrite: { "*": "localhost" },
|
|
356
|
+
selfHandleResponse: true,
|
|
357
|
+
onProxyReq: (proxyReq, req, res) => {
|
|
358
|
+
const upgradeHeader = req.headers.upgrade;
|
|
359
|
+
const isWebSocket = upgradeHeader && upgradeHeader.toLowerCase() === "websocket";
|
|
360
|
+
if (isWebSocket) {
|
|
361
|
+
log(
|
|
362
|
+
`[WebSocket] ${req.method} ${req.url} -> ${protocol}${proxyReq.getHeader("host")}${proxyReq.path}`
|
|
363
|
+
);
|
|
364
|
+
} else {
|
|
365
|
+
log(
|
|
366
|
+
`[proxyReq] ${req.method} ${req.url} -> ${protocol}${proxyReq.getHeader("host")}${proxyReq.path}`
|
|
367
|
+
);
|
|
368
|
+
}
|
|
369
|
+
},
|
|
370
|
+
onError: (err, req, res) => {
|
|
371
|
+
const upgradeHeader = req.headers.upgrade;
|
|
372
|
+
const isWebSocket = upgradeHeader && upgradeHeader.toLowerCase() === "websocket";
|
|
373
|
+
if (isWebSocket) {
|
|
374
|
+
logError(`[WebSocket Error] ${req.url}:`, err.message);
|
|
375
|
+
} else {
|
|
376
|
+
logError(`[proxyError] ${req.url}:`, err.message);
|
|
377
|
+
}
|
|
378
|
+
if (!res.headersSent && !isWebSocket) {
|
|
379
|
+
res.writeHead(500, { "Content-Type": "text/plain" });
|
|
380
|
+
res.end("Proxy Error: " + err.message);
|
|
381
|
+
}
|
|
382
|
+
},
|
|
383
|
+
onProxyRes: (proxyRes, req, res) => {
|
|
384
|
+
const startTime = Date.now();
|
|
385
|
+
const contentType = proxyRes.headers["content-type"] || "";
|
|
386
|
+
const requestUrl = req.url || "";
|
|
387
|
+
const acceptHeader = req.headers.accept || "";
|
|
388
|
+
log(
|
|
389
|
+
`[proxyRes] ${requestUrl} - Status: ${proxyRes.statusCode}, ContentType: ${contentType}`
|
|
390
|
+
);
|
|
391
|
+
const isRedirect = isRedirectResponse(proxyRes);
|
|
392
|
+
if (isRedirect) {
|
|
393
|
+
handleRedirect(proxyRes, req, res, appHost, log, startTime);
|
|
394
|
+
return;
|
|
395
|
+
}
|
|
396
|
+
const shouldProcessHtml = shouldProcessAsHtml(
|
|
397
|
+
contentType,
|
|
398
|
+
acceptHeader,
|
|
399
|
+
requestUrl,
|
|
400
|
+
isRedirect
|
|
401
|
+
);
|
|
402
|
+
log(
|
|
403
|
+
`[shouldProcessHtml] ${shouldProcessHtml}, requestUrl: ${requestUrl}`
|
|
404
|
+
);
|
|
405
|
+
if (shouldProcessHtml) {
|
|
406
|
+
if (localIndexHtml) {
|
|
407
|
+
handleLibModeHtml(localIndexHtml, res, log, logError, startTime);
|
|
408
|
+
return;
|
|
409
|
+
}
|
|
410
|
+
handleHtmlResponse(proxyRes, req, res, {
|
|
411
|
+
fullEntry,
|
|
412
|
+
developmentAgentOccupancy,
|
|
413
|
+
clearScriptCssPrefixes,
|
|
414
|
+
log,
|
|
415
|
+
logError,
|
|
416
|
+
startTime
|
|
417
|
+
});
|
|
418
|
+
return;
|
|
419
|
+
}
|
|
420
|
+
const headers = rewriteCookies({ ...proxyRes.headers }, log);
|
|
421
|
+
res.writeHead(proxyRes.statusCode, headers);
|
|
422
|
+
proxyRes.pipe(res);
|
|
423
|
+
log(`[Proxy request]: ${requestUrl} (${Date.now() - startTime}ms)`);
|
|
424
|
+
},
|
|
425
|
+
bypass: (req) => {
|
|
426
|
+
const url = req.url || "";
|
|
427
|
+
if (shouldUseLocal(url, normalizedStaticPrefix, remotePrefixes)) {
|
|
428
|
+
log(`[shouldUseLocal] ${url}`);
|
|
429
|
+
return url;
|
|
430
|
+
}
|
|
431
|
+
log(`[Proxy] ${url}`);
|
|
432
|
+
return null;
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
};
|
|
436
|
+
}
|
|
437
|
+
function vueCliDevProxy(options = {}) {
|
|
438
|
+
if (process.env.NODE_ENV !== "development") {
|
|
439
|
+
return {};
|
|
440
|
+
}
|
|
441
|
+
const proxyConfig = createVueCliProxyConfig(options);
|
|
442
|
+
return {
|
|
443
|
+
devServer: {
|
|
444
|
+
proxy: proxyConfig
|
|
445
|
+
}
|
|
446
|
+
};
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
const index = {
|
|
450
|
+
VitePluginDevProxy: viteDevProxy,
|
|
451
|
+
VueCliPluginDevProxy: vueCliDevProxy
|
|
452
|
+
};
|
|
453
|
+
|
|
454
|
+
export { viteDevProxy as VitePluginDevProxy, vueCliDevProxy as VueCliPluginDevProxy, createVueCliProxyConfig, index as default };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xysfe/vite-plugin-dev-proxy",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.4",
|
|
4
4
|
"description": "A Vite plugin for development environment proxy that automatically proxies remote server requests and handles HTML responses",
|
|
5
5
|
"files": [
|
|
6
6
|
"dist"
|
|
@@ -51,6 +51,5 @@
|
|
|
51
51
|
"typescript": "^5.3.3",
|
|
52
52
|
"unbuild": "^2.0.0",
|
|
53
53
|
"vite": "^5.0.12"
|
|
54
|
-
}
|
|
55
|
-
"packageManager": "pnpm"
|
|
54
|
+
}
|
|
56
55
|
}
|