@ubean/build 0.5.2 → 0.6.0-beta.2

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.
@@ -63,11 +63,16 @@ function findBalanced(code, openChar, closeChar, startIdx) {
63
63
  }
64
64
  /**
65
65
  * 计算项目相对路径(用于 action ID 生成)。
66
+ *
67
+ * **自己负责去掉 query 与虚拟前缀**:调用方可能传原始 id(`Page.vue?vue&type=script&lang.ts`),
68
+ * 带 query 算出的 id 与不带 query 的不是同一个 —— 客户端 stub 与 服务端注册因此会对不上。
69
+ * 插件入口已经规范化过一次,这里再兜一次,让这两个变换函数单独用也正确。
66
70
  */
67
71
  function toProjectRelative(filePath, root) {
68
- let rel = filePath;
72
+ const cleanPath = filePath.split("?")[0].replace(/^\0/, "");
73
+ let rel = cleanPath;
69
74
  if (root) try {
70
- rel = relative(root, filePath);
75
+ rel = relative(root, cleanPath);
71
76
  } catch {}
72
77
  return rel.replace(/\\/g, "/");
73
78
  }
@@ -353,8 +358,51 @@ function transformActionsForClient(code, filePath, root) {
353
358
  const replacement = `__ubean_createActionStub(${JSON.stringify(id)})`;
354
359
  result = result.slice(0, call.start) + replacement + result.slice(call.end + 1);
355
360
  }
361
+ result = stripUnusedSpecifiers(result, [
362
+ "defineAction",
363
+ "fail",
364
+ "ActionError",
365
+ "defineServerFn"
366
+ ]);
356
367
  const importStmt = `import { createActionStub as __ubean_createActionStub } from '@ubean/routes/runtime';\n`;
357
- if (!result.includes("@ubean/routes/runtime'")) result = importStmt + result;
368
+ if (!result.includes("@ubean/routes/runtime'")) result = injectImport(result, importStmt);
369
+ return result;
370
+ }
371
+ /**
372
+ * 注入 import:JS/TS 放到文件顶部;**SFC 放到 `<script>` 块内**。
373
+ *
374
+ * 必须是块内 —— `@vitejs/plugin-vue` 会把 script 块的 import 提升进它编译出的主模块,放在 SFC
375
+ * 顶部(`<script>` 之前)会直接变成非法 SFC。这也是「转换必须落在主模块而不是 `?type=script`
376
+ * 子请求」的原因:主模块才是 Vue 真正编译的那份(脚本子请求的转换结果不会被使用)。
377
+ */
378
+ function injectImport(code, importStmt) {
379
+ if (!code.trimStart().startsWith("<")) return importStmt + code;
380
+ const scriptTag = /<script\b[^>]*>/.exec(code);
381
+ if (!scriptTag) return importStmt + code;
382
+ const insertAt = scriptTag.index + scriptTag[0].length;
383
+ return `${code.slice(0, insertAt)}\n${importStmt}${code.slice(insertAt)}`;
384
+ }
385
+ /**
386
+ * 删除已无引用的具名 import 说明符(并清理空掉的 import 语句)。
387
+ *
388
+ * 只处理「命名导入 + from '…'」这一种形态(框架约定:服务端 API 从 `ubean/server` 等入口具名导入)。
389
+ * 判定用「删除 import 语句后名字是否还出现」——够用且不引入解析器依赖;名字出现在字符串/注释里
390
+ * 时会保守地保留该说明符(保留是安全的,删除才危险)。
391
+ */
392
+ function stripUnusedSpecifiers(code, names) {
393
+ let result = code;
394
+ for (const name of new Set(names)) {
395
+ const match = new RegExp(`^import\\s*\\{([^}]*)\\}\\s*from\\s*(['"][^'"]+['"]);?[ \\t]*$`, "m").exec(result);
396
+ if (!match) continue;
397
+ const specifiers = match[1].split(",").map((part) => part.trim()).filter(Boolean);
398
+ const target = specifiers.find((spec) => spec === name || spec === `${name} as ${name}`);
399
+ if (!target) continue;
400
+ const withoutImport = result.replace(match[0], "");
401
+ if (new RegExp(`\\b${name}\\b`).test(withoutImport)) continue;
402
+ const remaining = specifiers.filter((spec) => spec !== target);
403
+ const replacement = remaining.length > 0 ? `import { ${remaining.join(", ")} } from ${match[2]};` : "";
404
+ result = result.replace(match[0], replacement);
405
+ }
358
406
  return result;
359
407
  }
