vite-plugin-vanjs 0.1.16 → 0.1.18
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 +21 -15
- package/package.json +3 -2
- package/plugin/helpers.mjs +13 -11
- package/plugin/index.mjs +4 -2
- package/plugin/types.d.ts +1 -1
- package/router/a.mjs +28 -16
- package/router/dataCache.mjs +168 -0
- package/router/global.d.ts +108 -53
- package/router/helpers.mjs +61 -14
- package/router/index.mjs +2 -1
- package/router/lazy.mjs +15 -53
- package/router/route.mjs +6 -6
- package/router/{cache.mjs → routeCache.mjs} +2 -2
- package/router/router.mjs +42 -27
- package/router/state.mjs +21 -15
- package/router/types.d.ts +64 -133
- package/server/global.d.ts +22 -0
- package/server/index.mjs +26 -10
- package/tsconfig.json +0 -3
package/router/helpers.mjs
CHANGED
|
@@ -1,22 +1,41 @@
|
|
|
1
1
|
import isServer from "../setup/isServer.mjs";
|
|
2
2
|
import { routerState, setRouterState } from "./state.mjs";
|
|
3
3
|
import { matchRoute } from "./matchRoute.mjs";
|
|
4
|
+
import * as dataCache from "./dataCache.mjs";
|
|
4
5
|
|
|
5
6
|
/** @typedef {typeof import("./types.d.ts").navigate} Navigate */
|
|
6
7
|
/** @typedef {import("./types.d.ts").Route} Route */
|
|
7
8
|
/** @typedef {import("./types.d.ts").VanNode} VanNode */
|
|
8
9
|
/** @typedef {import("./types.d.ts").ComponentModule} ComponentModule */
|
|
9
|
-
/** @typedef {import("./types.d.ts").DynamicModule} DynamicModule */
|
|
10
10
|
/** @typedef {import("./types.d.ts").ComponentFn} ComponentFn */
|
|
11
11
|
/** @typedef {import("./types.d.ts").LazyComponent} LazyComponent */
|
|
12
12
|
|
|
13
|
+
/**
|
|
14
|
+
* Returns the HREF string value
|
|
15
|
+
* @param {unknown} v
|
|
16
|
+
* @returns {string}
|
|
17
|
+
*/
|
|
18
|
+
export const getValue = (v) => {
|
|
19
|
+
return typeof v === "function" ? v() : v.rawVal ? v.val : v;
|
|
20
|
+
};
|
|
21
|
+
|
|
13
22
|
/**
|
|
14
23
|
* Check if selected page is the current page;
|
|
15
24
|
* @param {string} pageName
|
|
16
25
|
* @returns {boolean}
|
|
17
26
|
*/
|
|
18
27
|
export const isCurrentPage = (pageName) => {
|
|
19
|
-
return routerState.pathname === pageName;
|
|
28
|
+
return routerState.pathname === getValue(pageName);
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Check if selected page is related to the current page;
|
|
33
|
+
* @param {string} pageName
|
|
34
|
+
* @returns {boolean}
|
|
35
|
+
*/
|
|
36
|
+
export const isCurrentLocation = (pageName) => {
|
|
37
|
+
const pathName = routerState.pathname;
|
|
38
|
+
return pathName !== "/" && pathName.includes(getValue(pageName));
|
|
20
39
|
};
|
|
21
40
|
|
|
22
41
|
/**
|
|
@@ -25,28 +44,46 @@ export const isCurrentPage = (pageName) => {
|
|
|
25
44
|
* @returns {component is (() => LazyComponent)}
|
|
26
45
|
*/
|
|
27
46
|
export const isLazyComponent = (component) => {
|
|
28
|
-
|
|
29
|
-
if (isServer && typeof component === "function") {
|
|
30
|
-
return component.constructor.name.includes("AsyncFunction");
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
// Client: Check if it's designated as lazy
|
|
47
|
+
if (typeof component !== "function") return false;
|
|
34
48
|
// @ts-expect-error - this property is optional and on purpose
|
|
35
|
-
return component?.isLazy === true
|
|
49
|
+
return component?.isLazy === true ||
|
|
50
|
+
component.constructor.name.includes("AsyncFunction");
|
|
36
51
|
};
|
|
37
52
|
|
|
38
53
|
/**
|
|
39
54
|
* Execute lifecycle methods preload and / or load
|
|
40
|
-
* @param {
|
|
55
|
+
* @param {import("./types.d.ts").RouteEntry} route
|
|
41
56
|
* @param {Record<string, string> | undefined} params
|
|
42
57
|
* @returns {Promise<boolean>}
|
|
43
58
|
*/
|
|
44
|
-
export const executeLifecycle = async (
|
|
59
|
+
export const executeLifecycle = async (route, params) => {
|
|
45
60
|
// istanbul ignore next
|
|
46
|
-
if (!route) return true;
|
|
47
61
|
try {
|
|
48
|
-
if (route
|
|
49
|
-
|
|
62
|
+
if (!route) return true;
|
|
63
|
+
|
|
64
|
+
let data;
|
|
65
|
+
const preload = route.preload;
|
|
66
|
+
const load = route.load;
|
|
67
|
+
|
|
68
|
+
if (preload) await preload(params);
|
|
69
|
+
if (!data && load) data = await load(params);
|
|
70
|
+
if (data) {
|
|
71
|
+
// initialLoaded = true;
|
|
72
|
+
const pathname = routerState.pathname;
|
|
73
|
+
const cacheKey = Object.keys(params || {}).length === 0
|
|
74
|
+
? ""
|
|
75
|
+
: JSON.stringify(params);
|
|
76
|
+
dataCache.set(pathname, cacheKey, {
|
|
77
|
+
data,
|
|
78
|
+
error: null,
|
|
79
|
+
status: "success",
|
|
80
|
+
timestamp: Date.now(),
|
|
81
|
+
});
|
|
82
|
+
if (!isServer) {
|
|
83
|
+
dataCache.touch(pathname);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
50
87
|
return true;
|
|
51
88
|
} catch (error) {
|
|
52
89
|
// istanbul ignore next
|
|
@@ -56,6 +93,16 @@ export const executeLifecycle = async ({ route }, params) => {
|
|
|
56
93
|
}
|
|
57
94
|
};
|
|
58
95
|
|
|
96
|
+
/**
|
|
97
|
+
* Convenience hook to get the current route's cached data.
|
|
98
|
+
* @returns {any | undefined}
|
|
99
|
+
*/
|
|
100
|
+
export const useRouteData = () => {
|
|
101
|
+
const params = routerState.params;
|
|
102
|
+
const key = Object.keys(params).length === 0 ? "" : JSON.stringify(params);
|
|
103
|
+
return dataCache.get(routerState.pathname, key)?.data;
|
|
104
|
+
};
|
|
105
|
+
|
|
59
106
|
/**
|
|
60
107
|
* Client only navigation utility.
|
|
61
108
|
* @type {Navigate}
|
package/router/index.mjs
CHANGED
|
@@ -5,6 +5,7 @@ export * from "./a.mjs";
|
|
|
5
5
|
export * from "./state.mjs";
|
|
6
6
|
export * from "./lazy.mjs";
|
|
7
7
|
export * from "./helpers.mjs";
|
|
8
|
-
export * from "./
|
|
8
|
+
export * from "./routeCache.mjs";
|
|
9
|
+
export * as dataCache from "./dataCache.mjs";
|
|
9
10
|
export * from "./unwrap.mjs";
|
|
10
11
|
export * from "./extractParams.mjs";
|
package/router/lazy.mjs
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
// router/lazy.mjs
|
|
2
|
-
import
|
|
3
|
-
import isServer from "../setup/isServer.mjs";
|
|
4
|
-
import { cache, getCached } from "./cache.mjs";
|
|
2
|
+
import { cacheRoute, getCachedRoute } from "./routeCache.mjs";
|
|
5
3
|
|
|
6
4
|
/** @typedef {import('./types').VanNode} VanNode */
|
|
7
5
|
/** @typedef {import('./types').DynamicModule} DynamicModule */
|
|
@@ -10,63 +8,27 @@ import { cache, getCached } from "./cache.mjs";
|
|
|
10
8
|
|
|
11
9
|
/**
|
|
12
10
|
* Registers a lazy component.
|
|
11
|
+
* Both server and client return an async function that resolves the module.
|
|
13
12
|
* @type {typeof import("./types").lazy}
|
|
14
13
|
*/
|
|
15
14
|
export const lazy = (importFn) => {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
const cached = getCached(importFn);
|
|
19
|
-
/* istanbul ignore next */
|
|
20
|
-
if (cached) {
|
|
21
|
-
return cached;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
const module = await importFn();
|
|
25
|
-
/** @type {ComponentFn} */
|
|
26
|
-
const component = module?.default || module.Page;
|
|
27
|
-
/** @type {ComponentModule} */
|
|
28
|
-
const result = { component, route: module.route };
|
|
29
|
-
|
|
30
|
-
cache(importFn, result);
|
|
31
|
-
return result;
|
|
32
|
-
};
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
let initialized = false;
|
|
36
|
-
/** @type {import("vanjs-core").State<(ComponentModule["component"] | (() => string))>} */
|
|
37
|
-
const component = van.state(() => "Loading..");
|
|
38
|
-
/** @type {import("vanjs-core").State<ComponentModule["route"]>} */
|
|
39
|
-
const route = van.state({});
|
|
40
|
-
|
|
41
|
-
const load = () => {
|
|
42
|
-
if (initialized) return;
|
|
43
|
-
|
|
44
|
-
const cached = getCached(importFn);
|
|
15
|
+
const resolveModule = async () => {
|
|
16
|
+
const cached = getCachedRoute(importFn);
|
|
45
17
|
/* istanbul ignore next */
|
|
46
18
|
if (cached) {
|
|
47
|
-
|
|
48
|
-
route.val = cached.route;
|
|
49
|
-
return;
|
|
19
|
+
return cached;
|
|
50
20
|
}
|
|
51
21
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
const pageComponent = module?.default || module.Page;
|
|
58
|
-
cache(importFn, { component: pageComponent, route: module.route });
|
|
59
|
-
component.val = pageComponent;
|
|
60
|
-
route.val = module.route;
|
|
61
|
-
},
|
|
62
|
-
);
|
|
63
|
-
};
|
|
22
|
+
const module = await importFn();
|
|
23
|
+
/** @type {ComponentFn} */
|
|
24
|
+
const component = module?.default || module.Page;
|
|
25
|
+
/** @type {ComponentModule} */
|
|
26
|
+
const result = { component, route: module.route };
|
|
64
27
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
return { component: component.val(), route: route.val };
|
|
28
|
+
cacheRoute(importFn, result);
|
|
29
|
+
return result;
|
|
68
30
|
};
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
return
|
|
31
|
+
|
|
32
|
+
resolveModule.isLazy = true;
|
|
33
|
+
return resolveModule;
|
|
72
34
|
};
|
package/router/route.mjs
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
// router/
|
|
1
|
+
// router/route.mjs
|
|
2
2
|
import { isLazyComponent } from "./helpers.mjs";
|
|
3
3
|
import { routes } from "./routes.mjs";
|
|
4
4
|
import { lazy } from "./lazy.mjs";
|
|
5
|
+
|
|
5
6
|
/** @typedef {import("./types.d.ts").RouteProps} RouteProps */
|
|
6
7
|
/** @typedef {import("./types.d.ts").ComponentModule} ComponentModule */
|
|
7
8
|
|
|
@@ -17,7 +18,7 @@ export const Route = (routeProps) => {
|
|
|
17
18
|
return;
|
|
18
19
|
}
|
|
19
20
|
|
|
20
|
-
// If component isn't lazy,
|
|
21
|
+
// If component isn't lazy, wrap it with preload/load
|
|
21
22
|
if (!isLazyComponent(component)) {
|
|
22
23
|
/** @type {() => Promise<ComponentModule>} */
|
|
23
24
|
const wrappedComponent = lazy(() =>
|
|
@@ -26,11 +27,10 @@ export const Route = (routeProps) => {
|
|
|
26
27
|
route: { preload, load },
|
|
27
28
|
})
|
|
28
29
|
);
|
|
29
|
-
routes.push({ ...rest, path, component: wrappedComponent });
|
|
30
|
+
routes.push({ ...rest, path, preload, load, component: wrappedComponent });
|
|
30
31
|
return;
|
|
31
32
|
}
|
|
32
33
|
|
|
33
|
-
//
|
|
34
|
-
|
|
35
|
-
routes.push(routeProps);
|
|
34
|
+
// Lazy component — same async function on server and client
|
|
35
|
+
routes.push({ ...rest, path, component, preload, load });
|
|
36
36
|
};
|
|
@@ -7,9 +7,9 @@
|
|
|
7
7
|
const routeCache = new Map();
|
|
8
8
|
|
|
9
9
|
/** @type {GetCachedRoute} */
|
|
10
|
-
export const
|
|
10
|
+
export const getCachedRoute = (key) => routeCache.get(key);
|
|
11
11
|
|
|
12
12
|
/** @type {CacheRoute} */
|
|
13
|
-
export const
|
|
13
|
+
export const cacheRoute = (key, value) => {
|
|
14
14
|
routeCache.set(key, value);
|
|
15
15
|
};
|
package/router/router.mjs
CHANGED
|
@@ -6,7 +6,7 @@ import { executeLifecycle } from "./helpers.mjs";
|
|
|
6
6
|
import { unwrap } from "./unwrap.mjs";
|
|
7
7
|
import { hydrate } from "../client/index.mjs";
|
|
8
8
|
import { Head, initializeHeadTags } from "../meta/index.mjs";
|
|
9
|
-
|
|
9
|
+
import * as dataCache from "./dataCache.mjs";
|
|
10
10
|
import "virtual:@vanjs/routes";
|
|
11
11
|
|
|
12
12
|
/** @typedef {import("./types.d.ts").ComponentModule} ComponentModule */
|
|
@@ -34,7 +34,7 @@ const resolveChildren = (module) => {
|
|
|
34
34
|
const updateHead = () => {
|
|
35
35
|
// istanbul ignore else
|
|
36
36
|
if (document.head) {
|
|
37
|
-
|
|
37
|
+
hydrate(document.head, Head());
|
|
38
38
|
}
|
|
39
39
|
};
|
|
40
40
|
|
|
@@ -64,16 +64,12 @@ const initClient = () => {
|
|
|
64
64
|
|
|
65
65
|
export const Router = (initialProps = /* istanbul ignore next */ {}) => {
|
|
66
66
|
const { div, main } = van.tags;
|
|
67
|
-
|
|
68
67
|
const props = Object.fromEntries(
|
|
69
68
|
Object.entries(initialProps).filter(([_, val]) => val !== undefined),
|
|
70
69
|
);
|
|
71
|
-
const wrapper = main({ ...props, "data-root":
|
|
72
|
-
|
|
73
|
-
// Initialize Head BEFORE any route matching or lifecycle execution
|
|
74
|
-
if (!isServer) initClient();
|
|
75
|
-
|
|
70
|
+
const wrapper = main({ ...props, "data-root": "" });
|
|
76
71
|
const route = matchRoute(routerState.pathname);
|
|
72
|
+
|
|
77
73
|
/* istanbul ignore else */
|
|
78
74
|
if (!route) return van.add(wrapper, div("No Route Found"));
|
|
79
75
|
|
|
@@ -81,45 +77,64 @@ export const Router = (initialProps = /* istanbul ignore next */ {}) => {
|
|
|
81
77
|
|
|
82
78
|
// Server-side rendering
|
|
83
79
|
if (isServer) {
|
|
84
|
-
return
|
|
80
|
+
return async () => {
|
|
85
81
|
try {
|
|
82
|
+
// 1. Resolve the module first (to get route lifecycle hooks)
|
|
86
83
|
const module = await route.component();
|
|
87
|
-
|
|
88
|
-
|
|
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);
|
|
89
91
|
} catch (error) {
|
|
90
92
|
/* istanbul ignore next */
|
|
91
93
|
console.error("Router error:", error);
|
|
92
94
|
/* istanbul ignore next */
|
|
93
95
|
return van.add(wrapper, div("Error loading page"));
|
|
94
96
|
}
|
|
95
|
-
}
|
|
97
|
+
};
|
|
96
98
|
}
|
|
97
99
|
|
|
98
|
-
//
|
|
100
|
+
// Init client here
|
|
101
|
+
if (!isServer) initClient();
|
|
102
|
+
|
|
103
|
+
// Client-side: hydrate data cache from SSR output
|
|
104
|
+
// This must happen BEFORE any component renders so useRouteData() works
|
|
105
|
+
if (globalThis.__DATA_CACHE) {
|
|
106
|
+
dataCache.hydrateFromJSON(globalThis.__DATA_CACHE);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Client-side: check if hydrating SSR content or pure SPA
|
|
99
110
|
const root = document.querySelector("[data-root]");
|
|
100
111
|
|
|
101
112
|
if (root) {
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
113
|
+
return async () => {
|
|
114
|
+
const module = await route.component();
|
|
115
|
+
|
|
116
|
+
await executeLifecycle(module.route, route.params);
|
|
117
|
+
const children = resolveChildren(module);
|
|
118
|
+
updateHead();
|
|
119
|
+
return van.add(wrapper, ...children);
|
|
120
|
+
};
|
|
107
121
|
}
|
|
108
122
|
|
|
109
|
-
// SPA path - reactive routing
|
|
123
|
+
// Pure SPA path - reactive routing
|
|
110
124
|
van.derive(() => {
|
|
111
|
-
const
|
|
112
|
-
if (!
|
|
125
|
+
const matchedRoute = matchRoute(routerState.pathname);
|
|
126
|
+
if (!matchedRoute) {
|
|
113
127
|
wrapper.replaceChildren(div("No Route Found"));
|
|
114
128
|
return;
|
|
115
129
|
}
|
|
116
130
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
131
|
+
(async () => {
|
|
132
|
+
const module = await matchedRoute.component();
|
|
133
|
+
await executeLifecycle(module.route, matchedRoute.params);
|
|
134
|
+
const children = resolveChildren(module);
|
|
135
|
+
wrapper.replaceChildren(...children);
|
|
136
|
+
updateHead();
|
|
137
|
+
})();
|
|
123
138
|
});
|
|
124
139
|
|
|
125
140
|
return wrapper;
|
package/router/state.mjs
CHANGED
|
@@ -53,21 +53,27 @@ const defineProxy = (key, value, target) => {
|
|
|
53
53
|
return stateObj;
|
|
54
54
|
};
|
|
55
55
|
|
|
56
|
+
/** @typedef */
|
|
57
|
+
|
|
56
58
|
/**
|
|
57
|
-
* @
|
|
59
|
+
* @template {Record<string, unknown>} T
|
|
60
|
+
* @param {T} init
|
|
58
61
|
* @returns {T}
|
|
59
62
|
*/
|
|
60
|
-
export function
|
|
63
|
+
export function microStore(init) {
|
|
64
|
+
/** @type {T} */
|
|
61
65
|
const target = {};
|
|
62
66
|
for (const [prop, value] of Object.entries(init)) {
|
|
63
|
-
|
|
64
|
-
value &&
|
|
65
|
-
|
|
66
|
-
) {
|
|
67
|
+
const isPlainObject = value && typeof value === "object" &&
|
|
68
|
+
!Array.isArray(value) && Object.getPrototypeOf(value) === Object;
|
|
69
|
+
|
|
70
|
+
if (isPlainObject && Object.keys(value).length > 0) {
|
|
67
71
|
for (const [sp, sv] of Object.entries(value)) {
|
|
68
72
|
target[prop] = defineProxy(sp, sv, {});
|
|
69
73
|
}
|
|
70
|
-
} else if (
|
|
74
|
+
} else if (isPlainObject) {
|
|
75
|
+
target[prop] = value;
|
|
76
|
+
} else if (!Array.isArray(value) && value != null) {
|
|
71
77
|
defineProxy(prop, value, target);
|
|
72
78
|
} else {
|
|
73
79
|
console.warn(typeof value + " is not supported.");
|
|
@@ -79,25 +85,25 @@ export function miniStore(init) {
|
|
|
79
85
|
/**
|
|
80
86
|
* @type {typeof import("./types.d.ts").routerState}
|
|
81
87
|
*/
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
export const routerState = miniStore({
|
|
88
|
+
const initialStatus = isServer
|
|
89
|
+
? "success"
|
|
90
|
+
: (globalThis.__DATA_CACHE ? "success" : "idle");
|
|
91
|
+
|
|
92
|
+
export const routerState = microStore({
|
|
88
93
|
pathname: initialPath,
|
|
89
94
|
searchParams: new URLSearchParams(initialSearch),
|
|
90
95
|
params: {},
|
|
96
|
+
status: initialStatus,
|
|
91
97
|
});
|
|
92
98
|
|
|
93
99
|
/**
|
|
94
100
|
* @type {typeof import("./types.d.ts").setRouterState}
|
|
95
101
|
*/
|
|
96
|
-
export const setRouterState = (path, search = undefined, params) => {
|
|
102
|
+
export const setRouterState = (path, search = undefined, params = {}) => {
|
|
97
103
|
const [pathname, searchParams] = fixRouteUrl(path).split("?");
|
|
98
104
|
routerState.pathname = pathname;
|
|
99
105
|
routerState.searchParams = new URLSearchParams(
|
|
100
106
|
search || searchParams || "",
|
|
101
107
|
);
|
|
102
|
-
Object.assign(routerState.params, params
|
|
108
|
+
Object.assign(routerState.params, params);
|
|
103
109
|
};
|