vite-plugin-vanjs 0.0.2 → 0.0.4

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.
Files changed (49) hide show
  1. package/README.md +147 -21
  2. package/client/global.d.ts +41 -0
  3. package/client/index.mjs +48 -0
  4. package/client/package.json +18 -0
  5. package/client/types.d.ts +34 -0
  6. package/jsx/fragment.d.ts +0 -1
  7. package/jsx/global.d.ts +4 -3
  8. package/jsx/index.mjs +3 -3
  9. package/jsx/jsx-dev-runtime.mjs +1 -1
  10. package/jsx/jsx-runtime.mjs +1 -1
  11. package/jsx/jsx.d.ts +18 -10
  12. package/jsx/jsx.mjs +3 -2
  13. package/jsx/package.json +1 -1
  14. package/jsx/types.d.ts +16 -0
  15. package/meta/Head.mjs +81 -0
  16. package/meta/global.d.ts +60 -0
  17. package/meta/helpers.mjs +52 -0
  18. package/meta/index.mjs +3 -0
  19. package/meta/package.json +18 -0
  20. package/meta/tags.mjs +57 -0
  21. package/meta/types.d.ts +36 -0
  22. package/package.json +45 -13
  23. package/router/a.mjs +50 -0
  24. package/router/cache.mjs +14 -0
  25. package/router/global.d.ts +162 -0
  26. package/router/helpers.mjs +198 -0
  27. package/router/index.mjs +6 -0
  28. package/router/lazy.mjs +60 -0
  29. package/router/package.json +19 -0
  30. package/router/router.mjs +43 -0
  31. package/router/routes.mjs +51 -0
  32. package/router/state.mjs +27 -0
  33. package/router/types.d.ts +156 -0
  34. package/server/global.d.ts +49 -0
  35. package/server/index.mjs +97 -0
  36. package/server/package.json +20 -0
  37. package/server/types.d.ts +38 -0
  38. package/setup/global.d.ts +33 -4
  39. package/setup/index.mjs +4 -1
  40. package/setup/package.json +2 -2
  41. package/setup/van.d.ts +1 -0
  42. package/setup/vanX.d.ts +1 -0
  43. package/setup/vanX.mjs +13 -1
  44. package/src/index.mjs +36 -1
  45. package/src/types.d.ts +29 -4
  46. package/tsconfig.json +14 -2
  47. package/jsx/index.d.ts +0 -16
  48. package/jsx/utils.mjs +0 -24
  49. /package/setup/{index.d.ts → types.d.ts} +0 -0
