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.
@@ -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 VanElement =
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
- | VanNode[]
30
- | (() => VanNode)
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
- preload?: (params?: Record<string, string>) => void;
127
- load?: (params?: Record<string, string>) => void;
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: VanComponent | (() => ComponentModule);
133
- preload?: (params?: Record<string, string>) => void;
134
- load?: (params?: Record<string, string>) => void;
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 single HTMLFragmentElement element.
184
- * @param source
185
- * @param {...VanNode[]} children
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
- | IsoTagFunc[]
193
- | (() => IsoTagFunc | IsoTagFunc[] | VanNode | VanNode[]),
194
- ...children: VanNode[]
195
- ) => VanNode;
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: VanComponent | JSX.Element;
199
- route: Pick<RouteEntry, "load" | "preload">;
234
+ component: ComponentFn;
235
+ route?: Pick<RouteEntry, "load" | "preload">;
200
236
  };
201
237
 
202
238
  export type DynamicModule = {
203
- Page: VanComponent | JSX.Element;
204
- default?: VanComponent | JSX.Element;
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: () => LazyComponent) => () => ComponentModule;
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
  }
@@ -1,12 +1,16 @@
1
1
  // import van from "vanjs-core";
2
- import setup from "../setup/index.mjs";
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('vanjs-core').TagFunc} TagFunc */
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 single HTMLFragmentElement element.
23
- * @param {Element | () => Element | Element[]} source
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
- const pageChildren = Array.isArray(source?.children)
30
- ? source.children
31
- : typeof source === "function"
32
- ? [...source()?.children || source()]
33
- : typeof HTMLElement !== "undefined" && source instanceof HTMLElement
34
- ? [...source.children]
35
- : /* istanbul ignore next */ Array.isArray(source)
36
- ? source
37
- : [source];
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 {unknown} component
56
- * @returns {component is (() => Promise<VanNode | VanNode[]>)}
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 (setup.isServer) {
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
- * @param {string} path - The path to navigate to
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 (!setup.isServer) {
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 (!setup.isServer) {
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 (!setup.isServer) {
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 setup from "../setup/index.mjs";
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
- * @param {Promise<VanNode>} importFn
11
- * @returns {ComponentModule | Promise<ComponentModule>}
12
+ * @type {typeof import("./types").lazy}
12
13
  */
13
14
  export const lazy = (importFn) => {
14
- if (setup.isServer) {
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
- const component = module.Page || module.default;
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
- const component = van.state(() => "");
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((module) => {
47
- const comp = module.Page || module.default;
48
- cache(importFn, { component: comp, route: module.route });
49
- component.val = comp;
50
- route.val = module.route;
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 setup from "../setup/index.mjs";
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("404 - Not Found"));
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 (setup.isServer) {
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
- const module = route.component();
43
- const children = module.component
44
- ? Array.from(unwrap(module.component).children)
45
- : [];
46
- return children.length ? van.add(wrapper, ...children) : wrapper;
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
- // If component has lifecycle methods but isn't lazy, make it lazy
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
- default: component,
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} path
45
+ * @param {string} initialPath
36
46
  * @returns {RouteEntry | null}
37
47
  */
38
- export const matchRoute = (path) => {
39
- const exactMatch = routes.find((r) => r.path === path);
40
- if (exactMatch) return { ...exactMatch, params: {} };
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 setup from "../setup/index.mjs";
3
+ import isServer from "../setup/isServer.mjs";
4
4
  import { fixRouteUrl } from "./helpers.mjs";
5
5
 
6
- const initialPath = !setup.isServer ? globalThis.location.pathname : "/";
7
- const initialSearch = !setup.isServer ? globalThis.location.search : "";
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}