@ubean/client 0.2.2 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,11 +1,10 @@
1
- import { computed, createApp, createSSRApp, defineComponent, h, inject, markRaw, provide, reactive, ref, shallowRef, watchEffect } from "vue";
1
+ import { a as getI18nRuntimeConfig, l as localizePath, r as createUbeanI18n, s as initClientI18n, t as bindI18nRuntime } from "./i18n-t0G3VyBs.js";
2
+ import { computed, createApp, createSSRApp, defineComponent, h, inject, markRaw, provide, reactive, shallowRef, watchEffect } from "vue";
2
3
  import { createMemoryHistory, createRouter, createWebHistory, useRoute, useRouter } from "vue-router";
3
4
  import { ERROR_KEY, ERROR_KEY as ERROR_KEY$1, LAYOUT_CHAIN_KEY, LOADING_KEY, LOADING_KEY as LOADING_KEY$1, LOCALIZE_PATH_KEY, LOCALIZE_PATH_KEY as LOCALIZE_PATH_KEY$1, LayoutChainRenderer, LayoutChainRenderer as LayoutChainRenderer$1, Link, PAGE_KEY, PAGE_KEY as PAGE_KEY$1, PageView, SSR_KEY, SSR_KEY as SSR_KEY$1, SlotView, TRANSITION_KEY, TRANSITION_KEY as TRANSITION_KEY$1, initCachedViewsFromRoutes, ubeanVue, useViewTransition } from "@ubean/vue";
4
5
  import { useHead as useUnheadHead, useSeoMeta } from "@unhead/vue";
5
- import { LOCALE_DATA_ID } from "@ubean/pages";
6
6
  import { vClient } from "@ubean/islands/directive";
7
7
  import { Head } from "@unhead/vue/components";
8
- import { addLocale, clearLocales, defineLocale, detectBrowserLocale, detectLocale, extractLocaleFromPath, getDefaultLocale, getI18nConfig, getLocale, getLocaleDir, getLocaleName, getRegisteredLocales, localizePath, mergeLocale, onLocaleChange, setI18nConfig, setLocale, switchLocalePath, t, useI18n } from "@ubean/i18n";
9
8
  //#region src/use-page.ts
10
9
  /**
11
10
  * 框架层 `usePage()` — 路由感知的响应式页面上下文。
@@ -63,209 +62,6 @@ function createUbeanRouter(options) {
63
62
  return router;
64
63
  }
65
64
  //#endregion
66
- //#region src/i18n.ts
67
- const _global = globalThis;
68
- function hydrateLocale() {
69
- if (typeof _global.document === "undefined") return {
70
- locale: null,
71
- dir: "ltr"
72
- };
73
- const el = _global.document.getElementById(LOCALE_DATA_ID);
74
- if (!el) return {
75
- locale: null,
76
- dir: "ltr"
77
- };
78
- try {
79
- const data = JSON.parse(el.textContent || "null");
80
- if (data && typeof data.locale === "string") return {
81
- locale: data.locale,
82
- dir: data.dir === "rtl" ? "rtl" : "ltr",
83
- messages: data.messages,
84
- availableLocales: Array.isArray(data.availableLocales) ? data.availableLocales : void 0
85
- };
86
- } catch {
87
- return {
88
- locale: null,
89
- dir: "ltr"
90
- };
91
- }
92
- return {
93
- locale: null,
94
- dir: "ltr"
95
- };
96
- }
97
- function syncHtmlLang(locale, dir) {
98
- if (typeof _global.document === "undefined") return;
99
- const html = _global.document.documentElement;
100
- if (html) {
101
- html.setAttribute("lang", locale);
102
- html.setAttribute("dir", dir);
103
- }
104
- }
105
- /**
106
- * Lazily hydrate SSR-injected locale data (idempotent).
107
- *
108
- * Previously this ran as a module-load side effect (reading the DOM and
109
- * mutating `<html lang/dir>` on import). It is now invoked explicitly by
110
- * `createUbeanClientApp` / `createUbeanSSRApp` and by the read APIs below,
111
- * keeping module import side-effect free.
112
- */
113
- let _i18nHydrated = false;
114
- function initClientI18n() {
115
- if (_i18nHydrated) return;
116
- _i18nHydrated = true;
117
- const { locale: hydratedLocale, dir: hydratedDir, messages: hydratedMessages, availableLocales: hydratedAvailable } = hydrateLocale();
118
- if (!hydratedLocale) return;
119
- if (hydratedAvailable && hydratedAvailable.length > 0) {
120
- const sorted = [...hydratedAvailable].sort((a, b) => (b.isDefault ? 1 : 0) - (a.isDefault ? 1 : 0));
121
- for (const loc of sorted) {
122
- const isCurrent = loc.code === hydratedLocale;
123
- defineLocale({
124
- code: loc.code,
125
- messages: isCurrent && hydratedMessages ? hydratedMessages : {},
126
- name: loc.name,
127
- dir: loc.dir,
128
- isDefault: loc.isDefault
129
- });
130
- }
131
- } else if (hydratedMessages && typeof hydratedMessages === "object") defineLocale({
132
- code: hydratedLocale,
133
- messages: hydratedMessages,
134
- dir: hydratedDir
135
- });
136
- setLocale(hydratedLocale);
137
- localeRef.value = hydratedLocale;
138
- syncHtmlLang(hydratedLocale, hydratedDir);
139
- }
140
- const localeRef = ref(getLocale());
141
- onLocaleChange((newLocale) => {
142
- localeRef.value = newLocale;
143
- syncHtmlLang(newLocale, getLocaleDir(newLocale));
144
- });
145
- function useI18n$1() {
146
- initClientI18n();
147
- const core = useI18n();
148
- const localeDir = computed(() => getLocaleDir(localeRef.value));
149
- const localeName = computed(() => getLocaleName(localeRef.value));
150
- function translate(key, params) {
151
- localeRef.value;
152
- return core.t(key, params);
153
- }
154
- return {
155
- locale: localeRef,
156
- get fallbackLocale() {
157
- return core.fallbackLocale;
158
- },
159
- get availableLocales() {
160
- return core.availableLocales;
161
- },
162
- t: translate,
163
- setLocale(locale) {
164
- core.setLocale(locale);
165
- },
166
- getLocale() {
167
- return localeRef.value;
168
- },
169
- onLocaleChange(callback) {
170
- return core.onLocaleChange(callback);
171
- },
172
- getLocaleDir(locale) {
173
- return core.getLocaleDir(locale);
174
- },
175
- getLocaleName(locale) {
176
- return core.getLocaleName(locale);
177
- },
178
- localeDir,
179
- localeName
180
- };
181
- }
182
- function defineLocale$1(definition) {
183
- initClientI18n();
184
- const result = defineLocale(definition);
185
- localeRef.value = getLocale();
186
- return result;
187
- }
188
- /**
189
- * 建立对当前 locale 的响应式依赖(模板中的 `t()` / `localizePath()` 经此驱动重渲染)。
190
- * i18n core 的状态存于 globalThis(非响应式),Vue 侧的 `localeRef` 是唯一响应源。
191
- */
192
- function trackLocale() {
193
- initClientI18n();
194
- localeRef.value;
195
- }
196
- function t$1(key, params) {
197
- trackLocale();
198
- return t(key, params);
199
- }
200
- function setLocale$1(locale) {
201
- setLocale(locale);
202
- }
203
- function getLocale$1() {
204
- return localeRef.value;
205
- }
206
- function onLocaleChange$1(callback) {
207
- return onLocaleChange(callback);
208
- }
209
- function getLocaleDir$1(locale) {
210
- return getLocaleDir(locale);
211
- }
212
- function getLocaleName$1(locale) {
213
- return getLocaleName(locale);
214
- }
215
- function getRegisteredLocales$1() {
216
- initClientI18n();
217
- return getRegisteredLocales();
218
- }
219
- function detectLocale$1(acceptLanguage) {
220
- return detectLocale(acceptLanguage);
221
- }
222
- function detectBrowserLocale$1() {
223
- return detectBrowserLocale();
224
- }
225
- function addLocale$1(code, messages, options) {
226
- addLocale(code, messages, options);
227
- }
228
- function mergeLocale$1(code, messages) {
229
- mergeLocale(code, messages);
230
- }
231
- function clearLocales$1() {
232
- clearLocales();
233
- localeRef.value = getLocale();
234
- }
235
- function getI18nConfig$1() {
236
- return getI18nConfig();
237
- }
238
- function setI18nConfig$1(config) {
239
- setI18nConfig(config);
240
- }
241
- function localizePath$1(path, locale) {
242
- trackLocale();
243
- return localizePath(path, locale);
244
- }
245
- function switchLocalePath$1(newLocale, currentPath) {
246
- trackLocale();
247
- return switchLocalePath(newLocale, currentPath);
248
- }
249
- function getDefaultLocale$1() {
250
- return getDefaultLocale();
251
- }
252
- function extractLocaleFromPath$1(path) {
253
- return extractLocaleFromPath(path);
254
- }
255
- function useSwitchLocalePath() {
256
- initClientI18n();
257
- return computed(() => (newLocale) => {
258
- const path = typeof _global.window !== "undefined" ? _global.window.location.pathname : "/";
259
- return switchLocalePath(newLocale, path);
260
- });
261
- }
262
- function useLocalePath() {
263
- initClientI18n();
264
- return computed(() => (path, locale) => {
265
- return localizePath(path, locale || localeRef.value);
266
- });
267
- }
268
- //#endregion
269
65
  //#region src/page-macro.ts
270
66
  function definePage(_meta) {}
271
67
  function defineMeta(_meta) {}
@@ -273,7 +69,7 @@ function defineMiddleware(_handler) {}
273
69
  //#endregion
274
70
  //#region src/app.ts
