vite-plugin-vanjs 0.1.5 → 0.1.7
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/client/index.mjs +34 -23
- package/package.json +1 -1
- package/plugin/index.mjs +2 -1
- package/plugin/types.d.ts +4 -7
- package/router/a.mjs +1 -1
- package/router/extractParams.mjs +27 -0
- package/router/helpers.mjs +1 -79
- package/router/index.mjs +3 -0
- package/router/lazy.mjs +2 -1
- package/router/matchRoute.mjs +49 -0
- package/router/route.mjs +36 -0
- package/router/router.mjs +3 -2
- package/router/routes.mjs +0 -81
- package/router/state.mjs +13 -1
- package/router/unwrap.mjs +33 -0
- package/server/index.mjs +1 -4
- package/server/types.d.ts +3 -3
package/client/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { unwrap } from "../router/
|
|
1
|
+
import { unwrap } from "../router/unwrap.mjs";
|
|
2
2
|
import { getTagKey } from "../meta/helpers.mjs";
|
|
3
3
|
|
|
4
4
|
/**
|
|
@@ -36,8 +36,8 @@ export const styleToString = (style) => {
|
|
|
36
36
|
: /* istanbul ignore next */ "";
|
|
37
37
|
};
|
|
38
38
|
|
|
39
|
-
/** @type {(el1: HTMLElement, el2: HTMLElement | HTMLElement[]) => boolean} */
|
|
40
|
-
export function elementsMatch(el1, el2) {
|
|
39
|
+
/** @type {(el1: HTMLElement, el2: HTMLElement | HTMLElement[], deep?: boolean) => boolean} */
|
|
40
|
+
export function elementsMatch(el1, el2, deep) {
|
|
41
41
|
// Quick initial checks before recursing
|
|
42
42
|
// istanbul ignore else
|
|
43
43
|
if (
|
|
@@ -63,15 +63,25 @@ export function elementsMatch(el1, el2) {
|
|
|
63
63
|
child.querySelector("[data-hk]"))
|
|
64
64
|
);
|
|
65
65
|
|
|
66
|
+
// istanbul ignore next
|
|
66
67
|
if (!hasHydratedChildren) {
|
|
67
68
|
return true; // If no hydrated children, elements match based on initial checks
|
|
68
69
|
}
|
|
69
70
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
71
|
+
// Only recurse if opted in
|
|
72
|
+
// istanbul ignore next
|
|
73
|
+
return deep
|
|
74
|
+
? childNodes1.every((child, idx) => elementsMatch(child, childNodes2[idx]))
|
|
75
|
+
: true;
|
|
73
76
|
}
|
|
74
77
|
|
|
78
|
+
/** @type {<E extends Element = Element, T extends keyof HTMLElementTagNameMap>(target: E, ...tagNames: T[]) => boolean} */
|
|
79
|
+
const isTag = (target, ...tagNames) => {
|
|
80
|
+
return tagNames.some((tag) =>
|
|
81
|
+
target.tagName.toLowerCase() === tag.toLowerCase()
|
|
82
|
+
);
|
|
83
|
+
};
|
|
84
|
+
|
|
75
85
|
function createHydrationContext() {
|
|
76
86
|
/** @type {WeakMap<Element, Element>} */
|
|
77
87
|
const parentCache = new WeakMap();
|
|
@@ -108,6 +118,7 @@ function createHydrationContext() {
|
|
|
108
118
|
|
|
109
119
|
/** @type {(oldDom: HTMLElement, newDom: HTMLElement | HTMLElement[]) => HTMLElement} */
|
|
110
120
|
function diffAndHydrate(oldDom, newDom) {
|
|
121
|
+
if (!oldDom || !newDom) return;
|
|
111
122
|
// SPA mode
|
|
112
123
|
// istanbul ignore else
|
|
113
124
|
if (!oldDom.children.length && !elementsMatch(oldDom, newDom)) {
|
|
@@ -180,12 +191,8 @@ export const hydrate = (target, content) => {
|
|
|
180
191
|
const wrapper = unwrap(content);
|
|
181
192
|
const currentChildren = Array.from(target.children);
|
|
182
193
|
const newChildren = Array.from(wrapper.children);
|
|
183
|
-
const isStyleLink = (tag) =>
|
|
184
|
-
[HTMLStyleElement, HTMLLinkElement].some((e) => tag instanceof e);
|
|
185
|
-
const isStyle = (tag) => tag instanceof HTMLStyleElement;
|
|
186
|
-
const isLink = (tag) => tag instanceof HTMLLinkElement;
|
|
187
194
|
|
|
188
|
-
if (target
|
|
195
|
+
if (isTag(target, "head")) {
|
|
189
196
|
// Keep current tags on first hydration
|
|
190
197
|
if (!target.hasAttribute("data-h")) {
|
|
191
198
|
target.setAttribute("data-h", "");
|
|
@@ -193,14 +200,18 @@ export const hydrate = (target, content) => {
|
|
|
193
200
|
}
|
|
194
201
|
|
|
195
202
|
// Handle non-style/link tags first
|
|
196
|
-
const regularTags = newChildren.filter((child) =>
|
|
203
|
+
const regularTags = newChildren.filter((child) =>
|
|
204
|
+
!isTag(child, "style", "link")
|
|
205
|
+
);
|
|
197
206
|
|
|
198
207
|
// Handle style/link tags separately
|
|
199
|
-
const styleTags = newChildren.filter((child) =>
|
|
208
|
+
const styleTags = newChildren.filter((child) =>
|
|
209
|
+
isTag(child, "style", "link")
|
|
210
|
+
);
|
|
200
211
|
|
|
201
212
|
// Create maps for existing tags
|
|
202
213
|
const existingStyles = new Map(
|
|
203
|
-
currentChildren.filter(
|
|
214
|
+
currentChildren.filter((child) => isTag(child, "style", "link"))
|
|
204
215
|
.map((child) => [getTagKey(child), child]),
|
|
205
216
|
);
|
|
206
217
|
|
|
@@ -224,29 +235,29 @@ export const hydrate = (target, content) => {
|
|
|
224
235
|
|
|
225
236
|
// Skip if tag already exists with same content+id/href
|
|
226
237
|
if (existing) {
|
|
227
|
-
|
|
228
|
-
if (
|
|
238
|
+
// istanbul ignore next - try again later
|
|
239
|
+
if (isTag(existing, "style") && isTag(newChild, "style")) {
|
|
229
240
|
if (
|
|
230
241
|
existing.textContent === newChild.textContent &&
|
|
231
242
|
existing.id === newChild.id
|
|
232
243
|
) return;
|
|
233
244
|
}
|
|
234
|
-
|
|
235
|
-
if (
|
|
245
|
+
// istanbul ignore next - try again later
|
|
246
|
+
if (isTag(existing, "link") && isTag(newChild, "link")) {
|
|
236
247
|
if (existing.href === newChild.href) return;
|
|
237
248
|
}
|
|
238
249
|
}
|
|
239
250
|
|
|
240
251
|
// For link tags, add with disabled state first
|
|
241
|
-
|
|
242
|
-
if (
|
|
252
|
+
// istanbul ignore else - try again later
|
|
253
|
+
if (isTag(newChild, "link")) {
|
|
243
254
|
const temp = newChild.cloneNode();
|
|
244
255
|
temp.disabled = true;
|
|
245
256
|
|
|
246
257
|
const originalRel = temp.rel;
|
|
247
258
|
temp.rel = "preload";
|
|
248
259
|
temp.as = "style";
|
|
249
|
-
|
|
260
|
+
// istanbul ignore next
|
|
250
261
|
temp.onload = () => {
|
|
251
262
|
temp.rel = originalRel;
|
|
252
263
|
temp.removeAttribute("as");
|
|
@@ -258,9 +269,9 @@ export const hydrate = (target, content) => {
|
|
|
258
269
|
|
|
259
270
|
target.appendChild(temp);
|
|
260
271
|
} // For style tags, add new one first
|
|
261
|
-
else if (
|
|
272
|
+
else if (isTag(newChild, "style")) {
|
|
262
273
|
target.appendChild(newChild);
|
|
263
|
-
|
|
274
|
+
// istanbul ignore next - try again later
|
|
264
275
|
if (existing && existing.parentNode === target) {
|
|
265
276
|
// Remove old one in next frame
|
|
266
277
|
existing.remove();
|
package/package.json
CHANGED
package/plugin/index.mjs
CHANGED
|
@@ -203,7 +203,8 @@ export default function VitePluginVanJS(options = {}) {
|
|
|
203
203
|
}
|
|
204
204
|
|
|
205
205
|
const routesScript = `
|
|
206
|
-
import { Route
|
|
206
|
+
import { Route } from "@vanjs/router/route.mjs";
|
|
207
|
+
import { routes } from "@vanjs/router/routes.mjs";
|
|
207
208
|
import { lazy } from "@vanjs/router/lazy.mjs";
|
|
208
209
|
|
|
209
210
|
// Reset current routes
|
package/plugin/types.d.ts
CHANGED
|
@@ -6,18 +6,15 @@ export * from "../meta/types";
|
|
|
6
6
|
export * from "../server/types";
|
|
7
7
|
export * from "../client/types";
|
|
8
8
|
export * from "../parser/types";
|
|
9
|
-
import type {
|
|
9
|
+
import type { PluginOption } from "vite";
|
|
10
10
|
|
|
11
11
|
export type VanJSPluginOptions = {
|
|
12
12
|
routesDir: string;
|
|
13
13
|
extensions: string[];
|
|
14
14
|
};
|
|
15
15
|
|
|
16
|
-
export
|
|
17
|
-
|
|
18
|
-
) =>
|
|
19
|
-
|
|
20
|
-
// This is what your plugin actually returns, so declare it as a Plugin type
|
|
21
|
-
declare const VitePluginVanJS: VanJSPlugin;
|
|
16
|
+
export declare const VitePluginVanJS: (
|
|
17
|
+
config?: VitePluginVanJS,
|
|
18
|
+
) => PluginOption<VanJSPluginOptions>;
|
|
22
19
|
|
|
23
20
|
export default VitePluginVanJS;
|
package/router/a.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// router/a.mjs
|
|
2
2
|
import van from "vanjs-core";
|
|
3
|
-
import { matchRoute } from "./
|
|
3
|
+
import { matchRoute } from "./matchRoute.mjs";
|
|
4
4
|
import { executeLifecycle, isCurrentPage, navigate } from "./helpers.mjs";
|
|
5
5
|
|
|
6
6
|
/** @typedef {typeof import("./types").A} A */
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Extract route params
|
|
3
|
+
* @param {string} pattern
|
|
4
|
+
* @param {string} path
|
|
5
|
+
* @returns {Record<string, string> | null}
|
|
6
|
+
*/
|
|
7
|
+
export const extractParams = (pattern, path) => {
|
|
8
|
+
/** @type {Record<string, string>} */
|
|
9
|
+
const params = {};
|
|
10
|
+
const patternParts = pattern.split("/");
|
|
11
|
+
const pathParts = path.split("/");
|
|
12
|
+
|
|
13
|
+
if (patternParts.length !== pathParts.length) return null;
|
|
14
|
+
|
|
15
|
+
for (let i = 0; i < patternParts.length; i++) {
|
|
16
|
+
const patternPart = patternParts[i];
|
|
17
|
+
const pathPart = pathParts[i];
|
|
18
|
+
|
|
19
|
+
if (patternPart.startsWith(":")) {
|
|
20
|
+
params[patternPart.slice(1)] = pathPart;
|
|
21
|
+
} else if (patternPart !== pathPart) {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return params;
|
|
27
|
+
};
|
package/router/helpers.mjs
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
1
|
-
// import van from "vanjs-core";
|
|
2
1
|
import isServer from "../setup/isServer.mjs";
|
|
3
2
|
import { routerState, setRouterState } from "./state.mjs";
|
|
4
|
-
import { matchRoute } from "./
|
|
3
|
+
import { matchRoute } from "./matchRoute.mjs";
|
|
5
4
|
|
|
6
|
-
/** @typedef {typeof import("./types.d.ts").unwrap} Unwrap */
|
|
7
5
|
/** @typedef {typeof import("./types.d.ts").navigate} Navigate */
|
|
8
6
|
/** @typedef {import("./types.d.ts").Route} Route */
|
|
9
7
|
/** @typedef {import("./types.d.ts").VanNode} VanNode */
|
|
@@ -21,41 +19,6 @@ export const isCurrentPage = (pageName) => {
|
|
|
21
19
|
return routerState.pathname.val === pageName;
|
|
22
20
|
};
|
|
23
21
|
|
|
24
|
-
/**
|
|
25
|
-
* Merge the children of an Element or an array of elements with an optional array of children
|
|
26
|
-
* into the childen prperty of a simple object.
|
|
27
|
-
* @type {Unwrap}
|
|
28
|
-
*/
|
|
29
|
-
export const unwrap = (source, ...children) => {
|
|
30
|
-
const layout = () => {
|
|
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];
|
|
44
|
-
|
|
45
|
-
// return van.tags.fragment(
|
|
46
|
-
// ...(children || /* istanbul ignore next */ []),
|
|
47
|
-
// ...pageChildren,
|
|
48
|
-
// );
|
|
49
|
-
return {
|
|
50
|
-
children: [
|
|
51
|
-
...(children || /* istanbul ignore next */ []),
|
|
52
|
-
...pageChildren,
|
|
53
|
-
],
|
|
54
|
-
};
|
|
55
|
-
};
|
|
56
|
-
return layout();
|
|
57
|
-
};
|
|
58
|
-
|
|
59
22
|
/**
|
|
60
23
|
* Check if component is a lazy component
|
|
61
24
|
* @param {ComponentFn | (() => LazyComponent)} component
|
|
@@ -121,47 +84,6 @@ export const navigate = (path, options = {}) => {
|
|
|
121
84
|
}
|
|
122
85
|
};
|
|
123
86
|
|
|
124
|
-
/**
|
|
125
|
-
* Extract route params
|
|
126
|
-
* @param {string} pattern
|
|
127
|
-
* @param {string} path
|
|
128
|
-
* @returns {Record<string, string> | null}
|
|
129
|
-
*/
|
|
130
|
-
export const extractParams = (pattern, path) => {
|
|
131
|
-
/** @type {Record<string, string>} */
|
|
132
|
-
const params = {};
|
|
133
|
-
const patternParts = pattern.split("/");
|
|
134
|
-
const pathParts = path.split("/");
|
|
135
|
-
|
|
136
|
-
if (patternParts.length !== pathParts.length) return null;
|
|
137
|
-
|
|
138
|
-
for (let i = 0; i < patternParts.length; i++) {
|
|
139
|
-
const patternPart = patternParts[i];
|
|
140
|
-
const pathPart = pathParts[i];
|
|
141
|
-
|
|
142
|
-
if (patternPart.startsWith(":")) {
|
|
143
|
-
params[patternPart.slice(1)] = pathPart;
|
|
144
|
-
} else if (patternPart !== pathPart) {
|
|
145
|
-
return null;
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
return params;
|
|
150
|
-
};
|
|
151
|
-
|
|
152
|
-
/**
|
|
153
|
-
* Fix the URL of a route
|
|
154
|
-
* @param {string=} url
|
|
155
|
-
* @returns
|
|
156
|
-
*/
|
|
157
|
-
export const fixRouteUrl = (url) => {
|
|
158
|
-
if (!url) return "/";
|
|
159
|
-
if (url.startsWith("/")) {
|
|
160
|
-
return url;
|
|
161
|
-
}
|
|
162
|
-
return `/${url}`;
|
|
163
|
-
};
|
|
164
|
-
|
|
165
87
|
/**
|
|
166
88
|
* Client only reload utility
|
|
167
89
|
* WORK IN PROGRESS
|
package/router/index.mjs
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
export * from "./router.mjs";
|
|
2
2
|
export * from "./routes.mjs";
|
|
3
|
+
export * from "./route.mjs";
|
|
3
4
|
export * from "./a.mjs";
|
|
4
5
|
export * from "./state.mjs";
|
|
5
6
|
export * from "./lazy.mjs";
|
|
6
7
|
export * from "./helpers.mjs";
|
|
7
8
|
export * from "./cache.mjs";
|
|
9
|
+
export * from "./unwrap.mjs";
|
|
10
|
+
export * from "./extractParams.mjs";
|
package/router/lazy.mjs
CHANGED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
// router/matchRoute.mjs
|
|
2
|
+
import { extractParams } from "./extractParams.mjs";
|
|
3
|
+
import { routes } from "./routes.mjs";
|
|
4
|
+
|
|
5
|
+
/** @typedef {import("./types.d.ts").RouteEntry} RouteEntry */
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Find a registered route that matches the given path
|
|
9
|
+
* @param {string} initialPath
|
|
10
|
+
* @returns {RouteEntry | null}
|
|
11
|
+
*/
|
|
12
|
+
export const matchRoute = (initialPath) => {
|
|
13
|
+
const path = initialPath !== "/" && initialPath.endsWith("/")
|
|
14
|
+
? initialPath.slice(0, -1)
|
|
15
|
+
: initialPath;
|
|
16
|
+
|
|
17
|
+
// First try exact match (excluding wildcards)
|
|
18
|
+
let foundMatch = routes.find((r) => r.path === path && !r.path.includes("*"));
|
|
19
|
+
|
|
20
|
+
// Then try nested wildcard match if no exact match found
|
|
21
|
+
if (!foundMatch) {
|
|
22
|
+
// Build the path for potential nested wildcard, e.g. /admin/* for /admin/articles
|
|
23
|
+
const nestedPath = path.split("/").slice(0, -1).join("/") + "/*";
|
|
24
|
+
foundMatch = routes.find((r) => r.path === nestedPath);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// If we found either an exact or nested wildcard match, return it with params
|
|
28
|
+
if (foundMatch) {
|
|
29
|
+
return {
|
|
30
|
+
...foundMatch,
|
|
31
|
+
params: extractParams(foundMatch.path, path) ?? /* istanbul ignore next */
|
|
32
|
+
undefined,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Try parameterized routes (like /users/:id)
|
|
37
|
+
for (const route of routes) {
|
|
38
|
+
// Skip the global catch-all
|
|
39
|
+
if (route.path === "*") continue;
|
|
40
|
+
const params = extractParams(route.path, path);
|
|
41
|
+
|
|
42
|
+
if (params) {
|
|
43
|
+
return { ...route, params };
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Finally, fallback to global catch-all route
|
|
48
|
+
return routes.find((r) => r.path === "*") || null;
|
|
49
|
+
};
|
package/router/route.mjs
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// router/routes.mjs
|
|
2
|
+
import { isLazyComponent } from "./helpers.mjs";
|
|
3
|
+
import { routes } from "./routes.mjs";
|
|
4
|
+
import { lazy } from "./lazy.mjs";
|
|
5
|
+
/** @typedef {import("./types.d.ts").RouteProps} RouteProps */
|
|
6
|
+
/** @typedef {import("./types.d.ts").ComponentModule} ComponentModule */
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @param {RouteProps} routeProps
|
|
10
|
+
*/
|
|
11
|
+
export const Route = (routeProps) => {
|
|
12
|
+
const { path, component, preload, load, ...rest } = routeProps;
|
|
13
|
+
|
|
14
|
+
// istanbul ignore next - no point testing this error
|
|
15
|
+
if (routes.some((r) => r.path === path)) {
|
|
16
|
+
console.error(`🍦 @vanjs/router: duplicated route for "${path}".`);
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// If component isn't lazy, make it lazy
|
|
21
|
+
if (!isLazyComponent(component)) {
|
|
22
|
+
/** @type {() => Promise<ComponentModule>} */
|
|
23
|
+
const wrappedComponent = lazy(() =>
|
|
24
|
+
Promise.resolve({
|
|
25
|
+
Page: component,
|
|
26
|
+
route: { preload, load },
|
|
27
|
+
})
|
|
28
|
+
);
|
|
29
|
+
routes.push({ ...rest, path, component: wrappedComponent });
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Otherwise keep original component
|
|
34
|
+
// @ts-expect-error - RouteProps and RouteEntry are now equivalent
|
|
35
|
+
routes.push(routeProps);
|
|
36
|
+
};
|
package/router/router.mjs
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import van from "vanjs-core";
|
|
2
2
|
import isServer from "../setup/isServer.mjs";
|
|
3
3
|
import { routerState, setRouterState } from "./state.mjs";
|
|
4
|
-
import { matchRoute } from "./
|
|
5
|
-
import { executeLifecycle
|
|
4
|
+
import { matchRoute } from "./matchRoute.mjs";
|
|
5
|
+
import { executeLifecycle } from "./helpers.mjs";
|
|
6
|
+
import { unwrap } from "./unwrap.mjs";
|
|
6
7
|
import { hydrate } from "../client/index.mjs";
|
|
7
8
|
import { Head, initializeHeadTags } from "../meta/index.mjs";
|
|
8
9
|
|
package/router/routes.mjs
CHANGED
|
@@ -1,85 +1,4 @@
|
|
|
1
1
|
// router/routes.mjs
|
|
2
|
-
import { extractParams, isLazyComponent } from "./helpers.mjs";
|
|
3
|
-
import { lazy } from "./lazy.mjs";
|
|
4
|
-
|
|
5
2
|
/** @typedef {import("./types.d.ts").RouteEntry} RouteEntry */
|
|
6
|
-
/** @typedef {import("./types.d.ts").RouteProps} RouteProps */
|
|
7
|
-
/** @typedef {import("./types.d.ts").DynamicModule} DynamicModule */
|
|
8
|
-
/** @typedef {import("./types.d.ts").ComponentModule} ComponentModule */
|
|
9
|
-
|
|
10
3
|
/** @type {RouteEntry[]} */
|
|
11
4
|
export const routes = [];
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* @param {RouteProps} routeProps
|
|
15
|
-
*/
|
|
16
|
-
export const Route = (routeProps) => {
|
|
17
|
-
const { path, component, preload, load, ...rest } = routeProps;
|
|
18
|
-
|
|
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
|
|
26
|
-
if (!isLazyComponent(component)) {
|
|
27
|
-
/** @type {() => Promise<ComponentModule>} */
|
|
28
|
-
const wrappedComponent = lazy(() =>
|
|
29
|
-
Promise.resolve({
|
|
30
|
-
Page: component,
|
|
31
|
-
route: { preload, load },
|
|
32
|
-
})
|
|
33
|
-
);
|
|
34
|
-
routes.push({ ...rest, path, component: wrappedComponent });
|
|
35
|
-
return;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
// Otherwise keep original component
|
|
39
|
-
// @ts-expect-error - RouteProps and RouteEntry are now equivalent
|
|
40
|
-
routes.push(routeProps);
|
|
41
|
-
};
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* Find a registered route that matches the given path
|
|
45
|
-
* @param {string} initialPath
|
|
46
|
-
* @returns {RouteEntry | null}
|
|
47
|
-
*/
|
|
48
|
-
export const matchRoute = (initialPath) => {
|
|
49
|
-
const path = initialPath !== "/" && initialPath.endsWith("/")
|
|
50
|
-
? initialPath.slice(0, -1)
|
|
51
|
-
: initialPath;
|
|
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)
|
|
73
|
-
for (const route of routes) {
|
|
74
|
-
// Skip the global catch-all
|
|
75
|
-
if (route.path === "*") continue;
|
|
76
|
-
const params = extractParams(route.path, path);
|
|
77
|
-
|
|
78
|
-
if (params) {
|
|
79
|
-
return { ...route, params };
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
// Finally, fallback to global catch-all route
|
|
84
|
-
return routes.find((r) => r.path === "*") || null;
|
|
85
|
-
};
|
package/router/state.mjs
CHANGED
|
@@ -1,7 +1,19 @@
|
|
|
1
1
|
// router/state.js
|
|
2
2
|
import van from "vanjs-core";
|
|
3
3
|
import isServer from "../setup/isServer.mjs";
|
|
4
|
-
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Fix the URL of a route
|
|
7
|
+
* @param {string=} url
|
|
8
|
+
* @returns
|
|
9
|
+
*/
|
|
10
|
+
export const fixRouteUrl = (url) => {
|
|
11
|
+
if (!url) return "/";
|
|
12
|
+
if (url.startsWith("/")) {
|
|
13
|
+
return url;
|
|
14
|
+
}
|
|
15
|
+
return `/${url}`;
|
|
16
|
+
};
|
|
5
17
|
|
|
6
18
|
const initialPath = !isServer ? globalThis.location.pathname : "/";
|
|
7
19
|
const initialSearch = !isServer ? globalThis.location.search : "";
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/** @typedef {typeof import("./types.d.ts").unwrap} Unwrap */
|
|
2
|
+
/** @typedef {import("./types.d.ts").VanNode} VanNode */
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Merge the children of an Element or an array of elements with an optional array of children
|
|
6
|
+
* into the childen prperty of a simple object.
|
|
7
|
+
* @type {Unwrap}
|
|
8
|
+
*/
|
|
9
|
+
export const unwrap = (source, ...children) => {
|
|
10
|
+
const layout = () => {
|
|
11
|
+
/** @type {VanNode[]} */
|
|
12
|
+
const pageChildren =
|
|
13
|
+
source && typeof source === "object" && "children" in source &&
|
|
14
|
+
Array.isArray(source?.children)
|
|
15
|
+
? source.children
|
|
16
|
+
: typeof source === "function"
|
|
17
|
+
// @ts-expect-error - this case is specific to VanJS components in SSR
|
|
18
|
+
? [...source()?.children || source()]
|
|
19
|
+
: typeof HTMLElement !== "undefined" && source instanceof HTMLElement
|
|
20
|
+
? [...source.children]
|
|
21
|
+
: /* istanbul ignore next */ Array.isArray(source)
|
|
22
|
+
? source
|
|
23
|
+
: [source];
|
|
24
|
+
|
|
25
|
+
return {
|
|
26
|
+
children: [
|
|
27
|
+
...(children || /* istanbul ignore next */ []),
|
|
28
|
+
...pageChildren,
|
|
29
|
+
],
|
|
30
|
+
};
|
|
31
|
+
};
|
|
32
|
+
return layout();
|
|
33
|
+
};
|
package/server/index.mjs
CHANGED
|
@@ -67,9 +67,6 @@ function renderPreloadLink(file) {
|
|
|
67
67
|
}
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
-
/**
|
|
71
|
-
* @type {typeof import("./types.d.ts").renderPreloadLinks}
|
|
72
|
-
*/
|
|
73
70
|
/**
|
|
74
71
|
* @type {typeof import("./types.d.ts").renderPreloadLinks}
|
|
75
72
|
*/
|
|
@@ -82,7 +79,7 @@ export function renderPreloadLinks(modules, manifest) {
|
|
|
82
79
|
Object.entries(manifest).forEach(([id, files]) => {
|
|
83
80
|
// istanbul ignore else - don't pre-render routes, layouts and JSX stuff
|
|
84
81
|
if (
|
|
85
|
-
["src/pages", "src/routes", "vite-plugin-vanjs/
|
|
82
|
+
["src/pages", "src/routes", "vite-plugin-vanjs/"].some((l) =>
|
|
86
83
|
id.includes(l)
|
|
87
84
|
)
|
|
88
85
|
) {
|
package/server/types.d.ts
CHANGED
|
@@ -21,11 +21,11 @@ type ValidVanNode =
|
|
|
21
21
|
| VanElement
|
|
22
22
|
| TagFunc;
|
|
23
23
|
|
|
24
|
-
type
|
|
24
|
+
type VComponent = () => HTMLElement | ValidVanNode | ValidVanNode[];
|
|
25
25
|
export type Source =
|
|
26
26
|
| Promise<ValidVanNode>
|
|
27
|
-
|
|
|
28
|
-
| (() =>
|
|
27
|
+
| VComponent
|
|
28
|
+
| (() => VComponent)
|
|
29
29
|
| ValidVanNode
|
|
30
30
|
| ValidVanNode[]
|
|
31
31
|
| undefined;
|