@xysfe/vite-plugin-dev-proxy 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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 aiwa
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,193 @@
1
+ # vite-plugin-dev-proxy
2
+
3
+ A Vite plugin for development environment proxy that automatically proxies remote server requests and handles HTML responses.
4
+
5
+ ## Features
6
+
7
+ - πŸš€ Automatically proxy remote server requests
8
+ - πŸ“¦ Smart HTML response handling, replacing remote scripts and stylesheets with local development versions
9
+ - πŸͺ Automatic cookie rewriting, removing Secure and Domain attributes
10
+ - πŸ”€ Redirect handling with protocol mismatch fixes
11
+ - βš™οΈ Support for custom static resource prefixes
12
+ - πŸ› Debug logging support
13
+ - πŸ”— Merge with other proxy configurations
14
+ - πŸ”§ TypeScript support with full type definitions
15
+
16
+ ## Installation
17
+
18
+ ```bash
19
+ npm install vite-plugin-dev-proxy --save-dev
20
+ ```
21
+
22
+ ```bash
23
+ yarn add vite-plugin-dev-proxy --dev
24
+ ```
25
+
26
+ ```bash
27
+ pnpm add vite-plugin-dev-proxy --save-dev
28
+ ```
29
+
30
+ ## Usage
31
+
32
+ ### Basic Usage
33
+
34
+ ```js
35
+ // vite.config.js
36
+ import { defineConfig } from "vite";
37
+ import viteDevProxy from "vite-plugin-dev-proxy";
38
+
39
+ export default defineConfig({
40
+ plugins: [
41
+ viteDevProxy({
42
+ appHost: "example.com",
43
+ }),
44
+ ],
45
+ });
46
+ ```
47
+
48
+ ### Full Configuration
49
+
50
+ ```js
51
+ // vite.config.js
52
+ import { defineConfig } from "vite";
53
+ import viteDevProxy from "vite-plugin-dev-proxy";
54
+
55
+ export default defineConfig({
56
+ plugins: [
57
+ viteDevProxy({
58
+ appHost: "example.com",
59
+ https: true,
60
+ staticPrefix: "/dev/static",
61
+ bypassPrefixes: ["/static"],
62
+ scriptCssPrefix: "/static/global",
63
+ entry: "/src/main.js",
64
+ debug: true,
65
+ }),
66
+ ],
67
+ });
68
+ ```
69
+
70
+ ### Coexisting with Other Proxy Configurations
71
+
72
+ ```js
73
+ // vite.config.js
74
+ export default defineConfig({
75
+ plugins: [
76
+ viteDevProxy({
77
+ appHost: "example.com",
78
+ }),
79
+ ],
80
+ server: {
81
+ proxy: {
82
+ "/api": {
83
+ target: "http://localhost:8080",
84
+ },
85
+ },
86
+ },
87
+ });
88
+ ```
89
+
90
+ ## API
91
+
92
+ ### `viteDevProxy(options?)`
93
+
94
+ #### Options
95
+
96
+ | Parameter | Type | Default | Required | Description |
97
+ | ----------------- | ---------- | ---------------- | -------- | ----------------------------------------------------------------------------------------------- |
98
+ | `appHost` | `string` | - | βœ… | Target server address |
99
+ | `https` | `boolean` | `true` | - | Whether to use HTTPS for the target server |
100
+ | `staticPrefix` | `string` | `''` | - | Static resource prefix, used to build local entry path |
101
+ | `bypassPrefixes` | `string[]` | `['/static']` | - | List of prefixes to bypass proxy, requests matching these prefixes will access remote resources |
102
+ | `scriptCssPrefix` | `string` | `''` | - | Script/CSS prefix, used to precisely match remote scripts and stylesheets to remove |
103
+ | `entry` | `string` | `'/src/main.js'` | - | Local development entry file path |
104
+ | `isLib` | `boolean` | `false` | - | Whether in component library mode, returns local HTML file when true |
105
+ | `localIndexHtml` | `string` | `'index.html'` | - | Local HTML file path (only used when isLib=true) |
106
+ | `debug` | `boolean` | `false` | - | Whether to enable debug logging |
107
+
108
+ ## How It Works
109
+
110
+ ### 1. Proxy Configuration
111
+
112
+ The plugin injects `server.proxy` configuration through Vite's `config` hook, proxying all requests to the target server.
113
+
114
+ ### 2. HTML Processing
115
+
116
+ When detecting a browser page navigation request and the response is HTML:
117
+
118
+ - Removes remote `type="module" crossorigin` script tags
119
+ - Removes remote `crossorigin` stylesheet link tags
120
+ - Inserts local development script entry
121
+
122
+ ### 3. Cookie Rewriting
123
+
124
+ Automatically removes `Secure`, `Domain`, and `SameSite` attributes from cookies to ensure proper functioning in the local development environment.
125
+
126
+ ### 4. Redirect Handling
127
+
128
+ - Replaces remote domain names in redirect URLs with local domain names
129
+ - Fixes protocol mismatch issues (https://localhost -> http://localhost)
130
+
131
+ ## Debugging
132
+
133
+ Enable the `debug` option to view detailed logs:
134
+
135
+ ```js
136
+ viteDevProxy({
137
+ appHost: "example.com",
138
+ debug: true,
139
+ });
140
+ ```
141
+
142
+ Log output example:
143
+
144
+ ```
145
+ vite-plugin-dev-proxy: staticPrefix /dev/static
146
+ vite-plugin-dev-proxy: scriptCssPrefix /static/global
147
+ Proxy request: /admin/index (5ms)
148
+ HTML processed: /admin/index (23ms)
149
+ Bypass proxy: /static/js/app.js
150
+ Redirect handled: https://example.com/login -> http://localhost:3003/login (3ms)
151
+ ```
152
+
153
+ ## TypeScript Support
154
+
155
+ This plugin is written in TypeScript and provides full type definitions. You can import types for better development experience:
156
+
157
+ ```typescript
158
+ import viteDevProxy, { ProxyOptions } from "vite-plugin-dev-proxy";
159
+
160
+ const config: ProxyOptions = {
161
+ appHost: "example.com",
162
+ https: true,
163
+ debug: true,
164
+ };
165
+
166
+ export default defineConfig({
167
+ plugins: [viteDevProxy(config)],
168
+ });
169
+ ```
170
+
171
+ ## Notes
172
+
173
+ 1. `appHost` is a required parameter, not providing it will throw an error
174
+ 2. The plugin will override `server.proxy` configuration in `vite.config.js`
175
+ 3. Ensure the local development server port matches the port in redirect handling
176
+ 4. Use `scriptCssPrefix` to precisely control which remote scripts and stylesheets to remove
177
+
178
+ ## Requirements
179
+
180
+ - Vite 5.0+
181
+ - Node.js 18+
182
+
183
+ ## License
184
+
185
+ MIT Β© [aiwa](https://cnlhb.github.io/blog/)
186
+
187
+ ## Contributing
188
+
189
+ Contributions are welcome! Please feel free to submit a Pull Request.
190
+
191
+ ## Issues
192
+
193
+ If you encounter any issues, please report them on [GitHub Issues](https://github.com/CNLHB/vite-plugin-dev-proxy/issues).
package/dist/index.cjs ADDED
@@ -0,0 +1,236 @@
1
+ 'use strict';
2
+
3
+ const zlib = require('zlib');
4
+ const path = require('path');
5
+ const fs = require('fs');
6
+
7
+ function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e.default : e; }
8
+
9
+ const zlib__default = /*#__PURE__*/_interopDefaultCompat(zlib);
10
+ const fs__default = /*#__PURE__*/_interopDefaultCompat(fs);
11
+
12
+ function createProxyConfig(options) {
13
+ const {
14
+ https = true,
15
+ appHost = "",
16
+ isLib = false,
17
+ localIndexHtml = "index.html",
18
+ staticPrefix = "",
19
+ bypassPrefixes = ["/static"],
20
+ // scriptCssPrefix = "",
21
+ developmentAgentOccupancy = "",
22
+ clearScriptCssPrefixes = "",
23
+ entry = "/src/main.js",
24
+ debug = false
25
+ } = options;
26
+ if (!appHost) {
27
+ throw new Error("vite-plugin-dev-proxy: appHost is required");
28
+ }
29
+ if (!Array.isArray(bypassPrefixes)) {
30
+ throw new Error("vite-plugin-dev-proxy: bypassPrefixes must be an array");
31
+ }
32
+ const log = debug ? console.log : () => {
33
+ };
34
+ const logError = debug ? console.error : () => {
35
+ };
36
+ const normalizedStaticPrefix = staticPrefix.endsWith("/") ? staticPrefix.slice(0, -1) : staticPrefix;
37
+ log("vite-plugin-dev-proxy: staticPrefix", normalizedStaticPrefix);
38
+ const fullEntry = normalizedStaticPrefix + entry;
39
+ const scriptLinkRegex = /<(?:script[^>]*>.*?<\/script>|link[^>]*>)/g;
40
+ const assetRegex = /\.(js|mjs|ts|tsx|jsx|css|scss|sass|less|vue|json|woff2?|ttf|eot|ico|png|jpe?g|gif|svg|webp)(\?.*)?$/i;
41
+ const staticPathRegex = /^\/(static|assets|public|images|css|js)\//i;
42
+ const bypassRegex = /\.(vue|js|mjs|ts|tsx|jsx|css|scss|sass|less|json|png|jpe?g|gif|svg|webp|ico|woff2?|ttf|eot)$/i;
43
+ return {
44
+ "/": {
45
+ target: `${https ? "https" : "http"}://${appHost}`,
46
+ changeOrigin: true,
47
+ secure: false,
48
+ cookieDomainRewrite: { "*": "localhost" },
49
+ selfHandleResponse: true,
50
+ configure: (proxy, options2) => {
51
+ const rewriteCookies = (headers) => {
52
+ const setCookie = headers["set-cookie"];
53
+ if (setCookie) {
54
+ headers["set-cookie"] = setCookie.map((cookie) => {
55
+ 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, "");
56
+ log("vite-plugin-dev-proxy: rewrittenCookie", rewrittenCookie);
57
+ return rewrittenCookie;
58
+ });
59
+ }
60
+ return headers;
61
+ };
62
+ proxy.on(
63
+ "proxyRes",
64
+ (proxyRes, req, res) => {
65
+ const startTime = Date.now();
66
+ const contentType = proxyRes.headers["content-type"] || "";
67
+ const redirectUrl = proxyRes.headers.location;
68
+ const requestUrl = req.url || "";
69
+ const acceptHeader = req.headers.accept || "";
70
+ const isRedirect = proxyRes.statusCode >= 300 && proxyRes.statusCode < 400 && redirectUrl;
71
+ if (isRedirect) {
72
+ const host = req.headers.host;
73
+ const regex = new RegExp(appHost, "gi");
74
+ let location = redirectUrl.replace(regex, host || "");
75
+ location = location.replace(
76
+ /https:\/\/(localhost|127\.0\.0\.1)(:\d+)?/gi,
77
+ "http://$1$2"
78
+ );
79
+ const headers2 = rewriteCookies({ ...proxyRes.headers });
80
+ headers2.location = location;
81
+ res.writeHead(proxyRes.statusCode, headers2);
82
+ res.end();
83
+ log(
84
+ `Redirect handled: ${redirectUrl} -> ${location} (${Date.now() - startTime}ms)`
85
+ );
86
+ return;
87
+ }
88
+ const isNavigationRequest = acceptHeader.includes("text/html");
89
+ const isAssetRequest = assetRegex.test(requestUrl);
90
+ const isStaticPath = staticPathRegex.test(requestUrl);
91
+ const shouldProcessHtml = contentType.includes("text/html") && isNavigationRequest && !isAssetRequest && !isStaticPath && !isRedirect;
92
+ if (shouldProcessHtml) {
93
+ if (isLib) {
94
+ try {
95
+ const indexHtml = fs__default.readFileSync(
96
+ path.resolve(__dirname, localIndexHtml),
97
+ "utf-8"
98
+ );
99
+ res.writeHead(200, {
100
+ "Content-Type": "text/html; charset=utf-8"
101
+ });
102
+ res.end(indexHtml);
103
+ log(
104
+ `Local HTML served: ${localIndexHtml}\uFF09 (${Date.now() - startTime}ms)`
105
+ );
106
+ } catch (err) {
107
+ logError("Failed to read local HTML:", err);
108
+ res.writeHead(500);
109
+ res.end("Failed to read local HTML");
110
+ }
111
+ return;
112
+ }
113
+ const encoding = proxyRes.headers["content-encoding"];
114
+ const chunks = [];
115
+ proxyRes.on(
116
+ "data",
117
+ (chunk) => {
118
+ if (chunk) {
119
+ chunks.push(chunk);
120
+ }
121
+ }
122
+ );
123
+ proxyRes.on("end", () => {
124
+ try {
125
+ let buffer = Buffer.concat(chunks);
126
+ const decompress = () => {
127
+ if (encoding === "gzip") {
128
+ return zlib__default.gunzipSync(buffer);
129
+ } else if (encoding === "deflate") {
130
+ return zlib__default.inflateSync(buffer);
131
+ } else if (encoding === "br") {
132
+ return zlib__default.brotliDecompressSync(buffer);
133
+ }
134
+ return buffer;
135
+ };
136
+ const decompressed = decompress();
137
+ let html = decompressed.toString("utf-8");
138
+ if (developmentAgentOccupancy) {
139
+ html = html.replace(
140
+ developmentAgentOccupancy,
141
+ `<script crossorigin type="module" src="${fullEntry}"><\/script>`
142
+ );
143
+ } else {
144
+ html = html.replace(
145
+ /<div[^>]*id=["']app["'][^>]*><\/div>/g,
146
+ (match) => `${match}<script crossorigin type="module" src="${fullEntry}"><\/script>`
147
+ );
148
+ }
149
+ clearScriptCssPrefixes;
150
+ html = html.replace(scriptLinkRegex, (match) => {
151
+ const srcMatch = match.match(/src="([^"]+)"/i);
152
+ const hrefMatch = match.match(/href="([^"]+)"/i);
153
+ const srcOrHref = srcMatch ? srcMatch[1] : hrefMatch ? hrefMatch[1] : null;
154
+ if (typeof clearScriptCssPrefixes === "string") {
155
+ if (srcOrHref?.startsWith(clearScriptCssPrefixes)) {
156
+ return "";
157
+ }
158
+ }
159
+ if (Array.isArray(clearScriptCssPrefixes)) {
160
+ if (clearScriptCssPrefixes.some(
161
+ (prefix) => srcOrHref?.startsWith(prefix)
162
+ )) {
163
+ return "";
164
+ }
165
+ }
166
+ if (clearScriptCssPrefixes instanceof RegExp) {
167
+ return clearScriptCssPrefixes.test(match) ? "" : match;
168
+ }
169
+ if (typeof clearScriptCssPrefixes === "function") {
170
+ return clearScriptCssPrefixes(match) ? "" : match;
171
+ }
172
+ return match;
173
+ });
174
+ if (html.indexOf(fullEntry) === -1) {
175
+ html = html.replace(
176
+ /<!--\sS ε…¬ε…±η»„δ»Ά 提瀺俑息\s-->/g,
177
+ `<script crossorigin type="module" src="${fullEntry}"><\/script>`
178
+ );
179
+ }
180
+ const headers2 = rewriteCookies({ ...proxyRes.headers });
181
+ headers2["content-type"] = "text/html; charset=utf-8";
182
+ delete headers2["content-encoding"];
183
+ delete headers2["content-length"];
184
+ res.writeHead(200, headers2);
185
+ res.end(html);
186
+ log(
187
+ `HTML processed: ${requestUrl} (${Date.now() - startTime}ms)`
188
+ );
189
+ } catch (err) {
190
+ logError("Decompress error:", err);
191
+ logError("Request URL:", requestUrl);
192
+ logError("Response headers:", proxyRes.headers);
193
+ res.writeHead(500);
194
+ res.end("Decompress error");
195
+ }
196
+ });
197
+ return;
198
+ }
199
+ const headers = rewriteCookies({ ...proxyRes.headers });
200
+ res.writeHead(proxyRes.statusCode, headers);
201
+ proxyRes.pipe(res);
202
+ log(`Proxy request: ${requestUrl} (${Date.now() - startTime}ms)`);
203
+ }
204
+ );
205
+ },
206
+ bypass: (req) => {
207
+ const url = req.url || "";
208
+ const pathname = url.split("?")[0];
209
+ if ((normalizedStaticPrefix && url.startsWith(`${normalizedStaticPrefix}`) || url.startsWith("/@") || url.startsWith("/src") || url.startsWith("/node_modules") || url.includes(".hot-update.") || url === "/" || bypassRegex.test(pathname)) && !bypassPrefixes.some((prefix) => url.startsWith(prefix))) {
210
+ log(`Bypass proxy: ${url}`);
211
+ return url;
212
+ }
213
+ }
214
+ }
215
+ };
216
+ }
217
+ function viteDevProxy(options = {}) {
218
+ return {
219
+ name: "vite-plugin-dev-proxy",
220
+ config: (viteConfig) => {
221
+ const pluginProxy = createProxyConfig(options);
222
+ const existingProxy = viteConfig.server?.proxy || {};
223
+ const mergedProxy = {
224
+ ...existingProxy,
225
+ ...pluginProxy
226
+ };
227
+ return {
228
+ server: {
229
+ proxy: mergedProxy
230
+ }
231
+ };
232
+ }
233
+ };
234
+ }
235
+
236
+ module.exports = viteDevProxy;
@@ -0,0 +1,17 @@
1
+ import { Plugin } from 'vite';
2
+
3
+ interface ProxyOptions {
4
+ https?: boolean;
5
+ appHost?: string;
6
+ isLib?: boolean;
7
+ localIndexHtml?: string;
8
+ staticPrefix?: string;
9
+ bypassPrefixes?: string[];
10
+ clearScriptCssPrefixes?: string | string[] | Function | RegExp;
11
+ developmentAgentOccupancy?: string;
12
+ entry?: string;
13
+ debug?: boolean;
14
+ }
15
+ declare function viteDevProxy(options?: ProxyOptions): Plugin;
16
+
17
+ export { viteDevProxy as default };
@@ -0,0 +1,17 @@
1
+ import { Plugin } from 'vite';
2
+
3
+ interface ProxyOptions {
4
+ https?: boolean;
5
+ appHost?: string;
6
+ isLib?: boolean;
7
+ localIndexHtml?: string;
8
+ staticPrefix?: string;
9
+ bypassPrefixes?: string[];
10
+ clearScriptCssPrefixes?: string | string[] | Function | RegExp;
11
+ developmentAgentOccupancy?: string;
12
+ entry?: string;
13
+ debug?: boolean;
14
+ }
15
+ declare function viteDevProxy(options?: ProxyOptions): Plugin;
16
+
17
+ export { viteDevProxy as default };
@@ -0,0 +1,17 @@
1
+ import { Plugin } from 'vite';
2
+
3
+ interface ProxyOptions {
4
+ https?: boolean;
5
+ appHost?: string;
6
+ isLib?: boolean;
7
+ localIndexHtml?: string;
8
+ staticPrefix?: string;
9
+ bypassPrefixes?: string[];
10
+ clearScriptCssPrefixes?: string | string[] | Function | RegExp;
11
+ developmentAgentOccupancy?: string;
12
+ entry?: string;
13
+ debug?: boolean;
14
+ }
15
+ declare function viteDevProxy(options?: ProxyOptions): Plugin;
16
+
17
+ export { viteDevProxy as default };
package/dist/index.mjs ADDED
@@ -0,0 +1,229 @@
1
+ import zlib from 'zlib';
2
+ import { resolve } from 'path';
3
+ import fs from 'fs';
4
+
5
+ function createProxyConfig(options) {
6
+ const {
7
+ https = true,
8
+ appHost = "",
9
+ isLib = false,
10
+ localIndexHtml = "index.html",
11
+ staticPrefix = "",
12
+ bypassPrefixes = ["/static"],
13
+ // scriptCssPrefix = "",
14
+ developmentAgentOccupancy = "",
15
+ clearScriptCssPrefixes = "",
16
+ entry = "/src/main.js",
17
+ debug = false
18
+ } = options;
19
+ if (!appHost) {
20
+ throw new Error("vite-plugin-dev-proxy: appHost is required");
21
+ }
22
+ if (!Array.isArray(bypassPrefixes)) {
23
+ throw new Error("vite-plugin-dev-proxy: bypassPrefixes must be an array");
24
+ }
25
+ const log = debug ? console.log : () => {
26
+ };
27
+ const logError = debug ? console.error : () => {
28
+ };
29
+ const normalizedStaticPrefix = staticPrefix.endsWith("/") ? staticPrefix.slice(0, -1) : staticPrefix;
30
+ log("vite-plugin-dev-proxy: staticPrefix", normalizedStaticPrefix);
31
+ const fullEntry = normalizedStaticPrefix + entry;
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;
36
+ return {
37
+ "/": {
38
+ target: `${https ? "https" : "http"}://${appHost}`,
39
+ changeOrigin: true,
40
+ secure: false,
41
+ cookieDomainRewrite: { "*": "localhost" },
42
+ selfHandleResponse: true,
43
+ configure: (proxy, options2) => {
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
+ };
55
+ proxy.on(
56
+ "proxyRes",
57
+ (proxyRes, req, res) => {
58
+ const startTime = Date.now();
59
+ const contentType = proxyRes.headers["content-type"] || "";
60
+ const redirectUrl = proxyRes.headers.location;
61
+ const requestUrl = req.url || "";
62
+ const acceptHeader = req.headers.accept || "";
63
+ const isRedirect = proxyRes.statusCode >= 300 && proxyRes.statusCode < 400 && redirectUrl;
64
+ if (isRedirect) {
65
+ const host = req.headers.host;
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
+ );
79
+ return;
80
+ }
81
+ const isNavigationRequest = acceptHeader.includes("text/html");
82
+ const isAssetRequest = assetRegex.test(requestUrl);
83
+ const isStaticPath = staticPathRegex.test(requestUrl);
84
+ const shouldProcessHtml = contentType.includes("text/html") && isNavigationRequest && !isAssetRequest && !isStaticPath && !isRedirect;
85
+ if (shouldProcessHtml) {
86
+ if (isLib) {
87
+ try {
88
+ const indexHtml = fs.readFileSync(
89
+ resolve(__dirname, localIndexHtml),
90
+ "utf-8"
91
+ );
92
+ res.writeHead(200, {
93
+ "Content-Type": "text/html; charset=utf-8"
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
+ }
104
+ return;
105
+ }
106
+ const encoding = proxyRes.headers["content-encoding"];
107
+ const chunks = [];
108
+ proxyRes.on(
109
+ "data",
110
+ (chunk) => {
111
+ if (chunk) {
112
+ chunks.push(chunk);
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
+ }
189
+ });
190
+ return;
191
+ }
192
+ const headers = rewriteCookies({ ...proxyRes.headers });
193
+ res.writeHead(proxyRes.statusCode, headers);
194
+ proxyRes.pipe(res);
195
+ log(`Proxy request: ${requestUrl} (${Date.now() - startTime}ms)`);
196
+ }
197
+ );
198
+ },
199
+ bypass: (req) => {
200
+ const url = req.url || "";
201
+ const pathname = url.split("?")[0];
202
+ if ((normalizedStaticPrefix && url.startsWith(`${normalizedStaticPrefix}`) || url.startsWith("/@") || url.startsWith("/src") || url.startsWith("/node_modules") || url.includes(".hot-update.") || url === "/" || bypassRegex.test(pathname)) && !bypassPrefixes.some((prefix) => url.startsWith(prefix))) {
203
+ log(`Bypass proxy: ${url}`);
204
+ return url;
205
+ }
206
+ }
207
+ }
208
+ };
209
+ }
210
+ function viteDevProxy(options = {}) {
211
+ return {
212
+ name: "vite-plugin-dev-proxy",
213
+ config: (viteConfig) => {
214
+ const pluginProxy = createProxyConfig(options);
215
+ const existingProxy = viteConfig.server?.proxy || {};
216
+ const mergedProxy = {
217
+ ...existingProxy,
218
+ ...pluginProxy
219
+ };
220
+ return {
221
+ server: {
222
+ proxy: mergedProxy
223
+ }
224
+ };
225
+ }
226
+ };
227
+ }
228
+
229
+ export { viteDevProxy as default };
package/package.json ADDED
@@ -0,0 +1,56 @@
1
+ {
2
+ "name": "@xysfe/vite-plugin-dev-proxy",
3
+ "version": "1.0.0",
4
+ "description": "A Vite plugin for development environment proxy that automatically proxies remote server requests and handles HTML responses",
5
+ "files": [
6
+ "dist"
7
+ ],
8
+ "type": "module",
9
+ "main": "./dist/index.mjs",
10
+ "module": "./dist/index.mjs",
11
+ "types": "./dist/index.d.ts",
12
+ "exports": {
13
+ ".": {
14
+ "require": {
15
+ "types": "./dist/index.d.cts",
16
+ "default": "./dist/index.cjs"
17
+ },
18
+ "import": {
19
+ "types": "./dist/index.d.mts",
20
+ "default": "./dist/index.mjs"
21
+ },
22
+ "types": "./dist/index.d.ts",
23
+ "default": "./dist/index.mjs"
24
+ }
25
+ },
26
+ "scripts": {
27
+ "build": "npx unbuild",
28
+ "prepack": "npm run build"
29
+ },
30
+ "repository": {
31
+ "type": "git",
32
+ "url": ""
33
+ },
34
+ "keywords": [
35
+ "vite",
36
+ "vite-plugin",
37
+ "proxy",
38
+ "dev-proxy",
39
+ "development",
40
+ "html-proxy",
41
+ "cookie",
42
+ "redirect"
43
+ ],
44
+ "author": "aiwa",
45
+ "license": "MIT",
46
+ "publishConfig": {
47
+ "access": "public"
48
+ },
49
+ "devDependencies": {
50
+ "@types/node": "^20.11.5",
51
+ "typescript": "^5.3.3",
52
+ "unbuild": "^2.0.0",
53
+ "vite": "^5.0.12"
54
+ },
55
+ "packageManager": "pnpm"
56
+ }