vite-plugin-vanjs 0.0.9 → 0.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 +23 -229
- package/client/global.d.ts +3 -3
- package/client/index.mjs +141 -7
- package/client/types.d.ts +3 -3
- package/jsx/jsx.d.ts +2 -0
- package/jsx/jsx.mjs +4 -4
- package/meta/Head.mjs +3 -3
- package/meta/helpers.mjs +3 -5
- package/package.json +20 -19
- package/plugin/helpers.mjs +271 -0
- package/plugin/index.mjs +203 -0
- package/plugin/types.d.ts +23 -0
- package/router/a.mjs +19 -20
- package/router/cache.mjs +4 -3
- package/router/global.d.ts +79 -28
- package/router/helpers.mjs +32 -27
- package/router/lazy.mjs +23 -12
- package/router/router.mjs +105 -12
- package/router/routes.mjs +42 -8
- package/router/state.mjs +3 -3
- package/router/types.d.ts +105 -28
- package/server/index.mjs +32 -9
- package/server/types.d.ts +56 -0
- package/setup/global.d.ts +41 -39
- package/setup/helpers.mjs +7 -0
- package/setup/index-debug.mjs +8 -20
- package/setup/index-ssr.mjs +5 -0
- package/setup/index.mjs +4 -20
- package/setup/isServer.mjs +2 -0
- package/setup/package.json +16 -0
- package/setup/van-debug.mjs +2 -2
- package/setup/van-ssr..d.ts +1 -0
- package/setup/van-ssr.mjs +51 -0
- package/setup/van.mjs +44 -2
- package/setup/vanX-ssr.d.ts +1 -0
- package/setup/vanX-ssr.mjs +12 -0
- package/setup/vanX.mjs +23 -4
- package/tsconfig.json +1 -1
- package/src/index.mjs +0 -79
- package/src/types.d.ts +0 -28
package/router/global.d.ts
CHANGED
|
@@ -14,20 +14,30 @@ declare module "@vanjs/router" {
|
|
|
14
14
|
} from "vanjs-core";
|
|
15
15
|
|
|
16
16
|
// type VanElement = VElement | Exclude<Primitive, boolean | undefined>;
|
|
17
|
-
type DOMElement = globalThis.Element;
|
|
17
|
+
// type DOMElement = globalThis.Element;
|
|
18
18
|
type PrimitiveChild = Primitive | State<Primitive | undefined | null>;
|
|
19
19
|
|
|
20
|
-
type
|
|
20
|
+
type DOMElement<T extends keyof HTMLElementTagNameMap = "div"> =
|
|
21
21
|
| SVGElement
|
|
22
22
|
| HTMLElement
|
|
23
|
-
| DOMElement
|
|
24
23
|
| Node
|
|
24
|
+
| HTMLElementTagNameMap[T];
|
|
25
|
+
|
|
26
|
+
export type VanElement =
|
|
27
|
+
| DOMElement
|
|
25
28
|
| VElement
|
|
26
29
|
| PrimitiveChild;
|
|
30
|
+
// type VanElement =
|
|
31
|
+
// | SVGElement
|
|
32
|
+
// | HTMLElement
|
|
33
|
+
// | DOMElement
|
|
34
|
+
// | Node
|
|
35
|
+
// | VElement
|
|
36
|
+
// | PrimitiveChild;
|
|
27
37
|
export type VanNode =
|
|
28
38
|
| VanElement
|
|
29
|
-
|
|
|
30
|
-
| (() =>
|
|
39
|
+
| VanElement[]
|
|
40
|
+
| (() => VanElement | VanElement[])
|
|
31
41
|
| null
|
|
32
42
|
| undefined;
|
|
33
43
|
|
|
@@ -62,14 +72,12 @@ declare module "@vanjs/router" {
|
|
|
62
72
|
* return Router(); // or <Router /> for JSX
|
|
63
73
|
* }
|
|
64
74
|
*/
|
|
65
|
-
// export const Router: JSX.Component<HTMLElement> & VanComponent<HTMLElement>;
|
|
66
|
-
// export function Router(props?: ComponentProps<HTMLElement>): HTMLElement;
|
|
67
|
-
// export function Router(
|
|
68
|
-
// props?: JSX.IntrinsicElements["main"] & { children: null },
|
|
69
|
-
// ): HTMLElement;
|
|
70
75
|
export const Router:
|
|
71
76
|
& VanComponent<"main">
|
|
72
77
|
& JSX.Component<"main">;
|
|
78
|
+
export const FileSystemRouter:
|
|
79
|
+
& VanComponent<"main">
|
|
80
|
+
& JSX.Component<"main">;
|
|
73
81
|
|
|
74
82
|
// a.mjs
|
|
75
83
|
/**
|
|
@@ -123,15 +131,32 @@ declare module "@vanjs/router" {
|
|
|
123
131
|
export type RouteEntry = {
|
|
124
132
|
path: string;
|
|
125
133
|
component: Promise<ComponentModule>;
|
|
126
|
-
|
|
127
|
-
|
|
134
|
+
params?: Record<string, string>;
|
|
135
|
+
preload?: (
|
|
136
|
+
params?: Record<string, string>,
|
|
137
|
+
) => boolean | void | Promise<boolean | void>;
|
|
138
|
+
load?: (
|
|
139
|
+
params?: Record<string, string>,
|
|
140
|
+
) => boolean | void | Promise<boolean | void>;
|
|
128
141
|
};
|
|
129
142
|
|
|
130
143
|
export type RouteProps = {
|
|
131
144
|
path: string;
|
|
132
|
-
component:
|
|
133
|
-
|
|
134
|
-
|
|
145
|
+
component:
|
|
146
|
+
| (() =>
|
|
147
|
+
| HTMLElementTagNameMap[unknown]
|
|
148
|
+
| HTMLElementTagNameMap[unknown][])
|
|
149
|
+
| VanComponent
|
|
150
|
+
| ComponentFn
|
|
151
|
+
| (() => Promise<DynamicModule>);
|
|
152
|
+
// | ComponentModule["component"];
|
|
153
|
+
// component: ComponentFn | (() => Promise<ComponentModule>);
|
|
154
|
+
preload?: (
|
|
155
|
+
params?: Record<string, string>,
|
|
156
|
+
) => boolean | void | Promise<boolean | void>;
|
|
157
|
+
load?: (
|
|
158
|
+
params?: Record<string, string>,
|
|
159
|
+
) => boolean | void | Promise<boolean | void>;
|
|
135
160
|
};
|
|
136
161
|
export const routes: RouteEntry[];
|
|
137
162
|
|
|
@@ -180,42 +205,68 @@ declare module "@vanjs/router" {
|
|
|
180
205
|
|
|
181
206
|
/**
|
|
182
207
|
* Merge the children of an Element or an array of elements with an optional array of children
|
|
183
|
-
* into the childen of a
|
|
184
|
-
* @param
|
|
185
|
-
* @param
|
|
208
|
+
* into the childen prperty of a simple object.
|
|
209
|
+
* @param source
|
|
210
|
+
* @param children
|
|
186
211
|
*/
|
|
187
212
|
export const unwrap: (
|
|
188
213
|
source:
|
|
189
214
|
| VanNode
|
|
190
|
-
| IsoTagFunc
|
|
191
215
|
| VanNode[]
|
|
192
|
-
|
|
|
193
|
-
|
|
|
194
|
-
|
|
195
|
-
|
|
216
|
+
| VanComponent
|
|
217
|
+
| { children: VanNode[] }
|
|
218
|
+
| ComponentFn
|
|
219
|
+
| ReturnType<IsoTagFunc>
|
|
220
|
+
| ReturnType<IsoTagFunc>[]
|
|
221
|
+
| (() =>
|
|
222
|
+
| ReturnType<IsoTagFunc>
|
|
223
|
+
| ReturnType<IsoTagFunc>[]
|
|
224
|
+
| VanNode
|
|
225
|
+
| VanNode[]),
|
|
226
|
+
...children: ChildDom[]
|
|
227
|
+
) => { children: ChildDom[] };
|
|
228
|
+
|
|
229
|
+
export type JSXComponentFn = () => JSX.Element;
|
|
230
|
+
export type FragmentFn = () => ChildDom | ChildDom[];
|
|
231
|
+
export type ComponentFn = FragmentFn | VanComponent | JSXComponentFn;
|
|
196
232
|
|
|
197
233
|
export type ComponentModule = {
|
|
198
|
-
component:
|
|
199
|
-
route
|
|
234
|
+
component: ComponentFn;
|
|
235
|
+
route?: Pick<RouteEntry, "load" | "preload">;
|
|
200
236
|
};
|
|
201
237
|
|
|
202
238
|
export type DynamicModule = {
|
|
203
|
-
Page:
|
|
204
|
-
default?:
|
|
239
|
+
Page: ComponentFn;
|
|
240
|
+
default?: ComponentFn;
|
|
205
241
|
route?: Pick<RouteEntry, "load" | "preload">;
|
|
206
242
|
};
|
|
207
243
|
|
|
208
244
|
export type LazyComponent = Promise<DynamicModule>;
|
|
209
245
|
|
|
246
|
+
export type ImportFn = () => LazyComponent;
|
|
247
|
+
|
|
210
248
|
/**
|
|
211
249
|
* Registers a lazy component.
|
|
212
250
|
* @param importFn
|
|
213
251
|
*/
|
|
214
|
-
export const lazy: (importFn:
|
|
252
|
+
export const lazy: (importFn: ImportFn) => () => Promise<ComponentModule>;
|
|
215
253
|
|
|
216
254
|
/**
|
|
217
255
|
* Fixes the URL of a route.
|
|
218
256
|
* @param url
|
|
219
257
|
*/
|
|
220
258
|
export const fixRouteUrl: (url: string) => string;
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Execute lifecycle methods preload and / or load
|
|
262
|
+
*/
|
|
263
|
+
export const executeLifecycle: (
|
|
264
|
+
{ route }: ComponentModule,
|
|
265
|
+
params?: Record<string, string> | undefined,
|
|
266
|
+
) => Promise<boolean | void>;
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Find a registered route that matches the given path
|
|
270
|
+
*/
|
|
271
|
+
export const matchRoute: (path: string) => RouteEntry | null;
|
|
221
272
|
}
|
package/router/helpers.mjs
CHANGED
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
// import van from "vanjs-core";
|
|
2
|
-
import
|
|
2
|
+
import isServer from "../setup/isServer.mjs";
|
|
3
3
|
import { routerState, setRouterState } from "./state.mjs";
|
|
4
4
|
import { matchRoute } from "./routes.mjs";
|
|
5
5
|
|
|
6
|
+
/** @typedef {typeof import("./types.d.ts").unwrap} Unwrap */
|
|
7
|
+
/** @typedef {typeof import("./types.d.ts").navigate} Navigate */
|
|
6
8
|
/** @typedef {import("./types.d.ts").Route} Route */
|
|
7
9
|
/** @typedef {import("./types.d.ts").VanNode} VanNode */
|
|
8
10
|
/** @typedef {import("./types.d.ts").ComponentModule} ComponentModule */
|
|
9
|
-
/** @typedef {import(
|
|
11
|
+
/** @typedef {import("./types.d.ts").DynamicModule} DynamicModule */
|
|
12
|
+
/** @typedef {import("./types.d.ts").ComponentFn} ComponentFn */
|
|
13
|
+
/** @typedef {import("./types.d.ts").LazyComponent} LazyComponent */
|
|
10
14
|
|
|
11
15
|
/**
|
|
12
16
|
* Check if selected page is the current page;
|
|
@@ -19,22 +23,24 @@ export const isCurrentPage = (pageName) => {
|
|
|
19
23
|
|
|
20
24
|
/**
|
|
21
25
|
* Merge the children of an Element or an array of elements with an optional array of children
|
|
22
|
-
* into the childen of a
|
|
23
|
-
* @
|
|
24
|
-
* @param {...Element[]} children
|
|
25
|
-
* @returns {TagFunc<HTMLFragmentElement> | HTMLElement}
|
|
26
|
+
* into the childen prperty of a simple object.
|
|
27
|
+
* @type {Unwrap}
|
|
26
28
|
*/
|
|
27
29
|
export const unwrap = (source, ...children) => {
|
|
28
30
|
const layout = () => {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
31
|
+
/** @type {VanNode[]} */
|
|
32
|
+
const pageChildren =
|
|
33
|
+
source && typeof source === "object" && "children" in source &&
|
|
34
|
+
Array.isArray(source?.children)
|
|
35
|
+
? source.children
|
|
36
|
+
: typeof source === "function"
|
|
37
|
+
// @ts-expect-error - this case is specific to VanJS components in SSR
|
|
38
|
+
? [...source()?.children || source()]
|
|
39
|
+
: typeof HTMLElement !== "undefined" && source instanceof HTMLElement
|
|
40
|
+
? [...source.children]
|
|
41
|
+
: /* istanbul ignore next */ Array.isArray(source)
|
|
42
|
+
? source
|
|
43
|
+
: [source];
|
|
38
44
|
|
|
39
45
|
// return van.tags.fragment(
|
|
40
46
|
// ...(children || /* istanbul ignore next */ []),
|
|
@@ -52,16 +58,17 @@ export const unwrap = (source, ...children) => {
|
|
|
52
58
|
|
|
53
59
|
/**
|
|
54
60
|
* Check if component is a lazy component
|
|
55
|
-
* @param {
|
|
56
|
-
* @returns {component is (() =>
|
|
61
|
+
* @param {ComponentFn | (() => LazyComponent)} component
|
|
62
|
+
* @returns {component is (() => LazyComponent)}
|
|
57
63
|
*/
|
|
58
64
|
export const isLazyComponent = (component) => {
|
|
59
65
|
// Server: Check if it's an async function
|
|
60
|
-
if (
|
|
66
|
+
if (isServer && typeof component === "function") {
|
|
61
67
|
return component.constructor.name.includes("AsyncFunction");
|
|
62
68
|
}
|
|
63
69
|
|
|
64
70
|
// Client: Check if it's designated as lazy
|
|
71
|
+
// @ts-expect-error - this property is optional and on purpose
|
|
65
72
|
return component?.isLazy === true;
|
|
66
73
|
};
|
|
67
74
|
|
|
@@ -69,7 +76,7 @@ export const isLazyComponent = (component) => {
|
|
|
69
76
|
* Execute lifecycle methods preload and / or load
|
|
70
77
|
* @param {ComponentModule} param0
|
|
71
78
|
* @param {Record<string, string> | undefined} params
|
|
72
|
-
* @returns
|
|
79
|
+
* @returns {Promise<boolean>}
|
|
73
80
|
*/
|
|
74
81
|
export const executeLifecycle = async ({ route }, params) => {
|
|
75
82
|
// istanbul ignore next
|
|
@@ -88,16 +95,13 @@ export const executeLifecycle = async ({ route }, params) => {
|
|
|
88
95
|
|
|
89
96
|
/**
|
|
90
97
|
* Client only navigation utility.
|
|
91
|
-
* @
|
|
92
|
-
* @param {{ replace: boolean } | undefined} options - Navigation options
|
|
93
|
-
* @param {boolean} options.replace - Whether to replace current history entry
|
|
94
|
-
* @returns {void}
|
|
98
|
+
* @type {Navigate}
|
|
95
99
|
*/
|
|
96
100
|
export const navigate = (path, options = {}) => {
|
|
97
101
|
const { replace = false } = options;
|
|
98
102
|
|
|
99
103
|
// istanbul ignore else
|
|
100
|
-
if (!
|
|
104
|
+
if (!isServer) {
|
|
101
105
|
// Client-side navigation
|
|
102
106
|
const url = new URL(path, globalThis.location.origin);
|
|
103
107
|
const route = matchRoute(url.pathname);
|
|
@@ -121,9 +125,10 @@ export const navigate = (path, options = {}) => {
|
|
|
121
125
|
* Extract route params
|
|
122
126
|
* @param {string} pattern
|
|
123
127
|
* @param {string} path
|
|
124
|
-
* @returns {Record<string, string>}
|
|
128
|
+
* @returns {Record<string, string> | null}
|
|
125
129
|
*/
|
|
126
130
|
export const extractParams = (pattern, path) => {
|
|
131
|
+
/** @type {Record<string, string>} */
|
|
127
132
|
const params = {};
|
|
128
133
|
const patternParts = pattern.split("/");
|
|
129
134
|
const pathParts = path.split("/");
|
|
@@ -164,7 +169,7 @@ export const fixRouteUrl = (url) => {
|
|
|
164
169
|
* @returns {void}
|
|
165
170
|
*/
|
|
166
171
|
// export const reload = (forceFetch = false) => {
|
|
167
|
-
// if (!
|
|
172
|
+
// if (!isServer) {
|
|
168
173
|
// // Client-side reload
|
|
169
174
|
// if (forceFetch) {
|
|
170
175
|
// window.location.reload();
|
|
@@ -191,7 +196,7 @@ export const fixRouteUrl = (url) => {
|
|
|
191
196
|
// export const redirect = (path, options = {}) => {
|
|
192
197
|
// const { status = 302, replace = true } = options;
|
|
193
198
|
|
|
194
|
-
// if (!
|
|
199
|
+
// if (!isServer) {
|
|
195
200
|
// // Client-side redirect
|
|
196
201
|
// navigate(path, { replace });
|
|
197
202
|
// } else {
|
package/router/lazy.mjs
CHANGED
|
@@ -1,25 +1,29 @@
|
|
|
1
|
-
import
|
|
1
|
+
import isServer from "../setup/isServer.mjs";
|
|
2
2
|
import van from "vanjs-core";
|
|
3
3
|
import { cache, getCached } from "./cache.mjs";
|
|
4
4
|
|
|
5
5
|
/** @typedef {import('./types').VanNode} VanNode */
|
|
6
|
+
/** @typedef {import('./types').DynamicModule} DynamicModule */
|
|
6
7
|
/** @typedef {import('./types').ComponentModule} ComponentModule */
|
|
8
|
+
/** @typedef {import('./types').ComponentFn} ComponentFn */
|
|
7
9
|
|
|
8
10
|
/**
|
|
9
11
|
* Registers a lazy component.
|
|
10
|
-
* @
|
|
11
|
-
* @returns {ComponentModule | Promise<ComponentModule>}
|
|
12
|
+
* @type {typeof import("./types").lazy}
|
|
12
13
|
*/
|
|
13
14
|
export const lazy = (importFn) => {
|
|
14
|
-
if (
|
|
15
|
+
if (isServer) {
|
|
15
16
|
return async () => {
|
|
16
17
|
const cached = getCached(importFn);
|
|
17
18
|
/* istanbul ignore next */
|
|
18
19
|
if (cached) {
|
|
19
20
|
return cached;
|
|
20
21
|
}
|
|
22
|
+
|
|
21
23
|
const module = await importFn();
|
|
22
|
-
|
|
24
|
+
/** @type {ComponentFn} */
|
|
25
|
+
const component = module?.default || module.Page;
|
|
26
|
+
/** @type {ComponentModule} */
|
|
23
27
|
const result = { component, route: module.route };
|
|
24
28
|
|
|
25
29
|
cache(importFn, result);
|
|
@@ -28,7 +32,9 @@ export const lazy = (importFn) => {
|
|
|
28
32
|
}
|
|
29
33
|
|
|
30
34
|
let initialized = false;
|
|
31
|
-
|
|
35
|
+
/** @type {import("vanjs-core").State<(ComponentModule["component"] | (() => string))>} */
|
|
36
|
+
const component = van.state(() => "Loading..");
|
|
37
|
+
/** @type {import("vanjs-core").State<ComponentModule["route"]>} */
|
|
32
38
|
const route = van.state({});
|
|
33
39
|
|
|
34
40
|
const load = () => {
|
|
@@ -43,12 +49,16 @@ export const lazy = (importFn) => {
|
|
|
43
49
|
}
|
|
44
50
|
|
|
45
51
|
initialized = true;
|
|
46
|
-
importFn().then(
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
+
importFn().then(
|
|
53
|
+
/** @param {DynamicModule} module */
|
|
54
|
+
(module) => {
|
|
55
|
+
/** @type {ComponentModule["component"]} */
|
|
56
|
+
const pageComponent = module?.default || module.Page;
|
|
57
|
+
cache(importFn, { component: pageComponent, route: module.route });
|
|
58
|
+
component.val = pageComponent;
|
|
59
|
+
route.val = module.route;
|
|
60
|
+
},
|
|
61
|
+
);
|
|
52
62
|
};
|
|
53
63
|
|
|
54
64
|
const lazyComponent = () => {
|
|
@@ -56,5 +66,6 @@ export const lazy = (importFn) => {
|
|
|
56
66
|
return { component: component.val(), route: route.val };
|
|
57
67
|
};
|
|
58
68
|
lazyComponent.isLazy = true;
|
|
69
|
+
// @ts-expect-error - typescript cannot handle this isomorphically
|
|
59
70
|
return lazyComponent;
|
|
60
71
|
};
|
package/router/router.mjs
CHANGED
|
@@ -1,28 +1,37 @@
|
|
|
1
1
|
import van from "vanjs-core";
|
|
2
|
-
import
|
|
3
|
-
import { routerState } from "./state.mjs";
|
|
2
|
+
import isServer from "../setup/isServer.mjs";
|
|
3
|
+
import { routerState, setRouterState } from "./state.mjs";
|
|
4
4
|
import { matchRoute } from "./routes.mjs";
|
|
5
5
|
import { executeLifecycle, unwrap } from "./helpers.mjs";
|
|
6
|
+
import { hydrate } from "../client/index.mjs";
|
|
7
|
+
import { Head, initializeHeadTags } from "../meta/index.mjs";
|
|
8
|
+
|
|
9
|
+
import "virtual:@vanjs/routes";
|
|
10
|
+
|
|
11
|
+
let isConnected = false;
|
|
6
12
|
|
|
7
13
|
export const Router = (initialProps = /* istanbul ignore next */ {}) => {
|
|
8
14
|
const { div, main } = van.tags;
|
|
15
|
+
|
|
9
16
|
/* istanbul ignore next - try again later */
|
|
10
17
|
const props = Object.fromEntries(
|
|
11
|
-
Object.entries(initialProps).filter(([_, val]) => val),
|
|
18
|
+
Object.entries(initialProps).filter(([_, val]) => val !== undefined),
|
|
12
19
|
);
|
|
13
|
-
const wrapper = main(props);
|
|
20
|
+
const wrapper = main({ ...props, "data-root": true });
|
|
14
21
|
const mainLayout = () => {
|
|
15
22
|
const route = matchRoute(routerState.pathname.val);
|
|
16
23
|
/* istanbul ignore else */
|
|
17
|
-
if (!route) return van.add(wrapper, div("
|
|
24
|
+
if (!route) return van.add(wrapper, div("No Route Found"));
|
|
18
25
|
|
|
19
26
|
routerState.params.val = route.params || {};
|
|
20
27
|
// Server-side or async component: use renderComponent
|
|
21
|
-
if (
|
|
28
|
+
if (isServer) {
|
|
22
29
|
const renderComponent = async () => {
|
|
23
30
|
try {
|
|
24
31
|
const module = await route.component();
|
|
25
|
-
const component = module.component
|
|
32
|
+
const component = typeof module.component === "function"
|
|
33
|
+
? module.component()
|
|
34
|
+
: /* istanbul ignore next */ module.component;
|
|
26
35
|
|
|
27
36
|
await executeLifecycle(module, route.params);
|
|
28
37
|
return van.add(wrapper, unwrap(component).children);
|
|
@@ -37,13 +46,97 @@ export const Router = (initialProps = /* istanbul ignore next */ {}) => {
|
|
|
37
46
|
return renderComponent();
|
|
38
47
|
}
|
|
39
48
|
|
|
49
|
+
const root = document.querySelector("[data-root]");
|
|
50
|
+
// istanbul ignore else - cannot test unmount
|
|
51
|
+
if (!isConnected || !root) {
|
|
52
|
+
initializeHeadTags();
|
|
53
|
+
globalThis.addEventListener(
|
|
54
|
+
"popstate",
|
|
55
|
+
/** @param {Event & {target: globalThis}} e */
|
|
56
|
+
// istanbul ignore next - cannot test
|
|
57
|
+
(e) => {
|
|
58
|
+
const location = e.target.location;
|
|
59
|
+
const oldPath = routerState.pathname._oldVal;
|
|
60
|
+
// istanbul ignore next - cannot test
|
|
61
|
+
if (location.pathname !== oldPath) {
|
|
62
|
+
setRouterState(location.pathname, location.search);
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
|
|
40
68
|
// Client-side lazy component, lifeCycle is already executed on the server
|
|
41
69
|
// or when A component has been clicked in the client
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
70
|
+
if (root) {
|
|
71
|
+
// this case is when root is server side rendered
|
|
72
|
+
const children = () => {
|
|
73
|
+
const module = route.component();
|
|
74
|
+
executeLifecycle(module, route.params);
|
|
75
|
+
// istanbul ignore next - cannot test
|
|
76
|
+
const cp = (Array.isArray(module) || module instanceof Element)
|
|
77
|
+
? module
|
|
78
|
+
: typeof module.component === "function"
|
|
79
|
+
? module.component()
|
|
80
|
+
: module.component;
|
|
81
|
+
// istanbul ignore next - cannot test
|
|
82
|
+
const kids = () => cp ? Array.from(unwrap(cp).children) : [];
|
|
83
|
+
const kudos = kids();
|
|
84
|
+
|
|
85
|
+
isConnected = true;
|
|
86
|
+
// istanbul ignore else
|
|
87
|
+
if (document.head) {
|
|
88
|
+
van.hydrate(document.head, (head) => hydrate(head, Head()));
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return kudos;
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
return van.add(wrapper, ...children());
|
|
95
|
+
}
|
|
96
|
+
// this case is when root is for SPA apps
|
|
97
|
+
const csrRoute = van.derive(() => {
|
|
98
|
+
const p = routerState.pathname.val;
|
|
99
|
+
return matchRoute(p);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
const children = van.derive(() => {
|
|
103
|
+
const route = csrRoute.val;
|
|
104
|
+
// istanbul ignore if - can only be tested in client
|
|
105
|
+
if (!route) return [div("No Route Found")];
|
|
106
|
+
const md = route.component();
|
|
107
|
+
executeLifecycle(md, route.params);
|
|
108
|
+
// istanbul ignore next - cannot test all cases
|
|
109
|
+
const cp = (Array.isArray(md) || md instanceof Element)
|
|
110
|
+
? md
|
|
111
|
+
: typeof md.component === "function"
|
|
112
|
+
? md.component()
|
|
113
|
+
: md.component;
|
|
114
|
+
return cp
|
|
115
|
+
? Array.from(unwrap(cp).children)
|
|
116
|
+
: /* istanbul ignore next */ [];
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
const component = () => {
|
|
120
|
+
const kids = () => children.val;
|
|
121
|
+
const result = () => {
|
|
122
|
+
return van.derive(() =>
|
|
123
|
+
van.hydrate(wrapper, (el) => {
|
|
124
|
+
const kudos = kids();
|
|
125
|
+
isConnected = true;
|
|
126
|
+
// istanbul ignore else
|
|
127
|
+
if (document.head) {
|
|
128
|
+
van.hydrate(document.head, (head) => hydrate(head, Head()));
|
|
129
|
+
}
|
|
130
|
+
return hydrate(el, kudos);
|
|
131
|
+
})
|
|
132
|
+
).val;
|
|
133
|
+
};
|
|
134
|
+
return result();
|
|
135
|
+
};
|
|
136
|
+
const finalResult = component();
|
|
137
|
+
return finalResult
|
|
138
|
+
? /* istanbul ignore next*/ van.add(wrapper, finalResult)
|
|
139
|
+
: wrapper;
|
|
47
140
|
};
|
|
48
141
|
|
|
49
142
|
return mainLayout();
|
package/router/routes.mjs
CHANGED
|
@@ -4,6 +4,8 @@ import { lazy } from "./lazy.mjs";
|
|
|
4
4
|
|
|
5
5
|
/** @typedef {import("./types.d.ts").RouteEntry} RouteEntry */
|
|
6
6
|
/** @typedef {import("./types.d.ts").RouteProps} RouteProps */
|
|
7
|
+
/** @typedef {import("./types.d.ts").DynamicModule} DynamicModule */
|
|
8
|
+
/** @typedef {import("./types.d.ts").ComponentModule} ComponentModule */
|
|
7
9
|
|
|
8
10
|
/** @type {RouteEntry[]} */
|
|
9
11
|
export const routes = [];
|
|
@@ -12,40 +14,72 @@ export const routes = [];
|
|
|
12
14
|
* @param {RouteProps} routeProps
|
|
13
15
|
*/
|
|
14
16
|
export const Route = (routeProps) => {
|
|
15
|
-
const { component, preload, load, ...rest } = routeProps;
|
|
17
|
+
const { path, component, preload, load, ...rest } = routeProps;
|
|
16
18
|
|
|
17
|
-
//
|
|
19
|
+
// istanbul ignore next - no point testing this error
|
|
20
|
+
if (routes.some((r) => r.path === path)) {
|
|
21
|
+
console.error(`🍦 @vanjs/router: duplicated route for "${path}".`);
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// If component isn't lazy, make it lazy
|
|
18
26
|
if (!isLazyComponent(component)) {
|
|
27
|
+
/** @type {() => Promise<ComponentModule>} */
|
|
19
28
|
const wrappedComponent = lazy(() =>
|
|
20
29
|
Promise.resolve({
|
|
21
|
-
|
|
30
|
+
Page: component,
|
|
22
31
|
route: { preload, load },
|
|
23
32
|
})
|
|
24
33
|
);
|
|
25
|
-
routes.push({ ...rest, component: wrappedComponent });
|
|
34
|
+
routes.push({ ...rest, path, component: wrappedComponent });
|
|
26
35
|
return;
|
|
27
36
|
}
|
|
28
37
|
|
|
29
38
|
// Otherwise keep original component
|
|
39
|
+
// @ts-expect-error - RouteProps and RouteEntry are now equivalent
|
|
30
40
|
routes.push(routeProps);
|
|
31
41
|
};
|
|
32
42
|
|
|
33
43
|
/**
|
|
34
44
|
* Find a registered route that matches the given path
|
|
35
|
-
* @param {string}
|
|
45
|
+
* @param {string} initialPath
|
|
36
46
|
* @returns {RouteEntry | null}
|
|
37
47
|
*/
|
|
38
|
-
export const matchRoute = (
|
|
39
|
-
const
|
|
40
|
-
|
|
48
|
+
export const matchRoute = (initialPath) => {
|
|
49
|
+
const path = initialPath !== "/" && initialPath.endsWith("/")
|
|
50
|
+
? initialPath.slice(0, -1)
|
|
51
|
+
: initialPath;
|
|
41
52
|
|
|
53
|
+
// First try exact match (excluding wildcards)
|
|
54
|
+
let foundMatch = routes.find((r) => r.path === path && !r.path.includes("*"));
|
|
55
|
+
|
|
56
|
+
// Then try nested wildcard match if no exact match found
|
|
57
|
+
if (!foundMatch) {
|
|
58
|
+
// Build the path for potential nested wildcard, e.g. /admin/* for /admin/articles
|
|
59
|
+
const nestedPath = path.split("/").slice(0, -1).join("/") + "/*";
|
|
60
|
+
foundMatch = routes.find((r) => r.path === nestedPath);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// If we found either an exact or nested wildcard match, return it with params
|
|
64
|
+
if (foundMatch) {
|
|
65
|
+
return {
|
|
66
|
+
...foundMatch,
|
|
67
|
+
params: extractParams(foundMatch.path, path) ?? /* istanbul ignore next */
|
|
68
|
+
undefined,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Try parameterized routes (like /users/:id)
|
|
42
73
|
for (const route of routes) {
|
|
74
|
+
// Skip the global catch-all
|
|
43
75
|
if (route.path === "*") continue;
|
|
44
76
|
const params = extractParams(route.path, path);
|
|
77
|
+
|
|
45
78
|
if (params) {
|
|
46
79
|
return { ...route, params };
|
|
47
80
|
}
|
|
48
81
|
}
|
|
49
82
|
|
|
83
|
+
// Finally, fallback to global catch-all route
|
|
50
84
|
return routes.find((r) => r.path === "*") || null;
|
|
51
85
|
};
|
package/router/state.mjs
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
// router/state.js
|
|
2
2
|
import van from "vanjs-core";
|
|
3
|
-
import
|
|
3
|
+
import isServer from "../setup/isServer.mjs";
|
|
4
4
|
import { fixRouteUrl } from "./helpers.mjs";
|
|
5
5
|
|
|
6
|
-
const initialPath = !
|
|
7
|
-
const initialSearch = !
|
|
6
|
+
const initialPath = !isServer ? globalThis.location.pathname : "/";
|
|
7
|
+
const initialSearch = !isServer ? globalThis.location.search : "";
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* @type {typeof import("./types.d.ts").routerState}
|