@route-forge/vue 2.2.1 → 3.1.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/README.md +30 -8
- package/README_zh.md +30 -8
- package/dist/index.cjs +127 -31
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +59 -15
- package/dist/index.d.ts +59 -15
- package/dist/index.js +127 -32
- package/dist/index.js.map +1 -1
- package/package.json +6 -4
package/README.md
CHANGED
|
@@ -26,9 +26,15 @@ const plugin = createRouteForgePlugin({
|
|
|
26
26
|
endpoint: '/_forge/routes',
|
|
27
27
|
})
|
|
28
28
|
|
|
29
|
+
// plugin.interceptors is exposed synchronously — register one or more interceptors
|
|
30
|
+
// here without awaiting ready() (they only affect later api() calls).
|
|
31
|
+
plugin.interceptors.request.use((config) => { /* … */ return config })
|
|
32
|
+
plugin.interceptors.response.use((resp) => resp.data, (err) => Promise.reject(err))
|
|
33
|
+
|
|
29
34
|
const app = createApp(App)
|
|
30
35
|
app.use(plugin)
|
|
31
|
-
|
|
36
|
+
|
|
37
|
+
// Option A (recommended): mount the app after ready() (summary + eager levels fully loaded);
|
|
32
38
|
// sync methods like route()/hasRoute() are then safe immediately
|
|
33
39
|
plugin.ready()
|
|
34
40
|
.then(() => app.mount('#app'))
|
|
@@ -37,9 +43,25 @@ plugin.ready()
|
|
|
37
43
|
// (network error / non-2xx / timeout) — avoid a silent blank page
|
|
38
44
|
console.error('[route-forge] init failed', err)
|
|
39
45
|
})
|
|
46
|
+
|
|
47
|
+
// Option B (equally supported): mount immediately and gate inside the component tree —
|
|
48
|
+
// <ForgeReady> holds route-dependent content until ready() resolves, same determinism:
|
|
49
|
+
// app.mount('#app')
|
|
50
|
+
// and wrap route content in App.vue (see ForgeReady below)
|
|
40
51
|
```
|
|
41
52
|
|
|
42
53
|
> Plugin options are exactly `createRouteForge(options)` (`endpoint` / `summary` / `levels` / `eager` / `adapter` / `cache` / `interceptors` / `timeout` / `baseURL`); full options table in the [core README](../core/README.md#options-createrouteforgeoptions). `options` itself is optional — when the summary is embedded in the page via `@forgeSummary` (`window.__ROUTE_FORGE__`), install with a bare `createRouteForgePlugin()`.
|
|
54
|
+
>
|
|
55
|
+
> **Reuse mode**: already hold a forge instance outside Vue (SSR entry, singleton module)? Pass it directly — `createRouteForgePlugin(instance)` reuses it and ignores options, symmetric with React's `<RouteForgeProvider forge?>`.
|
|
56
|
+
|
|
57
|
+
**Option B — direct mount with a deterministic first frame:** wrap route-dependent content in the `ForgeReady` gate component — it renders the `fallback` slot until ready, then the default slot; equivalent determinism to `plugin.ready().then(mount)`:
|
|
58
|
+
|
|
59
|
+
```vue
|
|
60
|
+
<ForgeReady>
|
|
61
|
+
<template #fallback><Splash /></template>
|
|
62
|
+
<RouterView />
|
|
63
|
+
</ForgeReady>
|
|
64
|
+
```
|
|
43
65
|
|
|
44
66
|
## useForge — the core composable
|
|
45
67
|
|
|
@@ -74,7 +96,7 @@ userApi.route('show', { user: 1 }) // → forge.route('admin', 'u
|
|
|
74
96
|
users.load() // load the bound level
|
|
75
97
|
users.isLoaded() // check the bound level's cache
|
|
76
98
|
users.invalidate() // invalidate the bound level's cache
|
|
77
|
-
// Global methods: isLoading() / onLoadingChange() / hasRoute(name) / getRoutes()
|
|
99
|
+
// Global methods: isLoading() / onLoadingChange() / hasRoute(name) / getRoutes() / getLevels()
|
|
78
100
|
```
|
|
79
101
|
|
|
80
102
|
> **Note**: the unbound full instance may throw the guard error (`RF_FE_010`) from `route()` / `hasRoute()` before auto-discovery completes. Prefer `await forge.ready()`, or use `useForgeRoute` for links.
|
|
@@ -166,7 +188,7 @@ import { ForgeLink } from '@route-forge/vue'
|
|
|
166
188
|
</template>
|
|
167
189
|
```
|
|
168
190
|
|
|
169
|
-
Router integration is **zero-dependency and automatic**: if [vue-router](https://router.vuejs.org/) is installed (`app.use(router)`), `ForgeLink` renders `<RouterLink :to="href">` for in-app navigation; otherwise it renders a native `<a>`. Nothing to configure. (A `RouterLink` registered only locally in a parent component is not detected — the global registration from `app.use(router)` is what's probed.)
|
|
191
|
+
Router integration is **zero-dependency and automatic**: if [vue-router](https://router.vuejs.org/) is installed (`app.use(router)`), `ForgeLink` renders `<RouterLink :to="href">` for in-app navigation; otherwise it renders a native `<a>`. Nothing to configure. (A `RouterLink` registered only locally in a parent component is not detected — the global registration from `app.use(router)` is what's probed.) For those cases — or any custom link component — pass it via the `as` prop; the injected component receives both `href` and `to` props (mirroring `@route-forge/react`).
|
|
170
192
|
|
|
171
193
|
**`ForgeRoute`** — the flexible one: exposes `{ href, loaded }` through a scoped slot for full control:
|
|
172
194
|
|
|
@@ -192,9 +214,9 @@ Shared contract (both components):
|
|
|
192
214
|
- Console behavior: while the level is not loaded each instance `console.warn`s **once** (a normal transient state — no spam); route resolution failures log `console.error` every time (rendering still never breaks)
|
|
193
215
|
- SSR: the components simply render the `loading` slot (or nothing) until the level cache is populated — preload the level on the server, or let the link appear after client hydration
|
|
194
216
|
|
|
195
|
-
## About the `$forge` global property (
|
|
217
|
+
## About the `$forge` global property (removed in v3.0.0)
|
|
196
218
|
|
|
197
|
-
|
|
219
|
+
Since v3.0.0 the plugin no longer injects `app.config.globalProperties.$forge`: it exposed only `route()` (no `api` / `url` / `hasRoute`) and threw on unready route data, breaking the render — a fragile, misuse-prone entry. Generate links in templates with `useForgeRoute` / `ForgeLink` / `ForgeRoute`; for imperative code use `useForge()` (unbound calls work directly: `forge(level, name, params?)`).
|
|
198
220
|
|
|
199
221
|
## Smart parameter resolution
|
|
200
222
|
|
|
@@ -237,12 +259,12 @@ See the [core README "Type safety" section](../core/README.md#type-safety-option
|
|
|
237
259
|
| `useForgeApi` `pending` / `error` | — (use `LoadingTracker`) | `Ref<boolean>` / `Ref<unknown>` | `boolean` / `unknown` |
|
|
238
260
|
| URL generation returns | `string` (sync; throws when unready) | `ComputedRef<string>` (`''` until ready) | `string` (`''` until ready) |
|
|
239
261
|
| `useForgeRoute` params | — | getter function | plain object (content-compared deps) |
|
|
240
|
-
| Binding signature | `forge.use(level, prefix?)` | `useForge(level?, prefix?)` | `useForge(
|
|
262
|
+
| Binding signature | `forge.use(level, prefix?)` | `useForge(level?, prefix?)` | `useForge(level?, prefix?)` |
|
|
241
263
|
|
|
242
264
|
## FAQ
|
|
243
265
|
|
|
244
|
-
**`$forge.route()`
|
|
245
|
-
|
|
266
|
+
**`$forge.route()` doesn't exist in a template?**
|
|
267
|
+
`$forge` was removed in v3.0.0. Use `useForgeRoute` instead (handles the loading state and degrades to `''`), or the composable form `useForge('admin').route(...)`.
|
|
246
268
|
|
|
247
269
|
**`useForge()` says "must be used inside an app with createRouteForgePlugin() installed"?**
|
|
248
270
|
A composable ran before the plugin was installed, or in a component tree without it; confirm `app.use(plugin)` happened first.
|
package/README_zh.md
CHANGED
|
@@ -26,9 +26,15 @@ const plugin = createRouteForgePlugin({
|
|
|
26
26
|
endpoint: '/_forge/routes',
|
|
27
27
|
})
|
|
28
28
|
|
|
29
|
+
// plugin.interceptors 工厂返回即同步可用——在这里注册一个或多个拦截器,无需 await ready()
|
|
30
|
+
// (拦截链只影响后续 api() 调用)
|
|
31
|
+
plugin.interceptors.request.use((config) => { /* … */ return config })
|
|
32
|
+
plugin.interceptors.response.use((resp) => resp.data, (err) => Promise.reject(err))
|
|
33
|
+
|
|
29
34
|
const app = createApp(App)
|
|
30
35
|
app.use(plugin)
|
|
31
|
-
|
|
36
|
+
|
|
37
|
+
// 方式 A(推荐):ready()(摘要 + eager 层级全部完成)后再挂载应用,
|
|
32
38
|
// 挂载后 route()/hasRoute() 等同步方法即刻可用
|
|
33
39
|
plugin.ready()
|
|
34
40
|
.then(() => app.mount('#app'))
|
|
@@ -36,9 +42,25 @@ plugin.ready()
|
|
|
36
42
|
// 失败必须接住:摘要端点不可达(网络错误/非 2xx/超时)时避免静默白屏
|
|
37
43
|
console.error('[route-forge] init failed', err)
|
|
38
44
|
})
|
|
45
|
+
|
|
46
|
+
// 方式 B(同等支持):立即挂载、在组件树内闭门——<ForgeReady> 会在 ready() resolve 前
|
|
47
|
+
// 暂存依赖路由的内容,确定性与方式 A 等价:
|
|
48
|
+
// app.mount('#app')
|
|
49
|
+
// 并在 App.vue 中用 <ForgeReady> 包住路由内容(见下)
|
|
39
50
|
```
|
|
40
51
|
|
|
41
52
|
> 插件选项与 `createRouteForge(options)` 完全一致(`endpoint` / `summary` / `levels` / `eager` / `adapter` / `cache` / `interceptors` / `timeout` / `baseURL`),完整选项表见 [core README](../core/README_zh.md#配置选项createruteforgeoptions)。`options` 本身可选——摘要由页面内嵌 `@forgeSummary`(`window.__ROUTE_FORGE__`)提供时,可直接 `createRouteForgePlugin()` 无参安装。
|
|
53
|
+
>
|
|
54
|
+
> **复用模式**:已在 Vue 之外(SSR 入口、单例模块)持有 forge 实例?直接 `createRouteForgePlugin(instance)` 传入即可复用、忽略 options——与 React 的 `<RouteForgeProvider forge?>` 对称。
|
|
55
|
+
|
|
56
|
+
**方式 B——直接 mount 且确定性首帧:** 用 `ForgeReady` 门闩组件包住依赖路由的内容——ready 前渲染 `fallback` 插槽、之后渲染 default,与 `plugin.ready().then(mount)` 的确定性等价:
|
|
57
|
+
|
|
58
|
+
```vue
|
|
59
|
+
<ForgeReady>
|
|
60
|
+
<template #fallback><Splash /></template>
|
|
61
|
+
<RouterView />
|
|
62
|
+
</ForgeReady>
|
|
63
|
+
```
|
|
42
64
|
|
|
43
65
|
## useForge — 核心 composable
|
|
44
66
|
|
|
@@ -73,7 +95,7 @@ userApi.route('show', { user: 1 }) // → forge.route('admin', 'u
|
|
|
73
95
|
users.load() // 加载绑定层级
|
|
74
96
|
users.isLoaded() // 检查绑定层级缓存
|
|
75
97
|
users.invalidate() // 失效绑定层级缓存
|
|
76
|
-
// 全局方法:isLoading() / onLoadingChange() / hasRoute(name) / getRoutes()
|
|
98
|
+
// 全局方法:isLoading() / onLoadingChange() / hasRoute(name) / getRoutes() / getLevels()
|
|
77
99
|
```
|
|
78
100
|
|
|
79
101
|
> **注意**:`useForge()` 无 `level` 时返回的完整实例在 auto-discovery 未完成前调用 `route()` / `hasRoute()` 可能抛守卫错误(`RF_FE_010`)。建议 `await forge.ready()`,或生成链接直接用 `useForgeRoute`。
|
|
@@ -165,7 +187,7 @@ import { ForgeLink } from '@route-forge/vue'
|
|
|
165
187
|
</template>
|
|
166
188
|
```
|
|
167
189
|
|
|
168
|
-
路由库集成**零依赖、全自动**:安装了 [vue-router](https://router.vuejs.org/)(`app.use(router)`)时,`ForgeLink` 自动渲染 `<RouterLink :to="href">` 做 SPA 内部跳转;否则渲染原生 `<a>`。无需任何配置。(仅在父组件局部注册的 `RouterLink` 探测不到——探测的是 `app.use(router)`
|
|
190
|
+
路由库集成**零依赖、全自动**:安装了 [vue-router](https://router.vuejs.org/)(`app.use(router)`)时,`ForgeLink` 自动渲染 `<RouterLink :to="href">` 做 SPA 内部跳转;否则渲染原生 `<a>`。无需任何配置。(仅在父组件局部注册的 `RouterLink` 探测不到——探测的是 `app.use(router)` 产生的全局注册。)这类场景(或任意自定义链接组件)可通过 `as` prop 手动注入,注入组件会同时收到 `href` 与 `to` 两个 prop(与 `@route-forge/react` 对齐)。
|
|
169
191
|
|
|
170
192
|
**`ForgeRoute`** —— 灵活形态:通过作用域插槽暴露 `{ href, loaded }`,完全自主控制渲染:
|
|
171
193
|
|
|
@@ -191,9 +213,9 @@ import { ForgeRoute } from '@route-forge/vue'
|
|
|
191
213
|
- 控制台行为:`level` 未加载时每实例 `console.warn` **一次**(正常瞬态,不刷屏);路由解析失败每次都 `console.error`(渲染仍不中断)
|
|
192
214
|
- SSR:`level` 缓存就绪前组件只渲染 `loading` 插槽(或不渲染)——可在服务端预加载层级,或让链接在客户端 hydration 后自然出现
|
|
193
215
|
|
|
194
|
-
## 关于 `$forge`
|
|
216
|
+
## 关于 `$forge` 全局属性(已于 v3.0.0 移除)
|
|
195
217
|
|
|
196
|
-
|
|
218
|
+
v3.0.0 起插件不再注入 `app.config.globalProperties.$forge`:它只暴露 `route()` 一个方法(无 `api` / `url` / `hasRoute`),且在层级未加载时直接抛错、打断渲染,属于易误用的残缺入口。模板内生成链接用 `useForgeRoute` / `ForgeLink` / `ForgeRoute`;命令式场景用 `useForge()`(未绑定层级时可直接 `forge(level, name, params?)` 调用)。
|
|
197
219
|
|
|
198
220
|
## 参数智能解析
|
|
199
221
|
|
|
@@ -236,12 +258,12 @@ await users('show', { user: 1 }) // ✅ params 类型自动校验
|
|
|
236
258
|
| `useForgeApi` 的 `pending` / `error` | —(用 `LoadingTracker`) | `Ref<boolean>` / `Ref<unknown>` | `boolean` / `unknown` |
|
|
237
259
|
| URL 生成返回值 | `string`(同步,未就绪抛错) | `ComputedRef<string>`(未就绪为 `''`) | `string`(未就绪为 `''`) |
|
|
238
260
|
| `useForgeRoute` 的 params | — | getter 函数 | 普通对象(按内容对比依赖) |
|
|
239
|
-
| 绑定签名 | `forge.use(level, prefix?)` | `useForge(level?, prefix?)` | `useForge(
|
|
261
|
+
| 绑定签名 | `forge.use(level, prefix?)` | `useForge(level?, prefix?)` | `useForge(level?, prefix?)` |
|
|
240
262
|
|
|
241
263
|
## 常见问题
|
|
242
264
|
|
|
243
|
-
**模板里 `$forge.route()`
|
|
244
|
-
|
|
265
|
+
**模板里 `$forge.route()` 报错不存在?**
|
|
266
|
+
`$forge` 已在 v3.0.0 移除。改用 `useForgeRoute`(自动处理加载态并降级为 `''`),或 `useForge('admin').route(...)` 的组合式写法。
|
|
245
267
|
|
|
246
268
|
**`useForge()` 报 "must be used inside an app with createRouteForgePlugin() installed"?**
|
|
247
269
|
composable 在插件安装前或插件未安装的组件树里被调用了;确认 `app.use(plugin)` 已执行。
|
package/dist/index.cjs
CHANGED
|
@@ -1,22 +1,23 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var vue = require('vue');
|
|
4
3
|
var core = require('@route-forge/core');
|
|
4
|
+
var vue = require('vue');
|
|
5
5
|
|
|
6
6
|
// src/plugin.ts
|
|
7
7
|
var FORGE_INJECTION_KEY = /* @__PURE__ */ Symbol("route-forge");
|
|
8
|
-
function createRouteForgePlugin(
|
|
9
|
-
const forge = core.createRouteForge(
|
|
8
|
+
function createRouteForgePlugin(forgeOrOptions = {}) {
|
|
9
|
+
const forge = isRouteForge(forgeOrOptions) ? forgeOrOptions : core.createRouteForge(forgeOrOptions);
|
|
10
10
|
return {
|
|
11
11
|
ready: forge.ready.bind(forge),
|
|
12
|
+
interceptors: forge.interceptors,
|
|
12
13
|
install(app) {
|
|
13
14
|
app.provide(FORGE_INJECTION_KEY, forge);
|
|
14
|
-
app.config.globalProperties.$forge = {
|
|
15
|
-
route: (level, name, params) => forge.route(level, name, params)
|
|
16
|
-
};
|
|
17
15
|
}
|
|
18
16
|
};
|
|
19
17
|
}
|
|
18
|
+
function isRouteForge(x) {
|
|
19
|
+
return typeof x === "object" && x !== null && typeof x.api === "function" && typeof x.ready === "function";
|
|
20
|
+
}
|
|
20
21
|
function useForge(level, prefix) {
|
|
21
22
|
const forge = vue.inject(FORGE_INJECTION_KEY);
|
|
22
23
|
if (!forge) {
|
|
@@ -64,7 +65,40 @@ function useForgeApi(level, prefix) {
|
|
|
64
65
|
}
|
|
65
66
|
return { pending, error, call };
|
|
66
67
|
}
|
|
67
|
-
function
|
|
68
|
+
function useInjectedForge(apiName) {
|
|
69
|
+
const forge = vue.inject(FORGE_INJECTION_KEY);
|
|
70
|
+
if (!forge) {
|
|
71
|
+
throw new Error(
|
|
72
|
+
`[route-forge/vue] ${apiName} must be used inside an app with createRouteForgePlugin() installed`
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
return forge;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// src/degrade.ts
|
|
79
|
+
var reportedKeys = /* @__PURE__ */ new Set();
|
|
80
|
+
var renderWarnKeys = /* @__PURE__ */ new Set();
|
|
81
|
+
function dedupeKey(component, error) {
|
|
82
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
83
|
+
return `${component}:${message}`;
|
|
84
|
+
}
|
|
85
|
+
function reportDegrade(component, error, enabled = true) {
|
|
86
|
+
if (!enabled) return;
|
|
87
|
+
const key = dedupeKey(component, error);
|
|
88
|
+
if (reportedKeys.has(key)) return;
|
|
89
|
+
reportedKeys.add(key);
|
|
90
|
+
console.error(
|
|
91
|
+
`%c[route-forge]%c ${component} \u8DEF\u7531\u89E3\u6790\u5931\u8D25\uFF08\u5DF2\u964D\u7EA7\u4E3A\u7A7A\u5B57\u7B26\u4E32\uFF0C\u6E32\u67D3\u672A\u4E2D\u65AD\uFF09`,
|
|
92
|
+
"color:#c0392b;font-weight:bold",
|
|
93
|
+
"color:inherit",
|
|
94
|
+
error
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
function reportRenderWarn(error, enabled) {
|
|
98
|
+
if (!enabled) return;
|
|
99
|
+
const key = `useForgeRoute:${error instanceof Error ? error.message : String(error)}`;
|
|
100
|
+
if (renderWarnKeys.has(key)) return;
|
|
101
|
+
renderWarnKeys.add(key);
|
|
68
102
|
console.warn(
|
|
69
103
|
"%c[route-forge]%c useForgeRoute \u6E32\u67D3\u671F\u9519\u8BEF\uFF08\u5DF2\u964D\u7EA7\u4E3A\u7A7A\u5B57\u7B26\u4E32\uFF0C\u6E32\u67D3\u672A\u4E2D\u65AD\uFF09",
|
|
70
104
|
"color:#e67e22;font-weight:bold",
|
|
@@ -72,13 +106,18 @@ function warnRenderError(error) {
|
|
|
72
106
|
error
|
|
73
107
|
);
|
|
74
108
|
}
|
|
109
|
+
|
|
110
|
+
// src/composables/useForgeRoute.ts
|
|
75
111
|
function useForgeRoute(level, name, params, hooks) {
|
|
112
|
+
return useForgeRouteState(level, name, params, hooks).href;
|
|
113
|
+
}
|
|
114
|
+
function useForgeRouteState(level, name, params, hooks) {
|
|
76
115
|
if (typeof level !== "string") {
|
|
77
116
|
throw new TypeError(
|
|
78
117
|
"[route-forge/vue] useForgeRoute(): level must be a static string \u2014 getter form is not supported (levels are deterministic declarations; create another useForgeRoute call for another level)"
|
|
79
118
|
);
|
|
80
119
|
}
|
|
81
|
-
const forge =
|
|
120
|
+
const forge = useInjectedForge("useForgeRoute()");
|
|
82
121
|
const lvl = level;
|
|
83
122
|
const levelLoaded = vue.ref(forge.isLoaded(lvl));
|
|
84
123
|
if (!levelLoaded.value) {
|
|
@@ -89,17 +128,36 @@ function useForgeRoute(level, name, params, hooks) {
|
|
|
89
128
|
});
|
|
90
129
|
});
|
|
91
130
|
}
|
|
92
|
-
|
|
93
|
-
|
|
131
|
+
const dataVersion = vue.ref(0);
|
|
132
|
+
const unsubscribe = forge.onRoutesChange((changed) => {
|
|
133
|
+
if (changed === lvl) dataVersion.value++;
|
|
134
|
+
});
|
|
135
|
+
if (vue.getCurrentScope()) vue.onScopeDispose(unsubscribe);
|
|
136
|
+
const state = vue.computed(() => {
|
|
137
|
+
if (!levelLoaded.value) return { href: "", error: null };
|
|
138
|
+
void dataVersion.value;
|
|
94
139
|
const n = typeof name === "function" ? name() : name;
|
|
95
140
|
const p = params ? params() : void 0;
|
|
96
141
|
try {
|
|
97
|
-
return forge.route(lvl, n, p);
|
|
142
|
+
return { href: forge.route(lvl, n, p), error: null };
|
|
98
143
|
} catch (e) {
|
|
99
|
-
|
|
100
|
-
return "";
|
|
144
|
+
return { href: "", error: e };
|
|
101
145
|
}
|
|
102
146
|
});
|
|
147
|
+
vue.watch(
|
|
148
|
+
() => state.value.error,
|
|
149
|
+
(err) => {
|
|
150
|
+
if (err == null) return;
|
|
151
|
+
if (hooks?.onDegrade) hooks.onDegrade(err);
|
|
152
|
+
else reportRenderWarn(err, forge.warnings);
|
|
153
|
+
},
|
|
154
|
+
{ immediate: true }
|
|
155
|
+
);
|
|
156
|
+
return {
|
|
157
|
+
href: vue.computed(() => state.value.href),
|
|
158
|
+
error: vue.computed(() => state.value.error),
|
|
159
|
+
isLevelLoaded: vue.computed(() => levelLoaded.value)
|
|
160
|
+
};
|
|
103
161
|
}
|
|
104
162
|
var forgeLinkProps = {
|
|
105
163
|
/** 层级名:静态快照,setup 求值一次后固定,不支持中途切换 */
|
|
@@ -114,16 +172,18 @@ var forgeLinkProps = {
|
|
|
114
172
|
type: [Object, Function],
|
|
115
173
|
required: false,
|
|
116
174
|
default: void 0
|
|
175
|
+
},
|
|
176
|
+
/**
|
|
177
|
+
* 手动注入链接组件(组件对象或标签名字符串),注入后同时收到 `href` 与 `to` 两个 prop
|
|
178
|
+
* (对齐 @route-forge/react 的 ForgeLink `as` 契约)。不传时按默认链路:
|
|
179
|
+
* 探测全局 RouterLink → 原生 `<a>`。用于 RouterLink 局部注册 / 非 vue-router 链接组件场景。
|
|
180
|
+
*/
|
|
181
|
+
as: {
|
|
182
|
+
type: [Object, String, Function],
|
|
183
|
+
required: false,
|
|
184
|
+
default: void 0
|
|
117
185
|
}
|
|
118
186
|
};
|
|
119
|
-
function reportDegrade(component, error) {
|
|
120
|
-
console.error(
|
|
121
|
-
`%c[route-forge]%c ${component} \u8DEF\u7531\u89E3\u6790\u5931\u8D25\uFF08\u5DF2\u964D\u7EA7\u4E3A\u7A7A\u5B57\u7B26\u4E32\uFF0C\u6E32\u67D3\u672A\u4E2D\u65AD\uFF09`,
|
|
122
|
-
"color:#c0392b;font-weight:bold",
|
|
123
|
-
"color:inherit",
|
|
124
|
-
error
|
|
125
|
-
);
|
|
126
|
-
}
|
|
127
187
|
function warnUnloadedOnce(component, level, warned) {
|
|
128
188
|
if (warned.value) return;
|
|
129
189
|
warned.value = true;
|
|
@@ -145,20 +205,23 @@ var ForgeRoute = vue.defineComponent({
|
|
|
145
205
|
props: forgeLinkProps,
|
|
146
206
|
slots: Object,
|
|
147
207
|
setup(props, { slots }) {
|
|
148
|
-
const forge =
|
|
208
|
+
const forge = useInjectedForge("ForgeRoute");
|
|
149
209
|
const nameGetter = () => typeof props.name === "function" ? props.name() : props.name;
|
|
150
210
|
const paramsGetter = () => typeof props.params === "function" ? props.params() : props.params;
|
|
151
|
-
const
|
|
152
|
-
onDegrade: (e) => reportDegrade("ForgeRoute", e)
|
|
211
|
+
const state = useForgeRouteState(props.level, nameGetter, paramsGetter, {
|
|
212
|
+
onDegrade: (e) => reportDegrade("ForgeRoute", e, forge.warnings)
|
|
153
213
|
});
|
|
154
214
|
const unloadWarned = { value: false };
|
|
155
215
|
return () => {
|
|
156
|
-
const url = href.value;
|
|
216
|
+
const url = state.href.value;
|
|
157
217
|
const loaded = url !== "";
|
|
158
218
|
if (!loaded && !forge.isLoaded(props.level)) {
|
|
159
219
|
warnUnloadedOnce("ForgeRoute", props.level, unloadWarned);
|
|
160
220
|
}
|
|
161
|
-
if (!
|
|
221
|
+
if (!state.isLevelLoaded.value) return slots.loading ? slots.loading() : null;
|
|
222
|
+
if (state.error.value != null) {
|
|
223
|
+
return slots.error ? slots.error({ error: state.error.value }) : slots.loading ? slots.loading() : null;
|
|
224
|
+
}
|
|
162
225
|
return slots.default ? slots.default({ href: url, loaded }) : null;
|
|
163
226
|
};
|
|
164
227
|
}
|
|
@@ -170,22 +233,32 @@ var ForgeLink = vue.defineComponent({
|
|
|
170
233
|
props: forgeLinkProps,
|
|
171
234
|
slots: Object,
|
|
172
235
|
setup(props, { slots, attrs }) {
|
|
173
|
-
const forge =
|
|
236
|
+
const forge = useInjectedForge("ForgeLink");
|
|
174
237
|
const nameGetter = () => typeof props.name === "function" ? props.name() : props.name;
|
|
175
238
|
const paramsGetter = () => typeof props.params === "function" ? props.params() : props.params;
|
|
176
|
-
const
|
|
177
|
-
onDegrade: (e) => reportDegrade("ForgeLink", e)
|
|
239
|
+
const state = useForgeRouteState(props.level, nameGetter, paramsGetter, {
|
|
240
|
+
onDegrade: (e) => reportDegrade("ForgeLink", e, forge.warnings)
|
|
178
241
|
});
|
|
179
242
|
const unloadWarned = { value: false };
|
|
180
243
|
const routerLink = resolveRouterLink();
|
|
181
244
|
return () => {
|
|
182
|
-
const url = href.value;
|
|
245
|
+
const url = state.href.value;
|
|
183
246
|
const loaded = url !== "";
|
|
184
247
|
if (!loaded && !forge.isLoaded(props.level)) {
|
|
185
248
|
warnUnloadedOnce("ForgeLink", props.level, unloadWarned);
|
|
186
249
|
}
|
|
187
|
-
if (!
|
|
250
|
+
if (!state.isLevelLoaded.value) return slots.loading ? slots.loading() : null;
|
|
251
|
+
if (state.error.value != null) {
|
|
252
|
+
return slots.error ? slots.error({ error: state.error.value }) : slots.loading ? slots.loading() : null;
|
|
253
|
+
}
|
|
188
254
|
const children = slots.default ? slots.default() : void 0;
|
|
255
|
+
if (props.as) {
|
|
256
|
+
return vue.h(
|
|
257
|
+
props.as,
|
|
258
|
+
{ ...attrs, to: url, href: url },
|
|
259
|
+
{ default: () => children }
|
|
260
|
+
);
|
|
261
|
+
}
|
|
189
262
|
if (routerLink) {
|
|
190
263
|
return vue.h(
|
|
191
264
|
routerLink,
|
|
@@ -197,9 +270,32 @@ var ForgeLink = vue.defineComponent({
|
|
|
197
270
|
};
|
|
198
271
|
}
|
|
199
272
|
});
|
|
273
|
+
var ForgeReady = vue.defineComponent({
|
|
274
|
+
name: "ForgeReady",
|
|
275
|
+
slots: Object,
|
|
276
|
+
setup(_, { slots }) {
|
|
277
|
+
const forge = useInjectedForge("ForgeReady");
|
|
278
|
+
const open = vue.ref(forge.isReady());
|
|
279
|
+
if (!open.value) {
|
|
280
|
+
forge.ready().then(
|
|
281
|
+
() => {
|
|
282
|
+
open.value = true;
|
|
283
|
+
},
|
|
284
|
+
(err) => {
|
|
285
|
+
console.error(
|
|
286
|
+
"[route-forge] ForgeReady: ready() rejected \u2014 content stays gated (same as an unhandled ready().catch)",
|
|
287
|
+
err
|
|
288
|
+
);
|
|
289
|
+
}
|
|
290
|
+
);
|
|
291
|
+
}
|
|
292
|
+
return () => open.value ? slots.default?.() : slots.fallback ? slots.fallback() : null;
|
|
293
|
+
}
|
|
294
|
+
});
|
|
200
295
|
|
|
201
296
|
exports.FORGE_INJECTION_KEY = FORGE_INJECTION_KEY;
|
|
202
297
|
exports.ForgeLink = ForgeLink;
|
|
298
|
+
exports.ForgeReady = ForgeReady;
|
|
203
299
|
exports.ForgeRoute = ForgeRoute;
|
|
204
300
|
exports.createRouteForgePlugin = createRouteForgePlugin;
|
|
205
301
|
exports.useForge = useForge;
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/plugin.ts","../src/composables/useForgeApi.ts","../src/composables/useForgeRoute.ts","../src/components/shared.ts","../src/components/ForgeRoute.ts","../src/components/ForgeLink.ts"],"names":["createRouteForge","inject","ref","onMounted","computed","getCurrentInstance","defineComponent","h"],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * Vue 3 插件入口\n * @see .docs/SPEC.md §4.1.7\n *\n * 提供:\n * - 全局属性 $forge(route() 工具)\n * - inject symbol 注入 RouteForge 实例供 composable 使用\n * - useForge() / useForge(level) / useForge(level, prefix) 返回统一方法的 forge 实例\n * • 不传 level:forge(level, name, params?) 直接调用\n * • 传 level:forge(name, params?) 直接调用,自动绑定层级\n * • 传 level + prefix:forge(suffix, params?) 自动拼接 prefix\n *\n * 类型推断:\n * 当 ForgeRouteMap 通过 codegen 或 module augmentation 定义时,\n * level / name / params 均自动推断,IDE 提供补全提示。\n */\n\nimport type { App, InjectionKey, Plugin, Ref } from 'vue';\nimport { inject, ref } from 'vue';\nimport {\n type ApiCallParams,\n type BoundForge,\n createRouteForge,\n type RouteForge,\n type RouteForgeOptions,\n} from '@route-forge/core';\n\nexport const FORGE_INJECTION_KEY: InjectionKey<RouteForge> = Symbol('route-forge');\n\nexport interface RouteForgePluginOptions extends RouteForgeOptions {}\n\n/** Vue 特化类型:levelLoaded 为 Ref<boolean> */\nexport type VueBoundForge = BoundForge<Ref<boolean>>;\n\nexport function createRouteForgePlugin(options: RouteForgePluginOptions = {}): Plugin<[]> & {\n ready: RouteForge['ready']\n} {\n const forge = createRouteForge(options);\n\n return {\n ready: forge.ready.bind(forge),\n install(app: App) {\n app.provide(FORGE_INJECTION_KEY, forge);\n app.config.globalProperties.$forge = {\n route: (level: string, name: string, params?: Record<string, unknown>) =>\n forge.route(level, name, params),\n };\n },\n };\n}\n\n/**\n * 获取 forge 实例。内部委托 core 的 forge.use(),仅将 levelLoaded 替换为 Vue 响应式 Ref。\n *\n * @example\n * // 不绑定层级\n * const forge = useForge()\n * forge('admin', 'users.show', { user: 1 })\n * forge.api('admin', 'users.show')\n *\n * @example\n * // 绑定层级 — 直接调用和 api/route/url 均无需传 level\n * const forge = useForge('admin')\n * forge.level // → 'admin'\n * forge.levelLoaded // Ref<boolean>\n * forge('users.show', { user: 1 })\n * forge.route('users.show', { user: 1 })\n *\n * @example\n * // 绑定层级 + 前缀 — 路由名自动拼接\n * const forge = useForge('admin', 'users')\n * forge('show', { user: 1 }) // → forge.api('admin', 'users.show', ...)\n * forge.route('show', { user: 1 }) // → forge.route('admin', 'users.show', ...)\n */\nexport function useForge<L extends string>(level: L, prefix: string): VueBoundForge;\nexport function useForge<L extends string>(level: L): VueBoundForge;\nexport function useForge(): RouteForge;\nexport function useForge(level?: string, prefix?: string): RouteForge | VueBoundForge {\n const forge = inject(FORGE_INJECTION_KEY);\n if (!forge) {\n throw new Error(\n '[route-forge/vue] useForge() must be used inside an app with createRouteForgePlugin() installed',\n );\n }\n\n if (level === undefined) {\n return forge;\n }\n\n // 委托 core 的 use(),获取 BoundForge(levelLoaded 为 Promise<void>)\n const bound = forge.use(level, prefix);\n\n // 将 levelLoaded 从 Promise<void> 替换为 Ref<boolean>\n const levelLoadedRef = ref(false);\n bound.levelLoaded.then(() => {\n levelLoadedRef.value = true;\n }).catch(() => { /* 加载失败时 levelLoaded 保持 false */ });\n\n Object.defineProperty(bound, 'levelLoaded', {\n value: levelLoadedRef,\n writable: false,\n enumerable: true,\n configurable: true,\n });\n\n return bound as unknown as VueBoundForge;\n}\n","/**\n * useForgeApi:包装 forge.api(),自动管理 loading/error 状态\n * @see .docs/SPEC.md §4.1.7\n *\n * 支持:\n * - useForgeApi() — call(level, name, params)\n * - useForgeApi(level) — call(name, params),自动绑定层级\n * - useForgeApi(level, prefix) — call(suffix, params),自动绑定层级 + 拼接前缀\n *\n * 类型推断:\n * 当 ForgeRouteMap 通过 codegen 或 module augmentation 定义时,\n * level / name / params 均自动推断,IDE 提供补全提示。\n *\n * pending 采用引用计数:并发多个 call 时,全部完成才置 false\n * (先完成的 call 不再把其他在途请求的 pending 提前清掉)。\n */\n\nimport type { Ref } from 'vue';\nimport { ref } from 'vue';\nimport { useForge } from '../plugin.js';\nimport type {\n BoundForge,\n RouteForge,\n UseForgeApiBoundCall,\n UseForgeApiBoundReturn,\n UseForgeApiCall,\n UseForgeApiReturn,\n} from '@route-forge/core';\n\n// Vue 特化类型:pending/error 为 Ref\nexport type { UseForgeApiCall, UseForgeApiBoundCall };\nexport type UseForgeApiReturnVue = UseForgeApiReturn<Ref<boolean>, Ref<unknown>>;\nexport type UseForgeApiBoundReturnVue<L extends string> = UseForgeApiBoundReturn<L, Ref<boolean>, Ref<unknown>>;\n\n/** 不绑定层级 — call 需要传 level */\nexport function useForgeApi(): UseForgeApiReturnVue;\n/** 绑定层级 — call 无需传 level */\nexport function useForgeApi<L extends string>(level: L): UseForgeApiBoundReturnVue<L>;\n/** 绑定层级 + 前缀 — call 无需传 level,路由名自动拼接 prefix */\nexport function useForgeApi<L extends string>(level: L, prefix: string): UseForgeApiBoundReturnVue<L>;\nexport function useForgeApi(level?: string, prefix?: string): UseForgeApiReturnVue | UseForgeApiBoundReturnVue<string> {\n const bound = level !== undefined ? useForge(level, prefix as string) : undefined;\n const unbound = bound === undefined ? useForge() : undefined;\n const pending = ref(false);\n const error = ref<unknown>(null);\n // 并发计数:setup 闭包变量(组件实例生命周期存活)\n let inflight = 0;\n\n async function call(...args: unknown[]): Promise<{ data: unknown; error: unknown }> {\n inflight++;\n pending.value = true;\n error.value = null;\n try {\n // 按绑定形态分发:bound 走 BoundForge.api(name, params),unbound 走 RouteForge.api(level, name, params)\n // (窄断言替代 as any:重载签名已保证参数形状,此处仅消除联合类型/框架特化类型的分支歧义)\n const data = bound !== undefined\n ? await (bound as unknown as BoundForge).api(args[0] as string, args[1] as never)\n : await (unbound as RouteForge).api(args[0] as string, args[1] as string, args[2] as never);\n return { data, error: null };\n } catch (e) {\n error.value = e;\n return { data: undefined, error: e };\n } finally {\n inflight--;\n pending.value = inflight > 0;\n }\n }\n\n return { pending, error, call: call as unknown as UseForgeApiCall };\n}\n","/**\n * useForgeRoute:响应式 URL 生成器,内部处理 level 加载状态\n * @see .docs/SPEC.md §4.1.7\n *\n * - level 未加载时返回空字符串 ''(不抛错,模板不崩)\n * - level 加载完成后自动重新计算并返回正确 URL\n * - 路由名不存在或必填参数缺失等渲染期错误:降级为 '' 保证渲染不中断,\n * 同时以醒目的样式化 warn 输出完整错误(含堆栈)——开发期可见,生产无副作用\n * - level 为静态字符串(层级是确定性声明,不支持 `() => string` 函数形式):\n * 不支持中途动态切换 level\n * (需要另一个层级请在别的组件 / 别的 useForgeRoute 调用里分别使用,与 useForge 契约一致)\n * - 用户无需关心 levelLoaded 状态,直接用即可\n */\n\nimport { computed, type ComputedRef, inject, onMounted, ref } from 'vue';\nimport { FORGE_INJECTION_KEY } from '../plugin.js';\nimport type { RouteForge } from '@route-forge/core';\n\n/** 渲染期错误降级输出:橙色加粗标签 + 完整错误对象,控制台一眼可见 */\nfunction warnRenderError(error: unknown): void {\n console.warn(\n '%c[route-forge]%c useForgeRoute 渲染期错误(已降级为空字符串,渲染未中断)',\n 'color:#e67e22;font-weight:bold',\n 'color:inherit',\n error,\n );\n}\n\n/** 降级报告钩子:组件层(ForgeRoute/ForgeLink)用它把默认 warn 升级为 error,避免双重打印 */\nexport interface ForgeRouteDegradeHooks {\n onDegrade?: (error: unknown) => void;\n}\n\nexport function useForgeRoute(\n level: string,\n name: string | (() => string),\n params?: () => Record<string, unknown> | undefined,\n hooks?: ForgeRouteDegradeHooks,\n): ComputedRef<string> {\n // 运行时守卫:level 必须是静态字符串(类型收窄后防 JS 用户误用静默降级为空链接)\n if (typeof level !== 'string') {\n throw new TypeError(\n '[route-forge/vue] useForgeRoute(): level must be a static string — ' +\n 'getter form is not supported (levels are deterministic declarations; ' +\n 'create another useForgeRoute call for another level)',\n );\n }\n\n const forge = inject(FORGE_INJECTION_KEY) as RouteForge;\n\n // level 为静态字符串(层级是确定性声明):绑定即固定,不支持中途动态切换\n //(需要另一个层级请在别的组件 / 别的 useForgeRoute 调用里分别使用,与 useForge 契约一致)。\n // name / params 仍保持响应式。\n const lvl = level;\n\n // 响应式追踪该 level 的加载状态(computed 依赖此 ref 触发重算)\n const levelLoaded = ref(forge.isLoaded(lvl));\n\n if (!levelLoaded.value) {\n onMounted(() => {\n forge.load(lvl).then(() => {\n levelLoaded.value = true;\n }).catch(() => { /* 加载失败时 computed 返回 '' */\n });\n });\n }\n\n return computed(() => {\n // level 未加载 → 返回空字符串,不抛错\n if (!levelLoaded.value) return '';\n // lvl 为 setup 快照(静态);name / params 每次重算读取,保持响应式\n const n = typeof name === 'function' ? name() : name;\n const p = params ? params() : undefined;\n try {\n return forge.route(lvl, n, p);\n } catch (e) {\n (hooks?.onDegrade ?? warnRenderError)(e);\n return '';\n }\n });\n}\n","/**\n * ForgeRoute / ForgeLink 共享内部实现:props 定义、降级报告、RouterLink 探测\n * (非公共 API,不进包入口导出)\n */\n\nimport { getCurrentInstance, type PropType } from 'vue';\nimport type { RouteForge } from '@route-forge/core';\n\n/** ForgeRoute / ForgeLink 共享 props:level 静态快照,name / params 双形态响应式 */\n// as const:防止 required: true 被宽化为 boolean,导致 defineComponent 推断出 string | undefined\nexport const forgeLinkProps = {\n /** 层级名:静态快照,setup 求值一次后固定,不支持中途切换 */\n level: { type: String, required: true },\n /** 路由名:string 或 getter 函数,保持响应式 */\n name: {\n type: [String, Function] as PropType<string | (() => string)>,\n required: true,\n },\n /** 路由参数:对象或 getter 函数,保持响应式 */\n params: {\n type: [Object, Function] as PropType<Record<string, unknown> | (() => Record<string, unknown>)>,\n required: false,\n default: undefined,\n },\n} as const;\n\n/** 解析出错降级报告:红色加粗标签 + 完整错误对象,控制台一眼可见(error 级,每次出错都报) */\nexport function reportDegrade(component: string, error: unknown): void {\n console.error(\n `%c[route-forge]%c ${component} 路由解析失败(已降级为空字符串,渲染未中断)`,\n 'color:#c0392b;font-weight:bold',\n 'color:inherit',\n error,\n );\n}\n\n/** level 未加载提示:每实例仅一次(warned 为组件实例内的可变标记),避免正常加载瞬态刷屏 */\nexport function warnUnloadedOnce(component: string, level: string, warned: { value: boolean }): void {\n if (warned.value) return;\n warned.value = true;\n console.warn(\n `[route-forge] ${component}: level \"${level}\" 尚未加载,链接暂不渲染(加载完成后自动出现)`,\n );\n}\n\n/**\n * 从 app 全局组件中探测 vue-router 的 RouterLink(零依赖:不 import vue-router)。\n * vue-router 在 app.use(router) 时会把 RouterLink / router-link 注册到全局组件表;\n * 仅在父组件局部注册 RouterLink 的场景探测不到,此时降级渲染原生 <a>(README 有说明)。\n */\nexport function resolveRouterLink(): object | null {\n const inst = getCurrentInstance();\n const comps = inst?.appContext.components as Record<string, object> | undefined;\n return comps?.RouterLink ?? comps?.['router-link'] ?? null;\n}\n\nexport type { RouteForge };\n","/**\n * ForgeRoute:URL 生成组件(通用形态),封装 useForgeRoute 的\"先空串、后更新\"异步行为\n * @see .docs/SPEC.md §4.1.7\n *\n * - 通过作用域插槽暴露 { href, loaded },模板里自由决定渲染 <a>、RouterLink 或任意内容\n * - loaded = href !== ''(level 未加载与路由解析出错都降级为 '',正好复用该哨兵值)\n * - 未加载且未传 loading 插槽时默认不渲染;每实例以 console.warn 提醒一次(防刷屏)\n * - 路由解析出错(UnknownRouteError 等)以 console.error 报告,渲染不中断\n * - level 为静态快照,name / params 保持响应式(值或 getter 函数双形态均可)\n */\n\nimport { defineComponent, inject, type SlotsType, type VNode } from 'vue';\nimport { FORGE_INJECTION_KEY } from '../plugin.js';\nimport { useForgeRoute } from '../composables/useForgeRoute.js';\nimport { forgeLinkProps, reportDegrade, warnUnloadedOnce } from './shared.js';\nimport type { RouteForge } from '@route-forge/core';\n\nexport const ForgeRoute = defineComponent({\n name: 'ForgeRoute',\n // 根节点是用户插槽内容,框架透传 attrs 落到插槽上没有意义,显式关闭\n inheritAttrs: false,\n props: forgeLinkProps,\n slots: Object as SlotsType<{\n default?: (props: { href: string; loaded: boolean }) => VNode[];\n loading?: () => VNode[];\n }>,\n setup(props, { slots }) {\n const forge = inject(FORGE_INJECTION_KEY) as RouteForge;\n // 统一收敛为 getter:name / params 双形态(值或函数)均保持响应式\n const nameGetter = () => (typeof props.name === 'function' ? props.name() : props.name);\n const paramsGetter = () =>\n typeof props.params === 'function' ? props.params() : props.params;\n const href = useForgeRoute(props.level, nameGetter, paramsGetter, {\n onDegrade: (e) => reportDegrade('ForgeRoute', e),\n });\n const unloadWarned = { value: false };\n\n return () => {\n const url = href.value;\n const loaded = url !== '';\n if (!loaded && !forge.isLoaded(props.level)) {\n warnUnloadedOnce('ForgeRoute', props.level, unloadWarned);\n }\n // 未加载/解析出错:loading 插槽优先(无则不渲染),与 ForgeLink 行为一致\n if (!loaded) return slots.loading ? slots.loading() : null;\n return slots.default ? slots.default({ href: url, loaded }) : null;\n };\n },\n});\n\nexport type ForgeRouteProps = InstanceType<typeof ForgeRoute>['$props'];\n","/**\n * ForgeLink:便捷链接组件,封装 useForgeRoute 的\"先空串、后更新\"异步行为\n * @see .docs/SPEC.md §4.1.7\n *\n * - loaded(href !== '')时直接渲染链接:检测到 vue-router 全局注册的 RouterLink\n * 则渲染 <RouterLink :to=\"href\">(SPA 内部跳转),否则渲染原生 <a :href=\"href\">\n * - 未加载(或路由解析出错)时渲染 loading 插槽,未传则默认不渲染;\n * 每实例以 console.warn 提醒一次(防刷屏)\n * - 路由解析出错以 console.error 报告,渲染不中断\n * - attrs 透传到根元素(class / target / rel 等),生成的 href / to 优先于同名 attr\n * - level 为静态快照,name / params 保持响应式(值或 getter 函数双形态均可)\n */\n\nimport { defineComponent, h, inject, type SlotsType, type VNode } from 'vue';\nimport { FORGE_INJECTION_KEY } from '../plugin.js';\nimport { useForgeRoute } from '../composables/useForgeRoute.js';\nimport {\n forgeLinkProps,\n reportDegrade,\n resolveRouterLink,\n warnUnloadedOnce,\n} from './shared.js';\nimport type { RouteForge } from '@route-forge/core';\n\nexport const ForgeLink = defineComponent({\n name: 'ForgeLink',\n // attrs 透传到 <a> / RouterLink,但 href / to 必须以组件生成的 URL 为准(显式合并)\n inheritAttrs: false,\n props: forgeLinkProps,\n slots: Object as SlotsType<{\n /** 链接文本/内容 */\n default?: () => VNode[];\n /** 未加载占位 */\n loading?: () => VNode[];\n }>,\n setup(props, { slots, attrs }) {\n const forge = inject(FORGE_INJECTION_KEY) as RouteForge;\n // 统一收敛为 getter:name / params 双形态(值或函数)均保持响应式\n const nameGetter = () => (typeof props.name === 'function' ? props.name() : props.name);\n const paramsGetter = () =>\n typeof props.params === 'function' ? props.params() : props.params;\n const href = useForgeRoute(props.level, nameGetter, paramsGetter, {\n onDegrade: (e) => reportDegrade('ForgeLink', e),\n });\n const unloadWarned = { value: false };\n // RouterLink 在 app.use(router) 时全局注册,app 生命周期内不变,setup 时探测一次即可\n const routerLink = resolveRouterLink();\n\n return () => {\n const url = href.value;\n const loaded = url !== '';\n if (!loaded && !forge.isLoaded(props.level)) {\n warnUnloadedOnce('ForgeLink', props.level, unloadWarned);\n }\n if (!loaded) return slots.loading ? slots.loading() : null;\n\n const children = slots.default ? slots.default() : undefined;\n if (routerLink) {\n return h(\n routerLink,\n { ...(attrs as Record<string, unknown>), to: url },\n { default: () => children },\n );\n }\n return h('a', { ...(attrs as Record<string, unknown>), href: url }, children);\n };\n },\n});\n\nexport type ForgeLinkProps = InstanceType<typeof ForgeLink>['$props'];\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/plugin.ts","../src/composables/useForge.ts","../src/composables/useForgeApi.ts","../src/useInjectedForge.ts","../src/degrade.ts","../src/composables/useForgeRoute.ts","../src/components/shared.ts","../src/components/ForgeRoute.ts","../src/components/ForgeLink.ts","../src/components/ForgeReady.ts"],"names":["createRouteForge","inject","ref","onMounted","getCurrentScope","onScopeDispose","computed","watch","getCurrentInstance","defineComponent","h"],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * Vue 3 插件入口\n * @see .docs/SPEC.md §4.1.7\n *\n * 提供:\n * - createRouteForgePlugin:装配插件,install 时经 inject symbol 注入 RouteForge 实例\n * - 返回对象上的 interceptors(转发实例拦截器):创建后即可同步注册,无需等待 ready()\n *\n * useForge / useForgeApi / useForgeRoute 等消费 composable 见 composables/ 下各文件。\n *\n * 注:v3.0.0 起不再注入 `$forge` 全局属性(残缺 facade,仅 route() 且未 ready 时抛错断渲染);\n * 模板内生成链接用 useForgeRoute / ForgeLink / ForgeRoute,命令式场景用 useForge()。\n */\n\nimport type { App, InjectionKey, Plugin } from 'vue';\nimport {\n createRouteForge,\n type RouteForge,\n type RouteForgeOptions,\n} from '@route-forge/core';\n\nexport const FORGE_INJECTION_KEY: InjectionKey<RouteForge> = Symbol('route-forge');\n\nexport interface RouteForgePluginOptions extends RouteForgeOptions {}\n\nexport function createRouteForgePlugin(\n forgeOrOptions: RouteForge | RouteForgePluginOptions = {},\n): Plugin<[]> & {\n ready: RouteForge['ready']\n /** 转发实例上的拦截器管理器:创建后即可同步注册,无需等待 ready()(请求/响应拦截链仅影响后续 api() 调用,eager 元信息预加载走 requestRaw 不受影响) */\n interceptors: RouteForge['interceptors']\n} {\n // 复用模式:直接传入 forge 实例(在非组件代码 / SSR 入口持有实例的场景),\n // 忽略 options、不再创建;与 react <RouteForgeProvider forge?> 对称\n const forge = isRouteForge(forgeOrOptions) ? forgeOrOptions : createRouteForge(forgeOrOptions);\n\n return {\n ready: forge.ready.bind(forge),\n interceptors: forge.interceptors,\n install(app: App) {\n app.provide(FORGE_INJECTION_KEY, forge);\n // v3.0.0 起不再注入 $forge 全局属性(残缺 facade 已移除,见 CHANGELOG)\n },\n };\n}\n\n/** 判定入参是否为 forge 实例(而非 options):api + ready 双函数特征 */\nfunction isRouteForge(x: unknown): x is RouteForge {\n return (\n typeof x === 'object' && x !== null &&\n typeof (x as RouteForge).api === 'function' &&\n typeof (x as RouteForge).ready === 'function'\n );\n}\n","/**\n * useForge — 获取 forge 实例(Vue 特化:levelLoaded 为 Ref<boolean>)\n * @see .docs/SPEC.md §4.1.7\n *\n * 内部委托 core 的 forge.use(),仅将 levelLoaded 从 Promise<void> 替换为 Vue 响应式 Ref。\n *\n * @example\n * // 不绑定层级\n * const forge = useForge()\n * forge('admin', 'users.show', { user: 1 })\n * forge.api('admin', 'users.show')\n *\n * @example\n * // 绑定层级 — 直接调用和 api/route/url 均无需传 level\n * const forge = useForge('admin')\n * forge.level // → 'admin'\n * forge.levelLoaded // Ref<boolean>\n * forge('users.show', { user: 1 })\n * forge.route('users.show', { user: 1 })\n *\n * @example\n * // 绑定层级 + 前缀 — 路由名自动拼接\n * const forge = useForge('admin', 'users')\n * forge('show', { user: 1 }) // → forge.api('admin', 'users.show', ...)\n * forge.route('show', { user: 1 }) // → forge.route('admin', 'users.show', ...)\n */\n\nimport { inject, ref, type Ref } from 'vue';\nimport { type BoundForge, type RouteForge } from '@route-forge/core';\nimport { FORGE_INJECTION_KEY } from '../plugin.js';\n\n/** Vue 特化类型:levelLoaded 为 Ref<boolean> */\nexport type VueBoundForge = BoundForge<Ref<boolean>>;\n\nexport function useForge<L extends string>(level: L, prefix: string): VueBoundForge;\nexport function useForge<L extends string>(level: L): VueBoundForge;\nexport function useForge(): RouteForge;\nexport function useForge(level?: string, prefix?: string): RouteForge | VueBoundForge {\n const forge = inject(FORGE_INJECTION_KEY);\n if (!forge) {\n throw new Error(\n '[route-forge/vue] useForge() must be used inside an app with createRouteForgePlugin() installed',\n );\n }\n\n if (level === undefined) {\n return forge;\n }\n\n // 委托 core 的 use(),获取 BoundForge(levelLoaded 为 Promise<void>)\n const bound = forge.use(level, prefix);\n\n // 将 levelLoaded 从 Promise<void> 替换为 Ref<boolean>\n const levelLoadedRef = ref(false);\n bound.levelLoaded.then(() => {\n levelLoadedRef.value = true;\n }).catch(() => { /* 加载失败时 levelLoaded 保持 false */ });\n\n Object.defineProperty(bound, 'levelLoaded', {\n value: levelLoadedRef,\n writable: false,\n enumerable: true,\n configurable: true,\n });\n\n return bound as unknown as VueBoundForge;\n}\n","/**\n * useForgeApi:包装 forge.api(),自动管理 loading/error 状态\n * @see .docs/SPEC.md §4.1.7\n *\n * 支持:\n * - useForgeApi() — call(level, name, params)\n * - useForgeApi(level) — call(name, params),自动绑定层级\n * - useForgeApi(level, prefix) — call(suffix, params),自动绑定层级 + 拼接前缀\n *\n * 类型推断:\n * 当 ForgeRouteMap 通过 codegen 或 module augmentation 定义时,\n * level / name / params 均自动推断,IDE 提供补全提示。\n *\n * pending 采用引用计数:并发多个 call 时,全部完成才置 false\n * (先完成的 call 不再把其他在途请求的 pending 提前清掉)。\n */\n\nimport type { Ref } from 'vue';\nimport { ref } from 'vue';\nimport { useForge } from './useForge.js';\nimport type {\n BoundForge,\n RouteForge,\n UseForgeApiBoundCall,\n UseForgeApiBoundReturn,\n UseForgeApiCall,\n UseForgeApiReturn,\n} from '@route-forge/core';\n\n// Vue 特化类型:pending/error 为 Ref\nexport type { UseForgeApiCall, UseForgeApiBoundCall };\nexport type UseForgeApiReturnVue = UseForgeApiReturn<Ref<boolean>, Ref<unknown>>;\nexport type UseForgeApiBoundReturnVue<L extends string> = UseForgeApiBoundReturn<L, Ref<boolean>, Ref<unknown>>;\n\n/** 不绑定层级 — call 需要传 level */\nexport function useForgeApi(): UseForgeApiReturnVue;\n/** 绑定层级 — call 无需传 level */\nexport function useForgeApi<L extends string>(level: L): UseForgeApiBoundReturnVue<L>;\n/** 绑定层级 + 前缀 — call 无需传 level,路由名自动拼接 prefix */\nexport function useForgeApi<L extends string>(level: L, prefix: string): UseForgeApiBoundReturnVue<L>;\nexport function useForgeApi(level?: string, prefix?: string): UseForgeApiReturnVue | UseForgeApiBoundReturnVue<string> {\n const bound = level !== undefined ? useForge(level, prefix as string) : undefined;\n const unbound = bound === undefined ? useForge() : undefined;\n const pending = ref(false);\n const error = ref<unknown>(null);\n // 并发计数:setup 闭包变量(组件实例生命周期存活)\n let inflight = 0;\n\n async function call(...args: unknown[]): Promise<{ data: unknown; error: unknown }> {\n inflight++;\n pending.value = true;\n error.value = null;\n try {\n // 按绑定形态分发:bound 走 BoundForge.api(name, params),unbound 走 RouteForge.api(level, name, params)\n // (窄断言替代 as any:重载签名已保证参数形状,此处仅消除联合类型/框架特化类型的分支歧义)\n const data = bound !== undefined\n ? await (bound as unknown as BoundForge).api(args[0] as string, args[1] as never)\n : await (unbound as RouteForge).api(args[0] as string, args[1] as string, args[2] as never);\n return { data, error: null };\n } catch (e) {\n error.value = e;\n return { data: undefined, error: e };\n } finally {\n inflight--;\n pending.value = inflight > 0;\n }\n }\n\n return { pending, error, call: call as unknown as UseForgeApiCall };\n}\n","/**\n * 内部 helper(非公共 API,不进包入口导出):inject forge 实例 + 缺失守卫。\n *\n * useForgeRoute / ForgeRoute / ForgeLink 统一走这里,避免裸 `as RouteForge` 强转:\n * 未安装 plugin 时报「怎么修」的指引性错误,而不是\n * `Cannot read properties of undefined (reading 'isLoaded')` 之类的晦涩堆栈。\n */\n\nimport { inject } from 'vue';\nimport { FORGE_INJECTION_KEY } from './plugin.js';\nimport type { RouteForge } from '@route-forge/core';\n\nexport function useInjectedForge(apiName: string): RouteForge {\n const forge = inject(FORGE_INJECTION_KEY);\n if (!forge) {\n throw new Error(\n `[route-forge/vue] ${apiName} must be used inside an app with createRouteForgePlugin() installed`,\n );\n }\n return forge;\n}\n","/**\n * 降级/渲染警告的统一报告器(内部实现,非公共 API,不进包入口导出):\n * - 会话级去重:同一 (component, message) 或同一渲染错误只报告一次,\n * name/params 联动输入等高频场景不刷屏\n * - enabled=false(createRouteForge({ warnings: false }))时静音\n * - __resetDegradeReportsForTests 仅供测试隔离使用\n */\n\nconst reportedKeys = new Set<string>();\nconst renderWarnKeys = new Set<string>();\n\nfunction dedupeKey(component: string, error: unknown): string {\n const message = error instanceof Error ? error.message : String(error);\n return `${component}:${message}`;\n}\n\n/** 解析出错降级报告(组件层,error 级) */\nexport function reportDegrade(component: string, error: unknown, enabled = true): void {\n if (!enabled) return;\n const key = dedupeKey(component, error);\n if (reportedKeys.has(key)) return;\n reportedKeys.add(key);\n console.error(\n `%c[route-forge]%c ${component} 路由解析失败(已降级为空字符串,渲染未中断)`,\n 'color:#c0392b;font-weight:bold',\n 'color:inherit',\n error,\n );\n}\n\n/** 渲染期错误降级输出(hook 默认路径,warn 级;组件层通常以 onDegrade 升级为 error) */\nexport function reportRenderWarn(error: unknown, enabled: boolean): void {\n if (!enabled) return;\n const key = `useForgeRoute:${error instanceof Error ? error.message : String(error)}`;\n if (renderWarnKeys.has(key)) return;\n renderWarnKeys.add(key);\n console.warn(\n '%c[route-forge]%c useForgeRoute 渲染期错误(已降级为空字符串,渲染未中断)',\n 'color:#e67e22;font-weight:bold',\n 'color:inherit',\n error,\n );\n}\n\n/** 测试隔离:清空去重状态(不进公共 API) */\nexport function __resetDegradeReportsForTests(): void {\n reportedKeys.clear();\n renderWarnKeys.clear();\n}\n","/**\n * useForgeRoute:响应式 URL 生成器,内部处理 level 加载状态\n * @see .docs/SPEC.md §4.1.7\n *\n * - level 未加载时返回空字符串 ''(不抛错,模板不崩)\n * - level 加载完成后自动重新计算并返回正确 URL\n * - 路由名不存在或必填参数缺失等渲染期错误:降级为 '' 保证渲染不中断,\n * 同时以醒目的样式化 warn 输出完整错误(含堆栈)——开发期可见,生产无副作用\n * - level 为静态字符串(层级是确定性声明,不支持 `() => string` 函数形式):\n * 不支持中途动态切换 level\n * (需要另一个层级请在别的组件 / 别的 useForgeRoute 调用里分别使用,与 useForge 契约一致)\n * - 用户无需关心 levelLoaded 状态,直接用即可\n */\n\nimport { computed, type ComputedRef, getCurrentScope, onMounted, onScopeDispose, ref, watch } from 'vue';\nimport { useInjectedForge } from '../useInjectedForge.js';\nimport { reportRenderWarn } from '../degrade.js';\n\n/** 降级报告钩子:组件层(ForgeRoute/ForgeLink)用它把默认 warn 升级为 error,避免双重打印 */\nexport interface ForgeRouteDegradeHooks {\n onDegrade?: (error: unknown) => void;\n}\n\nexport function useForgeRoute(\n level: string,\n name: string | (() => string),\n params?: () => Record<string, unknown> | undefined,\n hooks?: ForgeRouteDegradeHooks,\n): ComputedRef<string> {\n return useForgeRouteState(level, name, params, hooks).href;\n}\n\n/** useForgeRoute 的三态内部形态:组件层(ForgeRoute/ForgeLink)用它区分「未加载 / 解析失败」 */\nexport interface ForgeRouteState {\n /** 生成的 URL;未加载或解析出错时为 '' */\n href: ComputedRef<string>;\n /** 解析错误(仅 level 已加载且 route() 抛错时非 null;未加载恒为 null),响应式 */\n error: ComputedRef<unknown>;\n /** level 是否已加载(区别「未加载」与「已加载但解析失败」两种空串来源),响应式 */\n isLevelLoaded: ComputedRef<boolean>;\n}\n\nexport function useForgeRouteState(\n level: string,\n name: string | (() => string),\n params?: () => Record<string, unknown> | undefined,\n hooks?: ForgeRouteDegradeHooks,\n): ForgeRouteState {\n // 运行时守卫:level 必须是静态字符串(类型收窄后防 JS 用户误用静默降级为空链接)\n if (typeof level !== 'string') {\n throw new TypeError(\n '[route-forge/vue] useForgeRoute(): level must be a static string — ' +\n 'getter form is not supported (levels are deterministic declarations; ' +\n 'create another useForgeRoute call for another level)',\n );\n }\n\n const forge = useInjectedForge('useForgeRoute()');\n\n // level 为静态字符串(层级是确定性声明):绑定即固定,不支持中途动态切换\n //(需要另一个层级请在别的组件 / 别的 useForgeRoute 调用里分别使用,与 useForge 契约一致)。\n // name / params 仍保持响应式。\n const lvl = level;\n\n // 响应式追踪该 level 的加载状态(computed 依赖此 ref 触发重算)\n const levelLoaded = ref(forge.isLoaded(lvl));\n\n if (!levelLoaded.value) {\n onMounted(() => {\n forge.load(lvl).then(() => {\n levelLoaded.value = true;\n }).catch(() => { /* 加载失败时 href 保持 '' */\n });\n });\n }\n\n // 路由数据版本:本层级被 revalidate 刷新 / invalidate 后 bump,驱动下面的 computed 重算(无刷新页面即热更新)\n const dataVersion = ref(0);\n const unsubscribe = forge.onRoutesChange((changed) => {\n if (changed === lvl) dataVersion.value++;\n });\n // 在 effect scope 内则随 scope 销毁自动退订;scope 外(如单例模块)不注册、由调用方持有 unsubscribe 自行管理\n if (getCurrentScope()) onScopeDispose(unsubscribe);\n\n // 三态同步求值(computed 纯读缓存,无副作用):\n // 未加载 → { href: '', error: null };解析失败 → { href: '', error };成功 → { href }\n const state = computed(() => {\n if (!levelLoaded.value) return { href: '', error: null as unknown };\n void dataVersion.value; // 依赖数据版本:revalidate/invalidate 后强制重算\n const n = typeof name === 'function' ? name() : name;\n const p = params ? params() : undefined;\n try {\n return { href: forge.route(lvl, n, p), error: null as unknown };\n } catch (e) {\n return { href: '', error: e as unknown };\n }\n });\n\n // 降级报告:渲染求值纯净化后,错误经 watch(组件更新前触发)输出,与 react 侧口径一致。\n // 同错会话级去重;createRouteForge({ warnings: false }) 时静音。\n watch(\n () => state.value.error,\n (err) => {\n if (err == null) return;\n if (hooks?.onDegrade) hooks.onDegrade(err);\n else reportRenderWarn(err, forge.warnings);\n },\n { immediate: true },\n );\n\n return {\n href: computed(() => state.value.href),\n error: computed(() => state.value.error),\n isLevelLoaded: computed(() => levelLoaded.value),\n };\n}\n","/**\n * ForgeRoute / ForgeLink 共享内部实现:props 定义、降级报告(转发 degrade.ts)、RouterLink 探测\n * (非公共 API,不进包入口导出)\n */\n\nimport { getCurrentInstance, type PropType } from 'vue';\n\nexport { reportDegrade, reportRenderWarn, __resetDegradeReportsForTests } from '../degrade.js';\n\n/** ForgeRoute / ForgeLink 共享 props:level 静态快照,name / params 双形态响应式 */\n// as const:防止 required: true 被宽化为 boolean,导致 defineComponent 推断出 string | undefined\nexport const forgeLinkProps = {\n /** 层级名:静态快照,setup 求值一次后固定,不支持中途切换 */\n level: { type: String, required: true },\n /** 路由名:string 或 getter 函数,保持响应式 */\n name: {\n type: [String, Function] as PropType<string | (() => string)>,\n required: true,\n },\n /** 路由参数:对象或 getter 函数,保持响应式 */\n params: {\n type: [Object, Function] as PropType<Record<string, unknown> | (() => Record<string, unknown>)>,\n required: false,\n default: undefined,\n },\n /**\n * 手动注入链接组件(组件对象或标签名字符串),注入后同时收到 `href` 与 `to` 两个 prop\n * (对齐 @route-forge/react 的 ForgeLink `as` 契约)。不传时按默认链路:\n * 探测全局 RouterLink → 原生 `<a>`。用于 RouterLink 局部注册 / 非 vue-router 链接组件场景。\n */\n as: {\n type: [Object, String, Function] as PropType<object>,\n required: false,\n default: undefined,\n },\n} as const;\n\n/** level 未加载提示:每实例仅一次(warned 为组件实例内的可变标记),避免正常加载瞬态刷屏 */\nexport function warnUnloadedOnce(component: string, level: string, warned: { value: boolean }): void {\n if (warned.value) return;\n warned.value = true;\n console.warn(\n `[route-forge] ${component}: level \"${level}\" 尚未加载,链接暂不渲染(加载完成后自动出现)`,\n );\n}\n\n/**\n * 从 app 全局组件中探测 vue-router 的 RouterLink(零依赖:不 import vue-router)。\n * vue-router 在 app.use(router) 时会把 RouterLink / router-link 注册到全局组件表;\n * 仅在父组件局部注册 RouterLink 的场景探测不到,此时降级渲染原生 <a>(README 有说明)。\n */\nexport function resolveRouterLink(): object | null {\n const inst = getCurrentInstance();\n const comps = inst?.appContext.components as Record<string, object> | undefined;\n return comps?.RouterLink ?? comps?.['router-link'] ?? null;\n}\n","/**\n * ForgeRoute:URL 生成组件(通用形态),封装 useForgeRoute 的\"先空串、后更新\"异步行为\n * @see .docs/SPEC.md §4.1.7\n *\n * - 通过作用域插槽暴露 { href, loaded },模板里自由决定渲染 <a>、RouterLink 或任意内容\n * - loaded = href !== ''(level 未加载与路由解析出错都降级为 '',正好复用该哨兵值)\n * - 三态分流:未加载 → loading 插槽;已加载但解析失败 → error 插槽(props { error },\n * 未传回落 loading);成功 → default 插槽\n * - 未加载且未传 loading 插槽时默认不渲染;每实例以 console.warn 提醒一次(防刷屏)\n * - 路由解析出错(UnknownRouteError 等)以 console.error 报告,渲染不中断\n * - level 为静态快照,name / params 保持响应式(值或 getter 函数双形态均可)\n */\n\nimport { defineComponent, type SlotsType, type VNode } from 'vue';\nimport { useInjectedForge } from '../useInjectedForge.js';\nimport { useForgeRouteState } from '../composables/useForgeRoute.js';\nimport { forgeLinkProps, reportDegrade, warnUnloadedOnce } from './shared.js';\n\nexport const ForgeRoute = defineComponent({\n name: 'ForgeRoute',\n // 根节点是用户插槽内容,框架透传 attrs 落到插槽上没有意义,显式关闭\n inheritAttrs: false,\n props: forgeLinkProps,\n slots: Object as SlotsType<{\n default?: (props: { href: string; loaded: boolean }) => VNode[];\n loading?: () => VNode[];\n /** 解析失败(路由名不存在等):props 携带错误对象;未传回落 loading 插槽 */\n error?: (props: { error: unknown }) => VNode[];\n }>,\n setup(props, { slots }) {\n const forge = useInjectedForge('ForgeRoute');\n // 统一收敛为 getter:name / params 双形态(值或函数)均保持响应式\n const nameGetter = () => (typeof props.name === 'function' ? props.name() : props.name);\n const paramsGetter = () =>\n typeof props.params === 'function' ? props.params() : props.params;\n const state = useForgeRouteState(props.level, nameGetter, paramsGetter, {\n onDegrade: (e) => reportDegrade('ForgeRoute', e, forge.warnings),\n });\n const unloadWarned = { value: false };\n\n return () => {\n const url = state.href.value;\n const loaded = url !== '';\n if (!loaded && !forge.isLoaded(props.level)) {\n warnUnloadedOnce('ForgeRoute', props.level, unloadWarned);\n }\n // 三态分流:未加载 → loading;解析失败 → error(未传回落 loading)\n if (!state.isLevelLoaded.value) return slots.loading ? slots.loading() : null;\n if (state.error.value != null) {\n return slots.error ? slots.error({ error: state.error.value }) : (slots.loading ? slots.loading() : null);\n }\n return slots.default ? slots.default({ href: url, loaded }) : null;\n };\n },\n});\n\nexport type ForgeRouteProps = InstanceType<typeof ForgeRoute>['$props'];\n","/**\n * ForgeLink:便捷链接组件,封装 useForgeRoute 的\"先空串、后更新\"异步行为\n * @see .docs/SPEC.md §4.1.7\n *\n * - loaded(href !== '')时直接渲染链接:`as` prop 显式注入的组件优先(同时收到 href+to);\n * 否则探测到 vue-router 全局注册的 RouterLink 则渲染 <RouterLink :to=\"href\">(SPA 内部跳转),\n * 再否则渲染原生 <a :href=\"href\">\n * - 三态分流:未加载 → loading 插槽;已加载但解析失败 → error 插槽(props { error },\n * 未传回落 loading);成功 → 链接\n * - 未加载且未传 loading 插槽时默认不渲染;每实例以 console.warn 提醒一次(防刷屏)\n * - 路由解析出错以 console.error 报告,渲染不中断\n * - attrs 透传到根元素(class / target / rel 等),生成的 href / to 优先于同名 attr\n * - level 为静态快照,name / params 保持响应式(值或 getter 函数双形态均可)\n */\n\nimport { defineComponent, h, type SlotsType, type VNode } from 'vue';\nimport { useInjectedForge } from '../useInjectedForge.js';\nimport { useForgeRouteState } from '../composables/useForgeRoute.js';\nimport {\n forgeLinkProps,\n reportDegrade,\n resolveRouterLink,\n warnUnloadedOnce,\n} from './shared.js';\n\nexport const ForgeLink = defineComponent({\n name: 'ForgeLink',\n // attrs 透传到 <a> / RouterLink,但 href / to 必须以组件生成的 URL 为准(显式合并)\n inheritAttrs: false,\n props: forgeLinkProps,\n slots: Object as SlotsType<{\n /** 链接文本/内容 */\n default?: () => VNode[];\n /** 未加载占位 */\n loading?: () => VNode[];\n /** 解析失败(路由名不存在等):props 携带错误对象;未传回落 loading 插槽 */\n error?: (props: { error: unknown }) => VNode[];\n }>,\n setup(props, { slots, attrs }) {\n const forge = useInjectedForge('ForgeLink');\n // 统一收敛为 getter:name / params 双形态(值或函数)均保持响应式\n const nameGetter = () => (typeof props.name === 'function' ? props.name() : props.name);\n const paramsGetter = () =>\n typeof props.params === 'function' ? props.params() : props.params;\n const state = useForgeRouteState(props.level, nameGetter, paramsGetter, {\n onDegrade: (e) => reportDegrade('ForgeLink', e, forge.warnings),\n });\n const unloadWarned = { value: false };\n // 链接组件解析:显式 as prop 优先(同时收 href+to,对齐 react 包契约);\n // 否则探测 RouterLink(app.use(router) 时全局注册,app 生命周期内不变,setup 时探测一次即可)\n const routerLink = resolveRouterLink();\n\n return () => {\n const url = state.href.value;\n const loaded = url !== '';\n if (!loaded && !forge.isLoaded(props.level)) {\n warnUnloadedOnce('ForgeLink', props.level, unloadWarned);\n }\n // 三态分流:未加载 → loading;解析失败 → error(未传回落 loading)\n if (!state.isLevelLoaded.value) return slots.loading ? slots.loading() : null;\n if (state.error.value != null) {\n return slots.error ? slots.error({ error: state.error.value }) : (slots.loading ? slots.loading() : null);\n }\n\n const children = slots.default ? slots.default() : undefined;\n if (props.as) {\n // 手动注入的链接组件:同时收到 href 与 to(对齐 @route-forge/react 的 as 契约)\n return h(\n props.as as object,\n { ...(attrs as Record<string, unknown>), to: url, href: url },\n { default: () => children },\n );\n }\n if (routerLink) {\n return h(\n routerLink,\n { ...(attrs as Record<string, unknown>), to: url },\n { default: () => children },\n );\n }\n return h('a', { ...(attrs as Record<string, unknown>), href: url }, children);\n };\n },\n});\n\nexport type ForgeLinkProps = InstanceType<typeof ForgeLink>['$props'];\n","/**\n * ForgeReady:ready 门闩组件——直接 mount 也能拿到 `ready().then(mount)` 同级的确定性。\n *\n * - ready() 未 resolve 时渲染 `fallback` 插槽(缺省不渲染任何内容),resolve 后渲染 default 插槽\n * - forge 已就绪(core `isReady()`)时同步开门,不产生多余的 fallback 首帧\n * - ready() reject:响亮 `console.error` 且保持闭门(与 `ready().then(mount).catch` 的语义对齐,\n * 不静默)——需要 reject 后继续渲染请勿使用本组件,改用 `ready().then(mount, onRejected)`\n *\n * @example\n * ```vue\n * <ForgeReady>\n * <template #fallback><Splash /></template>\n * <RouterView />\n * </ForgeReady>\n * ```\n */\n\nimport { defineComponent, ref, type SlotsType, type VNode } from 'vue';\nimport { useInjectedForge } from '../useInjectedForge.js';\n\nexport const ForgeReady = defineComponent({\n name: 'ForgeReady',\n slots: Object as SlotsType<{\n /** ready 后渲染的内容 */\n default?: () => VNode[];\n /** 门闩期间的占位 UI(缺省不渲染任何内容) */\n fallback?: () => VNode[];\n }>,\n setup(_, { slots }) {\n const forge = useInjectedForge('ForgeReady');\n const open = ref(forge.isReady());\n if (!open.value) {\n forge.ready().then(\n () => {\n open.value = true;\n },\n (err) => {\n console.error(\n '[route-forge] ForgeReady: ready() rejected — content stays gated (same as an unhandled ready().catch)',\n err,\n );\n },\n );\n }\n return () => (open.value ? slots.default?.() : (slots.fallback ? slots.fallback() : null));\n },\n});\n\nexport type ForgeReadyProps = InstanceType<typeof ForgeReady>['$props'];\n"]}
|