@ubean/vue 0.4.5 → 0.4.7

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 CHANGED
@@ -15,7 +15,7 @@ import { routes } from 'virtual:ubean-vue-routes'; // generated by @ubean/vue/vi
15
15
  const router = createRouter({ history: createWebHistory(), routes });
16
16
  const app = createApp(App);
17
17
  app.use(router);
18
- app.use(ubeanVue, { routes }); // registers Link/PageView/SlotView + seeds page cache
18
+ app.use(ubeanVue, { routes }); // registers Link/PageView/SlotView/ClientOnly + seeds page cache
19
19
  app.mount('#app');
20
20
  ```
21
21
 
@@ -61,7 +61,7 @@ app.mount('#app');
61
61
  **Included:**
62
62
 
63
63
  - `ubeanVue` Vue plugin (the only wiring entry — plugin-first, no factory)
64
- - `<PageView>` / `<Link>` / `<SlotView>` / `<LayoutChainRenderer>` / `<ErrorBoundary>`
64
+ - `<PageView>` / `<Link>` / `<SlotView>` / `<LayoutChainRenderer>` / `<ErrorBoundary>` / `<ClientOnly>`
65
65
  - Page cache (keep-alive) store with imperative + declarative APIs
66
66
  - Page transitions & reload signal (per-page remount with fresh cache)
67
67
  - View Transitions helpers (feature detection + wrapper)
@@ -345,7 +345,7 @@ The `RouteFileGenerator` class is also exported for incremental use (`new RouteF
345
345
  app.use(ubeanVue, { routes });
346
346
  ```
347
347
 
348
- Registers `Link` / `PageView` / `SlotView` as global components and seeds the keep-alive include list from `meta.cache: true` declarations (`initCachedViewsFromRoutes`). This is the **only** wiring entry of the package (main subpath — distinct from the same-named Vite plugin in the `/vite` subpath).
348
+ Registers `Link` / `PageView` / `SlotView` / `ClientOnly` as global components and seeds the keep-alive include list from `meta.cache: true` declarations (`initCachedViewsFromRoutes`). This is the **only** wiring entry of the package (main subpath — distinct from the same-named Vite plugin in the `/vite` subpath).
349
349
 
350
350
  ## Components
351
351
 
@@ -379,6 +379,43 @@ Recursively renders a chain of nested layout components (framework factories bui
379
379
 
380
380
  Catches render/async/setup errors from descendants and renders the configured error component; resets on route change.
381
381
 
382
+ ### `<ClientOnly>`
383
+
384
+ Renders its content ONLY on the client, after hydration. Useful for browser-only markup or template fragments (the islands `v-client.*` transform only applies to capitalized component tags).
385
+
386
+ ```vue
387
+ <template>
388
+ <!-- string fallback prop -->
389
+ <ClientOnly fallback="loading…">
390
+ <BrowserChart />
391
+ </ClientOnly>
392
+
393
+ <!-- fallback slot (reserve space to avoid CLS) -->
394
+ <ClientOnly>
395
+ <template #fallback>
396
+ <div class="skeleton" />
397
+ </template>
398
+ <BrowserMap />
399
+ </ClientOnly>
400
+
401
+ <!-- plain fragments work too -->
402
+ <ClientOnly>Window width: {{ width }}px</ClientOnly>
403
+ </template>
404
+ ```
405
+
406
+ **Hydration safety:** the placeholder (fallback / nothing) is rendered during SSR AND during the client's hydration render; the real content is patched in right after mount. There is no `typeof window` branching, so no hydration mismatch warnings.
407
+
408
+ **When to use what:**
409
+
410
+ | Scenario | Use |
411
+ | -------------------------------------------------------------------- | ----------------------------------------------------------- |
412
+ | Template fragments / plain HTML, same-tree rendering, fallback slots | `<ClientOnly>` |
413
+ | Component-level deferred hydration strategies (idle/visible/media) | islands `v-client.*` |
414
+ | Programmatic islands / client-only wrappers | `defineIsland` / `defineClientComponent` (`@ubean/islands`) |
415
+ | File-level server/client split | `.server.vue` / `.client.vue` |
416
+
417
+ > Notes: default-slot content is absent from the SSR HTML — don't use `<ClientOnly>` for SEO-critical content. `<ClientOnly>` inside an island placeholder's children is wiped when the island hydrates (islands hydrate via a separate app).
418
+
382
419
  ## Composables
383
420
 
384
421
  - **`usePage<T>()`** — lean page-data access: returns the `PAGE_KEY` injected data (`props` / `component` / `errors`) as-is, or a shared empty object when nothing was provided. Route state (`url` / `params` / `query` / `meta`) is NOT included — use vue-router's `useRoute()` directly. The framework runtime (`@ubean/client`) layers its own route-aware `usePage` on top for the full PageObject protocol.
package/README.zh-CN.md CHANGED
@@ -15,7 +15,7 @@ import { routes } from 'virtual:ubean-vue-routes'; // 由 @ubean/vue/vite 生成
15
15
  const router = createRouter({ history: createWebHistory(), routes });
16
16
  const app = createApp(App);
17
17
  app.use(router);
18
- app.use(ubeanVue, { routes }); // 注册 Link/PageView/SlotView + 播种页面缓存
18
+ app.use(ubeanVue, { routes }); // 注册 Link/PageView/SlotView/ClientOnly + 播种页面缓存
19
19
  app.mount('#app');
20
20
  ```
21
21
 
