@ubean/build 0.6.1 → 0.6.3
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 +572 -0
- package/README.zh-CN.md +541 -0
- package/dist/actions-plugin.d.ts +2 -1
- package/dist/codegen/index.js +1 -2
- package/dist/production.js +1 -2
- package/dist/{vite-KFKQ41Jy.js → vite-D9LgxIQD.js} +28 -6
- package/dist/vite.d.ts +22 -13
- package/dist/vite.js +2 -2
- package/dist/{vue-Dh1dtOIU.js → vue-CAQVpHaN.js} +56 -20
- package/dist/vue.js +1 -2
- package/package.json +19 -19
package/README.zh-CN.md
ADDED
|
@@ -0,0 +1,541 @@
|
|
|
1
|
+
# @ubean/build
|
|
2
|
+
|
|
3
|
+
**[English](./README.md) | 中文**
|
|
4
|
+
|
|
5
|
+
ubean 的 Vite 插件、生产构建编排、SSG 预渲染、类型代码生成与 Server Actions 插件。
|
|
6
|
+
|
|
7
|
+
```ts
|
|
8
|
+
// vite.config.ts —— 整条工具链唯一的注册点
|
|
9
|
+
import { defineConfig } from 'vite-plus';
|
|
10
|
+
import { ubeanPlugin } from 'ubean/vite';
|
|
11
|
+
|
|
12
|
+
export default defineConfig({
|
|
13
|
+
plugins: [ubeanPlugin()]
|
|
14
|
+
});
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## 目录
|
|
18
|
+
|
|
19
|
+
- [@ubean/build](#ubeanbuild)
|
|
20
|
+
- [目录](#目录)
|
|
21
|
+
- [包定位](#包定位)
|
|
22
|
+
- [安装](#安装)
|
|
23
|
+
- [快速开始](#快速开始)
|
|
24
|
+
- [Vite 插件](#vite-插件)
|
|
25
|
+
- [Vue 插件](#vue-插件)
|
|
26
|
+
- [虚拟模块](#虚拟模块)
|
|
27
|
+
- [宏](#宏)
|
|
28
|
+
- [dev 服务端自举](#dev-服务端自举)
|
|
29
|
+
- [预览](#预览)
|
|
30
|
+
- [Server Actions](#server-actions)
|
|
31
|
+
- [生产构建](#生产构建)
|
|
32
|
+
- [预渲染](#预渲染)
|
|
33
|
+
- [静态渲染路径](#静态渲染路径)
|
|
34
|
+
- [代码生成](#代码生成)
|
|
35
|
+
- [类型安全](#类型安全)
|
|
36
|
+
- [架构说明](#架构说明)
|
|
37
|
+
|
|
38
|
+
## 包定位
|
|
39
|
+
|
|
40
|
+
`@ubean/build` 是 ubean 的**构建期包**:所有在 Vite 内运行的部分,以及由 Vite 驱动的生产构建与 SSG 流水线。
|
|
41
|
+
|
|
42
|
+
| 负责 | 不负责 |
|
|
43
|
+
| -------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------- |
|
|
44
|
+
| 核心 Vite 插件(`./vite` 的 `ubeanPlugin`):环境注册、dev 请求路由、构建编排、preview 接管 | **组合版** `ubeanPlugin()` —— 那是聚合包 `ubean` 的 `ubean/vite` |
|
|
45
|
+
| Vue 侧插件(`./vue` 的 `ubeanVite`):页面/应用/入口虚拟模块、自动引入、组件 resolver、`.vue` 编译 | Vue **编译器**本身 —— `@vitejs/plugin-vue` |
|
|
46
|
+
| 虚拟模块注册表、`definePage` 宏提取、组件/CSS resolver 注册表 | 页面扫描 —— 页面归 `@ubean/vue`,聚合扫描归 `@ubean/scan` |
|
|
47
|
+
| 生产构建编排(`buildWithEnvironments`)、preset 构建配置、server/worker/handler 入口生成器 | 部署平台定义 —— `@ubean/preset` |
|
|
48
|
+
| 预渲染 / SSG:路由收集、路由→文件映射、payload 提取、静态 entry 直接渲染 | 运行时缓存、ISR、PPR 语义 —— `@ubean/routes` / `@ubean/server` |
|
|
49
|
+
| 类型代码生成(`generateTypes` + unplugin 预设)与配置→Vite 的转换 | 配置加载与解析 —— `@ubean/config` |
|
|
50
|
+
| Server Actions 的**源码转换**(客户端 stub / 服务端 `filePath` 注入) | `defineAction` 运行时与 `POST /__actions` 处理器 —— `@ubean/routes` |
|
|
51
|
+
|
|
52
|
+
**运行时依赖白名单(主入口 `@ubean/build`):** 宏、虚拟模块注册表、组件/CSS resolver 注册表、SSR 单例策略
|
|
53
|
+
—— 不含 `vite`、不含 `@vitejs/plugin-vue`、不含 Vue 编译器。更重的能力全部落在子路径之后;
|
|
54
|
+
`./vite` 与 `./vue` 是唯一两个 import Vite 的子路径;`./production`、`./prerender`、`./static-render`、
|
|
55
|
+
`./codegen`、`./actions` 是纯构建期 —— **绝不要**从运行时路由代码 import(见[架构说明](#架构说明))。
|
|
56
|
+
|
|
57
|
+
`vite` 声明为 **optional peer dependency**(`peerDependenciesMeta.vite.optional`)。
|
|
58
|
+
|
|
59
|
+
## 安装
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
pnpm add -D @ubean/build vite
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
多数项目不会直接安装本包 —— 聚合包 `ubean` 依赖它,并通过 `ubean/vite`、`ubean/build`、`ubean/ssr` 重新导出插件。
|
|
66
|
+
|
|
67
|
+
## 快速开始
|
|
68
|
+
|
|
69
|
+
受支持的入口是聚合包提供的组合插件:
|
|
70
|
+
|
|
71
|
+
```ts
|
|
72
|
+
// vite.config.ts
|
|
73
|
+
import { defineConfig } from 'vite-plus';
|
|
74
|
+
import { ubeanPlugin } from 'ubean/vite';
|
|
75
|
+
|
|
76
|
+
export default defineConfig({
|
|
77
|
+
plugins: [ubeanPlugin()]
|
|
78
|
+
});
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
仅组合,无需 `await`:Vite 会在跑任何钩子之前 await `plugins` 数组里的 promise,因此即使工厂是 async 的,
|
|
82
|
+
`plugins: [ubeanPlugin()]` 也是正确写法。
|
|
83
|
+
|
|
84
|
+
框架无关或需要细粒度控制时直接 import 子路径:
|
|
85
|
+
|
|
86
|
+
```ts
|
|
87
|
+
import { ubeanServerActionsPlugin } from '@ubean/build/actions';
|
|
88
|
+
import { prerender } from '@ubean/build/prerender';
|
|
89
|
+
import { createStaticSsgRenderer } from '@ubean/build/static-render';
|
|
90
|
+
import { ubeanPlugin as ubeanCorePlugin } from '@ubean/build/vite'; // 无 Vue、无 islands
|
|
91
|
+
import { ubeanVite } from '@ubean/build/vue'; // Vue 页面 + 应用入口 + 自动引入
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## Vite 插件
|
|
95
|
+
|
|
96
|
+
入口:`@ubean/build/vite`。
|
|
97
|
+
|
|
98
|
+
`ubeanPlugin()` 是框架无关的核心插件(`name: 'ubean:core'`,`enforce: 'pre'`)。它的 `config` 钩子注册
|
|
99
|
+
`client` 与 `ubean` 两个环境以及 `builder.buildApp` —— 因此**裸 `vite dev | build | preview` 就是完整工具链**,
|
|
100
|
+
`ubean dev | build | preview` 只是它的薄别名。
|
|
101
|
+
|
|
102
|
+
```ts
|
|
103
|
+
import { defineConfig } from 'vite-plus';
|
|
104
|
+
import { ubeanPlugin } from '@ubean/build/vite';
|
|
105
|
+
|
|
106
|
+
export default defineConfig({
|
|
107
|
+
plugins: [ubeanPlugin()] // 未传参时从 ubean.config.ts 加载配置
|
|
108
|
+
});
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
```ts
|
|
112
|
+
// 显式传配置(`ubean dev` / `ubean build` 内部就是这么做的)
|
|
113
|
+
ubeanPlugin({ config: resolvedConfig, registry: createVirtualRegistry() });
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
| 钩子 | 作用 |
|
|
117
|
+
| --------------------------- | --------------------------------------------------------------------------------------------------------------------------------- |
|
|
118
|
+
| `config` | 需要时加载 `ubean.config.ts`、跑项目依赖检查、注册 `environments` + `builder.buildApp` |
|
|
119
|
+
| `buildStart` | 扫描项目、登记 dev 扫描基线、应用扫描结果、跑代码生成 |
|
|
120
|
+
| `resolveId` / `load` | 提供虚拟模块(`ubean:routes`、`ubean:pages`、`ubean:meta`、`ubean:app-config`、`ubean:locales`)与 `virtual:ubean-asset-manifest` |
|
|
121
|
+
| `transform` | 提取 / 剥除 `definePage` 宏 |
|
|
122
|
+
| `configureServer` | 注册 dev 扫描协调器,并在 `listening` 后生成 OpenAPI 类型 |
|
|
123
|
+
| `renderChunk` / `resolveId` | 仅 worker 目标:把 `node:*` 换成抛错的桩、重写 `import.meta.url` |
|
|
124
|
+
| `configurePreviewServer` | 挂载 preview 中间件 |
|
|
125
|
+
|
|
126
|
+
`UbeanPluginOptions`:
|
|
127
|
+
|
|
128
|
+
| 选项 | 类型 | 说明 |
|
|
129
|
+
| ---------- | ----------------------- | ---------------------------------------------------- |
|
|
130
|
+
| `config` | `UbeanResolvedConfig` | 已解析的配置;否则读全局缓存或在 `buildStart` 中加载 |
|
|
131
|
+
| `registry` | `VirtualModuleRegistry` | 调用方注入的虚拟模块注册表;省略时插件自建一个实例 |
|
|
132
|
+
|
|
133
|
+
**`experimental.viteBuilder` 已删除。** 该开关随两次 `viteBuild` 的旧编排一并下线(ADR-0012)。
|
|
134
|
+
在 `ubean.config.ts` 里写它会被当作未知字段忽略 —— 没有配置层的逃生口,只有回退版本。
|
|
135
|
+
|
|
136
|
+
**worker 目标。** 当生效 preset 的 `entryType` 为 `'worker'` 时,插件把 `node:fs` 等换成会抛错的桩,
|
|
137
|
+
并把 `import.meta.url` 重写为 `"file:///worker.mjs"`(workerd 的 ESM 里它是 `undefined`,
|
|
138
|
+
会让打包器生成的 `createRequire` 互操作垫片在模块实例化期就崩掉)。这两条只作用于 `ubean` 环境。
|
|
139
|
+
|
|
140
|
+
## Vue 插件
|
|
141
|
+
|
|
142
|
+
入口:`@ubean/build/vue`(同时是该子路径的默认导出)。
|
|
143
|
+
|
|
144
|
+
`ubeanVite()` 提供 Vue 专属的另一半:页面/应用/客户端入口/服务端入口虚拟模块、`unplugin-auto-import`、
|
|
145
|
+
`unplugin-vue-components`、Vue i18n 插件与 markdown 页面。
|
|
146
|
+
|
|
147
|
+
```ts
|
|
148
|
+
import { ubeanVite } from '@ubean/build/vue';
|
|
149
|
+
|
|
150
|
+
ubeanVite({
|
|
151
|
+
config,
|
|
152
|
+
ssr: true, // 可选
|
|
153
|
+
vue: true, // 默认:注册 @vitejs/plugin-vue
|
|
154
|
+
registry
|
|
155
|
+
});
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
| 选项 | 类型 | 说明 |
|
|
159
|
+
| ---------- | ----------------------- | ----------------------------------------------------------------------------- |
|
|
160
|
+
| `config` | `UbeanResolvedConfig` | 必填 —— 已解析的 ubean 配置 |
|
|
161
|
+
| `ssr` | `boolean` | 可选 SSR 标记 |
|
|
162
|
+
| `registry` | `VirtualModuleRegistry` | 与 `ubeanPlugin()` 同一个实例;否则由 `createVirtualRegistry()` 各自持有 |
|
|
163
|
+
| `vue` | `boolean` | 是否注册 `@vitejs/plugin-vue`(默认 **true**);自带 Vue 插件配置时传 `false` |
|
|
164
|
+
|
|
165
|
+
`ubeanVite` 拥有 `@vitejs/plugin-vue` 的注册权,因为裸 `vite dev` 只会经过这一条入口 —— 若改由 CLI 注册,
|
|
166
|
+
同一份 `.vue` 会被编译两次。`VUE_PLUGIN_INCLUDE` 与它一起导出(`[/\.vue$/, /\.md$/]`)。
|
|
167
|
+
|
|
168
|
+
自行组装插件数组的工具可用的模块工厂:
|
|
169
|
+
|
|
170
|
+
```ts
|
|
171
|
+
import {
|
|
172
|
+
createVuePagesVirtualModule,
|
|
173
|
+
createVueAppEntryVirtualModule,
|
|
174
|
+
createClientEntryVirtualModule,
|
|
175
|
+
createServerEntryVirtualModule
|
|
176
|
+
} from '@ubean/build/vue';
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
## 虚拟模块
|
|
180
|
+
|
|
181
|
+
入口:`@ubean/build`(主入口)。
|
|
182
|
+
|
|
183
|
+
虚拟模块是注册表后面的普通对象,不是全局变量。框架路径用 `createVirtualRegistry()` 建一个并**显式注入**给插件
|
|
184
|
+
—— 模块级单例会让同一个 builder 的环境之间互相污染(ADR-0012 §6)。
|
|
185
|
+
|
|
186
|
+
```ts
|
|
187
|
+
import { createVirtualRegistry, defineVirtualModule, defineVirtualModulePrefix } from '@ubean/build';
|
|
188
|
+
|
|
189
|
+
const registry = createVirtualRegistry([
|
|
190
|
+
defineVirtualModule('virtual:ubean-app-config', () => 'export const config = { mode: "fullstack" };'),
|
|
191
|
+
defineVirtualModulePrefix('virtual:ubean-page:', id => `export default {}; // ${id}`)
|
|
192
|
+
]);
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
| 导出 | 说明 |
|
|
196
|
+
| ------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------- |
|
|
197
|
+
| `VirtualModuleRegistry` | 注册表类:`register` / `resolveId` / `load` / `getModules` / `invalidate` |
|
|
198
|
+
| `createVirtualRegistry(modules?)` | 建独立实例(推荐形态) |
|
|
199
|
+
| `defineVirtualModule(id, loader)` | 精确 id 模块 |
|
|
200
|
+
| `defineVirtualModulePrefix(prefix, loader)` | 前缀匹配的模块族 |
|
|
201
|
+
| `useVirtualRegistry()` / `resetVirtualRegistry()` | 已废弃的模块级单例 —— 仅供既有测试 |
|
|
202
|
+
| `VirtualModule`、`VirtualModuleResolver`、`VirtualModuleContext`、`VirtualModuleTransform` | 类型 |
|
|
203
|
+
|
|
204
|
+
框架提供的模块:
|
|
205
|
+
|
|
206
|
+
| 工厂 | 虚拟模块 |
|
|
207
|
+
| ---------------------------------------------------------------------------- | ------------------------- |
|
|
208
|
+
| `createRoutingVirtualModule(routes, middlewares, cwd)` | API 路由 + 中间件 |
|
|
209
|
+
| `createPagesVirtualModule(pages, layouts, cwd)` | 页面 / 布局记录 |
|
|
210
|
+
| `createMetaVirtualModule()` | 路由元数据 |
|
|
211
|
+
| `createAppVirtualModule(apiRoutes, middlewares, pages, srcDir)` | 应用配置 |
|
|
212
|
+
| `createLocalesVirtualModule(locales, defaultLocale, srcDir, i18nConfigJson)` | 语言数据 + `loadLocale()` |
|
|
213
|
+
|
|
214
|
+
## 宏
|
|
215
|
+
|
|
216
|
+
入口:`@ubean/build`(主入口)。
|
|
217
|
+
|
|
218
|
+
`definePage` 是编译期宏。`transformMacros()` 在 `src/pages/`、`src/routes/`、`src/middleware/` 内提取并移除调用;
|
|
219
|
+
`stripMacros()` 是纯剥除器。
|
|
220
|
+
|
|
221
|
+
```ts
|
|
222
|
+
import { transformMacros, stripMacros } from '@ubean/build';
|
|
223
|
+
|
|
224
|
+
transformMacros('definePage({ cache: true })', '/app/src/pages/index.vue'); // → ''
|
|
225
|
+
stripMacros('definePage({ name: "Home" }); const a = 1;'); // → '; const a = 1;'
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
`MACRO_NAMES` **只**包含 `definePage`。往里加 `defineHandlerMeta` / `defineMiddleware` 会把运行时函数调用剥掉,
|
|
229
|
+
产出语法错误的产物 —— 那两个是真实运行时函数。
|
|
230
|
+
|
|
231
|
+
Vue SFC 在剥除后 `<script setup>` 为空时会保留一个注释块:一个只用 `definePage` 作为 setup 内容、
|
|
232
|
+
但同时有普通 `<script>`(`loader` + `definePage` 的文档约定形态)的页面,否则会丢掉默认导出并以
|
|
233
|
+
`MISSING_EXPORT "default"` 失败。
|
|
234
|
+
|
|
235
|
+
## dev 服务端自举
|
|
236
|
+
|
|
237
|
+
入口:`@ubean/build/vite`。
|
|
238
|
+
|
|
239
|
+
这些导出拥有 dev 请求路由与宿主 app,因此没有 CLI 时 `vite dev` 也能工作。
|
|
240
|
+
|
|
241
|
+
```ts
|
|
242
|
+
import { bootstrapDevApp, ubeanDevRequestPlugin, ensureDevApp } from '@ubean/build/vite';
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
| 导出 | 说明 |
|
|
246
|
+
| ------------------------------------------------------------------------ | ----------------------------------------------------------------------------- |
|
|
247
|
+
| `bootstrapDevApp(options)` | 由扫描结果创建 Hono dev app、接到 Vite SSR 图上、准备 `ready`、暴露 `rebuild` |
|
|
248
|
+
| `createDevApp(options)` | 扫描(可选)+ 创建宿主 app —— 不需要 Vite server |
|
|
249
|
+
| `createDevAppReady(options)` | 幂等的首次请求初始化:locales、`defineServer`、`app.init()`、`onServerReady` |
|
|
250
|
+
| `ubeanDevRequestPlugin(options)` | 请求路由插件:前置判据 + 后置兜底 |
|
|
251
|
+
| `createUbeanRequestHandlers(server, options)` | 插件内部使用的处理器 |
|
|
252
|
+
| `getDevApp(server)` / `ensureDevApp(server)` | 读取 / 懒自举某个 Vite server 关联的 dev app |
|
|
253
|
+
| `enhanceDevApp(options)` | 把 SSR 图的加载器与渲染器接到已有 Hono app 上 |
|
|
254
|
+
| `isFrameworkHtmlPage(url)` | 框架自有 HTML 页面(`/_devtools`、`/_scalar`、整个 `/__devframes/**`) |
|
|
255
|
+
| `isViteResourceRequest(url, headers)` | 请求是否属于 Vite |
|
|
256
|
+
| `collectDevCssLinks(moduleGraph)` / `injectStylesheetLinks(html, hrefs)` | CSS link 注入 |
|
|
257
|
+
| `toWebRequest(req, host, protocol)` / `sendWebResponse(res, webRes)` | Node ↔ Web 适配 |
|
|
258
|
+
|
|
259
|
+
dev 扫描协调(一套监听、一次扫描、一个重载顺序):
|
|
260
|
+
|
|
261
|
+
| 导出 | 说明 |
|
|
262
|
+
| --------------------------------------------------------------------------- | ------------------------------------------------------------------------------ |
|
|
263
|
+
| `DEV_SCAN_DIRS` | `routes`、`middleware`、`pages`、`layouts`、`plugins`、`app`、`api`、`locales` |
|
|
264
|
+
| `createDevScanCoordinator(options)` / `getDevScanCoordinator(server, init)` | 协调器生命周期 |
|
|
265
|
+
| `onDevScan(server, handler)` | 订阅扫描结果 |
|
|
266
|
+
| `peekDevScanCoordinator(server)` | 不触发初始化的读取 |
|
|
267
|
+
|
|
268
|
+
`DEVTOOLS_PASS_THROUGH_PREFIXES`(`['/__devframes', '/__devtools', '/_devtools']`)是保留 dev 命名空间的
|
|
269
|
+
唯一声明 —— 按**裸字符串前缀**匹配,而不是按路径段,因为按段匹配会漏掉兄弟路径。
|
|
270
|
+
|
|
271
|
+
dev 安全头按模式推导:
|
|
272
|
+
|
|
273
|
+
```ts
|
|
274
|
+
import { resolveDevSecurityHeaders } from '@ubean/build/vite';
|
|
275
|
+
|
|
276
|
+
// `ssg` 模式默认关闭:静态产物没有框架注入的安全头。
|
|
277
|
+
resolveDevSecurityHeaders(config); // → boolean | Record<string, unknown>
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
基于 worker 的 dev 环境(`UbeanDevEnvironment`、`createUbeanDevEnvironmentFactory`、`createEnvRunnerTransport`、
|
|
281
|
+
`bridgeEnvRunnerInvokes`、`UBEAN_DEV_ENV_NAME`)及其生成的入口(`DEV_WORKER_FILE`、`getDevWorkerEntrySource`、
|
|
282
|
+
`writeDevWorkerEntry`、`invalidateDevWorkerModules`)从同一子路径导出。它们是**可运行能力,而非当前接线的 dev 路径**:
|
|
283
|
+
`createEnvironment` 目前没有注册点,SSR 在主进程执行。
|
|
284
|
+
|
|
285
|
+
## 预览
|
|
286
|
+
|
|
287
|
+
入口:`@ubean/build/vite`。
|
|
288
|
+
|
|
289
|
+
`ubeanPreviewPlugin()` / `attachPreviewMiddleware()` 让 `vite preview` 预览**产物本身**:fullstack / backend
|
|
290
|
+
在进程内加载构建产物 `dist/server/entry.mjs` 的 `createFetchHandler()`;spa / ssg 由与 CLI 内置服务器共用的
|
|
291
|
+
静态解析器服务。
|
|
292
|
+
|
|
293
|
+
```ts
|
|
294
|
+
import { ubeanPreviewPlugin, resolvePreviewFile, previewMimeType } from '@ubean/build/vite';
|
|
295
|
+
|
|
296
|
+
ubeanPreviewPlugin();
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
| 导出 | 说明 |
|
|
300
|
+
| --------------------------------------------------------------------- | ---------------------------- |
|
|
301
|
+
| `ubeanPreviewPlugin(options?)` | preview 插件 |
|
|
302
|
+
| `attachPreviewMiddleware(server, config, host?)` | 挂到已有 preview server 上 |
|
|
303
|
+
| `createPreviewMiddleware(options)` | connect 中间件 |
|
|
304
|
+
| `resolvePreviewFile(root, pathname, mode)` | `'spa'` / `'ssg'` 的静态解析 |
|
|
305
|
+
| `sendPreviewFile(res, path)` / `sendPreviewStatus(res, status, text)` | 响应工具 |
|
|
306
|
+
| `previewMimeType(filePath)` | MIME 查询 |
|
|
307
|
+
|
|
308
|
+
cloudflare 产物经 miniflare 预览,后者是可选依赖:
|
|
309
|
+
|
|
310
|
+
```ts
|
|
311
|
+
import { createCloudflarePreviewRunner } from '@ubean/build/vite';
|
|
312
|
+
|
|
313
|
+
const result = await createCloudflarePreviewRunner({ cwd, outDir });
|
|
314
|
+
// → { ok: false, reason: 'miniflare-missing', hint: MINIFLARE_INSTALL_HINT }
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
`MINIFLARE_INSTALL_HINT` 给出替代方案:_Install it with `pnpm add -D miniflare`, or preview with
|
|
318
|
+
`wrangler dev` instead._
|
|
319
|
+
|
|
320
|
+
## Server Actions
|
|
321
|
+
|
|
322
|
+
入口:`@ubean/build/actions`。
|
|
323
|
+
|
|
324
|
+
插件检测 `defineAction()` 调用表达式并按环境拆分:**服务端**注入 `filePath`(以及可推断的 `name`),
|
|
325
|
+
使 action ID 在 dev 与构建间稳定;**客户端**把调用替换为 `createActionStub('<id>')` —— 一个 POST 到 `/__actions`
|
|
326
|
+
的 RPC stub。
|
|
327
|
+
|
|
328
|
+
它已包含在 `ubean/vite` 的组合版 `ubeanPlugin()` 中 —— 只有自行组装插件数组时才需要手动加入。
|
|
329
|
+
|
|
330
|
+
```ts
|
|
331
|
+
import { ubeanServerActionsPlugin } from '@ubean/build/actions';
|
|
332
|
+
|
|
333
|
+
plugin: ubeanServerActionsPlugin({ root: process.cwd() });
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
| 导出 | 说明 |
|
|
337
|
+
| ----------------------------------------------------------- | ------------------------------------------------------- |
|
|
338
|
+
| `findDefineActionCalls(code)` / `hasDefineActionCall(code)` | 检测 |
|
|
339
|
+
| `transformActionsForServer(code, filePath, root)` | 注入 `filePath` / `name` |
|
|
340
|
+
| `transformActionsForClient(code, filePath, root)` | 替换为 `createActionStub(...)` |
|
|
341
|
+
| `ServerActionsPluginOptions` | `{ root?: string }` —— 默认插件创建时的 `process.cwd()` |
|
|
342
|
+
|
|
343
|
+
name 推断顺序:options 对象里的 `name` 属性 → 赋值目标的变量/属性名(`export const login = defineAction(...)`
|
|
344
|
+
→ `'login'`)→ `'anonymous'`。
|
|
345
|
+
|
|
346
|
+
## 生产构建
|
|
347
|
+
|
|
348
|
+
入口:`@ubean/build/production`。
|
|
349
|
+
|
|
350
|
+
```ts
|
|
351
|
+
import { buildWithEnvironments, getPresetBuildConfig, generateNodeServerEntry } from '@ubean/build/production';
|
|
352
|
+
|
|
353
|
+
const manifest = await buildWithEnvironments({
|
|
354
|
+
cwd,
|
|
355
|
+
config,
|
|
356
|
+
preset,
|
|
357
|
+
scanResult,
|
|
358
|
+
minify: true,
|
|
359
|
+
sourcemap: false,
|
|
360
|
+
contentSnapshot
|
|
361
|
+
});
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
| 导出 | 说明 |
|
|
365
|
+
| ------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------- |
|
|
366
|
+
| `buildWithEnvironments(options)` | 一次 `createBuilder` 完成整套构建;返回 `BuildManifest` |
|
|
367
|
+
| `BuildOptions` | `cwd`、`config`、`preset`、`scanResult`、`minify?`、`sourcemap?`、`contentSnapshot?` |
|
|
368
|
+
| `BuildManifest` | `assets`、`entry`、`clientDir`、`serverDir`、`preset` |
|
|
369
|
+
| `getPresetBuildConfig(preset)` | `{ format, target, entryType, external }` —— `entryType` 为 `'node'` / `'worker'` / `'fetch'` |
|
|
370
|
+
| `generateNodeServerEntry()` / `generateStandardHandlerEntry()` / `generateCloudflareWorkerEntry()` | 按 entryType 生成服务端产物 |
|
|
371
|
+
| `generateVirtualModulesToDisk(cwd, config, scanResult, virtualDir, preset, registry, contentSnapshot?)` | 虚拟模块的构建期快照 |
|
|
372
|
+
| `serializePagesForEntry(pages)` | 把扫描到的页面序列化进服务端入口 |
|
|
373
|
+
| `createIslandsSsrStubPlugin()` | SSR 期的 islands 注册表空壳 |
|
|
374
|
+
|
|
375
|
+
**`serializePagesForEntry()` 是一份白名单,而这份白名单承载语义。** 它此前只序列化
|
|
376
|
+
`relativePath` / `name` / `route` / `layout` / `reuseTarget` / `isReuse` / `pageMeta`,于是 `matchers` 与 `slot`
|
|
377
|
+
**在生产构建里消失** —— dev 用扫描得到的活对象因此一切正常,而带 `[id=numeric]` 的路由在 dev 返回 404、
|
|
378
|
+
在生产返回 200。给 `ScannedPage` 加字段就必须同步加到这里;`packages/builder/test/page-metadata.test.ts`
|
|
379
|
+
逐字段锁住这份名单。
|
|
380
|
+
|
|
381
|
+
`buildWithEnvironments()` 在执行期间声明「构建由调用方驱动」(`UBEAN_BUILD_DRIVEN_BY_CLI=1`),
|
|
382
|
+
使插件的 `config` 钩子**不要**再注册自己的 `buildApp` —— 否则插件的返回值会覆盖内联的 `builder` 字段,
|
|
383
|
+
表现为构建静默产出空结果。
|
|
384
|
+
|
|
385
|
+
## 预渲染
|
|
386
|
+
|
|
387
|
+
入口:`@ubean/build/prerender`。
|
|
388
|
+
|
|
389
|
+
`prerender()` 用 `fetcher` 跑收集到的路由列表、落盘 HTML,并可选把页面 payload 外置为 `__data.json`。
|
|
390
|
+
|
|
391
|
+
```ts
|
|
392
|
+
import { prerender, collectPrerenderRoutes, routeToFilePath } from '@ubean/build/prerender';
|
|
393
|
+
|
|
394
|
+
const result = await prerender({
|
|
395
|
+
cwd,
|
|
396
|
+
outputDir: 'dist',
|
|
397
|
+
pages,
|
|
398
|
+
prerender: { all: true },
|
|
399
|
+
fetcher: async url => ({ html: await render(url), statusCode: 200 }),
|
|
400
|
+
expandRoutes: routes => routes, // i18n 展开钩子
|
|
401
|
+
notFoundRoute: true // 静态 SSG:把 404 哨兵入队 → 404.html
|
|
402
|
+
});
|
|
403
|
+
```
|
|
404
|
+
|
|
405
|
+
| 导出 | 说明 |
|
|
406
|
+
| -------------------------------------------------------- | --------------------------------------------------------------------- |
|
|
407
|
+
| `prerender(options)` | 完整运行 → `PrerenderResult` |
|
|
408
|
+
| `collectPrerenderRoutes(pages, options)` | `{ routes, skipped }`;合并 `include` + routeRule 发现 + content 路由 |
|
|
409
|
+
| `extractPrerenderRoutesFromRules(routeRules)` | 标记 `prerender: true` 或 `ppr: true` 的路由 |
|
|
410
|
+
| `extractLinks(html, baseUrl?)` | 爬链支持 |
|
|
411
|
+
| `routeToFilePath(route, outputDir)` | `/` → `index.html`,404 哨兵 → `404.html`,`/robots.txt` 原样保留 |
|
|
412
|
+
| `routeToDataFilePath(route, outputDir)` | 每个路由的 `__data.json` 路径 |
|
|
413
|
+
| `extractDataPayload(html, route)` | 从 HTML 中抽出 `__UBEAN_DATA__` → `ExtractedPayload` |
|
|
414
|
+
| `writePrerenderedFile(filePath, html)` | 写入工具 |
|
|
415
|
+
| `resolvePrerenderStaticDir(cwd, buildOutputDir, config)` | 解析 `<outputDir>/public` —— 不再写死 `dist/public` |
|
|
416
|
+
| `generatePrerenderManifest(result, baseUrl?)` | 供部署工具消费的清单 |
|
|
417
|
+
|
|
418
|
+
`PrerendererOptions.fetcher` 是同构点:fullstack 路径提供 `createSsrFetcher`(导入真实 server entry,
|
|
419
|
+
逐路由走 Hono 管道),SSG 路径提供 `./static-render` 的直接渲染器。
|
|
420
|
+
|
|
421
|
+
## 静态渲染路径
|
|
422
|
+
|
|
423
|
+
入口:`@ubean/build/static-render`。
|
|
424
|
+
|
|
425
|
+
`mode: 'ssg'` 通过**最小静态 entry** 渲染,而不是走请求管道(ADR-0011)。`createStaticSsgRenderer()`
|
|
426
|
+
加载构建产物中的静态 entry 并返回同一个 fetcher 契约,因此 `prerender()` 无需改动。
|
|
427
|
+
|
|
428
|
+
```ts
|
|
429
|
+
import { createStaticSsgRenderer, matchRoutePattern, expandRoutesForLocales } from '@ubean/build/static-render';
|
|
430
|
+
|
|
431
|
+
const renderer = await createStaticSsgRenderer(cwd, manifest, {
|
|
432
|
+
pages,
|
|
433
|
+
notFoundPage,
|
|
434
|
+
i18n: { enabled: true, strategy: 'prefix_except_default', defaultLocale: 'en', locales: ['en', 'zh'] }
|
|
435
|
+
});
|
|
436
|
+
|
|
437
|
+
const result = await prerender({
|
|
438
|
+
...options,
|
|
439
|
+
expandRoutes: renderer.expandRoutes,
|
|
440
|
+
fetcher: renderer.fetcher
|
|
441
|
+
});
|
|
442
|
+
```
|
|
443
|
+
|
|
444
|
+
| 导出 | 说明 |
|
|
445
|
+
| ---------------------------------------------- | -------------------------------------------------------------- |
|
|
446
|
+
| `createStaticSsgRenderer(cwd, manifest, init)` | 加载 `dist/server/entry.mjs`;失败返回 `undefined` |
|
|
447
|
+
| `StaticSsgRenderer` | `{ fetcher, expandRoutes, renderStaticPage }` |
|
|
448
|
+
| `StaticSsgRenderer.renderStaticPage` | 生成进静态 entry 产物的函数(构建产物,不是本子路径的 import) |
|
|
449
|
+
| `compilePageRoute(page)` | 把扫描到的页面编译为可匹配路由 |
|
|
450
|
+
| `matchRoutePattern(pages, path)` | 按特异性排序匹配;返回 `StaticRouteMatch \| null` |
|
|
451
|
+
| `expandRoutesForLocales(routes, i18n)` | 按 strategy 展开全语言 URL |
|
|
452
|
+
| `STATIC_NOT_FOUND_ROUTE` | `'/__ubean_404__'` —— 404 哨兵路由 |
|
|
453
|
+
|
|
454
|
+
**直接路径没有什么**(ADR-0011):没有 Hono app、没有 API 路由、没有中间件、没有 crons、没有 IPX、
|
|
455
|
+
不生成 preset 包装文件。页面 `loader` **不执行** —— 导出它会打印一次性警告,数据只能来自 content collections、
|
|
456
|
+
模块级常量或客户端水合 fetch。Server actions、ISR / PPR / streaming、routeRule 的 redirect/rewrite
|
|
457
|
+
在静态产物里没有执行点。fullstack 的 `prerender` 路径正好相反:逐路由驱动真实 server entry,
|
|
458
|
+
因此 loader 与中间件都会跑。
|
|
459
|
+
|
|
460
|
+
`matchRoutePattern` 同时理解两种方言:扫描器的(`:param`、`**:slug`、`:id?`)与来自 `definePage({ path })`
|
|
461
|
+
的 vue-router 语法(`:param(regex)`、`:param*`、`:param+`)。参数会 `decodeURIComponent`,与 `c.req.param()` 对齐。
|
|
462
|
+
|
|
463
|
+
## 代码生成
|
|
464
|
+
|
|
465
|
+
入口:`@ubean/build/codegen`。
|
|
466
|
+
|
|
467
|
+
```ts
|
|
468
|
+
import { generateTypes, CODEGEN_CONTRACT_VERSION, CODEGEN_FILES } from '@ubean/build/codegen';
|
|
469
|
+
|
|
470
|
+
const result = await generateTypes(scanResult, {
|
|
471
|
+
cwd,
|
|
472
|
+
srcDir: 'src',
|
|
473
|
+
buildDir: '.ubean',
|
|
474
|
+
autoImports: config.autoImports,
|
|
475
|
+
components: config.components
|
|
476
|
+
});
|
|
477
|
+
```
|
|
478
|
+
|
|
479
|
+
| 导出 | 说明 |
|
|
480
|
+
| ---------------------------------------------------------------------------------------- | --------------------------------------------------------------------------- |
|
|
481
|
+
| `generateTypes(result, options)` | 写出全部声明,返回 `CodegenResult` + `codegen.manifest.json` |
|
|
482
|
+
| `generateRouteTypes(result, options)` | `routes.d.ts`(`ubean:routes`:`ApiRouteMap`、`ApiRoutePath`、`ApiMethod`) |
|
|
483
|
+
| `generatePageTypes(result, options)` | `pages.d.ts`(`ubean:pages`:`RouteName`、`LayoutName`) |
|
|
484
|
+
| `generateI18nTypes(result, options)` / `messagesToTsType(value, indent?)` | `i18n.d.ts`(`vue-i18n` 的 `DefineLocaleMessage`) |
|
|
485
|
+
| `generateOpenApiTypes(schema, options)` | 由 OpenAPI schema 生成 `openapi.d.ts` |
|
|
486
|
+
| `generateOpenApiTypesFromServer(baseUrl, options)` | 同上,但从 base URL 拉取 `/_openapi.json` |
|
|
487
|
+
| `generateAutoImports(scanResult, options)` | 由扫描结果生成自动引入声明 |
|
|
488
|
+
| `CODEGEN_CONTRACT_VERSION` / `CODEGEN_FILES` / `CodegenManifest` / `CodegenFileContract` | 产物文件契约(带版本,供 studio / IDE 消费) |
|
|
489
|
+
|
|
490
|
+
自动引入预设解析 —— 运行时插件与代码生成共用:
|
|
491
|
+
|
|
492
|
+
```ts
|
|
493
|
+
import { resolveAutoImportsConfig, resolveComponentsConfig, getAutoImportPresets } from '@ubean/build/codegen';
|
|
494
|
+
|
|
495
|
+
const resolved = resolveAutoImportsConfig(config.autoImports);
|
|
496
|
+
const presets = getAutoImportPresets(resolved);
|
|
497
|
+
```
|
|
498
|
+
|
|
499
|
+
`ResolvedAutoImports` 把 `boolean | AutoImportOptions` 拆成分库开关 —— `enabled`、`ubean`(默认 `true`)、
|
|
500
|
+
`vue`、`vueRouter`、`vueI18n`、`honoOpenapi`(其余默认 `false`)—— 外加透传用的原始 `options`。
|
|
501
|
+
预设常量也一并导出:`VUE_PRESET`、`VUE_MACROS_PRESET`、`UBEAN_CLIENT_PRESET`、`VUE_ROUTER_PRESET`、
|
|
502
|
+
`VUE_I18N_PRESET`、`UBEAN_SERVER_PRESET`、`HONO_OPENAPI_PRESET`、`BUILTIN_PRESETS`,以及 `toArray`、
|
|
503
|
+
`getBuiltinComposables`、`getUbeanAutoImportConfig`、`getUbeanComponentsConfig`、`generateImportsTransform`。
|
|
504
|
+
配对组件的两个半边(`**/*.server.vue`、`**/*.client.vue`)在组件扫描阶段被识别,因此由同一次代码生成产出声明。
|
|
505
|
+
|
|
506
|
+
**代码生成在哪里跑也是契约的一部分。** 此前只有 CLI 会跑,于是裸 `vite dev` / `vite build` 完全不产出
|
|
507
|
+
`.ubean/*.d.ts`,用户的类型检查直接不成立。现在插件在 `buildStart` 里跑 `generateTypes()`,
|
|
508
|
+
并在设置了 `UBEAN_CODEGEN_BY_CLI=1` 或 `UBEAN_BUILD_DRIVEN_BY_CLI=1` 时让位,保证每份产物只有一个执行者。
|
|
509
|
+
|
|
510
|
+
## 类型安全
|
|
511
|
+
|
|
512
|
+
- 每个子路径都带 `.d.ts`(`package.json` 逐 export condition 声明 `types`)。
|
|
513
|
+
- `CodegenResult` 返回它写出的每个产物的路径,调用方无需猜文件名。
|
|
514
|
+
- `CodegenManifest` + `CODEGEN_CONTRACT_VERSION` 是面向外部消费者(studio、IDE 插件)的机器可读契约 ——
|
|
515
|
+
`codegen.manifest.json` 逐文件记录其虚拟模块、是否必需、本次是否真的生成。
|
|
516
|
+
- `PresetBuildConfig` 是 `ReturnType<typeof getPresetBuildConfig>`,因此服务端环境的外部化列表与 preset 实现保持同步。
|
|
517
|
+
- `VirtualModuleRegistry` 实现了 `VirtualModuleResolver` —— 插件消费的只读视图,因此插件代码无法改动被交给它的注册表。
|
|
518
|
+
- 配置类型从 `@ubean/config` 的 `ResolvedConfig` 流入;本包每个选项都用该类型,不重复声明配置形状。
|
|
519
|
+
|
|
520
|
+
## 架构说明
|
|
521
|
+
|
|
522
|
+
- **生命周期归属(ADR-0012)。** `config` 钩子注册 `client` + `ubean` 两个环境与
|
|
523
|
+
`builder.buildApp`;因此 `vite dev | build | preview` 就是完整工具链。两次 `viteBuild` 的旧编排与
|
|
524
|
+
`experimental.viteBuilder` 开关**已删除** —— 现在写这个字段会被静默忽略。回滚只能回退版本,这是有意的:
|
|
525
|
+
一个能悄悄改语义的开关,代价高于回退版本。
|
|
526
|
+
- **三个阶段、两种驱动方。** `prepareBuild` → `runEnvBuilds` 是共用的;`buildWithEnvironments()` 自建 builder,
|
|
527
|
+
而插件路径让 Vite 创建 builder,只调这两个阶段。在 `buildApp` 里调 `buildWithEnvironments()` 会递归。
|
|
528
|
+
- **不拆 `ssr` 环境。** 刻意不对齐 Nitro:ubean 是 Vue 专属,服务端入口本就把 Hono app 与 Vue 渲染器合成一体,
|
|
529
|
+
第三个环境只会换来跨环境单例代理,别无所获(ADR-0012 §2)。
|
|
530
|
+
- **产物布局是 `dist/{public,server}` + `dist/manifest.json`。** 刻意不采用 `.output/` —— 该布局已被文档、
|
|
531
|
+
preview 接管与 `analyze` 的基线读取承诺(ADR-0012 §5)。
|
|
532
|
+
- **预渲染保持在服务端产物之后。** 两条渲染路径都消费构建产物,因此 prerender 不能提到打包之前
|
|
533
|
+
(对 Nitro 的第二处刻意偏离,ADR-0012 §4)。
|
|
534
|
+
- **资产标签在构建期内联**,不在运行时读 `<public>/.vite/manifest.json`。
|
|
535
|
+
- **绝不要从运行时路由代码 import 本包。** 它会把整条构建工具链打进服务端产物:在 Node 上只是体积浪费,
|
|
536
|
+
在 worker 上则**构建期直接失败**,因为工具链的可选依赖(`velocityjs`、`atpl` …)无法打包。
|
|
537
|
+
静态资源与缓存走平台层(ADR-0013)。
|
|
538
|
+
- **`env-runner` 不得成为本包的依赖** —— 它留在 `@ubean/cli`。加到这里会触发 pnpm 的 peer 变体分裂,
|
|
539
|
+
表现为 `Plugin` 类型身份不一致的类型错误。dev 环境代码因此用本地结构性接口(`EnvRunnerLike`)。
|
|
540
|
+
- **跨实例共享状态挂 `globalThis`,不用模块级 `Map`。** dev 的 SSR 图会内联 `ubean`、外部化 `@ubean/vue`,
|
|
541
|
+
两份模块实例共存且读到的注册表不是同一份,**没有任何报错**。组件/CSS resolver 注册表遵循这条规则。
|
package/dist/actions-plugin.d.ts
CHANGED
|
@@ -58,4 +58,5 @@ export declare function transformActionsForClient(code: string, filePath: string
|
|
|
58
58
|
* - 客户端(浏览器):将 `defineAction(...)` 替换为 `createActionStub('<id>')`。
|
|
59
59
|
*/
|
|
60
60
|
export declare function ubeanServerActionsPlugin(options?: ServerActionsPluginOptions): Plugin;
|
|
61
|
-
//#endregion
|
|
61
|
+
//#endregion
|
|
62
|
+
export {};
|
package/dist/codegen/index.js
CHANGED
|
@@ -1,2 +1 @@
|
|
|
1
|
-
|
|
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 };
|
|
1
|
+
export { u as BUILTIN_PRESETS, t as CODEGEN_CONTRACT_VERSION, n as CODEGEN_FILES, f as HONO_OPENAPI_PRESET, p as UBEAN_CLIENT_PRESET, m as UBEAN_SERVER_PRESET, h as VUE_I18N_PRESET, g as VUE_MACROS_PRESET, _ as VUE_PRESET, v as VUE_ROUTER_PRESET, y as generateAutoImports, c as generateI18nTypes, b as generateImportsTransform, i as generateOpenApiTypes, a as generateOpenApiTypesFromServer, o as generatePageTypes, s as generateRouteTypes, r as generateTypes, x as getAutoImportPresets, S as getBuiltinComposables, C as getUbeanAutoImportConfig, w as getUbeanComponentsConfig, l as messagesToTsType, T as resolveAutoImportsConfig, E as resolveComponentsConfig, D as toArray } from "../codegen-DNUCCW3C.js";
|
package/dist/production.js
CHANGED
|
@@ -1,2 +1 @@
|
|
|
1
|
-
|
|
2
|
-
export { buildWithEnvironments, createIslandsSsrStubPlugin, generateCloudflareWorkerEntry, generateNodeServerEntry, generateStandardHandlerEntry, generateVirtualModulesToDisk, getPresetBuildConfig, serializePagesForEntry };
|
|
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-D9LgxIQD.js";
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { a as ssrSingletonProdOptimizeExclude, m as createVirtualRegistry, o as ssrSingletonProdSsr, y as __exportAll } from "./ssr-singleton-Bbds29jS.js";
|
|
2
2
|
import { a as createRoutingVirtualModule, i as createPagesVirtualModule, n as createLocalesVirtualModule, r as createMetaVirtualModule, s as transformMacros, t as createAppVirtualModule } from "./virtual-modules-BK0L1Op-.js";
|
|
3
3
|
import { i as generateOpenApiTypes, r as generateTypes } from "./codegen-DNUCCW3C.js";
|
|
4
|
-
import { _ as toWebRequest, a as createVueAppEntryVirtualModule, b as getDevScanCoordinator, g as sendWebResponse, i as createServerEntryVirtualModule, n as ubeanVite, o as createVuePagesVirtualModule, r as createClientEntryVirtualModule, u as ensureDevApp } from "./vue-
|
|
4
|
+
import { _ as toWebRequest, a as createVueAppEntryVirtualModule, b as getDevScanCoordinator, g as sendWebResponse, i as createServerEntryVirtualModule, n as ubeanVite, o as createVuePagesVirtualModule, r as createClientEntryVirtualModule, u as ensureDevApp } from "./vue-CAQVpHaN.js";
|
|
5
5
|
import { createStaticSsgRenderer } from "./static-render.js";
|
|
6
6
|
import { prerender, resolvePrerenderStaticDir } from "./prerender.js";
|
|
7
7
|
import { dirname, join, relative, resolve } from "pathe";
|
|
8
8
|
import * as nodeFs from "node:fs";
|
|
9
9
|
import { existsSync, readFileSync, readdirSync, realpathSync, statSync } from "node:fs";
|
|
10
|
-
import { loadUbeanConfig, resolveModules, resolvePrerenderConfig, tryGetConfig } from "@ubean/config";
|
|
10
|
+
import { checkProjectDependenciesOnce, loadUbeanConfig, resolveModules, resolvePrerenderConfig, tryGetConfig } from "@ubean/config";
|
|
11
11
|
import { isEphemeralCachePreset, registerBuiltinPresets, resolvePresetByName, resolveProductionCacheStore } from "@ubean/preset";
|
|
12
12
|
import { createServerRouter } from "@ubean/routes";
|
|
13
13
|
import { scanProject } from "@ubean/scan";
|
|
@@ -356,8 +356,14 @@ const RESOLVED_ID = `\0${ASSET_MANIFEST_VIRTUAL_ID}`;
|
|
|
356
356
|
/**
|
|
357
357
|
* 从 client manifest 算出资产标签。
|
|
358
358
|
*
|
|
359
|
-
*
|
|
360
|
-
*
|
|
359
|
+
* CSS 取**全量 chunk 的并集**,而不是只读 entry:代码分割后页面/布局的样式落在各自的 chunk 上,
|
|
360
|
+
* entry 的 `css` 常常是空的(实测 `examples/ubean-test` 为 0 条),只读 entry 会让生产 HTML
|
|
361
|
+
* 首帧无样式(FOUC)—— 与 dev 侧 `collectDevCssLinks` 读 client 图为空是同一类缺陷。
|
|
362
|
+
* entry 自己的 css 排在前面,其余按文件名排序,保证构建可重现。
|
|
363
|
+
*
|
|
364
|
+
* 代价:会多加载未访问页面的 CSS(实测 ubean-test 15 个 chunk 共 12.0KB、docs 仅 +0.3KB)。
|
|
365
|
+
* 若要收敛到「只注入当前页实际用到的」,需要走 ssr-manifest + 渲染期收集(见 dev 侧的
|
|
366
|
+
* `isCssModuleUrl` 路径),属于更大的改造。
|
|
361
367
|
*/
|
|
362
368
|
function computeAssetTags(manifest, favicon) {
|
|
363
369
|
const tags = {
|
|
@@ -366,10 +372,16 @@ function computeAssetTags(manifest, favicon) {
|
|
|
366
372
|
body: "",
|
|
367
373
|
favicon: favicon ?? null
|
|
368
374
|
};
|
|
369
|
-
const
|
|
375
|
+
const entries = manifest ? Object.values(manifest) : [];
|
|
376
|
+
const entry = entries.find((item) => item.isEntry);
|
|
370
377
|
if (!entry) return tags;
|
|
371
378
|
tags.body = `<script type="module" src="/${entry.file}"><\/script>`;
|
|
372
|
-
|
|
379
|
+
const cssFiles = /* @__PURE__ */ new Set();
|
|
380
|
+
for (const css of entry.css ?? []) cssFiles.add(css);
|
|
381
|
+
const rest = /* @__PURE__ */ new Set();
|
|
382
|
+
for (const item of entries) for (const css of item.css ?? []) if (!cssFiles.has(css)) rest.add(css);
|
|
383
|
+
for (const css of [...rest].sort()) cssFiles.add(css);
|
|
384
|
+
if (cssFiles.size > 0) tags.css = [...cssFiles].map((css) => `<link rel="stylesheet" href="/${css}">`).join("\n");
|
|
373
385
|
return tags;
|
|
374
386
|
}
|
|
375
387
|
/** 从磁盘读 client manifest(`<publicDir>/.vite/manifest.json`)。 */
|
|
@@ -2538,6 +2550,16 @@ function ubeanPlugin(options) {
|
|
|
2538
2550
|
ubeanConfig = await loadUbeanConfig();
|
|
2539
2551
|
ensureDerived();
|
|
2540
2552
|
}
|
|
2553
|
+
if (!process.env.UBEAN_SKIP_DEP_CHECK) await checkProjectDependenciesOnce({
|
|
2554
|
+
cwd: ubeanConfig.rootDir,
|
|
2555
|
+
config: ubeanConfig,
|
|
2556
|
+
srcDir: ubeanConfig.srcDir,
|
|
2557
|
+
mode: "prompt",
|
|
2558
|
+
logger: {
|
|
2559
|
+
info: (message) => logger.info(message),
|
|
2560
|
+
warn: (message) => logger.warn(message)
|
|
2561
|
+
}
|
|
2562
|
+
});
|
|
2541
2563
|
if (process.env.UBEAN_BUILD_DRIVEN_BY_CLI === "1") return void 0;
|
|
2542
2564
|
return {
|
|
2543
2565
|
/**
|