360
408
  /**
@@ -370,13 +418,14 @@ function ubeanServerActionsPlugin(options = {}) {
370
418
  enforce: "pre",
371
419
  transform(code, id, transformOptions) {
372
420
  if (id.includes("/node_modules/") || id.includes("\0")) return null;
373
- if (!/\.(ts|js|mts|mjs|tsx|jsx)$/.test(id)) return null;
421
+ const [filePath, query = ""] = id.split("?");
422
+ if (!(filePath.endsWith(".vue") && (!query || /(^|&)(vue|type=script)(&|$)/.test(query))) && !/\.(ts|js|mts|mjs|tsx|jsx)$/.test(filePath)) return null;
374
423
  if (!hasDefineActionCall(code)) return null;
375
424
  if (transformOptions?.ssr === true) {
376
- const transformed = transformActionsForServer(code, id, root);
425
+ const transformed = transformActionsForServer(code, filePath, root);
377
426
  return transformed ? { code: transformed } : null;
378
427
  }
379
- const transformed = transformActionsForClient(code, id, root);
428
+ const transformed = transformActionsForClient(code, filePath, root);
380
429
  return transformed ? { code: transformed } : null;
381
430
  }
382
431
  };
@@ -99,6 +99,8 @@ interface AutoImportResult {
99
99
  components: ComponentInfo[];
100
100
  autoImportsDtsPath: string;
101
101
  componentsDtsPath: string;
102
+ /** 虚拟组件(配对/单边基名)的 ambient 声明文件。没有虚拟组件时内容为空声明。 */
103
+ virtualComponentsDtsPath: string;
102
104
  }
103
105
  export declare function generateAutoImports(_scanResult: ScanResult, options: GenerateAutoImportsOptions): Promise<AutoImportResult>;
104
106
  export declare function getBuiltinComposables(): Import[];
@@ -228,6 +230,7 @@ export interface CodegenResult {
228
230
  i18nTypesPath?: string | null;
229
231
  autoImportsDtsPath?: string;
230
232
  componentsDtsPath?: string;
233
+ virtualComponentsDtsPath?: string;
231
234
  generated: string[];
232
235
  }
233
236
  export declare function generateTypes(result: ScanResult, options: CodegenOptions): Promise<CodegenResult>;
@@ -1,2 +1,2 @@
1
- import { C as getUbeanComponentsConfig, E as toArray, S as getUbeanAutoImportConfig, T as resolveComponentsConfig, _ as VUE_ROUTER_PRESET, a as generateOpenApiTypesFromServer, b as getAutoImportPresets, c as generateI18nTypes, d as HONO_OPENAPI_PRESET, f as UBEAN_CLIENT_PRESET, g as VUE_PRESET, h as VUE_MACROS_PRESET, i as generateOpenApiTypes, l as messagesToTsType, m as VUE_I18N_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 generateAutoImports, w as resolveAutoImportsConfig, x as getBuiltinComposables, y as generateImportsTransform } from "../codegen-B3oGeRtK.js";
1
+ import { C as getUbeanAutoImportConfig, D as toArray, E as resolveComponentsConfig, S as getBuiltinComposables, T as resolveAutoImportsConfig, _ as VUE_PRESET, a as generateOpenApiTypesFromServer, b as generateImportsTransform, c as generateI18nTypes, f as HONO_OPENAPI_PRESET, g as VUE_MACROS_PRESET, h as VUE_I18N_PRESET, i as generateOpenApiTypes, l as messagesToTsType, m as UBEAN_SERVER_PRESET, n as CODEGEN_FILES, o as generatePageTypes, p as UBEAN_CLIENT_PRESET, r as generateTypes, s as generateRouteTypes, t as CODEGEN_CONTRACT_VERSION, u as BUILTIN_PRESETS, v as VUE_ROUTER_PRESET, w as getUbeanComponentsConfig, x as getAutoImportPresets, y as generateAutoImports } from "../codegen-Up8-XhRo.js";
2
2
  export { BUILTIN_PRESETS, CODEGEN_CONTRACT_VERSION, CODEGEN_FILES, HONO_OPENAPI_PRESET, UBEAN_CLIENT_PRESET, UBEAN_SERVER_PRESET, VUE_I18N_PRESET, VUE_MACROS_PRESET, VUE_PRESET, VUE_ROUTER_PRESET, generateAutoImports, generateI18nTypes, generateImportsTransform, generateOpenApiTypes, generateOpenApiTypesFromServer, generatePageTypes, generateRouteTypes, generateTypes, getAutoImportPresets, getBuiltinComposables, getUbeanAutoImportConfig, getUbeanComponentsConfig, messagesToTsType, resolveAutoImportsConfig, resolveComponentsConfig, toArray };
