@ubean/build 0.2.2 → 0.3.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/actions-plugin.d.ts +62 -0
- package/dist/actions-plugin.js +385 -0
- package/dist/codegen/index.d.ts +175 -0
- package/dist/codegen/index.js +2 -0
- package/dist/codegen-D6ewf11I.js +678 -0
- package/dist/index.d.ts +95 -7
- package/dist/index.js +4 -3
- package/dist/prerender.d.ts +129 -0
- package/dist/prerender.js +308 -0
- package/dist/production.d.ts +2 -0
- package/dist/production.js +51 -21
- package/dist/ssr-singleton-CEQi_H6-.js +113 -0
- package/dist/{virtual-modules-BDgTqx3t.js → virtual-modules-D5WBA3u7.js} +175 -68
- package/dist/virtual-registry-BF0jYPle.d.ts +30 -0
- package/dist/virtual-registry-BWkaQHXN.js +69 -0
- package/dist/vite-D640Tpv8.js +226 -0
- package/dist/vite.d.ts +1 -1
- package/dist/vite.js +1 -210
- package/dist/vue-BJO18Lwe.js +694 -0
- package/dist/vue.d.ts +29 -0
- package/dist/vue.js +2 -0
- package/package.json +38 -15
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { Plugin } from "vite";
|
|
2
|
+
//#region src/actions-plugin.d.ts
|
|
3
|
+
interface ServerActionsPluginOptions {
|
|
4
|
+
/**
|
|
5
|
+
* The project root directory (used for computing stable action IDs).
|
|
6
|
+
* Defaults to `process.cwd()` at plugin creation time.
|
|
7
|
+
*/
|
|
8
|
+
root?: string;
|
|
9
|
+
}
|
|
10
|
+
interface DefineActionCall {
|
|
11
|
+
/** `defineAction` 标识符起始位置。 */
|
|
12
|
+
start: number;
|
|
13
|
+
/** `(` 后第一个字符位置(参数区起点)。 */
|
|
14
|
+
argStart: number;
|
|
15
|
+
/** 匹配的 `)` 位置。 */
|
|
16
|
+
end: number;
|
|
17
|
+
/** 推断出的 action 名称。 */
|
|
18
|
+
name: string;
|
|
19
|
+
/** 最后一个参数是否为对象字面量(options)。 */
|
|
20
|
+
hasOptionsObject: boolean;
|
|
21
|
+
/** options 对象 `{` 的位置(若 hasOptionsObject)。 */
|
|
22
|
+
optionsBraceStart: number;
|
|
23
|
+
/** options 对象 `}` 的位置(若 hasOptionsObject)。 */
|
|
24
|
+
optionsBraceEnd: number;
|
|
25
|
+
/** options 对象是否已包含 `name` 属性。 */
|
|
26
|
+
optionsHasName: boolean;
|
|
27
|
+
/** options 对象是否已包含 `filePath` 属性。 */
|
|
28
|
+
optionsHasFilePath: boolean;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* 提取源码中所有 `defineAction(...)` 调用信息。
|
|
32
|
+
*
|
|
33
|
+
* 跳过注释内的匹配(如 JSDoc 示例)和函数声明(`function defineAction(`)。
|
|
34
|
+
*/
|
|
35
|
+
declare function findDefineActionCalls(code: string): DefineActionCall[];
|
|
36
|
+
/**
|
|
37
|
+
* 检测源码是否包含 `defineAction(` 调用。
|
|
38
|
+
*/
|
|
39
|
+
declare function hasDefineActionCall(code: string): boolean;
|
|
40
|
+
/**
|
|
41
|
+
* 服务端转换:为每个 `defineAction()` 调用注入 `filePath`(以及缺失时的 `name`),
|
|
42
|
+
* 确保 action ID 在 dev / build 之间稳定。
|
|
43
|
+
*
|
|
44
|
+
* 返回转换后的代码;若无 `defineAction` 调用则返回 `null`。
|
|
45
|
+
*/
|
|
46
|
+
declare function transformActionsForServer(code: string, filePath: string, root: string): string | null;
|
|
47
|
+
/**
|
|
48
|
+
* 客户端转换:将每个 `defineAction(...)` 调用替换为
|
|
49
|
+
* `__ubean_createActionStub('<id>')`(别名 import,避免与用户代码冲突)。
|
|
50
|
+
*
|
|
51
|
+
* 返回转换后的代码(含注入的 import);若无 `defineAction` 调用则返回 `null`。
|
|
52
|
+
*/
|
|
53
|
+
declare function transformActionsForClient(code: string, filePath: string, root: string): string | null;
|
|
54
|
+
/**
|
|
55
|
+
* Vite 插件:Server Actions 的客户端/服务端拆分。
|
|
56
|
+
*
|
|
57
|
+
* - 服务端(SSR):注入 `filePath`/`name` 到 `defineAction()` 选项。
|
|
58
|
+
* - 客户端(浏览器):将 `defineAction(...)` 替换为 `createActionStub('<id>')`。
|
|
59
|
+
*/
|
|
60
|
+
declare function ubeanServerActionsPlugin(options?: ServerActionsPluginOptions): Plugin;
|
|
61
|
+
//#endregion
|
|
62
|
+
export { ServerActionsPluginOptions, findDefineActionCalls, hasDefineActionCall, transformActionsForClient, transformActionsForServer, ubeanServerActionsPlugin };
|
|
@@ -0,0 +1,385 @@
|
|
|
1
|
+
import { relative } from "pathe";
|
|
2
|
+
import { createActionId } from "@ubean/routes";
|
|
3
|
+
//#region src/actions-plugin.ts
|
|
4
|
+
/**
|
|
5
|
+
* 查找匹配的闭合括号(支持字符串/模板字面量/注释跳过)。
|
|
6
|
+
*/
|
|
7
|
+
function findBalanced(code, openChar, closeChar, startIdx) {
|
|
8
|
+
let depth = 1;
|
|
9
|
+
let i = startIdx;
|
|
10
|
+
let inString = null;
|
|
11
|
+
let escaped = false;
|
|
12
|
+
let inTemplateExpr = 0;
|
|
13
|
+
while (i < code.length && depth > 0) {
|
|
14
|
+
const ch = code[i];
|
|
15
|
+
if (escaped) {
|
|
16
|
+
escaped = false;
|
|
17
|
+
i++;
|
|
18
|
+
continue;
|
|
19
|
+
}
|
|
20
|
+
if (ch === "\\") {
|
|
21
|
+
escaped = true;
|
|
22
|
+
i++;
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
25
|
+
if (inString) {
|
|
26
|
+
if (inString === "`" && ch === "$" && code[i + 1] === "{") {
|
|
27
|
+
inTemplateExpr++;
|
|
28
|
+
i += 2;
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
if (inString === "`" && ch === "}" && inTemplateExpr > 0) {
|
|
32
|
+
inTemplateExpr--;
|
|
33
|
+
i++;
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
if (ch === inString) inString = null;
|
|
37
|
+
i++;
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
if (ch === "\"" || ch === "'" || ch === "`") {
|
|
41
|
+
inString = ch;
|
|
42
|
+
i++;
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
if (ch === "/" && code[i + 1] === "/") {
|
|
46
|
+
while (i < code.length && code[i] !== "\n") i++;
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
if (ch === "/" && code[i + 1] === "*") {
|
|
50
|
+
i += 2;
|
|
51
|
+
while (i < code.length && !(code[i] === "*" && code[i + 1] === "/")) i++;
|
|
52
|
+
i += 2;
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
if (ch === openChar) depth++;
|
|
56
|
+
else if (ch === closeChar) {
|
|
57
|
+
depth--;
|
|
58
|
+
if (depth === 0) return i;
|
|
59
|
+
}
|
|
60
|
+
i++;
|
|
61
|
+
}
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* 计算项目相对路径(用于 action ID 生成)。
|
|
66
|
+
*/
|
|
67
|
+
function toProjectRelative(filePath, root) {
|
|
68
|
+
let rel = filePath;
|
|
69
|
+
if (root) try {
|
|
70
|
+
rel = relative(root, filePath);
|
|
71
|
+
} catch {}
|
|
72
|
+
return rel.replace(/\\/g, "/");
|
|
73
|
+
}
|
|
74
|
+
const DEFINE_ACTION_RE = /\b(?:defineAction|defineServerFn)\s*\(/g;
|
|
75
|
+
/**
|
|
76
|
+
* 从 options 对象文本中提取 `name` 属性的字符串字面量值。
|
|
77
|
+
*
|
|
78
|
+
* 匹配 `name: 'xxx'` 或 `name: "xxx"`。不支持动态值(如 `name: someVar`),
|
|
79
|
+
* 此类情况下返回 `null`。
|
|
80
|
+
*/
|
|
81
|
+
function extractOptionsName(objText) {
|
|
82
|
+
const m = objText.match(/\bname\s*:\s*['"]([^'"]+)['"]/);
|
|
83
|
+
return m ? m[1] : null;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* 推断 action 名称。
|
|
87
|
+
*
|
|
88
|
+
* 优先级:
|
|
89
|
+
* 1. options 对象中的 `name` 字符串字面量(若存在)
|
|
90
|
+
* 2. 变量/属性赋名:
|
|
91
|
+
* - `export const <name> = defineAction(`
|
|
92
|
+
* - `const <name> = defineAction(`
|
|
93
|
+
* - `<name>: defineAction(` (对象属性)
|
|
94
|
+
* 3. `'anonymous'`
|
|
95
|
+
*/
|
|
96
|
+
function inferName(code, callStart, optionsObjText) {
|
|
97
|
+
if (optionsObjText) {
|
|
98
|
+
const optsName = extractOptionsName(optionsObjText);
|
|
99
|
+
if (optsName) return optsName;
|
|
100
|
+
}
|
|
101
|
+
const before = code.slice(0, callStart).replace(/\s+$/, "");
|
|
102
|
+
if (before.length === 0) return "anonymous";
|
|
103
|
+
const varMatch = before.match(/(?:export\s+)?(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=\s*$/);
|
|
104
|
+
if (varMatch) return varMatch[1];
|
|
105
|
+
const propMatch = before.match(/([A-Za-z_$][\w$]*|['"]([^'"]+)['"])\s*:\s*$/);
|
|
106
|
+
if (propMatch) return propMatch[2] || propMatch[1];
|
|
107
|
+
return "anonymous";
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* 检查对象字面量文本中是否包含指定顶层属性。
|
|
111
|
+
*/
|
|
112
|
+
function objectHasProperty(objText, propName) {
|
|
113
|
+
return new RegExp(`\\b${propName}\\s*:`).test(objText);
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* 在调用参数区(argStart..end)内查找顶层逗号位置(深度 0 相对于调用括号)。
|
|
117
|
+
*
|
|
118
|
+
* 返回所有顶层逗号的索引数组。
|
|
119
|
+
*/
|
|
120
|
+
function findTopLevelCommas(code, argStart, end) {
|
|
121
|
+
const commas = [];
|
|
122
|
+
let depth = 0;
|
|
123
|
+
let i = argStart;
|
|
124
|
+
let inString = null;
|
|
125
|
+
let escaped = false;
|
|
126
|
+
let inTemplateExpr = 0;
|
|
127
|
+
while (i < end) {
|
|
128
|
+
const ch = code[i];
|
|
129
|
+
if (escaped) {
|
|
130
|
+
escaped = false;
|
|
131
|
+
i++;
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
if (ch === "\\") {
|
|
135
|
+
escaped = true;
|
|
136
|
+
i++;
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
if (inString) {
|
|
140
|
+
if (inString === "`" && ch === "$" && code[i + 1] === "{") {
|
|
141
|
+
inTemplateExpr++;
|
|
142
|
+
i += 2;
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
if (inString === "`" && ch === "}" && inTemplateExpr > 0) {
|
|
146
|
+
inTemplateExpr--;
|
|
147
|
+
i++;
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
150
|
+
if (ch === inString) inString = null;
|
|
151
|
+
i++;
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
if (ch === "\"" || ch === "'" || ch === "`") {
|
|
155
|
+
inString = ch;
|
|
156
|
+
i++;
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
if (ch === "/" && code[i + 1] === "/") {
|
|
160
|
+
while (i < end && code[i] !== "\n") i++;
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
if (ch === "/" && code[i + 1] === "*") {
|
|
164
|
+
i += 2;
|
|
165
|
+
while (i < end && !(code[i] === "*" && code[i + 1] === "/")) i++;
|
|
166
|
+
i += 2;
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
if (ch === "(" || ch === "[" || ch === "{") depth++;
|
|
170
|
+
else if (ch === ")" || ch === "]" || ch === "}") depth--;
|
|
171
|
+
else if (ch === "," && depth === 0) commas.push(i);
|
|
172
|
+
i++;
|
|
173
|
+
}
|
|
174
|
+
return commas;
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* 预扫描代码,返回所有"非代码"区间 `[start, end)`:注释(块/行)和字符串字面量。
|
|
178
|
+
*
|
|
179
|
+
* 用于跳过注释/字符串内的 `defineAction(` 匹配(如 JSDoc 示例或说明性字符串)。
|
|
180
|
+
*/
|
|
181
|
+
function findNonCodeRanges(code) {
|
|
182
|
+
const ranges = [];
|
|
183
|
+
const len = code.length;
|
|
184
|
+
let i = 0;
|
|
185
|
+
while (i < len) {
|
|
186
|
+
if (code[i] === "/" && code[i + 1] === "*") {
|
|
187
|
+
const start = i;
|
|
188
|
+
i += 2;
|
|
189
|
+
while (i < len && !(code[i] === "*" && code[i + 1] === "/")) i++;
|
|
190
|
+
i += 2;
|
|
191
|
+
ranges.push([start, Math.min(i, len)]);
|
|
192
|
+
continue;
|
|
193
|
+
}
|
|
194
|
+
if (code[i] === "/" && code[i + 1] === "/") {
|
|
195
|
+
const start = i;
|
|
196
|
+
i += 2;
|
|
197
|
+
while (i < len && code[i] !== "\n") i++;
|
|
198
|
+
ranges.push([start, i]);
|
|
199
|
+
continue;
|
|
200
|
+
}
|
|
201
|
+
if (code[i] === "\"" || code[i] === "'" || code[i] === "`") {
|
|
202
|
+
const start = i;
|
|
203
|
+
const quote = code[i];
|
|
204
|
+
i++;
|
|
205
|
+
while (i < len) {
|
|
206
|
+
if (code[i] === "\\") {
|
|
207
|
+
i += 2;
|
|
208
|
+
continue;
|
|
209
|
+
}
|
|
210
|
+
if (quote === "`" && code[i] === "$" && code[i + 1] === "{") {
|
|
211
|
+
i += 2;
|
|
212
|
+
let depth = 1;
|
|
213
|
+
while (i < len && depth > 0) {
|
|
214
|
+
if (code[i] === "{") depth++;
|
|
215
|
+
else if (code[i] === "}") depth--;
|
|
216
|
+
i++;
|
|
217
|
+
}
|
|
218
|
+
continue;
|
|
219
|
+
}
|
|
220
|
+
if (code[i] === quote) {
|
|
221
|
+
i++;
|
|
222
|
+
break;
|
|
223
|
+
}
|
|
224
|
+
i++;
|
|
225
|
+
}
|
|
226
|
+
ranges.push([start, i]);
|
|
227
|
+
continue;
|
|
228
|
+
}
|
|
229
|
+
i++;
|
|
230
|
+
}
|
|
231
|
+
return ranges;
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* 检查位置 `pos` 是否落在任意区间内。
|
|
235
|
+
*/
|
|
236
|
+
function isInsideRanges(pos, ranges) {
|
|
237
|
+
for (const [start, end] of ranges) if (pos >= start && pos < end) return true;
|
|
238
|
+
return false;
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* 提取源码中所有 `defineAction(...)` 调用信息。
|
|
242
|
+
*
|
|
243
|
+
* 跳过注释内的匹配(如 JSDoc 示例)和函数声明(`function defineAction(`)。
|
|
244
|
+
*/
|
|
245
|
+
function findDefineActionCalls(code) {
|
|
246
|
+
const results = [];
|
|
247
|
+
const nonCodeRanges = findNonCodeRanges(code);
|
|
248
|
+
let m;
|
|
249
|
+
DEFINE_ACTION_RE.lastIndex = 0;
|
|
250
|
+
while ((m = DEFINE_ACTION_RE.exec(code)) !== null) {
|
|
251
|
+
const start = m.index;
|
|
252
|
+
if (isInsideRanges(start, nonCodeRanges)) continue;
|
|
253
|
+
const before = code.slice(0, start);
|
|
254
|
+
if (/\bfunction\s+$/.test(before)) continue;
|
|
255
|
+
const argStart = m.index + m[0].length;
|
|
256
|
+
const end = findBalanced(code, "(", ")", argStart);
|
|
257
|
+
if (end === null) continue;
|
|
258
|
+
const commas = findTopLevelCommas(code, argStart, end);
|
|
259
|
+
let hasOptionsObject = false;
|
|
260
|
+
let optionsBraceStart = -1;
|
|
261
|
+
let optionsBraceEnd = -1;
|
|
262
|
+
let optionsHasName = false;
|
|
263
|
+
let optionsHasFilePath = false;
|
|
264
|
+
let optionsObjText = null;
|
|
265
|
+
let scanIdx = commas.length > 0 ? commas[commas.length - 1] + 1 : argStart;
|
|
266
|
+
while (scanIdx < end && /\s/.test(code[scanIdx])) scanIdx++;
|
|
267
|
+
if (scanIdx < end && code[scanIdx] === "{") {
|
|
268
|
+
const braceEnd = findBalanced(code, "{", "}", scanIdx + 1);
|
|
269
|
+
if (braceEnd !== null && braceEnd < end) {
|
|
270
|
+
let after = braceEnd + 1;
|
|
271
|
+
while (after < end && /\s/.test(code[after])) after++;
|
|
272
|
+
if (after === end) {
|
|
273
|
+
hasOptionsObject = true;
|
|
274
|
+
optionsBraceStart = scanIdx;
|
|
275
|
+
optionsBraceEnd = braceEnd;
|
|
276
|
+
optionsObjText = code.slice(scanIdx + 1, braceEnd);
|
|
277
|
+
optionsHasName = objectHasProperty(optionsObjText, "name");
|
|
278
|
+
optionsHasFilePath = objectHasProperty(optionsObjText, "filePath");
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
const name = inferName(code, start, optionsObjText);
|
|
283
|
+
results.push({
|
|
284
|
+
start,
|
|
285
|
+
argStart,
|
|
286
|
+
end,
|
|
287
|
+
name,
|
|
288
|
+
hasOptionsObject,
|
|
289
|
+
optionsBraceStart,
|
|
290
|
+
optionsBraceEnd,
|
|
291
|
+
optionsHasName,
|
|
292
|
+
optionsHasFilePath
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
results.sort((a, b) => b.start - a.start);
|
|
296
|
+
return results;
|
|
297
|
+
}
|
|
298
|
+
/**
|
|
299
|
+
* 检测源码是否包含 `defineAction(` 调用。
|
|
300
|
+
*/
|
|
301
|
+
function hasDefineActionCall(code) {
|
|
302
|
+
DEFINE_ACTION_RE.lastIndex = 0;
|
|
303
|
+
return DEFINE_ACTION_RE.test(code);
|
|
304
|
+
}
|
|
305
|
+
/**
|
|
306
|
+
* 服务端转换:为每个 `defineAction()` 调用注入 `filePath`(以及缺失时的 `name`),
|
|
307
|
+
* 确保 action ID 在 dev / build 之间稳定。
|
|
308
|
+
*
|
|
309
|
+
* 返回转换后的代码;若无 `defineAction` 调用则返回 `null`。
|
|
310
|
+
*/
|
|
311
|
+
function transformActionsForServer(code, filePath, root) {
|
|
312
|
+
if (!hasDefineActionCall(code)) return null;
|
|
313
|
+
const calls = findDefineActionCalls(code);
|
|
314
|
+
if (calls.length === 0) return null;
|
|
315
|
+
const relPath = toProjectRelative(filePath, root);
|
|
316
|
+
let result = code;
|
|
317
|
+
for (const call of calls) {
|
|
318
|
+
const injectName = !call.optionsHasName ? call.name : null;
|
|
319
|
+
const injectFilePath = !call.optionsHasFilePath ? relPath : null;
|
|
320
|
+
if (call.hasOptionsObject) {
|
|
321
|
+
const injectParts = [];
|
|
322
|
+
if (injectFilePath) injectParts.push(`filePath: ${JSON.stringify(injectFilePath)}`);
|
|
323
|
+
if (injectName) injectParts.push(`name: ${JSON.stringify(injectName)}`);
|
|
324
|
+
if (injectParts.length === 0) continue;
|
|
325
|
+
const injectText = `${injectParts.join(", ")}, `;
|
|
326
|
+
const insertPos = call.optionsBraceStart + 1;
|
|
327
|
+
result = result.slice(0, insertPos) + injectText + result.slice(insertPos);
|
|
328
|
+
} else {
|
|
329
|
+
const opts = {};
|
|
330
|
+
if (injectFilePath) opts.filePath = JSON.stringify(injectFilePath);
|
|
331
|
+
if (injectName) opts.name = JSON.stringify(injectName);
|
|
332
|
+
const optsText = `{ ${Object.entries(opts).map(([k, v]) => `${k}: ${v}`).join(", ")} }`;
|
|
333
|
+
const insertText = `${result.slice(call.argStart, call.end).replace(/\s+$/, "").length > 0 ? ", " : ""}${optsText}`;
|
|
334
|
+
result = result.slice(0, call.end) + insertText + result.slice(call.end);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
return result;
|
|
338
|
+
}
|
|
339
|
+
/**
|
|
340
|
+
* 客户端转换:将每个 `defineAction(...)` 调用替换为
|
|
341
|
+
* `__ubean_createActionStub('<id>')`(别名 import,避免与用户代码冲突)。
|
|
342
|
+
*
|
|
343
|
+
* 返回转换后的代码(含注入的 import);若无 `defineAction` 调用则返回 `null`。
|
|
344
|
+
*/
|
|
345
|
+
function transformActionsForClient(code, filePath, root) {
|
|
346
|
+
if (!hasDefineActionCall(code)) return null;
|
|
347
|
+
const calls = findDefineActionCalls(code);
|
|
348
|
+
if (calls.length === 0) return null;
|
|
349
|
+
const relPath = toProjectRelative(filePath, root);
|
|
350
|
+
let result = code;
|
|
351
|
+
for (const call of calls) {
|
|
352
|
+
const id = createActionId(relPath, call.name);
|
|
353
|
+
const replacement = `__ubean_createActionStub(${JSON.stringify(id)})`;
|
|
354
|
+
result = result.slice(0, call.start) + replacement + result.slice(call.end + 1);
|
|
355
|
+
}
|
|
356
|
+
const importStmt = `import { createActionStub as __ubean_createActionStub } from '@ubean/routes/runtime';\n`;
|
|
357
|
+
if (!result.includes("@ubean/routes/runtime'")) result = importStmt + result;
|
|
358
|
+
return result;
|
|
359
|
+
}
|
|
360
|
+
/**
|
|
361
|
+
* Vite 插件:Server Actions 的客户端/服务端拆分。
|
|
362
|
+
*
|
|
363
|
+
* - 服务端(SSR):注入 `filePath`/`name` 到 `defineAction()` 选项。
|
|
364
|
+
* - 客户端(浏览器):将 `defineAction(...)` 替换为 `createActionStub('<id>')`。
|
|
365
|
+
*/
|
|
366
|
+
function ubeanServerActionsPlugin(options = {}) {
|
|
367
|
+
const root = options.root || (typeof process !== "undefined" ? process.cwd() : "");
|
|
368
|
+
return {
|
|
369
|
+
name: "ubean:server-actions",
|
|
370
|
+
enforce: "pre",
|
|
371
|
+
transform(code, id, transformOptions) {
|
|
372
|
+
if (id.includes("/node_modules/") || id.includes("\0")) return null;
|
|
373
|
+
if (!/\.(ts|js|mts|mjs|tsx|jsx)$/.test(id)) return null;
|
|
374
|
+
if (!hasDefineActionCall(code)) return null;
|
|
375
|
+
if (transformOptions?.ssr === true) {
|
|
376
|
+
const transformed = transformActionsForServer(code, id, root);
|
|
377
|
+
return transformed ? { code: transformed } : null;
|
|
378
|
+
}
|
|
379
|
+
const transformed = transformActionsForClient(code, id, root);
|
|
380
|
+
return transformed ? { code: transformed } : null;
|
|
381
|
+
}
|
|
382
|
+
};
|
|
383
|
+
}
|
|
384
|
+
//#endregion
|
|
385
|
+
export { findDefineActionCalls, hasDefineActionCall, transformActionsForClient, transformActionsForServer, ubeanServerActionsPlugin };
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import { ScanResult } from "@ubean/scan";
|
|
2
|
+
import { Import, InlinePreset } from "unimport";
|
|
3
|
+
//#region src/codegen/auto-imports.d.ts
|
|
4
|
+
interface AutoImportOptions {
|
|
5
|
+
cwd: string;
|
|
6
|
+
srcDir: string;
|
|
7
|
+
buildDir: string;
|
|
8
|
+
composablesDirs?: string[];
|
|
9
|
+
componentsDirs?: string[];
|
|
10
|
+
dirs?: {
|
|
11
|
+
composables?: string;
|
|
12
|
+
components?: string;
|
|
13
|
+
};
|
|
14
|
+
imports?: {
|
|
15
|
+
autoImport?: boolean;
|
|
16
|
+
global?: boolean;
|
|
17
|
+
};
|
|
18
|
+
components?: {
|
|
19
|
+
autoImport?: boolean;
|
|
20
|
+
directoryAsNamespace?: boolean;
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
declare const VUE_PRESET: InlinePreset;
|
|
24
|
+
declare const VUE_MACROS_PRESET: InlinePreset;
|
|
25
|
+
/**
|
|
26
|
+
* Client-safe symbols sourced from the first-class `ubean/client` entry.
|
|
27
|
+
* These are safe to auto-import in Vue components (browser-side) because
|
|
28
|
+
* `@ubean/client` has zero build-time dependencies and no `node:*` imports.
|
|
29
|
+
* (`ubean/runtime/vue` still works — it re-exports the same kernel — but
|
|
30
|
+
* new code should prefer `ubean/client`.)
|
|
31
|
+
*/
|
|
32
|
+
declare const UBEAN_CLIENT_PRESET: InlinePreset;
|
|
33
|
+
/**
|
|
34
|
+
* Server-only symbols that come from the main `ubean` package.
|
|
35
|
+
* These import the full ubean entry (which includes build tools like `vite`),
|
|
36
|
+
* so they must only be used in server-side files (API routes, middleware, etc.).
|
|
37
|
+
*/
|
|
38
|
+
declare const UBEAN_SERVER_PRESET: InlinePreset;
|
|
39
|
+
declare const HONO_OPENAPI_PRESET: InlinePreset;
|
|
40
|
+
declare const BUILTIN_PRESETS: InlinePreset[];
|
|
41
|
+
interface ComponentInfo {
|
|
42
|
+
name: string;
|
|
43
|
+
filePath: string;
|
|
44
|
+
importPath: string;
|
|
45
|
+
pascalName: string;
|
|
46
|
+
}
|
|
47
|
+
interface AutoImportResult {
|
|
48
|
+
composablesImports: Import[];
|
|
49
|
+
components: ComponentInfo[];
|
|
50
|
+
autoImportsDtsPath: string;
|
|
51
|
+
componentsDtsPath: string;
|
|
52
|
+
}
|
|
53
|
+
declare function generateAutoImports(_scanResult: ScanResult, options: AutoImportOptions): Promise<AutoImportResult>;
|
|
54
|
+
declare function getBuiltinComposables(): Import[];
|
|
55
|
+
declare function getUbeanAutoImportConfig(options?: {
|
|
56
|
+
cwd?: string;
|
|
57
|
+
srcDir?: string;
|
|
58
|
+
buildDir?: string;
|
|
59
|
+
composablesDirs?: string[];
|
|
60
|
+
}): {
|
|
61
|
+
imports: InlinePreset[];
|
|
62
|
+
dirs: string[];
|
|
63
|
+
dts: string;
|
|
64
|
+
vueTemplate: boolean;
|
|
65
|
+
eslintrc: {
|
|
66
|
+
enabled: boolean;
|
|
67
|
+
};
|
|
68
|
+
};
|
|
69
|
+
declare function getUbeanComponentsConfig(options?: {
|
|
70
|
+
cwd?: string;
|
|
71
|
+
srcDir?: string;
|
|
72
|
+
buildDir?: string;
|
|
73
|
+
componentsDirs?: string[];
|
|
74
|
+
directoryAsNamespace?: boolean;
|
|
75
|
+
}): {
|
|
76
|
+
dirs: string[];
|
|
77
|
+
extensions: string[];
|
|
78
|
+
directoryAsNamespace: boolean;
|
|
79
|
+
dts: string;
|
|
80
|
+
deep: boolean;
|
|
81
|
+
};
|
|
82
|
+
declare function generateImportsTransform(imports: Import[]): {
|
|
83
|
+
code: string;
|
|
84
|
+
map?: null;
|
|
85
|
+
};
|
|
86
|
+
//#endregion
|
|
87
|
+
//#region src/codegen/route-types.d.ts
|
|
88
|
+
interface RouteTypesOptions {
|
|
89
|
+
/** Project root directory (absolute). Used to compute portable relative `filePath` values. */
|
|
90
|
+
cwd: string;
|
|
91
|
+
outDir: string;
|
|
92
|
+
fileName?: string;
|
|
93
|
+
}
|
|
94
|
+
declare function generateRouteTypes(result: ScanResult, options: RouteTypesOptions): Promise<string>;
|
|
95
|
+
interface PageTypesOptions {
|
|
96
|
+
outDir: string;
|
|
97
|
+
fileName?: string;
|
|
98
|
+
}
|
|
99
|
+
declare function generatePageTypes(result: ScanResult, options: PageTypesOptions): Promise<string>;
|
|
100
|
+
//#endregion
|
|
101
|
+
//#region src/codegen/i18n-types.d.ts
|
|
102
|
+
interface I18nTypesOptions {
|
|
103
|
+
outDir: string;
|
|
104
|
+
fileName?: string;
|
|
105
|
+
defaultLocale?: string;
|
|
106
|
+
}
|
|
107
|
+
/** Convert a JSON message tree into a TypeScript object type body. */
|
|
108
|
+
declare function messagesToTsType(value: unknown, indent?: number): string;
|
|
109
|
+
/**
|
|
110
|
+
* Generate `.ubean/i18n.d.ts` augmenting vue-i18n `DefineLocaleMessage`
|
|
111
|
+
* from the default locale JSON. Users can also hand-write the same
|
|
112
|
+
* module augmentation if they prefer.
|
|
113
|
+
*/
|
|
114
|
+
declare function generateI18nTypes(result: ScanResult, options: I18nTypesOptions): Promise<string | null>;
|
|
115
|
+
//#endregion
|
|
116
|
+
//#region src/codegen/openapi-types.d.ts
|
|
117
|
+
/**
|
|
118
|
+
* OpenAPI 类型生成
|
|
119
|
+
*
|
|
120
|
+
* 从 OpenAPI schema 生成 `paths` 类型声明文件(.ubean/openapi.d.ts),
|
|
121
|
+
* 供 `createTypedClient<paths>` / `createTypedFlatClient<paths>` / `callTypedInternal<paths>` 消费。
|
|
122
|
+
*
|
|
123
|
+
* 底层使用 `openapi-typescript` 编译 schema 为 TypeScript 类型声明。
|
|
124
|
+
*/
|
|
125
|
+
interface GenerateOpenApiTypesOptions {
|
|
126
|
+
/** 输出目录(如 .ubean) */
|
|
127
|
+
outDir: string;
|
|
128
|
+
/** 输出文件名,默认 `openapi.d.ts` */
|
|
129
|
+
fileName?: string;
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* 从 OpenAPI schema 对象生成 paths 类型声明文件。
|
|
133
|
+
*/
|
|
134
|
+
declare function generateOpenApiTypes(schema: unknown, options: GenerateOpenApiTypesOptions): Promise<string>;
|
|
135
|
+
/**
|
|
136
|
+
* 从 dev server 的 `/_openapi.json` 路由获取 schema 并生成类型声明文件。
|
|
137
|
+
*/
|
|
138
|
+
declare function generateOpenApiTypesFromServer(baseUrl: string, options: GenerateOpenApiTypesOptions): Promise<string>;
|
|
139
|
+
//#endregion
|
|
140
|
+
//#region src/codegen/index.d.ts
|
|
141
|
+
declare const CODEGEN_CONTRACT_VERSION: 1;
|
|
142
|
+
interface CodegenFileContract {
|
|
143
|
+
name: string;
|
|
144
|
+
module: string | null;
|
|
145
|
+
required: boolean;
|
|
146
|
+
types: string[];
|
|
147
|
+
}
|
|
148
|
+
declare const CODEGEN_FILES: readonly CodegenFileContract[];
|
|
149
|
+
interface CodegenManifest {
|
|
150
|
+
contractVersion: typeof CODEGEN_CONTRACT_VERSION;
|
|
151
|
+
generatedAt: string;
|
|
152
|
+
files: Array<{
|
|
153
|
+
name: string;
|
|
154
|
+
module: string | null;
|
|
155
|
+
required: boolean;
|
|
156
|
+
types: string[];
|
|
157
|
+
generated: boolean;
|
|
158
|
+
}>;
|
|
159
|
+
}
|
|
160
|
+
interface CodegenOptions extends Omit<AutoImportOptions, 'cwd' | 'srcDir' | 'buildDir'> {
|
|
161
|
+
cwd: string;
|
|
162
|
+
srcDir: string;
|
|
163
|
+
buildDir: string;
|
|
164
|
+
}
|
|
165
|
+
interface CodegenResult {
|
|
166
|
+
routeTypesPath: string;
|
|
167
|
+
pageTypesPath: string;
|
|
168
|
+
i18nTypesPath?: string | null;
|
|
169
|
+
autoImportsDtsPath?: string;
|
|
170
|
+
componentsDtsPath?: string;
|
|
171
|
+
generated: string[];
|
|
172
|
+
}
|
|
173
|
+
declare function generateTypes(result: ScanResult, options: CodegenOptions): Promise<CodegenResult>;
|
|
174
|
+
//#endregion
|
|
175
|
+
export { type AutoImportOptions, type AutoImportResult, BUILTIN_PRESETS, CODEGEN_CONTRACT_VERSION, CODEGEN_FILES, CodegenFileContract, CodegenManifest, CodegenOptions, CodegenResult, type ComponentInfo, type GenerateOpenApiTypesOptions, HONO_OPENAPI_PRESET, type I18nTypesOptions, type Import, type InlinePreset, type PageTypesOptions, type RouteTypesOptions, UBEAN_CLIENT_PRESET, UBEAN_SERVER_PRESET, VUE_MACROS_PRESET, VUE_PRESET, generateAutoImports, generateI18nTypes, generateImportsTransform, generateOpenApiTypes, generateOpenApiTypesFromServer, generatePageTypes, generateRouteTypes, generateTypes, getBuiltinComposables, getUbeanAutoImportConfig, getUbeanComponentsConfig, messagesToTsType };
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { _ as generateImportsTransform, a as generateOpenApiTypesFromServer, b as getUbeanComponentsConfig, c as generateI18nTypes, d as HONO_OPENAPI_PRESET, f as UBEAN_CLIENT_PRESET, g as generateAutoImports, h as VUE_PRESET, i as generateOpenApiTypes, l as messagesToTsType, m as VUE_MACROS_PRESET, n as CODEGEN_FILES, o as generatePageTypes, p as UBEAN_SERVER_PRESET, r as generateTypes, s as generateRouteTypes, t as CODEGEN_CONTRACT_VERSION, u as BUILTIN_PRESETS, v as getBuiltinComposables, y as getUbeanAutoImportConfig } from "../codegen-D6ewf11I.js";
|
|
2
|
+
export { BUILTIN_PRESETS, CODEGEN_CONTRACT_VERSION, CODEGEN_FILES, HONO_OPENAPI_PRESET, UBEAN_CLIENT_PRESET, UBEAN_SERVER_PRESET, VUE_MACROS_PRESET, VUE_PRESET, generateAutoImports, generateI18nTypes, generateImportsTransform, generateOpenApiTypes, generateOpenApiTypesFromServer, generatePageTypes, generateRouteTypes, generateTypes, getBuiltinComposables, getUbeanAutoImportConfig, getUbeanComponentsConfig, messagesToTsType };
|