275
71
  /**
276
- * Framework app factories — consumed by the ubean aggregator & `@ubean/ssr`.
72
+ * Framework app factories — consumed by the ubean aggregator & `@ubean/client/ssr`.
277
73
  *
278
74
  * `createUbeanClientApp` / `createUbeanSSRApp` wire the lean kernel from
279
75
  * `@ubean/vue` together with framework extras: unhead, i18n path
@@ -352,7 +148,7 @@ function createRootComponent(LayoutWrapper, page, transitionOpts, isSSR, loading
352
148
  provide(SSR_KEY, isSSR);
353
149
  provide(LOADING_KEY, loadingComponent ?? null);
354
150
  provide(ERROR_KEY, errorComponent ?? null);
355
- provide(LOCALIZE_PATH_KEY, (path) => localizePath$1(path));
151
+ provide(LOCALIZE_PATH_KEY, (path, locale) => localizePath(path, locale));
356
152
  return () => h(LayoutWrapper);
357
153
  }
358
154
  });
@@ -378,8 +174,12 @@ function createUbeanClientApp(options) {
378
174
  initCachedViewsFromRoutes(options.routes);
379
175
  const RootComponent = createRootComponent(LayoutWrapper, page, transitionOpts, false, resolveComp(options.loadingComponent), resolveComp(options.errorComponent));
380
176
  const app = options.hydrate ? createSSRApp(RootComponent) : createApp(RootComponent);
177
+ const i18n = options.i18n ?? createUbeanI18n();
178
+ app.use(i18n);
381
179
  app.use(head);
382
180
  app.use(router);
181
+ bindI18nRuntime(i18n, router);
182
+ app.provide("ubean:i18n-runtime-config", getI18nRuntimeConfig());
383
183
  app.component("Link", Link);
384
184
  app.component("PageView", PageView);
385
185
  app.component("SlotView", SlotView);
@@ -411,8 +211,12 @@ function createUbeanSSRApp(initialPage, options) {
411
211
  initCachedViewsFromRoutes(options.routes);
412
212
  const RootComponent = createRootComponent(LayoutWrapper, page, { enabled: false }, true, resolveComp(options.loadingComponent), resolveComp(options.errorComponent));
413
213
  const app = createSSRApp(RootComponent);
214
+ const i18n = options.i18n ?? createUbeanI18n();
215
+ app.use(i18n);
414
216
  app.use(head);
415
217
  app.use(router);
218
+ bindI18nRuntime(i18n, router);
219
+ app.provide("ubean:i18n-runtime-config", getI18nRuntimeConfig());
416
220
  app.component("Link", Link);
417
221
  app.component("PageView", PageView);
418
222
  app.component("SlotView", SlotView);
@@ -430,4 +234,4 @@ function createUbeanSSRApp(initialPage, options) {
430
234
  }
431
235
  const Head$1 = Head;
432
236
  //#endregion
433
- export { getI18nConfig$1 as A, setLocale$1 as B, addLocale$1 as C, detectLocale$1 as D, detectBrowserLocale$1 as E, initClientI18n as F, useSwitchLocalePath as G, t$1 as H, localizePath$1 as I, createUbeanRouter as K, mergeLocale$1 as L, getLocaleDir$1 as M, getLocaleName$1 as N, extractLocaleFromPath$1 as O, getRegisteredLocales$1 as P, onLocaleChange$1 as R, definePage as S, defineLocale$1 as T, useI18n$1 as U, switchLocalePath$1 as V, useLocalePath as W, useSeoMeta as _, LOCALIZE_PATH_KEY$1 as a, defineMeta as b, PAGE_KEY$1 as c, SlotView as d, TRANSITION_KEY$1 as f, useRouter as g, ubeanVue as h, LOADING_KEY$1 as i, getLocale$1 as j, getDefaultLocale$1 as k, PageView as l, createUbeanSSRApp as m, Head$1 as n, LayoutChainRenderer$1 as o, createUbeanClientApp as p, usePage as q, LAYOUT_CHAIN_KEY as r, Link as s, ERROR_KEY$1 as t, SSR_KEY$1 as u, useUnheadHead as v, clearLocales$1 as w, defineMiddleware as x, useViewTransition as y, setI18nConfig$1 as z };
237
+ export { createUbeanRouter as C, definePage as S, useSeoMeta as _, LOCALIZE_PATH_KEY$1 as a, defineMeta as b, PAGE_KEY$1 as c, SlotView as d, TRANSITION_KEY$1 as f, useRouter as g, ubeanVue as h, LOADING_KEY$1 as i, PageView as l, createUbeanSSRApp as m, Head$1 as n, LayoutChainRenderer$1 as o, createUbeanClientApp as p, LAYOUT_CHAIN_KEY as r, Link as s, ERROR_KEY$1 as t, SSR_KEY$1 as u, useUnheadHead as v, usePage as w, defineMiddleware as x, useViewTransition as y };
@@ -3,6 +3,7 @@ import { RouteRecordRaw, Router, useRouter } from "vue-router";
3
3
  import { ERROR_KEY as ERROR_KEY$1, LAYOUT_CHAIN_KEY, LOADING_KEY as LOADING_KEY$1, LOCALIZE_PATH_KEY as LOCALIZE_PATH_KEY$1, LayoutChainContext, LayoutChainRenderer as LayoutChainRenderer$1, Link, PAGE_KEY as PAGE_KEY$1, PageMeta, PageView, SSR_KEY as SSR_KEY$1, SlotView, TRANSITION_KEY as TRANSITION_KEY$1, UbeanVueOptions, ViewTransitionOptions, ubeanVue, useViewTransition } from "@ubean/vue";
4
4
  import { VueHeadClient, useHead as useUnheadHead, useSeoMeta } from "@unhead/vue";
5
5
  import { PageObject } from "@ubean/pages";
6
+ import { I18n } from "vue-i18n";
6
7
  import { Input, RouteMeta } from "@ubean/shared";
7
8
  //#region src/router.d.ts
8
9
  interface CreateUbeanRouterOptions {
@@ -83,6 +84,11 @@ interface UbeanAppOptions {
83
84
  * 通过 Vue 的 `errorCaptured` 生命周期实现错误边界。
84
85
  */
85
86
  errorComponent?: Component | (() => Component);
87
+ /**
88
+ * vue-i18n instance (`legacy: false`). Created by `createUbeanI18n`.
89
+ * Installed with `app.use(i18n)` before the router.
90
+ */
91
+ i18n?: I18n;
86
92
  }
