@real-router/ssr-data-plugin 0.0.1
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 +120 -0
- package/dist/cjs/index.d.ts +14 -0
- package/dist/cjs/index.js +1 -0
- package/dist/cjs/index.js.map +1 -0
- package/dist/cjs/metafile-cjs.json +1 -0
- package/dist/esm/index.d.mts +14 -0
- package/dist/esm/index.mjs +1 -0
- package/dist/esm/index.mjs.map +1 -0
- package/dist/esm/metafile-esm.json +1 -0
- package/package.json +50 -0
- package/src/constants.ts +3 -0
- package/src/factory.ts +43 -0
- package/src/index.ts +11 -0
- package/src/types.ts +5 -0
- package/src/validation.ts +21 -0
package/README.md
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# @real-router/ssr-data-plugin
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@real-router/ssr-data-plugin)
|
|
4
|
+
[](https://www.npmjs.com/package/@real-router/ssr-data-plugin)
|
|
5
|
+
[](https://bundlejs.com/?q=@real-router/ssr-data-plugin&treeshake=[*])
|
|
6
|
+
[](../../LICENSE)
|
|
7
|
+
|
|
8
|
+
> Per-route data loading for SSR with [Real-Router](https://github.com/greydragon888/real-router). Intercepts `start()` to load data before server rendering.
|
|
9
|
+
|
|
10
|
+
```typescript
|
|
11
|
+
// Without plugin:
|
|
12
|
+
const state = await router.start(url);
|
|
13
|
+
const data = await loadRouteData(state.name, state.params); // manual
|
|
14
|
+
|
|
15
|
+
// With plugin:
|
|
16
|
+
router.usePlugin(ssrDataPluginFactory(loaders));
|
|
17
|
+
const state = await router.start(url);
|
|
18
|
+
const data = router.getRouteData(); // loaded automatically
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Installation
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
npm install @real-router/ssr-data-plugin
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
**Peer dependency:** `@real-router/core`
|
|
28
|
+
|
|
29
|
+
## Quick Start
|
|
30
|
+
|
|
31
|
+
```typescript
|
|
32
|
+
import { createRouter } from "@real-router/core";
|
|
33
|
+
import { cloneRouter } from "@real-router/core/api";
|
|
34
|
+
import { ssrDataPluginFactory } from "@real-router/ssr-data-plugin";
|
|
35
|
+
|
|
36
|
+
const loaders = {
|
|
37
|
+
"users.profile": async (params) => fetchUser(params.id),
|
|
38
|
+
"users.list": async () => fetchUsers(),
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
// Base router — created once
|
|
42
|
+
const baseRouter = createRouter(routes, { defaultRoute: "home", allowNotFound: true });
|
|
43
|
+
|
|
44
|
+
// Per-request SSR
|
|
45
|
+
const router = cloneRouter(baseRouter, { isAuthenticated: true });
|
|
46
|
+
router.usePlugin(ssrDataPluginFactory(loaders));
|
|
47
|
+
|
|
48
|
+
const state = await router.start(url);
|
|
49
|
+
const data = router.getRouteData(); // data loaded by matching loader
|
|
50
|
+
|
|
51
|
+
const html = renderToString(<App />);
|
|
52
|
+
router.dispose();
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Configuration
|
|
56
|
+
|
|
57
|
+
Loaders are keyed by **route name** (not path). Each loader receives route `params` and returns a `Promise`:
|
|
58
|
+
|
|
59
|
+
```typescript
|
|
60
|
+
import type { DataLoaderMap } from "@real-router/ssr-data-plugin";
|
|
61
|
+
|
|
62
|
+
const loaders: DataLoaderMap = {
|
|
63
|
+
home: async () => ({ featured: await fetchFeatured() }),
|
|
64
|
+
"users.profile": async (params) => ({ user: await fetchUser(params.id) }),
|
|
65
|
+
"users.list": async () => ({ users: await fetchUsers() }),
|
|
66
|
+
};
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Routes without a matching loader produce no data — `getRouteData()` returns `null`.
|
|
70
|
+
|
|
71
|
+
## Router Extension
|
|
72
|
+
|
|
73
|
+
The plugin extends the router instance with one method via [`extendRouter()`](https://github.com/greydragon888/real-router/wiki/plugin-architecture):
|
|
74
|
+
|
|
75
|
+
| Method | Returns | Description |
|
|
76
|
+
| ---------------------- | --------- | ------------------------------------------ |
|
|
77
|
+
| `getRouteData(state?)` | `unknown` | Get loaded data for current or given state |
|
|
78
|
+
|
|
79
|
+
```typescript
|
|
80
|
+
router.getRouteData(); // data for current state
|
|
81
|
+
router.getRouteData(state); // data for a specific state
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## SSR-Only by Design
|
|
85
|
+
|
|
86
|
+
This plugin intercepts `start()` only — not `navigate()`. In SSR, the flow is:
|
|
87
|
+
|
|
88
|
+
```
|
|
89
|
+
cloneRouter → usePlugin → start(url) → data loaded → renderToString → getRouteData()
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Client-side navigation and data fetching is the application's responsibility (React Query, Suspense, `useEffect`, etc.).
|
|
93
|
+
|
|
94
|
+
## Cleanup
|
|
95
|
+
|
|
96
|
+
```typescript
|
|
97
|
+
const unsubscribe = router.usePlugin(ssrDataPluginFactory(loaders));
|
|
98
|
+
|
|
99
|
+
// Later — removes getRouteData and stops data loading
|
|
100
|
+
unsubscribe();
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
In SSR, `router.dispose()` handles cleanup automatically.
|
|
104
|
+
|
|
105
|
+
## Documentation
|
|
106
|
+
|
|
107
|
+
- [ARCHITECTURE.md](ARCHITECTURE.md) — Design decisions and data flow
|
|
108
|
+
- [SSR Example](../../examples/ssr-react) — Full working example with React + Vite
|
|
109
|
+
|
|
110
|
+
## Related Packages
|
|
111
|
+
|
|
112
|
+
| Package | Description |
|
|
113
|
+
| ---------------------------------------------------------------------------------------- | -------------------------------------- |
|
|
114
|
+
| [@real-router/core](https://www.npmjs.com/package/@real-router/core) | Core router (required peer dependency) |
|
|
115
|
+
| [@real-router/browser-plugin](https://www.npmjs.com/package/@real-router/browser-plugin) | Browser History API integration |
|
|
116
|
+
| [@real-router/react](https://www.npmjs.com/package/@real-router/react) | React bindings |
|
|
117
|
+
|
|
118
|
+
## License
|
|
119
|
+
|
|
120
|
+
[MIT](../../LICENSE) © [Oleg Ivanov](https://github.com/greydragon888)
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { Params, PluginFactory, State } from '@real-router/core';
|
|
2
|
+
|
|
3
|
+
type DataLoaderFn = (params: Params) => Promise<unknown>;
|
|
4
|
+
type DataLoaderMap = Record<string, DataLoaderFn>;
|
|
5
|
+
|
|
6
|
+
declare function ssrDataPluginFactory(loaders: DataLoaderMap): PluginFactory;
|
|
7
|
+
|
|
8
|
+
declare module "@real-router/core" {
|
|
9
|
+
interface Router {
|
|
10
|
+
getRouteData: (state?: State) => unknown;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export { type DataLoaderFn, type DataLoaderMap, ssrDataPluginFactory };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
var t=require("@real-router/core/api"),e="[@real-router/ssr-data-plugin]";exports.ssrDataPluginFactory=function(r){return function(t){if(null===t||"object"!=typeof t)throw new TypeError(`${e} loaders must be a non-null object`);for(const[r,n]of Object.entries(t))if("function"!=typeof n)throw new TypeError(`${e} loader for route "${r}" must be a function`)}(r),e=>{const n=t.getPluginApi(e),o=new WeakMap,a=n.addInterceptor("start",async(t,e)=>{const n=await t(e);return Object.hasOwn(r,n.name)&&o.set(n,await r[n.name](n.params)),n}),u=n.extendRouter({getRouteData(t){const r=t??e.getState();return r?o.get(r)??null:null}});return{teardown(){a(),u()}}}};//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/constants.ts","../../src/validation.ts","../../src/factory.ts"],"names":["api","getPluginApi"],"mappings":";;;AAAO,IAAM,cAAA,GAAiB,iBAAA;AAEvB,IAAM,YAAA,GAAe,iBAAiB,cAAc,CAAA,CAAA,CAAA;;;ACEpD,SAAS,gBACd,OAAA,EACkC;AAClC,EAAA,IAAI,OAAA,KAAY,IAAA,IAAQ,OAAO,OAAA,KAAY,QAAA,EAAU;AACnD,IAAA,MAAM,IAAI,SAAA,CAAU,CAAA,EAAG,YAAY,CAAA,kCAAA,CAAoC,CAAA;AAAA,EACzE;AAEA,EAAA,KAAA,MAAW,CAAC,GAAA,EAAK,KAAK,CAAA,IAAK,MAAA,CAAO,OAAA;AAAA,IAChC;AAAA,GACF,EAAG;AACD,IAAA,IAAI,OAAO,UAAU,UAAA,EAAY;AAC/B,MAAA,MAAM,IAAI,SAAA;AAAA,QACR,CAAA,EAAG,YAAY,CAAA,mBAAA,EAAsB,GAAG,CAAA,oBAAA;AAAA,OAC1C;AAAA,IACF;AAAA,EACF;AACF;;;ACbO,SAAS,qBAAqB,OAAA,EAAuC;AAC1E,EAAA,eAAA,CAAgB,OAAO,CAAA;AAEvB,EAAA,OAAO,CAAC,MAAA,KAAmB;AACzB,IAAA,MAAMA,KAAA,GAAMC,iBAAa,MAAM,CAAA;AAC/B,IAAA,MAAM,SAAA,uBAAgB,OAAA,EAAwB;AAE9C,IAAA,MAAM,yBAAyBD,KAAA,CAAI,cAAA;AAAA,MACjC,OAAA;AAAA,MACA,OAAO,MAAM,IAAA,KAAS;AACpB,QAAA,MAAM,KAAA,GAAQ,MAAM,IAAA,CAAK,IAAI,CAAA;AAE7B,QAAA,IAAI,MAAA,CAAO,MAAA,CAAO,OAAA,EAAS,KAAA,CAAM,IAAI,CAAA,EAAG;AACtC,UAAA,SAAA,CAAU,GAAA,CAAI,OAAO,MAAM,OAAA,CAAQ,MAAM,IAAI,CAAA,CAAE,KAAA,CAAM,MAAM,CAAC,CAAA;AAAA,QAC9D;AAEA,QAAA,OAAO,KAAA;AAAA,MACT;AAAA,KACF;AAEA,IAAA,MAAM,gBAAA,GAAmBA,MAAI,YAAA,CAAa;AAAA,MACxC,aAAa,KAAA,EAAwB;AACnC,QAAA,MAAM,CAAA,GAAI,KAAA,IAAS,MAAA,CAAO,QAAA,EAAS;AAEnC,QAAA,OAAO,CAAA,GAAK,SAAA,CAAU,GAAA,CAAI,CAAC,KAAK,IAAA,GAAQ,IAAA;AAAA,MAC1C;AAAA,KACD,CAAA;AAED,IAAA,OAAO;AAAA,MACL,QAAA,GAAW;AACT,QAAA,sBAAA,EAAuB;AACvB,QAAA,gBAAA,EAAiB;AAAA,MACnB;AAAA,KACF;AAAA,EACF,CAAA;AACF","file":"index.js","sourcesContent":["export const LOGGER_CONTEXT = \"ssr-data-plugin\";\n\nexport const ERROR_PREFIX = `[@real-router/${LOGGER_CONTEXT}]`;\n","import { ERROR_PREFIX } from \"./constants\";\n\nimport type { DataLoaderMap } from \"./types\";\n\nexport function validateLoaders(\n loaders: unknown,\n): asserts loaders is DataLoaderMap {\n if (loaders === null || typeof loaders !== \"object\") {\n throw new TypeError(`${ERROR_PREFIX} loaders must be a non-null object`);\n }\n\n for (const [key, value] of Object.entries(\n loaders as Record<string, unknown>,\n )) {\n if (typeof value !== \"function\") {\n throw new TypeError(\n `${ERROR_PREFIX} loader for route \"${key}\" must be a function`,\n );\n }\n }\n}\n","import { getPluginApi } from \"@real-router/core/api\";\n\nimport { validateLoaders } from \"./validation\";\n\nimport type { DataLoaderMap } from \"./types\";\nimport type { State, PluginFactory, Plugin } from \"@real-router/core\";\n\nexport function ssrDataPluginFactory(loaders: DataLoaderMap): PluginFactory {\n validateLoaders(loaders);\n\n return (router): Plugin => {\n const api = getPluginApi(router);\n const dataStore = new WeakMap<State, unknown>();\n\n const removeStartInterceptor = api.addInterceptor(\n \"start\",\n async (next, path) => {\n const state = await next(path);\n\n if (Object.hasOwn(loaders, state.name)) {\n dataStore.set(state, await loaders[state.name](state.params));\n }\n\n return state;\n },\n );\n\n const removeExtensions = api.extendRouter({\n getRouteData(state?: State): unknown {\n const s = state ?? router.getState();\n\n return s ? (dataStore.get(s) ?? null) : null;\n },\n });\n\n return {\n teardown() {\n removeStartInterceptor();\n removeExtensions();\n },\n };\n };\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"inputs":{"../../node_modules/.pnpm/tsup@8.5.1_jiti@2.6.1_postcss@8.5.8_tsx@4.19.4_typescript@5.9.3_yaml@2.8.2/node_modules/tsup/assets/cjs_shims.js":{"bytes":569,"imports":[],"format":"esm"},"src/constants.ts":{"bytes":114,"imports":[{"path":"/Users/olegivanov/WebstormProjects/real-router/node_modules/.pnpm/tsup@8.5.1_jiti@2.6.1_postcss@8.5.8_tsx@4.19.4_typescript@5.9.3_yaml@2.8.2/node_modules/tsup/assets/cjs_shims.js","kind":"import-statement","external":true}],"format":"esm"},"src/validation.ts":{"bytes":574,"imports":[{"path":"src/constants.ts","kind":"import-statement","original":"./constants"},{"path":"/Users/olegivanov/WebstormProjects/real-router/node_modules/.pnpm/tsup@8.5.1_jiti@2.6.1_postcss@8.5.8_tsx@4.19.4_typescript@5.9.3_yaml@2.8.2/node_modules/tsup/assets/cjs_shims.js","kind":"import-statement","external":true}],"format":"esm"},"src/factory.ts":{"bytes":1095,"imports":[{"path":"@real-router/core/api","kind":"import-statement","external":true},{"path":"src/validation.ts","kind":"import-statement","original":"./validation"},{"path":"/Users/olegivanov/WebstormProjects/real-router/node_modules/.pnpm/tsup@8.5.1_jiti@2.6.1_postcss@8.5.8_tsx@4.19.4_typescript@5.9.3_yaml@2.8.2/node_modules/tsup/assets/cjs_shims.js","kind":"import-statement","external":true}],"format":"esm"},"src/index.ts":{"bytes":271,"imports":[{"path":"src/factory.ts","kind":"import-statement","original":"./factory"},{"path":"/Users/olegivanov/WebstormProjects/real-router/node_modules/.pnpm/tsup@8.5.1_jiti@2.6.1_postcss@8.5.8_tsx@4.19.4_typescript@5.9.3_yaml@2.8.2/node_modules/tsup/assets/cjs_shims.js","kind":"import-statement","external":true}],"format":"esm"}},"outputs":{"dist/cjs/index.js.map":{"imports":[],"exports":[],"inputs":{},"bytes":2982},"dist/cjs/index.js":{"imports":[{"path":"@real-router/core/api","kind":"import-statement","external":true}],"exports":["ssrDataPluginFactory"],"entryPoint":"src/index.ts","inputs":{"src/factory.ts":{"bytesInOutput":853},"src/constants.ts":{"bytesInOutput":95},"src/validation.ts":{"bytesInOutput":397},"src/index.ts":{"bytesInOutput":0}},"bytes":1460}}}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { Params, PluginFactory, State } from '@real-router/core';
|
|
2
|
+
|
|
3
|
+
type DataLoaderFn = (params: Params) => Promise<unknown>;
|
|
4
|
+
type DataLoaderMap = Record<string, DataLoaderFn>;
|
|
5
|
+
|
|
6
|
+
declare function ssrDataPluginFactory(loaders: DataLoaderMap): PluginFactory;
|
|
7
|
+
|
|
8
|
+
declare module "@real-router/core" {
|
|
9
|
+
interface Router {
|
|
10
|
+
getRouteData: (state?: State) => unknown;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export { type DataLoaderFn, type DataLoaderMap, ssrDataPluginFactory };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{getPluginApi as t}from"@real-router/core/api";var e="[@real-router/ssr-data-plugin]";function r(r){return function(t){if(null===t||"object"!=typeof t)throw new TypeError(`${e} loaders must be a non-null object`);for(const[r,n]of Object.entries(t))if("function"!=typeof n)throw new TypeError(`${e} loader for route "${r}" must be a function`)}(r),e=>{const n=t(e),o=new WeakMap,a=n.addInterceptor("start",async(t,e)=>{const n=await t(e);return Object.hasOwn(r,n.name)&&o.set(n,await r[n.name](n.params)),n}),u=n.extendRouter({getRouteData(t){const r=t??e.getState();return r?o.get(r)??null:null}});return{teardown(){a(),u()}}}}export{r as ssrDataPluginFactory};//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/constants.ts","../../src/validation.ts","../../src/factory.ts"],"names":[],"mappings":";;;AAAO,IAAM,cAAA,GAAiB,iBAAA;AAEvB,IAAM,YAAA,GAAe,iBAAiB,cAAc,CAAA,CAAA,CAAA;;;ACEpD,SAAS,gBACd,OAAA,EACkC;AAClC,EAAA,IAAI,OAAA,KAAY,IAAA,IAAQ,OAAO,OAAA,KAAY,QAAA,EAAU;AACnD,IAAA,MAAM,IAAI,SAAA,CAAU,CAAA,EAAG,YAAY,CAAA,kCAAA,CAAoC,CAAA;AAAA,EACzE;AAEA,EAAA,KAAA,MAAW,CAAC,GAAA,EAAK,KAAK,CAAA,IAAK,MAAA,CAAO,OAAA;AAAA,IAChC;AAAA,GACF,EAAG;AACD,IAAA,IAAI,OAAO,UAAU,UAAA,EAAY;AAC/B,MAAA,MAAM,IAAI,SAAA;AAAA,QACR,CAAA,EAAG,YAAY,CAAA,mBAAA,EAAsB,GAAG,CAAA,oBAAA;AAAA,OAC1C;AAAA,IACF;AAAA,EACF;AACF;;;ACbO,SAAS,qBAAqB,OAAA,EAAuC;AAC1E,EAAA,eAAA,CAAgB,OAAO,CAAA;AAEvB,EAAA,OAAO,CAAC,MAAA,KAAmB;AACzB,IAAA,MAAM,GAAA,GAAM,aAAa,MAAM,CAAA;AAC/B,IAAA,MAAM,SAAA,uBAAgB,OAAA,EAAwB;AAE9C,IAAA,MAAM,yBAAyB,GAAA,CAAI,cAAA;AAAA,MACjC,OAAA;AAAA,MACA,OAAO,MAAM,IAAA,KAAS;AACpB,QAAA,MAAM,KAAA,GAAQ,MAAM,IAAA,CAAK,IAAI,CAAA;AAE7B,QAAA,IAAI,MAAA,CAAO,MAAA,CAAO,OAAA,EAAS,KAAA,CAAM,IAAI,CAAA,EAAG;AACtC,UAAA,SAAA,CAAU,GAAA,CAAI,OAAO,MAAM,OAAA,CAAQ,MAAM,IAAI,CAAA,CAAE,KAAA,CAAM,MAAM,CAAC,CAAA;AAAA,QAC9D;AAEA,QAAA,OAAO,KAAA;AAAA,MACT;AAAA,KACF;AAEA,IAAA,MAAM,gBAAA,GAAmB,IAAI,YAAA,CAAa;AAAA,MACxC,aAAa,KAAA,EAAwB;AACnC,QAAA,MAAM,CAAA,GAAI,KAAA,IAAS,MAAA,CAAO,QAAA,EAAS;AAEnC,QAAA,OAAO,CAAA,GAAK,SAAA,CAAU,GAAA,CAAI,CAAC,KAAK,IAAA,GAAQ,IAAA;AAAA,MAC1C;AAAA,KACD,CAAA;AAED,IAAA,OAAO;AAAA,MACL,QAAA,GAAW;AACT,QAAA,sBAAA,EAAuB;AACvB,QAAA,gBAAA,EAAiB;AAAA,MACnB;AAAA,KACF;AAAA,EACF,CAAA;AACF","file":"index.mjs","sourcesContent":["export const LOGGER_CONTEXT = \"ssr-data-plugin\";\n\nexport const ERROR_PREFIX = `[@real-router/${LOGGER_CONTEXT}]`;\n","import { ERROR_PREFIX } from \"./constants\";\n\nimport type { DataLoaderMap } from \"./types\";\n\nexport function validateLoaders(\n loaders: unknown,\n): asserts loaders is DataLoaderMap {\n if (loaders === null || typeof loaders !== \"object\") {\n throw new TypeError(`${ERROR_PREFIX} loaders must be a non-null object`);\n }\n\n for (const [key, value] of Object.entries(\n loaders as Record<string, unknown>,\n )) {\n if (typeof value !== \"function\") {\n throw new TypeError(\n `${ERROR_PREFIX} loader for route \"${key}\" must be a function`,\n );\n }\n }\n}\n","import { getPluginApi } from \"@real-router/core/api\";\n\nimport { validateLoaders } from \"./validation\";\n\nimport type { DataLoaderMap } from \"./types\";\nimport type { State, PluginFactory, Plugin } from \"@real-router/core\";\n\nexport function ssrDataPluginFactory(loaders: DataLoaderMap): PluginFactory {\n validateLoaders(loaders);\n\n return (router): Plugin => {\n const api = getPluginApi(router);\n const dataStore = new WeakMap<State, unknown>();\n\n const removeStartInterceptor = api.addInterceptor(\n \"start\",\n async (next, path) => {\n const state = await next(path);\n\n if (Object.hasOwn(loaders, state.name)) {\n dataStore.set(state, await loaders[state.name](state.params));\n }\n\n return state;\n },\n );\n\n const removeExtensions = api.extendRouter({\n getRouteData(state?: State): unknown {\n const s = state ?? router.getState();\n\n return s ? (dataStore.get(s) ?? null) : null;\n },\n });\n\n return {\n teardown() {\n removeStartInterceptor();\n removeExtensions();\n },\n };\n };\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"inputs":{"src/constants.ts":{"bytes":114,"imports":[],"format":"esm"},"src/validation.ts":{"bytes":574,"imports":[{"path":"src/constants.ts","kind":"import-statement","original":"./constants"}],"format":"esm"},"src/factory.ts":{"bytes":1095,"imports":[{"path":"@real-router/core/api","kind":"import-statement","external":true},{"path":"src/validation.ts","kind":"import-statement","original":"./validation"}],"format":"esm"},"src/index.ts":{"bytes":271,"imports":[{"path":"src/factory.ts","kind":"import-statement","original":"./factory"}],"format":"esm"}},"outputs":{"dist/esm/index.mjs.map":{"imports":[],"exports":[],"inputs":{},"bytes":2982},"dist/esm/index.mjs":{"imports":[{"path":"@real-router/core/api","kind":"import-statement","external":true}],"exports":["ssrDataPluginFactory"],"entryPoint":"src/index.ts","inputs":{"src/factory.ts":{"bytesInOutput":853},"src/constants.ts":{"bytesInOutput":95},"src/validation.ts":{"bytesInOutput":397},"src/index.ts":{"bytesInOutput":0}},"bytes":1460}}}
|
package/package.json
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@real-router/ssr-data-plugin",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"type": "commonjs",
|
|
5
|
+
"description": "SSR per-route data loading plugin for Real-Router",
|
|
6
|
+
"main": "./dist/cjs/index.js",
|
|
7
|
+
"module": "./dist/esm/index.mjs",
|
|
8
|
+
"types": "./dist/esm/index.d.mts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"development": "./src/index.ts",
|
|
12
|
+
"types": {
|
|
13
|
+
"import": "./dist/esm/index.d.mts",
|
|
14
|
+
"require": "./dist/cjs/index.d.ts"
|
|
15
|
+
},
|
|
16
|
+
"import": "./dist/esm/index.mjs",
|
|
17
|
+
"require": "./dist/cjs/index.js"
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
"files": [
|
|
21
|
+
"dist",
|
|
22
|
+
"src"
|
|
23
|
+
],
|
|
24
|
+
"repository": {
|
|
25
|
+
"type": "git",
|
|
26
|
+
"url": "git+https://github.com/greydragon888/real-router.git"
|
|
27
|
+
},
|
|
28
|
+
"author": {
|
|
29
|
+
"name": "Oleg Ivanov",
|
|
30
|
+
"email": "greydragon888@gmail.com",
|
|
31
|
+
"url": "https://github.com/greydragon888"
|
|
32
|
+
},
|
|
33
|
+
"license": "MIT",
|
|
34
|
+
"bugs": {
|
|
35
|
+
"url": "https://github.com/greydragon888/real-router/issues"
|
|
36
|
+
},
|
|
37
|
+
"homepage": "https://github.com/greydragon888/real-router",
|
|
38
|
+
"scripts": {
|
|
39
|
+
"test": "vitest",
|
|
40
|
+
"build": "tsup",
|
|
41
|
+
"type-check": "tsc --noEmit",
|
|
42
|
+
"lint": "eslint --cache --ext .ts src/ tests/ --fix --max-warnings 0",
|
|
43
|
+
"lint:package": "publint",
|
|
44
|
+
"lint:types": "attw --pack ."
|
|
45
|
+
},
|
|
46
|
+
"sideEffects": false,
|
|
47
|
+
"peerDependencies": {
|
|
48
|
+
"@real-router/core": "workspace:^"
|
|
49
|
+
}
|
|
50
|
+
}
|
package/src/constants.ts
ADDED
package/src/factory.ts
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { getPluginApi } from "@real-router/core/api";
|
|
2
|
+
|
|
3
|
+
import { validateLoaders } from "./validation";
|
|
4
|
+
|
|
5
|
+
import type { DataLoaderMap } from "./types";
|
|
6
|
+
import type { State, PluginFactory, Plugin } from "@real-router/core";
|
|
7
|
+
|
|
8
|
+
export function ssrDataPluginFactory(loaders: DataLoaderMap): PluginFactory {
|
|
9
|
+
validateLoaders(loaders);
|
|
10
|
+
|
|
11
|
+
return (router): Plugin => {
|
|
12
|
+
const api = getPluginApi(router);
|
|
13
|
+
const dataStore = new WeakMap<State, unknown>();
|
|
14
|
+
|
|
15
|
+
const removeStartInterceptor = api.addInterceptor(
|
|
16
|
+
"start",
|
|
17
|
+
async (next, path) => {
|
|
18
|
+
const state = await next(path);
|
|
19
|
+
|
|
20
|
+
if (Object.hasOwn(loaders, state.name)) {
|
|
21
|
+
dataStore.set(state, await loaders[state.name](state.params));
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return state;
|
|
25
|
+
},
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
const removeExtensions = api.extendRouter({
|
|
29
|
+
getRouteData(state?: State): unknown {
|
|
30
|
+
const s = state ?? router.getState();
|
|
31
|
+
|
|
32
|
+
return s ? (dataStore.get(s) ?? null) : null;
|
|
33
|
+
},
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
return {
|
|
37
|
+
teardown() {
|
|
38
|
+
removeStartInterceptor();
|
|
39
|
+
removeExtensions();
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
};
|
|
43
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { State } from "@real-router/core";
|
|
2
|
+
|
|
3
|
+
export type { DataLoaderMap, DataLoaderFn } from "./types";
|
|
4
|
+
|
|
5
|
+
export { ssrDataPluginFactory } from "./factory";
|
|
6
|
+
|
|
7
|
+
declare module "@real-router/core" {
|
|
8
|
+
interface Router {
|
|
9
|
+
getRouteData: (state?: State) => unknown;
|
|
10
|
+
}
|
|
11
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { ERROR_PREFIX } from "./constants";
|
|
2
|
+
|
|
3
|
+
import type { DataLoaderMap } from "./types";
|
|
4
|
+
|
|
5
|
+
export function validateLoaders(
|
|
6
|
+
loaders: unknown,
|
|
7
|
+
): asserts loaders is DataLoaderMap {
|
|
8
|
+
if (loaders === null || typeof loaders !== "object") {
|
|
9
|
+
throw new TypeError(`${ERROR_PREFIX} loaders must be a non-null object`);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
for (const [key, value] of Object.entries(
|
|
13
|
+
loaders as Record<string, unknown>,
|
|
14
|
+
)) {
|
|
15
|
+
if (typeof value !== "function") {
|
|
16
|
+
throw new TypeError(
|
|
17
|
+
`${ERROR_PREFIX} loader for route "${key}" must be a function`,
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|