@@ -260,23 +260,55 @@ function transformImportPath(filePath, srcDir) {
260
260
  function isPackageSource(from) {
261
261
  return !from.startsWith(".") && !from.startsWith("/") && !from.startsWith("~");
262
262
  }
263
+ /**
264
+ * `.server.vue` / `.client.vue` 不是独立组件,**不能**进自动导入表。
265
+ *
266
+ * 它们是同一个组件的两个变体:`.client.vue` 客户端独有、`.server.vue` 服务端独有,
267
+ * 成对出现时(`Foo.server.vue` + `Foo.client.vue`)由 `@ubean/islands` 的
268
+ * `resolveId` 按基名 `Foo.vue` 生成配对包装。用户侧的用法是**显式 import**
269
+ * (`import Foo from './Foo.vue'` 或直接 import 某一半),不存在 `<Foo.server />` 这种写法。
270
+ *
271
+ * 而按文件名派生名字会把它们扫成 `Foo.server` / `Foo.client` —— 这些名字进了
272
+ * `components.d.ts` 就是**非法 TypeScript**:ubean 自己的写入器会产出未加引号的
273
+ * 带点键(接口成员不允许限定名,`@ts-nocheck` 也压不住语法错误),
274
+ * unplugin-vue-components 重写时会产出 `const 'Foo.server': …`(`declare global` 块里
275
+ * 非法)—— 结果示例项目的 `pnpm type-check` 直接红。
276
+ *
277
+ * codegen 侧在 `scanComponentsDir` 里按这个形态分区(半成品不进组件表,但会被收成虚拟组件);
278
+ * unplugin 没有分区逻辑,所以这份 glob 直接喂给它的 `globsExclude`(`./vue-plugin.ts`),
279
+ * 保证两个写入器都不产出这些条目。
280
+ */
281
+ const COMPONENT_HALF_GLOBS = ["**/*.server.vue", "**/*.client.vue"];
263
282
  async function scanComponentsDir(dir, srcDir, directoryAsNamespace, ignore = [
264
283
  "**/*.test.*",
265
284
  "**/*.spec.*",
266
285
  "**/_*"
267
286
  ]) {
268
287
  const components = [];
288
+ const virtualComponents = [];
269
289
  const files = await glob("**/*.vue", {
270
290
  cwd: dir,
271
291
  dot: true,
272
292
  ignore,
273
293
  absolute: true
274
294
  }).catch(() => []);
295
+ /** 基名 → 半成品真实路径(`/foo/Base.server.vue` 等)。 */
296
+ const halves = /* @__PURE__ */ new Map();
297
+ /** 同目录下真实存在的 `.vue` 基名 —— 它们由 TS 自行解析,不需要 ambient 声明。 */
298
+ const realBases = /* @__PURE__ */ new Set();
275
299
  for (const fullPath of files.sort()) {
276
300
  const relativeToSrc = toPosixPath(relative(srcDir, fullPath));
277
301
  const relativeToDir = toPosixPath(relative(dir, fullPath));
278
302
  const base = fileBasename(fullPath);
279
303
  if (base.startsWith("_")) continue;
304
+ const half = /^(.*)\.(server|client)$/.exec(base);
305
+ if (half) {
306
+ const entry = halves.get(half[1]) ?? {};
307
+ entry[half[2]] = fullPath;
308
+ halves.set(half[1], entry);
309
+ continue;
310
+ }
311
+ realBases.add(base);
280
312
  let name;
281
313
  if (directoryAsNamespace) {
282
314
  const dirPart = fileDirname(relativeToDir) === "." ? "" : fileDirname(relativeToDir);
@@ -291,7 +323,18 @@ async function scanComponentsDir(dir, srcDir, directoryAsNamespace, ignore = [
291
323
  pascalName: name
292
324
  });
293
325
  }
294
- return components;
326
+ for (const [baseName, pair] of halves) {
327
+ if (realBases.has(baseName)) continue;
328
+ virtualComponents.push({
329
+ baseName,
330
+ name: toPascalCase(baseName),
331
+ typeSourcePath: pair.server ?? pair.client
332
+ });
333
+ }
334
+ return {
335
+ components,
336
+ virtualComponents
337
+ };
295
338
  }
296
339
  async function generateAutoImports(_scanResult, options) {
297
340
  const { cwd, srcDir, buildDir, dirs = {}, autoImports: autoImportsInput, components: componentsInput } = options;
@@ -304,6 +347,7 @@ async function generateAutoImports(_scanResult, options) {
304
347
  const componentsDir = dirs.components || "components";
305
348
  let composablesImports = [];
306
349
  let components = [];
350
+ const virtualComponents = [];
307
351
  const autoImportsDtsPath = join(outDir, "auto-imports.d.ts");
308
352
  const componentsDtsPath = join(outDir, "components.d.ts");
309
353
  if (resolvedAutoImports.enabled) {
@@ -330,29 +374,86 @@ async function generateAutoImports(_scanResult, options) {
330
374
  from: transformImportPath(imp.from, srcDir)
331
375
  };
332
376
  });
333
- const dtsContent = toTypeDeclarationFile(composablesImports, { resolvePath: (imp) => {
334
- if (isPackageSource(imp.from)) return imp.from;
335
- return transformImportPath(imp.from, srcDir);
336
- } });
337
- await writeFile(autoImportsDtsPath, dtsContent, "utf-8");
377
+ const resolveFrom = (from) => isPackageSource(from) ? from : transformImportPath(from, srcDir);
378
+ const dtsContent = toTypeDeclarationFile(composablesImports, { resolvePath: (imp) => resolveFrom(imp.from) });
379
+ await writeFile(autoImportsDtsPath, `${dtsContent}\n\n${buildTemplateAutoImportBlock(composablesImports, resolveFrom)}`, "utf-8");
338
380
  } else await writeFile(autoImportsDtsPath, "// Auto-generated by ubean - auto-imports disabled\n/* eslint-disable */\n// @ts-nocheck\nexport {}\n", "utf-8");
339
381
  if (resolvedComponents.enabled) {
340
382
  const allComponentsDirs = [join(srcDir, componentsDir), ...resolvedComponents.options.dirs ?? []];
341
383
  for (const dir of allComponentsDirs) {
342
384
  const scanned = await scanComponentsDir(dir, srcDir, directoryAsNamespace);
343
- components.push(...scanned);
385
+ components.push(...scanned.components);
386
+ virtualComponents.push(...scanned.virtualComponents);
344
387
  }
345
388
  }
346
389
  const componentsDts = generateComponentsDts(components, componentsDtsPath);
347
390
  await writeFile(componentsDtsPath, componentsDts, "utf-8");
391
+ const virtualComponentsDtsPath = join(outDir, "virtual-components.d.ts");
392
+ await writeFile(virtualComponentsDtsPath, generateVirtualComponentsDts(virtualComponents, virtualComponentsDtsPath), "utf-8");
348
393
  return {
349
394
  composablesImports,
350
395
  components,
351
396
  autoImportsDtsPath,
352
- componentsDtsPath
397
+ componentsDtsPath,
398
+ virtualComponentsDtsPath
353
399
  };
354
400
  }
355
401
  /**
402
+ * 生成「模板里的自动导入」声明段(对齐 `AutoImport({ vueTemplate: true })` 的产物)。
403
+ *
404
+ * **为什么必须自己产出这段**:模板表达式里的标识符由 vue 的**组件实例类型**解析
405
+ * (vue-tsc 把模板编译成 `__VLS_ctx.x`,即 `ComponentCustomProperties`),而不是走模块作用域的
406
+ * 全局声明。只有 `declare global { const … }` 时,`<script setup>` 里的自动导入照常工作,而
407
+ * **模板里**用同一个 helper 会报 `Property 'isPageCached' does not exist on type '{ $: … }'`。
408
+ *
409
+ * ubean 的 codegen 与 unplugin-auto-import 写的是同一份文件(谁后写谁赢),所以两边都必须产出
410
+ * 这段:codegen 只在 `prepare` / CLI dev / build 跑,unplugin 在每次 Vite dev/build 重写 ——
411
+ * 少一段就会出现「先跑 CLI 时红、跑过一次 dev 后变绿」这种漂移。
412
+ */
413
+ function buildTemplateAutoImportBlock(imports, resolveFrom) {
414
+ return [
415
+ "// for vue template auto import",
416
+ "import { UnwrapRef } from 'vue'",
417
+ "declare module 'vue' {",
418
+ " interface GlobalComponents {}",
419
+ " interface ComponentCustomProperties {",
420
+ ...imports.filter((imp) => imp.name && imp.from && imp.type !== true).map((imp) => ` readonly ${toDtsKey(imp.name)}: UnwrapRef<typeof import('${resolveFrom(imp.from)}')['${imp.name}']>`).sort(),
421
+ " }",
422
+ "}"
423
+ ].join("\n");
424
+ }
425
+ /**
426
+ * 生成 `.ubean/virtual-components.d.ts` —— 虚拟组件(配对/单边基名)的 ambient 声明。
427
+ *
428
+ * 为什么必须单独一份文件:这份内容会被 `unplugin-vue-components` 的重写丢掉(它只捞
429
+ * `GlobalComponents` 接口里的条目,周围的 `declare module` 语句一律不保留),而
430
+ * `components.d.ts` 正是它拥有的文件。
431
+ *
432
+ * 用**通配**说明符(`*foo.vue`)而不是相对路径:`declare module` 里的相对路径只在「说明符
433
+ * 字符串完全相同」时匹配,而同一个虚拟组件可以从任意目录、任意相对路径导入。通配声明只在
434
+ * TS 无法解析到真实文件时才生效,因此不会遮蔽真实 `.vue` 文件。
435
+ *
436
+ * 类型取服务端变体(配对与 server-only 都有),单边 `.client.vue` 则取客户端变体 —— 与
437
+ * 运行时首屏渲染的那一侧一致。
438
+ */
439
+ function generateVirtualComponentsDts(virtual, dtsPath) {
440
+ const dtsDir = fileDirname(toPosixPath(normalize(dtsPath)));
441
+ const lines = [
442
+ "// Auto-generated by ubean - do not edit manually",
443
+ "/* eslint-disable */",
444
+ "// @ts-nocheck",
445
+ "",
446
+ "// 本文件刻意保持 script(非模块)语义,让下面的声明成为全局 ambient 声明。",
447
+ ""
448
+ ];
449
+ const sorted = [...virtual].sort((a, b) => a.baseName.localeCompare(b.baseName));
450
+ for (const comp of sorted) {
451
+ const rel = toPosixPath(relative(dtsDir, toPosixPath(normalize(comp.typeSourcePath))));
452
+ lines.push(`// ${comp.name}:磁盘上没有 ${comp.baseName}.vue,由 @ubean/islands 按同名 .server.vue / .client.vue 合成`, `declare module '*${comp.baseName}.vue' {`, ` const component: typeof import('./${rel}')['default'];`, " export default component;", "}", "");
453
+ }
454
+ return `${lines.join("\n")}\n`;
455
+ }
456
+ /**
356
457
  * Generate the `.ubean/components.d.ts` in a format compatible with
357
458
  * unplugin-vue-components (both writers own the same file).
358
459
  *
@@ -390,12 +491,26 @@ function generateComponentsDts(components, dtsPath) {
390
491
  " export interface GlobalComponents {"
391
492
  ];
392
493
  const sorted = [...entries.entries()].sort(([a], [b]) => a.localeCompare(b));
393
- lines.push(...sorted.map(([name, declaration]) => ` ${name}: ${declaration}`));
494
+ lines.push(...sorted.map(([name, declaration]) => ` ${toDtsKey(name)}: ${declaration}`));
394
495
  lines.push(" }");
395
496
  lines.push("}");
396
497
  lines.push("");
397
498
  return `${lines.join("\n")}\n`;
398
499
  }
500
+ /**
501
+ * 接口成员名 → 合法的 TS 键。
502
+ *
503
+ * 名字里含 `.` 时(如 `My.Comp.vue` 这种文件名)直接写就是**限定名**,在接口里是语法
504
+ * 错误 —— 而语法错误不受本文件顶部 `@ts-nocheck` 影响,整个 d.ts 会连累项目 type-check。
505
+ * 加引号后是合法的字符串字面量键,且 unplugin-vue-components 重写时也用同样形式
506
+ * (实测它把非标识符名写成 `'My.Comp': …`),两边不会互相反复改写。
507
+ *
508
+ * 注意这里只改**写法**不改名字:名字必须与 unplugin 的派生结果一致,否则两个写入器
509
+ * 会各自重写同一份文件。
510
+ */
511
+ function toDtsKey(name) {
512
+ return /^[A-Za-z_$][\w$]*$/.test(name) ? name : JSON.stringify(name);
513
+ }
399
514
  function getBuiltinComposables() {
400
515
  const imports = [];
401
516
  for (const preset of BUILTIN_PRESETS) for (const name of preset.imports) if (typeof name === "string") imports.push({
@@ -725,6 +840,12 @@ const CODEGEN_FILES = [
725
840
  module: null,
726
841
  required: true,
727
842
  types: []
843
+ },
844
+ {
845
+ name: "virtual-components.d.ts",
846
+ module: null,
847
+ required: false,
848
+ types: []
728
849
  }
729
850
  ];
730
851
  async function generateTypes(result, options) {
@@ -741,13 +862,13 @@ async function generateTypes(result, options) {
741
862
  generated.push(pageTypesPath);
742
863
  const i18nTypesPath = await generateI18nTypes(result, { outDir });
743
864
  if (i18nTypesPath) generated.push(i18nTypesPath);
744
- const { autoImportsDtsPath, componentsDtsPath } = await generateAutoImports(result, {
865
+ const { autoImportsDtsPath, componentsDtsPath, virtualComponentsDtsPath } = await generateAutoImports(result, {
745
866
  cwd,
746
867
  srcDir,
747
868
  buildDir,
748
869
  ...autoImportOptions
749
870
  });
750
- generated.push(autoImportsDtsPath, componentsDtsPath);
871
+ generated.push(autoImportsDtsPath, componentsDtsPath, virtualComponentsDtsPath);
751
872
  const manifest = {
752
873
  contractVersion: 1,
753
874
  generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
@@ -765,8 +886,9 @@ async function generateTypes(result, options) {
765
886
  i18nTypesPath,
766
887
  autoImportsDtsPath,
767
888
  componentsDtsPath,
889
+ virtualComponentsDtsPath,
768
890
  generated
769
891
  };
770
892
  }
771
893
  //#endregion
772
- export { getUbeanComponentsConfig as C, toArray as E, getUbeanAutoImportConfig as S, resolveComponentsConfig as T, VUE_ROUTER_PRESET as _, generateOpenApiTypesFromServer as a, getAutoImportPresets as b, generateI18nTypes as c, HONO_OPENAPI_PRESET as d, UBEAN_CLIENT_PRESET as f, VUE_PRESET as g, VUE_MACROS_PRESET as h, generateOpenApiTypes as i, messagesToTsType as l, VUE_I18N_PRESET as m, CODEGEN_FILES as n, generatePageTypes as o, UBEAN_SERVER_PRESET as p, generateTypes as r, generateRouteTypes as s, CODEGEN_CONTRACT_VERSION as t, BUILTIN_PRESETS as u, generateAutoImports as v, resolveAutoImportsConfig as w, getBuiltinComposables as x, generateImportsTransform as y };
894
+ export { getUbeanAutoImportConfig as C, toArray as D, resolveComponentsConfig as E, getBuiltinComposables as S, resolveAutoImportsConfig as T, VUE_PRESET as _, generateOpenApiTypesFromServer as a, generateImportsTransform as b, generateI18nTypes as c, COMPONENT_HALF_GLOBS as d, HONO_OPENAPI_PRESET as f, VUE_MACROS_PRESET as g, VUE_I18N_PRESET as h, generateOpenApiTypes as i, messagesToTsType as l, UBEAN_SERVER_PRESET as m, CODEGEN_FILES as n, generatePageTypes as o, UBEAN_CLIENT_PRESET as p, generateTypes as r, generateRouteTypes as s, CODEGEN_CONTRACT_VERSION as t, BUILTIN_PRESETS as u, VUE_ROUTER_PRESET as v, getUbeanComponentsConfig as w, getAutoImportPresets as x, generateAutoImports as y };
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { a as defineVirtualModule, c as useVirtualRegistry, i as VirtualModuleTransform, n as VirtualModuleContext, o as defineVirtualModulePrefix, r as VirtualModuleRegistry, s as resetVirtualRegistry, t as VirtualModule } from "./virtual-registry-BF0jYPle.js";
1
+ import { a as VirtualModuleTransform, c as defineVirtualModulePrefix, i as VirtualModuleResolver, l as resetVirtualRegistry, n as VirtualModuleContext, o as createVirtualRegistry, r as VirtualModuleRegistry, s as defineVirtualModule, t as VirtualModule, u as useVirtualRegistry } from "./virtual-registry-CaU8k-YP.js";
2
2
  import { CompiledLayout, CompiledMiddleware, CompiledPage, CompiledRoute } from "@ubean/routes";
3
3
  import { ScannedApiRoute, ScannedLocale, ScannedMiddleware, ScannedPageRoute } from "@ubean/scan";
4
4
  //#region src/macros.d.ts
@@ -96,4 +96,4 @@ export declare function createMetaVirtualModule(): VirtualModule;
96
96
  export declare function createAppVirtualModule(apiRoutes: ScannedApiRoute[], middlewares: ScannedMiddleware[], pages: ScannedPageRoute[], srcDir: string): VirtualModule;
97
97
  export declare function createLocalesVirtualModule(_locales: ScannedLocale[], defaultLocale: string | undefined, srcDir?: string, i18nConfigJson?: string): VirtualModule;
98
98
  //#endregion
99
- export { type VirtualModule, type VirtualModuleContext, VirtualModuleRegistry, type VirtualModuleTransform, defineVirtualModule, defineVirtualModulePrefix, resetVirtualRegistry, useVirtualRegistry };
99
+ export { type VirtualModule, type VirtualModuleContext, VirtualModuleRegistry, type VirtualModuleResolver, type VirtualModuleTransform, createVirtualRegistry, defineVirtualModule, defineVirtualModulePrefix, resetVirtualRegistry, useVirtualRegistry };
package/dist/index.js CHANGED
@@ -1,4 +1,3 @@
1
- import { a as useVirtualRegistry, i as resetVirtualRegistry, n as defineVirtualModule, r as defineVirtualModulePrefix, t as VirtualModuleRegistry } from "./virtual-registry-BWkaQHXN.js";
2
- import { a as createRoutingVirtualModule, i as createPagesVirtualModule, n as createLocalesVirtualModule, o as stripMacros, r as createMetaVirtualModule, s as transformMacros, t as createAppVirtualModule } from "./virtual-modules-vLkLkAfT.js";
3
- import { a as ssrSingletonProdOptimizeExclude, c as getCssImports, d as resetModuleRegistry, i as ssrSingletonDevPolicy, l as registerComponentResolver, n as SSR_SINGLETON_OPTIMIZE_EXCLUDE, o as ssrSingletonProdSsr, r as SSR_SINGLETON_PACKAGES, s as getComponentResolvers, t as SSR_SINGLETON_DEDUPE, u as registerCssImport } from "./ssr-singleton-CEQi_H6-.js";
4
- export { SSR_SINGLETON_DEDUPE, SSR_SINGLETON_OPTIMIZE_EXCLUDE, SSR_SINGLETON_PACKAGES, VirtualModuleRegistry, createAppVirtualModule, createLocalesVirtualModule, createMetaVirtualModule, createPagesVirtualModule, createRoutingVirtualModule, defineVirtualModule, defineVirtualModulePrefix, getComponentResolvers, getCssImports, registerComponentResolver, registerCssImport, resetModuleRegistry, resetVirtualRegistry, ssrSingletonDevPolicy, ssrSingletonProdOptimizeExclude, ssrSingletonProdSsr, stripMacros, transformMacros, useVirtualRegistry };
1
+ import { _ as resetVirtualRegistry, a as ssrSingletonProdOptimizeExclude, c as getComponentResolvers, d as registerCssImport, f as resetModuleRegistry, g as defineVirtualModulePrefix, h as defineVirtualModule, i as ssrSingletonDevPolicy, l as getCssImports, m as createVirtualRegistry, n as SSR_SINGLETON_OPTIMIZE_EXCLUDE, o as ssrSingletonProdSsr, p as VirtualModuleRegistry, r as SSR_SINGLETON_PACKAGES, t as SSR_SINGLETON_DEDUPE, u as registerComponentResolver, v as useVirtualRegistry } from "./ssr-singleton-DPTawJSx.js";
2
+ import { a as createRoutingVirtualModule, i as createPagesVirtualModule, n as createLocalesVirtualModule, o as stripMacros, r as createMetaVirtualModule, s as transformMacros, t as createAppVirtualModule } from "./virtual-modules-CvGWlsXj.js";
3
+ export { SSR_SINGLETON_DEDUPE, SSR_SINGLETON_OPTIMIZE_EXCLUDE, SSR_SINGLETON_PACKAGES, VirtualModuleRegistry, createAppVirtualModule, createLocalesVirtualModule, createMetaVirtualModule, createPagesVirtualModule, createRoutingVirtualModule, createVirtualRegistry, defineVirtualModule, defineVirtualModulePrefix, getComponentResolvers, getCssImports, registerComponentResolver, registerCssImport, resetModuleRegistry, resetVirtualRegistry, ssrSingletonDevPolicy, ssrSingletonProdOptimizeExclude, ssrSingletonProdSsr, stripMacros, transformMacros, useVirtualRegistry };
@@ -1,4 +1,4 @@
1
- import { PrerenderConfig, PrerenderResult, RouteRule } from "@ubean/config";
1
+ import { PrerenderConfig, PrerenderResult, ResolvedPrerenderConfig, RouteRule } from "@ubean/config";
2
2
  import { ScannedPageRoute } from "@ubean/scan";
3
3
  import { RouteRule as RouteRule$1 } from "@ubean/shared";
4
4
  //#region src/prerender.d.ts
@@ -128,6 +128,18 @@ export interface ExtractedPayload {
128
128
  */
129
129
  export declare function extractDataPayload(html: string, route: string): ExtractedPayload | null;
130
130
  export declare function writePrerenderedFile(filePath: string, html: string): Promise<void>;
131
+ /**
132
+ * 预渲染产物的落盘目录(**唯一**计算处,`prerender()` 与搜索索引共用)。
133
+ *
134
+ * 优先用显式配置的 `prerender.staticDir`;未配置时从**本次构建的** `build.outputDir` 派生为
135
+ * `<outputDir>/public`。
136
+ *
137
+ * 为什么不能给 `staticDir` 一个写死的默认值(曾经是 `'dist/public'`):`build.outputDir` 会被
138
+ * preset(`dist/aws` 等)与 `--outDir` 改动,而写死的默认值不跟着动 —— 实测 `ubean build --outDir
139
+ * .temp-x` 的客户端产物落在 `.temp-x/public`,预渲染 HTML 却写进了 `dist/public`:产物被劈成两
140
+ * 半,且失败是静默的(两边都不报错,只有产物树里少文件)。
141
+ */
142
+ export declare function resolvePrerenderStaticDir(cwd: string, buildOutputDir: string, prerenderConfig: Pick<ResolvedPrerenderConfig, 'staticDir'>): string;
131
143
  export declare function prerender(options: PrerendererOptions): Promise<PrerenderResult>;
132
144
  export declare function generatePrerenderManifest(result: PrerenderResult, baseUrl?: string): {
133
145
  routes: string[];
package/dist/prerender.js CHANGED
@@ -1,8 +1,8 @@
1
1
  import { STATIC_NOT_FOUND_ROUTE } from "./static-render.js";
2
- import { dirname, join } from "pathe";
2
+ import { dirname, isAbsolute, join } from "pathe";
3
3
  import { resolvePrerenderConfig } from "@ubean/config";
4
- import { DATA_PAYLOAD_ID } from "@ubean/pages";
5
4
  import { mkdir, writeFile } from "node:fs/promises";
5
+ import { DATA_PAYLOAD_ID } from "@ubean/pages";
6
6
  import { matchGlob } from "@ubean/shared";
7
7
  //#region src/prerender.ts
8
8
  const LINK_REGEX = /<a[^>]+href=["']([^"']+)["'][^>]*>/gi;
@@ -196,6 +196,21 @@ const _logger = {
196
196
  success: (msg) => console.log(msg),
197
197
  debug: (_msg) => {}
198
198
  };
199
+ /**
200
+ * 预渲染产物的落盘目录(**唯一**计算处,`prerender()` 与搜索索引共用)。
201
+ *
202
+ * 优先用显式配置的 `prerender.staticDir`;未配置时从**本次构建的** `build.outputDir` 派生为
203
+ * `<outputDir>/public`。
204
+ *
205
+ * 为什么不能给 `staticDir` 一个写死的默认值(曾经是 `'dist/public'`):`build.outputDir` 会被
206
+ * preset(`dist/aws` 等)与 `--outDir` 改动,而写死的默认值不跟着动 —— 实测 `ubean build --outDir
207
+ * .temp-x` 的客户端产物落在 `.temp-x/public`,预渲染 HTML 却写进了 `dist/public`:产物被劈成两
208
+ * 半,且失败是静默的(两边都不报错,只有产物树里少文件)。
209
+ */
210
+ function resolvePrerenderStaticDir(cwd, buildOutputDir, prerenderConfig) {
211
+ if (prerenderConfig.staticDir) return isAbsolute(prerenderConfig.staticDir) ? prerenderConfig.staticDir : join(cwd, prerenderConfig.staticDir);
212
+ return isAbsolute(buildOutputDir) ? join(buildOutputDir, "public") : join(cwd, buildOutputDir, "public");
213
+ }
199
214
  async function prerender(options) {
200
215
  const startTime = Date.now();
201
216
  const config = resolvePrerenderConfig(options.prerender);
@@ -207,7 +222,7 @@ async function prerender(options) {
207
222
  skipped: [],
208
223
  duration: 0
209
224
  };
210
- const outputDir = join(options.cwd, config.staticDir);
225
+ const outputDir = resolvePrerenderStaticDir(options.cwd, options.outputDir, config);
211
226
  const { routes: initialRoutes, skipped: initiallySkipped } = collectPrerenderRoutes(options.pages, {
212
227
  all: config.all,
213
228
  include: config.include,
@@ -309,4 +324,4 @@ function generatePrerenderManifest(result, baseUrl = "/") {
309
324
  };
310
325
  }
311
326
  //#endregion
312
- export { collectPrerenderRoutes, extractDataPayload, extractLinks, extractPrerenderRoutesFromRules, generatePrerenderManifest, prerender, routeToDataFilePath, routeToFilePath, writePrerenderedFile };
327
+ export { collectPrerenderRoutes, extractDataPayload, extractLinks, extractPrerenderRoutesFromRules, generatePrerenderManifest, prerender, resolvePrerenderStaticDir, routeToDataFilePath, routeToFilePath, writePrerenderedFile };
@@ -1,6 +1,20 @@
1
+ import { r as VirtualModuleRegistry } from "./virtual-registry-CaU8k-YP.js";
1
2
  import { ResolvedConfig } from "@ubean/config";
2
- import { ScanResult } from "@ubean/scan";
3
3
  import { Preset } from "@ubean/preset";
4
+ import { ScanResult } from "@ubean/scan";
5
+ import { Plugin } from "vite";
6
+ //#region src/vite/build-app.d.ts
7
+ /**
8
+ * 一次 `createBuilder` 完成整套生产构建(`ubean build` 与 `vite build` 共用的唯一编排)。
9
+ *
10
+ * **驱动权声明**:本函数自己就是编排者,因此先在环境里声明「构建由调用方驱动」,让插件侧的
11
+ * `config` 钩子**不要**再注册它自己的 `builder.buildApp` —— 否则同一个 config 的 `builder` 字段
12
+ * 会被插件返回值合并掉,本函数的内联编排不再执行、`manifest` 保持 undefined,表现为**静默返回
13
+ * undefined**(RM-V36 把开关默认打开后由 `build-parity.test.ts` 实测撞到)。与 CLI 用的是同一个
14
+ * 环境变量,结束后还原,避免影响同进程内的其它调用。
15
+ */
16
+ export declare function buildWithEnvironments(options: BuildOptions): Promise<BuildManifest>;
17
+ //#endregion
4
18
  //#region src/production.d.ts
5
19
  export interface BuildOptions {
6
20
  cwd: string;
@@ -24,5 +38,47 @@ export interface BuildManifest {
24
38
  serverDir: string;
25
39
  preset: string;
26
40
  }
27
- export declare function buildProduction(options: BuildOptions): Promise<BuildManifest>;
41
+ /**
42
+ * 把扫描到的页面序列化进服务端入口(`const _pages = [...]`)。
43
+ *
44
+ * **白名单必须覆盖语义字段**:此前只序列化了 `relativePath/name/route/layout/reuseTarget/isReuse/
45
+ * pageMeta`,于是 `matchers` / `slot` **在生产构建里丢失** ——
46
+ * dev 用扫描得到的活对象(`enhanceDevApp` / `buildDevSsrRoutes` 直接拿 `scanResult.pages`),所以本地
47
+ * 一切正常,只有构建产物坏掉:带着 `[id=numeric]` 的路由在 dev 返回 404、在生产返回 200(实测)。
48
+ * 这类「dev 正常、产物丢失元数据」的缺陷对用户最不友好,因此单独抽成函数并配单测
49
+ * (`packages/builder/test/page-metadata.test.ts`)。
50
+ *
51
+ * 不含 `fullPath`/`dirname`/`basename`(构建机上的文件系统细节,产物不需要)与 `frontmatter`
52
+ * (Markdown 页正文用,服务端路由不消费)。拦截路由的两个字段(`interceptFrom` /
53
+ * `interceptTarget`)曾在这份白名单里,随该约定一起移除([docs/adr/0010] 刻意不做)。
54
+ */
55
+ export declare function serializePagesForEntry(pages: ScanResult['pages']): string;
56
+ export declare function generateVirtualModulesToDisk(cwd: string, config: ResolvedConfig, scanResult: ScanResult, virtualDir: string, preset: Preset, registry: VirtualModuleRegistry, contentSnapshot?: Record<string, unknown[]>): Promise<string>;
57
+ export type PresetBuildConfig = ReturnType<typeof getPresetBuildConfig>;
58
+ export declare function getPresetBuildConfig(preset: Preset): {
59
+ format: "esm";
60
+ target: string;
61
+ entryType: "node";
62
+ external: (string | RegExp)[];
63
+ } | {
64
+ format: "esm";
65
+ target: string;
66
+ entryType: "worker";
67
+ external: (string | RegExp)[];
68
+ } | {
69
+ format: "esm";
70
+ target: string;
71
+ entryType: "fetch";
72
+ external: (string | RegExp)[];
73
+ };
74
+ /**
75
+ * SSR 期的 islands 注册表空壳插件。
76
+ *
77
+ * 真正的注册表由 `ubeanIslandsPlugin` 在**客户端**构建期填充;SSR 期 islands 由服务端渲染
78
+ * (不水合),空壳即可 —— 但不能让它以裸 `virtual:ubean-islands-registry` 泄漏给 Node。
79
+ */
80
+ export declare function createIslandsSsrStubPlugin(): Plugin;
81
+ export declare function generateNodeServerEntry(): string;
82
+ export declare function generateStandardHandlerEntry(): string;
83
+ export declare function generateCloudflareWorkerEntry(): string;
28
84
  //#endregion