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 CHANGED
@@ -1,4 +1,4 @@
1
- import { unwrap } from "../router/index.mjs";
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
- return childNodes1.every((child, idx) =>
71
- elementsMatch(child, childNodes2[idx])
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.tagName.toLowerCase() === "head") {
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) => !isStyleLink(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) => isStyleLink(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(isStyleLink)
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
- /* istanbul ignore next - try again later */
228
- if (isStyle(existing) && isStyle(newChild)) {
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
- /* istanbul ignore next - try again later */
235
- if (isLink(existing) && isLink(newChild)) {
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
- /* istanbul ignore else - try again later */
242
- if (isLink(newChild)) {
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
- /* istanbul ignore next */
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 (isStyle(newChild)) {
272
+ else if (isTag(newChild, "style")) {
262
273
  target.appendChild(newChild);
263
- /* istanbul ignore next - try again later */
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vite-plugin-vanjs",
3
- "version": "0.1.5",
3
+ "version": "0.1.7",
4
4
  "author": "thednp",
5
5
  "license": "MIT",
6
6
  "description": "A mini meta-framework for VanJS powered by Vite",
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, routes } from "@vanjs/router/routes.mjs";
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 { Plugin } from "vite";
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 type VanJSPlugin = (
17
- options?: Partial<VanJSPluginOptions>,
18
- ) => Plugin;
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 "./routes.mjs";
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
+ };
@@ -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 "./routes.mjs";
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
@@ -1,5 +1,6 @@
1
- import isServer from "../setup/isServer.mjs";
1
+ // router/lazy.mjs
2
2
  import van from "vanjs-core";
3
+ import isServer from "../setup/isServer.mjs";
3
4
  import { cache, getCached } from "./cache.mjs";
4
5
 
5
6
  /** @typedef {import('./types').VanNode} VanNode */
@@ -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
+ };
@@ -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 "./routes.mjs";
5
- import { executeLifecycle, unwrap } from "./helpers.mjs";
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
- import { fixRouteUrl } from "./helpers.mjs";
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/jsx"].some((l) =>
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 VanComponent = () => HTMLElement | ValidVanNode | ValidVanNode[];
24
+ type VComponent = () => HTMLElement | ValidVanNode | ValidVanNode[];
25
25
  export type Source =
26
26
  | Promise<ValidVanNode>
27
- | VanComponent
28
- | (() => VanComponent)
27
+ | VComponent
28
+ | (() => VComponent)
29
29
  | ValidVanNode
30
30
  | ValidVanNode[]
31
31
  | undefined;