@ubean/client 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/dist/app-Bc1d5Svw.js +433 -0
- package/dist/app-BknjDM9j.d.ts +101 -0
- package/dist/app.d.ts +2 -0
- package/dist/app.js +2 -0
- package/dist/define-app.d.ts +138 -0
- package/dist/define-app.js +97 -0
- package/dist/index.d.ts +486 -0
- package/dist/index.js +1024 -0
- package/dist/server.d.ts +3 -0
- package/dist/server.js +3 -0
- package/package.json +59 -0
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { App, Component, Plugin } from "vue";
|
|
2
|
+
import { Router } from "vue-router";
|
|
3
|
+
import { ViewTransitionOptions } from "@ubean/vue";
|
|
4
|
+
import { PageHead } from "@ubean/shared";
|
|
5
|
+
//#region src/define-app.d.ts
|
|
6
|
+
interface AppPluginConfig {
|
|
7
|
+
plugin: Plugin | [Plugin, ...any[]];
|
|
8
|
+
mode?: 'all' | 'client' | 'server';
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Router 钩子配置 — 暴露 vue-router 的导航守卫注册入口。
|
|
12
|
+
*
|
|
13
|
+
* `setup(router)` 在 router 实例创建后、`app.use(router)` 之前调用,
|
|
14
|
+
* 因此守卫能拦截首次导航(包括 SSR 的初始 URL push)。
|
|
15
|
+
*
|
|
16
|
+
* Client 和 SSR 都会执行,适合做鉴权、埋点、进度条等逻辑。
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* ```ts
|
|
20
|
+
* defineApp({
|
|
21
|
+
* router: {
|
|
22
|
+
* setup(router) {
|
|
23
|
+
* router.beforeEach((to, from) => {
|
|
24
|
+
* if (to.meta.requiresAuth && !isAuthenticated()) {
|
|
25
|
+
* return '/login';
|
|
26
|
+
* }
|
|
27
|
+
* });
|
|
28
|
+
* router.afterEach((to) => {
|
|
29
|
+
* analytics.track('page_view', { path: to.fullPath });
|
|
30
|
+
* });
|
|
31
|
+
* }
|
|
32
|
+
* }
|
|
33
|
+
* });
|
|
34
|
+
* ```
|
|
35
|
+
*/
|
|
36
|
+
interface RouterConfig {
|
|
37
|
+
/**
|
|
38
|
+
* 在 router 创建后、初始导航前调用。
|
|
39
|
+
* 用于注册 `beforeEach` / `beforeResolve` / `afterEach` 等守卫。
|
|
40
|
+
*
|
|
41
|
+
* 注意:setup 必须同步注册守卫(虽然守卫本身可以返回 Promise)。
|
|
42
|
+
* 不要在 setup 中执行异步操作(如请求 API),因为这会延迟首次导航。
|
|
43
|
+
*/
|
|
44
|
+
setup?: (router: Router) => void;
|
|
45
|
+
}
|
|
46
|
+
interface DefineAppOptions {
|
|
47
|
+
plugins?: Array<Plugin | [Plugin, ...any[]] | AppPluginConfig>;
|
|
48
|
+
globalComponents?: Record<string, Component>;
|
|
49
|
+
provides?: Record<string | symbol, unknown>;
|
|
50
|
+
head?: PageHead;
|
|
51
|
+
rootId?: string;
|
|
52
|
+
rootAttrs?: Record<string, string>;
|
|
53
|
+
/**
|
|
54
|
+
* 路由钩子配置,用于注册 vue-router 的导航守卫。
|
|
55
|
+
* 在 Client 和 SSR 都会执行。
|
|
56
|
+
*/
|
|
57
|
+
router?: RouterConfig;
|
|
58
|
+
onAppCreated?: (app: App) => void | Promise<void>;
|
|
59
|
+
onClientReady?: (app: App) => void | Promise<void>;
|
|
60
|
+
errorComponent?: Component;
|
|
61
|
+
loadingComponent?: Component;
|
|
62
|
+
viewTransitions?: boolean | ViewTransitionOptions;
|
|
63
|
+
/**
|
|
64
|
+
* SSR 专用:在 `renderToString(app)` 完成后调用,从 app 实例提取需要
|
|
65
|
+
* 序列化到客户端的状态(如 Pinia 的 `pinia.state.value`)。
|
|
66
|
+
*
|
|
67
|
+
* 返回的对象会被 `safeJsonStringify` 序列化并注入到 HTML 的
|
|
68
|
+
* `__UBEAN_STATE__` script 标签中。
|
|
69
|
+
*
|
|
70
|
+
* 仅在 server mode 下生效;client mode 下被忽略。
|
|
71
|
+
*
|
|
72
|
+
* @example
|
|
73
|
+
* ```ts
|
|
74
|
+
* import { createPinia } from 'pinia';
|
|
75
|
+
* defineApp({
|
|
76
|
+
* plugins: [createPinia()],
|
|
77
|
+
* serializeState: (app) => ({
|
|
78
|
+
* pinia: app.config.globalProperties.$pinia.state.value
|
|
79
|
+
* })
|
|
80
|
+
* });
|
|
81
|
+
* ```
|
|
82
|
+
*/
|
|
83
|
+
serializeState?: (app: App) => Record<string, unknown> | Promise<Record<string, unknown>>;
|
|
84
|
+
/**
|
|
85
|
+
* Client 专用:在 `applyAppConfig`(注册插件/provide)之后、`app.mount()` 之前调用,
|
|
86
|
+
* 用于从 `__UBEAN_STATE__` 反序列化的状态回填到 app(如 Pinia 的水合)。
|
|
87
|
+
*
|
|
88
|
+
* `state` 即 `serializeState` 在服务端返回的对象;若服务端未配置 `serializeState`
|
|
89
|
+
* 或返回空,`state` 为 `null`。
|
|
90
|
+
*
|
|
91
|
+
* 仅在 client mode 下生效;server mode 下被忽略。
|
|
92
|
+
*
|
|
93
|
+
* @example
|
|
94
|
+
* ```ts
|
|
95
|
+
* import { createPinia } from 'pinia';
|
|
96
|
+
* defineApp({
|
|
97
|
+
* plugins: [createPinia()],
|
|
98
|
+
* hydrateState: (app, state) => {
|
|
99
|
+
* if (state?.pinia) {
|
|
100
|
+
* app.config.globalProperties.$pinia.state.value = state.pinia;
|
|
101
|
+
* }
|
|
102
|
+
* }
|
|
103
|
+
* });
|
|
104
|
+
* ```
|
|
105
|
+
*/
|
|
106
|
+
hydrateState?: (app: App, state: Record<string, unknown> | null) => void;
|
|
107
|
+
}
|
|
108
|
+
interface ResolvedAppConfig {
|
|
109
|
+
plugins: AppPluginConfig[];
|
|
110
|
+
globalComponents: Record<string, Component>;
|
|
111
|
+
provides: Record<string | symbol, unknown>;
|
|
112
|
+
head?: PageHead;
|
|
113
|
+
rootId: string;
|
|
114
|
+
rootAttrs: Record<string, string>;
|
|
115
|
+
router?: RouterConfig;
|
|
116
|
+
onAppCreated?: (app: App) => void | Promise<void>;
|
|
117
|
+
onClientReady?: (app: App) => void | Promise<void>;
|
|
118
|
+
errorComponent?: Component;
|
|
119
|
+
loadingComponent?: Component;
|
|
120
|
+
viewTransitions?: boolean | ViewTransitionOptions;
|
|
121
|
+
serializeState?: (app: App) => Record<string, unknown> | Promise<Record<string, unknown>>;
|
|
122
|
+
hydrateState?: (app: App, state: Record<string, unknown> | null) => void;
|
|
123
|
+
}
|
|
124
|
+
declare function defineApp(options: DefineAppOptions): ResolvedAppConfig;
|
|
125
|
+
declare function applyAppConfig(app: App, config: ResolvedAppConfig, mode: 'client' | 'server'): void;
|
|
126
|
+
declare function createDefaultAppConfig(): ResolvedAppConfig;
|
|
127
|
+
/**
|
|
128
|
+
* 从多个 `ResolvedAppConfig` 合并出一个最终配置(shared + client/server)。
|
|
129
|
+
*
|
|
130
|
+
* `serializeState` 与 `hydrateState` 的合并语义:server 配置的 `serializeState` 优先,
|
|
131
|
+
* client 配置的 `hydrateState` 优先;shared 配置作为默认值。
|
|
132
|
+
*
|
|
133
|
+
* 这个函数在 `virtual:ubean-app` 虚拟模块中以 JS 字符串形式重新实现
|
|
134
|
+
* (`_mergeAppConfig`),保持两者语义一致。
|
|
135
|
+
*/
|
|
136
|
+
declare function mergeAppConfig(base: ResolvedAppConfig, ...configs: (ResolvedAppConfig | null | undefined)[]): ResolvedAppConfig;
|
|
137
|
+
//#endregion
|
|
138
|
+
export { AppPluginConfig, DefineAppOptions, ResolvedAppConfig, RouterConfig, applyAppConfig, createDefaultAppConfig, defineApp, mergeAppConfig };
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
//#region src/define-app.ts
|
|
2
|
+
function defineApp(options) {
|
|
3
|
+
const plugins = [];
|
|
4
|
+
if (options.plugins) for (const p of options.plugins) if (Array.isArray(p)) plugins.push({
|
|
5
|
+
plugin: p,
|
|
6
|
+
mode: "all"
|
|
7
|
+
});
|
|
8
|
+
else if (typeof p === "object" && "plugin" in p) plugins.push(p);
|
|
9
|
+
else plugins.push({
|
|
10
|
+
plugin: p,
|
|
11
|
+
mode: "all"
|
|
12
|
+
});
|
|
13
|
+
return {
|
|
14
|
+
plugins,
|
|
15
|
+
globalComponents: options.globalComponents || {},
|
|
16
|
+
provides: options.provides || {},
|
|
17
|
+
head: options.head,
|
|
18
|
+
rootId: options.rootId || "app",
|
|
19
|
+
rootAttrs: options.rootAttrs || {},
|
|
20
|
+
router: options.router,
|
|
21
|
+
onAppCreated: options.onAppCreated,
|
|
22
|
+
onClientReady: options.onClientReady,
|
|
23
|
+
errorComponent: options.errorComponent,
|
|
24
|
+
loadingComponent: options.loadingComponent,
|
|
25
|
+
viewTransitions: options.viewTransitions,
|
|
26
|
+
serializeState: options.serializeState,
|
|
27
|
+
hydrateState: options.hydrateState
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
function applyAppConfig(app, config, mode) {
|
|
31
|
+
for (const { plugin, mode: pluginMode = "all" } of config.plugins) if (pluginMode === "all" || pluginMode === mode) {
|
|
32
|
+
if (Array.isArray(plugin)) app.use(plugin[0], ...plugin.slice(1));
|
|
33
|
+
else app.use(plugin);
|
|
34
|
+
}
|
|
35
|
+
for (const [name, comp] of Object.entries(config.globalComponents)) app.component(name, comp);
|
|
36
|
+
for (const [key, value] of Object.entries(config.provides)) app.provide(key, value);
|
|
37
|
+
}
|
|
38
|
+
function createDefaultAppConfig() {
|
|
39
|
+
return {
|
|
40
|
+
plugins: [],
|
|
41
|
+
globalComponents: {},
|
|
42
|
+
provides: {},
|
|
43
|
+
rootId: "app",
|
|
44
|
+
rootAttrs: {}
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* 从多个 `ResolvedAppConfig` 合并出一个最终配置(shared + client/server)。
|
|
49
|
+
*
|
|
50
|
+
* `serializeState` 与 `hydrateState` 的合并语义:server 配置的 `serializeState` 优先,
|
|
51
|
+
* client 配置的 `hydrateState` 优先;shared 配置作为默认值。
|
|
52
|
+
*
|
|
53
|
+
* 这个函数在 `virtual:ubean-app` 虚拟模块中以 JS 字符串形式重新实现
|
|
54
|
+
* (`_mergeAppConfig`),保持两者语义一致。
|
|
55
|
+
*/
|
|
56
|
+
function mergeAppConfig(base, ...configs) {
|
|
57
|
+
const result = { ...base };
|
|
58
|
+
for (const cfg of configs) {
|
|
59
|
+
if (!cfg) continue;
|
|
60
|
+
if (cfg.plugins) result.plugins = [...result.plugins || [], ...cfg.plugins];
|
|
61
|
+
if (cfg.globalComponents) result.globalComponents = {
|
|
62
|
+
...result.globalComponents,
|
|
63
|
+
...cfg.globalComponents
|
|
64
|
+
};
|
|
65
|
+
if (cfg.provides) result.provides = {
|
|
66
|
+
...result.provides,
|
|
67
|
+
...cfg.provides
|
|
68
|
+
};
|
|
69
|
+
if (cfg.head) result.head = {
|
|
70
|
+
...result.head,
|
|
71
|
+
...cfg.head
|
|
72
|
+
};
|
|
73
|
+
if (cfg.rootId) result.rootId = cfg.rootId;
|
|
74
|
+
if (cfg.rootAttrs) result.rootAttrs = {
|
|
75
|
+
...result.rootAttrs,
|
|
76
|
+
...cfg.rootAttrs
|
|
77
|
+
};
|
|
78
|
+
if (cfg.onAppCreated) result.onAppCreated = cfg.onAppCreated;
|
|
79
|
+
if (cfg.onClientReady) result.onClientReady = cfg.onClientReady;
|
|
80
|
+
if (cfg.errorComponent) result.errorComponent = cfg.errorComponent;
|
|
81
|
+
if (cfg.loadingComponent) result.loadingComponent = cfg.loadingComponent;
|
|
82
|
+
if (cfg.viewTransitions !== void 0) result.viewTransitions = cfg.viewTransitions;
|
|
83
|
+
if (cfg.serializeState) result.serializeState = cfg.serializeState;
|
|
84
|
+
if (cfg.hydrateState) result.hydrateState = cfg.hydrateState;
|
|
85
|
+
if (cfg.router?.setup) {
|
|
86
|
+
const prevSetup = result.router?.setup;
|
|
87
|
+
if (prevSetup) result.router = { setup: (router) => {
|
|
88
|
+
prevSetup(router);
|
|
89
|
+
cfg.router.setup(router);
|
|
90
|
+
} };
|
|
91
|
+
else result.router = { setup: cfg.router.setup };
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return result;
|
|
95
|
+
}
|
|
96
|
+
//#endregion
|
|
97
|
+
export { applyAppConfig, createDefaultAppConfig, defineApp, mergeAppConfig };
|