87
93
  interface UbeanAppInstance {
88
94
  app: App;
package/dist/app.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- import { A as CreateUbeanRouterOptions, C as useUnheadHead, D as definePage, E as defineMiddleware, O as UbeanVuePage, S as useSeoMeta, T as defineMeta, _ as VueHeadClient, a as LOCALIZE_PATH_KEY, b as ubeanVue, c as Link, d as SSR_KEY, f as SlotView, g as UbeanVueOptions, h as UbeanAppOptions, i as LOADING_KEY, j as createUbeanRouter, k as usePage, l as PAGE_KEY, m as UbeanAppInstance, n as Head, o as LayoutChainContext, p as TRANSITION_KEY, r as LAYOUT_CHAIN_KEY, s as LayoutChainRenderer, t as ERROR_KEY, u as PageView, v as createUbeanClientApp, w as useViewTransition, x as useRouter, y as createUbeanSSRApp } from "./app-BknjDM9j.js";
1
+ import { A as CreateUbeanRouterOptions, C as useUnheadHead, D as definePage, E as defineMiddleware, O as UbeanVuePage, S as useSeoMeta, T as defineMeta, _ as VueHeadClient, a as LOCALIZE_PATH_KEY, b as ubeanVue, c as Link, d as SSR_KEY, f as SlotView, g as UbeanVueOptions, h as UbeanAppOptions, i as LOADING_KEY, j as createUbeanRouter, k as usePage, l as PAGE_KEY, m as UbeanAppInstance, n as Head, o as LayoutChainContext, p as TRANSITION_KEY, r as LAYOUT_CHAIN_KEY, s as LayoutChainRenderer, t as ERROR_KEY, u as PageView, v as createUbeanClientApp, w as useViewTransition, x as useRouter, y as createUbeanSSRApp } from "./app-BP3eySMH.js";
2
2
  export { type CreateUbeanRouterOptions, ERROR_KEY, Head, LAYOUT_CHAIN_KEY, LOADING_KEY, LOCALIZE_PATH_KEY, type LayoutChainContext, LayoutChainRenderer, Link, PAGE_KEY, PageView, SSR_KEY, SlotView, TRANSITION_KEY, UbeanAppInstance, UbeanAppOptions, type UbeanVueOptions, type UbeanVuePage, type VueHeadClient, createUbeanClientApp, createUbeanRouter, createUbeanSSRApp, defineMeta, defineMiddleware, definePage, ubeanVue, useUnheadHead as useHead, usePage, useRouter, useSeoMeta, useViewTransition };
package/dist/app.js CHANGED
@@ -1,2 +1,2 @@
1
- import { K as createUbeanRouter, S as definePage, _ as useSeoMeta, a as LOCALIZE_PATH_KEY, b as defineMeta, c as PAGE_KEY, d as SlotView, f as TRANSITION_KEY, g as useRouter, h as ubeanVue, i as LOADING_KEY, l as PageView, m as createUbeanSSRApp, n as Head, o as LayoutChainRenderer, p as createUbeanClientApp, q as usePage, r as LAYOUT_CHAIN_KEY, s as Link, t as ERROR_KEY, u as SSR_KEY, v as useUnheadHead, x as defineMiddleware, y as useViewTransition } from "./app-Bc1d5Svw.js";
1
+ import { C as createUbeanRouter, S as definePage, _ as useSeoMeta, a as LOCALIZE_PATH_KEY, b as defineMeta, c as PAGE_KEY, d as SlotView, f as TRANSITION_KEY, g as useRouter, h as ubeanVue, i as LOADING_KEY, l as PageView, m as createUbeanSSRApp, n as Head, o as LayoutChainRenderer, p as createUbeanClientApp, r as LAYOUT_CHAIN_KEY, s as Link, t as ERROR_KEY, u as SSR_KEY, v as useUnheadHead, w as usePage, x as defineMiddleware, y as useViewTransition } from "./app-B6vVB7hp.js";
2
2
  export { ERROR_KEY, Head, LAYOUT_CHAIN_KEY, LOADING_KEY, LOCALIZE_PATH_KEY, LayoutChainRenderer, Link, PAGE_KEY, PageView, SSR_KEY, SlotView, TRANSITION_KEY, createUbeanClientApp, createUbeanRouter, createUbeanSSRApp, defineMeta, defineMiddleware, definePage, ubeanVue, useUnheadHead as useHead, usePage, useRouter, useSeoMeta, useViewTransition };
@@ -0,0 +1,206 @@
1
+ import { computed } from "vue";
2
+ import { useRoute } from "vue-router";
3
+ import { LOCALE_DATA_ID } from "@ubean/pages";
4
+ import { createI18n, useI18n as useVueI18n } from "vue-i18n";
5
+ import { buildLocaleHead, extractLocaleFromPath, localizePath, switchLocalePath } from "@ubean/i18n/browser";
6
+ //#region src/i18n.ts
7
+ const RUNTIME_KEY = "__UBEAN_I18N_RUNTIME__";
8
+ function getRuntimeState() {
9
+ const g = globalThis;
10
+ if (!g[RUNTIME_KEY]) g[RUNTIME_KEY] = {
11
+ config: null,
12
+ loader: null,
13
+ metas: []
14
+ };
15
+ return g[RUNTIME_KEY];
16
+ }
17
+ function getRuntimeConfigState() {
18
+ return getRuntimeState().config;
19
+ }
20
+ function setRuntimeConfigState(config) {
21
+ getRuntimeState().config = config;
22
+ }
23
+ function bindI18nRuntime(i18n, router) {
24
+ const state = getRuntimeState();
25
+ state.i18n = i18n;
26
+ if (router) state.router = router;
27
+ }
28
+ function configureI18nRuntime(options) {
29
+ const state = getRuntimeState();
30
+ state.config = options.config;
31
+ if (options.loadLocale) state.loader = options.loadLocale;
32
+ if (options.locales) state.metas = options.locales;
33
+ }
34
+ function getI18nRuntimeConfig() {
35
+ return getRuntimeConfigState();
36
+ }
37
+ function localeCodesFrom(input) {
38
+ if (!Array.isArray(input)) return [];
39
+ const codes = [];
40
+ for (const item of input) if (typeof item === "string") codes.push(item);
41
+ else if (item && typeof item === "object" && "code" in item) codes.push(String(item.code));
42
+ return codes;
43
+ }
44
+ function applyHydratedPayload(hydrated) {
45
+ const state = getRuntimeState();
46
+ if (hydrated.routing && !state.config) setRuntimeConfigState({
47
+ defaultLocale: hydrated.routing.defaultLocale,
48
+ locales: localeCodesFrom(hydrated.routing.locales),
49
+ strategy: hydrated.routing.strategy,
50
+ fallbackLocale: hydrated.fallbackLocale || hydrated.routing.defaultLocale,
51
+ cookieName: hydrated.cookieName || "ubean_locale",
52
+ baseUrl: hydrated.baseUrl || ""
53
+ });
54
+ else if (hydrated.routing && state.config && !state.config.locales.length) state.config.locales = localeCodesFrom(hydrated.routing.locales);
55
+ const metas = hydrated.locales || hydrated.availableLocales;
56
+ if (metas) state.metas = metas;
57
+ }
58
+ function routingFrom(config) {
59
+ return {
60
+ defaultLocale: config.defaultLocale,
61
+ locales: config.locales,
62
+ strategy: config.strategy
63
+ };
64
+ }
65
+ function readHydratedPayload() {
66
+ if (typeof document === "undefined") return null;
67
+ const el = document.getElementById(LOCALE_DATA_ID);
68
+ if (!el?.textContent) return null;
69
+ try {
70
+ return JSON.parse(el.textContent);
71
+ } catch {
72
+ return null;
73
+ }
74
+ }
75
+ function createUbeanI18n(options) {
76
+ const hydrated = typeof document !== "undefined" ? readHydratedPayload() : null;
77
+ const cfg = getRuntimeConfigState();
78
+ const locale = options?.locale || hydrated?.locale || cfg?.defaultLocale || "en";
79
+ const fallbackLocale = options?.fallbackLocale || hydrated?.fallbackLocale || cfg?.fallbackLocale || cfg?.defaultLocale || locale;
80
+ const messages = { ...options?.messages };
81
+ if (hydrated?.messages && hydrated.locale) messages[hydrated.locale] = hydrated.messages;
82
+ if (hydrated?.fallbackMessages && hydrated.fallbackLocale && hydrated.fallbackLocale !== hydrated.locale) messages[hydrated.fallbackLocale] = hydrated.fallbackMessages;
83
+ if (!messages[locale]) messages[locale] = {};
84
+ if (fallbackLocale !== locale && !messages[fallbackLocale]) messages[fallbackLocale] = {};
85
+ if (hydrated) applyHydratedPayload(hydrated);
86
+ const hydratedConfig = getRuntimeState().config;
87
+ if (hydratedConfig && options?.vueI18n) hydratedConfig.vueI18n = options.vueI18n;
88
+ return createI18n({
89
+ legacy: false,
90
+ locale,
91
+ fallbackLocale,
92
+ messages,
93
+ missingWarn: false,
94
+ fallbackWarn: false,
95
+ ...cfg?.vueI18n,
96
+ ...options?.vueI18n
97
+ });
98
+ }
99
+ function installUbeanI18n(app, i18n) {
100
+ app.use(i18n);
101
+ }
102
+ function writeLocaleCookie(code) {
103
+ if (typeof document === "undefined") return;
104
+ const name = getRuntimeConfigState()?.cookieName || "ubean_locale";
105
+ document.cookie = `${name}=${encodeURIComponent(code)}; Path=/; SameSite=Lax`;
106
+ }
107
+ /**
108
+ * Framework setLocale: load messages, switch composer locale, write cookie,
109
+ * navigate to the localized path (`no_prefix` skips navigation).
110
+ *
111
+ * Uses the runtime bound by `bindI18nRuntime` (not Vue composables) so it is
112
+ * safe to call from click handlers.
113
+ */
114
+ async function setLocale(code) {
115
+ const state = getRuntimeState();
116
+ const composer = state.i18n?.global;
117
+ if (!composer) return;
118
+ composer.locale.value = code;
119
+ writeLocaleCookie(code);
120
+ const loader = state.loader;
121
+ const loadPromise = loader ? loader(code).then((msgs) => {
122
+ composer.setLocaleMessage(code, msgs);
123
+ }) : Promise.resolve();
124
+ const cfg = state.config;
125
+ let navPromise = Promise.resolve();
126
+ if (cfg && cfg.strategy !== "no_prefix") {
127
+ const router = state.router;
128
+ const currentPath = (router?.currentRoute.value.path || router?.currentRoute.value.fullPath?.split("?")[0] || (typeof window !== "undefined" ? window.location.pathname : "/")).split("?")[0];
129
+ const target = switchLocalePath(code, currentPath, routingFrom(cfg));
130
+ if (target !== currentPath && router) navPromise = router.replace(target).then(() => void 0, () => void 0);
131
+ }
132
+ await Promise.all([loadPromise, navPromise]);
133
+ }
134
+ function getLocale() {
135
+ try {
136
+ return String(useVueI18n().locale.value);
137
+ } catch {
138
+ return getRuntimeConfigState()?.defaultLocale || "en";
139
+ }
140
+ }
141
+ function localizePath$1(path, locale) {
142
+ const cfg = getRuntimeConfigState();
143
+ if (!cfg) return path;
144
+ return localizePath(path, locale || getLocale(), routingFrom(cfg));
145
+ }
146
+ function switchLocalePath$1(locale, path) {
147
+ const cfg = getRuntimeConfigState();
148
+ if (!cfg) return path || "/";
149
+ const current = path ?? (typeof window !== "undefined" ? window.location.pathname : "/");
150
+ return switchLocalePath(locale, current, routingFrom(cfg));
151
+ }
152
+ function extractLocaleFromPath$1(path) {
153
+ const codes = getRuntimeConfigState()?.locales || [];
154
+ return extractLocaleFromPath(path, codes);
155
+ }
156
+ function useI18n() {
157
+ return useVueI18n();
158
+ }
159
+ function t(key, ...args) {
160
+ const translate = useVueI18n().t;
161
+ return String(translate(key, ...args));
162
+ }
163
+ function useLocalePath() {
164
+ const i18n = useVueI18n();
165
+ return (path, locale) => localizePath$1(path, locale ?? String(i18n.locale.value));
166
+ }
167
+ function useSwitchLocalePath() {
168
+ const route = useRoute();
169
+ return (locale) => switchLocalePath$1(locale, route.path);
170
+ }
171
+ function useLocaleRoute() {
172
+ return useLocalePath();
173
+ }
174
+ function useLocaleHead() {
175
+ const i18n = useVueI18n();
176
+ const route = useRoute();
177
+ return computed(() => {
178
+ const cfg = getRuntimeConfigState();
179
+ const routing = cfg ? routingFrom(cfg) : {
180
+ defaultLocale: "en",
181
+ locales: ["en"],
182
+ strategy: "prefix_except_default"
183
+ };
184
+ return buildLocaleHead({
185
+ path: route.path,
186
+ locale: String(i18n.locale.value),
187
+ locales: (getRuntimeState().metas || []).map((l) => ({
188
+ code: l.code,
189
+ language: l.language,
190
+ name: l.name,
191
+ dir: l.dir,
192
+ isDefault: l.isDefault
193
+ })),
194
+ routing,
195
+ baseUrl: cfg?.baseUrl
196
+ });
197
+ });
198
+ }
199
+ /** Hydrate routing config from SSR payload before the first render. */
200
+ function initClientI18n() {
201
+ const hydrated = readHydratedPayload();
202
+ if (!hydrated) return;
203
+ applyHydratedPayload(hydrated);
204
+ }
205
+ //#endregion
206
+ export { useSwitchLocalePath as _, getI18nRuntimeConfig as a, installUbeanI18n as c, switchLocalePath$1 as d, t as f, useLocaleRoute as g, useLocalePath as h, extractLocaleFromPath$1 as i, localizePath$1 as l, useLocaleHead as m, configureI18nRuntime as n, getLocale as o, useI18n as p, createUbeanI18n as r, initClientI18n as s, bindI18nRuntime as t, setLocale as u };
package/dist/index.d.ts CHANGED
@@ -1,13 +1,14 @@
1
- import { A as CreateUbeanRouterOptions, C as useUnheadHead, D as definePage, E as defineMiddleware, O as UbeanVuePage, S as useSeoMeta, T as defineMeta, _ as VueHeadClient$1, h as UbeanAppOptions, j as createUbeanRouter, k as usePage, m as UbeanAppInstance, n as Head, v as createUbeanClientApp, y as createUbeanSSRApp } from "./app-BknjDM9j.js";
1
+ import { A as CreateUbeanRouterOptions, C as useUnheadHead, D as definePage, E as defineMiddleware, O as UbeanVuePage, S as useSeoMeta, T as defineMeta, _ as VueHeadClient$1, h as UbeanAppOptions, j as createUbeanRouter, k as usePage, m as UbeanAppInstance, n as Head, v as createUbeanClientApp, y as createUbeanSSRApp } from "./app-BP3eySMH.js";
2
2
  import { AppPluginConfig, DefineAppOptions, ResolvedAppConfig, applyAppConfig, createDefaultAppConfig, defineApp, mergeAppConfig } from "./define-app.js";
3
- import { ComputedRef, Ref, ShallowRef } from "vue";
3
+ import { App, ComputedRef, Ref, ShallowRef } from "vue";
4
4
  import { useRouter } from "vue-router";
5
5
  import { ERROR_KEY, ErrorBoundary, LAYOUT_CHAIN_KEY, LOADING_KEY, LOCALIZE_PATH_KEY, LayoutChainContext, LayoutChainRenderer, Link, PAGE_KEY, PageView, RouteLocation, RouteLocationRaw, SSR_KEY, SlotView, TRANSITION_KEY, TypedLinkProps, UbeanVueOptions, UbeanVuePageData, UseCacheViewsReturn, UsePageTransitionReturn, UseReloadSignalReturn, ViewTransitionOptions, clearPageTransition, disablePageCache, enablePageCache, excludePageCache, getCacheEnabled, getCachedViewNames, getExcludedViewNames, getNamedPageWrapper, getNavigationType, getPageTransitionName, getReloadCounter, includePageCache, initCachedViewsFromRoutes, invalidatePageCache, isActiveRoute, isCacheEnabled, isPageCached, isPageExcluded, isReloading, reloadPage, resetNamedPageWrappers, resetRouteCache, resolveRoute, setPageTransition, supportsViewTransitions, ubeanVue, useCacheViews, usePageTransition, useReloadSignal, useViewTransition, useViewTransitionState, withViewTransition } from "@ubean/vue";
6
6
  import { VueHeadClient, injectHead } from "@unhead/vue";
7
7
  import { createHead as createClientHead } from "@unhead/vue/client";
8
- import { PageObject } from "@ubean/pages";
9
- import { I18nConfig, LocaleChangeCallback, LocaleDefinition, LocaleMessages } from "@ubean/i18n";
10
- import { HydrateIslandsOptions, IslandHydrateOptions, IslandRecord, collectIslands, hydrateIsland, hydrateIslands } from "@ubean/islands/runtime";
8
+ import { DataResult, PageObject, UseAsyncDataOptions, UseFetchOptions, invalidateData, setDefaultFetch, useAsyncData, useData, useFetch } from "@ubean/pages";
9
+ import { Composer, I18n, I18n as VueI18nInstance } from "vue-i18n";
10
+ import { LocaleRoutingConfig, buildLocaleHead } from "@ubean/i18n/browser";
11
+ import { HydrateIslandsOptions, IslandHydrateOptions, IslandHydrationScheduler, IslandRecord, collectIslands, hasPendingIslands, hydrateIsland, hydrateIslands, scheduleIslandHydration } from "@ubean/islands/runtime";
11
12
  import { LinkTag, MetaTag, SeoMetadata } from "@ubean/seo";
12
13
  //#region src/client.d.ts
13
14
  declare function getInitialPageData<T = Record<string, unknown>>(): PageObject<T> | null;
@@ -46,7 +47,7 @@ interface DataCacheStore {
46
47
  getTimestamp: (key: string | symbol) => number | undefined;
47
48
  }
48
49
  declare function createDataCacheStore(): DataCacheStore;
49
- interface UseAsyncDataOptions<T> {
50
+ interface UseAsyncDataOptions$1<T> {
50
51
  key?: string;
51
52
  tags?: string[];
52
53
  lazy?: boolean;
@@ -67,7 +68,7 @@ interface UseAsyncDataReturn<T> {
67
68
  refresh: () => Promise<void>;
68
69
  invalidate: () => void;
69
70
  }
70
- declare function createUseAsyncData(store: DataCacheStore): <T = unknown>(keyOrFetcher: string | (() => Promise<T>), fetcherOrOptions?: (() => Promise<T>) | UseAsyncDataOptions<T>, options?: UseAsyncDataOptions<T>) => UseAsyncDataReturn<T>;
71
+ declare function createUseAsyncData(store: DataCacheStore): <T = unknown>(keyOrFetcher: string | (() => Promise<T>), fetcherOrOptions?: (() => Promise<T>) | UseAsyncDataOptions$1<T>, options?: UseAsyncDataOptions$1<T>) => UseAsyncDataReturn<T>;
71
72
  declare function invalidateCache(store: DataCacheStore, keyOrTag: string): number;
72
73
  declare function clearCache(store: DataCacheStore): void;
73
74
  declare function defineDataKey(key: string): symbol;
@@ -83,54 +84,79 @@ declare function useServerData<T>(fetcher: () => Promise<T>): Promise<T>;
83
84
  declare function getInvalidatedKeysForAction(actionName: string, invalidationMap: Record<string, Array<string | symbol>>, store: DataCacheStore): number;
84
85
  //#endregion
85
86
  //#region src/i18n.d.ts
86
- declare function initClientI18n(): void;
87
- interface VueI18nInstance {
88
- locale: {
89
- value: string;
90
- };
87
+ type LocaleMessages = Record<string, unknown>;
88
+ interface I18nRuntimeConfig {
89
+ defaultLocale: string;
90
+ locales: string[];
91
+ strategy: LocaleRoutingConfig['strategy'];
91
92
  fallbackLocale: string;
92
- availableLocales: string[];
93
- t: (key: string, params?: Record<string, string | number>) => string;
94
- setLocale: (locale: string) => void;
95
- getLocale: () => string;
96
- onLocaleChange: (callback: LocaleChangeCallback) => () => void;
97
- getLocaleDir: (locale?: string) => 'ltr' | 'rtl';
98
- getLocaleName: (locale?: string) => string | undefined;
99
- localeDir: {
100
- value: 'ltr' | 'rtl';
101
- };
102
- localeName: {
103
- value: string | undefined;
104
- };
93
+ cookieName: string;
94
+ baseUrl: string;
95
+ vueI18n?: Record<string, unknown>;
105
96
  }
106
- declare function useI18n(): VueI18nInstance;
107
- declare function defineLocale(definition: LocaleDefinition): LocaleDefinition;
108
- declare function t(key: string, params?: Record<string, string | number>): string;
109
- declare function setLocale(locale: string): void;
110
- declare function getLocale(): string;
111
- declare function onLocaleChange(callback: LocaleChangeCallback): () => void;
112
- declare function getLocaleDir(locale?: string): 'ltr' | 'rtl';
113
- declare function getLocaleName(locale?: string): string | undefined;
114
- declare function getRegisteredLocales(): string[];
115
- declare function detectLocale(acceptLanguage?: string): string;
116
- declare function detectBrowserLocale(): string;
117
- declare function addLocale(code: string, messages: LocaleMessages, options?: {
118
- name?: string;
97
+ interface LocaleLoader {
98
+ (code: string): Promise<LocaleMessages>;
99
+ }
100
+ interface HydratedLocalePayload {
101
+ locale?: string;
119
102
  dir?: 'ltr' | 'rtl';
103
+ messages?: LocaleMessages;
104
+ fallbackLocale?: string;
105
+ fallbackMessages?: LocaleMessages;
106
+ routing?: LocaleRoutingConfig;
107
+ cookieName?: string;
108
+ baseUrl?: string;
109
+ locales?: Array<{
110
+ code: string;
111
+ language?: string;
112
+ name?: string;
113
+ dir?: 'ltr' | 'rtl';
114
+ isDefault?: boolean;
115
+ }>;
116
+ availableLocales?: Array<{
117
+ code: string;
118
+ language?: string;
119
+ name?: string;
120
+ dir?: 'ltr' | 'rtl';
121
+ isDefault?: boolean;
122
+ }>;
123
+ }
124
+ declare function configureI18nRuntime(options: {
125
+ config: I18nRuntimeConfig;
126
+ loadLocale?: LocaleLoader;
127
+ locales?: HydratedLocalePayload['locales'];
120
128
  }): void;
121
- declare function mergeLocale(code: string, messages: LocaleMessages): void;
122
- declare function clearLocales(): void;
123
- declare function getI18nConfig(): I18nConfig;
124
- declare function setI18nConfig(config: Partial<I18nConfig>): void;
129
+ declare function getI18nRuntimeConfig(): I18nRuntimeConfig | null;
130
+ declare function createUbeanI18n(options?: {
131
+ locale?: string;
132
+ fallbackLocale?: string;
133
+ messages?: Record<string, LocaleMessages>;
134
+ vueI18n?: Record<string, unknown>;
135
+ }): I18n;
136
+ declare function installUbeanI18n(app: App, i18n: I18n): void;
137
+ /**
138
+ * Framework setLocale: load messages, switch composer locale, write cookie,
139
+ * navigate to the localized path (`no_prefix` skips navigation).
140
+ *
141
+ * Uses the runtime bound by `bindI18nRuntime` (not Vue composables) so it is
142
+ * safe to call from click handlers.
143
+ */
144
+ declare function setLocale(code: string): Promise<void>;
145
+ declare function getLocale(): string;
125
146
  declare function localizePath(path: string, locale?: string): string;
126
- declare function switchLocalePath(newLocale: string, currentPath?: string): string;
127
- declare function getDefaultLocale(): string;
147
+ declare function switchLocalePath(locale: string, path?: string): string;
128
148
  declare function extractLocaleFromPath(path: string): {
129
149
  locale: string | null;
130
150
  pathWithoutLocale: string;
131
151
  };
132
- declare function useSwitchLocalePath(): import("vue").ComputedRef<(newLocale: string) => string>;
133
- declare function useLocalePath(): import("vue").ComputedRef<(path: string, locale?: string) => string>;
152
+ declare function useI18n(): Composer;
153
+ declare function t(key: string, ...args: unknown[]): string;
154
+ declare function useLocalePath(): (path: string, locale?: string) => string;
155
+ declare function useSwitchLocalePath(): (locale: string) => string;
156
+ declare function useLocaleRoute(): (path: string, locale?: string) => string;
157
+ declare function useLocaleHead(): ComputedRef<ReturnType<typeof buildLocaleHead>>;
158
+ /** Hydrate routing config from SSR payload before the first render. */
159
+ declare function initClientI18n(): void;
134
160
  //#endregion
135
161
  //#region src/color-mode.d.ts
136
162
  interface ColorModeConfig {
@@ -483,4 +509,4 @@ declare function _resetSearch(): void;
483
509
  */
484
510
  declare function _setPagefindMock(mock: any): void;
485
511
  //#endregion
486
- export { type AppPluginConfig, type ColorMode, type ColorModeConfig, type CreateUbeanRouterOptions, type DataCacheStore, type DefineAppOptions, ERROR_KEY, ErrorBoundary, Head, type VueHeadClient as HeadClient, type HydrateIslandsOptions, type IslandHydrateOptions, type IslandRecord, LAYOUT_CHAIN_KEY, LOADING_KEY, LOCALIZE_PATH_KEY, type LayoutChainContext, LayoutChainRenderer, Link, type LinkProps, type LinkTag, type MetaTag, PAGE_KEY, PageView, type PartyTownConfig, type ResolvedAppConfig, type RouteLocation, type RouteLocationRaw, SSR_KEY, type ScriptTrigger, type SearchFilters, type SearchResult, type SearchRuntimeConfig, type SeoMetadata, SlotView, TRANSITION_KEY, type TypedLinkProps, type UbeanAppInstance, type UbeanAppOptions, type UbeanVueContext, type UbeanVueOptions, type UbeanVuePage, type UbeanVuePageData, type UseAsyncDataOptions, type UseAsyncDataReturn, type UseCacheViewsReturn, type UsePageTransitionReturn, type UseReloadSignalReturn, type UseScriptOptions, type UseScriptReturn, type UseSearchOptions, type UseSearchReturn, type ViewTransitionOptions, type VueHeadClient$1 as VueHeadClient, type VueI18nInstance, _resetColorMode, _resetPartyTown, _resetSearch, _setPagefindMock, addLocale, applyAppConfig, clearCache, clearLocales, clearPageTransition, collectIslands, configureColorMode, configurePartyTown, configureSearch, createClientHead, createDataCacheStore, createDefaultAppConfig, createLinkHandler, createUbeanClientApp, createUbeanRouter, createUbeanSSRApp, createUseAsyncData, defineApp, defineDataKey, defineLocale, defineMeta, defineMiddleware, definePage, detectBrowserLocale, detectLocale, disablePageCache, enablePageCache, excludePageCache, executeSearch, extractLocaleFromPath, extractPageData, forceColorMode, getCacheEnabled, getCachedViewNames, getColorModeConfig, getColorModeScript, getDefaultLocale, getExcludedViewNames, getI18nConfig, getInitialPageData, getInitialState, getInvalidatedKeysForAction, getLocale, getLocaleDir, getLocaleName, getNamedPageWrapper, getNavigationType, getPageTransitionName, getPartyTownConfig, getPartyTownHeadContent, getPartyTownScript, getRegisteredLocales, getReloadCounter, getSearchConfig, hydrateIsland, hydrateIslands, includePageCache, initCachedViewsFromRoutes, initClientI18n, initPagefind, injectHead, invalidateCache, invalidatePageCache, isActiveRoute, isCacheEnabled, isPageCached, isPageExcluded, isPagefindLoaded, isPartyTownEnabled, isReloading, localizePath, mergeAppConfig, mergeLocale, onLocaleChange, reloadPage, resetNamedPageWrappers, resetRouteCache, resolveColorModeConfig, resolvePartyTownConfig, resolveRoute, resolveSearchConfig, setI18nConfig, setLocale, setPageTransition, supportsViewTransitions, switchLocalePath, t, ubeanVue, unforceColorMode, useCacheViews, useColorMode, useUnheadHead as useHead, useHeadInstance, useI18n, useLocalePath, usePage, usePageTransition, useReloadSignal, useRouter, useScript, useSearch, useSeoMeta, useServerData, useSwitchLocalePath, useViewTransition, useViewTransitionState, withViewTransition };
512
+ export { type AppPluginConfig, type ColorMode, type ColorModeConfig, type CreateUbeanRouterOptions, type DataCacheStore, type DataResult, type DefineAppOptions, ERROR_KEY, ErrorBoundary, Head, type VueHeadClient as HeadClient, type HydrateIslandsOptions, type I18nRuntimeConfig, type IslandHydrateOptions, type IslandHydrationScheduler, type IslandRecord, LAYOUT_CHAIN_KEY, LOADING_KEY, LOCALIZE_PATH_KEY, type LayoutChainContext, LayoutChainRenderer, Link, type LinkProps, type LinkTag, type LocaleLoader, type LocaleMessages, type MetaTag, PAGE_KEY, PageView, type PartyTownConfig, type ResolvedAppConfig, type RouteLocation, type RouteLocationRaw, SSR_KEY, type ScriptTrigger, type SearchFilters, type SearchResult, type SearchRuntimeConfig, type SeoMetadata, SlotView, TRANSITION_KEY, type TypedLinkProps, type UbeanAppInstance, type UbeanAppOptions, type UbeanVueContext, type UbeanVueOptions, type UbeanVuePage, type UbeanVuePageData, type UseAsyncDataOptions, type UseAsyncDataReturn, type UseCacheViewsReturn, type UseFetchOptions, type UsePageTransitionReturn, type UseReloadSignalReturn, type UseScriptOptions, type UseScriptReturn, type UseSearchOptions, type UseSearchReturn, type ViewTransitionOptions, type VueHeadClient$1 as VueHeadClient, type VueI18nInstance, _resetColorMode, _resetPartyTown, _resetSearch, _setPagefindMock, applyAppConfig, clearCache, clearPageTransition, collectIslands, configureColorMode, configureI18nRuntime, configurePartyTown, configureSearch, createClientHead, createDataCacheStore, createDefaultAppConfig, createLinkHandler, createUbeanClientApp, createUbeanI18n, createUbeanRouter, createUbeanSSRApp, createUseAsyncData, defineApp, defineDataKey, defineMeta, defineMiddleware, definePage, disablePageCache, enablePageCache, excludePageCache, executeSearch, extractLocaleFromPath, extractPageData, forceColorMode, getCacheEnabled, getCachedViewNames, getColorModeConfig, getColorModeScript, getExcludedViewNames, getI18nRuntimeConfig, getInitialPageData, getInitialState, getInvalidatedKeysForAction, getLocale, getNamedPageWrapper, getNavigationType, getPageTransitionName, getPartyTownConfig, getPartyTownHeadContent, getPartyTownScript, getReloadCounter, getSearchConfig, hasPendingIslands, hydrateIsland, hydrateIslands, includePageCache, initCachedViewsFromRoutes, initClientI18n, initPagefind, injectHead, installUbeanI18n, invalidateCache, invalidateData, invalidatePageCache, isActiveRoute, isCacheEnabled, isPageCached, isPageExcluded, isPagefindLoaded, isPartyTownEnabled, isReloading, localizePath, mergeAppConfig, reloadPage, resetNamedPageWrappers, resetRouteCache, resolveColorModeConfig, resolvePartyTownConfig, resolveRoute, resolveSearchConfig, scheduleIslandHydration, setDefaultFetch, setLocale, setPageTransition, supportsViewTransitions, switchLocalePath, t, ubeanVue, unforceColorMode, useAsyncData, useCacheViews, useColorMode, useData, useFetch, useUnheadHead as useHead, useHeadInstance, useI18n, useLocaleHead, useLocalePath, useLocaleRoute, usePage, usePageTransition, useReloadSignal, useRouter, useScript, useSearch, useSeoMeta, useServerData, useSwitchLocalePath, useViewTransition, useViewTransitionState, withViewTransition };
package/dist/index.js CHANGED
@@ -1,12 +1,13 @@
1
- import { A as getI18nConfig, B as setLocale, C as addLocale, D as detectLocale, E as detectBrowserLocale, F as initClientI18n, G as useSwitchLocalePath, H as t, I as localizePath, K as createUbeanRouter, L as mergeLocale, M as getLocaleDir, N as getLocaleName, O as extractLocaleFromPath, P as getRegisteredLocales, R as onLocaleChange, S as definePage, T as defineLocale, U as useI18n, V as switchLocalePath, W as useLocalePath, _ as useSeoMeta, b as defineMeta, j as getLocale, k as getDefaultLocale, m as createUbeanSSRApp, n as Head, p as createUbeanClientApp, q as usePage, v as useUnheadHead, w as clearLocales, x as defineMiddleware, z as setI18nConfig } from "./app-Bc1d5Svw.js";
1
+ import { C as createUbeanRouter, S as definePage, _ as useSeoMeta, b as defineMeta, m as createUbeanSSRApp, n as Head, p as createUbeanClientApp, v as useUnheadHead, w as usePage, x as defineMiddleware } from "./app-B6vVB7hp.js";
2
+ import { _ as useSwitchLocalePath, a as getI18nRuntimeConfig, c as installUbeanI18n, d as switchLocalePath, f as t, g as useLocaleRoute, h as useLocalePath, i as extractLocaleFromPath, l as localizePath, m as useLocaleHead, n as configureI18nRuntime, o as getLocale, p as useI18n, r as createUbeanI18n, s as initClientI18n, u as setLocale } from "./i18n-t0G3VyBs.js";
2
3
  import { applyAppConfig, createDefaultAppConfig, defineApp, mergeAppConfig } from "./define-app.js";
3
4
  import { computed, getCurrentInstance, markRaw, onMounted, onScopeDispose, onUnmounted, ref, shallowRef, watch } from "vue";
4
5
  import { useRouter } from "vue-router";
5
6
  import { ERROR_KEY, ErrorBoundary, LAYOUT_CHAIN_KEY, LOADING_KEY, LOCALIZE_PATH_KEY, LayoutChainRenderer, Link, PAGE_KEY, PageView, SSR_KEY, SlotView, TRANSITION_KEY, clearPageTransition, disablePageCache, enablePageCache, excludePageCache, getCacheEnabled, getCachedViewNames, getExcludedViewNames, getNamedPageWrapper, getNavigationType, getPageTransitionName, getReloadCounter, includePageCache, initCachedViewsFromRoutes, invalidatePageCache, isActiveRoute, isCacheEnabled, isPageCached, isPageExcluded, isReloading, reloadPage, resetNamedPageWrappers, resetRouteCache, resolveRoute, setPageTransition, supportsViewTransitions, ubeanVue, useCacheViews, usePageTransition, useReloadSignal, useViewTransition, useViewTransitionState, withViewTransition } from "@ubean/vue";
6
7
  import { injectHead, injectHead as injectHead$1 } from "@unhead/vue";
7
8
  import { createHead as createClientHead } from "@unhead/vue/client";
8
- import { defineDataKey as defineDataKey$1 } from "@ubean/pages";
9
- import { collectIslands, hydrateIsland, hydrateIslands } from "@ubean/islands/runtime";
9
+ import { defineDataKey as defineDataKey$1, invalidateData, setDefaultFetch, useAsyncData, useData, useFetch } from "@ubean/pages";
10
+ import { collectIslands, hasPendingIslands, hydrateIsland, hydrateIslands, scheduleIslandHydration } from "@ubean/islands/runtime";
10
11
  //#region src/client.ts
11
12
  const _global$1 = globalThis;
12
13
  function getInitialPageData() {
@@ -1021,4 +1022,4 @@ function _setPagefindMock(mock) {
1021
1022
  pagefindPromise = Promise.resolve(mock);
1022
1023
  }
1023
1024
  //#endregion
1024
- export { ERROR_KEY, ErrorBoundary, Head, LAYOUT_CHAIN_KEY, LOADING_KEY, LOCALIZE_PATH_KEY, LayoutChainRenderer, Link, PAGE_KEY, PageView, SSR_KEY, SlotView, TRANSITION_KEY, _resetColorMode, _resetPartyTown, _resetSearch, _setPagefindMock, addLocale, applyAppConfig, clearCache, clearLocales, clearPageTransition, collectIslands, configureColorMode, configurePartyTown, configureSearch, createClientHead, createDataCacheStore, createDefaultAppConfig, createLinkHandler, createUbeanClientApp, createUbeanRouter, createUbeanSSRApp, createUseAsyncData, defineApp, defineDataKey, defineLocale, defineMeta, defineMiddleware, definePage, detectBrowserLocale, detectLocale, disablePageCache, enablePageCache, excludePageCache, executeSearch, extractLocaleFromPath, extractPageData, forceColorMode, getCacheEnabled, getCachedViewNames, getColorModeConfig, getColorModeScript, getDefaultLocale, getExcludedViewNames, getI18nConfig, getInitialPageData, getInitialState, getInvalidatedKeysForAction, getLocale, getLocaleDir, getLocaleName, getNamedPageWrapper, getNavigationType, getPageTransitionName, getPartyTownConfig, getPartyTownHeadContent, getPartyTownScript, getRegisteredLocales, getReloadCounter, getSearchConfig, hydrateIsland, hydrateIslands, includePageCache, initCachedViewsFromRoutes, initClientI18n, initPagefind, injectHead, invalidateCache, invalidatePageCache, isActiveRoute, isCacheEnabled, isPageCached, isPageExcluded, isPagefindLoaded, isPartyTownEnabled, isReloading, localizePath, mergeAppConfig, mergeLocale, onLocaleChange, reloadPage, resetNamedPageWrappers, resetRouteCache, resolveColorModeConfig, resolvePartyTownConfig, resolveRoute, resolveSearchConfig, setI18nConfig, setLocale, setPageTransition, supportsViewTransitions, switchLocalePath, t, ubeanVue, unforceColorMode, useCacheViews, useColorMode, useUnheadHead as useHead, useHeadInstance, useI18n, useLocalePath, usePage, usePageTransition, useReloadSignal, useRouter, useScript, useSearch, useSeoMeta, useServerData, useSwitchLocalePath, useViewTransition, useViewTransitionState, withViewTransition };
1025
+ export { ERROR_KEY, ErrorBoundary, Head, LAYOUT_CHAIN_KEY, LOADING_KEY, LOCALIZE_PATH_KEY, LayoutChainRenderer, Link, PAGE_KEY, PageView, SSR_KEY, SlotView, TRANSITION_KEY, _resetColorMode, _resetPartyTown, _resetSearch, _setPagefindMock, applyAppConfig, clearCache, clearPageTransition, collectIslands, configureColorMode, configureI18nRuntime, configurePartyTown, configureSearch, createClientHead, createDataCacheStore, createDefaultAppConfig, createLinkHandler, createUbeanClientApp, createUbeanI18n, createUbeanRouter, createUbeanSSRApp, createUseAsyncData, defineApp, defineDataKey, defineMeta, defineMiddleware, definePage, disablePageCache, enablePageCache, excludePageCache, executeSearch, extractLocaleFromPath, extractPageData, forceColorMode, getCacheEnabled, getCachedViewNames, getColorModeConfig, getColorModeScript, getExcludedViewNames, getI18nRuntimeConfig, getInitialPageData, getInitialState, getInvalidatedKeysForAction, getLocale, getNamedPageWrapper, getNavigationType, getPageTransitionName, getPartyTownConfig, getPartyTownHeadContent, getPartyTownScript, getReloadCounter, getSearchConfig, hasPendingIslands, hydrateIsland, hydrateIslands, includePageCache, initCachedViewsFromRoutes, initClientI18n, initPagefind, injectHead, installUbeanI18n, invalidateCache, invalidateData, invalidatePageCache, isActiveRoute, isCacheEnabled, isPageCached, isPageExcluded, isPagefindLoaded, isPartyTownEnabled, isReloading, localizePath, mergeAppConfig, reloadPage, resetNamedPageWrappers, resetRouteCache, resolveColorModeConfig, resolvePartyTownConfig, resolveRoute, resolveSearchConfig, scheduleIslandHydration, setDefaultFetch, setLocale, setPageTransition, supportsViewTransitions, switchLocalePath, t, ubeanVue, unforceColorMode, useAsyncData, useCacheViews, useColorMode, useData, useFetch, useUnheadHead as useHead, useHeadInstance, useI18n, useLocaleHead, useLocalePath, useLocaleRoute, usePage, usePageTransition, useReloadSignal, useRouter, useScript, useSearch, useSeoMeta, useServerData, useSwitchLocalePath, useViewTransition, useViewTransitionState, withViewTransition };
package/dist/server.d.ts CHANGED
@@ -1,3 +1,3 @@
1
- import { A as CreateUbeanRouterOptions, C as useUnheadHead, D as definePage, E as defineMiddleware, O as UbeanVuePage, S as useSeoMeta, T as defineMeta, _ as VueHeadClient, a as LOCALIZE_PATH_KEY, b as ubeanVue, c as Link, d as SSR_KEY, f as SlotView, g as UbeanVueOptions, h as UbeanAppOptions, i as LOADING_KEY, j as createUbeanRouter, k as usePage, l as PAGE_KEY, m as UbeanAppInstance, n as Head, o as LayoutChainContext, p as TRANSITION_KEY, r as LAYOUT_CHAIN_KEY, s as LayoutChainRenderer, t as ERROR_KEY, u as PageView, v as createUbeanClientApp, w as useViewTransition, x as useRouter, y as createUbeanSSRApp } from "./app-BknjDM9j.js";
1
+ import { A as CreateUbeanRouterOptions, C as useUnheadHead, D as definePage, E as defineMiddleware, O as UbeanVuePage, S as useSeoMeta, T as defineMeta, _ as VueHeadClient, a as LOCALIZE_PATH_KEY, b as ubeanVue, c as Link, d as SSR_KEY, f as SlotView, g as UbeanVueOptions, h as UbeanAppOptions, i as LOADING_KEY, j as createUbeanRouter, k as usePage, l as PAGE_KEY, m as UbeanAppInstance, n as Head, o as LayoutChainContext, p as TRANSITION_KEY, r as LAYOUT_CHAIN_KEY, s as LayoutChainRenderer, t as ERROR_KEY, u as PageView, v as createUbeanClientApp, w as useViewTransition, x as useRouter, y as createUbeanSSRApp } from "./app-BP3eySMH.js";
2
2
  import { createHead as createServerHead } from "@unhead/vue/server";
3
3
  export { type CreateUbeanRouterOptions, ERROR_KEY, Head, LAYOUT_CHAIN_KEY, LOADING_KEY, LOCALIZE_PATH_KEY, type LayoutChainContext, LayoutChainRenderer, Link, PAGE_KEY, PageView, SSR_KEY, SlotView, TRANSITION_KEY, UbeanAppInstance, UbeanAppOptions, type UbeanVueOptions, type UbeanVuePage, type VueHeadClient, createServerHead, createUbeanClientApp, createUbeanRouter, createUbeanSSRApp, defineMeta, defineMiddleware, definePage, ubeanVue, useUnheadHead as useHead, usePage, useRouter, useSeoMeta, useViewTransition };
package/dist/server.js CHANGED
@@ -1,3 +1,3 @@
1
- import { K as createUbeanRouter, S as definePage, _ as useSeoMeta, a as LOCALIZE_PATH_KEY, b as defineMeta, c as PAGE_KEY, d as SlotView, f as TRANSITION_KEY, g as useRouter, h as ubeanVue, i as LOADING_KEY, l as PageView, m as createUbeanSSRApp, n as Head, o as LayoutChainRenderer, p as createUbeanClientApp, q as usePage, r as LAYOUT_CHAIN_KEY, s as Link, t as ERROR_KEY, u as SSR_KEY, v as useUnheadHead, x as defineMiddleware, y as useViewTransition } from "./app-Bc1d5Svw.js";
1
+ import { C as createUbeanRouter, S as definePage, _ as useSeoMeta, a as LOCALIZE_PATH_KEY, b as defineMeta, c as PAGE_KEY, d as SlotView, f as TRANSITION_KEY, g as useRouter, h as ubeanVue, i as LOADING_KEY, l as PageView, m as createUbeanSSRApp, n as Head, o as LayoutChainRenderer, p as createUbeanClientApp, r as LAYOUT_CHAIN_KEY, s as Link, t as ERROR_KEY, u as SSR_KEY, v as useUnheadHead, w as usePage, x as defineMiddleware, y as useViewTransition } from "./app-B6vVB7hp.js";
2
2
  import { createHead as createServerHead } from "@unhead/vue/server";
3
3
  export { ERROR_KEY, Head, LAYOUT_CHAIN_KEY, LOADING_KEY, LOCALIZE_PATH_KEY, LayoutChainRenderer, Link, PAGE_KEY, PageView, SSR_KEY, SlotView, TRANSITION_KEY, createServerHead, createUbeanClientApp, createUbeanRouter, createUbeanSSRApp, defineMeta, defineMiddleware, definePage, ubeanVue, useUnheadHead as useHead, usePage, useRouter, useSeoMeta, useViewTransition };
package/dist/ssr.d.ts ADDED
@@ -0,0 +1,32 @@
1
+ import { ResolvedAppConfig } from "./define-app.js";
2
+ import { Component } from "vue";
3
+ import { RouteRecordRaw } from "vue-router";
4
+ import { PageRenderer } from "@ubean/pages";
5
+ import { renderToString } from "@vue/server-renderer";
6
+ //#region src/ssr.d.ts
7
+ interface VueRendererSimpleOptions {
8
+ resolvePageComponent: (path: string) => Promise<Component | null>;
9
+ resolveLayoutComponent: (name: string | false | null | undefined) => Promise<Component | null>;
10
+ defaultLayout?: string | null;
11
+ /**
12
+ * Resolves the user's `defineApp` config for the server side.
13
+ * When provided, plugins / globalComponents / provides / head / onAppCreated
14
+ * will be applied to each SSR app instance.
15
+ */
16
+ resolveAppConfig?: () => ResolvedAppConfig | Promise<ResolvedAppConfig>;
17
+ }
18
+ interface VueRendererRouterOptions {
19
+ routes: RouteRecordRaw[];
20
+ resolveLayoutComponent: (name: string | false | null | undefined) => Promise<Component | null>;
21
+ defaultLayout?: string | null;
22
+ /**
23
+ * Resolves the user's `defineApp` config for the server side.
24
+ * When provided, plugins / globalComponents / provides / head / onAppCreated
25
+ * will be applied to each SSR app instance.
26
+ */
27
+ resolveAppConfig?: () => ResolvedAppConfig | Promise<ResolvedAppConfig>;
28
+ }
29
+ type VueRendererOptions = VueRendererSimpleOptions | VueRendererRouterOptions;
30
+ declare function createVueRenderer(options: VueRendererOptions): PageRenderer;
31
+ //#endregion
32
+ export { VueRendererOptions, VueRendererRouterOptions, VueRendererSimpleOptions, createVueRenderer, renderToString };
package/dist/ssr.js ADDED
@@ -0,0 +1,267 @@
1
+ import { r as createUbeanI18n } from "./i18n-t0G3VyBs.js";
2
+ import { applyAppConfig } from "./define-app.js";
3
+ import { createSSRApp, defineComponent, h, provide, reactive } from "vue";
4
+ import { SSR_CONTENT_MARKER, STATE_DATA_ID, STATE_MARKER, __clearDataPayload, __clearDeferred, __resolveDataPayload, __resolveDeferred, __serializeDataPayload, __serializeDeferred, safeJsonStringify } from "@ubean/pages";
5
+ import { buildLocaleHead } from "@ubean/i18n/browser";
6
+ import { createHead, renderSSRHead, transformHtmlTemplate } from "@unhead/vue/server";
7
+ import { renderToNodeStream, renderToString, renderToString as renderToString$1 } from "@vue/server-renderer";
8
+ import { getIslandsBootstrapScript } from "@ubean/islands";
9
+ //#region src/ssr.ts
10
+ /**
11
+ * Push a `PageHead` (from `defineApp` or page-level `head`) into an @unhead head instance.
12
+ * Extracted so both the global app head and per-page head use the same mapping.
13
+ */
14
+ function pushPageHead(head, pageHead) {
15
+ const headInput = {};
16
+ if (pageHead.title) headInput.title = pageHead.title;
17
+ if (pageHead.htmlAttrs) headInput.htmlAttrs = pageHead.htmlAttrs;
18
+ if (pageHead.bodyAttrs) headInput.bodyAttrs = pageHead.bodyAttrs;
19
+ if (pageHead.meta) headInput.meta = pageHead.meta;
20
+ if (pageHead.link) headInput.link = pageHead.link;
21
+ if (pageHead.script) headInput.script = pageHead.script;
22
+ head.push(headInput);
23
+ }
24
+ /**
25
+ * Apply user's `defineApp` config (plugins, globalComponents, provides) and
26
+ * invoke `onAppCreated` on a freshly created SSR app instance.
27
+ */
28
+ async function applyServerAppConfig(app, appConfig) {
29
+ applyAppConfig(app, appConfig, "server");
30
+ if (appConfig.onAppCreated) await appConfig.onAppCreated(app);
31
+ }
32
+ function isSimpleOptions(opts) {
33
+ return "resolvePageComponent" in opts;
34
+ }
35
+ /**
36
+ * Resolve a single layout component by name.
37
+ * Returns `null` when layout is `false`, `null`, or not found.
38
+ * ubean uses flat single-layer layout (no nesting), matching the
39
+ * `layout: string | false` field on each page route.
40
+ */
41
+ async function resolveSingleLayout(layoutName, defaultLayout, resolveLayoutComponent) {
42
+ const resolved = layoutName === void 0 ? defaultLayout : layoutName;
43
+ if (resolved === false || resolved == null) return null;
44
+ return resolveLayoutComponent(resolved);
45
+ }
46
+ function createSimpleApp(pageObj, PageComp, layout, head) {
47
+ const pageData = reactive({ ...pageObj });
48
+ const PAGE_KEY = Symbol("ubean-page");
49
+ const RootComponent = defineComponent({
50
+ name: "UbeanSimpleApp",
51
+ setup() {
52
+ provide(PAGE_KEY, pageData);
53
+ return () => {
54
+ const inner = h(PageComp, pageObj.props || {});
55
+ if (!layout) return inner;
56
+ return h(layout, {}, { default: () => inner });
57
+ };
58
+ }
59
+ });
60
+ const app = createSSRApp(RootComponent);
61
+ app.use(head);
62
+ return app;
63
+ }
64
+ /**
65
+ * Shared setup for both buffered and streaming renders: creates the head
66
+ * instance, pushes static head (defineApp + locale + page), builds the Vue
67
+ * SSR app, and returns everything the render methods need.
68
+ */
69
+ async function prepareRender(options, pageObj, renderContext) {
70
+ const head = createHead();
71
+ const appConfig = options.resolveAppConfig ? await options.resolveAppConfig() : null;
72
+ if (appConfig?.head) pushPageHead(head, appConfig.head);
73
+ if (renderContext?.locale) {
74
+ const routing = renderContext.routing;
75
+ if (routing && renderContext.availableLocales?.length) {
76
+ const tags = buildLocaleHead({
77
+ path: pageObj.url.split("?")[0] || "/",
78
+ locale: renderContext.locale,
79
+ locales: renderContext.availableLocales,
80
+ routing,
81
+ baseUrl: renderContext.baseUrl
82
+ });
83
+ head.push({
84
+ htmlAttrs: tags.htmlAttrs,
85
+ link: tags.link,
86
+ meta: tags.meta
87
+ });
88
+ } else head.push({ htmlAttrs: {
89
+ lang: renderContext.locale,
90
+ dir: renderContext.localeDir || "ltr"
91
+ } });
92
+ }
93
+ if (pageObj.head) pushPageHead(head, pageObj.head);
94
+ const i18n = renderContext?.locale ? createUbeanI18n({
95
+ locale: renderContext.locale,
96
+ fallbackLocale: renderContext.fallbackLocale || renderContext.locale,
97
+ messages: {
98
+ [renderContext.locale]: renderContext.messages || {},
99
+ ...renderContext.fallbackLocale && renderContext.fallbackMessages && renderContext.fallbackLocale !== renderContext.locale ? { [renderContext.fallbackLocale]: renderContext.fallbackMessages } : {}
100
+ }
101
+ }) : void 0;
102
+ let renderApp;
103
+ if (isSimpleOptions(options)) {
104
+ const pageComponent = await options.resolvePageComponent(pageObj.component);
105
+ if (!pageComponent) throw new Error(`[ubean] Could not resolve page component: ${pageObj.component}`);
106
+ const app = createSimpleApp(pageObj, pageComponent, pageObj.layout === false ? null : await resolveSingleLayout(pageObj.layout, options.defaultLayout || null, options.resolveLayoutComponent), head);
107
+ if (i18n) app.use(i18n);
108
+ if (appConfig) await applyServerAppConfig(app, appConfig);
109
+ renderApp = app;
110
+ } else {
111
+ const { createUbeanSSRApp } = await import("@ubean/client/app");
112
+ const { app: createdApp, router } = createUbeanSSRApp(pageObj, {
113
+ routes: options.routes,
114
+ resolveLayoutComponent: options.resolveLayoutComponent,
115
+ defaultLayout: options.defaultLayout,
116
+ head,
117
+ i18n
118
+ });
119
+ if (appConfig) await applyServerAppConfig(createdApp, appConfig);
120
+ await router.isReady();
121
+ renderApp = createdApp;
122
+ }
123
+ return {
124
+ app: renderApp,
125
+ head,
126
+ appConfig
127
+ };
128
+ }
129
+ /**
130
+ * Serialize SSR state (e.g. Pinia state) from the app instance after render.
131
+ * Returns `undefined` when there is no state or serialization fails.
132
+ */
133
+ async function resolveState(app, appConfig) {
134
+ if (!appConfig?.serializeState) return void 0;
135
+ try {
136
+ const serialized = await appConfig.serializeState(app);
137
+ if (serialized && Object.keys(serialized).length > 0) return serialized;
138
+ } catch (err) {
139
+ console.error("[ubean-ssr] serializeState failed:", err);
140
+ }
141
+ }
142
+ /**
143
+ * P9-24: Collect dynamic head tags that were added during streaming.
144
+ *
145
+ * During streaming SSR, the initial `<head>` is sent before the Vue app
146
+ * renders. Any `useHead()` / `useSeoMeta()` calls inside component setup
147
+ * push entries to the head instance *after* the initial head was sent.
148
+ *
149
+ * This function compares the head state before and after streaming to
150
+ * find tags that were added dynamically, and returns them as an HTML string
151
+ * that can be injected before the stream closes.
152
+ *
153
+ * This ensures SEO crawlers and social media bots see the complete metadata
154
+ * (title, og:tags, etc.) without waiting for client hydration.
155
+ */
156
+ function collectDynamicHeadTags(head, staticHeadTags) {
157
+ const fullTags = renderSSRHead(head).headTags || "";
158
+ if (!fullTags || fullTags === staticHeadTags) return "";
159
+ const staticLines = new Set(staticHeadTags.split("\n").map((l) => l.trim()).filter(Boolean));
160
+ const dynamicLines = fullTags.split("\n").map((l) => l.trim()).filter((l) => Boolean(l) && !staticLines.has(l));
161
+ if (dynamicLines.length === 0) return "";
162
+ return dynamicLines.join("\n");
163
+ }
164
+ function createVueRenderer(options) {
165
+ const render = async (pageObj, shellHtml, _assetTags, renderContext) => {
166
+ __clearDeferred();
167
+ __clearDataPayload();
168
+ const { app, head, appConfig } = await prepareRender(options, pageObj, renderContext);
169
+ const appHtml = await renderToString$1(app);
170
+ const state = await resolveState(app, appConfig);
171
+ const deferredData = await __resolveDeferred();
172
+ const deferredScript = __serializeDeferred(deferredData);
173
+ const dataPayload = __resolveDataPayload();
174
+ const dataScript = __serializeDataPayload(dataPayload);
175
+ if (!shellHtml) {
176
+ const tail = (dataScript ? dataScript : "") + (deferredScript ? deferredScript : "");
177
+ return tail ? appHtml + tail : appHtml;
178
+ }
179
+ const htmlWithApp = shellHtml.replace(SSR_CONTENT_MARKER, appHtml);
180
+ let html = transformHtmlTemplate(head, htmlWithApp);
181
+ const preStateScripts = (dataScript ? `${dataScript}\n ` : "") + (deferredScript ? `${deferredScript}\n ` : "");
182
+ if (preStateScripts) html = html.replace(new RegExp(`(<script id="${STATE_DATA_ID}")`), `${preStateScripts}$1`);
183
+ if (state) return {
184
+ html,
185
+ state
186
+ };
187
+ return html;
188
+ };
189
+ /**
190
+ * Streaming SSR: 返回一个 ReadableStream<Uint8Array>,将 HTML 文档分块流式输出。
191
+ *
192
+ * 与 `render` 的关键差异:
193
+ * - 使用 Vue 的 `renderToStream` 替代 `renderToString`,app HTML 边渲染边输出
194
+ * - 静态 head(defineApp + definePage)在流式开始前注入 `<head>`
195
+ * - SSR state(Pinia 等)在 app 流结束后、tail 之前注入为 `<script>`(移到 app div 之后,
196
+ * 因为 state 只有渲染完成后才可用;客户端通过 getElementById 读取,位置无关)
197
+ * - P9-24: 动态 `useHead()`(组件 setup 内)在 app 流结束后收集,作为 late head tags
198
+ * 注入到 tail 之前。浏览器会将 `<meta>`/`<title>`/`<link>` 标签自动移入 `<head>`,
199
+ * 确保 SEO 爬虫和社交机器人看到完整 metadata,无需等待客户端水合。
200
+ */
201
+ const renderToStreamFn = (pageObj, shellHtml, _assetTags, renderContext) => {
202
+ const encoder = new TextEncoder();
203
+ async function pumpVueStream(vueStream, controller) {
204
+ for await (const chunk of vueStream) {
205
+ const text = typeof chunk === "string" ? chunk : new TextDecoder().decode(chunk);
206
+ controller.enqueue(encoder.encode(text));
207
+ }
208
+ }
209
+ return new ReadableStream({ async start(controller) {
210
+ try {
211
+ __clearDeferred();
212
+ __clearDataPayload();
213
+ const { app, head, appConfig } = await prepareRender(options, pageObj, renderContext);
214
+ if (!shellHtml) {
215
+ await pumpVueStream(renderToNodeStream(app), controller);
216
+ const dataPayload = __resolveDataPayload();
217
+ const dataScript = __serializeDataPayload(dataPayload);
218
+ if (dataScript) controller.enqueue(encoder.encode(dataScript));
219
+ const deferredData = await __resolveDeferred();
220
+ const deferredScript = __serializeDeferred(deferredData);
221
+ if (deferredScript) controller.enqueue(encoder.encode(deferredScript));
222
+ controller.close();
223
+ return;
224
+ }
225
+ const markerIdx = shellHtml.indexOf(SSR_CONTENT_MARKER);
226
+ if (markerIdx === -1) {
227
+ await pumpVueStream(renderToNodeStream(app), controller);
228
+ const dataPayload = __resolveDataPayload();
229
+ const dataScript = __serializeDataPayload(dataPayload);
230
+ if (dataScript) controller.enqueue(encoder.encode(dataScript));
231
+ const deferredData = await __resolveDeferred();
232
+ const deferredScript = __serializeDeferred(deferredData);
233
+ if (deferredScript) controller.enqueue(encoder.encode(deferredScript));
234
+ controller.close();
235
+ return;
236
+ }
237
+ let headPart = shellHtml.slice(0, markerIdx);
238
+ const tailPart = shellHtml.slice(markerIdx + SSR_CONTENT_MARKER.length);
239
+ const stateScriptRegex = new RegExp(`<script id="${STATE_DATA_ID}" type="application/json">${STATE_MARKER}<\/script>`);
240
+ headPart = headPart.replace(stateScriptRegex, "");
241
+ headPart = transformHtmlTemplate(head, headPart);
242
+ const staticHeadTags = renderSSRHead(head).headTags || "";
243
+ controller.enqueue(encoder.encode(headPart));
244
+ await pumpVueStream(renderToNodeStream(app), controller);
245
+ const dynamicHeadTags = collectDynamicHeadTags(head, staticHeadTags);
246
+ const dataPayload = __resolveDataPayload();
247
+ const dataScript = __serializeDataPayload(dataPayload);
248
+ const deferredData = await __resolveDeferred();
249
+ const deferredScript = __serializeDeferred(deferredData);
250
+ const state = await resolveState(app, appConfig);
251
+ const stateScript = `<script id="${STATE_DATA_ID}" type="application/json">${state ? safeJsonStringify(state) : ""}<\/script>`;
252
+ const tailContent = (dynamicHeadTags ? `${dynamicHeadTags}\n` : "") + (dataScript ? `${dataScript}\n ` : "") + (deferredScript ? `${deferredScript}\n ` : "") + stateScript + tailPart;
253
+ controller.enqueue(encoder.encode(tailContent));
254
+ controller.close();
255
+ } catch (err) {
256
+ controller.error(err);
257
+ }
258
+ } });
259
+ };
260
+ return {
261
+ render,
262
+ renderToStream: renderToStreamFn,
263
+ preambleScript: getIslandsBootstrapScript()
264
+ };
265
+ }
266
+ //#endregion
267
+ export { createVueRenderer, renderToString };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ubean/client",
3
- "version": "0.2.2",
3
+ "version": "0.3.0",
4
4
  "description": "Framework client runtime for ubean — layered over the lean @ubean/vue kernel, adding app factories, unhead, i18n, data layer, islands hydration and SSR state helpers",
5
5
  "files": [
6
6
  "dist"
@@ -25,17 +25,23 @@
25
25
  "./server": {
26
26
  "types": "./dist/server.d.ts",
27
27
  "import": "./dist/server.js"
28
+ },
29
+ "./ssr": {
30
+ "types": "./dist/ssr.d.ts",
31
+ "import": "./dist/ssr.js"
28
32
  }
29
33
  },
30
34
  "dependencies": {
31
- "@unhead/vue": "^3.3.2",
35
+ "@unhead/vue": "^3.4.0",
36
+ "@vue/server-renderer": "^3.5.41",
32
37
  "vue": "^3.5.41",
38
+ "vue-i18n": "11.4.8",
33
39
  "vue-router": "^5.2.0",
34
- "@ubean/islands": "0.2.2",
35
- "@ubean/i18n": "0.2.2",
36
- "@ubean/pages": "0.2.2",
37
- "@ubean/shared": "0.2.2",
38
- "@ubean/vue": "0.2.2"
40
+ "@ubean/pages": "0.3.0",
41
+ "@ubean/i18n": "0.3.0",
42
+ "@ubean/shared": "0.3.0",
43
+ "@ubean/islands": "0.3.0",
44
+ "@ubean/vue": "0.3.0"
39
45
  },
40
46
  "devDependencies": {
41
47
  "@types/node": "^26.2.0",
@@ -43,13 +49,16 @@
43
49
  "vite-plus": "0.2.9"
44
50
  },
45
51
  "peerDependencies": {
46
- "@ubean/seo": "0.2.2"
52
+ "@ubean/seo": "0.3.0"
47
53
  },
48
54
  "peerDependenciesMeta": {
49
55
  "@ubean/seo": {
50
56
  "optional": true
51
57
  }
52
58
  },
59
+ "engines": {
60
+ "node": ">=22"
61
+ },
53
62
  "scripts": {
54
63
  "build": "vp pack",
55
64
  "dev": "vp pack --watch",