@ubean/build 0.6.2 → 0.6.4
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.d.ts +21 -1
- package/dist/codegen/index.js +1 -2
- package/dist/{codegen-DNUCCW3C.js → codegen-BL0s5Pyq.js} +101 -2
- package/dist/env/plugin.d.ts +89 -0
- package/dist/env/plugin.js +80 -0
- package/dist/prerender.js +1 -1
- package/dist/production.js +1 -2
- package/dist/types-JZjwXWJd.js +160 -0
- package/dist/{vite-CrhHLCqC.js → vite-DR0qiIma.js} +43 -8
- package/dist/vite.d.ts +11 -6
- package/dist/vite.js +2 -2
- package/dist/{vue-DTMMMa4v.js → vue-MyIMqUw0.js} +30 -12
- package/dist/vue.js +1 -2
- package/package.json +23 -19
package/README.md
ADDED
|
@@ -0,0 +1,572 @@
|
|
|
1
|
+
# @ubean/build
|
|
2
|
+
|
|
3
|
+
**English | [中文](./README.zh-CN.md)**
|
|
4
|
+
|
|
5
|
+
Vite plugins, production build orchestration, SSG prerender, type codegen, and the Server Actions plugin for ubean.
|
|
6
|
+
|
|
7
|
+
```ts
|
|
8
|
+
// vite.config.ts — the single registration point of the whole toolchain
|
|
9
|
+
import { defineConfig } from 'vite-plus';
|
|
10
|
+
import { ubeanPlugin } from 'ubean/vite';
|
|
11
|
+
|
|
12
|
+
export default defineConfig({
|
|
13
|
+
plugins: [ubeanPlugin()]
|
|
14
|
+
});
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Table of Contents
|
|
18
|
+
|
|
19
|
+
- [Package Positioning](#package-positioning)
|
|
20
|
+
- [Install](#install)
|
|
21
|
+
- [Quick Start](#quick-start)
|
|
22
|
+
- [Vite Plugin](#vite-plugin)
|
|
23
|
+
- [Vue Plugin](#vue-plugin)
|
|
24
|
+
- [Virtual Modules](#virtual-modules)
|
|
25
|
+
- [Macros](#macros)
|
|
26
|
+
- [Dev Server Bootstrap](#dev-server-bootstrap)
|
|
27
|
+
- [Preview](#preview)
|
|
28
|
+
- [Server Actions](#server-actions)
|
|
29
|
+
- [Production Build](#production-build)
|
|
30
|
+
- [Prerender](#prerender)
|
|
31
|
+
- [Static Render Path](#static-render-path)
|
|
32
|
+
- [Codegen](#codegen)
|
|
33
|
+
- [Type Safety](#type-safety)
|
|
34
|
+
- [Architecture Notes](#architecture-notes)
|
|
35
|
+
|
|
36
|
+
## Package Positioning
|
|
37
|
+
|
|
38
|
+
`@ubean/build` is the **build-time package** of ubean: everything that runs inside Vite, plus the
|
|
39
|
+
production-build and SSG pipeline that Vite drives.
|
|
40
|
+
|
|
41
|
+
| Responsible for | NOT responsible for |
|
|
42
|
+
| ------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------- |
|
|
43
|
+
| The core Vite plugin (`ubeanPlugin` in `./vite`): environments, dev request routing, build orchestration, preview takeover | The **composed** `ubeanPlugin()` — that is `ubean/vite` in the `ubean` aggregator package |
|
|
44
|
+
| The Vue-side plugin (`ubeanVite` in `./vue`): page/app/entry virtual modules, auto-imports, component resolvers, `.vue` compile | Vue **compiler** behaviour itself — `@vitejs/plugin-vue` |
|
|
45
|
+
| Virtual-module registry, `definePage` macro extraction, component/CSS resolver registry | Page scanning — `@ubean/vue` owns pages, `@ubean/scan` owns the aggregating scan |
|
|
46
|
+
| Production orchestrations (`buildWithEnvironments`), preset build config, server/worker/handler entry generators | Deployment platform definitions — `@ubean/preset` |
|
|
47
|
+
| Prerender / SSG: route collection, route→file mapping, payload extraction, static-entry direct render | Runtime caching, ISR, PPR semantics — `@ubean/routes` / `@ubean/server` |
|
|
48
|
+
| Type codegen (`generateTypes` + the unplugin presets) and the config → Vite translation | Config loading/resolution — `@ubean/config` |
|
|
49
|
+
| Server Actions **source transform** (client stub / server `filePath` injection) | The `defineAction` runtime and the `POST /__actions` handler — `@ubean/routes` |
|
|
50
|
+
|
|
51
|
+
**Runtime dependency whitelist (main entry `@ubean/build`):** macros, the virtual-module registry, the
|
|
52
|
+
component/CSS resolver registry, and the SSR singleton policy — no `vite`, no `@vitejs/plugin-vue`, no
|
|
53
|
+
Vue compiler. Every heavier capability sits behind a subpath; `./vite` and `./vue` are the only two
|
|
54
|
+
that import Vite, and `./production`, `./prerender`, `./static-render`, `./codegen`, `./actions` are
|
|
55
|
+
build-time only — never import them from runtime route code (see [Architecture Notes](#architecture-notes)).
|
|
56
|
+
|
|
57
|
+
`vite` is declared as an **optional peer dependency** (`peerDependenciesMeta.vite.optional`).
|
|
58
|
+
|
|
59
|
+
## Install
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
pnpm add -D @ubean/build vite
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Most projects never install this package directly — the `ubean` aggregator depends on it and
|
|
66
|
+
re-exports the plugin through `ubean/vite`, `ubean/build`, and `ubean/ssr`.
|
|
67
|
+
|
|
68
|
+
## Quick Start
|
|
69
|
+
|
|
70
|
+
The supported entry point is the composed plugin from the aggregator:
|
|
71
|
+
|
|
72
|
+
```ts
|
|
73
|
+
// vite.config.ts
|
|
74
|
+
import { defineConfig } from 'vite-plus';
|
|
75
|
+
import { ubeanPlugin } from 'ubean/vite';
|
|
76
|
+
|
|
77
|
+
export default defineConfig({
|
|
78
|
+
plugins: [ubeanPlugin()]
|
|
79
|
+
});
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Composition only — no `await` needed. Vite awaits promises found in the `plugins` array before any
|
|
83
|
+
hook runs, so `plugins: [ubeanPlugin()]` is correct even though the factory is async.
|
|
84
|
+
|
|
85
|
+
Framework-agnostic or fine-grained use imports the subpaths directly:
|
|
86
|
+
|
|
87
|
+
```ts
|
|
88
|
+
import { ubeanServerActionsPlugin } from '@ubean/build/actions';
|
|
89
|
+
import { prerender } from '@ubean/build/prerender';
|
|
90
|
+
import { createStaticSsgRenderer } from '@ubean/build/static-render';
|
|
91
|
+
import { ubeanPlugin as ubeanCorePlugin } from '@ubean/build/vite'; // no Vue, no islands
|
|
92
|
+
import { ubeanVite } from '@ubean/build/vue'; // Vue pages + app entry + auto-imports
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## Vite Plugin
|
|
96
|
+
|
|
97
|
+
Entry point: `@ubean/build/vite`.
|
|
98
|
+
|
|
99
|
+
`ubeanPlugin()` is the framework-agnostic core plugin (`name: 'ubean:core'`, `enforce: 'pre'`). Its
|
|
100
|
+
`config` hook registers the `client` and `ubean` environments plus `builder.buildApp` — so **bare
|
|
101
|
+
`vite dev | build | preview` is the full toolchain**, and `ubean dev | build | preview` are thin
|
|
102
|
+
aliases over it.
|
|
103
|
+
|
|
104
|
+
```ts
|
|
105
|
+
import { defineConfig } from 'vite-plus';
|
|
106
|
+
import { ubeanPlugin } from '@ubean/build/vite';
|
|
107
|
+
|
|
108
|
+
export default defineConfig({
|
|
109
|
+
plugins: [ubeanPlugin()] // config is loaded from ubean.config.ts if not passed
|
|
110
|
+
});
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
```ts
|
|
114
|
+
// Explicit config (what `ubean dev` / `ubean build` do internally)
|
|
115
|
+
ubeanPlugin({ config: resolvedConfig, registry: createVirtualRegistry() });
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
| Hook | What it does |
|
|
119
|
+
| --------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
120
|
+
| `config` | Loads `ubean.config.ts` if needed, runs the project dependency check, registers `environments` + `builder.buildApp` |
|
|
121
|
+
| `buildStart` | Scans the project, primes the dev-scan baseline, applies the scan, runs codegen |
|
|
122
|
+
| `resolveId` / `load` | Serves the virtual modules (`ubean:routes`, `ubean:pages`, `ubean:meta`, `ubean:app-config`, `ubean:locales`) + `virtual:ubean-asset-manifest` |
|
|
123
|
+
| `transform` | Extracts / strips `definePage` macros |
|
|
124
|
+
| `configureServer` | Registers the dev-scan coordinator and generates OpenAPI types on `listening` |
|
|
125
|
+
| `renderChunk` / `resolveId` | Worker targets only: replaces `node:*` with throwing stubs, rewrites `import.meta.url` |
|
|
126
|
+
| `configurePreviewServer` | Attaches the preview middleware |
|
|
127
|
+
|
|
128
|
+
`UbeanPluginOptions`:
|
|
129
|
+
|
|
130
|
+
| Option | Type | Description |
|
|
131
|
+
| ---------- | ----------------------- | ---------------------------------------------------------------------------------------- |
|
|
132
|
+
| `config` | `UbeanResolvedConfig` | Pre-resolved config; otherwise read from the global cache or loaded in `buildStart` |
|
|
133
|
+
| `registry` | `VirtualModuleRegistry` | Virtual modules injected by the caller; the plugin creates its own instance when omitted |
|
|
134
|
+
|
|
135
|
+
**`experimental.viteBuilder` is gone.** The switch was deleted together with the two-`viteBuild`
|
|
136
|
+
legacy orchestration (ADR-0012). Writing it in `ubean.config.ts` is ignored as an unknown field —
|
|
137
|
+
there is no in-config escape hatch, only a version rollback.
|
|
138
|
+
|
|
139
|
+
**Worker targets.** When the active preset's `entryType` is `'worker'`, the plugin swaps `node:fs`
|
|
140
|
+
and friends for stubs that throw, and rewrites `import.meta.url` to `"file:///worker.mjs"` (workerd's
|
|
141
|
+
ESM leaves it `undefined`, which breaks bundler-generated `createRequire` interop shims at module
|
|
142
|
+
instantiation time). These only apply to the `ubean` environment.
|
|
143
|
+
|
|
144
|
+
## Vue Plugin
|
|
145
|
+
|
|
146
|
+
Entry point: `@ubean/build/vue` (also the subpath's default export).
|
|
147
|
+
|
|
148
|
+
`ubeanVite()` provides the Vue-specific half: the page/app/client-entry/server-entry virtual modules,
|
|
149
|
+
`unplugin-auto-import`, `unplugin-vue-components`, the Vue i18n plugin, and markdown pages.
|
|
150
|
+
|
|
151
|
+
```ts
|
|
152
|
+
import { ubeanVite } from '@ubean/build/vue';
|
|
153
|
+
|
|
154
|
+
ubeanVite({
|
|
155
|
+
config,
|
|
156
|
+
ssr: true, // optional
|
|
157
|
+
vue: true, // default: register @vitejs/plugin-vue
|
|
158
|
+
registry
|
|
159
|
+
});
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
| Option | Type | Description |
|
|
163
|
+
| ---------- | ----------------------- | -------------------------------------------------------------------------------------------------------- |
|
|
164
|
+
| `config` | `UbeanResolvedConfig` | Required — the resolved ubean config |
|
|
165
|
+
| `ssr` | `boolean` | Optional SSR flag |
|
|
166
|
+
| `registry` | `VirtualModuleRegistry` | Same instance as `ubeanPlugin()`; without it `createVirtualRegistry()` usage is per-plugin |
|
|
167
|
+
| `vue` | `boolean` | Register `@vitejs/plugin-vue` (default **true**). Pass `false` when you bring your own Vue plugin config |
|
|
168
|
+
|
|
169
|
+
`ubeanVite` owns the `@vitejs/plugin-vue` registration because it is the only entry point a plain
|
|
170
|
+
`vite dev` goes through — if the CLI registered it instead, the same plugin would compile `.vue`
|
|
171
|
+
files twice. `VUE_PLUGIN_INCLUDE` is exported alongside it (`[/\.vue$/, /\.md$/]`).
|
|
172
|
+
|
|
173
|
+
Module factory helpers, for tooling that builds its own plugin array:
|
|
174
|
+
|
|
175
|
+
```ts
|
|
176
|
+
import {
|
|
177
|
+
createVuePagesVirtualModule,
|
|
178
|
+
createVueAppEntryVirtualModule,
|
|
179
|
+
createClientEntryVirtualModule,
|
|
180
|
+
createServerEntryVirtualModule
|
|
181
|
+
} from '@ubean/build/vue';
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
## Virtual Modules
|
|
185
|
+
|
|
186
|
+
Entry point: `@ubean/build` (main).
|
|
187
|
+
|
|
188
|
+
Virtual modules are plain objects behind a registry, not globals. Framework paths build one with
|
|
189
|
+
`createVirtualRegistry()` and **inject** it into the plugins — a module-level singleton would leak
|
|
190
|
+
state across the environments of a single builder (ADR-0012 §6).
|
|
191
|
+
|
|
192
|
+
```ts
|
|
193
|
+
import { createVirtualRegistry, defineVirtualModule, defineVirtualModulePrefix } from '@ubean/build';
|
|
194
|
+
|
|
195
|
+
const registry = createVirtualRegistry([
|
|
196
|
+
defineVirtualModule('virtual:ubean-app-config', () => 'export const config = { mode: "fullstack" };'),
|
|
197
|
+
defineVirtualModulePrefix('virtual:ubean-page:', id => `export default {}; // ${id}`)
|
|
198
|
+
]);
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
| Export | Description |
|
|
202
|
+
| ------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------- |
|
|
203
|
+
| `VirtualModuleRegistry` | Registry class: `register` / `resolveId` / `load` / `getModules` / `invalidate` |
|
|
204
|
+
| `createVirtualRegistry(modules?)` | Build an independent instance (the recommended form) |
|
|
205
|
+
| `defineVirtualModule(id, loader)` | Exact-id module |
|
|
206
|
+
| `defineVirtualModulePrefix(prefix, loader)` | Prefix-matched module family |
|
|
207
|
+
| `useVirtualRegistry()` / `resetVirtualRegistry()` | Deprecated module-level singleton — kept for existing tests only |
|
|
208
|
+
| `VirtualModule`, `VirtualModuleResolver`, `VirtualModuleContext`, `VirtualModuleTransform` | Types |
|
|
209
|
+
|
|
210
|
+
Framework-provided modules:
|
|
211
|
+
|
|
212
|
+
| Factory | Virtual module |
|
|
213
|
+
| ---------------------------------------------------------------------------- | ---------------------------- |
|
|
214
|
+
| `createRoutingVirtualModule(routes, middlewares, cwd)` | API routes + middleware |
|
|
215
|
+
| `createPagesVirtualModule(pages, layouts, cwd)` | Page / layout records |
|
|
216
|
+
| `createMetaVirtualModule()` | Route metadata |
|
|
217
|
+
| `createAppVirtualModule(apiRoutes, middlewares, pages, srcDir)` | App config |
|
|
218
|
+
| `createLocalesVirtualModule(locales, defaultLocale, srcDir, i18nConfigJson)` | Locale data + `loadLocale()` |
|
|
219
|
+
|
|
220
|
+
## Macros
|
|
221
|
+
|
|
222
|
+
Entry point: `@ubean/build` (main).
|
|
223
|
+
|
|
224
|
+
`definePage` is a compile-time macro. `transformMacros()` extracts and removes the call inside
|
|
225
|
+
`src/pages/`, `src/routes/`, and `src/middleware/`; `stripMacros()` is the plain stripper.
|
|
226
|
+
|
|
227
|
+
```ts
|
|
228
|
+
import { transformMacros, stripMacros } from '@ubean/build';
|
|
229
|
+
|
|
230
|
+
transformMacros('definePage({ cache: true })', '/app/src/pages/index.vue'); // → ''
|
|
231
|
+
stripMacros('definePage({ name: "Home" }); const a = 1;'); // → '; const a = 1;'
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
`MACRO_NAMES` contains **only** `definePage`. Adding `defineHandlerMeta` / `defineMiddleware` there
|
|
235
|
+
would strip runtime function calls and produce syntax errors — those are real runtime functions.
|
|
236
|
+
|
|
237
|
+
Vue SFCs keep an empty `<script setup>` block (as a comment) when stripping would leave it blank: a
|
|
238
|
+
page whose `definePage` was its only setup content, but which also has a plain `<script>` (the
|
|
239
|
+
documented shape for `loader` + `definePage`), would otherwise lose its default export and fail with
|
|
240
|
+
`MISSING_EXPORT "default"`.
|
|
241
|
+
|
|
242
|
+
## Dev Server Bootstrap
|
|
243
|
+
|
|
244
|
+
Entry point: `@ubean/build/vite`.
|
|
245
|
+
|
|
246
|
+
These exports own dev request routing and the host app, so `vite dev` works without the CLI.
|
|
247
|
+
|
|
248
|
+
```ts
|
|
249
|
+
import { bootstrapDevApp, ubeanDevRequestPlugin, ensureDevApp } from '@ubean/build/vite';
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
| Export | Description |
|
|
253
|
+
| ------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------- |
|
|
254
|
+
| `bootstrapDevApp(options)` | Create the Hono dev app from a scan, wire it to the Vite SSR graph, prepare `ready`, expose `rebuild` |
|
|
255
|
+
| `createDevApp(options)` | Scan (optional) + create the host app — no Vite server needed |
|
|
256
|
+
| `createDevAppReady(options)` | Idempotent first-request initialisation: locales, `defineServer`, `app.init()`, `onServerReady` |
|
|
257
|
+
| `ubeanDevRequestPlugin(options)` | The request-routing plugin: pre-classification + post fallback |
|
|
258
|
+
| `createUbeanRequestHandlers(server, options)` | The handlers used by the plugin |
|
|
259
|
+
| `getDevApp(server)` / `ensureDevApp(server)` | Read / lazily bootstrap the dev app associated with a Vite server |
|
|
260
|
+
| `enhanceDevApp(options)` | Attach the SSR-graph loaders and renderer to an existing Hono app |
|
|
261
|
+
| `isFrameworkHtmlPage(url)` | Framework-owned HTML pages (`/_devtools`, `/_scalar`, all `/__devframes/**`) |
|
|
262
|
+
| `isViteResourceRequest(url, headers)` | Whether the request belongs to Vite |
|
|
263
|
+
| `collectDevCssLinks(moduleGraph)` / `injectStylesheetLinks(html, hrefs)` | CSS link injection |
|
|
264
|
+
| `toWebRequest(req, host, protocol)` / `sendWebResponse(res, webRes)` | Node ↔ Web adapters |
|
|
265
|
+
|
|
266
|
+
Dev-scan coordination (one watcher, one scan, one reload order):
|
|
267
|
+
|
|
268
|
+
| Export | Description |
|
|
269
|
+
| --------------------------------------------------------------------------- | ------------------------------------------------------------------------------ |
|
|
270
|
+
| `DEV_SCAN_DIRS` | `routes`, `middleware`, `pages`, `layouts`, `plugins`, `app`, `api`, `locales` |
|
|
271
|
+
| `createDevScanCoordinator(options)` / `getDevScanCoordinator(server, init)` | Coordinator lifecycle |
|
|
272
|
+
| `onDevScan(server, handler)` | Subscribe to scan results |
|
|
273
|
+
| `peekDevScanCoordinator(server)` | Non-initialising read |
|
|
274
|
+
|
|
275
|
+
`DEVTOOLS_PASS_THROUGH_PREFIXES` (`['/__devframes', '/__devtools', '/_devtools']`) is the single
|
|
276
|
+
declaration of the reserved dev namespace — matched by **bare string prefix**, not by path segment,
|
|
277
|
+
because segment matching misses sibling paths.
|
|
278
|
+
|
|
279
|
+
Dev security headers are mode-aware:
|
|
280
|
+
|
|
281
|
+
```ts
|
|
282
|
+
import { resolveDevSecurityHeaders } from '@ubean/build/vite';
|
|
283
|
+
|
|
284
|
+
// `ssg` mode defaults to OFF: static output ships no framework-injected headers.
|
|
285
|
+
resolveDevSecurityHeaders(config); // → boolean | Record<string, unknown>
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
The worker-based dev environment (`UbeanDevEnvironment`, `createUbeanDevEnvironmentFactory`,
|
|
289
|
+
`createEnvRunnerTransport`, `bridgeEnvRunnerInvokes`, `UBEAN_DEV_ENV_NAME`) and its generated entry
|
|
290
|
+
(`DEV_WORKER_FILE`, `getDevWorkerEntrySource`, `writeDevWorkerEntry`, `invalidateDevWorkerModules`)
|
|
291
|
+
are exported from the same subpath. They are a runnable capability, **not** the wired-up dev path:
|
|
292
|
+
`createEnvironment` currently has no registration point, and SSR executes in the main process.
|
|
293
|
+
|
|
294
|
+
## Preview
|
|
295
|
+
|
|
296
|
+
Entry point: `@ubean/build/vite`.
|
|
297
|
+
|
|
298
|
+
`ubeanPreviewPlugin()` / `attachPreviewMiddleware()` make `vite preview` serve **the artifact itself**:
|
|
299
|
+
fullstack / backend load `createFetchHandler()` from the built `dist/server/entry.mjs` in-process; spa /
|
|
300
|
+
ssg are served by a static resolver shared with the CLI's built-in server.
|
|
301
|
+
|
|
302
|
+
```ts
|
|
303
|
+
import { ubeanPreviewPlugin, resolvePreviewFile, previewMimeType } from '@ubean/build/vite';
|
|
304
|
+
|
|
305
|
+
ubeanPreviewPlugin();
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
| Export | Description |
|
|
309
|
+
| --------------------------------------------------------------------- | --------------------------------------- |
|
|
310
|
+
| `ubeanPreviewPlugin(options?)` | The preview plugin |
|
|
311
|
+
| `attachPreviewMiddleware(server, config, host?)` | Attach to an existing preview server |
|
|
312
|
+
| `createPreviewMiddleware(options)` | The connect middleware |
|
|
313
|
+
| `resolvePreviewFile(root, pathname, mode)` | Static resolution for `'spa'` / `'ssg'` |
|
|
314
|
+
| `sendPreviewFile(res, path)` / `sendPreviewStatus(res, status, text)` | Response helpers |
|
|
315
|
+
| `previewMimeType(filePath)` | MIME lookup |
|
|
316
|
+
|
|
317
|
+
Cloudflare artifacts preview through miniflare, which is an optional dependency:
|
|
318
|
+
|
|
319
|
+
```ts
|
|
320
|
+
import { createCloudflarePreviewRunner } from '@ubean/build/vite';
|
|
321
|
+
|
|
322
|
+
const result = await createCloudflarePreviewRunner({ cwd, outDir });
|
|
323
|
+
// → { ok: false, reason: 'miniflare-missing', hint: MINIFLARE_INSTALL_HINT }
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
`MINIFLARE_INSTALL_HINT` names the alternative: _Install it with `pnpm add -D miniflare`, or preview
|
|
327
|
+
with `wrangler dev` instead._
|
|
328
|
+
|
|
329
|
+
## Server Actions
|
|
330
|
+
|
|
331
|
+
Entry point: `@ubean/build/actions`.
|
|
332
|
+
|
|
333
|
+
The plugin detects `defineAction()` call expressions and splits them per environment: **server**
|
|
334
|
+
injects `filePath` (and the inferred `name`) so the action ID is stable across dev and build;
|
|
335
|
+
**client** replaces the call with `createActionStub('<id>')`, an RPC stub that POSTs to `/__actions`.
|
|
336
|
+
|
|
337
|
+
It is already part of the composed `ubeanPlugin()` from `ubean/vite` — you only add it manually when
|
|
338
|
+
composing your own plugin array.
|
|
339
|
+
|
|
340
|
+
```ts
|
|
341
|
+
import { ubeanServerActionsPlugin } from '@ubean/build/actions';
|
|
342
|
+
|
|
343
|
+
plugin: ubeanServerActionsPlugin({ root: process.cwd() });
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
| Export | Description |
|
|
347
|
+
| ----------------------------------------------------------- | -------------------------------------------------------------------- |
|
|
348
|
+
| `findDefineActionCalls(code)` / `hasDefineActionCall(code)` | Detection |
|
|
349
|
+
| `transformActionsForServer(code, filePath, root)` | Inject `filePath` / `name` |
|
|
350
|
+
| `transformActionsForClient(code, filePath, root)` | Replace with `createActionStub(...)` |
|
|
351
|
+
| `ServerActionsPluginOptions` | `{ root?: string }` — defaults to `process.cwd()` at plugin creation |
|
|
352
|
+
|
|
353
|
+
Name inference order: the `name` property in the options object → the variable/property the result is
|
|
354
|
+
assigned to (`export const login = defineAction(...)` → `'login'`) → `'anonymous'`.
|
|
355
|
+
|
|
356
|
+
## Production Build
|
|
357
|
+
|
|
358
|
+
Entry point: `@ubean/build/production`.
|
|
359
|
+
|
|
360
|
+
```ts
|
|
361
|
+
import { buildWithEnvironments, getPresetBuildConfig, generateNodeServerEntry } from '@ubean/build/production';
|
|
362
|
+
|
|
363
|
+
const manifest = await buildWithEnvironments({
|
|
364
|
+
cwd,
|
|
365
|
+
config,
|
|
366
|
+
preset,
|
|
367
|
+
scanResult,
|
|
368
|
+
minify: true,
|
|
369
|
+
sourcemap: false,
|
|
370
|
+
contentSnapshot
|
|
371
|
+
});
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
| Export | Description |
|
|
375
|
+
| ------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------- |
|
|
376
|
+
| `buildWithEnvironments(options)` | One `createBuilder` for the whole build; returns a `BuildManifest` |
|
|
377
|
+
| `BuildOptions` | `cwd`, `config`, `preset`, `scanResult`, `minify?`, `sourcemap?`, `contentSnapshot?` |
|
|
378
|
+
| `BuildManifest` | `assets`, `entry`, `clientDir`, `serverDir`, `preset` |
|
|
379
|
+
| `getPresetBuildConfig(preset)` | `{ format, target, entryType, external }` — `entryType` is `'node'` / `'worker'` / `'fetch'` |
|
|
380
|
+
| `generateNodeServerEntry()` / `generateStandardHandlerEntry()` / `generateCloudflareWorkerEntry()` | Per-entry-type server bundles |
|
|
381
|
+
| `generateVirtualModulesToDisk(cwd, config, scanResult, virtualDir, preset, registry, contentSnapshot?)` | Build-time snapshot of the virtual modules |
|
|
382
|
+
| `serializePagesForEntry(pages)` | Serialize scanned pages into the server entry |
|
|
383
|
+
| `createIslandsSsrStubPlugin()` | Non-functional islands registry for SSR |
|
|
384
|
+
|
|
385
|
+
**`serializePagesForEntry()` is a whitelist, and the whitelist is load-bearing.** It previously
|
|
386
|
+
serialized only `relativePath` / `name` / `route` / `layout` / `reuseTarget` / `isReuse` / `pageMeta`,
|
|
387
|
+
so `matchers` and `slot` **disappeared from production builds** — dev used the live scanned objects
|
|
388
|
+
and behaved correctly, while a `[id=numeric]` route returned 404 in dev and 200 in production. Add a
|
|
389
|
+
field to `ScannedPage` and you must add it here too; `packages/builder/test/page-metadata.test.ts`
|
|
390
|
+
locks the list field by field.
|
|
391
|
+
|
|
392
|
+
`buildWithEnvironments()` declares "the build is caller-driven" (`UBEAN_BUILD_DRIVEN_BY_CLI=1`) for
|
|
393
|
+
its duration so the plugin's `config` hook does **not** also register its own `buildApp` — otherwise
|
|
394
|
+
the plugin's return value would overwrite the inline `builder` field and the build would silently
|
|
395
|
+
produce nothing.
|
|
396
|
+
|
|
397
|
+
## Prerender
|
|
398
|
+
|
|
399
|
+
Entry point: `@ubean/build/prerender`.
|
|
400
|
+
|
|
401
|
+
`prerender()` drives a `fetcher` over a collected route list, writes HTML files, and can externalize
|
|
402
|
+
the page payload as `__data.json`.
|
|
403
|
+
|
|
404
|
+
```ts
|
|
405
|
+
import { prerender, collectPrerenderRoutes, routeToFilePath } from '@ubean/build/prerender';
|
|
406
|
+
|
|
407
|
+
const result = await prerender({
|
|
408
|
+
cwd,
|
|
409
|
+
outputDir: 'dist',
|
|
410
|
+
pages,
|
|
411
|
+
prerender: { all: true },
|
|
412
|
+
fetcher: async url => ({ html: await render(url), statusCode: 200 }),
|
|
413
|
+
expandRoutes: routes => routes, // i18n expansion hook
|
|
414
|
+
notFoundRoute: true // static SSG: enqueue the 404 sentinel → 404.html
|
|
415
|
+
});
|
|
416
|
+
```
|
|
417
|
+
|
|
418
|
+
| Export | Description |
|
|
419
|
+
| -------------------------------------------------------- | ------------------------------------------------------------------------------- |
|
|
420
|
+
| `prerender(options)` | Full run → `PrerenderResult` |
|
|
421
|
+
| `collectPrerenderRoutes(pages, options)` | `{ routes, skipped }`; merges `include` + route-rule discovery + content routes |
|
|
422
|
+
| `extractPrerenderRoutesFromRules(routeRules)` | Routes marked `prerender: true` or `ppr: true` |
|
|
423
|
+
| `extractLinks(html, baseUrl?)` | Crawl support |
|
|
424
|
+
| `routeToFilePath(route, outputDir)` | `/` → `index.html`, `404` sentinel → `404.html`, `/robots.txt` preserved |
|
|
425
|
+
| `routeToDataFilePath(route, outputDir)` | `__data.json` path per route |
|
|
426
|
+
| `extractDataPayload(html, route)` | Pull `__UBEAN_DATA__` out of HTML → `ExtractedPayload` |
|
|
427
|
+
| `writePrerenderedFile(filePath, html)` | Write helper |
|
|
428
|
+
| `resolvePrerenderStaticDir(cwd, buildOutputDir, config)` | Resolves `<outputDir>/public` — no longer a hardcoded `dist/public` |
|
|
429
|
+
| `generatePrerenderManifest(result, baseUrl?)` | Manifest for deployment tooling |
|
|
430
|
+
|
|
431
|
+
`PrerendererOptions.fetcher` is the isomorphism point: the fullstack path supplies `createSsrFetcher`
|
|
432
|
+
(imports the real server entry and drives the Hono pipeline per route), the SSG path supplies the
|
|
433
|
+
direct renderer from `./static-render`.
|
|
434
|
+
|
|
435
|
+
## Static Render Path
|
|
436
|
+
|
|
437
|
+
Entry point: `@ubean/build/static-render`.
|
|
438
|
+
|
|
439
|
+
`mode: 'ssg'` renders through a **minimal static entry** instead of the request pipeline (ADR-0011).
|
|
440
|
+
`createStaticSsgRenderer()` loads the built static entry and returns the same fetcher contract, so
|
|
441
|
+
`prerender()` needs no changes.
|
|
442
|
+
|
|
443
|
+
```ts
|
|
444
|
+
import { createStaticSsgRenderer, matchRoutePattern, expandRoutesForLocales } from '@ubean/build/static-render';
|
|
445
|
+
|
|
446
|
+
const renderer = await createStaticSsgRenderer(cwd, manifest, {
|
|
447
|
+
pages,
|
|
448
|
+
notFoundPage,
|
|
449
|
+
i18n: { enabled: true, strategy: 'prefix_except_default', defaultLocale: 'en', locales: ['en', 'zh'] }
|
|
450
|
+
});
|
|
451
|
+
|
|
452
|
+
const result = await prerender({
|
|
453
|
+
...options,
|
|
454
|
+
expandRoutes: renderer.expandRoutes,
|
|
455
|
+
fetcher: renderer.fetcher
|
|
456
|
+
});
|
|
457
|
+
```
|
|
458
|
+
|
|
459
|
+
| Export | Description |
|
|
460
|
+
| ---------------------------------------------- | ---------------------------------------------------------------------------------------------------- |
|
|
461
|
+
| `createStaticSsgRenderer(cwd, manifest, init)` | Loads `dist/server/entry.mjs`; returns `StaticSsgRenderer` or `undefined` on failure |
|
|
462
|
+
| `StaticSsgRenderer` | `{ fetcher, expandRoutes, renderStaticPage }` |
|
|
463
|
+
| `StaticSsgRenderer.renderStaticPage` | The function generated into the built static entry (built artifact, not an import from this subpath) |
|
|
464
|
+
| `compilePageRoute(page)` | Compile a scanned page into a matchable route |
|
|
465
|
+
| `matchRoutePattern(pages, path)` | Match with specificity ordering; returns `StaticRouteMatch \| null` |
|
|
466
|
+
| `expandRoutesForLocales(routes, i18n)` | Per-strategy URL expansion |
|
|
467
|
+
| `STATIC_NOT_FOUND_ROUTE` | `'/__ubean_404__'` — the 404 sentinel route |
|
|
468
|
+
|
|
469
|
+
**What the direct path does not have** (ADR-0011): no Hono app, no API routes, no middleware, no
|
|
470
|
+
crons, no IPX, no preset wrapper files. Page `loader` is **not executed** — exporting one logs a
|
|
471
|
+
one-time warning, and data must come from content collections, module constants, or client hydration.
|
|
472
|
+
Server actions, ISR / PPR / streaming, and route-rule redirect/rewrite have no execution point in
|
|
473
|
+
static output. The fullstack `prerender` path is the opposite: it drives the real server entry per
|
|
474
|
+
route, so loaders and middleware run.
|
|
475
|
+
|
|
476
|
+
`matchRoutePattern` understands both dialects: the scanner's (`:param`, `**:slug`, `:id?`) and
|
|
477
|
+
vue-router syntax coming from `definePage({ path })` (`:param(regex)`, `:param*`, `:param+`).
|
|
478
|
+
Parameters are `decodeURIComponent`-ed to match `c.req.param()`.
|
|
479
|
+
|
|
480
|
+
## Codegen
|
|
481
|
+
|
|
482
|
+
Entry point: `@ubean/build/codegen`.
|
|
483
|
+
|
|
484
|
+
```ts
|
|
485
|
+
import { generateTypes, CODEGEN_CONTRACT_VERSION, CODEGEN_FILES } from '@ubean/build/codegen';
|
|
486
|
+
|
|
487
|
+
const result = await generateTypes(scanResult, {
|
|
488
|
+
cwd,
|
|
489
|
+
srcDir: 'src',
|
|
490
|
+
buildDir: '.ubean',
|
|
491
|
+
autoImports: config.autoImports,
|
|
492
|
+
components: config.components
|
|
493
|
+
});
|
|
494
|
+
```
|
|
495
|
+
|
|
496
|
+
| Export | Description |
|
|
497
|
+
| ---------------------------------------------------------------------------------------- | -------------------------------------------------------------------------- |
|
|
498
|
+
| `generateTypes(result, options)` | Writes all declarations, returns `CodegenResult` + `codegen.manifest.json` |
|
|
499
|
+
| `generateRouteTypes(result, options)` | `routes.d.ts` (`ubean:routes`: `ApiRouteMap`, `ApiRoutePath`, `ApiMethod`) |
|
|
500
|
+
| `generatePageTypes(result, options)` | `pages.d.ts` (`ubean:pages`: `RouteName`, `LayoutName`) |
|
|
501
|
+
| `generateI18nTypes(result, options)` / `messagesToTsType(value, indent?)` | `i18n.d.ts` (`vue-i18n` `DefineLocaleMessage`) |
|
|
502
|
+
| `generateOpenApiTypes(schema, options)` | `openapi.d.ts` from an OpenAPI schema |
|
|
503
|
+
| `generateOpenApiTypesFromServer(baseUrl, options)` | Same, fetching `/_openapi.json` from a base URL |
|
|
504
|
+
| `generateAutoImports(scanResult, options)` | Auto-import declarations from the scan |
|
|
505
|
+
| `CODEGEN_CONTRACT_VERSION` / `CODEGEN_FILES` / `CodegenManifest` / `CodegenFileContract` | The emitted-file contract (versioned for studio / IDE consumers) |
|
|
506
|
+
|
|
507
|
+
Auto-import preset resolution — shared by the runtime plugin and codegen:
|
|
508
|
+
|
|
509
|
+
```ts
|
|
510
|
+
import { resolveAutoImportsConfig, resolveComponentsConfig, getAutoImportPresets } from '@ubean/build/codegen';
|
|
511
|
+
|
|
512
|
+
const resolved = resolveAutoImportsConfig(config.autoImports);
|
|
513
|
+
const presets = getAutoImportPresets(resolved);
|
|
514
|
+
```
|
|
515
|
+
|
|
516
|
+
`ResolvedAutoImports` decomposes `boolean | AutoImportOptions` into per-library switches — `enabled`,
|
|
517
|
+
`ubean` (default `true`), `vue`, `vueRouter`, `vueI18n`, `honoOpenapi` (the rest default `false`) — plus
|
|
518
|
+
the raw `options` for pass-through. The preset constants are exported too: `VUE_PRESET`,
|
|
519
|
+
`VUE_MACROS_PRESET`, `UBEAN_CLIENT_PRESET`, `VUE_ROUTER_PRESET`, `VUE_I18N_PRESET`,
|
|
520
|
+
`UBEAN_SERVER_PRESET`, `HONO_OPENAPI_PRESET`, `BUILTIN_PRESETS`, alongside `toArray`,
|
|
521
|
+
`getBuiltinComposables`, `getUbeanAutoImportConfig`, `getUbeanComponentsConfig`, and
|
|
522
|
+
`generateImportsTransform`. The paired-component halves (`**/*.server.vue`, `**/*.client.vue`) are
|
|
523
|
+
detected during the component scan, so they are declared from the same codegen run.
|
|
524
|
+
|
|
525
|
+
**Where codegen runs is part of the contract.** Previously only the CLI ran it, so bare
|
|
526
|
+
`vite dev` / `vite build` produced no `.ubean/*.d.ts` at all and user type-checks failed. The plugin
|
|
527
|
+
now runs `generateTypes()` in `buildStart` and yields when `UBEAN_CODEGEN_BY_CLI=1` or
|
|
528
|
+
`UBEAN_BUILD_DRIVEN_BY_CLI=1` is set, so exactly one executor writes each artifact.
|
|
529
|
+
|
|
530
|
+
## Type Safety
|
|
531
|
+
|
|
532
|
+
- Every subpath ships `.d.ts` files (`types` is declared per export condition in `package.json`).
|
|
533
|
+
- `CodegenResult` returns the path of every artifact it wrote, so callers never guess at filenames.
|
|
534
|
+
- `CodegenManifest` + `CODEGEN_CONTRACT_VERSION` are the machine-readable contract for external
|
|
535
|
+
consumers (studio, IDE plugins) — `codegen.manifest.json` records, per file, its virtual module,
|
|
536
|
+
whether it is required, and whether it was actually generated this run.
|
|
537
|
+
- `PresetBuildConfig` is `ReturnType<typeof getPresetBuildConfig>`, so the server-environment
|
|
538
|
+
external lists stay in sync with the preset implementation.
|
|
539
|
+
- `VirtualModuleRegistry` implements `VirtualModuleResolver`, the read-only view the plugins consume —
|
|
540
|
+
plugin code cannot mutate the registry it was handed.
|
|
541
|
+
- Config typing flows through `@ubean/config`'s `ResolvedConfig`; every option in this package takes
|
|
542
|
+
that type rather than redeclaring config shapes.
|
|
543
|
+
|
|
544
|
+
## Architecture Notes
|
|
545
|
+
|
|
546
|
+
- **Lifecycle ownership (ADR-0012).** The `config` hook registers `client` + `ubean` environments and
|
|
547
|
+
`builder.buildApp`; `vite dev | build | preview` are therefore the complete toolchain. The legacy
|
|
548
|
+
two-`viteBuild` orchestration and the `experimental.viteBuilder` switch **were deleted** — writing
|
|
549
|
+
the field now is silently ignored. Rollback is a version rollback, deliberately: a switch that can
|
|
550
|
+
change semantics silently costs more than a version rollback.
|
|
551
|
+
- **Three phases, two drivers.** `prepareBuild` → `runEnvBuilds` are shared; `buildWithEnvironments()`
|
|
552
|
+
owns its builder, while the plugin path lets Vite create the builder and only calls the two phases.
|
|
553
|
+
Calling `buildWithEnvironments()` from inside `buildApp` would recurse.
|
|
554
|
+
- **No `ssr` environment.** Deliberately not aligned with Nitro: ubean is Vue-only and the server
|
|
555
|
+
entry already fuses the Hono app with the Vue renderer, so a third environment would buy a
|
|
556
|
+
cross-environment singleton proxy and nothing else (ADR-0012 §2).
|
|
557
|
+
- **Output layout is `dist/{public,server}` + `dist/manifest.json`.** `.output/` is deliberately not
|
|
558
|
+
adopted — the layout is promised by the docs, by preview takeover, and by `analyze`'s baseline
|
|
559
|
+
reading (ADR-0012 §5).
|
|
560
|
+
- **Prerender stays after the server bundle.** Both render paths consume the built artifact, so
|
|
561
|
+
prerender cannot move before packing (a second deliberate deviation from Nitro, ADR-0012 §4).
|
|
562
|
+
- **Asset tags are inlined at build time**, not read from `<public>/.vite/manifest.json` at runtime.
|
|
563
|
+
- **Never import this package from runtime route code.** It pulls the whole build toolchain into the
|
|
564
|
+
server bundle: wasted size on Node, and a hard build failure on workers because the toolchain's
|
|
565
|
+
optional dependencies (`velocityjs`, `atpl`, …) cannot be bundled. Static assets and caching go
|
|
566
|
+
through the platform layer (ADR-0013).
|
|
567
|
+
- **`env-runner` must not become a dependency of this package** — it stays in `@ubean/cli`. Adding it
|
|
568
|
+
here triggers pnpm peer-variant drift, which shows up as `Plugin` type identity mismatches. The dev
|
|
569
|
+
environment code uses a local structural interface (`EnvRunnerLike`) instead.
|
|
570
|
+
- **Cross-instance shared state goes on `globalThis`, not in a module-level `Map`.** The dev SSR graph
|
|
571
|
+
inlines `ubean` while externalising `@ubean/vue`, so two module instances coexist and read different
|
|
572
|
+
registries with no error. The component/CSS resolver registry follows this rule.
|