vite-plugin-vanjs 0.1.22 → 0.1.24
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/meta/Head.mjs +20 -0
- package/meta/global.d.ts +2 -1
- package/meta/helpers.mjs +1 -1
- package/meta/types.d.ts +2 -1
- package/package.json +3 -3
- package/plugin/const.mjs +8 -0
- package/plugin/helpers.mjs +12 -12
- package/plugin/types.d.ts +2 -2
- package/router/a.mjs +2 -2
- package/router/global.d.ts +9 -19
- package/router/helpers.mjs +80 -12
- package/router/router.mjs +33 -54
- package/router/state.mjs +15 -14
- package/router/types.d.ts +9 -18
package/meta/Head.mjs
CHANGED
|
@@ -6,6 +6,7 @@ import { getTagKey } from "./helpers.mjs";
|
|
|
6
6
|
/** @typedef {typeof import("./types.d.ts").resetHeadTags} ResetTags */
|
|
7
7
|
/** @typedef {typeof import("./types.d.ts").initializeHeadTags} InitializeTags */
|
|
8
8
|
/** @typedef {typeof import("./types.d.ts").addMeta} AddMeta */
|
|
9
|
+
/** @typedef {typeof import("./types.d.ts").removeMeta} RemoveMeta */
|
|
9
10
|
/** @typedef {typeof import("./types.d.ts").Head} HeadComp */
|
|
10
11
|
|
|
11
12
|
/**
|
|
@@ -69,6 +70,25 @@ export const addMeta = (tag) => {
|
|
|
69
70
|
tags.set(key, tag);
|
|
70
71
|
};
|
|
71
72
|
|
|
73
|
+
/**
|
|
74
|
+
* Remove a new meta tag `van.tags.script({ id })`
|
|
75
|
+
* or TAGNAME.id key.
|
|
76
|
+
* Only client side.
|
|
77
|
+
* @type {RemoveMeta}
|
|
78
|
+
*/
|
|
79
|
+
export const removeMeta = (tagOrId) => {
|
|
80
|
+
if (!tagOrId || isServer) return;
|
|
81
|
+
const tags = getHeadTags();
|
|
82
|
+
const tagIsString = typeof tagOrId === "string";
|
|
83
|
+
const key = tagIsString ? tagOrId : getTagKey(tagOrId);
|
|
84
|
+
const id = tagIsString ? tagOrId.split(".")[1] : tagOrId.id;
|
|
85
|
+
// istanbul ignore else
|
|
86
|
+
if (id) {
|
|
87
|
+
document.getElementById(id)?.remove();
|
|
88
|
+
tags.delete(key);
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
|
|
72
92
|
/**
|
|
73
93
|
* Get the head tags
|
|
74
94
|
* @type {HeadComp}
|
package/meta/global.d.ts
CHANGED
|
@@ -29,7 +29,8 @@ declare module "@vanjs/meta" {
|
|
|
29
29
|
|
|
30
30
|
export type HeadTags = SupportedTags[] | TagFunc[];
|
|
31
31
|
|
|
32
|
-
export const addMeta: (tag?:
|
|
32
|
+
export const addMeta: (tag?: Partial<SupportedTags>) => void;
|
|
33
|
+
export const removeMeta = (_tag: string | Partial<SupportedTags>) => null;
|
|
33
34
|
|
|
34
35
|
export const Head: () => HeadTags;
|
|
35
36
|
|
package/meta/helpers.mjs
CHANGED
|
@@ -19,6 +19,7 @@ export const parseAttributes = (attributeString) => {
|
|
|
19
19
|
/** @type {typeof import('./types.d.ts').getTagAttribute} */
|
|
20
20
|
const getTagAttribute = (tag) => {
|
|
21
21
|
const attributes = [
|
|
22
|
+
"id",
|
|
22
23
|
"name",
|
|
23
24
|
"property",
|
|
24
25
|
"charset",
|
|
@@ -28,7 +29,6 @@ const getTagAttribute = (tag) => {
|
|
|
28
29
|
"rel",
|
|
29
30
|
"src",
|
|
30
31
|
"href",
|
|
31
|
-
"id",
|
|
32
32
|
];
|
|
33
33
|
|
|
34
34
|
for (const attr of attributes) {
|
package/meta/types.d.ts
CHANGED
|
@@ -29,7 +29,8 @@ export type TagProps = SupportedTags | PropsWithKnownKeys<SupportedTags>;
|
|
|
29
29
|
|
|
30
30
|
export type HeadTags = AllHeadTags[] | TagFunc[];
|
|
31
31
|
|
|
32
|
-
export const addMeta = (_tag: string |
|
|
32
|
+
export const addMeta = (_tag: string | Partial<SupportedTags>) => null;
|
|
33
|
+
export const removeMeta = (_tag: string | Partial<SupportedTags>) => null;
|
|
33
34
|
|
|
34
35
|
export const Head: () => HeadTags;
|
|
35
36
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vite-plugin-vanjs",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.24",
|
|
4
4
|
"author": "thednp",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"description": "An async first mini meta-framework for VanJS powered by Vite",
|
|
@@ -88,13 +88,13 @@
|
|
|
88
88
|
"vanjs-ext": "^0.6.3"
|
|
89
89
|
},
|
|
90
90
|
"devDependencies": {
|
|
91
|
-
"@types/node": "^25.6.
|
|
91
|
+
"@types/node": "^25.6.2",
|
|
92
92
|
"@vitest/browser": "^4.1.5",
|
|
93
93
|
"@vitest/coverage-istanbul": "^4.1.5",
|
|
94
94
|
"@vitest/ui": "^4.1.5",
|
|
95
95
|
"happy-dom": "^20.9.0",
|
|
96
96
|
"typescript": "6.0.3",
|
|
97
|
-
"vite": "^8.0.
|
|
97
|
+
"vite": "^8.0.12",
|
|
98
98
|
"vitest": "^4.1.5"
|
|
99
99
|
},
|
|
100
100
|
"engines": {
|
package/plugin/const.mjs
ADDED
package/plugin/helpers.mjs
CHANGED
|
@@ -218,28 +218,28 @@ export const generateRouteProloaders = (route) => {
|
|
|
218
218
|
const layoutName = "Module";
|
|
219
219
|
|
|
220
220
|
return `{
|
|
221
|
-
|
|
222
|
-
|
|
221
|
+
preload: async (params) => {
|
|
222
|
+
${
|
|
223
223
|
route.layouts.map((layout) =>
|
|
224
224
|
`if (${layout.id + layoutName}?.route?.preload) await ${
|
|
225
225
|
layout.id + layoutName
|
|
226
226
|
}?.route?.preload(params);`
|
|
227
|
-
).join("\n
|
|
227
|
+
).join("\n ")
|
|
228
228
|
}
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
229
|
+
if (${moduleName}?.route?.preload) await ${moduleName}?.route?.preload(params);
|
|
230
|
+
},
|
|
231
|
+
load: async (params) => {
|
|
232
|
+
let _data;
|
|
233
|
+
${
|
|
234
234
|
route.layouts.map((layout) =>
|
|
235
235
|
`if (${layout.id + layoutName}?.route?.load) await ${
|
|
236
236
|
layout.id + layoutName
|
|
237
237
|
}?.route?.load(params);`
|
|
238
|
-
).join("\n
|
|
239
|
-
}
|
|
240
|
-
if (${moduleName}?.route?.load) _data = await ${moduleName}?.route?.load(params);
|
|
241
|
-
return _data;
|
|
238
|
+
).join("\n ")
|
|
242
239
|
}
|
|
240
|
+
if (${moduleName}?.route?.load) _data = await ${moduleName}?.route?.load(params);
|
|
241
|
+
return _data;
|
|
242
|
+
}
|
|
243
243
|
}`;
|
|
244
244
|
};
|
|
245
245
|
|
package/plugin/types.d.ts
CHANGED
|
@@ -14,8 +14,8 @@ export type VanJSPluginOptions = {
|
|
|
14
14
|
excludeRoutesProd?: string[]; // NEW — excluded in production only
|
|
15
15
|
};
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
const VitePluginVanJS: (
|
|
18
18
|
config?: VanJSPluginOptions,
|
|
19
|
-
) => Plugin
|
|
19
|
+
) => Plugin<VanJSPluginOptions>;
|
|
20
20
|
|
|
21
21
|
export default VitePluginVanJS;
|
package/router/a.mjs
CHANGED
|
@@ -29,8 +29,8 @@ export const A = (
|
|
|
29
29
|
href,
|
|
30
30
|
...props,
|
|
31
31
|
onclick: async (e) => {
|
|
32
|
-
const HREF = getValue(href);
|
|
33
32
|
e.preventDefault();
|
|
33
|
+
const HREF = getValue(href);
|
|
34
34
|
/* istanbul ignore next */
|
|
35
35
|
if (isCurrentPage(HREF)) return;
|
|
36
36
|
|
|
@@ -60,7 +60,7 @@ export const A = (
|
|
|
60
60
|
newProps["aria-current"] = van.derive(() => {
|
|
61
61
|
const isPage = isCurrentPage(href);
|
|
62
62
|
const isLocation = isCurrentLocation(href);
|
|
63
|
-
return isPage ? "page" : isLocation ? "location" :
|
|
63
|
+
return isPage ? "page" : isLocation ? "location" : "";
|
|
64
64
|
});
|
|
65
65
|
|
|
66
66
|
return van.tags.a(newProps, children || otherChildren);
|
package/router/global.d.ts
CHANGED
|
@@ -139,12 +139,8 @@ declare module "@vanjs/router" {
|
|
|
139
139
|
path: string;
|
|
140
140
|
component: () => Promise<ComponentModule>;
|
|
141
141
|
params?: Record<string, string>;
|
|
142
|
-
preload?: (
|
|
143
|
-
|
|
144
|
-
) => boolean | void | Promise<boolean | void>;
|
|
145
|
-
load?: (
|
|
146
|
-
params?: Record<string, string>,
|
|
147
|
-
) => boolean | void | Promise<boolean | void>;
|
|
142
|
+
preload?: (params?: Record<string, string>) => unknown;
|
|
143
|
+
load?: (params?: Record<string, string>) => Promise<unknown>;
|
|
148
144
|
};
|
|
149
145
|
|
|
150
146
|
export type ImportFn = () => LazyComponent;
|
|
@@ -157,12 +153,8 @@ declare module "@vanjs/router" {
|
|
|
157
153
|
| VanComponent
|
|
158
154
|
| ComponentFn
|
|
159
155
|
| (() => Promise<ComponentModule>);
|
|
160
|
-
preload?: (
|
|
161
|
-
|
|
162
|
-
) => boolean | void | Promise<boolean | void>;
|
|
163
|
-
load?: (
|
|
164
|
-
params?: Record<string, string>,
|
|
165
|
-
) => boolean | void | Promise<boolean | void>;
|
|
156
|
+
preload?: (params?: Record<string, string>) => unknown;
|
|
157
|
+
load?: (params?: Record<string, string>) => Promise<unknown>;
|
|
166
158
|
};
|
|
167
159
|
|
|
168
160
|
export type RouteConfig = {
|
|
@@ -191,9 +183,9 @@ declare module "@vanjs/router" {
|
|
|
191
183
|
// state.mjs
|
|
192
184
|
export type RouterState = {
|
|
193
185
|
pathname: string;
|
|
194
|
-
searchParams:
|
|
186
|
+
searchParams: string;
|
|
195
187
|
params: Record<string, string>;
|
|
196
|
-
|
|
188
|
+
loading: boolean;
|
|
197
189
|
};
|
|
198
190
|
|
|
199
191
|
/**
|
|
@@ -204,7 +196,7 @@ declare module "@vanjs/router" {
|
|
|
204
196
|
pathname: RouterState["pathname"];
|
|
205
197
|
searchParams: RouterState["searchParams"];
|
|
206
198
|
params?: RouterState["params"];
|
|
207
|
-
|
|
199
|
+
loading: RouterState["loading"];
|
|
208
200
|
};
|
|
209
201
|
|
|
210
202
|
/**
|
|
@@ -294,9 +286,8 @@ declare module "@vanjs/router" {
|
|
|
294
286
|
export const executeLifecycle: (
|
|
295
287
|
route: RouteEntry | {
|
|
296
288
|
preload?: (params?: Record<string, string>) => unknown;
|
|
297
|
-
load?: (params?: Record<string, string>) => unknown
|
|
289
|
+
load?: (params?: Record<string, string>) => Promise<unknown>;
|
|
298
290
|
} | null,
|
|
299
|
-
params: Record<string, string> | undefined,
|
|
300
291
|
) => Promise<boolean>;
|
|
301
292
|
|
|
302
293
|
/**
|
|
@@ -304,8 +295,7 @@ declare module "@vanjs/router" {
|
|
|
304
295
|
*/
|
|
305
296
|
export const matchRoute: (path: string) => RouteEntry | null;
|
|
306
297
|
|
|
307
|
-
/**
|
|
308
|
-
* Convenience hook to get the current route's cached data.
|
|
298
|
+
/** * Convenience hook to get the current route's cached data.
|
|
309
299
|
*/
|
|
310
300
|
export const useRouteData: <T>() => T | undefined;
|
|
311
301
|
|
package/router/helpers.mjs
CHANGED
|
@@ -1,15 +1,46 @@
|
|
|
1
|
+
import van from "vanjs-core";
|
|
1
2
|
import isServer from "../setup/isServer.mjs";
|
|
2
3
|
import { routerState, setRouterState } from "./state.mjs";
|
|
3
4
|
import { matchRoute } from "./matchRoute.mjs";
|
|
5
|
+
import { unwrap } from "./unwrap.mjs";
|
|
6
|
+
import { hydrate } from "../client/index.mjs";
|
|
7
|
+
import { Head } from "../meta/index.mjs";
|
|
8
|
+
|
|
4
9
|
import * as dataCache from "./dataCache.mjs";
|
|
5
10
|
|
|
6
11
|
/** @typedef {typeof import("./types.d.ts").navigate} Navigate */
|
|
12
|
+
/** @typedef {import("./types.d.ts").SearchParamDef} SearchParamDef */
|
|
7
13
|
/** @typedef {import("./types.d.ts").Route} Route */
|
|
8
14
|
/** @typedef {import("./types.d.ts").VanNode} VanNode */
|
|
9
15
|
/** @typedef {import("./types.d.ts").ComponentModule} ComponentModule */
|
|
10
16
|
/** @typedef {import("./types.d.ts").ComponentFn} ComponentFn */
|
|
11
17
|
/** @typedef {import("./types.d.ts").LazyComponent} LazyComponent */
|
|
12
18
|
|
|
19
|
+
/**
|
|
20
|
+
* Update head tags
|
|
21
|
+
*/
|
|
22
|
+
const updateHead = () => {
|
|
23
|
+
// istanbul ignore else
|
|
24
|
+
if (document.head) {
|
|
25
|
+
hydrate(document.head, Head());
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Resolve component children from a module
|
|
31
|
+
* @param {ComponentModule | Element | Element[] | any} module
|
|
32
|
+
* @returns {VanNode[]}
|
|
33
|
+
*/
|
|
34
|
+
export const resolveChildren = (module) => {
|
|
35
|
+
const isElement = typeof Element !== "undefined" && module instanceof Element;
|
|
36
|
+
const cp = (Array.isArray(module) || isElement)
|
|
37
|
+
? module
|
|
38
|
+
: typeof module.component === "function"
|
|
39
|
+
? module.component()
|
|
40
|
+
: module.component;
|
|
41
|
+
return cp ? Array.from(unwrap(cp).children) : /* istanbul ignore next */ [];
|
|
42
|
+
};
|
|
43
|
+
|
|
13
44
|
/**
|
|
14
45
|
* Returns the HREF string value
|
|
15
46
|
* @param {unknown} v
|
|
@@ -19,13 +50,25 @@ export const getValue = (v) => {
|
|
|
19
50
|
return typeof v === "function" ? v() : v.rawVal ? v.val : v;
|
|
20
51
|
};
|
|
21
52
|
|
|
53
|
+
export const getCacheKey = () => {
|
|
54
|
+
const params = routerState.params;
|
|
55
|
+
const search = routerState.searchParams;
|
|
56
|
+
return Object.keys(params).length === 0
|
|
57
|
+
? search || ""
|
|
58
|
+
: JSON.stringify(params) + (search ? `&${search}` : "");
|
|
59
|
+
};
|
|
60
|
+
|
|
22
61
|
/**
|
|
23
62
|
* Check if selected page is the current page;
|
|
24
63
|
* @param {string} pageName
|
|
25
64
|
* @returns {boolean}
|
|
26
65
|
*/
|
|
27
66
|
export const isCurrentPage = (pageName) => {
|
|
28
|
-
|
|
67
|
+
const href = getValue(pageName);
|
|
68
|
+
const url = new URL(href, "http://localhost:5173");
|
|
69
|
+
// console.log({ href }, routerState.searchParams, url.searchParams.toString())
|
|
70
|
+
return routerState.pathname === url.pathname &&
|
|
71
|
+
routerState.searchParams === url.searchParams.toString();
|
|
29
72
|
};
|
|
30
73
|
|
|
31
74
|
/**
|
|
@@ -34,8 +77,12 @@ export const isCurrentPage = (pageName) => {
|
|
|
34
77
|
* @returns {boolean}
|
|
35
78
|
*/
|
|
36
79
|
export const isCurrentLocation = (pageName) => {
|
|
37
|
-
const
|
|
38
|
-
|
|
80
|
+
const href = getValue(pageName);
|
|
81
|
+
const searchParams = new URLSearchParams(routerState.searchParams);
|
|
82
|
+
const pathname = routerState.pathname;
|
|
83
|
+
const url = new URL(href, "http://localhost:5173");
|
|
84
|
+
return url.pathname !== "/" && pathname.includes(url.pathname) ||
|
|
85
|
+
pathname === url.pathname && searchParams.size > 0;
|
|
39
86
|
};
|
|
40
87
|
|
|
41
88
|
/**
|
|
@@ -53,10 +100,9 @@ export const isLazyComponent = (component) => {
|
|
|
53
100
|
/**
|
|
54
101
|
* Execute lifecycle methods preload and / or load
|
|
55
102
|
* @param {import("./types.d.ts").RouteEntry} route
|
|
56
|
-
* @param {Record<string, string> | undefined} params
|
|
57
103
|
* @returns {Promise<boolean>}
|
|
58
104
|
*/
|
|
59
|
-
export const executeLifecycle = async (route
|
|
105
|
+
export const executeLifecycle = async (route) => {
|
|
60
106
|
// istanbul ignore next
|
|
61
107
|
try {
|
|
62
108
|
if (!route) return true;
|
|
@@ -66,13 +112,11 @@ export const executeLifecycle = async (route, params) => {
|
|
|
66
112
|
const load = route.load;
|
|
67
113
|
|
|
68
114
|
const pathname = routerState.pathname;
|
|
69
|
-
const cacheKey =
|
|
70
|
-
? ""
|
|
71
|
-
: JSON.stringify(params);
|
|
115
|
+
const cacheKey = getCacheKey();
|
|
72
116
|
|
|
73
|
-
if (preload) await preload(params);
|
|
117
|
+
if (preload) await preload(route.params);
|
|
74
118
|
if (!data && load && !dataCache.has(pathname, cacheKey)) {
|
|
75
|
-
data = await load(params);
|
|
119
|
+
data = await load(route.params);
|
|
76
120
|
} else if (load && dataCache.has(pathname, cacheKey)) {
|
|
77
121
|
dataCache.touch(pathname);
|
|
78
122
|
}
|
|
@@ -97,13 +141,37 @@ export const executeLifecycle = async (route, params) => {
|
|
|
97
141
|
}
|
|
98
142
|
};
|
|
99
143
|
|
|
144
|
+
/**
|
|
145
|
+
* @param {RouteEntry} route
|
|
146
|
+
* @param {HTMLElement} wrapper
|
|
147
|
+
* @param {boolean} ssr
|
|
148
|
+
* @returns
|
|
149
|
+
*/
|
|
150
|
+
export const executeModule = async (route, wrapper, ssr) => {
|
|
151
|
+
if (routerState.loading === true) return;
|
|
152
|
+
// 0. Set Loading State
|
|
153
|
+
routerState.loading = true;
|
|
154
|
+
// 1. Resolve the module first (to get route lifecycle hooks)
|
|
155
|
+
const module = await route.component();
|
|
156
|
+
// 2. Execute lifecycle (a complete route includes all props)
|
|
157
|
+
await executeLifecycle(Object.assign(route, module.route));
|
|
158
|
+
// 3. Resolve children
|
|
159
|
+
const children = resolveChildren(module);
|
|
160
|
+
// 4. Update <head> in the client
|
|
161
|
+
if (!isServer) updateHead();
|
|
162
|
+
// 5. Set Loading State
|
|
163
|
+
routerState.loading = false;
|
|
164
|
+
// 6. Update / replace children in wrapper
|
|
165
|
+
if (ssr) return van.add(wrapper, ...children);
|
|
166
|
+
else wrapper.replaceChildren(...children);
|
|
167
|
+
};
|
|
168
|
+
|
|
100
169
|
/**
|
|
101
170
|
* Convenience hook to get the current route's cached data.
|
|
102
171
|
* @returns {any | undefined}
|
|
103
172
|
*/
|
|
104
173
|
export const useRouteData = () => {
|
|
105
|
-
const
|
|
106
|
-
const key = Object.keys(params).length === 0 ? "" : JSON.stringify(params);
|
|
174
|
+
const key = getCacheKey();
|
|
107
175
|
return dataCache.get(routerState.pathname, key)?.data;
|
|
108
176
|
};
|
|
109
177
|
|
package/router/router.mjs
CHANGED
|
@@ -1,43 +1,19 @@
|
|
|
1
1
|
import van from "vanjs-core";
|
|
2
2
|
import isServer from "../setup/isServer.mjs";
|
|
3
|
+
import { MODE } from "../plugin/const.mjs";
|
|
3
4
|
import { routerState, setRouterState } from "./state.mjs";
|
|
4
5
|
import { matchRoute } from "./matchRoute.mjs";
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import { hydrate } from "../client/index.mjs";
|
|
8
|
-
import { Head, initializeHeadTags } from "../meta/index.mjs";
|
|
6
|
+
import { executeModule } from "./helpers.mjs";
|
|
7
|
+
import { initializeHeadTags } from "../meta/index.mjs";
|
|
9
8
|
import * as dataCache from "./dataCache.mjs";
|
|
10
9
|
import "virtual:@vanjs/routes";
|
|
11
10
|
|
|
11
|
+
const isDev = MODE === "development";
|
|
12
|
+
|
|
12
13
|
/** @typedef {import("./types.d.ts").ComponentModule} ComponentModule */
|
|
13
14
|
/** @typedef {import("./types.d.ts").RouteEntry} RouteEntry */
|
|
14
15
|
/** @typedef {import("./types.d.ts").VanNode} VanNode */
|
|
15
16
|
|
|
16
|
-
/**
|
|
17
|
-
* Resolve component children from a module
|
|
18
|
-
* @param {ComponentModule | Element | Element[] | any} module
|
|
19
|
-
* @returns {VanNode[]}
|
|
20
|
-
*/
|
|
21
|
-
const resolveChildren = (module) => {
|
|
22
|
-
const isElement = typeof Element !== "undefined" && module instanceof Element;
|
|
23
|
-
const cp = (Array.isArray(module) || isElement)
|
|
24
|
-
? module
|
|
25
|
-
: typeof module.component === "function"
|
|
26
|
-
? module.component()
|
|
27
|
-
: module.component;
|
|
28
|
-
return cp ? Array.from(unwrap(cp).children) : /* istanbul ignore next */ [];
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Update head tags
|
|
33
|
-
*/
|
|
34
|
-
const updateHead = () => {
|
|
35
|
-
// istanbul ignore else
|
|
36
|
-
if (document.head) {
|
|
37
|
-
hydrate(document.head, Head());
|
|
38
|
-
}
|
|
39
|
-
};
|
|
40
|
-
|
|
41
17
|
/**
|
|
42
18
|
* Initialize client-side router (Head + popstate listener)
|
|
43
19
|
*/
|
|
@@ -53,8 +29,10 @@ const initClient = () => {
|
|
|
53
29
|
(e) => {
|
|
54
30
|
const location = e.target.location;
|
|
55
31
|
const oldPath = routerState.pathname;
|
|
32
|
+
const oldSearch = routerState.searchParams;
|
|
33
|
+
const newSearch = new URLSearchParams(location.search).toString();
|
|
56
34
|
// istanbul ignore next - cannot test
|
|
57
|
-
if (location.pathname !== oldPath) {
|
|
35
|
+
if (location.pathname !== oldPath || newSearch !== oldSearch) {
|
|
58
36
|
setRouterState(location.pathname, location.search);
|
|
59
37
|
}
|
|
60
38
|
},
|
|
@@ -69,25 +47,18 @@ export const Router = (initialProps = /* istanbul ignore next */ {}) => {
|
|
|
69
47
|
);
|
|
70
48
|
const wrapper = main({ ...props, "data-root": "" });
|
|
71
49
|
const route = matchRoute(routerState.pathname);
|
|
50
|
+
let _searchParams = routerState.searchParams;
|
|
72
51
|
|
|
73
52
|
/* istanbul ignore else */
|
|
74
53
|
if (!route) return van.add(wrapper, div("No Route Found"));
|
|
75
|
-
|
|
76
|
-
Object.assign(routerState.params, route.params
|
|
54
|
+
// It's important to READ the params
|
|
55
|
+
Object.assign(routerState.params, route.params);
|
|
77
56
|
|
|
78
57
|
// Server-side rendering
|
|
79
58
|
if (isServer) {
|
|
80
59
|
return async () => {
|
|
81
60
|
try {
|
|
82
|
-
|
|
83
|
-
const module = await route.component();
|
|
84
|
-
|
|
85
|
-
// 2. Execute lifecycle
|
|
86
|
-
await executeLifecycle(module.route, route.params);
|
|
87
|
-
const children = resolveChildren(module);
|
|
88
|
-
|
|
89
|
-
// 3. Render the component (data is now in dataCache)
|
|
90
|
-
return van.add(wrapper, ...children);
|
|
61
|
+
return await executeModule(route, wrapper, true);
|
|
91
62
|
} catch (error) {
|
|
92
63
|
/* istanbul ignore next */
|
|
93
64
|
console.error("Router error:", error);
|
|
@@ -98,11 +69,13 @@ export const Router = (initialProps = /* istanbul ignore next */ {}) => {
|
|
|
98
69
|
}
|
|
99
70
|
|
|
100
71
|
// Init client here
|
|
101
|
-
|
|
72
|
+
initClient();
|
|
73
|
+
let initialized = false;
|
|
102
74
|
|
|
103
75
|
// Client-side: hydrate data cache from SSR output
|
|
104
76
|
// This must happen BEFORE any component renders so useRouteData() works
|
|
105
|
-
|
|
77
|
+
// Skip in dev mode: we manually clear dataCache on mutations for instant updates
|
|
78
|
+
if (globalThis.__DATA_CACHE && !isDev) {
|
|
106
79
|
dataCache.hydrateFromJSON(globalThis.__DATA_CACHE);
|
|
107
80
|
}
|
|
108
81
|
|
|
@@ -110,13 +83,22 @@ export const Router = (initialProps = /* istanbul ignore next */ {}) => {
|
|
|
110
83
|
const root = document.querySelector("[data-root]");
|
|
111
84
|
|
|
112
85
|
if (root) {
|
|
86
|
+
van.derive(() => {
|
|
87
|
+
if (!initialized) return;
|
|
88
|
+
const matchedRoute = matchRoute(routerState.pathname);
|
|
89
|
+
if (!matchedRoute) {
|
|
90
|
+
wrapper.replaceChildren(div("No Route Found"));
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
(async () => {
|
|
94
|
+
_searchParams = routerState.searchParams;
|
|
95
|
+
await executeModule(matchedRoute, wrapper);
|
|
96
|
+
})();
|
|
97
|
+
});
|
|
113
98
|
return async () => {
|
|
114
|
-
const
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
const children = resolveChildren(module);
|
|
118
|
-
updateHead();
|
|
119
|
-
return van.add(wrapper, ...children);
|
|
99
|
+
const result = await executeModule(route, wrapper, true);
|
|
100
|
+
initialized = true;
|
|
101
|
+
return result;
|
|
120
102
|
};
|
|
121
103
|
}
|
|
122
104
|
|
|
@@ -129,11 +111,8 @@ export const Router = (initialProps = /* istanbul ignore next */ {}) => {
|
|
|
129
111
|
}
|
|
130
112
|
|
|
131
113
|
(async () => {
|
|
132
|
-
|
|
133
|
-
await
|
|
134
|
-
const children = resolveChildren(module);
|
|
135
|
-
wrapper.replaceChildren(...children);
|
|
136
|
-
updateHead();
|
|
114
|
+
_searchParams = routerState.searchParams;
|
|
115
|
+
await executeModule(matchedRoute, wrapper);
|
|
137
116
|
})();
|
|
138
117
|
});
|
|
139
118
|
|
package/router/state.mjs
CHANGED
|
@@ -18,6 +18,16 @@ export const fixRouteUrl = (url) => {
|
|
|
18
18
|
const initialPath = !isServer ? globalThis.location.pathname : "/";
|
|
19
19
|
const initialSearch = !isServer ? globalThis.location.search : "";
|
|
20
20
|
|
|
21
|
+
/**
|
|
22
|
+
* Normalize a search string to a plain string (strip leading "?")
|
|
23
|
+
* @param {string} search
|
|
24
|
+
* @returns {string}
|
|
25
|
+
*/
|
|
26
|
+
const normalizeSearch = (search) => {
|
|
27
|
+
if (!search) return "";
|
|
28
|
+
return search.startsWith("?") ? search.slice(1) : search;
|
|
29
|
+
};
|
|
30
|
+
|
|
21
31
|
const STATE_PROXY = "_proxy";
|
|
22
32
|
const proxyProps = {
|
|
23
33
|
value: 1,
|
|
@@ -72,7 +82,7 @@ export function microStore(init) {
|
|
|
72
82
|
target[prop] = defineProxy(sp, sv, {});
|
|
73
83
|
}
|
|
74
84
|
} else if (isPlainObject) {
|
|
75
|
-
|
|
85
|
+
defineProxy(prop, value, target);
|
|
76
86
|
} else if (!Array.isArray(value) && value != null) {
|
|
77
87
|
defineProxy(prop, value, target);
|
|
78
88
|
} else {
|
|
@@ -82,29 +92,20 @@ export function microStore(init) {
|
|
|
82
92
|
return target;
|
|
83
93
|
}
|
|
84
94
|
|
|
85
|
-
/**
|
|
86
|
-
* @type {typeof import("./types.d.ts").routerState}
|
|
87
|
-
*/
|
|
88
|
-
const initialStatus = isServer
|
|
89
|
-
? "success"
|
|
90
|
-
: (globalThis.__DATA_CACHE ? "success" : "idle");
|
|
91
|
-
|
|
92
95
|
export const routerState = microStore({
|
|
93
96
|
pathname: initialPath,
|
|
94
|
-
searchParams:
|
|
97
|
+
searchParams: normalizeSearch(initialSearch),
|
|
95
98
|
params: {},
|
|
96
|
-
|
|
99
|
+
loading: false,
|
|
97
100
|
});
|
|
98
101
|
|
|
99
102
|
/**
|
|
100
103
|
* @type {typeof import("./types.d.ts").setRouterState}
|
|
101
104
|
*/
|
|
102
105
|
export const setRouterState = (path, search = undefined, params = {}) => {
|
|
103
|
-
const [pathname,
|
|
106
|
+
const [pathname, searchPart] = fixRouteUrl(path).split("?");
|
|
104
107
|
routerState.pathname = pathname;
|
|
105
|
-
routerState.searchParams =
|
|
106
|
-
search || searchParams || "",
|
|
107
|
-
);
|
|
108
|
+
routerState.searchParams = normalizeSearch(search || searchPart || "");
|
|
108
109
|
Object.keys(routerState.params).forEach((key) =>
|
|
109
110
|
delete routerState.params[key]
|
|
110
111
|
);
|
package/router/types.d.ts
CHANGED
|
@@ -83,10 +83,9 @@ export const isLazyComponent: (component: unknown) => boolean;
|
|
|
83
83
|
|
|
84
84
|
export const executeLifecycle: (
|
|
85
85
|
route: RouteEntry | {
|
|
86
|
-
preload?: (params?: Record<string,
|
|
87
|
-
load?: (params?: Record<string, string>) => unknown
|
|
86
|
+
preload?: (params?: Record<string, unknown>) => unknown;
|
|
87
|
+
load?: (params?: Record<string, string>) => Promise<unknown>;
|
|
88
88
|
} | null,
|
|
89
|
-
params: Record<string, string> | undefined,
|
|
90
89
|
) => Promise<boolean>;
|
|
91
90
|
|
|
92
91
|
export const useRouteData: <T>() => T | undefined;
|
|
@@ -104,12 +103,8 @@ export type RouteEntry = {
|
|
|
104
103
|
path: string;
|
|
105
104
|
component: () => Promise<ComponentModule>;
|
|
106
105
|
params?: Record<string, string>;
|
|
107
|
-
preload?: (
|
|
108
|
-
|
|
109
|
-
) => boolean | void | Promise<boolean | void>;
|
|
110
|
-
load?: (
|
|
111
|
-
params?: Record<string, string>,
|
|
112
|
-
) => boolean | void | Promise<boolean | void>;
|
|
106
|
+
preload?: (params?: Record<string, string>) => unknown;
|
|
107
|
+
load?: (params?: Record<string, string>) => Promise<unknown>;
|
|
113
108
|
};
|
|
114
109
|
|
|
115
110
|
export type ImportFn = () => LazyComponent;
|
|
@@ -124,12 +119,8 @@ export type RouteProps = {
|
|
|
124
119
|
| VanComponent
|
|
125
120
|
| ComponentFn
|
|
126
121
|
| (() => Promise<ComponentModule>);
|
|
127
|
-
preload?: (
|
|
128
|
-
|
|
129
|
-
) => boolean | void | Promise<boolean | void>;
|
|
130
|
-
load?: (
|
|
131
|
-
params?: Record<string, string>,
|
|
132
|
-
) => boolean | void | Promise<boolean | void>;
|
|
122
|
+
preload?: (params?: Record<string, string>) => unknown;
|
|
123
|
+
load?: (params?: Record<string, string>) => Promise<unknown>;
|
|
133
124
|
};
|
|
134
125
|
|
|
135
126
|
export type RouteConfig = {
|
|
@@ -145,16 +136,16 @@ export const Route: (route: RouteProps) => void;
|
|
|
145
136
|
// state.mjs
|
|
146
137
|
export type RouterState = {
|
|
147
138
|
pathname: string;
|
|
148
|
-
searchParams:
|
|
139
|
+
searchParams: string;
|
|
149
140
|
params: Record<string, string>;
|
|
150
|
-
|
|
141
|
+
loading: boolean;
|
|
151
142
|
};
|
|
152
143
|
|
|
153
144
|
export const routerState: {
|
|
154
145
|
pathname: RouterState["pathname"];
|
|
155
146
|
searchParams: RouterState["searchParams"];
|
|
156
147
|
params?: RouterState["params"];
|
|
157
|
-
|
|
148
|
+
loading: RouterState["loading"];
|
|
158
149
|
};
|
|
159
150
|
|
|
160
151
|
export const setRouterState: (
|