@ubean/build 0.6.8 → 0.7.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/README.md +1 -1
- package/README.zh-CN.md +1 -1
- package/dist/codegen/index.js +1 -1
- package/dist/{codegen-CUlE95gB.js → codegen-ClIh5VKh.js} +97 -33
- package/dist/env/plugin.d.ts +2 -0
- package/dist/env/plugin.js +1 -1
- package/dist/index.js +1 -1
- package/dist/prerender.d.ts +8 -0
- package/dist/prerender.js +19 -1
- package/dist/production.d.ts +1 -1
- package/dist/production.js +1 -1
- package/dist/{types-JZjwXWJd.js → types-C2WT1hXQ.js} +14 -0
- package/dist/{virtual-modules-C_Onkx4t.js → virtual-modules-7mD1FgVQ.js} +2 -2
- package/dist/{vite-C8I25VzP.js → vite-C5MyVhMg.js} +62 -26
- package/dist/vite.d.ts +16 -0
- package/dist/vite.js +2 -2
- package/dist/{vue-QvsTw-R-.js → vue-ClGXUBJz.js} +21 -6
- package/dist/vue.js +1 -1
- package/package.json +14 -14
package/README.md
CHANGED
|
@@ -383,7 +383,7 @@ const manifest = await buildWithEnvironments({
|
|
|
383
383
|
| `createIslandsSsrStubPlugin()` | Non-functional islands registry for SSR |
|
|
384
384
|
|
|
385
385
|
**`serializePagesForEntry()` is a whitelist, and the whitelist is load-bearing.** It previously
|
|
386
|
-
serialized only `relativePath` / `name` / `route` / `layout` / `reuseTarget` / `
|
|
386
|
+
serialized only `relativePath` / `name` / `route` / `layout` / `reuseTarget` / `pageKind` / `pageMeta`,
|
|
387
387
|
so `matchers` and `slot` **disappeared from production builds** — dev used the live scanned objects
|
|
388
388
|
and behaved correctly, while a `[id=numeric]` route returned 404 in dev and 200 in production. Add a
|
|
389
389
|
field to `ScannedPage` and you must add it here too; `packages/builder/test/page-metadata.test.ts`
|
package/README.zh-CN.md
CHANGED
|
@@ -373,7 +373,7 @@ const manifest = await buildWithEnvironments({
|
|
|
373
373
|
| `createIslandsSsrStubPlugin()` | SSR 期的 islands 注册表空壳 |
|
|
374
374
|
|
|
375
375
|
**`serializePagesForEntry()` 是一份白名单,而这份白名单承载语义。** 它此前只序列化
|
|
376
|
-
`relativePath` / `name` / `route` / `layout` / `reuseTarget` / `
|
|
376
|
+
`relativePath` / `name` / `route` / `layout` / `reuseTarget` / `pageKind` / `pageMeta`,于是 `matchers` 与 `slot`
|
|
377
377
|
**在生产构建里消失** —— dev 用扫描得到的活对象因此一切正常,而带 `[id=numeric]` 的路由在 dev 返回 404、
|
|
378
378
|
在生产返回 200。给 `ScannedPage` 加字段就必须同步加到这里;`packages/builder/test/page-metadata.test.ts`
|
|
379
379
|
逐字段锁住这份名单。
|
package/dist/codegen/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { d as BUILTIN_PRESETS, t as CODEGEN_CONTRACT_VERSION, n as CODEGEN_FILES, r as CODEGEN_NON_CONTRACT_FILES, p as HONO_OPENAPI_PRESET, m as UBEAN_CLIENT_PRESET, h as UBEAN_SERVER_PRESET, g as VUE_I18N_PRESET, _ as VUE_MACROS_PRESET, v as VUE_PRESET, y as VUE_ROUTER_PRESET, b as generateAutoImports, l as generateI18nTypes, x as generateImportsTransform, a as generateOpenApiTypes, o as generateOpenApiTypesFromServer, s as generatePageTypes, c as generateRouteTypes, i as generateTypes, S as getAutoImportPresets, C as getBuiltinComposables, w as getUbeanAutoImportConfig, T as getUbeanComponentsConfig, u as messagesToTsType, E as resolveAutoImportsConfig, D as resolveComponentsConfig, O as toArray } from "../codegen-
|
|
1
|
+
export { d as BUILTIN_PRESETS, t as CODEGEN_CONTRACT_VERSION, n as CODEGEN_FILES, r as CODEGEN_NON_CONTRACT_FILES, p as HONO_OPENAPI_PRESET, m as UBEAN_CLIENT_PRESET, h as UBEAN_SERVER_PRESET, g as VUE_I18N_PRESET, _ as VUE_MACROS_PRESET, v as VUE_PRESET, y as VUE_ROUTER_PRESET, b as generateAutoImports, l as generateI18nTypes, x as generateImportsTransform, a as generateOpenApiTypes, o as generateOpenApiTypesFromServer, s as generatePageTypes, c as generateRouteTypes, i as generateTypes, S as getAutoImportPresets, C as getBuiltinComposables, w as getUbeanAutoImportConfig, T as getUbeanComponentsConfig, u as messagesToTsType, E as resolveAutoImportsConfig, D as resolveComponentsConfig, O as toArray } from "../codegen-ClIh5VKh.js";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { r as resolveEnvModules } from "./types-
|
|
1
|
+
import { r as resolveEnvModules } from "./types-C2WT1hXQ.js";
|
|
2
2
|
import { isAbsolute, join, normalize, relative } from "pathe";
|
|
3
3
|
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
4
4
|
import { glob } from "tinyglobby";
|
|
@@ -16,13 +16,6 @@ import openapiTS, { astToString } from "openapi-typescript";
|
|
|
16
16
|
* `src/env.ts` 读了 `import.meta.env.VITE_API_PROXY_KEYS`,而 `src/typings/env.d.ts`
|
|
17
17
|
* 里没有对应声明 —— 手写清单没有东西能提醒它过期。
|
|
18
18
|
*/
|
|
19
|
-
const VITE_BUILTIN_ENV_KEYS = /* @__PURE__ */ new Set([
|
|
20
|
-
"MODE",
|
|
21
|
-
"DEV",
|
|
22
|
-
"PROD",
|
|
23
|
-
"BASE_URL",
|
|
24
|
-
"SSR"
|
|
25
|
-
]);
|
|
26
19
|
/**
|
|
27
20
|
* 产物文件名(单一来源:`generateEnvTypes()` 的默认值与 `CODEGEN_NON_CONTRACT_FILES`
|
|
28
21
|
* 都用它,免得改了一处漏另一处)。
|
|
@@ -33,32 +26,69 @@ function tsTypeOfLiteral(value) {
|
|
|
33
26
|
if (typeof value === "boolean") return "boolean";
|
|
34
27
|
return "string";
|
|
35
28
|
}
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
29
|
+
/**
|
|
30
|
+
* 把 `envField.*({ description })` 渲染成 JSDoc 行。
|
|
31
|
+
*
|
|
32
|
+
* 转义行内注释终止符:说明文字里出现它就提前闭合注释,后面的声明会变成语法错误 ——
|
|
33
|
+
* 而报错位置指向生成的 `.d.ts`(用户不该去改的文件),完全看不出根因。
|
|
34
|
+
* 多行说明逐行加 ` * ` 前缀,否则第二行会跑到注释外面。
|
|
35
|
+
*/
|
|
36
|
+
function renderJsDoc(description) {
|
|
37
|
+
const lines = description.replaceAll("*/", "*\\/").split(/\r?\n/).map((line) => line.trimEnd());
|
|
38
|
+
if (lines.length === 1) return [` /** ${lines[0]} */`];
|
|
39
|
+
return [
|
|
40
|
+
" /**",
|
|
41
|
+
...lines.map((line) => ` * ${line}`.trimEnd()),
|
|
42
|
+
" */"
|
|
43
|
+
];
|
|
44
|
+
}
|
|
45
|
+
function renderModuleTypes(moduleId, fields, extraLines = []) {
|
|
46
|
+
const lines = fields.flatMap((f) => {
|
|
47
|
+
const decl = ` export const ${f.key}: ${f.type}${f.optional ? " | undefined" : ""};`;
|
|
48
|
+
return f.description ? [...renderJsDoc(f.description), decl] : [decl];
|
|
49
|
+
});
|
|
50
|
+
const body = [
|
|
51
|
+
...lines,
|
|
52
|
+
...lines.length > 0 && extraLines.length > 0 ? [""] : [],
|
|
53
|
+
...extraLines
|
|
54
|
+
];
|
|
55
|
+
if (body.length === 0) return `declare module '${moduleId}' {\n // env.schema is empty\n}\n`;
|
|
56
|
+
return `declare module '${moduleId}' {\n${body.join("\n")}\n}\n`;
|
|
41
57
|
}
|
|
42
58
|
/**
|
|
43
|
-
* 从 `import.meta.env` 快照生成 `ImportMetaEnv
|
|
59
|
+
* 从 `import.meta.env` 快照生成 `ImportMetaEnv`,只声明 `VITE_` 前缀的 key。
|
|
44
60
|
*
|
|
45
61
|
* 值同时用于**类型**:字符串字面量能收窄成具体字符串 —— 但只在值是字面量时。这里选择
|
|
46
62
|
* 宽化到 `string` / `number` / `boolean`,因为 `.env` 里的值一律是字符串,写成裸字面量
|
|
47
63
|
* 会让 `VITE_API_ENV` 被推成 `"local"` 这种过窄类型,赋值时报错。
|
|
64
|
+
*
|
|
65
|
+
* **Vite 内建(`MODE` / `DEV` / `PROD` / `BASE_URL` / `SSR`)刻意不声明。** 它们已经由
|
|
66
|
+
* `vite/client`(`dist/vite/types/importMeta.d.ts`)声明,且那边**没有** `readonly` 修饰符 ——
|
|
67
|
+
* 重复声明会报 `TS2687: All declarations of 'MODE' must have identical modifiers`(实测
|
|
68
|
+
* soybean-admin-v3:`--skipLibCheck false` 下 8 条)。默认的 `skipLibCheck: true` 会把它吞掉,
|
|
69
|
+
* 于是这个冲突只对关掉 skipLibCheck 的项目可见。不声明就不冲突,而且那 5 个 key 是 Vite 常量,
|
|
70
|
+
* 没有漂移可言。
|
|
71
|
+
*
|
|
72
|
+
* 没有 `VITE_*` key 时返回 `undefined`,整个 `interface ImportMetaEnv` 块都不产出 —— 只留一个
|
|
73
|
+
* `readonly [key: string]: …` 兜底索引签名会反向削弱 `vite/client` 的声明(`strictImportMetaEnv`
|
|
74
|
+
* 关掉时它自带 `Record<string, any>`;我们那份索引签名把 `import.meta.env.X` 从 `any` 收窄成
|
|
75
|
+
* `string | boolean | undefined`,看着更严,实则是把 Vite 的类型面往下拉)。
|
|
48
76
|
*/
|
|
49
77
|
function renderImportMetaEnv(importMetaEnv) {
|
|
50
|
-
const keys = Object.keys(importMetaEnv).filter((key) => key.startsWith("VITE_")
|
|
51
|
-
if (keys.length === 0) return
|
|
52
|
-
return `interface ImportMetaEnv {\n${keys.map((key) => {
|
|
53
|
-
const value = importMetaEnv[key];
|
|
54
|
-
if (VITE_BUILTIN_ENV_KEYS.has(key)) {
|
|
55
|
-
if (key === "MODE" || key === "BASE_URL") return ` readonly ${key}: string;`;
|
|
56
|
-
return ` readonly ${key}: boolean;`;
|
|
57
|
-
}
|
|
58
|
-
return ` readonly ${key}: ${tsTypeOfLiteral(value)};`;
|
|
59
|
-
}).join("\n")}\n}\n`;
|
|
78
|
+
const keys = Object.keys(importMetaEnv).filter((key) => key.startsWith("VITE_")).sort();
|
|
79
|
+
if (keys.length === 0) return void 0;
|
|
80
|
+
return `interface ImportMetaEnv {\n${keys.map((key) => ` readonly ${key}: ${tsTypeOfLiteral(importMetaEnv[key])};`).join("\n")}\n}\n`;
|
|
60
81
|
}
|
|
61
82
|
/**
|
|
83
|
+
* `renderEnvServerModule()` 生成的源码除了字段还会 `export function getSecret(key)`
|
|
84
|
+
* (`packages/builder/src/env/types.ts`),但早先这里只声明字段 —— 用户按
|
|
85
|
+
* `docs/contracts/env-v1.md` 的指引去 import 它,`tsc` 直接报 TS2305。
|
|
86
|
+
*
|
|
87
|
+
* 源码里还有一个 `__env`,它是生成代码的内部聚合对象,刻意**不**声明:声明了就等于
|
|
88
|
+
* 把它纳入公开 API,而它有更好的具名导出可用。
|
|
89
|
+
*/
|
|
90
|
+
const ENV_SERVER_EXTRA_EXPORTS = [" /** 读取未在 `env.schema` 里声明的变量(动态 / per-request 场景)。 */", " export function getSecret(key: string): string | undefined;"];
|
|
91
|
+
/**
|
|
62
92
|
* 生成 `.ubean/env.d.ts`。
|
|
63
93
|
*
|
|
64
94
|
* 顶层**不能**出现 `export {}`:`ImportMetaEnv` 的声明必须留在全局作用域,一旦文件变成
|
|
@@ -67,17 +97,13 @@ function renderImportMetaEnv(importMetaEnv) {
|
|
|
67
97
|
*/
|
|
68
98
|
async function generateEnvTypes(options) {
|
|
69
99
|
const { outDir, fileName = ENV_TYPES_FILE_NAME, modules, importMetaEnv = {} } = options;
|
|
100
|
+
const importMetaEnvBlock = renderImportMetaEnv(importMetaEnv);
|
|
70
101
|
const contents = `// Auto-generated by ubean from \`env.schema\` in ubean.config.ts — do not edit manually.
|
|
71
102
|
// Provides typed access to \`ubean:env/client\` / \`ubean:env/server\` and declares ImportMetaEnv.
|
|
72
103
|
/* eslint-disable */
|
|
73
104
|
/// <reference types="vite/client" />
|
|
74
|
-
|
|
75
|
-
${
|
|
76
|
-
interface ImportMeta {
|
|
77
|
-
readonly env: ImportMetaEnv;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
${renderModuleTypes("ubean:env/client", modules.client)}${renderModuleTypes("ubean:env/server", modules.server)}`;
|
|
105
|
+
${importMetaEnvBlock ? `\n${importMetaEnvBlock}interface ImportMeta {\n readonly env: ImportMetaEnv;\n}\n` : ""}
|
|
106
|
+
${renderModuleTypes("ubean:env/client", modules.client)}${renderModuleTypes("ubean:env/server", modules.server, ENV_SERVER_EXTRA_EXPORTS)}`;
|
|
81
107
|
await mkdir(outDir, { recursive: true });
|
|
82
108
|
const outPath = join(outDir, fileName);
|
|
83
109
|
await writeFile(outPath, contents, "utf8");
|
|
@@ -467,7 +493,7 @@ async function generateAutoImports(_scanResult, options) {
|
|
|
467
493
|
virtualComponents.push(...scanned.virtualComponents);
|
|
468
494
|
}
|
|
469
495
|
}
|
|
470
|
-
const componentsDts = generateComponentsDts(components, componentsDtsPath);
|
|
496
|
+
const componentsDts = generateComponentsDts(components, componentsDtsPath, await readComponentsDtsEntries(componentsDtsPath));
|
|
471
497
|
await writeFile(componentsDtsPath, componentsDts, "utf-8");
|
|
472
498
|
const virtualComponentsDtsPath = join(outDir, "virtual-components.d.ts");
|
|
473
499
|
await writeFile(virtualComponentsDtsPath, generateVirtualComponentsDts(virtualComponents, virtualComponentsDtsPath), "utf-8");
|
|
@@ -535,6 +561,39 @@ function generateVirtualComponentsDts(virtual, dtsPath) {
|
|
|
535
561
|
return `${lines.join("\n")}\n`;
|
|
536
562
|
}
|
|
537
563
|
/**
|
|
564
|
+
* 读回 `components.d.ts` 里已有的条目(名字 → 声明)。
|
|
565
|
+
*
|
|
566
|
+
* 这个文件有**两个写入者**:本函数(每次重扫整体重写)与 unplugin-vue-components(dev 下
|
|
567
|
+
* 按需重写,只扫它编译过的 SFC)。两边都只列自己知道的东西,所以必须**合并**而不是覆盖。
|
|
568
|
+
*
|
|
569
|
+
* 后果(实测 soybean-admin-v3):`<SConfigProvider>` 只在 `src/app.vue` 里用,而 app.vue 只在
|
|
570
|
+
* 渲染某个页面时才被加载 —— 于是 `SConfigProvider` / `SButton` 先被 unplugin 写进 d.ts,
|
|
571
|
+
* 随后任意文件改动触发重扫,本函数把它们整个覆盖掉(我们只扫 `src/components` 与内置 4 个,
|
|
572
|
+
* 既不认识模块注册的 resolver 也不认识 `RouterLink`/`RouterView`)。用户看到的就是
|
|
573
|
+
* 「组件自动导入时有时无」。
|
|
574
|
+
*
|
|
575
|
+
* 为什么不去遍历模块注册的 resolver 拿候选名:`@vean/ui` 主入口不导出可枚举的组件清单
|
|
576
|
+
* (161 个导出里只有一部分是组件),resolver 本身只能按名字查表。没有候选列表就只能合并。
|
|
577
|
+
*
|
|
578
|
+
* 不解析 AST:这是两个写入器共有的扁平接口,行格式稳定(` Name: typeof import('…')['…']`)。
|
|
579
|
+
*/
|
|
580
|
+
async function readComponentsDtsEntries(dtsPath) {
|
|
581
|
+
const entries = /* @__PURE__ */ new Map();
|
|
582
|
+
let raw;
|
|
583
|
+
try {
|
|
584
|
+
raw = await readFile(dtsPath, "utf-8");
|
|
585
|
+
} catch {
|
|
586
|
+
return entries;
|
|
587
|
+
}
|
|
588
|
+
for (const line of raw.split("\n")) {
|
|
589
|
+
const match = /^ {4}(?:"((?:[^"\\]|\\.)*)"|([A-Za-z_$][\w$]*)): (typeof import\('.*'\)\[[^\]]+\])$/.exec(line);
|
|
590
|
+
if (!match) continue;
|
|
591
|
+
const name = match[1] !== void 0 ? JSON.parse(`"${match[1]}"`) : match[2];
|
|
592
|
+
entries.set(name, match[3]);
|
|
593
|
+
}
|
|
594
|
+
return entries;
|
|
595
|
+
}
|
|
596
|
+
/**
|
|
538
597
|
* Generate the `.ubean/components.d.ts` in a format compatible with
|
|
539
598
|
* unplugin-vue-components (both writers own the same file).
|
|
540
599
|
*
|
|
@@ -544,10 +603,15 @@ function generateVirtualComponentsDts(virtual, dtsPath) {
|
|
|
544
603
|
* `import`/`const` declaration lines. Entries must therefore be
|
|
545
604
|
* self-contained inline `typeof import('...')` declarations so they stay
|
|
546
605
|
* valid after unplugin merges them.
|
|
606
|
+
*
|
|
607
|
+
* 反向也必须成立:`previous`(上一份文件里的条目)**优先保留**。unplugin 在 dev 下按需
|
|
608
|
+
* 收集(只扫它编译过的 SFC),我们只扫 `src/components` + 内置 4 个 —— 覆盖式重写会把
|
|
609
|
+
* 它写进去的第三方组件(`SButton`/`SConfigProvider`/`RouterLink`…)全抹掉。
|
|
547
610
|
*/
|
|
548
|
-
function generateComponentsDts(components, dtsPath) {
|
|
611
|
+
function generateComponentsDts(components, dtsPath, previous = /* @__PURE__ */ new Map()) {
|
|
549
612
|
const dtsDir = fileDirname(toPosixPath(normalize(dtsPath)));
|
|
550
613
|
const entries = /* @__PURE__ */ new Map();
|
|
614
|
+
for (const [name, declaration] of previous) entries.set(name, declaration);
|
|
551
615
|
for (const name of [
|
|
552
616
|
"Link",
|
|
553
617
|
"Head",
|
package/dist/env/plugin.d.ts
CHANGED
|
@@ -20,6 +20,8 @@ interface EnvFieldSpec {
|
|
|
20
20
|
/** TypeScript 字面量类型,直接写进 `.ubean/env.d.ts`。 */
|
|
21
21
|
type: string;
|
|
22
22
|
optional: boolean;
|
|
23
|
+
/** 字段说明(`envField.*({ description })`),写进 `.ubean/env.d.ts` 的 JSDoc。 */
|
|
24
|
+
description?: string;
|
|
23
25
|
/**
|
|
24
26
|
* 已解析的值:
|
|
25
27
|
* - `context: 'client'` → 构建期内联用的字面量(来自 `import.meta.env` / `.env`,已含 default 回退)
|
package/dist/env/plugin.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { n as renderEnvServerModule, r as resolveEnvModules, t as renderEnvClientModule } from "../types-
|
|
1
|
+
import { n as renderEnvServerModule, r as resolveEnvModules, t as renderEnvClientModule } from "../types-C2WT1hXQ.js";
|
|
2
2
|
import { loadEnv } from "vite";
|
|
3
3
|
//#region src/env/plugin.ts
|
|
4
4
|
/**
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import { _ as defineVirtualModule, a as ssrSingletonProdOptimizeExclude, b as useVirtualRegistry, c as getComponentResolvers, d as registerComponentResolver, f as registerCssImport, g as createVirtualRegistry, h as VirtualModuleRegistry, i as ssrSingletonDevPolicy, l as getCssImports, m as resetModuleRegistry, n as SSR_SINGLETON_OPTIMIZE_EXCLUDE, o as ssrSingletonProdSsr, p as registerHeadScript, r as SSR_SINGLETON_PACKAGES, t as SSR_SINGLETON_DEDUPE, u as getHeadScripts, v as defineVirtualModulePrefix, y as resetVirtualRegistry } from "./ssr-singleton-CUHZLgiO.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-
|
|
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-7mD1FgVQ.js";
|
|
3
3
|
export { SSR_SINGLETON_DEDUPE, SSR_SINGLETON_OPTIMIZE_EXCLUDE, SSR_SINGLETON_PACKAGES, VirtualModuleRegistry, createAppVirtualModule, createLocalesVirtualModule, createMetaVirtualModule, createPagesVirtualModule, createRoutingVirtualModule, createVirtualRegistry, defineVirtualModule, defineVirtualModulePrefix, getComponentResolvers, getCssImports, getHeadScripts, registerComponentResolver, registerCssImport, registerHeadScript, resetModuleRegistry, resetVirtualRegistry, ssrSingletonDevPolicy, ssrSingletonProdOptimizeExclude, ssrSingletonProdSsr, stripMacros, transformMacros, useVirtualRegistry };
|
package/dist/prerender.d.ts
CHANGED
|
@@ -38,6 +38,14 @@ export interface PrerendererOptions {
|
|
|
38
38
|
statusCode: number;
|
|
39
39
|
}>;
|
|
40
40
|
}
|
|
41
|
+
/**
|
|
42
|
+
* `definePage({ redirect })` 的页面是否在预渲染时应跳过。
|
|
43
|
+
*
|
|
44
|
+
* 静态产物里没有重定向的执行点:SSG 直接渲染路径会渲出源页面的 HTML,
|
|
45
|
+
* fullstack 管道下 fetcher 拿到的是空体的 302(写盘就是空文件)。两者都是错的,
|
|
46
|
+
* 所以两边共用这一个判据(调用方负责出 warn,本模块只负责跳过)。
|
|
47
|
+
*/
|
|
48
|
+
export declare function isRedirectPage(page: ScannedPageRoute): boolean;
|
|
41
49
|
/**
|
|
42
50
|
* 从 routeRules 中提取标记了 `prerender: true` 或 `ppr: true` 的路由模式
|
|
43
51
|
* (P9-03 + P9-04)。
|
package/dist/prerender.js
CHANGED
|
@@ -18,6 +18,16 @@ const DATA_PAYLOAD_REGEX = new RegExp(`<script id="${DATA_PAYLOAD_ID}" type="app
|
|
|
18
18
|
function isDynamicPath(path) {
|
|
19
19
|
return /\[.*?\]/.test(path) || path.includes(":");
|
|
20
20
|
}
|
|
21
|
+
/**
|
|
22
|
+
* `definePage({ redirect })` 的页面是否在预渲染时应跳过。
|
|
23
|
+
*
|
|
24
|
+
* 静态产物里没有重定向的执行点:SSG 直接渲染路径会渲出源页面的 HTML,
|
|
25
|
+
* fullstack 管道下 fetcher 拿到的是空体的 302(写盘就是空文件)。两者都是错的,
|
|
26
|
+
* 所以两边共用这一个判据(调用方负责出 warn,本模块只负责跳过)。
|
|
27
|
+
*/
|
|
28
|
+
function isRedirectPage(page) {
|
|
29
|
+
return page.routable !== false && Boolean(page.redirect);
|
|
30
|
+
}
|
|
21
31
|
function normalizePagePath(filePath) {
|
|
22
32
|
let path = filePath.replace(/\.(vue|ts|js|tsx|jsx|md|mdx)$/, "");
|
|
23
33
|
if (path === "index" || path.endsWith("/index")) path = path.slice(0, -5);
|
|
@@ -63,9 +73,14 @@ function collectPrerenderRoutes(pages, options = {}) {
|
|
|
63
73
|
...extractPrerenderRoutesFromRules(options.routeRules),
|
|
64
74
|
...options.contentRoutes ?? []
|
|
65
75
|
];
|
|
76
|
+
const redirectPaths = /* @__PURE__ */ new Set();
|
|
66
77
|
const allPageRoutes = [];
|
|
67
78
|
for (const page of pages) {
|
|
68
79
|
if (page.routable === false) continue;
|
|
80
|
+
if (isRedirectPage(page)) {
|
|
81
|
+
redirectPaths.add(normalizePagePath(page.path));
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
69
84
|
const path = normalizePagePath(page.path);
|
|
70
85
|
if (!isDynamicPath(path)) allPageRoutes.push(path);
|
|
71
86
|
}
|
|
@@ -74,6 +89,7 @@ function collectPrerenderRoutes(pages, options = {}) {
|
|
|
74
89
|
else for (const pattern of include) if (pattern.includes("*")) {
|
|
75
90
|
for (const r of allPageRoutes) if (matchGlob(r, pattern)) matched.add(r);
|
|
76
91
|
} else matched.add(pattern.startsWith("/") ? pattern : `/${pattern}`);
|
|
92
|
+
for (const path of redirectPaths) matched.delete(path);
|
|
77
93
|
const skipped = [];
|
|
78
94
|
for (const r of matched) if (exclude.some((p) => matchGlob(r, p))) skipped.push(r);
|
|
79
95
|
for (const r of skipped) matched.delete(r);
|
|
@@ -232,6 +248,8 @@ async function prerender(options) {
|
|
|
232
248
|
contentRoutes: options.contentRoutes
|
|
233
249
|
});
|
|
234
250
|
const queue = [...options.expandRoutes ? options.expandRoutes(initialRoutes) : initialRoutes];
|
|
251
|
+
const redirectPages = options.pages.filter(isRedirectPage).map((p) => normalizePagePath(p.path));
|
|
252
|
+
if (redirectPages.length > 0) _logger.warn(`Skipping prerender for ${redirectPages.length} redirect page(s) (${redirectPages.join(", ")}) — static output has no redirect execution point`);
|
|
235
253
|
if (options.notFoundRoute) queue.unshift(STATIC_NOT_FOUND_ROUTE);
|
|
236
254
|
const visited = /* @__PURE__ */ new Set();
|
|
237
255
|
const results = [];
|
|
@@ -325,4 +343,4 @@ function generatePrerenderManifest(result, baseUrl = "/") {
|
|
|
325
343
|
};
|
|
326
344
|
}
|
|
327
345
|
//#endregion
|
|
328
|
-
export { collectPrerenderRoutes, extractDataPayload, extractLinks, extractPrerenderRoutesFromRules, generatePrerenderManifest, prerender, resolvePrerenderStaticDir, routeToDataFilePath, routeToFilePath, writePrerenderedFile };
|
|
346
|
+
export { collectPrerenderRoutes, extractDataPayload, extractLinks, extractPrerenderRoutesFromRules, generatePrerenderManifest, isRedirectPage, prerender, resolvePrerenderStaticDir, routeToDataFilePath, routeToFilePath, writePrerenderedFile };
|
package/dist/production.d.ts
CHANGED
|
@@ -46,7 +46,7 @@ export interface BuildManifest {
|
|
|
46
46
|
/**
|
|
47
47
|
* 把扫描到的页面序列化进服务端入口(`const _pages = [...]`)。
|
|
48
48
|
*
|
|
49
|
-
* **白名单必须覆盖语义字段**:此前只序列化了 `relativePath/name/route/layout/reuseTarget/
|
|
49
|
+
* **白名单必须覆盖语义字段**:此前只序列化了 `relativePath/name/route/layout/reuseTarget/pageKind/
|
|
50
50
|
* pageMeta`,于是 `matchers` / `slot` **在生产构建里丢失** ——
|
|
51
51
|
* dev 用扫描得到的活对象(`enhanceDevApp` / `buildDevSsrRoutes` 直接拿 `scanResult.pages`),所以本地
|
|
52
52
|
* 一切正常,只有构建产物坏掉:带着 `[id=numeric]` 的路由在 dev 返回 404、在生产返回 200(实测)。
|
package/dist/production.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { T as buildWithEnvironments, v as createIslandsSsrStubPlugin, y as generateCloudflareWorkerEntry, b as generateNodeServerEntry, x as generateStandardHandlerEntry, S as generateVirtualModulesToDisk, C as getPresetBuildConfig, w as serializePagesForEntry } from "./vite-
|
|
1
|
+
export { T as buildWithEnvironments, v as createIslandsSsrStubPlugin, y as generateCloudflareWorkerEntry, b as generateNodeServerEntry, x as generateStandardHandlerEntry, S as generateVirtualModulesToDisk, C as getPresetBuildConfig, w as serializePagesForEntry } from "./vite-C5MyVhMg.js";
|
|
@@ -10,6 +10,17 @@ import { isEnvFieldMeta } from "@ubean/shared";
|
|
|
10
10
|
* 维度规则照抄 Astro `astro:env`:`client+public` / `server+public` / `server+secret` 三种合法,
|
|
11
11
|
* `client+secret` 在**规范化阶段**就抛错(配置期失败,比构建期失败早)。
|
|
12
12
|
*/
|
|
13
|
+
/**
|
|
14
|
+
* 判断一个 schema 条目是不是**运行期**校验器(Standard Schema / `safeParse`)。
|
|
15
|
+
*
|
|
16
|
+
* 这类值不能进 `env.schema` —— 见 `resolveEnvModules()` 里的抛错说明。
|
|
17
|
+
*/
|
|
18
|
+
function isRuntimeSchema(def) {
|
|
19
|
+
if (typeof def !== "object" || def === null) return false;
|
|
20
|
+
const standard = def["~standard"];
|
|
21
|
+
if (typeof standard === "object" && standard !== null && typeof standard.validate === "function") return true;
|
|
22
|
+
return typeof def.safeParse === "function";
|
|
23
|
+
}
|
|
13
24
|
function typeOfField(def) {
|
|
14
25
|
if (isEnvFieldMeta(def)) {
|
|
15
26
|
if (def.type === "enum") return (def.values ?? []).map((v) => JSON.stringify(v)).join(" | ") || "string";
|
|
@@ -44,6 +55,8 @@ function resolveEnvModules(input) {
|
|
|
44
55
|
const client = [];
|
|
45
56
|
const server = [];
|
|
46
57
|
for (const [key, def] of Object.entries(schema)) {
|
|
58
|
+
if (isRuntimeSchema(def)) throw new Error(`Invalid env field "${key}": a runtime schema (Standard Schema / safeParse) cannot be used in \`env.schema\` in ubean.config.ts.\nGenerated env modules are source files and only carry plain metadata, so the validator would be silently dropped.
|
|
59
|
+
Use \`envField.*({ context, access })\` here, or move the field into \`defineEnv({ schema })\` in server code if you need schema validation.`);
|
|
47
60
|
const { context, access } = dimensionsOf(def);
|
|
48
61
|
if (context === "client" && access === "secret") throw new Error(`Invalid env field "${key}": context 'client' cannot be combined with access 'secret'. Secrets never reach the client — use \`{ context: 'server', access: 'secret' }\`.`);
|
|
49
62
|
const meta = isEnvFieldMeta(def) ? def : null;
|
|
@@ -56,6 +69,7 @@ function resolveEnvModules(input) {
|
|
|
56
69
|
optional,
|
|
57
70
|
sourceKey: key
|
|
58
71
|
};
|
|
72
|
+
if (meta?.description) spec.description = meta.description;
|
|
59
73
|
if (context === "client") {
|
|
60
74
|
const raw = importMetaEnv[key] ?? dotEnv[key];
|
|
61
75
|
const resolved = raw === void 0 || raw === "" ? meta?.default : raw;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { _ as defineVirtualModule } from "./ssr-singleton-CUHZLgiO.js";
|
|
2
2
|
import { extractScriptBlocks, parseScriptAst, statementCall } from "@ubean/vue/vite";
|
|
3
|
+
import { resolvePageKind } from "@ubean/vue";
|
|
3
4
|
import { isAbsolute, relative } from "pathe";
|
|
4
5
|
//#region src/macros.ts
|
|
5
6
|
/**
|
|
@@ -174,8 +175,7 @@ function createAppVirtualModule(apiRoutes, middlewares, pages, srcDir, pageExten
|
|
|
174
175
|
relativePath: p.relativePath,
|
|
175
176
|
name: p.name,
|
|
176
177
|
route: p.route,
|
|
177
|
-
|
|
178
|
-
isMeta: p.isMeta,
|
|
178
|
+
pageKind: resolvePageKind(p),
|
|
179
179
|
routable: p.routable,
|
|
180
180
|
layout: p.layout
|
|
181
181
|
}));
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { a as ssrSingletonProdOptimizeExclude, g as createVirtualRegistry, o as ssrSingletonProdSsr, x as __exportAll } from "./ssr-singleton-CUHZLgiO.js";
|
|
2
|
-
import { a as createRoutingVirtualModule, i as createPagesVirtualModule, n as createLocalesVirtualModule, r as createMetaVirtualModule, s as transformMacros, t as createAppVirtualModule } from "./virtual-modules-
|
|
3
|
-
import { a as generateOpenApiTypes, i as generateTypes } from "./codegen-
|
|
4
|
-
import { _ as sendWebResponse, a as createServerEntryVirtualModule, d as ensureDevApp, i as createClientEntryVirtualModule, n as ubeanVite, o as createVueAppEntryVirtualModule, r as composeHeadScripts, s as createVuePagesVirtualModule, v as toWebRequest, x as getDevScanCoordinator } from "./vue-
|
|
2
|
+
import { a as createRoutingVirtualModule, i as createPagesVirtualModule, n as createLocalesVirtualModule, r as createMetaVirtualModule, s as transformMacros, t as createAppVirtualModule } from "./virtual-modules-7mD1FgVQ.js";
|
|
3
|
+
import { a as generateOpenApiTypes, i as generateTypes } from "./codegen-ClIh5VKh.js";
|
|
4
|
+
import { _ as sendWebResponse, a as createServerEntryVirtualModule, d as ensureDevApp, i as createClientEntryVirtualModule, n as ubeanVite, o as createVueAppEntryVirtualModule, r as composeHeadScripts, s as createVuePagesVirtualModule, v as toWebRequest, x as getDevScanCoordinator } from "./vue-ClGXUBJz.js";
|
|
5
5
|
import { createStaticSsgRenderer } from "./static-render.js";
|
|
6
6
|
import { prerender, resolvePrerenderStaticDir } from "./prerender.js";
|
|
7
|
+
import { resolvePageKind } from "@ubean/vue";
|
|
7
8
|
import { dirname, isAbsolute, join, relative, resolve } from "pathe";
|
|
8
9
|
import * as nodeFs from "node:fs";
|
|
9
10
|
import { existsSync, readFileSync, readdirSync, realpathSync, statSync } from "node:fs";
|
|
@@ -93,13 +94,25 @@ const _localeVueParam = ${JSON.stringify(localeVueParam)};
|
|
|
93
94
|
const _pageToRoute = p => {
|
|
94
95
|
// For reuse routes, load the target page's module (the .reuse.ts file
|
|
95
96
|
// only contains definePage metadata, not a Vue component).
|
|
96
|
-
const _targetPage = p.
|
|
97
|
+
const _targetPage = p.pageKind === 'reuse' && p.reuseTarget ? _pages.find(tp => tp.name === p.reuseTarget) : undefined;
|
|
97
98
|
const _loaderKey = _targetPage?.relativePath || p.relativePath;
|
|
99
|
+
const _routePath = p.route.replace(/\\*\\*:(\\w[\\w-]*)/g, ':$1(.*)*');
|
|
98
100
|
return {
|
|
99
|
-
path: _localeVueParam
|
|
100
|
-
? toVueRouterLocalePath(p.route.replace(/\\*\\*:(\\w[\\w-]*)/g, ':$1(.*)*'), _localeVueParam)
|
|
101
|
-
: p.route.replace(/\\*\\*:(\\w[\\w-]*)/g, ':$1(.*)*'),
|
|
101
|
+
path: _localeVueParam ? toVueRouterLocalePath(_routePath, _localeVueParam) : _routePath,
|
|
102
102
|
name: p.name,
|
|
103
|
+
// \`definePage({ redirect })\`: 不渲染组件,访问时跳转(与服务端 302 同语义)。
|
|
104
|
+
// 字符串重定向不插值参数,带语言前缀时用函数形式拼 —— 与客户端 / dev 同形。
|
|
105
|
+
// 注意这里是**生成期模板**:\`p.redirect\` 必须原样落到产物里(运行期才按页取值)。
|
|
106
|
+
...(p.redirect
|
|
107
|
+
? {
|
|
108
|
+
redirect: _localeVueParam
|
|
109
|
+
? to =>
|
|
110
|
+
to.params.locale
|
|
111
|
+
? '/' + to.params.locale + (p.redirect === '/' ? '' : p.redirect)
|
|
112
|
+
: p.redirect
|
|
113
|
+
: p.redirect
|
|
114
|
+
}
|
|
115
|
+
: {}),
|
|
103
116
|
component: async () => {
|
|
104
117
|
const loader = pageLoaders[_loaderKey];
|
|
105
118
|
if (!loader) throw new Error('Page loader not found: ' + _loaderKey);
|
|
@@ -109,7 +122,10 @@ const _pageToRoute = p => {
|
|
|
109
122
|
meta: {
|
|
110
123
|
layout: p.layout === false ? false : p.layout || _defaultLayout,
|
|
111
124
|
pageName: p.name,
|
|
112
|
-
cache: p.cache === true ? true : undefined
|
|
125
|
+
cache: p.cache === true ? true : undefined,
|
|
126
|
+
/* 同客户端 buildRouteMeta:透传 definePage({ meta })。否则靠 meta(如 iframeUrl)
|
|
127
|
+
取参数的页面在 SSR 首屏渲染空态,水合后才有值。 */
|
|
128
|
+
...(p.pageMeta?.meta || undefined)
|
|
113
129
|
}
|
|
114
130
|
};
|
|
115
131
|
};
|
|
@@ -135,7 +151,7 @@ for (const _group of _routeGroups.values()) {
|
|
|
135
151
|
// 注意组件加载器在 _record 上(_primary 是扫描得到的页面条目,没有 component 字段 ——
|
|
136
152
|
// 写成 _primary.component 会得到 undefined,vue-router 报 Invalid route component)。
|
|
137
153
|
const _components = {};
|
|
138
|
-
if (_group.default) _components.default = _record.component;
|
|
154
|
+
if (_group.default && !_group.default.redirect) _components.default = _record.component;
|
|
139
155
|
for (const _slotPage of _group.slots) _components[_slotPage.slot] = _pageToRoute(_slotPage).component;
|
|
140
156
|
delete _record.component;
|
|
141
157
|
_record.components = _components;
|
|
@@ -293,7 +309,7 @@ export async function renderStaticPage(input) {
|
|
|
293
309
|
|
|
294
310
|
// loader 检测 warn(模块加载有缓存,此处与渲染共用同一实例)
|
|
295
311
|
const _targetPage =
|
|
296
|
-
page.
|
|
312
|
+
page.pageKind === 'reuse' && page.reuseTarget ? _pages.find(tp => tp.name === page.reuseTarget) : undefined;
|
|
297
313
|
const _loaderKey = (_targetPage && _targetPage.relativePath) || page.relativePath;
|
|
298
314
|
const _modLoader = pageLoaders[_loaderKey];
|
|
299
315
|
if (_modLoader && !_loaderWarned.has(page.name)) {
|
|
@@ -936,6 +952,23 @@ function createBuildEnvironments(input) {
|
|
|
936
952
|
return {
|
|
937
953
|
client: {
|
|
938
954
|
consumer: "client",
|
|
955
|
+
/**
|
|
956
|
+
* 客户端环境的顶层 `input` 用**虚拟模块 id**,而不是 `build.rollupOptions.input` 里的绝对路径。
|
|
957
|
+
*
|
|
958
|
+
* 原因:Vite 的依赖扫描(`computeEntries`)优先读顶层 `input`,并走
|
|
959
|
+
* `pluginContainer.resolveId()` —— 虚拟模块能被插件解析;而 `rollupOptions.input` 是
|
|
960
|
+
* 磁盘路径,在没有 `src/entry.client.ts` 的项目里指向 `.ubean/virtual/client-entry.mjs`,
|
|
961
|
+
* 那是**构建产物**:dev 从不写它。
|
|
962
|
+
*
|
|
963
|
+
* 后果(实测 soybean-admin-v3,v0.6.9):扫描抛
|
|
964
|
+
* `failed to resolve rolldownOptions.input value: "…/.ubean/virtual/client-entry.mjs"`,
|
|
965
|
+
* 依赖预构建整段跳过 —— dev server 能起、页面能渲染,但依赖不预构建(首屏请求瀑布 + 慢),
|
|
966
|
+
* 且报错直接吐给用户。
|
|
967
|
+
*
|
|
968
|
+
* 用虚拟 id 还顺带让扫描真的爬进客户端入口(拿到 `vue` / `vue-router` / UI 库等依赖)。
|
|
969
|
+
* 构建期取值不变:解析后仍是同一个虚拟模块。
|
|
970
|
+
*/
|
|
971
|
+
input: "virtual:ubean-client-entry",
|
|
939
972
|
optimizeDeps: { exclude: clientOptimizeDepsExclude() },
|
|
940
973
|
build: {
|
|
941
974
|
outDir: outDirs.public,
|
|
@@ -1444,7 +1477,7 @@ function toVitePath(p) {
|
|
|
1444
1477
|
/**
|
|
1445
1478
|
* 把扫描到的页面序列化进服务端入口(`const _pages = [...]`)。
|
|
1446
1479
|
*
|
|
1447
|
-
* **白名单必须覆盖语义字段**:此前只序列化了 `relativePath/name/route/layout/reuseTarget/
|
|
1480
|
+
* **白名单必须覆盖语义字段**:此前只序列化了 `relativePath/name/route/layout/reuseTarget/pageKind/
|
|
1448
1481
|
* pageMeta`,于是 `matchers` / `slot` **在生产构建里丢失** ——
|
|
1449
1482
|
* dev 用扫描得到的活对象(`enhanceDevApp` / `buildDevSsrRoutes` 直接拿 `scanResult.pages`),所以本地
|
|
1450
1483
|
* 一切正常,只有构建产物坏掉:带着 `[id=numeric]` 的路由在 dev 返回 404、在生产返回 200(实测)。
|
|
@@ -1463,14 +1496,14 @@ function serializePagesForEntry(pages) {
|
|
|
1463
1496
|
path: p.path,
|
|
1464
1497
|
layout: p.layout,
|
|
1465
1498
|
cache: p.cache,
|
|
1466
|
-
|
|
1467
|
-
isMeta: p.isMeta,
|
|
1499
|
+
pageKind: resolvePageKind(p),
|
|
1468
1500
|
routable: p.routable,
|
|
1469
1501
|
isMarkdown: p.isMarkdown,
|
|
1470
1502
|
reuseTarget: p.reuseTarget,
|
|
1471
1503
|
pageMeta: p.pageMeta,
|
|
1472
1504
|
matchers: p.matchers,
|
|
1473
|
-
slot: p.slot
|
|
1505
|
+
slot: p.slot,
|
|
1506
|
+
redirect: p.redirect
|
|
1474
1507
|
})));
|
|
1475
1508
|
}
|
|
1476
1509
|
async function generateVirtualModulesToDisk(cwd, config, scanResult, virtualDir, preset, registry, contentSnapshot) {
|
|
@@ -1520,18 +1553,21 @@ async function generateVirtualModulesToDisk(cwd, config, scanResult, virtualDir,
|
|
|
1520
1553
|
const content = mod ? await mod.load() : "";
|
|
1521
1554
|
await writeFile(join(virtualDir, filename), header + content, "utf-8");
|
|
1522
1555
|
}
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1556
|
+
/**
|
|
1557
|
+
* 只写**有磁盘消费者**的虚拟模块。
|
|
1558
|
+
*
|
|
1559
|
+
* 这里曾经把 12 个虚拟模块全写了一遍,其中 10 个没有任何读取方 —— 构建 / dev 都从注册表取
|
|
1560
|
+
* 虚拟 id(`ubeanVite` 的 `resolveId`/`load`、dev 的 `loadModule`),磁盘副本纯装饰。代价不是
|
|
1561
|
+
* 空间而是**类型检查**:scaffold 模板的 `include` 是 `.ubean` 全目录,这些文件会被 `vue-tsc` 扫进去,
|
|
1562
|
+
* 而它们只带 `// @ts-nocheck` 头部(`server-entry.ts` 连头部都是空串)—— 实测 soybean-admin-v3
|
|
1563
|
+
* 的 30 个类型错误**全部**来自生成产物,`src/` 一个都没有。
|
|
1564
|
+
*
|
|
1565
|
+
* 剩下的三个各自有真实读取方:
|
|
1566
|
+
* - `client-entry.mjs`:`createBuildEnvironments()` 的 `clientInput` 回退路径(无 `src/entry.client.ts` 时);
|
|
1567
|
+
* - `server-entry.mjs`:`ubean` 环境的 `build.rollupOptions.input`,真正的构建入口;
|
|
1568
|
+
* - `index.html`:`writeClientIndexHtml()` 在 `prepareBuild` 里单独写,见 `build-app.ts`。
|
|
1569
|
+
*/
|
|
1570
|
+
if (hasPages) await writeModule("virtual:ubean-client-entry", "client-entry.mjs", "");
|
|
1535
1571
|
const pagesGlob = JSON.stringify(`${viteSrcPrefix}/pages/**/*.{${pagesExts}}`);
|
|
1536
1572
|
const routesGlob = JSON.stringify(`${viteSrcPrefix}/routes/**/*.{ts,js,mjs}`);
|
|
1537
1573
|
const middlewareGlob = JSON.stringify(`${viteSrcPrefix}/middleware/**/*.{ts,js,mjs}`);
|
package/dist/vite.d.ts
CHANGED
|
@@ -392,6 +392,9 @@ export declare function ubeanPreviewPlugin(options?: UbeanPreviewPluginOptions):
|
|
|
392
392
|
//#region src/dev/dev-ssr-routes.d.ts
|
|
393
393
|
/** 扫描器方言 `**:slug`(catch-all)→ vue-router 的 `:slug(.*)*`。 */
|
|
394
394
|
export declare function toVueRouterPagePath(route: string): string;
|
|
395
|
+
interface DevSsrRedirectLocation {
|
|
396
|
+
params: Record<string, string | string[]>;
|
|
397
|
+
}
|
|
395
398
|
/** vue-router 路由记录的运行期形状(只声明 dev 侧真正用到的字段)。 */
|
|
396
399
|
interface DevSsrRoute {
|
|
397
400
|
path: string;
|
|
@@ -399,11 +402,24 @@ interface DevSsrRoute {
|
|
|
399
402
|
/** 普通路由用 `component`;并行路由(`@slot/`)用 `components` 命名视图,二者互斥。 */
|
|
400
403
|
component?: () => Promise<unknown>;
|
|
401
404
|
components?: Record<string, () => Promise<unknown>>;
|
|
405
|
+
/**
|
|
406
|
+
* `definePage({ redirect })` 的目标 —— 本路由不渲染组件,访问时跳转。
|
|
407
|
+
*
|
|
408
|
+
* 与客户端 `virtual:ubean-pages` 同形:字符串是静态路径,带语言前缀时用
|
|
409
|
+
* 函数形式按 `to.params.locale` 补前缀(字符串重定向不插值参数)。
|
|
410
|
+
*/
|
|
411
|
+
redirect?: string | ((to: DevSsrRedirectLocation) => string);
|
|
402
412
|
meta: {
|
|
403
413
|
/** 单层布局名、多层嵌套数组(P9-17)或 `false`(禁用)。 */
|
|
404
414
|
layout: string | string[] | false | null;
|
|
405
415
|
pageName: string;
|
|
406
416
|
cache?: true;
|
|
417
|
+
/**
|
|
418
|
+
* 其余键是 `definePage({ meta })` 的透传(`iframeUrl` / `title` / `i18nKey` …)。
|
|
419
|
+
* 不声明的话,用到这些 meta 的页面(例如按 `meta.iframeUrl` 取地址的内嵌页)
|
|
420
|
+
* 在 SSR 首屏渲染成空态,水合后客户端才有值 —— 首屏与水合结果不一致。
|
|
421
|
+
*/
|
|
422
|
+
[key: string]: unknown;
|
|
407
423
|
};
|
|
408
424
|
}
|
|
409
425
|
interface DevSsrRoutesOptions {
|
package/dist/vite.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import { g as createVirtualRegistry } from "./ssr-singleton-CUHZLgiO.js";
|
|
2
|
-
import { A as buildDevSsrRoutes, C as peekDevScanCoordinator, D as resolveDevSecurityHeaders, E as createDevAppReady, O as resolveLocaleVueParam, S as onDevScan, T as createDevApp, _ as sendWebResponse, b as createDevScanCoordinator, c as DEVTOOLS_PASS_THROUGH_PREFIXES, d as ensureDevApp, f as getDevApp, g as ubeanDevRequestPlugin, h as isViteResourceRequest, j as toVueRouterPagePath, k as enhanceDevApp, l as collectDevCssLinks, m as isFrameworkHtmlPage, p as injectStylesheetLinks, u as createUbeanRequestHandlers, v as toWebRequest, w as bootstrapDevApp, x as getDevScanCoordinator, y as DEV_SCAN_DIRS } from "./vue-
|
|
3
|
-
import { D as createCloudflarePreviewRunner, E as MINIFLARE_INSTALL_HINT, O as defaultLoadMiniflare, _ as ubeanPreviewPlugin, a as createEnvRunnerTransport, c as getDevWorkerEntrySource, d as attachPreviewMiddleware, f as createPreviewMiddleware, g as sendPreviewStatus, h as sendPreviewFile, i as bridgeEnvRunnerInvokes, k as readCompatibilityDate, l as invalidateDevWorkerModules, m as resolvePreviewFile, n as UBEAN_DEV_ENV_NAME, o as createUbeanDevEnvironmentFactory, p as previewMimeType, r as UbeanDevEnvironment, s as DEV_WORKER_FILE, t as ubeanPlugin, u as writeDevWorkerEntry } from "./vite-
|
|
2
|
+
import { A as buildDevSsrRoutes, C as peekDevScanCoordinator, D as resolveDevSecurityHeaders, E as createDevAppReady, O as resolveLocaleVueParam, S as onDevScan, T as createDevApp, _ as sendWebResponse, b as createDevScanCoordinator, c as DEVTOOLS_PASS_THROUGH_PREFIXES, d as ensureDevApp, f as getDevApp, g as ubeanDevRequestPlugin, h as isViteResourceRequest, j as toVueRouterPagePath, k as enhanceDevApp, l as collectDevCssLinks, m as isFrameworkHtmlPage, p as injectStylesheetLinks, u as createUbeanRequestHandlers, v as toWebRequest, w as bootstrapDevApp, x as getDevScanCoordinator, y as DEV_SCAN_DIRS } from "./vue-ClGXUBJz.js";
|
|
3
|
+
import { D as createCloudflarePreviewRunner, E as MINIFLARE_INSTALL_HINT, O as defaultLoadMiniflare, _ as ubeanPreviewPlugin, a as createEnvRunnerTransport, c as getDevWorkerEntrySource, d as attachPreviewMiddleware, f as createPreviewMiddleware, g as sendPreviewStatus, h as sendPreviewFile, i as bridgeEnvRunnerInvokes, k as readCompatibilityDate, l as invalidateDevWorkerModules, m as resolvePreviewFile, n as UBEAN_DEV_ENV_NAME, o as createUbeanDevEnvironmentFactory, p as previewMimeType, r as UbeanDevEnvironment, s as DEV_WORKER_FILE, t as ubeanPlugin, u as writeDevWorkerEntry } from "./vite-C5MyVhMg.js";
|
|
4
4
|
export { DEVTOOLS_PASS_THROUGH_PREFIXES, DEV_SCAN_DIRS, DEV_WORKER_FILE, MINIFLARE_INSTALL_HINT, UBEAN_DEV_ENV_NAME, UbeanDevEnvironment, attachPreviewMiddleware, bootstrapDevApp, bridgeEnvRunnerInvokes, buildDevSsrRoutes, collectDevCssLinks, createCloudflarePreviewRunner, createDevApp, createDevAppReady, createDevScanCoordinator, createEnvRunnerTransport, createPreviewMiddleware, createUbeanDevEnvironmentFactory, createUbeanRequestHandlers, createVirtualRegistry, defaultLoadMiniflare, enhanceDevApp, ensureDevApp, getDevApp, getDevScanCoordinator, getDevWorkerEntrySource, injectStylesheetLinks, invalidateDevWorkerModules, isFrameworkHtmlPage, isViteResourceRequest, onDevScan, peekDevScanCoordinator, previewMimeType, readCompatibilityDate, resolveDevSecurityHeaders, resolveLocaleVueParam, resolvePreviewFile, sendPreviewFile, sendPreviewStatus, sendWebResponse, toVueRouterPagePath, toWebRequest, ubeanDevRequestPlugin, ubeanPlugin, ubeanPreviewPlugin, writeDevWorkerEntry };
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { _ as defineVirtualModule, c as getComponentResolvers, g as createVirtualRegistry, i as ssrSingletonDevPolicy, l as getCssImports, u as getHeadScripts } from "./ssr-singleton-CUHZLgiO.js";
|
|
2
|
-
import { D as resolveComponentsConfig, E as resolveAutoImportsConfig, O as toArray, S as getAutoImportPresets, f as COMPONENT_HALF_GLOBS } from "./codegen-
|
|
2
|
+
import { D as resolveComponentsConfig, E as resolveAutoImportsConfig, O as toArray, S as getAutoImportPresets, f as COMPONENT_HALF_GLOBS } from "./codegen-ClIh5VKh.js";
|
|
3
3
|
import { createRequire } from "node:module";
|
|
4
4
|
import { generatePagesModuleSource } from "@ubean/vue/vite";
|
|
5
|
+
import { resolvePageKind } from "@ubean/vue";
|
|
5
6
|
import { isAbsolute, join, normalize, relative, resolve } from "pathe";
|
|
6
7
|
import { existsSync } from "node:fs";
|
|
7
8
|
import { loadUbeanConfig, localePathsOf, resolvePageCompilers } from "@ubean/config";
|
|
@@ -39,6 +40,19 @@ function toVueRouterPagePath(route) {
|
|
|
39
40
|
return route.replace(/\*\*:(\w[\w-]*)/g, ":$1(.*)*");
|
|
40
41
|
}
|
|
41
42
|
/**
|
|
43
|
+
* `definePage({ redirect })` 的 vue-router 目标。
|
|
44
|
+
*
|
|
45
|
+
* 字符串重定向是**静态路径**,vue-router 不插值参数,所以带语言前缀时必须用
|
|
46
|
+
* 函数形式拼一次(客户端 `generatePagesModuleSource` 生成的是同一个形状)。
|
|
47
|
+
* `to.params.locale` 的运行期值就是 URL 段本身,不需要再查 locale 表;默认语言在
|
|
48
|
+
* `prefix_except_default` / `prefix_and_default` 下没有 param,自然不带前缀。
|
|
49
|
+
*/
|
|
50
|
+
function redirectTarget(target, localeVueParam) {
|
|
51
|
+
if (!localeVueParam) return target;
|
|
52
|
+
const suffix = target === "/" ? "" : target;
|
|
53
|
+
return (to) => to.params.locale ? `/${to.params.locale}${suffix}` : target;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
42
56
|
* 构建 SSR 路由表。
|
|
43
57
|
*
|
|
44
58
|
* 顺序与形状刻意对齐客户端:页面按扫描顺序、reuse 路由复用目标页面的组件、
|
|
@@ -54,17 +68,18 @@ function buildDevSsrRoutes(options) {
|
|
|
54
68
|
const pageByName = /* @__PURE__ */ new Map();
|
|
55
69
|
for (const page of routablePages) pageByName.set(page.name, page);
|
|
56
70
|
const componentFor = (page) => {
|
|
57
|
-
const fullPath = (page
|
|
71
|
+
const fullPath = (resolvePageKind(page) === "reuse" && page.reuseTarget ? pageByName.get(page.reuseTarget) : void 0)?.fullPath || page.fullPath;
|
|
58
72
|
return async () => await loadComponent(fullPath);
|
|
59
73
|
};
|
|
60
74
|
const routeFor = (page) => ({
|
|
61
75
|
path: withLocale(toVueRouterPagePath(page.route)),
|
|
62
76
|
name: page.name,
|
|
63
|
-
component: componentFor(page),
|
|
77
|
+
...page.redirect ? { redirect: redirectTarget(page.redirect, localeVueParam) } : { component: componentFor(page) },
|
|
64
78
|
meta: {
|
|
65
79
|
layout: page.layout === false ? false : page.layout || defaultLayout,
|
|
66
80
|
pageName: page.name,
|
|
67
|
-
cache: page.cache === true ? true : void 0
|
|
81
|
+
cache: page.cache === true ? true : void 0,
|
|
82
|
+
...page.pageMeta?.meta
|
|
68
83
|
}
|
|
69
84
|
});
|
|
70
85
|
const grouped = /* @__PURE__ */ new Map();
|
|
@@ -81,7 +96,7 @@ function buildDevSsrRoutes(options) {
|
|
|
81
96
|
const route = routeFor(primary);
|
|
82
97
|
if (group.slots.length > 0) {
|
|
83
98
|
const components = {};
|
|
84
|
-
if (group.default) components.default = componentFor(group.default);
|
|
99
|
+
if (group.default && !group.default.redirect) components.default = componentFor(group.default);
|
|
85
100
|
for (const slotPage of group.slots) components[slotPage.slot] = componentFor(slotPage);
|
|
86
101
|
delete route.component;
|
|
87
102
|
route.components = components;
|
|
@@ -120,7 +135,7 @@ function enhanceDevApp(options) {
|
|
|
120
135
|
for (const page of app.options.pages || []) pageByName.set(page.name, page);
|
|
121
136
|
const pageLoaders = {};
|
|
122
137
|
for (const page of app.options.pages || []) {
|
|
123
|
-
const fullPath = (page
|
|
138
|
+
const fullPath = (resolvePageKind(page) === "reuse" && page.reuseTarget ? pageByName.get(page.reuseTarget) : void 0)?.fullPath || page.fullPath;
|
|
124
139
|
pageLoaders[page.relativePath] = () => loadModule(fullPath);
|
|
125
140
|
}
|
|
126
141
|
app.options.pageLoaders = pageLoaders;
|
package/dist/vue.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { t as VUE_PLUGIN_INCLUDE, r as composeHeadScripts, i as createClientEntryVirtualModule, a as createServerEntryVirtualModule, o as createVueAppEntryVirtualModule, s as createVuePagesVirtualModule, n as default, n as ubeanVite } from "./vue-
|
|
1
|
+
export { t as VUE_PLUGIN_INCLUDE, r as composeHeadScripts, i as createClientEntryVirtualModule, a as createServerEntryVirtualModule, o as createVueAppEntryVirtualModule, s as createVuePagesVirtualModule, n as default, n as ubeanVite } from "./vue-ClGXUBJz.js";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ubean/build",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "Vite plugins, production build, SSG prerender, type codegen, and Server Actions plugin for ubean",
|
|
5
5
|
"files": [
|
|
6
6
|
"dist"
|
|
@@ -49,19 +49,19 @@
|
|
|
49
49
|
},
|
|
50
50
|
"dependencies": {
|
|
51
51
|
"@intlify/unplugin-vue-i18n": "11.2.7",
|
|
52
|
-
"@ubean/app": "0.
|
|
53
|
-
"@ubean/client": "0.
|
|
54
|
-
"@ubean/config": "0.
|
|
55
|
-
"@ubean/i18n": "0.
|
|
56
|
-
"@ubean/islands": "0.
|
|
57
|
-
"@ubean/markdown": "0.
|
|
58
|
-
"@ubean/pages": "0.
|
|
59
|
-
"@ubean/preset": "0.
|
|
60
|
-
"@ubean/routes": "0.
|
|
61
|
-
"@ubean/scan": "0.
|
|
62
|
-
"@ubean/server": "0.
|
|
63
|
-
"@ubean/shared": "0.
|
|
64
|
-
"@ubean/vue": "0.
|
|
52
|
+
"@ubean/app": "0.7.0",
|
|
53
|
+
"@ubean/client": "0.7.0",
|
|
54
|
+
"@ubean/config": "0.7.0",
|
|
55
|
+
"@ubean/i18n": "0.7.0",
|
|
56
|
+
"@ubean/islands": "0.7.0",
|
|
57
|
+
"@ubean/markdown": "0.7.0",
|
|
58
|
+
"@ubean/pages": "0.7.0",
|
|
59
|
+
"@ubean/preset": "0.7.0",
|
|
60
|
+
"@ubean/routes": "0.7.0",
|
|
61
|
+
"@ubean/scan": "0.7.0",
|
|
62
|
+
"@ubean/server": "0.7.0",
|
|
63
|
+
"@ubean/shared": "0.7.0",
|
|
64
|
+
"@ubean/vue": "0.7.0",
|
|
65
65
|
"@vitejs/plugin-vue": "^6.0.10",
|
|
66
66
|
"@vitejs/plugin-vue-jsx": "^5.1.6",
|
|
67
67
|
"openapi-typescript": "^7.13.0",
|