@real-router/lifecycle-plugin 0.2.1 → 0.4.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 +58 -23
- package/dist/cjs/index.d.ts +24 -11
- package/dist/cjs/index.d.ts.map +1 -1
- package/dist/cjs/index.js +1 -1
- package/dist/cjs/index.js.map +1 -1
- package/dist/esm/index.d.mts +24 -11
- package/dist/esm/index.d.mts.map +1 -1
- package/dist/esm/index.mjs +1 -1
- package/dist/esm/index.mjs.map +1 -1
- package/package.json +5 -4
- package/src/factory.ts +41 -18
- package/src/index.ts +17 -10
- package/src/types.ts +14 -1
package/README.md
CHANGED
|
@@ -5,18 +5,18 @@
|
|
|
5
5
|
[](https://bundlejs.com/?q=@real-router/lifecycle-plugin&treeshake=[*])
|
|
6
6
|
[](../../LICENSE)
|
|
7
7
|
|
|
8
|
-
> Route-level lifecycle hooks for [Real-Router](https://github.com/greydragon888/real-router). Add `onEnter`, `onStay`, `onLeave` callbacks directly to route definitions.
|
|
8
|
+
> Route-level lifecycle hooks for [Real-Router](https://github.com/greydragon888/real-router). Add `onNavigate`, `onEnter`, `onStay`, `onLeave` callbacks directly to route definitions.
|
|
9
9
|
|
|
10
10
|
```typescript
|
|
11
11
|
// Without plugin — scattered subscribe() calls with route checks:
|
|
12
12
|
router.subscribe(({ route, previousRoute }) => {
|
|
13
|
-
if (route.name === "
|
|
13
|
+
if (route.name === "catalog") loadServices(route.params);
|
|
14
14
|
if (previousRoute?.name === "editor") saveEditorState();
|
|
15
15
|
});
|
|
16
16
|
|
|
17
17
|
// With plugin — declarative, per-route:
|
|
18
|
-
{ name: "
|
|
19
|
-
{ name: "editor", path: "/editor", onLeave: () => saveEditorState() }
|
|
18
|
+
{ name: "catalog", path: "/catalog?q&sort", onNavigate: () => (s) => loadServices(s.params) }
|
|
19
|
+
{ name: "editor", path: "/editor", onLeave: () => () => saveEditorState() }
|
|
20
20
|
```
|
|
21
21
|
|
|
22
22
|
## Installation
|
|
@@ -35,20 +35,26 @@ import { lifecyclePluginFactory } from "@real-router/lifecycle-plugin";
|
|
|
35
35
|
|
|
36
36
|
const routes = [
|
|
37
37
|
{
|
|
38
|
-
name: "
|
|
39
|
-
path: "/",
|
|
40
|
-
|
|
41
|
-
|
|
38
|
+
name: "services.catalog",
|
|
39
|
+
path: "/catalog?q&sort&dir",
|
|
40
|
+
// Fires on entry AND on param-change — recommended default
|
|
41
|
+
onNavigate: () => (toState) => {
|
|
42
|
+
loadServices(toState.params);
|
|
42
43
|
},
|
|
43
44
|
},
|
|
44
45
|
{
|
|
45
|
-
name: "
|
|
46
|
-
path: "/
|
|
47
|
-
|
|
48
|
-
|
|
46
|
+
name: "chat",
|
|
47
|
+
path: "/chat/:roomId",
|
|
48
|
+
// Orthogonal: onEnter covers entry-only setup, onNavigate covers
|
|
49
|
+
// every navigation (including entry). Both fire on entry.
|
|
50
|
+
onEnter: () => (toState) => {
|
|
51
|
+
chatSocket.connect(toState.params.roomId);
|
|
49
52
|
},
|
|
50
|
-
|
|
51
|
-
|
|
53
|
+
onNavigate: () => (toState) => {
|
|
54
|
+
loadMessages(toState.params.roomId);
|
|
55
|
+
},
|
|
56
|
+
onLeave: () => () => {
|
|
57
|
+
chatSocket.disconnect();
|
|
52
58
|
},
|
|
53
59
|
},
|
|
54
60
|
];
|
|
@@ -59,15 +65,31 @@ router.usePlugin(lifecyclePluginFactory());
|
|
|
59
65
|
await router.start("/");
|
|
60
66
|
```
|
|
61
67
|
|
|
68
|
+
> **Start with `onNavigate`.** It covers the most common case — running the same logic whenever the route is the navigation target (data loading, analytics, UI reset). Add `onEnter` or `onStay` for extra case-specific logic.
|
|
69
|
+
|
|
62
70
|
## Hook Reference
|
|
63
71
|
|
|
64
|
-
| Hook
|
|
65
|
-
|
|
|
66
|
-
| `
|
|
67
|
-
| `
|
|
68
|
-
| `
|
|
72
|
+
| Hook | Fires when | Typical use case |
|
|
73
|
+
| ------------ | --------------------------------------- | ------------------------------------------- |
|
|
74
|
+
| `onNavigate` | Any successful navigation to the route | Data loading, analytics, UI reset (default) |
|
|
75
|
+
| `onEnter` | Route is entered | Entry-only setup (open socket, scroll top) |
|
|
76
|
+
| `onStay` | Same route, params changed | Stay-only logic (incremental updates) |
|
|
77
|
+
| `onLeave` | Route is left | Cleanup timers, save state |
|
|
78
|
+
|
|
79
|
+
**Orthogonal dispatch:** `onEnter` / `onStay` / `onNavigate` fire independently based on their own conditions. On entry, `onEnter` **and** `onNavigate` fire. On param-change, `onStay` **and** `onNavigate` fire. Each hook is composable — declaring one never silences another.
|
|
80
|
+
|
|
81
|
+
Each hook field is a **factory function** `(router, getDependency) => (toState, fromState?) => void`. The factory runs once per route; the returned callback is cached and invoked on each matching transition. When you don't need DI, omit the factory params:
|
|
69
82
|
|
|
70
|
-
|
|
83
|
+
```typescript
|
|
84
|
+
// Without DI — ignore factory params:
|
|
85
|
+
onEnter: () => (toState) => { console.log("entered", toState.name); }
|
|
86
|
+
|
|
87
|
+
// With DI — access router and dependencies:
|
|
88
|
+
onEnter: (router, getDependency) => (toState) => {
|
|
89
|
+
const analytics = getDependency("analytics");
|
|
90
|
+
analytics.track("page_viewed", { route: toState.name });
|
|
91
|
+
}
|
|
92
|
+
```
|
|
71
93
|
|
|
72
94
|
### Execution order
|
|
73
95
|
|
|
@@ -75,13 +97,26 @@ All hooks receive `(toState: State, fromState: State | undefined) => void`.
|
|
|
75
97
|
|
|
76
98
|
## Use Cases
|
|
77
99
|
|
|
100
|
+
### Data loading (onNavigate — recommended default)
|
|
101
|
+
|
|
102
|
+
```typescript
|
|
103
|
+
{
|
|
104
|
+
name: "services.catalog",
|
|
105
|
+
path: "/catalog?q&sort&dir",
|
|
106
|
+
onNavigate: () => (toState) => {
|
|
107
|
+
// Fires on entry from another route AND on filter/sort param changes
|
|
108
|
+
loadServices(toState.params);
|
|
109
|
+
},
|
|
110
|
+
}
|
|
111
|
+
```
|
|
112
|
+
|
|
78
113
|
### Analytics tracking
|
|
79
114
|
|
|
80
115
|
```typescript
|
|
81
116
|
{
|
|
82
117
|
name: "product",
|
|
83
118
|
path: "/products/:id",
|
|
84
|
-
onEnter: (toState) => {
|
|
119
|
+
onEnter: () => (toState) => {
|
|
85
120
|
analytics.track("product_viewed", { productId: toState.params.id });
|
|
86
121
|
},
|
|
87
122
|
}
|
|
@@ -93,7 +128,7 @@ All hooks receive `(toState: State, fromState: State | undefined) => void`.
|
|
|
93
128
|
{
|
|
94
129
|
name: "editor",
|
|
95
130
|
path: "/editor/:docId",
|
|
96
|
-
onLeave: () => {
|
|
131
|
+
onLeave: () => () => {
|
|
97
132
|
autosaveTimer.clear();
|
|
98
133
|
webSocket.disconnect();
|
|
99
134
|
},
|
|
@@ -106,7 +141,7 @@ All hooks receive `(toState: State, fromState: State | undefined) => void`.
|
|
|
106
141
|
{
|
|
107
142
|
name: "search",
|
|
108
143
|
path: "/search?q",
|
|
109
|
-
onStay: (toState) => {
|
|
144
|
+
onStay: () => (toState) => {
|
|
110
145
|
searchStore.setQuery(toState.params.q);
|
|
111
146
|
},
|
|
112
147
|
}
|
package/dist/cjs/index.d.ts
CHANGED
|
@@ -1,11 +1,18 @@
|
|
|
1
|
-
import { PluginFactory, State } from "@real-router/core";
|
|
1
|
+
import { DefaultDependencies, PluginFactory, State } from "@real-router/core";
|
|
2
|
+
import { DefaultDependencies as DefaultDependencies$1, Router } from "@real-router/types";
|
|
2
3
|
|
|
3
4
|
//#region src/types.d.ts
|
|
4
5
|
/**
|
|
5
6
|
* Lifecycle hook callback for route transitions.
|
|
6
|
-
* Fire-and-forget: return values are ignored, errors
|
|
7
|
+
* Fire-and-forget: return values are ignored, errors propagate to EventEmitter (logged to stderr).
|
|
7
8
|
*/
|
|
8
9
|
type LifecycleHook = (toState: State, fromState: State | undefined) => void;
|
|
10
|
+
/**
|
|
11
|
+
* Factory function for creating lifecycle hooks.
|
|
12
|
+
* Receives the router instance and a dependency getter (same pattern as GuardFnFactory).
|
|
13
|
+
* Factory runs once at first invocation; the returned hook is cached per route.
|
|
14
|
+
*/
|
|
15
|
+
type LifecycleHookFactory<Dependencies extends DefaultDependencies$1 = DefaultDependencies$1> = (router: Router<Dependencies>, getDependency: <K extends keyof Dependencies>(key: K) => Dependencies[K]) => LifecycleHook;
|
|
9
16
|
//#endregion
|
|
10
17
|
//#region src/factory.d.ts
|
|
11
18
|
declare function lifecyclePluginFactory(): PluginFactory;
|
|
@@ -13,18 +20,24 @@ declare function lifecyclePluginFactory(): PluginFactory;
|
|
|
13
20
|
//#region src/index.d.ts
|
|
14
21
|
/**
|
|
15
22
|
* Module augmentation for real-router.
|
|
16
|
-
* Extends Route interface with lifecycle
|
|
23
|
+
* Extends Route interface with lifecycle hook factories.
|
|
17
24
|
*/
|
|
18
25
|
declare module "@real-router/core" {
|
|
19
|
-
interface Route {
|
|
20
|
-
/**
|
|
21
|
-
onEnter?:
|
|
22
|
-
/**
|
|
23
|
-
onStay?:
|
|
24
|
-
/**
|
|
25
|
-
onLeave?:
|
|
26
|
+
interface Route<Dependencies extends DefaultDependencies> {
|
|
27
|
+
/** Factory that returns a hook called when this route segment is newly activated (entered). */
|
|
28
|
+
onEnter?: LifecycleHookFactory<Dependencies>;
|
|
29
|
+
/** Factory that returns a hook called when this route segment stays active but params changed. */
|
|
30
|
+
onStay?: LifecycleHookFactory<Dependencies>;
|
|
31
|
+
/** Factory that returns a hook called when this route segment is deactivated (left). */
|
|
32
|
+
onLeave?: LifecycleHookFactory<Dependencies>;
|
|
33
|
+
/**
|
|
34
|
+
* Factory that returns a hook called on every successful navigation to this route
|
|
35
|
+
* (both entry and param-change). Acts as a fallback when `onEnter` or `onStay`
|
|
36
|
+
* is not defined for the corresponding case.
|
|
37
|
+
*/
|
|
38
|
+
onNavigate?: LifecycleHookFactory<Dependencies>;
|
|
26
39
|
}
|
|
27
40
|
}
|
|
28
41
|
//#endregion
|
|
29
|
-
export { type LifecycleHook, lifecyclePluginFactory };
|
|
42
|
+
export { type LifecycleHook, type LifecycleHookFactory, lifecyclePluginFactory };
|
|
30
43
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/cjs/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","names":[],"sources":["../../src/types.ts","../../src/factory.ts","../../src/index.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../../src/types.ts","../../src/factory.ts","../../src/index.ts"],"mappings":";;;;;;AAOA;;KAAY,aAAA,IACV,OAAA,EAAS,KAAA,EACT,SAAA,EAAW,KAAA;;;;;;KAQD,oBAAA,sBACW,qBAAA,GAAsB,qBAAA,KAE3C,MAAA,EAAQ,MAAA,CAAO,YAAA,GACf,aAAA,mBAAgC,YAAA,EAAc,GAAA,EAAK,CAAA,KAAM,YAAA,CAAa,CAAA,MACnE,aAAA;;;iBC6CW,sBAAA,CAAA,GAA0B,aAAA;;;;AD5D1C;;;;YEGY,KAAA,sBAA2B,mBAAA;IFFrC;IEIE,OAAA,GAAU,oBAAA,CAAqB,YAAA;IFHjC;IEKE,MAAA,GAAS,oBAAA,CAAqB,YAAA;IFLJ;IEO1B,OAAA,GAAU,oBAAA,CAAqB,YAAA;IFCH;;;;;IEK5B,UAAA,GAAa,oBAAA,CAAqB,YAAA;EAAA;AAAA"}
|
package/dist/cjs/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});let e=require(`@real-router/core/api`);function t(
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});let e=require(`@real-router/core/api`);function t(...t){let[n,r]=t,i=(0,e.getPluginApi)(n),a=new Map;function o(e,t){let o=`${e}:${t}`,s=i.getRouteConfig(t),c=typeof s?.[e]==`function`?s[e]:void 0;if(!c){a.delete(o);return}let l=a.get(o);if(l?.factory===c)return l.hook;let u=c(n,r);return a.set(o,{hook:u,factory:c}),u}return{onTransitionLeaveApprove:(e,t)=>{t&&e.name!==t.name&&o(`onLeave`,t.name)?.(e,t)},onTransitionSuccess:(e,t)=>{e.name===t?.name?o(`onStay`,e.name)?.(e,t):o(`onEnter`,e.name)?.(e,t),o(`onNavigate`,e.name)?.(e,t)}}}function n(){return t}exports.lifecyclePluginFactory=n;
|
|
2
2
|
//# sourceMappingURL=index.js.map
|
package/dist/cjs/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../../src/factory.ts"],"sourcesContent":["import { getPluginApi } from \"@real-router/core/api\";\n\nimport type { LifecycleHook } from \"./types\";\nimport type { PluginFactory, State } from \"@real-router/core\";\
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../src/factory.ts"],"sourcesContent":["import { getPluginApi } from \"@real-router/core/api\";\n\nimport type { LifecycleHook, LifecycleHookFactory } from \"./types\";\nimport type { PluginFactory, State } from \"@real-router/core\";\n\nfunction createPlugin(\n ...args: Parameters<PluginFactory>\n): ReturnType<PluginFactory> {\n const [router, getDependency] = args;\n const api = getPluginApi(router);\n const compiledHooks = new Map<\n string,\n { hook: LifecycleHook; factory: LifecycleHookFactory }\n >();\n\n function compileHook(\n hookName: \"onEnter\" | \"onStay\" | \"onLeave\" | \"onNavigate\",\n routeName: string,\n ): LifecycleHook | undefined {\n const key = `${hookName}:${routeName}`;\n const config = api.getRouteConfig(routeName);\n const factory =\n typeof config?.[hookName] === \"function\"\n ? (config[hookName] as LifecycleHookFactory)\n : undefined;\n\n if (!factory) {\n compiledHooks.delete(key);\n\n return undefined;\n }\n\n const cached = compiledHooks.get(key);\n\n if (cached?.factory === factory) {\n return cached.hook;\n }\n\n const hook = factory(router, getDependency);\n\n compiledHooks.set(key, { hook, factory });\n\n return hook;\n }\n\n return {\n onTransitionLeaveApprove: (\n toState: State,\n fromState: State | undefined,\n ) => {\n if (fromState && toState.name !== fromState.name) {\n compileHook(\"onLeave\", fromState.name)?.(toState, fromState);\n }\n },\n\n onTransitionSuccess: (toState: State, fromState: State | undefined) => {\n if (toState.name === fromState?.name) {\n compileHook(\"onStay\", toState.name)?.(toState, fromState);\n } else {\n compileHook(\"onEnter\", toState.name)?.(toState, fromState);\n }\n\n compileHook(\"onNavigate\", toState.name)?.(toState, fromState);\n },\n };\n}\n\nexport function lifecyclePluginFactory(): PluginFactory {\n return createPlugin;\n}\n"],"mappings":"0GAKA,SAAS,EACP,GAAG,EACwB,CAC3B,GAAM,CAAC,EAAQ,GAAiB,EAC1B,GAAA,EAAA,EAAA,cAAmB,EAAO,CAC1B,EAAgB,IAAI,IAK1B,SAAS,EACP,EACA,EAC2B,CAC3B,IAAM,EAAM,GAAG,EAAS,GAAG,IACrB,EAAS,EAAI,eAAe,EAAU,CACtC,EACJ,OAAO,IAAS,IAAc,WACzB,EAAO,GACR,IAAA,GAEN,GAAI,CAAC,EAAS,CACZ,EAAc,OAAO,EAAI,CAEzB,OAGF,IAAM,EAAS,EAAc,IAAI,EAAI,CAErC,GAAI,GAAQ,UAAY,EACtB,OAAO,EAAO,KAGhB,IAAM,EAAO,EAAQ,EAAQ,EAAc,CAI3C,OAFA,EAAc,IAAI,EAAK,CAAE,OAAM,UAAS,CAAC,CAElC,EAGT,MAAO,CACL,0BACE,EACA,IACG,CACC,GAAa,EAAQ,OAAS,EAAU,MAC1C,EAAY,UAAW,EAAU,KAAK,GAAG,EAAS,EAAU,EAIhE,qBAAsB,EAAgB,IAAiC,CACjE,EAAQ,OAAS,GAAW,KAC9B,EAAY,SAAU,EAAQ,KAAK,GAAG,EAAS,EAAU,CAEzD,EAAY,UAAW,EAAQ,KAAK,GAAG,EAAS,EAAU,CAG5D,EAAY,aAAc,EAAQ,KAAK,GAAG,EAAS,EAAU,EAEhE,CAGH,SAAgB,GAAwC,CACtD,OAAO"}
|
package/dist/esm/index.d.mts
CHANGED
|
@@ -1,11 +1,18 @@
|
|
|
1
|
-
import { PluginFactory, State } from "@real-router/core";
|
|
1
|
+
import { DefaultDependencies, PluginFactory, State } from "@real-router/core";
|
|
2
|
+
import { DefaultDependencies as DefaultDependencies$1, Router } from "@real-router/types";
|
|
2
3
|
|
|
3
4
|
//#region src/types.d.ts
|
|
4
5
|
/**
|
|
5
6
|
* Lifecycle hook callback for route transitions.
|
|
6
|
-
* Fire-and-forget: return values are ignored, errors
|
|
7
|
+
* Fire-and-forget: return values are ignored, errors propagate to EventEmitter (logged to stderr).
|
|
7
8
|
*/
|
|
8
9
|
type LifecycleHook = (toState: State, fromState: State | undefined) => void;
|
|
10
|
+
/**
|
|
11
|
+
* Factory function for creating lifecycle hooks.
|
|
12
|
+
* Receives the router instance and a dependency getter (same pattern as GuardFnFactory).
|
|
13
|
+
* Factory runs once at first invocation; the returned hook is cached per route.
|
|
14
|
+
*/
|
|
15
|
+
type LifecycleHookFactory<Dependencies extends DefaultDependencies$1 = DefaultDependencies$1> = (router: Router<Dependencies>, getDependency: <K extends keyof Dependencies>(key: K) => Dependencies[K]) => LifecycleHook;
|
|
9
16
|
//#endregion
|
|
10
17
|
//#region src/factory.d.ts
|
|
11
18
|
declare function lifecyclePluginFactory(): PluginFactory;
|
|
@@ -13,18 +20,24 @@ declare function lifecyclePluginFactory(): PluginFactory;
|
|
|
13
20
|
//#region src/index.d.ts
|
|
14
21
|
/**
|
|
15
22
|
* Module augmentation for real-router.
|
|
16
|
-
* Extends Route interface with lifecycle
|
|
23
|
+
* Extends Route interface with lifecycle hook factories.
|
|
17
24
|
*/
|
|
18
25
|
declare module "@real-router/core" {
|
|
19
|
-
interface Route {
|
|
20
|
-
/**
|
|
21
|
-
onEnter?:
|
|
22
|
-
/**
|
|
23
|
-
onStay?:
|
|
24
|
-
/**
|
|
25
|
-
onLeave?:
|
|
26
|
+
interface Route<Dependencies extends DefaultDependencies> {
|
|
27
|
+
/** Factory that returns a hook called when this route segment is newly activated (entered). */
|
|
28
|
+
onEnter?: LifecycleHookFactory<Dependencies>;
|
|
29
|
+
/** Factory that returns a hook called when this route segment stays active but params changed. */
|
|
30
|
+
onStay?: LifecycleHookFactory<Dependencies>;
|
|
31
|
+
/** Factory that returns a hook called when this route segment is deactivated (left). */
|
|
32
|
+
onLeave?: LifecycleHookFactory<Dependencies>;
|
|
33
|
+
/**
|
|
34
|
+
* Factory that returns a hook called on every successful navigation to this route
|
|
35
|
+
* (both entry and param-change). Acts as a fallback when `onEnter` or `onStay`
|
|
36
|
+
* is not defined for the corresponding case.
|
|
37
|
+
*/
|
|
38
|
+
onNavigate?: LifecycleHookFactory<Dependencies>;
|
|
26
39
|
}
|
|
27
40
|
}
|
|
28
41
|
//#endregion
|
|
29
|
-
export { type LifecycleHook, lifecyclePluginFactory };
|
|
42
|
+
export { type LifecycleHook, type LifecycleHookFactory, lifecyclePluginFactory };
|
|
30
43
|
//# sourceMappingURL=index.d.mts.map
|
package/dist/esm/index.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","names":[],"sources":["../../src/types.ts","../../src/factory.ts","../../src/index.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../../src/types.ts","../../src/factory.ts","../../src/index.ts"],"mappings":";;;;;;AAOA;;KAAY,aAAA,IACV,OAAA,EAAS,KAAA,EACT,SAAA,EAAW,KAAA;;;;;;KAQD,oBAAA,sBACW,qBAAA,GAAsB,qBAAA,KAE3C,MAAA,EAAQ,MAAA,CAAO,YAAA,GACf,aAAA,mBAAgC,YAAA,EAAc,GAAA,EAAK,CAAA,KAAM,YAAA,CAAa,CAAA,MACnE,aAAA;;;iBC6CW,sBAAA,CAAA,GAA0B,aAAA;;;;AD5D1C;;;;YEGY,KAAA,sBAA2B,mBAAA;IFFrC;IEIE,OAAA,GAAU,oBAAA,CAAqB,YAAA;IFHjC;IEKE,MAAA,GAAS,oBAAA,CAAqB,YAAA;IFLJ;IEO1B,OAAA,GAAU,oBAAA,CAAqB,YAAA;IFCH;;;;;IEK5B,UAAA,GAAa,oBAAA,CAAqB,YAAA;EAAA;AAAA"}
|
package/dist/esm/index.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{getPluginApi as e}from"@real-router/core/api";function t(
|
|
1
|
+
import{getPluginApi as e}from"@real-router/core/api";function t(...t){let[n,r]=t,i=e(n),a=new Map;function o(e,t){let o=`${e}:${t}`,s=i.getRouteConfig(t),c=typeof s?.[e]==`function`?s[e]:void 0;if(!c){a.delete(o);return}let l=a.get(o);if(l?.factory===c)return l.hook;let u=c(n,r);return a.set(o,{hook:u,factory:c}),u}return{onTransitionLeaveApprove:(e,t)=>{t&&e.name!==t.name&&o(`onLeave`,t.name)?.(e,t)},onTransitionSuccess:(e,t)=>{e.name===t?.name?o(`onStay`,e.name)?.(e,t):o(`onEnter`,e.name)?.(e,t),o(`onNavigate`,e.name)?.(e,t)}}}function n(){return t}export{n as lifecyclePluginFactory};
|
|
2
2
|
//# sourceMappingURL=index.mjs.map
|
package/dist/esm/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":[],"sources":["../../src/factory.ts"],"sourcesContent":["import { getPluginApi } from \"@real-router/core/api\";\n\nimport type { LifecycleHook } from \"./types\";\nimport type { PluginFactory, State } from \"@real-router/core\";\
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../../src/factory.ts"],"sourcesContent":["import { getPluginApi } from \"@real-router/core/api\";\n\nimport type { LifecycleHook, LifecycleHookFactory } from \"./types\";\nimport type { PluginFactory, State } from \"@real-router/core\";\n\nfunction createPlugin(\n ...args: Parameters<PluginFactory>\n): ReturnType<PluginFactory> {\n const [router, getDependency] = args;\n const api = getPluginApi(router);\n const compiledHooks = new Map<\n string,\n { hook: LifecycleHook; factory: LifecycleHookFactory }\n >();\n\n function compileHook(\n hookName: \"onEnter\" | \"onStay\" | \"onLeave\" | \"onNavigate\",\n routeName: string,\n ): LifecycleHook | undefined {\n const key = `${hookName}:${routeName}`;\n const config = api.getRouteConfig(routeName);\n const factory =\n typeof config?.[hookName] === \"function\"\n ? (config[hookName] as LifecycleHookFactory)\n : undefined;\n\n if (!factory) {\n compiledHooks.delete(key);\n\n return undefined;\n }\n\n const cached = compiledHooks.get(key);\n\n if (cached?.factory === factory) {\n return cached.hook;\n }\n\n const hook = factory(router, getDependency);\n\n compiledHooks.set(key, { hook, factory });\n\n return hook;\n }\n\n return {\n onTransitionLeaveApprove: (\n toState: State,\n fromState: State | undefined,\n ) => {\n if (fromState && toState.name !== fromState.name) {\n compileHook(\"onLeave\", fromState.name)?.(toState, fromState);\n }\n },\n\n onTransitionSuccess: (toState: State, fromState: State | undefined) => {\n if (toState.name === fromState?.name) {\n compileHook(\"onStay\", toState.name)?.(toState, fromState);\n } else {\n compileHook(\"onEnter\", toState.name)?.(toState, fromState);\n }\n\n compileHook(\"onNavigate\", toState.name)?.(toState, fromState);\n },\n };\n}\n\nexport function lifecyclePluginFactory(): PluginFactory {\n return createPlugin;\n}\n"],"mappings":"qDAKA,SAAS,EACP,GAAG,EACwB,CAC3B,GAAM,CAAC,EAAQ,GAAiB,EAC1B,EAAM,EAAa,EAAO,CAC1B,EAAgB,IAAI,IAK1B,SAAS,EACP,EACA,EAC2B,CAC3B,IAAM,EAAM,GAAG,EAAS,GAAG,IACrB,EAAS,EAAI,eAAe,EAAU,CACtC,EACJ,OAAO,IAAS,IAAc,WACzB,EAAO,GACR,IAAA,GAEN,GAAI,CAAC,EAAS,CACZ,EAAc,OAAO,EAAI,CAEzB,OAGF,IAAM,EAAS,EAAc,IAAI,EAAI,CAErC,GAAI,GAAQ,UAAY,EACtB,OAAO,EAAO,KAGhB,IAAM,EAAO,EAAQ,EAAQ,EAAc,CAI3C,OAFA,EAAc,IAAI,EAAK,CAAE,OAAM,UAAS,CAAC,CAElC,EAGT,MAAO,CACL,0BACE,EACA,IACG,CACC,GAAa,EAAQ,OAAS,EAAU,MAC1C,EAAY,UAAW,EAAU,KAAK,GAAG,EAAS,EAAU,EAIhE,qBAAsB,EAAgB,IAAiC,CACjE,EAAQ,OAAS,GAAW,KAC9B,EAAY,SAAU,EAAQ,KAAK,GAAG,EAAS,EAAU,CAEzD,EAAY,UAAW,EAAQ,KAAK,GAAG,EAAS,EAAU,CAG5D,EAAY,aAAc,EAAQ,KAAK,GAAG,EAAS,EAAU,EAEhE,CAGH,SAAgB,GAAwC,CACtD,OAAO"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@real-router/lifecycle-plugin",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"type": "commonjs",
|
|
5
5
|
"description": "Route-level lifecycle hooks: onEnter, onStay, onLeave",
|
|
6
6
|
"main": "./dist/cjs/index.js",
|
|
@@ -45,16 +45,17 @@
|
|
|
45
45
|
"homepage": "https://github.com/greydragon888/real-router",
|
|
46
46
|
"sideEffects": false,
|
|
47
47
|
"dependencies": {
|
|
48
|
-
"@real-router/core": "^0.48.0"
|
|
48
|
+
"@real-router/core": "^0.48.0",
|
|
49
|
+
"@real-router/types": "^0.34.0"
|
|
49
50
|
},
|
|
50
51
|
"scripts": {
|
|
51
|
-
"build": "tsdown --config-loader unrun",
|
|
52
52
|
"test": "vitest run",
|
|
53
53
|
"test:properties": "vitest run --config vitest.config.properties.mts",
|
|
54
54
|
"test:stress": "vitest --config vitest.config.stress.mts --run",
|
|
55
55
|
"type-check": "tsc --noEmit",
|
|
56
56
|
"lint": "eslint --cache --ext .ts src/ tests/ --fix --max-warnings 0",
|
|
57
57
|
"lint:package": "publint",
|
|
58
|
-
"lint:types": "attw --pack ."
|
|
58
|
+
"lint:types": "attw --pack .",
|
|
59
|
+
"bundle": "tsdown --config-loader unrun"
|
|
59
60
|
}
|
|
60
61
|
}
|
package/src/factory.ts
CHANGED
|
@@ -1,26 +1,47 @@
|
|
|
1
1
|
import { getPluginApi } from "@real-router/core/api";
|
|
2
2
|
|
|
3
|
-
import type { LifecycleHook } from "./types";
|
|
3
|
+
import type { LifecycleHook, LifecycleHookFactory } from "./types";
|
|
4
4
|
import type { PluginFactory, State } from "@real-router/core";
|
|
5
|
-
import type { PluginApi } from "@real-router/core/api";
|
|
6
5
|
|
|
7
|
-
function
|
|
8
|
-
|
|
9
|
-
|
|
6
|
+
function createPlugin(
|
|
7
|
+
...args: Parameters<PluginFactory>
|
|
8
|
+
): ReturnType<PluginFactory> {
|
|
9
|
+
const [router, getDependency] = args;
|
|
10
|
+
const api = getPluginApi(router);
|
|
11
|
+
const compiledHooks = new Map<
|
|
12
|
+
string,
|
|
13
|
+
{ hook: LifecycleHook; factory: LifecycleHookFactory }
|
|
14
|
+
>();
|
|
15
|
+
|
|
16
|
+
function compileHook(
|
|
17
|
+
hookName: "onEnter" | "onStay" | "onLeave" | "onNavigate",
|
|
10
18
|
routeName: string,
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
const
|
|
19
|
+
): LifecycleHook | undefined {
|
|
20
|
+
const key = `${hookName}:${routeName}`;
|
|
21
|
+
const config = api.getRouteConfig(routeName);
|
|
22
|
+
const factory =
|
|
23
|
+
typeof config?.[hookName] === "function"
|
|
24
|
+
? (config[hookName] as LifecycleHookFactory)
|
|
25
|
+
: undefined;
|
|
26
|
+
|
|
27
|
+
if (!factory) {
|
|
28
|
+
compiledHooks.delete(key);
|
|
15
29
|
|
|
16
|
-
|
|
17
|
-
(hook as LifecycleHook)(toState, fromState);
|
|
30
|
+
return undefined;
|
|
18
31
|
}
|
|
19
|
-
};
|
|
20
|
-
}
|
|
21
32
|
|
|
22
|
-
|
|
23
|
-
|
|
33
|
+
const cached = compiledHooks.get(key);
|
|
34
|
+
|
|
35
|
+
if (cached?.factory === factory) {
|
|
36
|
+
return cached.hook;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const hook = factory(router, getDependency);
|
|
40
|
+
|
|
41
|
+
compiledHooks.set(key, { hook, factory });
|
|
42
|
+
|
|
43
|
+
return hook;
|
|
44
|
+
}
|
|
24
45
|
|
|
25
46
|
return {
|
|
26
47
|
onTransitionLeaveApprove: (
|
|
@@ -28,16 +49,18 @@ function createPlugin(router: Parameters<PluginFactory>[0]) {
|
|
|
28
49
|
fromState: State | undefined,
|
|
29
50
|
) => {
|
|
30
51
|
if (fromState && toState.name !== fromState.name) {
|
|
31
|
-
|
|
52
|
+
compileHook("onLeave", fromState.name)?.(toState, fromState);
|
|
32
53
|
}
|
|
33
54
|
},
|
|
34
55
|
|
|
35
56
|
onTransitionSuccess: (toState: State, fromState: State | undefined) => {
|
|
36
57
|
if (toState.name === fromState?.name) {
|
|
37
|
-
|
|
58
|
+
compileHook("onStay", toState.name)?.(toState, fromState);
|
|
38
59
|
} else {
|
|
39
|
-
|
|
60
|
+
compileHook("onEnter", toState.name)?.(toState, fromState);
|
|
40
61
|
}
|
|
62
|
+
|
|
63
|
+
compileHook("onNavigate", toState.name)?.(toState, fromState);
|
|
41
64
|
},
|
|
42
65
|
};
|
|
43
66
|
}
|
package/src/index.ts
CHANGED
|
@@ -1,20 +1,27 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { LifecycleHookFactory } from "./types";
|
|
2
|
+
import type { DefaultDependencies } from "@real-router/core";
|
|
2
3
|
|
|
3
4
|
export { lifecyclePluginFactory } from "./factory";
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* Module augmentation for real-router.
|
|
7
|
-
* Extends Route interface with lifecycle
|
|
8
|
+
* Extends Route interface with lifecycle hook factories.
|
|
8
9
|
*/
|
|
9
10
|
declare module "@real-router/core" {
|
|
10
|
-
interface Route {
|
|
11
|
-
/**
|
|
12
|
-
onEnter?:
|
|
13
|
-
/**
|
|
14
|
-
onStay?:
|
|
15
|
-
/**
|
|
16
|
-
onLeave?:
|
|
11
|
+
interface Route<Dependencies extends DefaultDependencies> {
|
|
12
|
+
/** Factory that returns a hook called when this route segment is newly activated (entered). */
|
|
13
|
+
onEnter?: LifecycleHookFactory<Dependencies>;
|
|
14
|
+
/** Factory that returns a hook called when this route segment stays active but params changed. */
|
|
15
|
+
onStay?: LifecycleHookFactory<Dependencies>;
|
|
16
|
+
/** Factory that returns a hook called when this route segment is deactivated (left). */
|
|
17
|
+
onLeave?: LifecycleHookFactory<Dependencies>;
|
|
18
|
+
/**
|
|
19
|
+
* Factory that returns a hook called on every successful navigation to this route
|
|
20
|
+
* (both entry and param-change). Acts as a fallback when `onEnter` or `onStay`
|
|
21
|
+
* is not defined for the corresponding case.
|
|
22
|
+
*/
|
|
23
|
+
onNavigate?: LifecycleHookFactory<Dependencies>;
|
|
17
24
|
}
|
|
18
25
|
}
|
|
19
26
|
|
|
20
|
-
export type { LifecycleHook } from "./types";
|
|
27
|
+
export type { LifecycleHook, LifecycleHookFactory } from "./types";
|
package/src/types.ts
CHANGED
|
@@ -1,10 +1,23 @@
|
|
|
1
1
|
import type { State } from "@real-router/core";
|
|
2
|
+
import type { DefaultDependencies, Router } from "@real-router/types";
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* Lifecycle hook callback for route transitions.
|
|
5
|
-
* Fire-and-forget: return values are ignored, errors
|
|
6
|
+
* Fire-and-forget: return values are ignored, errors propagate to EventEmitter (logged to stderr).
|
|
6
7
|
*/
|
|
7
8
|
export type LifecycleHook = (
|
|
8
9
|
toState: State,
|
|
9
10
|
fromState: State | undefined,
|
|
10
11
|
) => void;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Factory function for creating lifecycle hooks.
|
|
15
|
+
* Receives the router instance and a dependency getter (same pattern as GuardFnFactory).
|
|
16
|
+
* Factory runs once at first invocation; the returned hook is cached per route.
|
|
17
|
+
*/
|
|
18
|
+
export type LifecycleHookFactory<
|
|
19
|
+
Dependencies extends DefaultDependencies = DefaultDependencies,
|
|
20
|
+
> = (
|
|
21
|
+
router: Router<Dependencies>,
|
|
22
|
+
getDependency: <K extends keyof Dependencies>(key: K) => Dependencies[K],
|
|
23
|
+
) => LifecycleHook;
|