@@ -45,6 +45,7 @@ app.mount('#app');
45
45
  - [`<SlotView>`](#slotview)
46
46
  - [`<LayoutChainRenderer>`](#layoutchainrenderer)
47
47
  - [`<ErrorBoundary>`](#errorboundary)
48
+ - [`<ClientOnly>`](#clientonly)
48
49
  - [组合式函数](#组合式函数)
49
50
  - [页面缓存(keep-alive)](#页面缓存keep-alive)
50
51
  - [页面过渡与重载](#页面过渡与重载)
@@ -68,7 +69,7 @@ app.mount('#app');
68
69
  **包含:**
69
70
 
70
71
  - `ubeanVue` Vue 插件(唯一接线入口 —— 插件优先,无工厂)
71
- - `<PageView>` / `<Link>` / `<SlotView>` / `<LayoutChainRenderer>` / `<ErrorBoundary>`
72
+ - `<PageView>` / `<Link>` / `<SlotView>` / `<LayoutChainRenderer>` / `<ErrorBoundary>` / `<ClientOnly>`
72
73
  - 页面缓存(keep-alive)store:命令式 + 声明式 API
73
74
  - 页面过渡与重载信号(按页重载 + 缓存新鲜重建)
74
75
  - View Transitions 工具(特性检测 + 包装器)
@@ -352,7 +353,7 @@ const result = await generateRouteFiles(
352
353
  app.use(ubeanVue, { routes });
353
354
  ```
354
355
 
355
- 注册 `Link` / `PageView` / `SlotView` 全局组件,并从 `meta.cache: true` 声明播种 keep-alive include 列表(`initCachedViewsFromRoutes`)。这是本包主入口**唯一**的接线入口(与 `/vite` 子路径的同名 Vite 插件是两个不同入口)。
356
+ 注册 `Link` / `PageView` / `SlotView` / `ClientOnly` 全局组件,并从 `meta.cache: true` 声明播种 keep-alive include 列表(`initCachedViewsFromRoutes`)。这是本包主入口**唯一**的接线入口(与 `/vite` 子路径的同名 Vite 插件是两个不同入口)。
356
357
 
357
358
  ## 组件
358
359
 
@@ -386,6 +387,43 @@ Props:`to`(字符串或位置对象)、`href`、`replace`、`activeClass`
386
387
 
387
388
  捕获后代组件的渲染/异步/setup 错误并渲染配置的错误组件;路由变更时自动重置。
388
389
 
390
+ ### `<ClientOnly>`
391
+
392
+ 内容仅在客户端、水合之后渲染。适合浏览器态内容与模板片段(islands 的 `v-client.*` 编译转换只作用于大写开头的组件标签)。
393
+
394
+ ```vue
395
+ <template>
396
+ <!-- 字符串 fallback prop -->
397
+ <ClientOnly fallback="加载中…">
398
+ <BrowserChart />
399
+ </ClientOnly>
400
+
401
+ <!-- #fallback slot(预留空间避免 CLS) -->
402
+ <ClientOnly>
403
+ <template #fallback>
404
+ <div class="skeleton" />
405
+ </template>
406
+ <BrowserMap />
407
+ </ClientOnly>
408
+
409
+ <!-- 裸模板片段也可以 -->
410
+ <ClientOnly>窗口宽度:{{ width }}px</ClientOnly>
411
+ </template>
412
+ ```
413
+
414
+ **水合安全**:占位内容(fallback / 空)在 SSR 与客户端水合首帧渲染**完全一致**,真实内容在挂载后 patch 进来。实现不做 `typeof window` 分支,因此不会产生水合 mismatch 警告。
415
+
416
+ **场景选择**:
417
+
418
+ | 场景 | 用法 |
419
+ | ------------------------------------------------------------- | ------------------------------------------------------------ |
420
+ | 模板片段 / 裸 HTML、同树渲染(完整应用上下文)、fallback slot | `<ClientOnly>` |
421
+ | 组件级延迟水合策略(idle/visible/media) | islands `v-client.*` |
422
+ | 编程式 island / 客户端组件包装 | `defineIsland` / `defineClientComponent`(`@ubean/islands`) |
423
+ | 文件级 server/client 拆分 | `.server.vue` / `.client.vue` |
424
+
425
+ > 注意:默认 slot 内容不进 SSR HTML —— SEO 关键内容不要用 `<ClientOnly>`。出现在 island 占位元素子内容中的 `<ClientOnly>` 会在 island 水合时一并被覆盖(islands 通过独立 app 水合)。
426
+
389
427
  ## 组合式函数
390
428
 
391
429
  - **`usePage<T>()`** —— 精简版页面数据访问:原样返回 `PAGE_KEY` 注入的数据(`props` / `component` / `errors`),未注入时返回共享空对象。**不含**路由态(`url` / `params` / `query` / `meta`)—— 请直接用 vue-router 的 `useRoute()`。框架运行时(`@ubean/client`)在其上叠加自己的路由感知 `usePage` 提供完整 PageObject 协议。
@@ -17,7 +17,7 @@ import { a as ScannedLayout, o as ScannedPage } from "./types-CeX4SJit.js";
17
17
  * `ScanPagesResult`(`@ubean/vue/vite` 的 `scanPages` 返回值)与聚合层的
18
18
  * `ScanResult`(`@ubean/scan` 的 `scanProject`)都结构性满足本接口。
19
19
  */
20
- interface GeneratorScanInput {
20
+ export interface GeneratorScanInput {
21
21
  pages: ScannedPage[];
22
22
  layouts: ScannedLayout[];
23
23
  }
@@ -41,7 +41,7 @@ interface GeneratorScanInput {
41
41
  * 在 `@ubean/vue` 不改变产物契约。精简 SPA 用虚拟模式的 `typed-router.d.ts`
42
42
  * (`@ubean/vue/vite` 的 `generateTypedRouter`),不经过本生成器。
43
43
  */
44
- interface GeneratorOptions {
44
+ export interface GeneratorOptions {
45
45
  /** Project root directory (absolute). */
46
46
  cwd: string;
47
47
  /**
@@ -93,16 +93,16 @@ interface GeneratorOptions {
93
93
  */
94
94
  headerComment?: string;
95
95
  }
96
- interface GeneratorResult {
96
+ export interface GeneratorResult {
97
97
  routesPath?: string;
98
98
  importsPath?: string;
99
99
  dtsPath?: string;
100
100
  routeCount: number;
101
101
  layoutCount: number;
102
102
  }
103
- declare const DEFAULT_HEADER_COMMENT: string;
104
- declare function generateRouteFiles(scan: GeneratorScanInput, options: GeneratorOptions): Promise<GeneratorResult>;
105
- declare class RouteFileGenerator {
103
+ export declare const DEFAULT_HEADER_COMMENT: string;
104
+ export declare function generateRouteFiles(scan: GeneratorScanInput, options: GeneratorOptions): Promise<GeneratorResult>;
105
+ export declare class RouteFileGenerator {
106
106
  private readonly opts;
107
107
  constructor(options: GeneratorOptions);
108
108
  generate(scan: GeneratorScanInput): Promise<GeneratorResult>;
@@ -127,5 +127,4 @@ declare class RouteFileGenerator {
127
127
  */
128
128
  private renderRouteRecordInfo;
129
129
  }
130
- //#endregion
131
- export { DEFAULT_HEADER_COMMENT, GeneratorOptions, GeneratorResult, GeneratorScanInput, RouteFileGenerator, generateRouteFiles };
130
+ //#endregion
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { a as ScannedLayout, i as ScanPagesResult, n as PageMeta, o as ScannedPage, r as ScanPagesOptions, t as PageHead } from "./types-CeX4SJit.js";
2
- import { Component, ComputedRef, InjectionKey, Plugin, PropType, Ref, VNode } from "vue";
2
+ import { Component, ComputedRef, InjectionKey, Plugin, PropType, Ref, SlotsType, VNode } from "vue";
3
3
  import { NavigationGuard, RouteMeta, Router } from "vue-router";
4
4
  //#region src/view-transitions.d.ts
5
5
  interface ViewTransitionOptions {
@@ -7,10 +7,10 @@ interface ViewTransitionOptions {
7
7
  fallback?: 'none' | 'crossfade';
8
8
  types?: string[];
9
9
  }
10
- declare function supportsViewTransitions(): boolean;
11
- declare function withViewTransition<T>(callback: () => Promise<T> | T, options?: ViewTransitionOptions): Promise<T>;
12
- declare function getNavigationType(): 'traverse' | 'push' | 'replace' | 'reload';
13
- declare function useViewTransitionState(name: string): Record<string, string>;
10
+ export declare function supportsViewTransitions(): boolean;
11
+ export declare function withViewTransition<T>(callback: () => Promise<T> | T, options?: ViewTransitionOptions): Promise<T>;
12
+ export declare function getNavigationType(): 'traverse' | 'push' | 'replace' | 'reload';
13
+ export declare function useViewTransitionState(name: string): Record<string, string>;
14
14
  //#endregion
15
15
  //#region src/components.d.ts
16
16
  /**
@@ -25,17 +25,17 @@ declare function useViewTransitionState(name: string): Record<string, string>;
25
25
  * hydration href mismatch). Registered keys are shared globally, so every
26
26
  * copy resolves to the same Symbol.
27
27
  */
28
- declare const PAGE_KEY: unique symbol;
29
- declare const TRANSITION_KEY: unique symbol;
30
- declare const SSR_KEY: InjectionKey<boolean>;
31
- declare const LOADING_KEY: InjectionKey<Component | null>;
32
- declare const ERROR_KEY: InjectionKey<Component | null>;
28
+ export declare const PAGE_KEY: unique symbol;
29
+ export declare const TRANSITION_KEY: unique symbol;
30
+ export declare const SSR_KEY: InjectionKey<boolean>;
31
+ export declare const LOADING_KEY: InjectionKey<Component | null>;
32
+ export declare const ERROR_KEY: InjectionKey<Component | null>;
33
33
  /**
34
34
  * Injectable path localizer (optional i18n bridge). The framework runtime
35
35
  * (`@ubean/client`) provides the reactive `localizePath`; when absent,
36
36
  * `Link` renders paths verbatim — this package never imports i18n itself.
37
37
  */
38
- declare const LOCALIZE_PATH_KEY: InjectionKey<(path: string, locale?: string) => string>;
38
+ export declare const LOCALIZE_PATH_KEY: InjectionKey<(path: string, locale?: string) => string>;
39
39
  /**
40
40
  * Layout chain context — provided by `LayoutChainRenderer` so that `<PageView />`
41
41
  * inside a layout knows whether to render the next nested layout or the actual
@@ -46,13 +46,13 @@ interface LayoutChainContext {
46
46
  components: Array<Component | null>;
47
47
  depth: number;
48
48
  }
49
- declare const LAYOUT_CHAIN_KEY: InjectionKey<LayoutChainContext | null>;
49
+ export declare const LAYOUT_CHAIN_KEY: InjectionKey<LayoutChainContext | null>;
50
50
  /**
51
51
  * LayoutChainRenderer — recursively renders a chain of nested layout
52
52
  * components (framework factories build the chain; lean SPAs may use it
53
53
  * directly for nested layouts via render functions).
54
54
  */
55
- declare const LayoutChainRenderer: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
55
+ export declare const LayoutChainRenderer: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
56
56
  components: {
57
57
  type: PropType<Array<Component | null>>;
58
58
  required: true;
@@ -79,7 +79,7 @@ declare const LayoutChainRenderer: import("vue").DefineComponent<import("vue").E
79
79
  * configured `errorComponent` instead of the slot content. The error state
80
80
  * resets automatically on route change.
81
81
  */
82
- declare const ErrorBoundary: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
82
+ export declare const ErrorBoundary: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
83
83
  component: {
84
84
  type: PropType<Component | null>;
85
85
  default: null;
@@ -96,6 +96,56 @@ declare const ErrorBoundary: import("vue").DefineComponent<import("vue").Extract
96
96
  }>> & Readonly<{}>, {
97
97
  component: Component | null;
98
98
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
99
+ interface ClientOnlyProps {
100
+ /** Placeholder text rendered during SSR and client hydration render. */
101
+ fallback?: string;
102
+ }
103
+ interface ClientOnlySlots {
104
+ /** Content rendered only on the client, after hydration. */
105
+ default?: () => VNode[];
106
+ /** Placeholder rendered during SSR and the client's hydration render. */
107
+ fallback?: () => VNode[];
108
+ }
109
+ /**
110
+ * ClientOnly — renders its default slot ONLY on the client, after hydration.
111
+ *
112
+ * ## Hydration-safety contract
113
+ *
114
+ * The implementation must NEVER branch on `typeof window` (that renders
115
+ * different trees on server vs client first paint → hydration mismatch).
116
+ * Instead it follows the same pattern as the islands runtime's
117
+ * `defineClientComponent`: the placeholder (fallback) is rendered during SSR
118
+ * AND during the client's hydration render, then `onMounted` flips
119
+ * `mounted` and Vue patches in the real content.
120
+ *
121
+ * ## Output shape
122
+ *
123
+ * - Default (no fallback): renders a comment vnode on BOTH server and client
124
+ * first paint — no wrapper element, no layout pollution.
125
+ * - `fallback` prop: renders the string as a text node.
126
+ * - `#fallback` slot: renders arbitrary placeholder content (reserve space to
127
+ * avoid CLS — the default slot content is absent from the SSR HTML).
128
+ *
129
+ * ## Typing
130
+ *
131
+ * Props and slots are fully typed (`ClientOnlyProps` / `ClientOnlySlots`) so
132
+ * templates get `#fallback` autocompletion and vue-tsc rejects unknown slots.
133
+ * Uses the function-style `defineComponent<Props, E, S>` overload — the
134
+ * object-style overload cannot carry slot types.
135
+ *
136
+ * ## When to use what (vs islands)
137
+ *
138
+ * - `<ClientOnly>` — template fragments / browser-only markup, same-tree
139
+ * rendering (full app context), works on plain HTML (the islands
140
+ * `v-client.*` transform only matches capitalized component tags).
141
+ * - `v-client.only` — islands architecture: registry-driven, hydrated via a
142
+ * separate app by `hydrateIslands()` (content inside an island placeholder
143
+ * is replaced on hydration, so `<ClientOnly>` inside island children is
144
+ * wiped with it).
145
+ * - `defineClientComponent` — programmatic wrapper (always renders a
146
+ * `<div data-client-only>` wrapper element).
147
+ */
148
+ export declare const ClientOnly: import("vue").DefineSetupFnComponent<ClientOnlyProps, {}, SlotsType<ClientOnlySlots>, ClientOnlyProps & {}, import("vue").PublicProps>;
99
149
  /**
100
150
  * PageView — renders the matched route page wrapped with `<Transition>` and
101
151
  * `<KeepAlive>`. Layouts use `<PageView />` (instead of `<slot />`) as the
@@ -123,7 +173,7 @@ declare const ErrorBoundary: import("vue").DefineComponent<import("vue").Extract
123
173
  * On SSR (via `SSR_KEY`), KeepAlive/Transition/Suspense/ErrorBoundary are
124
174
  * skipped (client-only concepts).
125
175
  */
126
- declare const PageView: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
176
+ export declare const PageView: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
127
177
  transition: {
128
178
  type: (BooleanConstructor | StringConstructor)[];
129
179
  default: undefined;
@@ -165,7 +215,7 @@ declare const PageView: import("vue").DefineComponent<import("vue").ExtractPropT
165
215
  * component (a nested `<RouterView>` there would look at depth + 1 and
166
216
  * never find the named view).
167
217
  */
168
- declare const SlotView: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
218
+ export declare const SlotView: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
169
219
  name: {
170
220
  type: StringConstructor;
171
221
  required: true;
@@ -183,7 +233,7 @@ declare const SlotView: import("vue").DefineComponent<import("vue").ExtractPropT
183
233
  * Path localization is opt-in via `LOCALIZE_PATH_KEY` (provided by the
184
234
  * framework runtime); lean SPAs render paths verbatim.
185
235
  */
186
- declare const Link: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
236
+ export declare const Link: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
187
237
  to: {
188
238
  type: PropType<string | Record<string, unknown>>;
189
239
  default: undefined;
@@ -288,9 +338,9 @@ interface UbeanVuePageData<TProps = Record<string, unknown>> {
288
338
  *
289
339
  * 未提供 `PAGE_KEY` 时返回共享的空对象(字段均为 undefined)。
290
340
  */
291
- declare function usePage<TProps = Record<string, unknown>>(): UbeanVuePageData<TProps>;
341
+ export declare function usePage<TProps = Record<string, unknown>>(): UbeanVuePageData<TProps>;
292
342
  /** `useViewTransition()` — whether native View Transitions are available/enabled. */
293
- declare function useViewTransition(): {
343
+ export declare function useViewTransition(): {
294
344
  enabled: boolean;
295
345
  supports: boolean;
296
346
  options: ViewTransitionOptions;
@@ -308,7 +358,7 @@ declare function useViewTransition(): {
308
358
  * const router = createRouter({ history: createWebHistory(), routes });
309
359
  * const app = createApp(App);
310
360
  * app.use(router);
311
- * app.use(ubeanVue, { routes }); // Link/PageView/SlotView + cache seeding
361
+ * app.use(ubeanVue, { routes }); // Link/PageView/SlotView/ClientOnly + cache seeding
312
362
  * app.mount('#app');
313
363
  * ```
314
364
  */
@@ -325,7 +375,7 @@ interface UbeanVueOptions {
325
375
  };
326
376
  }>;
327
377
  }
328
- declare const ubeanVue: Plugin<UbeanVueOptions>;
378
+ export declare const ubeanVue: Plugin<UbeanVueOptions>;
329
379
  //#endregion
330
380
  //#region src/cache-views.d.ts
331
381
  interface UseCacheViewsReturn {
@@ -364,11 +414,11 @@ interface UseCacheViewsReturn {
364
414
  * Safe to call inside any component `setup()`; the returned computeds are
365
415
  * tracked by the rendering effect so `<keep-alive :include>` stays in sync.
366
416
  */
367
- declare function useCacheViews(): UseCacheViewsReturn;
417
+ export declare function useCacheViews(): UseCacheViewsReturn;
368
418
  /** Enable keep-alive caching for a specific page (by route name). */
369
- declare function enablePageCache(name: string): void;
419
+ export declare function enablePageCache(name: string): void;
370
420
  /** Disable keep-alive caching for a specific page (by route name). */
371
- declare function disablePageCache(name: string): void;
421
+ export declare function disablePageCache(name: string): void;
372
422
  /**
373
423
  * Temporarily exclude a page from keep-alive (forces its cached instance
374
424
  * to be pruned on next render). Pair with `includePageCache()` to restore.
@@ -377,11 +427,11 @@ declare function disablePageCache(name: string): void;
377
427
  * the exclude list, wait a tick, then remove it — the cached instance is
378
428
  * pruned and the page remounts fresh on next render.
379
429
  */
380
- declare function excludePageCache(name: string): void;
430
+ export declare function excludePageCache(name: string): void;
381
431
  /** Remove a route name from the cache exclude list (restores caching). */
382
- declare function includePageCache(name: string): void;
432
+ export declare function includePageCache(name: string): void;
383
433
  /** Whether a specific page is currently excluded from cache. */
384
- declare function isPageExcluded(name: string): boolean;
434
+ export declare function isPageExcluded(name: string): boolean;
385
435
  /**
386
436
  * Reload a cached page by pruning its keep-alive instance.
387
437
  *
@@ -403,7 +453,7 @@ declare function isPageExcluded(name: string): boolean;
403
453
  * @param delay Ignored — kept for signature compatibility with the previous
404
454
  * exclude→include implementation.
405
455
  */
406
- declare function resetRouteCache(name?: string, _delay?: number): Promise<void>;
456
+ export declare function resetRouteCache(name?: string, _delay?: number): Promise<void>;
407
457
  /**
408
458
  * Invalidate cached page instance(s).
409
459
  *
@@ -414,17 +464,17 @@ declare function resetRouteCache(name?: string, _delay?: number): Promise<void>;
414
464
  * Note: the page will be re-cached on next visit if its `definePage({ cache: true })`
415
465
  * is still in effect or `enablePageCache(name)` is called again.
416
466
  */
417
- declare function invalidatePageCache(name?: string): void;
467
+ export declare function invalidatePageCache(name?: string): void;
418
468
  /** Whether a specific page is currently cached. */
419
- declare function isPageCached(name: string): boolean;
469
+ export declare function isPageCached(name: string): boolean;
420
470
  /** Whether page caching is globally enabled. */
421
- declare function isCacheEnabled(): boolean;
471
+ export declare function isCacheEnabled(): boolean;
422
472
  /** Reactive ref of cached route names — for advanced use cases. */
423
- declare function getCachedViewNames(): Ref<string[]>;
473
+ export declare function getCachedViewNames(): Ref<string[]>;
424
474
  /** Reactive ref of excluded route names — for advanced use cases. */
425
- declare function getExcludedViewNames(): Ref<string[]>;
475
+ export declare function getExcludedViewNames(): Ref<string[]>;
426
476
  /** Reactive ref of the global cache-enabled flag. */
427
- declare function getCacheEnabled(): Ref<boolean>;
477
+ export declare function getCacheEnabled(): Ref<boolean>;
428
478
  /**
429
479
  * Seed the cache include list from route metadata.
430
480
  *
@@ -432,7 +482,7 @@ declare function getCacheEnabled(): Ref<boolean>;
432
482
  * declared `definePage({ cache: true })`. Routes without `meta.cache` are
433
483
  * left untouched so runtime toggling remains possible.
434
484
  */
435
- declare function initCachedViewsFromRoutes(routes: Array<{
485
+ export declare function initCachedViewsFromRoutes(routes: Array<{
436
486
  name?: string | symbol;
437
487
  meta?: {
438
488
  cache?: boolean;
@@ -448,14 +498,14 @@ declare function initCachedViewsFromRoutes(routes: Array<{
448
498
  *
449
499
  * @internal — used by `createLayoutWrapper` in `app.ts`.
450
500
  */
451
- declare function getNamedPageWrapper(routeName: string, original: Component): Component;
501
+ export declare function getNamedPageWrapper(routeName: string, original: Component): Component;
452
502
  /**
453
503
  * Clear the wrapper registry. Mainly useful in tests or when the entire
454
504
  * route table is rebuilt.
455
505
  *
456
506
  * @internal
457
507
  */
458
- declare function resetNamedPageWrappers(): void;
508
+ export declare function resetNamedPageWrappers(): void;
459
509
  //#endregion
460
510
  //#region src/page-runtime.d.ts
461
511
  interface UsePageTransitionReturn {
@@ -483,13 +533,13 @@ interface UsePageTransitionReturn {
483
533
  * transition.clear();
484
534
  * ```
485
535
  */
486
- declare function usePageTransition(): UsePageTransitionReturn;
536
+ export declare function usePageTransition(): UsePageTransitionReturn;
487
537
  /** Imperatively set the global page transition name. */
488
- declare function setPageTransition(name: string): void;
538
+ export declare function setPageTransition(name: string): void;
489
539
  /** Imperatively clear the global page transition name (disables transitions). */
490
- declare function clearPageTransition(): void;
540
+ export declare function clearPageTransition(): void;
491
541
  /** Reactive ref of the global transition name — for advanced use cases. */
492
- declare function getPageTransitionName(): Ref<string>;
542
+ export declare function getPageTransitionName(): Ref<string>;
493
543
  interface UseReloadSignalReturn {
494
544
  /** Reactive counter that increments on each `reloadPage()` call. */
495
545
  counter: ComputedRef<number>;
@@ -498,7 +548,7 @@ interface UseReloadSignalReturn {
498
548
  /** Trigger a reload of the current page. */
499
549
  reload: (routeName?: string, duration?: number) => Promise<void>;
500
550
  }
501
- declare function useReloadSignal(routeNameGetter?: () => string | undefined | null): UseReloadSignalReturn;
551
+ export declare function useReloadSignal(routeNameGetter?: () => string | undefined | null): UseReloadSignalReturn;
502
552
  /**
503
553
  * Imperatively trigger a page reload.
504
554
  *
@@ -510,11 +560,11 @@ declare function useReloadSignal(routeNameGetter?: () => string | undefined | nu
510
560
  * `useReloadSignal().counter` consumers; nothing remounts).
511
561
  * - `duration`: milliseconds to wait for transition/cleanup. Defaults to 300.
512
562
  */
513
- declare function reloadPage(name?: string, duration?: number): Promise<void>;
563
+ export declare function reloadPage(name?: string, duration?: number): Promise<void>;
514
564
  /** Reactive ref of the reload counter — for advanced use cases. */
515
- declare function getReloadCounter(): Ref<number>;
565
+ export declare function getReloadCounter(): Ref<number>;
516
566
  /** Whether a reload is currently in progress. */
517
- declare function isReloading(): boolean;
567
+ export declare function isReloading(): boolean;
518
568
  //#endregion
519
569
  //#region src/define-page.d.ts
520
570
  /**
@@ -607,7 +657,7 @@ interface DefineClientPageOptions {
607
657
  * });
608
658
  * ```
609
659
  */
610
- declare function definePage(options?: DefineClientPageOptions): void;
660
+ export declare function definePage(options?: DefineClientPageOptions): void;
611
661
  //#endregion
612
662
  //#region src/router-location.d.ts
613
663
  interface RouteLocationRaw {
@@ -618,7 +668,7 @@ interface RouteLocationRaw {
618
668
  hash?: string;
619
669
  }
620
670
  type RouteLocation = string | RouteLocationRaw;
621
- declare function resolveRoute(to: RouteLocation, routeMap?: Record<string, {
671
+ export declare function resolveRoute(to: RouteLocation, routeMap?: Record<string, {
622
672
  path: string;
623
673
  route: string;
624
674
  }>): string;
@@ -631,7 +681,7 @@ type TypedLinkProps = {
631
681
  activeClass?: string;
632
682
  exactActiveClass?: string;
633
683
  };
634
- declare function isActiveRoute(currentPath: string, targetHref: string, exact?: boolean): boolean;
684
+ export declare function isActiveRoute(currentPath: string, targetHref: string, exact?: boolean): boolean;
635
685
  //#endregion
636
686
  //#region src/matchers.d.ts
637
687
  /**
@@ -656,25 +706,25 @@ type MatcherFunction = (value: string) => boolean | null | undefined;
656
706
  * export default defineMatcher('numeric', (value) => /^\d+$/.test(value));
657
707
  * ```
658
708
  */
659
- declare function defineMatcher(name: string, fn: MatcherFunction): MatcherFunction;
709
+ export declare function defineMatcher(name: string, fn: MatcherFunction): MatcherFunction;
660
710
  /**
661
711
  * 按名称获取已注册的 matcher。未注册时返回 `undefined`。
662
712
  */
663
- declare function getMatcher(name: string): MatcherFunction | undefined;
713
+ export declare function getMatcher(name: string): MatcherFunction | undefined;
664
714
  /**
665
715
  * 判断指定名称的 matcher 是否已注册。
666
716
  */
667
- declare function hasMatcher(name: string): boolean;
717
+ export declare function hasMatcher(name: string): boolean;
668
718
  /**
669
719
  * 获取所有已注册 matcher 的名称列表(主要用于调试 / DevTools)。
670
720
  */
671
- declare function listMatcherNames(): string[];
721
+ export declare function listMatcherNames(): string[];
672
722
  /**
673
723
  * 清空所有已注册的 matcher。
674
724
  *
675
725
  * **仅供测试使用** —— 应用代码不应调用,避免误删其他模块注册的 matcher。
676
726
  */
677
- declare function clearMatchers(): void;
727
+ export declare function clearMatchers(): void;
678
728
  /**
679
729
  * 校验一组路由参数是否通过对应的 matcher。
680
730
  *
@@ -689,7 +739,7 @@ declare function clearMatchers(): void;
689
739
  * - 若参数不存在于 `params` 中,视为校验失败返回 `false`
690
740
  * - matcher 函数抛异常时,捕获并视为不匹配
691
741
  */
692
- declare function validateParams(matchers: Record<string, string> | undefined, params: Record<string, string | string[] | undefined>): boolean;
742
+ export declare function validateParams(matchers: Record<string, string> | undefined, params: Record<string, string | string[] | undefined>): boolean;
693
743
  interface MatcherGuardOptions {
694
744
  /**
695
745
  * matcher 校验失败时跳转的路由名。默认 `'NotFound'`。
@@ -728,7 +778,7 @@ interface MatcherGuardOptions {
728
778
  * - 对于纯 SPA 应用(`ssr: false`),强烈建议注册此守卫,否则客户端导航到
729
779
  * `/users/abc`(应当被 `[id=numeric]` 拒绝)会渲染页面而非 404。
730
780
  */
731
- declare function createMatcherGuard(options?: MatcherGuardOptions): NavigationGuard;
781
+ export declare function createMatcherGuard(options?: MatcherGuardOptions): NavigationGuard;
732
782
  //#endregion
733
783
  //#region src/head.d.ts
734
784
  /**
@@ -751,7 +801,7 @@ interface PageHeadClient {
751
801
  * 将静态 `PageHead` push 进 head 实例(falsy 字段自动跳过)。
752
802
  * 与 SSR 侧 `pushPageHead`(@ubean/client/ssr)语义一致,保证双端同构。
753
803
  */
754
- declare function pushPageHead(head: PageHeadClient, pageHead: PageHead): void;
804
+ export declare function pushPageHead(head: PageHeadClient, pageHead: PageHead): void;
755
805
  /**
756
806
  * SPA 页面级 head 守卫:导航完成后读取 `route.meta.head` 并 push 进 head 实例。
757
807
  *
@@ -766,14 +816,14 @@ declare function pushPageHead(head: PageHeadClient, pageHead: PageHead): void;
766
816
  *
767
817
  * 初始导航同样触发(vue-router 首次导航完成后 afterEach 执行)。
768
818
  */
769
- declare function setupPageHeadGuard(router: Router, head: PageHeadClient): void;
819
+ export declare function setupPageHeadGuard(router: Router, head: PageHeadClient): void;
770
820
  /**
771
821
  * 按需创建 head 实例(懒加载 `@unhead/vue`,optional peer dependency)。
772
822
  *
773
823
  * 项目已自带 unhead 实例时无需调用 —— 直接把实例传给
774
824
  * `setupPageHeadGuard` 即可,零额外加载。
775
825
  */
776
- declare function createPageHead(): Promise<PageHeadClient>;
826
+ export declare function createPageHead(): Promise<PageHeadClient>;
777
827
  //#endregion
778
828
  //#region src/index.d.ts
779
829
  declare module 'vue-router' {
@@ -805,4 +855,4 @@ declare module 'vue-router' {
805
855
  }
806
856
  }
807
857
  //#endregion
808
- export { type DefineClientPageOptions, ERROR_KEY, ErrorBoundary, LAYOUT_CHAIN_KEY, LOADING_KEY, LOCALIZE_PATH_KEY, type LayoutChainContext, LayoutChainRenderer, Link, type MatcherFunction, type MatcherGuardOptions, PAGE_KEY, type PageHead, type PageHeadClient, type PageMeta, PageView, type RouteLocation, type RouteLocationRaw, SSR_KEY, type ScanPagesOptions, type ScanPagesResult, type ScannedLayout, type ScannedPage, SlotView, TRANSITION_KEY, type TypedLinkProps, type UbeanVueOptions, type UbeanVuePageData, type UseCacheViewsReturn, type UsePageTransitionReturn, type UseReloadSignalReturn, type ViewTransitionOptions, clearMatchers, clearPageTransition, createMatcherGuard, createPageHead, ubeanVue as default, ubeanVue, defineMatcher, definePage, disablePageCache, enablePageCache, excludePageCache, getCacheEnabled, getCachedViewNames, getExcludedViewNames, getMatcher, getNamedPageWrapper, getNavigationType, getPageTransitionName, getReloadCounter, hasMatcher, includePageCache, initCachedViewsFromRoutes, invalidatePageCache, isActiveRoute, isCacheEnabled, isPageCached, isPageExcluded, isReloading, listMatcherNames, pushPageHead, reloadPage, resetNamedPageWrappers, resetRouteCache, resolveRoute, setPageTransition, setupPageHeadGuard, supportsViewTransitions, useCacheViews, usePage, usePageTransition, useReloadSignal, useViewTransition, useViewTransitionState, validateParams, withViewTransition };
858
+ export { type ClientOnlyProps, type ClientOnlySlots, type DefineClientPageOptions, type LayoutChainContext, type MatcherFunction, type MatcherGuardOptions, type PageHead, type PageHeadClient, type PageMeta, type RouteLocation, type RouteLocationRaw, type ScanPagesOptions, type ScanPagesResult, type ScannedLayout, type ScannedPage, type TypedLinkProps, type UbeanVueOptions, type UbeanVuePageData, type UseCacheViewsReturn, type UsePageTransitionReturn, type UseReloadSignalReturn, type ViewTransitionOptions, ubeanVue as default };
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { KeepAlive, Suspense, Transition, computed, createCommentVNode, defineAsyncComponent, defineComponent, h, inject, nextTick, onErrorCaptured, provide, reactive, ref, shallowRef, watch } from "vue";
1
+ import { KeepAlive, Suspense, Transition, computed, createCommentVNode, createTextVNode, defineAsyncComponent, defineComponent, h, inject, nextTick, onErrorCaptured, onMounted, provide, reactive, ref, shallowRef, watch } from "vue";
2
2
  import { RouterLink, RouterView, useRoute } from "vue-router";
3
3
  //#region src/cache-views.ts
4
4
  /**
@@ -624,6 +624,61 @@ const ErrorBoundary = defineComponent({
624
624
  }
625
625
  });
626
626
  /**
627
+ * ClientOnly — renders its default slot ONLY on the client, after hydration.
628
+ *
629
+ * ## Hydration-safety contract
630
+ *
631
+ * The implementation must NEVER branch on `typeof window` (that renders
632
+ * different trees on server vs client first paint → hydration mismatch).
633
+ * Instead it follows the same pattern as the islands runtime's
634
+ * `defineClientComponent`: the placeholder (fallback) is rendered during SSR
635
+ * AND during the client's hydration render, then `onMounted` flips
636
+ * `mounted` and Vue patches in the real content.
637
+ *
638
+ * ## Output shape
639
+ *
640
+ * - Default (no fallback): renders a comment vnode on BOTH server and client
641
+ * first paint — no wrapper element, no layout pollution.
642
+ * - `fallback` prop: renders the string as a text node.
643
+ * - `#fallback` slot: renders arbitrary placeholder content (reserve space to
644
+ * avoid CLS — the default slot content is absent from the SSR HTML).
645
+ *
646
+ * ## Typing
647
+ *
648
+ * Props and slots are fully typed (`ClientOnlyProps` / `ClientOnlySlots`) so
649
+ * templates get `#fallback` autocompletion and vue-tsc rejects unknown slots.
650
+ * Uses the function-style `defineComponent<Props, E, S>` overload — the
651
+ * object-style overload cannot carry slot types.
652
+ *
653
+ * ## When to use what (vs islands)
654
+ *
655
+ * - `<ClientOnly>` — template fragments / browser-only markup, same-tree
656
+ * rendering (full app context), works on plain HTML (the islands
657
+ * `v-client.*` transform only matches capitalized component tags).
658
+ * - `v-client.only` — islands architecture: registry-driven, hydrated via a
659
+ * separate app by `hydrateIslands()` (content inside an island placeholder
660
+ * is replaced on hydration, so `<ClientOnly>` inside island children is
661
+ * wiped with it).
662
+ * - `defineClientComponent` — programmatic wrapper (always renders a
663
+ * `<div data-client-only>` wrapper element).
664
+ */
665
+ const ClientOnly = defineComponent((props, { slots }) => {
666
+ const mounted = ref(false);
667
+ onMounted(() => {
668
+ mounted.value = true;
669
+ });
670
+ return () => {
671
+ if (mounted.value) return slots.default?.() ?? null;
672
+ if (slots.fallback) return slots.fallback();
673
+ if (props.fallback !== void 0) return createTextVNode(props.fallback);
674
+ return createCommentVNode("client-only");
675
+ };
676
+ }, {
677
+ name: "ClientOnly",
678
+ inheritAttrs: false,
679
+ props: ["fallback"]
680
+ });
681
+ /**
627
682
  * PageView — renders the matched route page wrapped with `<Transition>` and
628
683
  * `<KeepAlive>`. Layouts use `<PageView />` (instead of `<slot />`) as the
629
684
  * page outlet.
@@ -868,6 +923,7 @@ const ubeanVue = { install(app, options) {
868
923
  app.component("Link", Link);
869
924
  app.component("PageView", PageView);
870
925
  app.component("SlotView", SlotView);
926
+ app.component("ClientOnly", ClientOnly);
871
927
  if (options?.routes) initCachedViewsFromRoutes(options.routes);
872
928
  } };
873
929
  //#endregion
@@ -1092,4 +1148,4 @@ async function createPageHead() {
1092
1148
  throw new Error("[ubean/vue] createPageHead() requires `@unhead/vue` to be installed (optional peer dependency)");
1093
1149
  }
1094
1150
  //#endregion
1095
- export { ERROR_KEY, ErrorBoundary, LAYOUT_CHAIN_KEY, LOADING_KEY, LOCALIZE_PATH_KEY, LayoutChainRenderer, Link, PAGE_KEY, PageView, SSR_KEY, SlotView, TRANSITION_KEY, clearMatchers, clearPageTransition, createMatcherGuard, createPageHead, ubeanVue as default, ubeanVue, defineMatcher, definePage, disablePageCache, enablePageCache, excludePageCache, getCacheEnabled, getCachedViewNames, getExcludedViewNames, getMatcher, getNamedPageWrapper, getNavigationType, getPageTransitionName, getReloadCounter, hasMatcher, includePageCache, initCachedViewsFromRoutes, invalidatePageCache, isActiveRoute, isCacheEnabled, isPageCached, isPageExcluded, isReloading, listMatcherNames, pushPageHead, reloadPage, resetNamedPageWrappers, resetRouteCache, resolveRoute, setPageTransition, setupPageHeadGuard, supportsViewTransitions, useCacheViews, usePage, usePageTransition, useReloadSignal, useViewTransition, useViewTransitionState, validateParams, withViewTransition };
1151
+ export { ClientOnly, ERROR_KEY, ErrorBoundary, LAYOUT_CHAIN_KEY, LOADING_KEY, LOCALIZE_PATH_KEY, LayoutChainRenderer, Link, PAGE_KEY, PageView, SSR_KEY, SlotView, TRANSITION_KEY, clearMatchers, clearPageTransition, createMatcherGuard, createPageHead, ubeanVue as default, ubeanVue, defineMatcher, definePage, disablePageCache, enablePageCache, excludePageCache, getCacheEnabled, getCachedViewNames, getExcludedViewNames, getMatcher, getNamedPageWrapper, getNavigationType, getPageTransitionName, getReloadCounter, hasMatcher, includePageCache, initCachedViewsFromRoutes, invalidatePageCache, isActiveRoute, isCacheEnabled, isPageCached, isPageExcluded, isReloading, listMatcherNames, pushPageHead, reloadPage, resetNamedPageWrappers, resetRouteCache, resolveRoute, setPageTransition, setupPageHeadGuard, supportsViewTransitions, useCacheViews, usePage, usePageTransition, useReloadSignal, useViewTransition, useViewTransitionState, validateParams, withViewTransition };
package/dist/vite.d.ts CHANGED
@@ -10,13 +10,13 @@ import { Plugin } from "vite";
10
10
  * `(.)target/page.vue` → intercept from same level, target = 'target'
11
11
  * `(...)target/page.vue` → intercept from root, target = 'target'
12
12
  */
13
- declare function extractSlotAndIntercept(fileBase: string): {
13
+ export declare function extractSlotAndIntercept(fileBase: string): {
14
14
  cleanedBase: string;
15
15
  slot?: string;
16
16
  interceptFrom?: string;
17
17
  interceptTarget?: string;
18
18
  };
19
- declare function scanPages(options: ScanPagesOptions): Promise<ScanPagesResult>;
19
+ export declare function scanPages(options: ScanPagesOptions): Promise<ScanPagesResult>;
20
20
  //#endregion
21
21
  //#region src/virtual-pages.d.ts
22
22
  type PagesModuleInput = Pick<ScanPagesResult, 'pages' | 'layouts' | 'notFoundPage' | 'loadingPage' | 'errorPage'>;
@@ -28,7 +28,7 @@ interface LocaleRouteCompileOptions {
28
28
  vueParam?: string;
29
29
  }
30
30
  /** Generate the virtual module source(plain JS — no TS syntax, SSR-safe)。 */
31
- declare function generatePagesModuleSource(input: PagesModuleInput, locale?: LocaleRouteCompileOptions): string;
31
+ export declare function generatePagesModuleSource(input: PagesModuleInput, locale?: LocaleRouteCompileOptions): string;
32
32
  /**
33
33
  * Generate the ambient module declaration d.ts for the virtual module.
34
34
  *
@@ -36,7 +36,7 @@ declare function generatePagesModuleSource(input: PagesModuleInput, locale?: Loc
36
36
  * script 上下文中才注册「环境模块声明」;在 module 文件中会被当作模块增强,
37
37
  * 对不存在的模块无法生效。由 `/vite` 插件写入 `<root>/ubean-vue-routes.d.ts`。
38
38
  */
39
- declare function generateVirtualModuleDts(input: PagesModuleInput, moduleId?: string): string;
39
+ export declare function generateVirtualModuleDts(input: PagesModuleInput, moduleId?: string): string;
40
40
  /**
41
41
  * Generate `typed-router.d.ts` content for the virtual module.
42
42
  *
@@ -48,10 +48,10 @@ declare function generateVirtualModuleDts(input: PagesModuleInput, moduleId?: st
48
48
  * 若以 script 形式输出,同名环境模块声明会整体遮蔽真实包的类型。
49
49
  * 虚拟模块自身的环境声明见 `generateVirtualModuleDts`。
50
50
  */
51
- declare function generateTypedRouter(input: PagesModuleInput, _moduleId?: string): string;
51
+ export declare function generateTypedRouter(input: PagesModuleInput, _moduleId?: string): string;
52
52
  //#endregion
53
53
  //#region src/route-path.d.ts
54
- declare function stripRouteGroups(path: string): string;
54
+ export declare function stripRouteGroups(path: string): string;
55
55
  interface ParsedRoutePath {
56
56
  route: string;
57
57
  method?: string;
@@ -78,11 +78,11 @@ interface ParsedRoutePath {
78
78
  * parseMatchers('blog/[...slug=any]') → { cleaned: 'blog/[...slug]', matchers: { slug: 'any' } }
79
79
  * parseMatchers('users/[id]') → { cleaned: 'users/[id]', matchers: undefined }
80
80
  */
81
- declare function parseMatchers(filePath: string): {
81
+ export declare function parseMatchers(filePath: string): {
82
82
  cleaned: string;
83
83
  matchers?: Record<string, string>;
84
84
  };
85
- declare function filePathToRoute(filePath: string, prefix?: string): ParsedRoutePath;
85
+ export declare function filePathToRoute(filePath: string, prefix?: string): ParsedRoutePath;
86
86
  //#endregion
87
87
  //#region src/route-name.d.ts
88
88
  /**
@@ -90,8 +90,8 @@ declare function filePathToRoute(filePath: string, prefix?: string): ParsedRoute
90
90
  * `@ubean/scan` 聚合层 re-export 保持向后兼容;服务端专用的
91
91
  * `generateApiRouteId` 保留在 `@ubean/scan`。
92
92
  */
93
- declare function generateRouteName(routePath: string): string;
94
- declare function generateLayoutName(layoutPath: string): string;
93
+ export declare function generateRouteName(routePath: string): string;
94
+ export declare function generateLayoutName(layoutPath: string): string;
95
95
  //#endregion
96
96
  //#region src/extract-page.d.ts
97
97
  /**
@@ -99,7 +99,7 @@ declare function generateLayoutName(layoutPath: string): string;
99
99
  * 对象字面量参数。供本包的 `definePage` 提取与 `@ubean/scan` 的
100
100
  * `defineHandlerMeta` 提取共用(单一解析器实现)。
101
101
  */
102
- declare function extractCallObject(code: string, funcName: string): Record<string, unknown> | null;
102
+ export declare function extractCallObject(code: string, funcName: string): Record<string, unknown> | null;
103
103
  /**
104
104
  * Normalize a parsed `head` value into a `PageHead` object。
105
105
  *
@@ -107,8 +107,8 @@ declare function extractCallObject(code: string, funcName: string): Record<strin
107
107
  * `definePage`) and Markdown pages (via frontmatter) share the same rules.
108
108
  */
109
109
  declare function normalizePageHead(raw: unknown): PageHead | undefined;
110
- declare function extractDefinePageFromCode(code: string): PageMeta | null;
111
- declare function extractDefinePage(filePath: string): Promise<PageMeta | null>;
110
+ export declare function extractDefinePageFromCode(code: string): PageMeta | null;
111
+ export declare function extractDefinePage(filePath: string): Promise<PageMeta | null>;
112
112
  //#endregion
113
113
  //#region src/vite.d.ts
114
114
  /**
@@ -125,8 +125,8 @@ declare function extractDefinePage(filePath: string): Promise<PageMeta | null>;
125
125
  * 框架层(`@ubean/vite`)通过 `generatePagesModuleSource` /
126
126
  * `scanPages` / `generateTypedRouter` 复用同一套生成器。
127
127
  */
128
- declare const VUE_ROUTES_MODULE_ID = "virtual:ubean-vue-routes";
129
- interface UbeanVueViteOptions {
128
+ export declare const VUE_ROUTES_MODULE_ID = "virtual:ubean-vue-routes";
129
+ export interface UbeanVueViteOptions {
130
130
  /** 页面目录(相对项目根或绝对),支持多目录。默认 `'src/pages'`。 */
131
131
  pagesDir?: string | string[];
132
132
  /** 布局目录(相对项目根或绝对),支持多目录。默认 `'src/layouts'`。 */
@@ -161,11 +161,11 @@ interface UbeanVueViteOptions {
161
161
  head?: boolean;
162
162
  }
163
163
  /** 剥离 `definePage({...})` 宏调用(构建期,扫描后源码中不再需要)。 */
164
- declare function stripDefinePageCalls(code: string): string;
164
+ export declare function stripDefinePageCalls(code: string): string;
165
165
  /**
166
166
  * 扫描客户端页面/布局(对外工具函数,`root` 为项目根)。
167
167
  */
168
- declare function scanClientPages(root: string, options?: UbeanVueViteOptions): Promise<ScanPagesResult>;
169
- declare function ubeanVueVite(options?: UbeanVueViteOptions): Plugin;
168
+ export declare function scanClientPages(root: string, options?: UbeanVueViteOptions): Promise<ScanPagesResult>;
169
+ export declare function ubeanVueVite(options?: UbeanVueViteOptions): Plugin;
170
170
  //#endregion
171
- export { type LocaleRouteCompileOptions, type PagesModuleInput, type ParsedRoutePath, UbeanVueViteOptions, VUE_ROUTES_MODULE_ID, ubeanVueVite as default, ubeanVueVite, extractCallObject, extractDefinePage, extractDefinePageFromCode, extractSlotAndIntercept, filePathToRoute, generateLayoutName, generatePagesModuleSource, generateRouteName, generateTypedRouter, generateVirtualModuleDts, normalizePageHead, parseMatchers, scanClientPages, scanPages, stripDefinePageCalls, stripRouteGroups };
171
+ export { type LocaleRouteCompileOptions, type PagesModuleInput, type ParsedRoutePath, ubeanVueVite as default, normalizePageHead };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ubean/vue",
3
- "version": "0.4.5",
3
+ "version": "0.4.7",
4
4
  "description": "Lean Vue client kernel & page-routing owner for ubean — plugin-first page routing outlet, page cache (keep-alive), transitions, reload signal, definePage macro + file-routing Vite plugin (/vite: multi-dir scan, reuse routes, markdown & page-head opt-in). Runtime deps: vue + vue-router only.",
5
5
  "files": [
6
6
  "dist"
@@ -33,13 +33,13 @@
33
33
  },
34
34
  "devDependencies": {
35
35
  "@types/node": "^26.5.0",
36
- "@ubean/markdown": "0.4.5",
36
+ "@ubean/markdown": "0.4.7",
37
37
  "@unhead/vue": "^3.4.0",
38
38
  "@vue/server-renderer": "^3.5.42",
39
39
  "happy-dom": "^20.14.0",
40
- "typescript": "7.0.2",
41
- "vite": "npm:@voidzero-dev/vite-plus-core@0.3.0",
42
- "vite-plus": "0.3.0",
40
+ "typescript": "npm:typescript-native-bridge@latest",
41
+ "vite": "npm:@voidzero-dev/vite-plus-core@0.3.1",
42
+ "vite-plus": "0.3.1",
43
43
  "vitest": "5.0.0"
44
44
  },
45
45
  "peerDependencies": {