vite-plugin-vanjs 0.1.23 → 0.1.25

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 CHANGED
@@ -108,18 +108,24 @@ export function elementsMatch(el1, el2, deep) {
108
108
  return false;
109
109
  }
110
110
 
111
- const childNodes1 = Array.from(el1.childNodes);
112
- const childNodes2 = Array.from(el2.childNodes);
111
+ // Filter empty text nodes — SSR omits them, client creates Text("")
112
+ // from function children returning ""
113
+ const cn1 = Array.from(el1.childNodes).filter((n) =>
114
+ n.nodeType !== 3 || /* istanbul ignore next */ n.textContent !== ""
115
+ );
116
+ const cn2 = Array.from(el2.childNodes).filter((n) =>
117
+ n.nodeType !== 3 || /* istanbul ignore next */ n.textContent !== ""
118
+ );
113
119
 
114
120
  // istanbul ignore else
115
- if (childNodes1.length !== childNodes2.length) {
121
+ if (cn1.length !== cn2.length) {
116
122
  return false;
117
123
  }
118
124
 
119
125
  // Only recurse if necessary (has childNodes with data-hk)
120
- const hasHydratedChildren = childNodes1.some((child) =>
121
- child instanceof HTMLElement && (child.hasAttribute("data-hk") ||
122
- child.querySelector("[data-hk]"))
126
+ const hasHydratedChildren = cn1.some((child) =>
127
+ child instanceof HTMLElement &&
128
+ (child.hasAttribute("data-hk") || child.querySelector("[data-hk]"))
123
129
  );
124
130
 
125
131
  // istanbul ignore next
@@ -130,7 +136,7 @@ export function elementsMatch(el1, el2, deep) {
130
136
  // Only recurse if opted in
131
137
  // istanbul ignore next
132
138
  return deep
133
- ? childNodes1.every((child, idx) => elementsMatch(child, childNodes2[idx]))
139
+ ? cn1.every((child, idx) => elementsMatch(child, cn2[idx]))
134
140
  : true;
135
141
  }
136
142
 
@@ -279,4 +285,5 @@ export const hydrate = (target, content) => {
279
285
  target.replaceChildren(...parsed);
280
286
  }
281
287
  }
288
+ return target;
282
289
  };
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?: TagProps) => void;
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
 
@@ -39,7 +40,10 @@ declare module "@vanjs/meta" {
39
40
  ) => null;
40
41
 
41
42
  export const Meta: (
42
- props: PropsWithKnownKeys<HTMLMetaElement> & { charset?: string & "utf-8" },
43
+ props: PropsWithKnownKeys<HTMLMetaElement> & {
44
+ charset?: string & "utf-8";
45
+ property?: string;
46
+ },
43
47
  ) => null;
44
48
 
45
49
  export const Style: (
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 | TagProps) => null;
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.23",
3
+ "version": "0.1.25",
4
4
  "author": "thednp",
5
5
  "license": "MIT",
6
6
  "description": "An async first mini meta-framework for VanJS powered by Vite",
@@ -88,14 +88,14 @@
88
88
  "vanjs-ext": "^0.6.3"
89
89
  },
90
90
  "devDependencies": {
91
- "@types/node": "^25.6.0",
92
- "@vitest/browser": "^4.1.5",
93
- "@vitest/coverage-istanbul": "^4.1.5",
94
- "@vitest/ui": "^4.1.5",
95
- "happy-dom": "^20.9.0",
91
+ "@types/node": "^25.9.3",
92
+ "@vitest/browser": "^4.1.8",
93
+ "@vitest/coverage-istanbul": "^4.1.8",
94
+ "@vitest/ui": "^4.1.8",
95
+ "happy-dom": "^20.10.3",
96
96
  "typescript": "6.0.3",
97
- "vite": "^8.0.10",
98
- "vitest": "^4.1.5"
97
+ "vite": "^8.0.16",
98
+ "vitest": "^4.1.8"
99
99
  },
100
100
  "engines": {
101
101
  "node": ">=20",
@@ -218,26 +218,26 @@ export const generateRouteProloaders = (route) => {
218
218
  const layoutName = "Module";
219
219
 
220
220
  return `{
221
- preload: async () => {
221
+ preload: async (params) => {
222
222
  ${
223
223
  route.layouts.map((layout) =>
224
224
  `if (${layout.id + layoutName}?.route?.preload) await ${
225
225
  layout.id + layoutName
226
- }?.route?.preload();`
226
+ }?.route?.preload(params);`
227
227
  ).join("\n ")
228
228
  }
