@adep/vite-plugin 0.1.2 → 0.1.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/dist/ide-proxy.d.ts +10 -2
- package/dist/index.js +68 -2
- package/package.json +1 -1
package/dist/ide-proxy.d.ts
CHANGED
|
@@ -5,7 +5,9 @@
|
|
|
5
5
|
*
|
|
6
6
|
* 背景:浏览器端 IIFE,patch window.fetch,把 /api/* 请求经 window.parent.postMessage
|
|
7
7
|
* 转发到 IDE 主线程(Nodebox 真 vite 预览路径);CLI 模式下脚本自检 window.parent ===
|
|
8
|
-
* window
|
|
8
|
+
* window 不激活,无害。新窗口打开预览(window.open)时 window.parent === window,
|
|
9
|
+
* 回退到 window.opener.postMessage——IDE 主线程经 event.source 单播回响应,
|
|
10
|
+
* 新窗口与 iframe 共用同一套请求/响应协议。
|
|
9
11
|
*
|
|
10
12
|
* **同步纪律**:本字符串必须与以下两处**逐字一致**(复制粘贴,无共享 import——预览
|
|
11
13
|
* iframe / 浏览器内 vite-dev 是两个独立加载上下文):
|
|
@@ -18,10 +20,16 @@
|
|
|
18
20
|
* 会退化成原生 fetch 并报 "Failed to parse URL"(真实事故:Web IDE 预览点「调用 hello 函数」
|
|
19
21
|
* 必报此错)。blob 文档继承创建者的 origin,故首次解析失败时用 `window.location.origin`
|
|
20
22
|
* 兜底再解析一次,两条预览路径(blob / Nodebox 异源隧道)就都能命中 /api/* 拦截。
|
|
23
|
+
*
|
|
24
|
+
* 新窗口预览(2026-09-12):`window.open` 打开的新标签页 `window.parent === window`,
|
|
25
|
+
* 回退到 `window.opener.postMessage` 转发 /api/* 请求(不能用 noopener,否则 opener 为 null)。
|
|
26
|
+
* blob URL SPA 路由:patch `history.pushState/replaceState`,对相对路径直接吞掉(blob 不可
|
|
27
|
+
* 分层,原生 pushState 解析相对路径抛 TypeError),`location.pathname` 覆盖为当前路由路径,
|
|
28
|
+
* sessionStorage 持久化,刷新后恢复。
|
|
21
29
|
*/
|
|
22
30
|
import type { IncomingMessage, ServerResponse } from 'node:http';
|
|
23
31
|
/** 浏览器端 fetch 拦截器脚本(IIFE,纯 JS——必须能直接内联进 script 标签,无 TS / ESM)。 */
|
|
24
|
-
export declare const FN_FETCH_INTERCEPTOR_SCRIPT = "(function () {\n if (window.__adepFnProxyInstalled) return;\n window.__adepFnProxyInstalled = true;\n var ORIGINAL_FETCH = window.fetch.bind(window);\n var pending = {};\n var nextId = 1;\n var TIMEOUT_MS = 15000;\n\n window.addEventListener(\"message\", function (event) {\n var data = event.data;\n if (!data || data.__adepFnResponse !== true) return;\n var entry = pending[data.id];\n if (!entry) return;\n delete pending[data.id];\n clearTimeout(entry.timer);\n var headers;\n try { headers = new Headers(data.headers || {}); }\n catch (e) { headers = new Headers({ \"content-type\": \"text/plain\" }); }\n var body = data.body == null ? null : data.body;\n try {\n entry.resolve(new Response(body, { status: data.status || 200, statusText: \"\", headers: headers }));\n } catch (e2) {\n entry.resolve(new Response(String(body), { status: data.status || 200, headers: { \"content-type\": \"text/plain\" } }));\n }\n });\n\n function readBody(input, init) {\n if (typeof Request !== \"undefined\" && input instanceof Request) {\n return input.text().catch(function () { return null; });\n }\n var body = init && init.body !== undefined ? init.body : input;\n if (body == null) return Promise.resolve(null);\n if (typeof body === \"string\") return Promise.resolve(body);\n if (typeof ArrayBuffer !== \"undefined\" && body instanceof ArrayBuffer) {\n return Promise.resolve(new TextDecoder().decode(body));\n }\n if (typeof Blob !== \"undefined\" && body instanceof Blob) return body.text();\n if (typeof URLSearchParams !== \"undefined\" && body instanceof URLSearchParams) {\n return Promise.resolve(body.toString());\n }\n try { return Promise.resolve(String(body)); } catch (e) { return Promise.resolve(null); }\n }\n\n function collectHeaders(input, init) {\n var out = {};\n try { input.headers.forEach(function (v, k) { out[k] = v; }); } catch (e) {}\n if (init && init.headers) {\n try { new Headers(init.headers).forEach(function (v, k) { out[k] = v; }); } catch (e2) {}\n }\n return out;\n }\n\n window.fetch = function (input, init) {\n var urlStr;\n var method;\n var headers = {};\n var isRequest = typeof Request !== \"undefined\" && input instanceof Request;\n if (isRequest) {\n urlStr = String(input.url);\n method = (init && init.method) || input.method || \"GET\";\n headers = collectHeaders(input, init);\n } else if (typeof input === \"string\" || (typeof URL !== \"undefined\" && input instanceof URL)) {\n urlStr = String(input);\n method = (init && init.method) || \"GET\";\n try { new Headers(init && init.headers || {}).forEach(function (v, k) { headers[k] = v; }); } catch (e) {}\n } else {\n return ORIGINAL_FETCH(input, init);\n }\n var u;\n try { u = new URL(urlStr, document.baseURI); }\n catch (e0) {\n try { u = new URL(urlStr, window.location.origin); }\n catch (e1) { return ORIGINAL_FETCH(input, init); }\n }\n if (u.pathname.indexOf(\"/api/\") !== 0 && u.pathname !== \"/api\") {\n return ORIGINAL_FETCH(input, init);\n }\n
|
|
32
|
+
export declare const FN_FETCH_INTERCEPTOR_SCRIPT = "(function () {\n if (window.__adepFnProxyInstalled) return;\n window.__adepFnProxyInstalled = true;\n var ORIGINAL_FETCH = window.fetch.bind(window);\n var pending = {};\n var nextId = 1;\n var TIMEOUT_MS = 15000;\n\n window.addEventListener(\"message\", function (event) {\n var data = event.data;\n if (!data || data.__adepFnResponse !== true) return;\n var entry = pending[data.id];\n if (!entry) return;\n delete pending[data.id];\n clearTimeout(entry.timer);\n var headers;\n try { headers = new Headers(data.headers || {}); }\n catch (e) { headers = new Headers({ \"content-type\": \"text/plain\" }); }\n var body = data.body == null ? null : data.body;\n try {\n entry.resolve(new Response(body, { status: data.status || 200, statusText: \"\", headers: headers }));\n } catch (e2) {\n entry.resolve(new Response(String(body), { status: data.status || 200, headers: { \"content-type\": \"text/plain\" } }));\n }\n });\n\n function readBody(input, init) {\n if (typeof Request !== \"undefined\" && input instanceof Request) {\n return input.text().catch(function () { return null; });\n }\n var body = init && init.body !== undefined ? init.body : input;\n if (body == null) return Promise.resolve(null);\n if (typeof body === \"string\") return Promise.resolve(body);\n if (typeof ArrayBuffer !== \"undefined\" && body instanceof ArrayBuffer) {\n return Promise.resolve(new TextDecoder().decode(body));\n }\n if (typeof Blob !== \"undefined\" && body instanceof Blob) return body.text();\n if (typeof URLSearchParams !== \"undefined\" && body instanceof URLSearchParams) {\n return Promise.resolve(body.toString());\n }\n try { return Promise.resolve(String(body)); } catch (e) { return Promise.resolve(null); }\n }\n\n function collectHeaders(input, init) {\n var out = {};\n try { input.headers.forEach(function (v, k) { out[k] = v; }); } catch (e) {}\n if (init && init.headers) {\n try { new Headers(init.headers).forEach(function (v, k) { out[k] = v; }); } catch (e2) {}\n }\n return out;\n }\n\n window.fetch = function (input, init) {\n var urlStr;\n var method;\n var headers = {};\n var isRequest = typeof Request !== \"undefined\" && input instanceof Request;\n if (isRequest) {\n urlStr = String(input.url);\n method = (init && init.method) || input.method || \"GET\";\n headers = collectHeaders(input, init);\n } else if (typeof input === \"string\" || (typeof URL !== \"undefined\" && input instanceof URL)) {\n urlStr = String(input);\n method = (init && init.method) || \"GET\";\n try { new Headers(init && init.headers || {}).forEach(function (v, k) { headers[k] = v; }); } catch (e) {}\n } else {\n return ORIGINAL_FETCH(input, init);\n }\n var u;\n try { u = new URL(urlStr, document.baseURI); }\n catch (e0) {\n try { u = new URL(urlStr, window.location.origin); }\n catch (e1) { return ORIGINAL_FETCH(input, init); }\n }\n if (u.pathname.indexOf(\"/api/\") !== 0 && u.pathname !== \"/api\") {\n return ORIGINAL_FETCH(input, init);\n }\n var bridgeWindow = window.parent !== window ? window.parent : window.opener;\n if (!bridgeWindow) return ORIGINAL_FETCH(input, init);\n method = (method || \"GET\").toUpperCase();\n var target = u.pathname + u.search;\n return readBody(input, init).then(function (textBody) {\n return new Promise(function (resolve) {\n var id = nextId++;\n var timer = setTimeout(function () {\n delete pending[id];\n resolve(new Response('{\"error\":{\"code\":\"FN_PROXY_TIMEOUT\",\"message\":\"\\u4e91\\u51fd\\u6570\\u6267\\u884c\\u8d85\\u65f6\"}}', { status: 504, headers: { \"content-type\": \"application/json\" } }));\n }, TIMEOUT_MS);\n pending[id] = { resolve: resolve, timer: timer };\n bridgeWindow.postMessage({ __adepFnRequest: true, id: id, method: method, url: target, headers: headers, body: textBody }, \"*\");\n });\n });\n };\n\n /* \u2014\u2014 blob URL SPA \u8DEF\u7531\u652F\u6301 \u2014\u2014\n blob: \u662F\u4E0D\u53EF\u5206\u5C42 scheme\uFF0Chistory.pushState('/about') \u5185\u90E8\u89E3\u6790\u76F8\u5BF9\u8DEF\u5F84\u4F1A\u629B\n TypeError \u2192 SPA history \u6A21\u5F0F\u8DEF\u7531\u5D29\u6E83\u3002patch pushState/replaceState\uFF0C\u5BF9\u76F8\u5BF9\n \u8DEF\u5F84\u76F4\u63A5\u541E\u6389\uFF08\u4E0D\u5BFC\u822A\u3001\u4E0D\u629B\u9519\uFF09\uFF0C\u8DEF\u7531\u5E93\u5185\u90E8\u72B6\u6001\u81EA\u884C\u66F4\u65B0\uFF1Blocation.pathname\n \u8986\u76D6\u4E3A\u5F53\u524D\u8DEF\u7531\u8DEF\u5F84\uFF0C\u4F9B\u8DEF\u7531\u5E93\u521D\u59CB\u5316\u65F6\u8BFB\u53D6\u3002sessionStorage \u6301\u4E45\u5316\uFF0C\u5237\u65B0\u540E\u6062\u590D\u3002 */\n if (window.location.protocol === \"blob:\") {\n var SPA_KEY = \"__adep_spa_path\";\n var spaPath = \"/\";\n try {\n var _saved = window.sessionStorage.getItem(SPA_KEY);\n if (_saved) spaPath = _saved;\n } catch (e) {}\n try {\n Object.defineProperty(window.location, \"pathname\", {\n get: function () { return spaPath; },\n configurable: true\n });\n } catch (e) {}\n var _origPush = window.history.pushState.bind(window.history);\n var _origReplace = window.history.replaceState.bind(window.history);\n function _isRelative(url) {\n if (!url || typeof url !== \"string\") return false;\n if (url.charAt(0) === \"#\") return false;\n if (/^[a-zA-Z][a-zA-Z0-9+.-]*:/.test(url)) return false;\n return true;\n }\n function _resolvePath(url) {\n var qi = url.indexOf(\"?\");\n var hi = url.indexOf(\"#\");\n var end = url.length;\n if (qi !== -1) end = qi;\n if (hi !== -1 && hi < end) end = hi;\n var p = url.slice(0, end);\n if (p.charAt(0) !== \"/\") {\n var base = spaPath.slice(0, spaPath.lastIndexOf(\"/\") + 1);\n p = base + p;\n }\n var parts = p.split(\"/\");\n var out = [];\n for (var i = 0; i < parts.length; i++) {\n var seg = parts[i];\n if (seg === \"\" || seg === \".\") continue;\n if (seg === \"..\") { out.pop(); continue; }\n out.push(seg);\n }\n return \"/\" + out.join(\"/\");\n }\n window.history.pushState = function (state, title, url) {\n if (_isRelative(url)) {\n spaPath = _resolvePath(url);\n try { window.sessionStorage.setItem(SPA_KEY, spaPath); } catch (e) {}\n return;\n }\n return _origPush(state, title, url);\n };\n window.history.replaceState = function (state, title, url) {\n if (_isRelative(url)) {\n spaPath = _resolvePath(url);\n try { window.sessionStorage.setItem(SPA_KEY, spaPath); } catch (e) {}\n return;\n }\n return _origReplace(state, title, url);\n };\n }\n})();";
|
|
25
33
|
/**
|
|
26
34
|
* 构造中间件:serve `/@adep/fn-proxy.js`(返回上面的拦截器脚本)。
|
|
27
35
|
* vite dev / preview server 均挂;路径未命中则 next() 放行。
|
package/dist/index.js
CHANGED
|
@@ -126,7 +126,8 @@ var FN_FETCH_INTERCEPTOR_SCRIPT = `(function () {
|
|
|
126
126
|
if (u.pathname.indexOf("/api/") !== 0 && u.pathname !== "/api") {
|
|
127
127
|
return ORIGINAL_FETCH(input, init);
|
|
128
128
|
}
|
|
129
|
-
|
|
129
|
+
var bridgeWindow = window.parent !== window ? window.parent : window.opener;
|
|
130
|
+
if (!bridgeWindow) return ORIGINAL_FETCH(input, init);
|
|
130
131
|
method = (method || "GET").toUpperCase();
|
|
131
132
|
var target = u.pathname + u.search;
|
|
132
133
|
return readBody(input, init).then(function (textBody) {
|
|
@@ -137,10 +138,75 @@ var FN_FETCH_INTERCEPTOR_SCRIPT = `(function () {
|
|
|
137
138
|
resolve(new Response('{"error":{"code":"FN_PROXY_TIMEOUT","message":"\\u4e91\\u51fd\\u6570\\u6267\\u884c\\u8d85\\u65f6"}}', { status: 504, headers: { "content-type": "application/json" } }));
|
|
138
139
|
}, TIMEOUT_MS);
|
|
139
140
|
pending[id] = { resolve: resolve, timer: timer };
|
|
140
|
-
|
|
141
|
+
bridgeWindow.postMessage({ __adepFnRequest: true, id: id, method: method, url: target, headers: headers, body: textBody }, "*");
|
|
141
142
|
});
|
|
142
143
|
});
|
|
143
144
|
};
|
|
145
|
+
|
|
146
|
+
/* \u2014\u2014 blob URL SPA \u8DEF\u7531\u652F\u6301 \u2014\u2014
|
|
147
|
+
blob: \u662F\u4E0D\u53EF\u5206\u5C42 scheme\uFF0Chistory.pushState('/about') \u5185\u90E8\u89E3\u6790\u76F8\u5BF9\u8DEF\u5F84\u4F1A\u629B
|
|
148
|
+
TypeError \u2192 SPA history \u6A21\u5F0F\u8DEF\u7531\u5D29\u6E83\u3002patch pushState/replaceState\uFF0C\u5BF9\u76F8\u5BF9
|
|
149
|
+
\u8DEF\u5F84\u76F4\u63A5\u541E\u6389\uFF08\u4E0D\u5BFC\u822A\u3001\u4E0D\u629B\u9519\uFF09\uFF0C\u8DEF\u7531\u5E93\u5185\u90E8\u72B6\u6001\u81EA\u884C\u66F4\u65B0\uFF1Blocation.pathname
|
|
150
|
+
\u8986\u76D6\u4E3A\u5F53\u524D\u8DEF\u7531\u8DEF\u5F84\uFF0C\u4F9B\u8DEF\u7531\u5E93\u521D\u59CB\u5316\u65F6\u8BFB\u53D6\u3002sessionStorage \u6301\u4E45\u5316\uFF0C\u5237\u65B0\u540E\u6062\u590D\u3002 */
|
|
151
|
+
if (window.location.protocol === "blob:") {
|
|
152
|
+
var SPA_KEY = "__adep_spa_path";
|
|
153
|
+
var spaPath = "/";
|
|
154
|
+
try {
|
|
155
|
+
var _saved = window.sessionStorage.getItem(SPA_KEY);
|
|
156
|
+
if (_saved) spaPath = _saved;
|
|
157
|
+
} catch (e) {}
|
|
158
|
+
try {
|
|
159
|
+
Object.defineProperty(window.location, "pathname", {
|
|
160
|
+
get: function () { return spaPath; },
|
|
161
|
+
configurable: true
|
|
162
|
+
});
|
|
163
|
+
} catch (e) {}
|
|
164
|
+
var _origPush = window.history.pushState.bind(window.history);
|
|
165
|
+
var _origReplace = window.history.replaceState.bind(window.history);
|
|
166
|
+
function _isRelative(url) {
|
|
167
|
+
if (!url || typeof url !== "string") return false;
|
|
168
|
+
if (url.charAt(0) === "#") return false;
|
|
169
|
+
if (/^[a-zA-Z][a-zA-Z0-9+.-]*:/.test(url)) return false;
|
|
170
|
+
return true;
|
|
171
|
+
}
|
|
172
|
+
function _resolvePath(url) {
|
|
173
|
+
var qi = url.indexOf("?");
|
|
174
|
+
var hi = url.indexOf("#");
|
|
175
|
+
var end = url.length;
|
|
176
|
+
if (qi !== -1) end = qi;
|
|
177
|
+
if (hi !== -1 && hi < end) end = hi;
|
|
178
|
+
var p = url.slice(0, end);
|
|
179
|
+
if (p.charAt(0) !== "/") {
|
|
180
|
+
var base = spaPath.slice(0, spaPath.lastIndexOf("/") + 1);
|
|
181
|
+
p = base + p;
|
|
182
|
+
}
|
|
183
|
+
var parts = p.split("/");
|
|
184
|
+
var out = [];
|
|
185
|
+
for (var i = 0; i < parts.length; i++) {
|
|
186
|
+
var seg = parts[i];
|
|
187
|
+
if (seg === "" || seg === ".") continue;
|
|
188
|
+
if (seg === "..") { out.pop(); continue; }
|
|
189
|
+
out.push(seg);
|
|
190
|
+
}
|
|
191
|
+
return "/" + out.join("/");
|
|
192
|
+
}
|
|
193
|
+
window.history.pushState = function (state, title, url) {
|
|
194
|
+
if (_isRelative(url)) {
|
|
195
|
+
spaPath = _resolvePath(url);
|
|
196
|
+
try { window.sessionStorage.setItem(SPA_KEY, spaPath); } catch (e) {}
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
return _origPush(state, title, url);
|
|
200
|
+
};
|
|
201
|
+
window.history.replaceState = function (state, title, url) {
|
|
202
|
+
if (_isRelative(url)) {
|
|
203
|
+
spaPath = _resolvePath(url);
|
|
204
|
+
try { window.sessionStorage.setItem(SPA_KEY, spaPath); } catch (e) {}
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
return _origReplace(state, title, url);
|
|
208
|
+
};
|
|
209
|
+
}
|
|
144
210
|
})();`;
|
|
145
211
|
function createAdepFnProxyScriptMiddleware() {
|
|
146
212
|
return (req, res, next) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adep/vite-plugin",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "云函数 Vite 插件(IDE-030,自 CLI-014 @adep/cli/vite 抽出):vite dev 进程内启动本地 adep dev server 并代理 /{prefix}/* 到云函数;同时 serve /@adep/fn-proxy.js(Web IDE 预览 fetch 拦截器)。本地运行时与配置加载经 createDevServer / loadConfig 注入端口接入,供 CLI / Web IDE / 非 CLI 用户独立使用。",
|