@adep/vite-plugin 0.1.1
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/dev-server.d.ts +102 -0
- package/dist/ide-proxy.d.ts +28 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.js +246 -0
- package/dist/prefix.d.ts +7 -0
- package/dist/proxy.d.ts +9 -0
- package/package.json +22 -0
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @adep/vite-plugin —— 云函数 Vite 插件核心(IDE-030,自 CLI-014 `@adep/cli/vite` 抽出)。
|
|
3
|
+
*
|
|
4
|
+
* 目标:`vite dev` 一个命令即可调试前端 + 云函数(全栈开发体验);同时供 Web IDE /
|
|
5
|
+
* 非 CLI 用户独立使用。
|
|
6
|
+
*
|
|
7
|
+
* 工作原理:
|
|
8
|
+
* - 若注入 `createDevServer`(CLI 注入 `startDevServer`):在 vite dev server 进程内
|
|
9
|
+
* 启动本地 adep dev server,把 `/{prefix}/*` 代理到它(默认前缀 /api,与线上函数路由
|
|
10
|
+
* `/api/{fn}` 对齐;读配置的 `functions_prefix`,未配置时强制 /api);
|
|
11
|
+
* - 始终挂中间件 serve `/@adep/fn-proxy.js`(浏览器端 fetch 拦截器,Web IDE 预览路径),
|
|
12
|
+
* 并经 transformIndexHtml 把它注入 index.html(CLI 模式下脚本自检 window.parent ===
|
|
13
|
+
* window 不激活,无害)。
|
|
14
|
+
*
|
|
15
|
+
* 依赖注入端口(避免循环依赖:CLI → vite-plugin → CLI):
|
|
16
|
+
* - `createDevServer`:本地 sim 运行时是 CLI 专有实现,由 CLI 注入;未提供时本插件不
|
|
17
|
+
* 启动 dev server、不挂函数代理(仍 serve 拦截器脚本——纯 IDE 预览场景);
|
|
18
|
+
* - `loadConfig`:读 `adep.config.ts` 的 `functions_prefix` 也是 CLI 专有(esbuild 转译),
|
|
19
|
+
* 由 CLI 注入;未提供时 prefix 缺省 /api。
|
|
20
|
+
*
|
|
21
|
+
* 边界:
|
|
22
|
+
* - 只代理 `/{prefix}/*` 形态;浏览器直接地址栏访问会被 vite 的 SPA 回退改写(前端
|
|
23
|
+
* `fetch()` 调用不受影响);
|
|
24
|
+
* - 不参与 `vite build`(构建时无需代理;构建产物见 vite.config.ts 的 outDir)。
|
|
25
|
+
*/
|
|
26
|
+
import type { IncomingMessage, ServerResponse } from 'node:http';
|
|
27
|
+
/** 本地 adep dev server 句柄(CLI `startDevServer` 返回值的结构子集)。 */
|
|
28
|
+
export interface AdepDevServerHandle {
|
|
29
|
+
/** `http://127.0.0.1:<port>`(随机端口时为真实端口)。 */
|
|
30
|
+
baseUrl: string;
|
|
31
|
+
close: () => Promise<void>;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* 本地 adep dev server 工厂(CLI 注入 `startDevServer`)。`port=0` 表示让系统分配
|
|
35
|
+
* 随机端口(插件端口占用重试时用)。
|
|
36
|
+
*/
|
|
37
|
+
export interface AdepDevServerFactory {
|
|
38
|
+
(opts: {
|
|
39
|
+
cwd: string;
|
|
40
|
+
port: number;
|
|
41
|
+
prefix: string;
|
|
42
|
+
log: (line: string) => void;
|
|
43
|
+
}): Promise<AdepDevServerHandle>;
|
|
44
|
+
}
|
|
45
|
+
/** 读 `adep.config.ts` 的 `functions_prefix`(CLI 注入 `loadAdepConfigModule`)。 */
|
|
46
|
+
export interface AdepConfigLoader {
|
|
47
|
+
(cwd: string): Promise<{
|
|
48
|
+
functions_prefix?: string;
|
|
49
|
+
} | null | undefined>;
|
|
50
|
+
}
|
|
51
|
+
export interface AdepVitePluginOptions {
|
|
52
|
+
/** adep dev 监听端口(缺省 8787;被占用时自动换随机端口)。 */
|
|
53
|
+
port?: number;
|
|
54
|
+
/** 代理前缀覆盖(缺省读 config 的 `functions_prefix`,再缺省 /api)。 */
|
|
55
|
+
prefix?: string;
|
|
56
|
+
/** 项目根目录(缺省 process.cwd(),即 vite 工程根)。 */
|
|
57
|
+
cwd?: string;
|
|
58
|
+
/**
|
|
59
|
+
* 启动本地 adep dev server 的工厂(CLI 注入);未提供时不启动 dev server、不挂函数
|
|
60
|
+
* 代理(仍 serve `/@adep/fn-proxy.js`——纯 IDE 预览场景)。
|
|
61
|
+
*/
|
|
62
|
+
createDevServer?: AdepDevServerFactory;
|
|
63
|
+
/** 读项目配置(CLI 注入);未提供时 prefix 缺省 /api。 */
|
|
64
|
+
loadConfig?: AdepConfigLoader;
|
|
65
|
+
}
|
|
66
|
+
/** vite server 结构子集(插件不依赖 vite 包,仅按需消费这些字段)。 */
|
|
67
|
+
export interface AdepViteServer {
|
|
68
|
+
middlewares: {
|
|
69
|
+
use(fn: (req: IncomingMessage, res: ServerResponse, next: () => void) => void): void;
|
|
70
|
+
};
|
|
71
|
+
config: {
|
|
72
|
+
logger: {
|
|
73
|
+
info(msg: string): void;
|
|
74
|
+
error(msg: string): void;
|
|
75
|
+
};
|
|
76
|
+
};
|
|
77
|
+
httpServer?: {
|
|
78
|
+
on(event: 'close', listener: () => void): void;
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
export interface AdepVitePlugin {
|
|
82
|
+
name: string;
|
|
83
|
+
/** 插件始终实现以下钩子(结构类型对齐 Vite Plugin 的子集,本包不依赖 vite 包)。 */
|
|
84
|
+
configureServer: (server: AdepViteServer) => void | Promise<void>;
|
|
85
|
+
configurePreviewServer: (server: AdepViteServer) => void | Promise<void>;
|
|
86
|
+
/** 注入 fetch 拦截器 script 标签(幂等)。 */
|
|
87
|
+
transformIndexHtml: (html: string) => string;
|
|
88
|
+
closeBundle: () => void | Promise<void>;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* 云函数 Vite 插件:注入了 `createDevServer` 时启动内置 adep dev server 并代理
|
|
92
|
+
* `/{prefix}/*` 到云函数;同时始终 serve `/@adep/fn-proxy.js`(IDE 预览 fetch 拦截器)。
|
|
93
|
+
* 用法(项目根 vite.config.ts):
|
|
94
|
+
* ```ts
|
|
95
|
+
* import { defineConfig } from 'vite'
|
|
96
|
+
* import vue from '@vitejs/plugin-vue'
|
|
97
|
+
* import { adepPlugin } from '@adep/vite-plugin'
|
|
98
|
+
* export default defineConfig({ plugins: [vue(), adepPlugin()] })
|
|
99
|
+
* ```
|
|
100
|
+
*/
|
|
101
|
+
export declare function adepPlugin(options?: AdepVitePluginOptions): AdepVitePlugin;
|
|
102
|
+
export default adepPlugin;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Web IDE 预览云函数 fetch 拦截器(IDE-030:从 shared/sdk/web-project-template.ts 迁移过来,
|
|
3
|
+
* 本文件为**单一真相源**;IDE-032 起 shared 模板不再保留文本副本,改由本包
|
|
4
|
+
* `adepPlugin()` 在 dev 时经 transformIndexHtml 自动注入)。
|
|
5
|
+
*
|
|
6
|
+
* 背景:浏览器端 IIFE,patch window.fetch,把 /api/* 请求经 window.parent.postMessage
|
|
7
|
+
* 转发到 IDE 主线程(Nodebox 真 vite 预览路径);CLI 模式下脚本自检 window.parent ===
|
|
8
|
+
* window 不激活,无害。
|
|
9
|
+
*
|
|
10
|
+
* **同步纪律**:本字符串必须与以下两处**逐字一致**(复制粘贴,无共享 import——预览
|
|
11
|
+
* iframe / 浏览器内 vite-dev 是两个独立加载上下文):
|
|
12
|
+
* - packages/web-container/src/function-fetch-proxy.ts 的 FN_FETCH_INTERCEPTOR_SCRIPT
|
|
13
|
+
* (浏览器内 vite-dev 预览经 @adep/web-container buildDocument() 注入)。
|
|
14
|
+
* 改拦截器务必同步后一处(文本复制,不改语义)。脚本内不得出现反引号或模板插值。
|
|
15
|
+
*/
|
|
16
|
+
import type { IncomingMessage, ServerResponse } from 'node:http';
|
|
17
|
+
/** 浏览器端 fetch 拦截器脚本(IIFE,纯 JS——必须能直接内联进 script 标签,无 TS / ESM)。 */
|
|
18
|
+
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 (e) { return ORIGINAL_FETCH(input, init); }\n if (u.pathname.indexOf(\"/api/\") !== 0 && u.pathname !== \"/api\") {\n return ORIGINAL_FETCH(input, init);\n }\n if (window.parent === window) 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 window.parent.postMessage({ __adepFnRequest: true, id: id, method: method, url: target, headers: headers, body: textBody }, \"*\");\n });\n });\n };\n})();";
|
|
19
|
+
/**
|
|
20
|
+
* 构造中间件:serve `/@adep/fn-proxy.js`(返回上面的拦截器脚本)。
|
|
21
|
+
* vite dev / preview server 均挂;路径未命中则 next() 放行。
|
|
22
|
+
*/
|
|
23
|
+
export declare function createAdepFnProxyScriptMiddleware(): (req: IncomingMessage, res: ServerResponse, next: () => void) => void;
|
|
24
|
+
/**
|
|
25
|
+
* 把 fetch 拦截器 script 标签注入 index.html 的 </head> 前(幂等:已注入过则原样返回)。
|
|
26
|
+
* Web IDE 预览路径用(CLI 模式下脚本自检 window.parent === window 不激活,无害)。
|
|
27
|
+
*/
|
|
28
|
+
export declare function injectFnProxyScriptTag(html: string): string;
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @adep/vite-plugin(IDE-030)—— 云函数 Vite 插件独立包,自 CLI-014 `@adep/cli/vite` 抽出。
|
|
3
|
+
*
|
|
4
|
+
* 公开面:
|
|
5
|
+
* - `adepPlugin(options?)`:主入口(命名 + 默认导出);
|
|
6
|
+
* - `normalizeFunctionPrefix` / `createAdepProxyMiddleware` / `isAddrInUse`:前缀与代理
|
|
7
|
+
* 原语,供测试与高级集成复用;
|
|
8
|
+
* - `FN_FETCH_INTERCEPTOR_SCRIPT` / `createAdepFnProxyScriptMiddleware` /
|
|
9
|
+
* `injectFnProxyScriptTag`:Web IDE 预览 fetch 拦截器(单一真相源,详见 ide-proxy.ts);
|
|
10
|
+
* - 全部类型(AdepVitePluginOptions / AdepDevServerFactory / AdepConfigLoader /
|
|
11
|
+
* AdepDevServerHandle / AdepVitePlugin / AdepViteServer)。
|
|
12
|
+
*
|
|
13
|
+
* 本包不 import vite(结构类型对齐其子集)、不 import CLI——本地 sim 运行时与配置加载
|
|
14
|
+
* 经 options 注入端口接入(dev-server.ts 头部注释)。
|
|
15
|
+
*/
|
|
16
|
+
export { adepPlugin, default } from './dev-server';
|
|
17
|
+
export type { AdepConfigLoader, AdepDevServerFactory, AdepDevServerHandle, AdepVitePlugin, AdepVitePluginOptions, AdepViteServer, } from './dev-server';
|
|
18
|
+
export { normalizeFunctionPrefix } from './prefix';
|
|
19
|
+
export { createAdepProxyMiddleware, isAddrInUse } from './proxy';
|
|
20
|
+
export { createAdepFnProxyScriptMiddleware, FN_FETCH_INTERCEPTOR_SCRIPT, injectFnProxyScriptTag, } from './ide-proxy';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
// packages/vite-plugin/src/dev-server.ts
|
|
2
|
+
import { resolve } from "node:path";
|
|
3
|
+
|
|
4
|
+
// packages/vite-plugin/src/prefix.ts
|
|
5
|
+
function normalizeFunctionPrefix(raw) {
|
|
6
|
+
const value = (raw ?? "").trim();
|
|
7
|
+
if (value.length === 0 || value === "/") return "";
|
|
8
|
+
return value.replace(/^\/+|\/+$/g, "");
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
// packages/vite-plugin/src/proxy.ts
|
|
12
|
+
import { request as httpRequest } from "node:http";
|
|
13
|
+
function isAddrInUse(error) {
|
|
14
|
+
return typeof error === "object" && error !== null && "code" in error && error.code === "EADDRINUSE";
|
|
15
|
+
}
|
|
16
|
+
function createAdepProxyMiddleware(prefix, targetBase) {
|
|
17
|
+
const normalized = normalizeFunctionPrefix(prefix);
|
|
18
|
+
const prefixPath = normalized === "" ? "" : `/${normalized}`;
|
|
19
|
+
return (req, res, next) => {
|
|
20
|
+
if (prefixPath === "") return next();
|
|
21
|
+
const url = new URL(req.url ?? "/", "http://127.0.0.1");
|
|
22
|
+
if (url.pathname !== prefixPath && !url.pathname.startsWith(`${prefixPath}/`)) {
|
|
23
|
+
return next();
|
|
24
|
+
}
|
|
25
|
+
const target = new URL(url.pathname + url.search, targetBase);
|
|
26
|
+
const proxyReq = httpRequest(
|
|
27
|
+
target,
|
|
28
|
+
{ method: req.method, headers: { ...req.headers, host: target.host } },
|
|
29
|
+
(proxyRes) => {
|
|
30
|
+
res.writeHead(proxyRes.statusCode ?? 502, proxyRes.headers);
|
|
31
|
+
proxyRes.pipe(res);
|
|
32
|
+
}
|
|
33
|
+
);
|
|
34
|
+
proxyReq.on("error", (error) => {
|
|
35
|
+
if (!res.headersSent) {
|
|
36
|
+
res.writeHead(502, { "content-type": "application/json" });
|
|
37
|
+
}
|
|
38
|
+
res.end(
|
|
39
|
+
JSON.stringify({
|
|
40
|
+
error: {
|
|
41
|
+
code: "ADEP_DEV_UNAVAILABLE",
|
|
42
|
+
message: `\u4E91\u51FD\u6570\u4EE3\u7406\u5931\u8D25\uFF08adep dev server \u4E0D\u53EF\u8FBE\uFF09\uFF1A${error.message}`
|
|
43
|
+
}
|
|
44
|
+
})
|
|
45
|
+
);
|
|
46
|
+
});
|
|
47
|
+
req.pipe(proxyReq);
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// packages/vite-plugin/src/ide-proxy.ts
|
|
52
|
+
var FN_FETCH_INTERCEPTOR_SCRIPT = `(function () {
|
|
53
|
+
if (window.__adepFnProxyInstalled) return;
|
|
54
|
+
window.__adepFnProxyInstalled = true;
|
|
55
|
+
var ORIGINAL_FETCH = window.fetch.bind(window);
|
|
56
|
+
var pending = {};
|
|
57
|
+
var nextId = 1;
|
|
58
|
+
var TIMEOUT_MS = 15000;
|
|
59
|
+
|
|
60
|
+
window.addEventListener("message", function (event) {
|
|
61
|
+
var data = event.data;
|
|
62
|
+
if (!data || data.__adepFnResponse !== true) return;
|
|
63
|
+
var entry = pending[data.id];
|
|
64
|
+
if (!entry) return;
|
|
65
|
+
delete pending[data.id];
|
|
66
|
+
clearTimeout(entry.timer);
|
|
67
|
+
var headers;
|
|
68
|
+
try { headers = new Headers(data.headers || {}); }
|
|
69
|
+
catch (e) { headers = new Headers({ "content-type": "text/plain" }); }
|
|
70
|
+
var body = data.body == null ? null : data.body;
|
|
71
|
+
try {
|
|
72
|
+
entry.resolve(new Response(body, { status: data.status || 200, statusText: "", headers: headers }));
|
|
73
|
+
} catch (e2) {
|
|
74
|
+
entry.resolve(new Response(String(body), { status: data.status || 200, headers: { "content-type": "text/plain" } }));
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
function readBody(input, init) {
|
|
79
|
+
if (typeof Request !== "undefined" && input instanceof Request) {
|
|
80
|
+
return input.text().catch(function () { return null; });
|
|
81
|
+
}
|
|
82
|
+
var body = init && init.body !== undefined ? init.body : input;
|
|
83
|
+
if (body == null) return Promise.resolve(null);
|
|
84
|
+
if (typeof body === "string") return Promise.resolve(body);
|
|
85
|
+
if (typeof ArrayBuffer !== "undefined" && body instanceof ArrayBuffer) {
|
|
86
|
+
return Promise.resolve(new TextDecoder().decode(body));
|
|
87
|
+
}
|
|
88
|
+
if (typeof Blob !== "undefined" && body instanceof Blob) return body.text();
|
|
89
|
+
if (typeof URLSearchParams !== "undefined" && body instanceof URLSearchParams) {
|
|
90
|
+
return Promise.resolve(body.toString());
|
|
91
|
+
}
|
|
92
|
+
try { return Promise.resolve(String(body)); } catch (e) { return Promise.resolve(null); }
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function collectHeaders(input, init) {
|
|
96
|
+
var out = {};
|
|
97
|
+
try { input.headers.forEach(function (v, k) { out[k] = v; }); } catch (e) {}
|
|
98
|
+
if (init && init.headers) {
|
|
99
|
+
try { new Headers(init.headers).forEach(function (v, k) { out[k] = v; }); } catch (e2) {}
|
|
100
|
+
}
|
|
101
|
+
return out;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
window.fetch = function (input, init) {
|
|
105
|
+
var urlStr;
|
|
106
|
+
var method;
|
|
107
|
+
var headers = {};
|
|
108
|
+
var isRequest = typeof Request !== "undefined" && input instanceof Request;
|
|
109
|
+
if (isRequest) {
|
|
110
|
+
urlStr = String(input.url);
|
|
111
|
+
method = (init && init.method) || input.method || "GET";
|
|
112
|
+
headers = collectHeaders(input, init);
|
|
113
|
+
} else if (typeof input === "string" || (typeof URL !== "undefined" && input instanceof URL)) {
|
|
114
|
+
urlStr = String(input);
|
|
115
|
+
method = (init && init.method) || "GET";
|
|
116
|
+
try { new Headers(init && init.headers || {}).forEach(function (v, k) { headers[k] = v; }); } catch (e) {}
|
|
117
|
+
} else {
|
|
118
|
+
return ORIGINAL_FETCH(input, init);
|
|
119
|
+
}
|
|
120
|
+
var u;
|
|
121
|
+
try { u = new URL(urlStr, document.baseURI); }
|
|
122
|
+
catch (e) { return ORIGINAL_FETCH(input, init); }
|
|
123
|
+
if (u.pathname.indexOf("/api/") !== 0 && u.pathname !== "/api") {
|
|
124
|
+
return ORIGINAL_FETCH(input, init);
|
|
125
|
+
}
|
|
126
|
+
if (window.parent === window) return ORIGINAL_FETCH(input, init);
|
|
127
|
+
method = (method || "GET").toUpperCase();
|
|
128
|
+
var target = u.pathname + u.search;
|
|
129
|
+
return readBody(input, init).then(function (textBody) {
|
|
130
|
+
return new Promise(function (resolve) {
|
|
131
|
+
var id = nextId++;
|
|
132
|
+
var timer = setTimeout(function () {
|
|
133
|
+
delete pending[id];
|
|
134
|
+
resolve(new Response('{"error":{"code":"FN_PROXY_TIMEOUT","message":"\\u4e91\\u51fd\\u6570\\u6267\\u884c\\u8d85\\u65f6"}}', { status: 504, headers: { "content-type": "application/json" } }));
|
|
135
|
+
}, TIMEOUT_MS);
|
|
136
|
+
pending[id] = { resolve: resolve, timer: timer };
|
|
137
|
+
window.parent.postMessage({ __adepFnRequest: true, id: id, method: method, url: target, headers: headers, body: textBody }, "*");
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
};
|
|
141
|
+
})();`;
|
|
142
|
+
function createAdepFnProxyScriptMiddleware() {
|
|
143
|
+
return (req, res, next) => {
|
|
144
|
+
const pathname = (req.url ?? "").split("?")[0];
|
|
145
|
+
if (pathname !== "/@adep/fn-proxy.js") return next();
|
|
146
|
+
res.setHeader("content-type", "application/javascript; charset=utf-8");
|
|
147
|
+
res.end(FN_FETCH_INTERCEPTOR_SCRIPT);
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
function injectFnProxyScriptTag(html) {
|
|
151
|
+
if (html.includes("/@adep/fn-proxy.js")) return html;
|
|
152
|
+
return html.replace("</head>", '<script src="/@adep/fn-proxy.js"></script></head>');
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// packages/vite-plugin/src/dev-server.ts
|
|
156
|
+
function adepPlugin(options = {}) {
|
|
157
|
+
const cwd = resolve(options.cwd ?? process.cwd());
|
|
158
|
+
let dev;
|
|
159
|
+
let prefix = normalizeFunctionPrefix(options.prefix);
|
|
160
|
+
let prefixPath = "";
|
|
161
|
+
let startPromise;
|
|
162
|
+
const startAdepDev = async (log) => {
|
|
163
|
+
if (startPromise !== void 0) return startPromise;
|
|
164
|
+
const create = options.createDevServer;
|
|
165
|
+
startPromise = (async () => {
|
|
166
|
+
if (create === void 0) return;
|
|
167
|
+
if (prefix === "") {
|
|
168
|
+
const config = options.loadConfig ? await options.loadConfig(cwd) : void 0;
|
|
169
|
+
prefix = normalizeFunctionPrefix(config?.functions_prefix);
|
|
170
|
+
if (prefix === "") {
|
|
171
|
+
log("[adep] adep.config.ts \u672A\u914D\u7F6E functions_prefix\uFF0C\u4E91\u51FD\u6570\u4EE3\u7406\u524D\u7F00\u9ED8\u8BA4 /api");
|
|
172
|
+
prefix = "api";
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
prefixPath = `/${prefix}`;
|
|
176
|
+
const targetPort = options.port ?? 8787;
|
|
177
|
+
try {
|
|
178
|
+
dev = await create({ cwd, port: targetPort, prefix, log });
|
|
179
|
+
} catch (error) {
|
|
180
|
+
if (isAddrInUse(error)) {
|
|
181
|
+
log(`[adep] \u7AEF\u53E3 ${targetPort} \u88AB\u5360\u7528\uFF0C\u6539\u7528\u968F\u673A\u7AEF\u53E3`);
|
|
182
|
+
dev = await create({ cwd, port: 0, prefix, log });
|
|
183
|
+
} else {
|
|
184
|
+
log(
|
|
185
|
+
`[adep] \u4E91\u51FD\u6570 dev server \u542F\u52A8\u5931\u8D25\uFF1A${error instanceof Error ? error.message : String(error)}\uFF08vite \u524D\u7AEF\u7167\u5E38\uFF0C\u51FD\u6570\u4EE3\u7406\u4E0D\u53EF\u7528\uFF09`
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
if (dev !== void 0) {
|
|
190
|
+
log(
|
|
191
|
+
`[adep] \u4E91\u51FD\u6570\u5DF2\u5C31\u7EEA\uFF1A${dev.baseUrl}${prefixPath}/<fn>\uFF08vite \u4EE3\u7406 ${prefixPath}/* \u2192 \u4E91\u51FD\u6570\uFF09`
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
})();
|
|
195
|
+
try {
|
|
196
|
+
await startPromise;
|
|
197
|
+
} finally {
|
|
198
|
+
startPromise = void 0;
|
|
199
|
+
}
|
|
200
|
+
};
|
|
201
|
+
const disposeAdepDev = async () => {
|
|
202
|
+
if (dev === void 0) return;
|
|
203
|
+
const target = dev;
|
|
204
|
+
dev = void 0;
|
|
205
|
+
await target.close();
|
|
206
|
+
};
|
|
207
|
+
const attachMiddlewares = (server) => {
|
|
208
|
+
if (dev !== void 0 && prefixPath !== "") {
|
|
209
|
+
server.middlewares.use(createAdepProxyMiddleware(prefixPath, dev.baseUrl));
|
|
210
|
+
}
|
|
211
|
+
server.middlewares.use(createAdepFnProxyScriptMiddleware());
|
|
212
|
+
};
|
|
213
|
+
return {
|
|
214
|
+
name: "adep-cloud-functions",
|
|
215
|
+
async configureServer(server) {
|
|
216
|
+
const log = (line) => server.config.logger.info(line);
|
|
217
|
+
await startAdepDev(log);
|
|
218
|
+
attachMiddlewares(server);
|
|
219
|
+
server.httpServer?.on("close", () => {
|
|
220
|
+
void disposeAdepDev();
|
|
221
|
+
});
|
|
222
|
+
},
|
|
223
|
+
async configurePreviewServer(server) {
|
|
224
|
+
const log = (line) => server.config.logger.info(line);
|
|
225
|
+
await startAdepDev(log);
|
|
226
|
+
attachMiddlewares(server);
|
|
227
|
+
},
|
|
228
|
+
transformIndexHtml(html) {
|
|
229
|
+
return injectFnProxyScriptTag(html);
|
|
230
|
+
},
|
|
231
|
+
async closeBundle() {
|
|
232
|
+
await disposeAdepDev();
|
|
233
|
+
}
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
var dev_server_default = adepPlugin;
|
|
237
|
+
export {
|
|
238
|
+
FN_FETCH_INTERCEPTOR_SCRIPT,
|
|
239
|
+
adepPlugin,
|
|
240
|
+
createAdepFnProxyScriptMiddleware,
|
|
241
|
+
createAdepProxyMiddleware,
|
|
242
|
+
dev_server_default as default,
|
|
243
|
+
injectFnProxyScriptTag,
|
|
244
|
+
isAddrInUse,
|
|
245
|
+
normalizeFunctionPrefix
|
|
246
|
+
};
|
package/dist/prefix.d.ts
ADDED
package/dist/proxy.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { IncomingMessage, ServerResponse } from 'node:http';
|
|
2
|
+
/** 判断监听失败是否为端口占用(插件据此换随机端口重试)。 */
|
|
3
|
+
export declare function isAddrInUse(error: unknown): boolean;
|
|
4
|
+
/**
|
|
5
|
+
* 构造代理中间件:命中 `/{prefix}` 或 `/{prefix}/*` 的请求转发到 `targetBase`
|
|
6
|
+
* (adep dev server 的 baseUrl),方法 / 头 / body / query 原样透传;dev server
|
|
7
|
+
* 不可达回 502 JSON。prefix 为空时不代理任何路径(next() 放行)。
|
|
8
|
+
*/
|
|
9
|
+
export declare function createAdepProxyMiddleware(prefix: string, targetBase: string): (req: IncomingMessage, res: ServerResponse, next: () => void) => void;
|
package/package.json
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@adep/vite-plugin",
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"private": false,
|
|
5
|
+
"type": "module",
|
|
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 用户独立使用。",
|
|
7
|
+
"main": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"default": "./dist/index.js"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist"
|
|
17
|
+
],
|
|
18
|
+
"scripts": {
|
|
19
|
+
"test": "vitest run",
|
|
20
|
+
"build": "bun run ../../scripts/build-package.ts vite-plugin"
|
|
21
|
+
}
|
|
22
|
+
}
|