229
- if (${moduleName}?.route?.preload) await ${moduleName}?.route?.preload();
229
+ if (${moduleName}?.route?.preload) await ${moduleName}?.route?.preload(params);
230
230
  },
231
- load: async () => {
231
+ load: async (params) => {
232
232
  let _data;
233
233
  ${
234
234
  route.layouts.map((layout) =>
235
235
  `if (${layout.id + layoutName}?.route?.load) await ${
236
236
  layout.id + layoutName
237
- }?.route?.load();`
237
+ }?.route?.load(params);`
238
238
  ).join("\n ")
239
239
  }
240
- if (${moduleName}?.route?.load) _data = await ${moduleName}?.route?.load();
240
+ if (${moduleName}?.route?.load) _data = await ${moduleName}?.route?.load(params);
241
241
  return _data;
242
242
  }
243
243
  }`;
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
- export declare const VitePluginVanJS: (
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" : null;
63
+ return isPage ? "page" : isLocation ? "location" : "";
64
64
  });
65
65
 
66
66
  return van.tags.a(newProps, children || otherChildren);
@@ -139,8 +139,8 @@ declare module "@vanjs/router" {
139
139
  path: string;
140
140
  component: () => Promise<ComponentModule>;
141
141
  params?: Record<string, string>;
142
- preload?: () => boolean | void | Promise<boolean | void>;
143
- load?: () => boolean | void | Promise<boolean | void>;
142
+ preload?: (params?: Record<string, string>) => unknown;
143
+ load?: (params?: Record<string, string>) => Promise<unknown>;
144
144
  };
145
145
 
146
146
  export type ImportFn = () => LazyComponent;
@@ -153,8 +153,8 @@ declare module "@vanjs/router" {
153
153
  | VanComponent
154
154
  | ComponentFn
155
155
  | (() => Promise<ComponentModule>);
156
- preload?: () => boolean | void | Promise<boolean | void>;
157
- load?: () => boolean | void | Promise<boolean | void>;
156
+ preload?: (params?: Record<string, string>) => unknown;
157
+ load?: (params?: Record<string, string>) => Promise<unknown>;
158
158
  };
159
159
 
160
160
  export type RouteConfig = {
@@ -185,7 +185,7 @@ declare module "@vanjs/router" {
185
185
  pathname: string;
186
186
  searchParams: string;
187
187
  params: Record<string, string>;
188
- status: "idle" | "pending" | "success" | "error";
188
+ loading: boolean;
189
189
  };
190
190
 
191
191
  /**
@@ -196,7 +196,7 @@ declare module "@vanjs/router" {
196
196
  pathname: RouterState["pathname"];
197
197
  searchParams: RouterState["searchParams"];
198
198
  params?: RouterState["params"];
199
- status: RouterState["status"];
199
+ loading: RouterState["loading"];
200
200
  };
201
201
 
202
202
  /**
@@ -285,8 +285,8 @@ declare module "@vanjs/router" {
285
285
  */
286
286
  export const executeLifecycle: (
287
287
  route: RouteEntry | {
288
- preload?: () => unknown;
289
- load?: () => unknown;
288
+ preload?: (params?: Record<string, string>) => unknown;
289
+ load?: (params?: Record<string, string>) => Promise<unknown>;
290
290
  } | null,
291
291
  ) => Promise<boolean>;
292
292
 
@@ -1,7 +1,11 @@
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";
4
5
  import { unwrap } from "./unwrap.mjs";
6
+ import { hydrate } from "../client/index.mjs";
7
+ import { Head } from "../meta/index.mjs";
8
+
5
9
  import * as dataCache from "./dataCache.mjs";
6
10
 
7
11
  /** @typedef {typeof import("./types.d.ts").navigate} Navigate */
@@ -12,6 +16,16 @@ import * as dataCache from "./dataCache.mjs";
12
16
  /** @typedef {import("./types.d.ts").ComponentFn} ComponentFn */
13
17
  /** @typedef {import("./types.d.ts").LazyComponent} LazyComponent */
14
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
+
15
29
  /**
16
30
  * Resolve component children from a module
17
31
  * @param {ComponentModule | Element | Element[] | any} module
@@ -51,8 +65,8 @@ export const getCacheKey = () => {
51
65
  */
52
66
  export const isCurrentPage = (pageName) => {
53
67
  const href = getValue(pageName);
54
- if (isServer) return routerState.pathname === href;
55
- const url = new URL(href, globalThis.location.origin);
68
+ const url = new URL(href, "http://localhost:5173");
69
+ // console.log({ href }, routerState.searchParams, url.searchParams.toString())
56
70
  return routerState.pathname === url.pathname &&
57
71
  routerState.searchParams === url.searchParams.toString();
58
72
  };
@@ -63,8 +77,12 @@ export const isCurrentPage = (pageName) => {
63
77
  * @returns {boolean}
64
78
  */
65
79
  export const isCurrentLocation = (pageName) => {
66
- const pathName = routerState.pathname;
67
- return pathName !== "/" && pathName.includes(getValue(pageName));
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;
68
86
  };
69
87
 
70
88
  /**
@@ -96,9 +114,9 @@ export const executeLifecycle = async (route) => {
96
114
  const pathname = routerState.pathname;
97
115
  const cacheKey = getCacheKey();
98
116
 
99
- if (preload) await preload();
117
+ if (preload) await preload(route.params);
100
118
  if (!data && load && !dataCache.has(pathname, cacheKey)) {
101
- data = await load();
119
+ data = await load(route.params);
102
120
  } else if (load && dataCache.has(pathname, cacheKey)) {
103
121
  dataCache.touch(pathname);
104
122
  }
@@ -123,6 +141,34 @@ export const executeLifecycle = async (route) => {
123
141
  }
124
142
  };
125
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
+ try {
155
+ // 1. Resolve the module first (to get route lifecycle hooks)
156
+ const module = await route.component();
157
+ // 2. Execute lifecycle (a complete route includes all props)
158
+ await executeLifecycle(Object.assign(route, module.route));
159
+ // 3. Resolve children
160
+ const children = resolveChildren(module);
161
+ // 4. Update <head> in the client
162
+ if (!isServer) updateHead();
163
+ // 6. Update / replace children in wrapper
164
+ if (ssr) return van.add(wrapper, ...children);
165
+ else wrapper.replaceChildren(...children);
166
+ } finally {
167
+ // 5. Set Loading State
168
+ routerState.loading = false;
169
+ }
170
+ };
171
+
126
172
  /**
127
173
  * Convenience hook to get the current route's cached data.
128
174
  * @returns {any | undefined}
package/router/router.mjs CHANGED
@@ -3,9 +3,8 @@ import isServer from "../setup/isServer.mjs";
3
3
  import { MODE } from "../plugin/const.mjs";
4
4
  import { routerState, setRouterState } from "./state.mjs";
5
5
  import { matchRoute } from "./matchRoute.mjs";
6
- import { executeLifecycle, resolveChildren } from "./helpers.mjs";
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
 
@@ -15,36 +14,6 @@ const isDev = MODE === "development";
15
14
  /** @typedef {import("./types.d.ts").RouteEntry} RouteEntry */
16
15
  /** @typedef {import("./types.d.ts").VanNode} VanNode */
17
16
 
18
- /**
19
- * Update head tags
20
- */
21
- const updateHead = () => {
22
- // istanbul ignore else
23
- if (document.head) {
24
- hydrate(document.head, Head());
25
- }
26
- };
27
-
28
- /**
29
- * @param {RouteEntry} route
30
- * @param {HTMLElement} wrapper
31
- * @param {boolean} ssr
32
- * @returns
33
- */
34
- const executeModule = async (route, wrapper, ssr) => {
35
- // 1. Resolve the module first (to get route lifecycle hooks)
36
- const module = await route.component();
37
- // 2. Execute lifecycle
38
- await executeLifecycle(module.route);
39
- // 3. Resolve children
40
- const children = resolveChildren(module);
41
- // 4. Update <head> in the client
42
- if (!isServer) updateHead();
43
- // 5. Update / replace children in wrapper
44
- if (ssr) return van.add(wrapper, ...children);
45
- else wrapper.replaceChildren(...children);
46
- };
47
-
48
17
  /**
49
18
  * Initialize client-side router (Head + popstate listener)
50
19
  */
@@ -83,7 +52,7 @@ export const Router = (initialProps = /* istanbul ignore next */ {}) => {
83
52
  /* istanbul ignore else */
84
53
  if (!route) return van.add(wrapper, div("No Route Found"));
85
54
  // It's important to READ the params
86
- Object.assign(routerState.params, route.params || {});
55
+ Object.assign(routerState.params, route.params);
87
56
 
88
57
  // Server-side rendering
89
58
  if (isServer) {
@@ -105,6 +74,7 @@ export const Router = (initialProps = /* istanbul ignore next */ {}) => {
105
74
 
106
75
  // Client-side: hydrate data cache from SSR output
107
76
  // This must happen BEFORE any component renders so useRouteData() works
77
+ // Skip in dev mode: we manually clear dataCache on mutations for instant updates
108
78
  if (globalThis.__DATA_CACHE && !isDev) {
109
79
  dataCache.hydrateFromJSON(globalThis.__DATA_CACHE);
110
80
  }
@@ -114,7 +84,6 @@ export const Router = (initialProps = /* istanbul ignore next */ {}) => {
114
84
 
115
85
  if (root) {
116
86
  van.derive(() => {
117
- _searchParams = routerState.searchParams;
118
87
  if (!initialized) return;
119
88
  const matchedRoute = matchRoute(routerState.pathname);
120
89
  if (!matchedRoute) {
@@ -122,26 +91,27 @@ export const Router = (initialProps = /* istanbul ignore next */ {}) => {
122
91
  return;
123
92
  }
124
93
  (async () => {
125
- initialized = true;
94
+ _searchParams = routerState.searchParams;
126
95
  await executeModule(matchedRoute, wrapper);
127
96
  })();
128
97
  });
129
98
  return async () => {
130
- return await executeModule(route, wrapper, true);
99
+ const result = await executeModule(route, wrapper, true);
100
+ initialized = true;
101
+ return result;
131
102
  };
132
103
  }
133
104
 
134
105
  // Pure SPA path - reactive routing
135
106
  van.derive(() => {
136
107
  const matchedRoute = matchRoute(routerState.pathname);
137
- _searchParams = routerState.searchParams;
138
- // routerState.searchParams;
139
108
  if (!matchedRoute) {
140
109
  wrapper.replaceChildren(div("No Route Found"));
141
110
  return;
142
111
  }
143
112
 
144
113
  (async () => {
114
+ _searchParams = routerState.searchParams;
145
115
  await executeModule(matchedRoute, wrapper);
146
116
  })();
147
117
  });
package/router/state.mjs CHANGED
@@ -92,18 +92,11 @@ export function microStore(init) {
92
92
  return target;
93
93
  }
94
94
 
95
- /**
96
- * @type {typeof import("./types.d.ts").routerState}
97
- */
98
- const initialStatus = isServer
99
- ? "success"
100
- : (globalThis.__DATA_CACHE ? "success" : "idle");
101
-
102
95
  export const routerState = microStore({
103
96
  pathname: initialPath,
104
97
  searchParams: normalizeSearch(initialSearch),
105
98
  params: {},
106
- status: initialStatus,
99
+ loading: false,
107
100
  });
108
101
 
109
102
  /**
package/router/types.d.ts CHANGED
@@ -83,8 +83,8 @@ export const isLazyComponent: (component: unknown) => boolean;
83
83
 
84
84
  export const executeLifecycle: (
85
85
  route: RouteEntry | {
86
- preload?: () => unknown;
87
- load?: () => unknown;
86
+ preload?: (params?: Record<string, unknown>) => unknown;
87
+ load?: (params?: Record<string, string>) => Promise<unknown>;
88
88
  } | null,
89
89
  ) => Promise<boolean>;
90
90
 
@@ -103,8 +103,8 @@ export type RouteEntry = {
103
103
  path: string;
104
104
  component: () => Promise<ComponentModule>;
105
105
  params?: Record<string, string>;
106
- preload?: () => boolean | void | Promise<boolean | void>;
107
- load?: () => boolean | void | Promise<boolean | void>;
106
+ preload?: (params?: Record<string, string>) => unknown;
107
+ load?: (params?: Record<string, string>) => Promise<unknown>;
108
108
  };
109
109
 
110
110
  export type ImportFn = () => LazyComponent;
@@ -119,8 +119,8 @@ export type RouteProps = {
119
119
  | VanComponent
120
120
  | ComponentFn
121
121
  | (() => Promise<ComponentModule>);
122
- preload?: () => boolean | void | Promise<boolean | void>;
123
- load?: () => boolean | void | Promise<boolean | void>;
122
+ preload?: (params?: Record<string, string>) => unknown;
123
+ load?: (params?: Record<string, string>) => Promise<unknown>;
124
124
  };
125
125
 
126
126
  export type RouteConfig = {
@@ -138,14 +138,14 @@ export type RouterState = {
138
138
  pathname: string;
139
139
  searchParams: string;
140
140
  params: Record<string, string>;
141
- status: "idle" | "pending" | "success" | "error";
141
+ loading: boolean;
142
142
  };
143
143
 
144
144
  export const routerState: {
145
145
  pathname: RouterState["pathname"];
146
146
  searchParams: RouterState["searchParams"];
147
147
  params?: RouterState["params"];
148
- status: RouterState["status"];
148
+ loading: RouterState["loading"];
149
149
  };
150
150
 
151
151
  export const setRouterState: (