reactive-route 0.0.1-alpha.26 → 0.0.1-alpha.28
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +1 -1
- package/dist/cjs/adapters/vue/index.js +39 -0
- package/dist/cjs/adapters/vue/package.json +1 -0
- package/dist/cjs/vue/index.js +0 -0
- package/dist/cjs/vue/package.json +1 -0
- package/dist/esm/adapters/vue/index.js +18 -0
- package/dist/esm/adapters/vue/package.json +1 -0
- package/dist/esm/vue/index.js +0 -0
- package/dist/esm/vue/package.json +1 -0
- package/dist/tsconfig.types.react.tsbuildinfo +1 -1
- package/dist/vue/index.d.ts +1 -1
- package/dist/vue/index.d.ts.map +1 -1
- package/e2e/app.test.ts +130 -0
- package/e2e/teardown.ts +12 -0
- package/package.json +25 -9
- package/playwright.config.ts +54 -0
- package/vitepress/.vitepress/cache/deps/_metadata.json +52 -0
- package/vitepress/.vitepress/cache/deps/chunk-FL23S3EK.js +12705 -0
- package/vitepress/.vitepress/cache/deps/chunk-FL23S3EK.js.map +7 -0
- package/vitepress/.vitepress/cache/deps/chunk-VGAXUAUJ.js +9952 -0
- package/vitepress/.vitepress/cache/deps/chunk-VGAXUAUJ.js.map +7 -0
- package/vitepress/.vitepress/cache/deps/package.json +3 -0
- package/vitepress/.vitepress/cache/deps/vitepress___@vue_devtools-api.js +3844 -0
- package/vitepress/.vitepress/cache/deps/vitepress___@vue_devtools-api.js.map +7 -0
- package/vitepress/.vitepress/cache/deps/vitepress___@vueuse_core.js +588 -0
- package/vitepress/.vitepress/cache/deps/vitepress___@vueuse_core.js.map +7 -0
- package/vitepress/.vitepress/cache/deps/vitepress___@vueuse_integrations_useFocusTrap.js +1153 -0
- package/vitepress/.vitepress/cache/deps/vitepress___@vueuse_integrations_useFocusTrap.js.map +7 -0
- package/vitepress/.vitepress/cache/deps/vitepress___mark__js_src_vanilla__js.js +1665 -0
- package/vitepress/.vitepress/cache/deps/vitepress___mark__js_src_vanilla__js.js.map +7 -0
- package/vitepress/.vitepress/cache/deps/vitepress___minisearch.js +1812 -0
- package/vitepress/.vitepress/cache/deps/vitepress___minisearch.js.map +7 -0
- package/vitepress/.vitepress/cache/deps/vue.js +342 -0
- package/vitepress/.vitepress/cache/deps/vue.js.map +7 -0
- package/vitepress/.vitepress/config.mts +72 -0
- package/vitepress/.vitepress/theme/custom.css +9 -0
- package/vitepress/.vitepress/theme/index.ts +5 -0
- package/vitepress/examples/preact.md +22 -0
- package/vitepress/examples/react.md +22 -0
- package/vitepress/examples/solid.md +21 -0
- package/vitepress/file.svg +79 -0
- package/vitepress/guide/advanced.md +131 -0
- package/vitepress/guide/getting-started.md +139 -0
- package/vitepress/guide/index.md +44 -0
- package/vitepress/guide/preact.md +33 -0
- package/vitepress/guide/react.md +33 -0
- package/vitepress/guide/router-component.md +67 -0
- package/vitepress/guide/router-configuration.md +185 -0
- package/vitepress/guide/routes-configuration.md +191 -0
- package/vitepress/guide/solid.md +68 -0
- package/vitepress/guide/ssr.md +70 -0
- package/vitepress/index.md +29 -0
- package/vitest.vue.config.mjs +23 -0
- package/dist/vue/Router.d.ts +0 -4
- package/dist/vue/Router.d.ts.map +0 -1
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { defineConfig } from 'vitepress';
|
|
2
|
+
import { groupIconMdPlugin, groupIconVitePlugin } from 'vitepress-plugin-group-icons';
|
|
3
|
+
|
|
4
|
+
export default defineConfig({
|
|
5
|
+
title: 'Reactive Route',
|
|
6
|
+
description: 'Config-based routing for different frameworks',
|
|
7
|
+
base: '/reactive-route/',
|
|
8
|
+
head: [['link', { rel: 'icon', href: '/file.svg' }]],
|
|
9
|
+
markdown: {
|
|
10
|
+
config(md) {
|
|
11
|
+
md.use(groupIconMdPlugin);
|
|
12
|
+
},
|
|
13
|
+
},
|
|
14
|
+
vite: {
|
|
15
|
+
plugins: [groupIconVitePlugin()],
|
|
16
|
+
},
|
|
17
|
+
themeConfig: {
|
|
18
|
+
logo: '/file.svg',
|
|
19
|
+
nav: [
|
|
20
|
+
{ text: 'Home', link: '/' },
|
|
21
|
+
{ text: 'Guide', link: '/guide/', activeMatch: '/guide/' },
|
|
22
|
+
{ text: 'Examples', link: '/examples/react', activeMatch: '/examples/' },
|
|
23
|
+
],
|
|
24
|
+
sidebar: {
|
|
25
|
+
'/guide/': [
|
|
26
|
+
{
|
|
27
|
+
text: 'Introduction',
|
|
28
|
+
items: [
|
|
29
|
+
{ text: 'What is Reactive Route?', link: '/guide/' },
|
|
30
|
+
{ text: 'Getting Started', link: '/guide/getting-started' },
|
|
31
|
+
],
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
text: 'Core Concepts',
|
|
35
|
+
items: [
|
|
36
|
+
{ text: 'Routes Configuration', link: '/guide/routes-configuration' },
|
|
37
|
+
{ text: 'Router Configuration', link: '/guide/router-configuration' },
|
|
38
|
+
{ text: 'Router Component', link: '/guide/router-component' },
|
|
39
|
+
{ text: 'SSR', link: '/guide/ssr' },
|
|
40
|
+
{ text: 'Advanced', link: '/guide/advanced' },
|
|
41
|
+
],
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
text: 'Framework Integration',
|
|
45
|
+
items: [
|
|
46
|
+
{ text: 'React', link: '/guide/react' },
|
|
47
|
+
{ text: 'Preact', link: '/guide/preact' },
|
|
48
|
+
{ text: 'Solid.js', link: '/guide/solid' },
|
|
49
|
+
],
|
|
50
|
+
},
|
|
51
|
+
],
|
|
52
|
+
'/examples/': [
|
|
53
|
+
{
|
|
54
|
+
text: 'Examples',
|
|
55
|
+
items: [
|
|
56
|
+
{ text: 'React', link: '/examples/react' },
|
|
57
|
+
{ text: 'Preact', link: '/examples/preact' },
|
|
58
|
+
{ text: 'Solid.js', link: '/examples/solid' },
|
|
59
|
+
],
|
|
60
|
+
},
|
|
61
|
+
],
|
|
62
|
+
},
|
|
63
|
+
search: {
|
|
64
|
+
provider: 'local',
|
|
65
|
+
},
|
|
66
|
+
socialLinks: [{ icon: 'github', link: 'https://github.com/dkazakov8/reactive-route' }],
|
|
67
|
+
footer: {
|
|
68
|
+
message: 'Released under the MIT License.',
|
|
69
|
+
copyright: 'Copyright © 2023-present Dmitry Kazakov',
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
});
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# Preact Example
|
|
2
|
+
|
|
3
|
+
To use it follow these steps:
|
|
4
|
+
|
|
5
|
+
```shell
|
|
6
|
+
git clone https://github.com/dkazakov8/reactive-route.git
|
|
7
|
+
cd ./reactive-route/examples/preact
|
|
8
|
+
pnpm install
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
This example is configured to use `pnpm` by default, but you may choose your own package manager
|
|
12
|
+
by editing `packageManager` field in `package.json`.
|
|
13
|
+
|
|
14
|
+
Next, choose the mode and reactivity system to start:
|
|
15
|
+
|
|
16
|
+
- `pnpm run mobx` - CSR (Client rendering only) for MobX
|
|
17
|
+
- `pnpm run observable` - CSR (Client rendering only) for Observable
|
|
18
|
+
- `pnpm run ssr-mobx` - SSR for MobX
|
|
19
|
+
- `pnpm run ssr-observable` - SSR for Observable
|
|
20
|
+
|
|
21
|
+
Note, that wrapping of components in `observer` is made by the ESBuild bundler in this example.
|
|
22
|
+
In your own projects, remember to follow the relevant [Framework Integration](/guide/preact).
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# React Example
|
|
2
|
+
|
|
3
|
+
To use it follow these steps:
|
|
4
|
+
|
|
5
|
+
```shell
|
|
6
|
+
git clone https://github.com/dkazakov8/reactive-route.git
|
|
7
|
+
cd ./reactive-route/examples/react
|
|
8
|
+
pnpm install
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
This example is configured to use `pnpm` by default, but you may choose your own package manager
|
|
12
|
+
by editing `packageManager` field in `package.json`.
|
|
13
|
+
|
|
14
|
+
Next, choose the mode and reactivity system to start:
|
|
15
|
+
|
|
16
|
+
- `pnpm run mobx` - CSR (Client rendering only) for MobX
|
|
17
|
+
- `pnpm run observable` - CSR (Client rendering only) for Observable
|
|
18
|
+
- `pnpm run ssr-mobx` - SSR for MobX
|
|
19
|
+
- `pnpm run ssr-observable` - SSR for Observable
|
|
20
|
+
|
|
21
|
+
Note, that wrapping of components in `observer` is made by the ESBuild bundler in this example.
|
|
22
|
+
In your own projects, remember to follow the relevant [Framework Integration](/guide/react).
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# Solid.js Example
|
|
2
|
+
|
|
3
|
+
To use it follow these steps:
|
|
4
|
+
|
|
5
|
+
```shell
|
|
6
|
+
git clone https://github.com/dkazakov8/reactive-route.git
|
|
7
|
+
cd ./reactive-route/examples/solid
|
|
8
|
+
pnpm install
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
This example is configured to use `pnpm` by default, but you may choose your own package manager
|
|
12
|
+
by editing `packageManager` field in `package.json`.
|
|
13
|
+
|
|
14
|
+
Next, choose the mode and reactivity system to start:
|
|
15
|
+
|
|
16
|
+
- `pnpm run solid` - CSR (Client rendering only) for Solid.js reactivity
|
|
17
|
+
- `pnpm run mobx` - CSR (Client rendering only) for MobX
|
|
18
|
+
- `pnpm run observable` - CSR (Client rendering only) for Observable
|
|
19
|
+
- `pnpm run ssr-solid` - SSR for Solid.js reactivity
|
|
20
|
+
- `pnpm run ssr-mobx` - SSR for MobX
|
|
21
|
+
- `pnpm run ssr-observable` - SSR for Observable
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
|
2
|
+
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
|
3
|
+
|
|
4
|
+
<svg
|
|
5
|
+
width="32"
|
|
6
|
+
height="32"
|
|
7
|
+
viewBox="0 0 32 32"
|
|
8
|
+
version="1.1"
|
|
9
|
+
id="svg1"
|
|
10
|
+
inkscape:version="1.4.2 (f4327f4, 2025-05-13)"
|
|
11
|
+
sodipodi:docname="ver-5.svg"
|
|
12
|
+
inkscape:export-filename="2.svg"
|
|
13
|
+
inkscape:export-xdpi="96"
|
|
14
|
+
inkscape:export-ydpi="96"
|
|
15
|
+
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
|
16
|
+
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
|
17
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
18
|
+
xmlns:svg="http://www.w3.org/2000/svg">
|
|
19
|
+
<sodipodi:namedview
|
|
20
|
+
id="namedview1"
|
|
21
|
+
pagecolor="#ffffff"
|
|
22
|
+
bordercolor="#000000"
|
|
23
|
+
borderopacity="0.25"
|
|
24
|
+
inkscape:showpageshadow="2"
|
|
25
|
+
inkscape:pageopacity="0.0"
|
|
26
|
+
inkscape:pagecheckerboard="0"
|
|
27
|
+
inkscape:deskcolor="#d1d1d1"
|
|
28
|
+
inkscape:document-units="px"
|
|
29
|
+
showgrid="true"
|
|
30
|
+
inkscape:zoom="4"
|
|
31
|
+
inkscape:cx="28.75"
|
|
32
|
+
inkscape:cy="45"
|
|
33
|
+
inkscape:window-width="1920"
|
|
34
|
+
inkscape:window-height="1009"
|
|
35
|
+
inkscape:window-x="-8"
|
|
36
|
+
inkscape:window-y="-8"
|
|
37
|
+
inkscape:window-maximized="1"
|
|
38
|
+
inkscape:current-layer="layer1">
|
|
39
|
+
<inkscape:grid
|
|
40
|
+
id="grid1"
|
|
41
|
+
units="px"
|
|
42
|
+
originx="0"
|
|
43
|
+
originy="0"
|
|
44
|
+
spacingx="1"
|
|
45
|
+
spacingy="1"
|
|
46
|
+
empcolor="#0099e5"
|
|
47
|
+
empopacity="0.30196078"
|
|
48
|
+
color="#0099e5"
|
|
49
|
+
opacity="0.14901961"
|
|
50
|
+
empspacing="4"
|
|
51
|
+
enabled="true"
|
|
52
|
+
visible="true" />
|
|
53
|
+
</sodipodi:namedview>
|
|
54
|
+
<defs
|
|
55
|
+
id="defs1" />
|
|
56
|
+
<g
|
|
57
|
+
inkscape:label="Слой 1"
|
|
58
|
+
inkscape:groupmode="layer"
|
|
59
|
+
id="layer1">
|
|
60
|
+
<path
|
|
61
|
+
id="rect1"
|
|
62
|
+
style="fill:#000000;fill-opacity:1;stroke-width:0;stroke-dasharray:none"
|
|
63
|
+
d="m 3,2 v 4 4 2 h 8 V 8 6 h 11 v 8 H 3 v 4 10 2 h 8 v -4 -8 h 16 v -4 -1 h 2 V 7 H 27 V 6 2 Z m 19,24 v 4 h 7 v -4 z" />
|
|
64
|
+
<rect
|
|
65
|
+
style="fill:#000000;fill-opacity:1;stroke-width:0;stroke-dasharray:none"
|
|
66
|
+
id="rect113"
|
|
67
|
+
width="8"
|
|
68
|
+
height="4"
|
|
69
|
+
x="19"
|
|
70
|
+
y="20" />
|
|
71
|
+
<rect
|
|
72
|
+
style="fill:#000000;fill-opacity:1;stroke-width:0;stroke-dasharray:none"
|
|
73
|
+
id="rect114"
|
|
74
|
+
width="1"
|
|
75
|
+
height="4"
|
|
76
|
+
x="21"
|
|
77
|
+
y="26" />
|
|
78
|
+
</g>
|
|
79
|
+
</svg>
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
# Advanced
|
|
2
|
+
|
|
3
|
+
## Redirects chain
|
|
4
|
+
|
|
5
|
+
This library fully supports unlimited redirects in SPA / SSR.
|
|
6
|
+
|
|
7
|
+
```typescript [routes.ts]
|
|
8
|
+
const routes = createRoutes({
|
|
9
|
+
one: {
|
|
10
|
+
path: '/1',
|
|
11
|
+
oader: () => import('./pages/one'),
|
|
12
|
+
},
|
|
13
|
+
two: {
|
|
14
|
+
path: '/2',
|
|
15
|
+
loader: () => import('./pages/two'),
|
|
16
|
+
async beforeEnter(config) {
|
|
17
|
+
return config.redirect({ route: 'one' });
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
three: {
|
|
21
|
+
path: '/3',
|
|
22
|
+
loader: () => import('./pages/three'),
|
|
23
|
+
async beforeEnter(config) {
|
|
24
|
+
return config.redirect({ route: 'two' });
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
four: {
|
|
28
|
+
path: '/4',
|
|
29
|
+
loader: () => import('./pages/four'),
|
|
30
|
+
async beforeEnter(config) {
|
|
31
|
+
return config.redirect({ route: 'three' });
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
|
|
35
|
+
// Other routes
|
|
36
|
+
});
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
In this case if user goes to `/4` he will be redirected to `/3` then `/2` then `/1`.
|
|
40
|
+
Browser's history and `router.routesHistory` will only have `['/1']`.
|
|
41
|
+
Also, chunks for pages four, three, two will not be loaded if you configured async chunks in your Bundler.
|
|
42
|
+
|
|
43
|
+
## Watch and react to params / query changes
|
|
44
|
+
|
|
45
|
+
`router.currentRoute` is an observable, so you can use it inside autorun / reaction / effect of your stack.
|
|
46
|
+
|
|
47
|
+
```tsx
|
|
48
|
+
import { TypeCurrentRoute } from 'reactive-route';
|
|
49
|
+
import { routes } from 'routes';
|
|
50
|
+
|
|
51
|
+
function MyComponent() {
|
|
52
|
+
const { router } = useContext(StoreContext);
|
|
53
|
+
|
|
54
|
+
const currentRoute = router.currentRoute as TypeCurrentRoute<typeof routes.tabs>;
|
|
55
|
+
|
|
56
|
+
// React + MobX way
|
|
57
|
+
useEffect(() => {
|
|
58
|
+
const disposer = autorun(() => {
|
|
59
|
+
// Always check the name. Because after redirecting the currentRoute
|
|
60
|
+
// will change, but this reaction is still alive, and the next route
|
|
61
|
+
// may not have params and query at all!
|
|
62
|
+
if (currentRoute.name !== 'tabs') return;
|
|
63
|
+
|
|
64
|
+
console.log(currentRoute.params.tab, currentRoute.query.foo);
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
return () => disposer();
|
|
68
|
+
}, []);
|
|
69
|
+
|
|
70
|
+
if (router.currentRoute.name !== 'tabs') return null;
|
|
71
|
+
|
|
72
|
+
if (router.currentRoute.params.tab === 'dashboard') {
|
|
73
|
+
return <Dashboard />
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (router.currentRoute.params.tab === 'table') {
|
|
77
|
+
return <Table />
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return <ModeNotFound />;
|
|
81
|
+
}
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## Prevent rerendering if pageId is the same
|
|
85
|
+
|
|
86
|
+
To Do
|
|
87
|
+
|
|
88
|
+
## Modular exports
|
|
89
|
+
|
|
90
|
+
All the exports from your pages are written to the route config. You may read them in
|
|
91
|
+
`beforeSetPageComponent` prop of the `Router`. For example, when you use code-splitting and SSR
|
|
92
|
+
it's a good practice to extend some global RootStore or IoC container with page's stores:
|
|
93
|
+
|
|
94
|
+
```tsx [pages/Home.tsx]
|
|
95
|
+
export const homeStore = {
|
|
96
|
+
foo: 'bar'
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export default function Home() {
|
|
100
|
+
const { modularStores } = useContext(StoreContext);
|
|
101
|
+
|
|
102
|
+
return `Home ${modularStores.homeStore.foo}`;
|
|
103
|
+
}
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
```tsx [components/Router.tsx]
|
|
107
|
+
export function Router() {
|
|
108
|
+
const { router, modularStores } = useContext(StoreContext);
|
|
109
|
+
|
|
110
|
+
return (
|
|
111
|
+
<RouterLib
|
|
112
|
+
routes={routes}
|
|
113
|
+
router={router}
|
|
114
|
+
beforeSetPageComponent={(route) => {
|
|
115
|
+
if (route.otherExports?.homeStore) {
|
|
116
|
+
modularStores.homeStore = route.otherExports.homeStore;
|
|
117
|
+
}
|
|
118
|
+
}}
|
|
119
|
+
beforeUpdatePageComponent={() => {
|
|
120
|
+
if (modularStores.homeStore) {
|
|
121
|
+
modularStores.homeStore.destroy();
|
|
122
|
+
modularStores.homeStore = null;
|
|
123
|
+
}
|
|
124
|
+
}}
|
|
125
|
+
/>
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
This way on hydration server can serialize `modularStores.homeStore` and the client can easily
|
|
131
|
+
hydrate it on the first render. This mechanism can also be used in other useful scenarios.
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
# Getting Started
|
|
2
|
+
|
|
3
|
+
This guide will help you set up Reactive Route in your application and create your first routes.
|
|
4
|
+
|
|
5
|
+
### Installation
|
|
6
|
+
|
|
7
|
+
The package includes the core router, React and Solid.js implementations, and adapters for different state management solutions.
|
|
8
|
+
Note that every combination requires corresponding libraries to be installed in your project.
|
|
9
|
+
|
|
10
|
+
::: code-group
|
|
11
|
+
```sh [npm]
|
|
12
|
+
npm i reactive-route
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
```sh [yarn]
|
|
16
|
+
yarn add reactive-route
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
```sh [pnpm]
|
|
20
|
+
pnpm add reactive-route
|
|
21
|
+
```
|
|
22
|
+
:::
|
|
23
|
+
|
|
24
|
+
### Modules map
|
|
25
|
+
|
|
26
|
+
```typescript
|
|
27
|
+
import { createRoutes, createRouter } from 'reactive-route';
|
|
28
|
+
import { Router } from 'reactive-route/react';
|
|
29
|
+
import { Router } from 'reactive-route/solid';
|
|
30
|
+
import { Router } from 'reactive-route/preact';
|
|
31
|
+
import { adapters } from 'reactive-route/adapters/mobx-react';
|
|
32
|
+
import { adapters } from 'reactive-route/adapters/mobx-preact';
|
|
33
|
+
import { adapters } from 'reactive-route/adapters/mobx-solid';
|
|
34
|
+
import { adapters } from 'reactive-route/adapters/solid';
|
|
35
|
+
import { adapters } from 'reactive-route/adapters/kr-observable-react';
|
|
36
|
+
import { adapters } from 'reactive-route/adapters/kr-observable-preact';
|
|
37
|
+
import { adapters } from 'reactive-route/adapters/kr-observable-solid';
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Basic Setup
|
|
41
|
+
|
|
42
|
+
### 1. Create a Router Store and Define Your Routes
|
|
43
|
+
|
|
44
|
+
First, create a router store using the `createRouter` function and
|
|
45
|
+
your routes configuration using the `createRoutes` function:
|
|
46
|
+
|
|
47
|
+
```typescript [router.ts]
|
|
48
|
+
import { createRoutes, createRouter } from 'reactive-route';
|
|
49
|
+
import { adapters } from 'reactive-route/adapters/{reactive-system}';
|
|
50
|
+
|
|
51
|
+
const routes = createRoutes({
|
|
52
|
+
home: {
|
|
53
|
+
path: '/',
|
|
54
|
+
loader: () => import('./pages/home'),
|
|
55
|
+
},
|
|
56
|
+
user: {
|
|
57
|
+
path: '/user/:id',
|
|
58
|
+
params: {
|
|
59
|
+
id: (value) => /^\d+$/.test(value),
|
|
60
|
+
},
|
|
61
|
+
query: {
|
|
62
|
+
phone: (value) => value.length > 0 && value.length < 10,
|
|
63
|
+
},
|
|
64
|
+
loader: () => import('./pages/user'),
|
|
65
|
+
},
|
|
66
|
+
notFound: {
|
|
67
|
+
path: '/not-found',
|
|
68
|
+
props: { errorCode: 404 },
|
|
69
|
+
loader: () => import('./pages/error'),
|
|
70
|
+
},
|
|
71
|
+
internalError: {
|
|
72
|
+
path: '/internal-error',
|
|
73
|
+
props: { errorCode: 500 },
|
|
74
|
+
loader: () => import('./pages/error'),
|
|
75
|
+
},
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
// If you prefer Context and SSR
|
|
79
|
+
export function getRouter() {
|
|
80
|
+
return createRouter({ routes, adapters });
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// If you prefer singletons
|
|
84
|
+
export const router = createRouter({ routes, adapters })
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Pages `notFound` and `internalError` are required for error handling in the library.
|
|
88
|
+
|
|
89
|
+
The recommended way is to use Context to pass it to UI components to avoid circular dependencies,
|
|
90
|
+
multiple instances and add the possibility of SSR.
|
|
91
|
+
|
|
92
|
+
```typescript [StoreContext.tsx]
|
|
93
|
+
import { createContext } from '{ui-library}';
|
|
94
|
+
|
|
95
|
+
import { getRouter } from './router';
|
|
96
|
+
|
|
97
|
+
export const StoreContext = createContext(
|
|
98
|
+
undefined as unknown as { router: ReturnType<typeof getRouter> }
|
|
99
|
+
);
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### 2. Set Up the Router Component
|
|
103
|
+
|
|
104
|
+
Create a custom Router component that uses the context to access the router store:
|
|
105
|
+
|
|
106
|
+
```tsx [components/Router.tsx]
|
|
107
|
+
import { useContext } from '{ui-library}';
|
|
108
|
+
import { Router as RouterLib } from 'reactive-route/{ui-library}';
|
|
109
|
+
|
|
110
|
+
import { StoreContext } from './StoreContext';
|
|
111
|
+
|
|
112
|
+
export function Router() {
|
|
113
|
+
const { router } = useContext(StoreContext);
|
|
114
|
+
|
|
115
|
+
return <RouterLib router={router} />;
|
|
116
|
+
}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### 3. Initialize the router and render
|
|
120
|
+
|
|
121
|
+
```tsx [client.tsx]
|
|
122
|
+
import { StoreContext } from './StoreContext';
|
|
123
|
+
import { getRouterStore } from './router';
|
|
124
|
+
import { Router } from './components/Router';
|
|
125
|
+
|
|
126
|
+
const router = getRouterStore();
|
|
127
|
+
|
|
128
|
+
await router.restoreFromURL({
|
|
129
|
+
pathname: location.pathname + location.search,
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
// the implementation is dependent on the UI library
|
|
133
|
+
render(
|
|
134
|
+
element,
|
|
135
|
+
<StoreContext.Provider value={{ router }}>
|
|
136
|
+
<Router />
|
|
137
|
+
</StoreContext.Provider>
|
|
138
|
+
);
|
|
139
|
+
```
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# What is Reactive Route?
|
|
2
|
+
|
|
3
|
+

|
|
4
|
+
[](https://www.npmjs.com/package/reactive-route)
|
|
5
|
+

|
|
6
|
+

|
|
7
|
+

|
|
8
|
+

|
|
9
|
+
|
|
10
|
+
Reactive Route is a lightweight, flexible, and reactive router for JavaScript applications.
|
|
11
|
+
|
|
12
|
+
### Framework and State Management Agnostic
|
|
13
|
+
|
|
14
|
+
The core routing logic is framework-agnostic, allowing it to be used with different UI frameworks and
|
|
15
|
+
state management solutions. Currently, Reactive Route provides official implementations for:
|
|
16
|
+
|
|
17
|
+
- React + MobX
|
|
18
|
+
- React + Observable
|
|
19
|
+
- Preact (no compat) + MobX
|
|
20
|
+
- Preact (no compat) + Observable
|
|
21
|
+
- Solid.js + Solid.js reactivity
|
|
22
|
+
- Solid.js + MobX
|
|
23
|
+
- Solid.js + Observable
|
|
24
|
+
- Vue + Vue reactivity (package in development)
|
|
25
|
+
|
|
26
|
+
### Key Advantages
|
|
27
|
+
|
|
28
|
+
Reactive Route offers several advantages that make it a powerful choice for routing in modern web applications:
|
|
29
|
+
|
|
30
|
+
- **Lifecycle Hooks**: Built-in `beforeEnter` and `beforeLeave` hooks allow you to control navigation flow, perform authentication checks, load data, and handle unsaved changes.
|
|
31
|
+
|
|
32
|
+
- **Dynamic Component Loading**: Supports dynamically loaded components through async imports (e.g., `() => import('./pages/Home')`), enabling code splitting and improving application performance.
|
|
33
|
+
|
|
34
|
+
- **Modular Data Integration**: Supports dynamically loaded modular stores and other data for pages, making state management more organized and efficient.
|
|
35
|
+
|
|
36
|
+
- **Server-Side Rendering**: Full SSR support for all the supported frameworks, ensuring optimal performance and SEO benefits.
|
|
37
|
+
|
|
38
|
+
- **Parameter Validation**: Ensures that every dynamic parameter from the URL has a validator, preventing invalid routes and improving application robustness.
|
|
39
|
+
|
|
40
|
+
- **TypeScript Integration**: Comprehensive TypeScript support for routes, dynamic parameters, and search queries, providing excellent developer experience and type safety.
|
|
41
|
+
|
|
42
|
+
- **Separation of Concerns**: Functions as a centralized separate layer, eliminating the need for markup like `<Route path="..." />` inside components and keeping your component tree clean.
|
|
43
|
+
|
|
44
|
+
In the following sections, we'll explore how to install and use Reactive Route in your applications.
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# Preact Integration
|
|
2
|
+
|
|
3
|
+
## Mobx
|
|
4
|
+
|
|
5
|
+
The relevant imports are as follows
|
|
6
|
+
|
|
7
|
+
```typescript
|
|
8
|
+
import { Router } from 'reactive-route/preact';
|
|
9
|
+
import { adapters } from 'reactive-route/adapters/mobx-preact';
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
You should ensure that packages `mobx`, `mobx-react-lite` are installed.
|
|
13
|
+
|
|
14
|
+
If you use `mobx-react` instead of `mobx-react-lite` you may create an alias in your bundler or
|
|
15
|
+
pass your own adapters with similar implementation but `observer` taken from `mobx-react`.
|
|
16
|
+
|
|
17
|
+
Be sure to wrap your components which read observable router parameters into `observer` (if you use
|
|
18
|
+
MobX this is presumably already done).
|
|
19
|
+
|
|
20
|
+
## Observable
|
|
21
|
+
|
|
22
|
+
The relevant imports are as follows
|
|
23
|
+
|
|
24
|
+
```typescript
|
|
25
|
+
import { Router } from 'reactive-route/preact';
|
|
26
|
+
import { adapters } from 'reactive-route/adapters/kr-observable-preact';
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
You should ensure that package `kr-observable` is installed.
|
|
30
|
+
|
|
31
|
+
Be sure to wrap your components which read observable router parameters into `observer` (if you use
|
|
32
|
+
Observable this is presumably already done).
|
|
33
|
+
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# React Integration
|
|
2
|
+
|
|
3
|
+
## Mobx
|
|
4
|
+
|
|
5
|
+
The relevant imports are as follows
|
|
6
|
+
|
|
7
|
+
```typescript
|
|
8
|
+
import { Router } from 'reactive-route/react';
|
|
9
|
+
import { adapters } from 'reactive-route/adapters/mobx-react';
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
You should ensure that packages `mobx`, `mobx-react-lite` are installed.
|
|
13
|
+
|
|
14
|
+
If you use `mobx-react` instead of `mobx-react-lite` you may create an alias in your bundler or
|
|
15
|
+
pass your own adapters with similar implementation but `observer` taken from `mobx-react`.
|
|
16
|
+
|
|
17
|
+
Be sure to wrap your components which read observable router parameters into `observer` (if you use
|
|
18
|
+
MobX this is presumably already done).
|
|
19
|
+
|
|
20
|
+
## Observable
|
|
21
|
+
|
|
22
|
+
The relevant imports are as follows
|
|
23
|
+
|
|
24
|
+
```typescript
|
|
25
|
+
import { Router } from 'reactive-route/react';
|
|
26
|
+
import { adapters } from 'reactive-route/adapters/kr-observable-react';
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
You should ensure that package `kr-observable` is installed.
|
|
30
|
+
|
|
31
|
+
Be sure to wrap your components which read observable router parameters into `observer` (if you use
|
|
32
|
+
Observable this is presumably already done).
|
|
33
|
+
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# Router Component Configuration
|
|
2
|
+
|
|
3
|
+
The router is the central piece that manages the state of the router and provides methods for navigation. It's created using the `createRouter` function.
|
|
4
|
+
|
|
5
|
+
## Creating a Router Component
|
|
6
|
+
|
|
7
|
+
```tsx [components/Router.tsx]
|
|
8
|
+
import { useContext } from '{ui-library}';
|
|
9
|
+
import { Router as RouterLib } from 'reactive-route/{ui-library}';
|
|
10
|
+
|
|
11
|
+
import { StoreContext } from './StoreContext';
|
|
12
|
+
|
|
13
|
+
export function Router() {
|
|
14
|
+
const { router } = useContext(StoreContext);
|
|
15
|
+
|
|
16
|
+
return <RouterLib router={router} />;
|
|
17
|
+
}
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
The `Router` accepts these props:
|
|
21
|
+
|
|
22
|
+
| Property | Type | Description |
|
|
23
|
+
|-----------------------------|----------------------------------------|------------------------------------------------------------------------------------------------------------------|
|
|
24
|
+
| `router` | `ReturnType<typeof createRouter>` | The router configuration |
|
|
25
|
+
| `beforeMount` | `() => void` | This function is called once on Router Component initiation, before any rendering (optional) |
|
|
26
|
+
| `beforeUpdatePageComponent` | `() => void` | This function is called when page component is changed, before `beforeSetPageComponent` and rendering (optional) |
|
|
27
|
+
| `beforeSetPageComponent` | `(componentConfig: TypeRoute) => void` | This function is called when page component is loaded, before rendering (optional) |
|
|
28
|
+
|
|
29
|
+
```tsx [components/Router.tsx]
|
|
30
|
+
import { useContext } from '{ui-library}';
|
|
31
|
+
import { Router as RouterLib } from 'reactive-route/{ui-library}';
|
|
32
|
+
|
|
33
|
+
import { routes } from '../routes';
|
|
34
|
+
import { StoreContext } from './StoreContext';
|
|
35
|
+
|
|
36
|
+
export function Router() {
|
|
37
|
+
const { router } = useContext(StoreContext);
|
|
38
|
+
|
|
39
|
+
return (
|
|
40
|
+
<RouterLib
|
|
41
|
+
routes={routes}
|
|
42
|
+
router={router}
|
|
43
|
+
beforeMount={() => {
|
|
44
|
+
// Router just mounted
|
|
45
|
+
}}
|
|
46
|
+
beforeUpdatePageComponent={() => {
|
|
47
|
+
// some new page will be rendered soon (not called on first render!)
|
|
48
|
+
// You may stop async actions and clear modular stores here
|
|
49
|
+
|
|
50
|
+
cancelExecutingApi();
|
|
51
|
+
cancelExecutingActions();
|
|
52
|
+
someStore.reset();
|
|
53
|
+
}}
|
|
54
|
+
beforeSetPageComponent={(route) => {
|
|
55
|
+
// some page will be rendered soon
|
|
56
|
+
// You may initiate modular stores here
|
|
57
|
+
|
|
58
|
+
console.log(route); // shows which page will be loaded
|
|
59
|
+
|
|
60
|
+
const myPageStore = route.otherExports?.myPageStore;
|
|
61
|
+
}}
|
|
62
|
+
/>
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
There are more examples of Router Component lifecycle in the [Advanced](/guide/advanced) section.
|