@@ -0,0 +1,198 @@
1
+ import setup from "@vanjs/setup";
2
+ import van from "vanjs-core";
3
+ import { routerState, setRouterState } from "./state";
4
+ import { matchRoute } from "./routes";
5
+
6
+ /** @typedef {import("./types.d.ts").Route} Route */
7
+ /** @typedef {import("./types.d.ts").VanNode} VanNode */
8
+ /** @typedef {import("./types.d.ts").ComponentModule} ComponentModule */
9
+ /** @typedef {import('vanjs-core').TagFunc} TagFunc */
10
+
11
+ /**
12
+ * Check if selected page is the current page;
13
+ * @param {string} pageName
14
+ * @returns {boolean}
15
+ */
16
+ export const isCurrentPage = (pageName) => {
17
+ return routerState.pathname.val === pageName;
18
+ };
19
+
20
+ /**
21
+ * 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
+ */
27
+ export const unwrap = (source, ...children) => {
28
+ 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
+ : Array.isArray(source)
36
+ ? source
37
+ : [source];
38
+
39
+ return van.tags.fragment(
40
+ ...(children || /* istanbul ignore next */ []),
41
+ ...pageChildren,
42
+ );
43
+ };
44
+ return layout();
45
+ };
46
+
47
+ /**
48
+ * Check if component is a lazy component
49
+ * @param {unknown} component
50
+ * @returns {component is (() => Promise<VanNode | VanNode[]>)}
51
+ */
52
+ export const isLazyComponent = (component) => {
53
+ // Server: Check if it's an async function
54
+ if (setup.isServer) {
55
+ return component.constructor.name.includes("AsyncFunction");
56
+ }
57
+
58
+ // Client: Check if it's designated as lazy
59
+ return component?.isLazy === true;
60
+ };
61
+
62
+ /**
63
+ * Execute lifecycle methods preload and / or load
64
+ * @param {ComponentModule} param0
65
+ * @param {Record<string, string> | undefined} params
66
+ * @returns
67
+ */
68
+ export const executeLifecycle = async ({ route }, params) => {
69
+ // istanbul ignore next
70
+ if (!route) return true;
71
+ try {
72
+ if (route?.preload) await route.preload(params);
73
+ if (route?.load) await route.load(params);
74
+ return true;
75
+ } catch (error) {
76
+ // istanbul ignore next
77
+ console.error("Lifecycle execution error:", error);
78
+ // istanbul ignore next
79
+ return false;
80
+ }
81
+ };
82
+
83
+ /**
84
+ * Client only navigation utility.
85
+ * @param {string} path - The path to navigate to
86
+ * @param {{ replace: boolean } | undefined} options - Navigation options
87
+ * @param {boolean} options.replace - Whether to replace current history entry
88
+ * @returns {void}
89
+ */
90
+ export const navigate = (path, options = {}) => {
91
+ const { replace = false } = options;
92
+
93
+ // istanbul ignore else
94
+ if (!setup.isServer) {
95
+ // Client-side navigation
96
+ const url = new URL(path, globalThis.location.origin);
97
+ const route = matchRoute(url.pathname);
98
+
99
+ // Update history
100
+ if (replace) {
101
+ globalThis.history.replaceState({}, "", path);
102
+ } else {
103
+ globalThis.history.pushState({}, "", path);
104
+ }
105
+
106
+ // Update router state
107
+ setRouterState(url.pathname, url.search, route?.params);
108
+ } else {
109
+ // Server-side navigation - throw error
110
+ console.error("Direct navigation is not supported on server");
111
+ }
112
+ };
113
+
114
+ /**
115
+ * Extract route params
116
+ * @param {string} pattern
117
+ * @param {string} path
118
+ * @returns {Record<string, string>}
119
+ */
120
+ export const extractParams = (pattern, path) => {
121
+ const params = {};
122
+ const patternParts = pattern.split("/");
123
+ const pathParts = path.split("/");
124
+
125
+ if (patternParts.length !== pathParts.length) return null;
126
+
127
+ for (let i = 0; i < patternParts.length; i++) {
128
+ const patternPart = patternParts[i];
129
+ const pathPart = pathParts[i];
130
+
131
+ if (patternPart.startsWith(":")) {
132
+ params[patternPart.slice(1)] = pathPart;
133
+ } else if (patternPart !== pathPart) {
134
+ return null;
135
+ }
136
+ }
137
+
138
+ return params;
139
+ };
140
+
141
+ /**
142
+ * Client only reload utility
143
+ * WORK IN PROGRESS
144
+ * @param {boolean} forceFetch - Force fetch from server
145
+ * @returns {void}
146
+ */
147
+ // export const reload = (forceFetch = false) => {
148
+ // if (!setup.isServer) {
149
+ // // Client-side reload
150
+ // if (forceFetch) {
151
+ // window.location.reload();
152
+ // } else {
153
+ // // Soft reload - just update router state
154
+ // const { pathname, search } = window.location;
155
+ // setRouterState(pathname, search);
156
+ // }
157
+ // } else {
158
+ // // Server-side reload - throw error
159
+ // console.error("Reload is not supported on server");
160
+ // }
161
+ // };
162
+
163
+ /**
164
+ * Isomorphic redirect utility
165
+ * WORK IN PROGRESS
166
+ * @param {string} path - The path to redirect to
167
+ * @param {object} options - Redirect options
168
+ * @param {number} options.status - HTTP status code (server-side only)
169
+ * @param {boolean} options.replace - Whether to replace current history entry (client-side only)
170
+ * @returns {void}
171
+ */
172
+ // export const redirect = (path, options = {}) => {
173
+ // const { status = 302, replace = true } = options;
174
+
175
+ // if (!setup.isServer) {
176
+ // // Client-side redirect
177
+ // navigate(path, { replace });
178
+ // } else {
179
+ // // Server-side redirect
180
+ // const error = new Error(`Redirect to ${path}`);
181
+ // error.status = status;
182
+ // error.location = path;
183
+ // throw error;
184
+ // }
185
+ // };
186
+
187
+ // Utility to handle server-side redirects in your server entry point
188
+ // export const handleServerRedirect = (error, res) => {
189
+ // if (error.location && error.status) {
190
+ // res.writeHead(error.status, {
191
+ // Location: error.location,
192
+ // "Content-Type": "text/plain",
193
+ // });
194
+ // res.end(`Redirecting to ${error.location}...`);
195
+ // return true;
196
+ // }
197
+ // return false;
198
+ // };
@@ -0,0 +1,6 @@
1
+ export * from "./router";
2
+ export * from "./routes";
3
+ export * from "./a";
4
+ export * from "./state";
5
+ export * from "./lazy";
6
+ export * from "./helpers";
@@ -0,0 +1,60 @@
1
+ import setup from "@vanjs/setup";
2
+ import van from "vanjs-core";
3
+ import { cache, getCached } from "./cache";
4
+
5
+ /** @typedef {import('./types').VanNode} VanNode */
6
+ /** @typedef {import('./types').ComponentModule} ComponentModule */
7
+
8
+ /**
9
+ * Registers a lazy component.
10
+ * @param {Promise<VanNode>} importFn
11
+ * @returns {ComponentModule | Promise<ComponentModule>}
12
+ */
13
+ export const lazy = (importFn) => {
14
+ if (setup.isServer) {
15
+ return async () => {
16
+ const cached = getCached(importFn);
17
+ /* istanbul ignore next */
18
+ if (cached) {
19
+ return cached;
20
+ }
21
+ const module = await importFn();
22
+ const component = module.Page || module.default;
23
+ const result = { component, route: module.route };
24
+
25
+ cache(importFn, result);
26
+ return result;
27
+ };
28
+ }
29
+
30
+ let initialized = false;
31
+ const component = van.state(() => "");
32
+ const route = van.state({});
33
+
34
+ const load = () => {
35
+ if (initialized) return;
36
+
37
+ const cached = getCached(importFn);
38
+ /* istanbul ignore next */
39
+ if (cached) {
40
+ component.val = cached.component;
41
+ route.val = cached.route;
42
+ return;
43
+ }
44
+
45
+ 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
+ };
53
+
54
+ const lazyComponent = () => {
55
+ load();
56
+ return { component: component.val(), route: route.val };
57
+ };
58
+ lazyComponent.isLazy = true;
59
+ return lazyComponent;
60
+ };
@@ -0,0 +1,19 @@
1
+ {
2
+ "name": "router",
3
+ "main": "./index.mjs",
4
+ "module": "./index.mjs",
5
+ "types": "./types.d.ts",
6
+ "type": "module",
7
+ "exports": {
8
+ ".": {
9
+ "types": "./types.d.ts",
10
+ "import": "./index.mjs"
11
+ }
12
+ },
13
+ "peerDependencies": {
14
+ "vite-plugin-vanjs": "*",
15
+ "vanjs-core": "*",
16
+ "vanjs-ext": "*"
17
+ },
18
+ "sideEffects": false
19
+ }
@@ -0,0 +1,43 @@
1
+ import van from "vanjs-core";
2
+ import setup from "@vanjs/setup";
3
+ import { routerState } from "./state";
4
+ import { matchRoute } from "./routes";
5
+ import { executeLifecycle, unwrap } from "./helpers";
6
+
7
+ export const Router = () => {
8
+ const { div } = van.tags;
9
+ // const meta = defaultMeta();
10
+
11
+ const mainLayout = () => {
12
+ const route = matchRoute(routerState.pathname.val);
13
+ /* istanbul ignore else */
14
+ if (!route) return div("404 - Not Found");
15
+
16
+ routerState.params.val = route.params || {};
17
+ // Server-side or async component: use renderComponent
18
+ if (setup.isServer) {
19
+ const renderComponent = async () => {
20
+ try {
21
+ const module = await route.component();
22
+ const component = module.component();
23
+ await executeLifecycle(module, route.params);
24
+ return unwrap(component).children;
25
+ } catch (error) {
26
+ /* istanbul ignore next */
27
+ console.error("Router error:", error);
28
+ /* istanbul ignore next */
29
+ return div("Error loading page");
30
+ }
31
+ };
32
+
33
+ return renderComponent();
34
+ }
35
+
36
+ const module = route.component();
37
+ // Client-side lazy component, lifeCycle is already executed on the server
38
+ // or when A component has been clicked in the client
39
+ return unwrap(module.component);
40
+ };
41
+
42
+ return mainLayout();
43
+ };
@@ -0,0 +1,51 @@
1
+ // router/routes.mjs
2
+ import { extractParams, isLazyComponent } from "./helpers";
3
+ import { lazy } from "./lazy";
4
+
5
+ /** @typedef {import("./types.d.ts").RouteEntry} RouteEntry */
6
+ /** @typedef {import("./types.d.ts").RouteProps} RouteProps */
7
+
8
+ /** @type {RouteEntry[]} */
9
+ export const routes = [];
10
+
11
+ /**
12
+ * @param {RouteProps} routeProps
13
+ */
14
+ export const Route = (routeProps) => {
15
+ const { component, preload, load, ...rest } = routeProps;
16
+
17
+ // If component has lifecycle methods but isn't lazy, make it lazy
18
+ if (!isLazyComponent(component)) {
19
+ const wrappedComponent = lazy(() =>
20
+ Promise.resolve({
21
+ default: component,
22
+ route: { preload, load },
23
+ })
24
+ );
25
+ routes.push({ ...rest, component: wrappedComponent });
26
+ return;
27
+ }
28
+
29
+ // Otherwise keep original component
30
+ routes.push(routeProps);
31
+ };
32
+
33
+ /**
34
+ * Find a registered route that matches the given path
35
+ * @param {string} path
36
+ * @returns {RouteEntry | null}
37
+ */
38
+ export const matchRoute = (path) => {
39
+ const exactMatch = routes.find((r) => r.path === path);
40
+ if (exactMatch) return { ...exactMatch, params: {} };
41
+
42
+ for (const route of routes) {
43
+ if (route.path === "*") continue;
44
+ const params = extractParams(route.path, path);
45
+ if (params) {
46
+ return { ...route, params };
47
+ }
48
+ }
49
+
50
+ return routes.find((r) => r.path === "*") || null;
51
+ };
@@ -0,0 +1,27 @@
1
+ // router/state.js
2
+ import van from "vanjs-core";
3
+ import setup from "../setup/index.mjs";
4
+
5
+ const initialPath = !setup.isServer ? globalThis.location.pathname : "/";
6
+ const initialSearch = !setup.isServer ? globalThis.location.search : "";
7
+
8
+ /**
9
+ * @type {typeof import("./types.d.ts").routerState}
10
+ */
11
+ export const routerState = {
12
+ pathname: van.state(initialPath),
13
+ searchParams: van.state(new URLSearchParams(initialSearch)),
14
+ params: van.state({}),
15
+ };
16
+
17
+ /**
18
+ * @type {typeof import("./types.d.ts").setRouterState}
19
+ */
20
+ export const setRouterState = (path, search = undefined, params) => {
21
+ const [pathname, searchParams] = path.split("?");
22
+ routerState.pathname.val = pathname;
23
+ routerState.searchParams.val = new URLSearchParams(
24
+ search || searchParams || "",
25
+ );
26
+ routerState.params.val = params || {};
27
+ };
@@ -0,0 +1,156 @@
1
+ /// <reference path="global.d.ts" />
2
+ import type { Element } from "mini-van-plate/van-plate";
3
+ import van from "vanjs-core";
4
+ import type { PropsWithKnownKeys } from "vanjs-core";
5
+
6
+ type VanElement = Element & { children: VanNode[] };
7
+ type VanNode = SVGElement | HTMLElement | VanElement;
8
+
9
+ // router.mjs
10
+ /**
11
+ * A virtual component that renders the current route
12
+ * in your VanJS application. It must be used in your main component.
13
+ *
14
+ * @example
15
+ * import { Router } from '@vanjs/router';
16
+ *
17
+ * export const App = () => {
18
+ * return Router(); // or <Router /> for JSX
19
+ * }
20
+ */
21
+ export const Router: () => VanNode | VanNode[];
22
+
23
+ // a.mjs
24
+ /**
25
+ * A virtual component that creates an anchor element
26
+ * that navigates to the specified href when clicked.
27
+ *
28
+ * @example
29
+ * import { A } from '@vanjs/router';
30
+ * import van from 'vanjs-core';
31
+ *
32
+ * export const Navigation = () => {
33
+ * const { nav } = van.tags
34
+ * return nav(
35
+ * A({ href="/" }, "Home"), // or <A href="/">Home</A> with JSX
36
+ * A({ href="/about" }, "About"), // or <A href="/about">About</A> with JSX
37
+ * // ...other children
38
+ * );
39
+ * }
40
+ */
41
+ export const A: (
42
+ props: PropsWithKnownKeys<HTMLAnchorProps>,
43
+ ...children: (Element | Node | string)[]
44
+ ) => HTMLAnchorElement;
45
+
46
+ // helpers.mjs
47
+ /**
48
+ * Navigates to the specified href in the client and sets the router state.
49
+ * Keep in mind that the router handles the search params and hash.
50
+ *
51
+ * @param href the URL to navigate to
52
+ * @param options when true, will replace the current history entry
53
+ */
54
+ export const navigate: (href: string, options?: { replace?: boolean }) => void;
55
+
56
+ /**
57
+ * A client only helper function that reloads the current page.
58
+ */
59
+ export const reload: () => void;
60
+
61
+ /**
62
+ * A helper function that redirects the user to the specified href.
63
+ * When called in the server, it will return a function that will redirect the user
64
+ * to the specified href when called.
65
+ * @param {string | undefined} href the URL to redirect to
66
+ */
67
+ export const redirect: (href?: string) => void | (() => void);
68
+
69
+ export type VanComponent = () => VanNode | VanNode[];
70
+
71
+ // routes.mjs
72
+ export type RouteEntry = {
73
+ path: string;
74
+ component: Promise<ComponentModule>;
75
+ preload?: (params?: Record<string, string>) => void;
76
+ load?: (params?: Record<string, string>) => void;
77
+ };
78
+
79
+ export type RouteProps = {
80
+ path: string;
81
+ component: VanComponent | (() => ComponentModule);
82
+ preload?: (params?: Record<string, string>) => void;
83
+ load?: (params?: Record<string, string>) => void;
84
+ };
85
+ export const routes: RouteEntry[];
86
+
87
+ /**
88
+ * Registers a new route in the router state.
89
+ * @param route the route to register
90
+ *
91
+ * @example
92
+ * import { Route, lazy } from '@vanjs/router';
93
+ * import Home from './pages/Home';
94
+ * import NotFound from './pages/NotFound';
95
+ *
96
+ * Route({ path: '/', component: Home });
97
+ * Route({ path: '/about', component: lazy(() => import("./pages/About.ts")) });
98
+ * Route({ path: '*', component: NotFound });
99
+ */
100
+ export const Route: (route: RouteProps) => void;
101
+
102
+ // state.mjs
103
+ export type RouterState = {
104
+ pathname: string;
105
+ searchParams: URLSearchParams;
106
+ params?: Record<string, string>;
107
+ };
108
+ /**
109
+ * A reactive object that holds the current router state.
110
+ * This state is maintained by both server and client.
111
+ */
112
+ export const routerState: {
113
+ pathname: van.state<RouterState.pathname>;
114
+ searchParams: van.state<RouterState.searchParams>;
115
+ params?: van.state<RouterState.params>;
116
+ };
117
+
118
+ /**
119
+ * Sets the router state to the specified href.
120
+ * @param href the URL to navigate to
121
+ * @param search the search string
122
+ * @param params the route params object
123
+ */
124
+ export const setRouterState: (
125
+ href: string,
126
+ search?: string,
127
+ params?: Record<string, string>,
128
+ ) => void;
129
+
130
+ /**
131
+ * Merge the children of an Element or an array of elements with an optional array of children
132
+ * into the childen of a single HTMLFragmentElement element.
133
+ * @param source
134
+ * @param children
135
+ */
136
+ export const unwrap: (
137
+ source: VanNode | VanNode[] | (() => VanNode | VanNode[]),
138
+ ...children: VanNode[]
139
+ ) => VanNode;
140
+
141
+ export type ComponentModule = {
142
+ component: VanComponent;
143
+ route: Pick<RouteEntry, "load" | "preload">;
144
+ };
145
+
146
+ export type DynamicModule = {
147
+ Page: VanComponent;
148
+ default?: VanComponent;
149
+ route?: Pick<RouteEntry, "load" | "preload">;
150
+ };
151
+
152
+ export type LazyComponent =
153
+ | Promise<DynamicModule>
154
+ | (() => Promise<DynamicModule>);
155
+
156
+ export const lazy: (importFn: () => LazyComponent) => () => ComponentModule;
@@ -0,0 +1,49 @@
1
+ declare module "@vanjs/server" {
2
+ import type { PropsWithKnownKeys } from "vanjs-core";
3
+ import type { SupportedTags } from "@vanjs/meta";
4
+ import type { JSX } from "@vanjs/jsx";
5
+ import type {
6
+ Element as VanElement,
7
+ TagFunc,
8
+ } from "mini-van-plate/van-plate";
9
+
10
+ /**
11
+ * A function that takes a list of files and a manifest and returns a string
12
+ * representing the HTML markup for preload links.
13
+ * @param files the list of files
14
+ * @param manifest the vite manifest
15
+ * @returns HTML string
16
+ */
17
+ export const renderPreloadLinks: (
18
+ files: string[],
19
+ manifest: Record<string, string[]>,
20
+ ) => string;
21
+
22
+ type ValidVanNode =
23
+ | boolean
24
+ | number
25
+ | string
26
+ | VanElement
27
+ | TagFunc;
28
+
29
+ type VanComponent = () =>
30
+ | ValidVanNode
31
+ | ValidVanNode[]
32
+ | SupportedTags
33
+ | SupportedTags[];
34
+ export type Source =
35
+ | JSX.Element
36
+ | VanComponent
37
+ | (() => VanComponent)
38
+ | Promise<ValidVanNode>
39
+ | ValidVanNode
40
+ | undefined;
41
+
42
+ /**
43
+ * A function that takes a multitude of source types and returns a string
44
+ * representing the HTML output.
45
+ * @param source the source
46
+ * @returns HTML string
47
+ */
48
+ export const renderToString: (source: Source) => Promise<string>;
49
+ }