vite-plugin-vanjs 0.0.3 → 0.0.5
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/README.md +143 -15
- package/client/global.d.ts +41 -0
- package/client/index.mjs +48 -0
- package/client/package.json +18 -0
- package/client/types.d.ts +34 -0
- package/jsx/global.d.ts +4 -3
- package/jsx/jsx.d.ts +18 -10
- package/jsx/jsx.mjs +3 -2
- package/jsx/package.json +1 -1
- package/jsx/types.d.ts +16 -0
- package/meta/Head.mjs +81 -0
- package/meta/global.d.ts +60 -0
- package/meta/helpers.mjs +52 -0
- package/meta/index.mjs +3 -0
- package/meta/package.json +18 -0
- package/meta/tags.mjs +57 -0
- package/meta/types.d.ts +36 -0
- package/package.json +45 -13
- package/router/a.mjs +50 -0
- package/router/cache.mjs +14 -0
- package/router/global.d.ts +162 -0
- package/router/helpers.mjs +198 -0
- package/router/index.mjs +7 -0
- package/router/lazy.mjs +60 -0
- package/router/package.json +19 -0
- package/router/router.mjs +43 -0
- package/router/routes.mjs +51 -0
- package/router/state.mjs +27 -0
- package/router/types.d.ts +156 -0
- package/server/global.d.ts +49 -0
- package/server/index.mjs +97 -0
- package/server/package.json +20 -0
- package/server/types.d.ts +38 -0
- package/setup/package.json +2 -2
- package/setup/van.d.ts +1 -0
- package/setup/vanX.d.ts +1 -0
- package/setup/vanX.mjs +1 -1
- package/src/index.mjs +10 -10
- package/src/types.d.ts +28 -4
- package/tsconfig.json +14 -2
- package/jsx/index.d.ts +0 -16
- package/jsx/utils.mjs +0 -24
- /package/setup/{index.d.ts → types.d.ts} +0 -0
package/meta/helpers.mjs
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import setup from "@vanjs/setup";
|
|
2
|
+
|
|
3
|
+
/** @type {import('./types.d.ts').ParseAttributes} */
|
|
4
|
+
export const parseAttributes = (attributeString) => {
|
|
5
|
+
/** @type {Record<string, string>} */
|
|
6
|
+
const attributes = {};
|
|
7
|
+
const attributeRegex = /([a-zA-Z0-9_-]+)(?:="([^"]*?)")?/g;
|
|
8
|
+
let match;
|
|
9
|
+
|
|
10
|
+
while ((match = attributeRegex.exec(attributeString)) !== null) {
|
|
11
|
+
const name = match[1];
|
|
12
|
+
const value = match[2] || /* istanbul ignore next */ "";
|
|
13
|
+
attributes[name] = value;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
return attributes;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
/** @type {typeof import('./types.d.ts').getTagAttribute} */
|
|
20
|
+
const getTagAttribute = (tag) => {
|
|
21
|
+
const attributes = [
|
|
22
|
+
"name",
|
|
23
|
+
"property",
|
|
24
|
+
"charset",
|
|
25
|
+
"viewport",
|
|
26
|
+
"media",
|
|
27
|
+
"http-equiv",
|
|
28
|
+
"rel",
|
|
29
|
+
"src",
|
|
30
|
+
"href",
|
|
31
|
+
"id",
|
|
32
|
+
];
|
|
33
|
+
|
|
34
|
+
for (const attr of attributes) {
|
|
35
|
+
const value = setup.isServer ? tag[attr] : tag.getAttribute(attr);
|
|
36
|
+
|
|
37
|
+
if (value) return value;
|
|
38
|
+
}
|
|
39
|
+
return "";
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
/** @type {import('./types.d.ts').GetTagKey} */
|
|
43
|
+
export const getTagKey = (tag) => {
|
|
44
|
+
const normalizedTag = setup.isServer
|
|
45
|
+
? { tagName: tag.name.toUpperCase(), ...parseAttributes(tag.propsStr) }
|
|
46
|
+
: tag;
|
|
47
|
+
|
|
48
|
+
return normalizedTag.tagName +
|
|
49
|
+
(normalizedTag.tagName !== "TITLE"
|
|
50
|
+
? `.${getTagAttribute(normalizedTag)}`
|
|
51
|
+
: "");
|
|
52
|
+
};
|
package/meta/index.mjs
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "meta",
|
|
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
|
+
},
|
|
17
|
+
"sideEffects": false
|
|
18
|
+
}
|
package/meta/tags.mjs
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import van from "vanjs-core";
|
|
2
|
+
import { addMeta } from "./Head.mjs";
|
|
3
|
+
|
|
4
|
+
/** @typedef {import("vanjs-core").PropsWithKnownKeys} PropsWithKnownKeys */
|
|
5
|
+
/** @typedef {import("./types.d.ts").TagProps} TagProps */
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Add a new title tag
|
|
9
|
+
* @type {(props: PropsWithKnownKeys<HTMLTitleElement>, content?: string) => null}
|
|
10
|
+
*/
|
|
11
|
+
export const Title = (props, content) => {
|
|
12
|
+
const { title } = van.tags;
|
|
13
|
+
addMeta(title(props, content));
|
|
14
|
+
return null;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Add a new meta tag
|
|
19
|
+
* @type {(props: PropsWithKnownKeys<HTMLMetaElement>) => null}
|
|
20
|
+
*/
|
|
21
|
+
export const Meta = (props) => {
|
|
22
|
+
const { meta } = van.tags;
|
|
23
|
+
addMeta(meta(props));
|
|
24
|
+
return null;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Add a new link tag
|
|
29
|
+
* @type {(props: PropsWithKnownKeys<HTMLLinkElement>) => null}
|
|
30
|
+
*/
|
|
31
|
+
export const Link = (props) => {
|
|
32
|
+
const { link } = van.tags;
|
|
33
|
+
addMeta(link(props));
|
|
34
|
+
return null;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Add a new script tag
|
|
39
|
+
* @type {(props: PropsWithKnownKeys<HTMLScriptElement>, content?: string) => null}
|
|
40
|
+
*/
|
|
41
|
+
export const Script = (props, content) => {
|
|
42
|
+
const { script } = van.tags;
|
|
43
|
+
// const realContent = content ? content.toString() : props;
|
|
44
|
+
// const realProps = content ? props : props;
|
|
45
|
+
addMeta(script(props, content));
|
|
46
|
+
return null;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Add a new style tag
|
|
51
|
+
* @type {(props: PropsWithKnownKeys<HTMLStyleElement>, content: string) => null}
|
|
52
|
+
*/
|
|
53
|
+
export const Style = (props, content) => {
|
|
54
|
+
const { style } = van.tags;
|
|
55
|
+
addMeta(style(props, content));
|
|
56
|
+
return null;
|
|
57
|
+
};
|
package/meta/types.d.ts
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/// <reference path="global.d.ts" />
|
|
2
|
+
import { type PropsWithKnownKeys } from "vanjs-core";
|
|
3
|
+
import { type TagFunc } from "mini-van-plate/van-plate";
|
|
4
|
+
|
|
5
|
+
export const createHeadTags: () => Map<
|
|
6
|
+
string,
|
|
7
|
+
SupportedTags | TagFunc<SupportedTags>
|
|
8
|
+
>;
|
|
9
|
+
|
|
10
|
+
export const getHeadTags = () =>
|
|
11
|
+
Map<string, SupportedTags | TagFunc<SupportedTags>>;
|
|
12
|
+
export const resetHeadTags: () => void;
|
|
13
|
+
export const initializeHeadTags: () => void;
|
|
14
|
+
|
|
15
|
+
export type SupportedTags =
|
|
16
|
+
| HTMLTitleElement
|
|
17
|
+
| HTMLMetaElement
|
|
18
|
+
| HTMLScriptElement
|
|
19
|
+
| HTMLLinkElement
|
|
20
|
+
| HTMLStyleElement;
|
|
21
|
+
|
|
22
|
+
export type TagProps = SupportedTags | PropsWithKnownKeys<SupportedTags>;
|
|
23
|
+
|
|
24
|
+
export type HeadTags = SupportedTags[] | TagFunc<SupportedTags>[];
|
|
25
|
+
|
|
26
|
+
export type AddMeta = (tag: string | TagProps) => null;
|
|
27
|
+
|
|
28
|
+
export declare const Head: () => HeadTags;
|
|
29
|
+
|
|
30
|
+
export type ParseAttributes = (
|
|
31
|
+
attributeString: string,
|
|
32
|
+
) => Record<string, string>;
|
|
33
|
+
|
|
34
|
+
export const getTagAttribute: (tag: TagProps) => string;
|
|
35
|
+
|
|
36
|
+
export const getTagKey: (tag: TagProps) => string;
|
package/package.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vite-plugin-vanjs",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.5",
|
|
4
4
|
"author": "thednp",
|
|
5
5
|
"license": "MIT",
|
|
6
|
-
"description": "A
|
|
6
|
+
"description": "A mini meta-framework for VanJS powered by Vite",
|
|
7
7
|
"repository": "https://github.com/thednp/vite-plugin-vanjs",
|
|
8
8
|
"type": "module",
|
|
9
9
|
"sideEffects": false,
|
|
@@ -13,6 +13,10 @@
|
|
|
13
13
|
"files": [
|
|
14
14
|
"src",
|
|
15
15
|
"setup",
|
|
16
|
+
"router",
|
|
17
|
+
"meta",
|
|
18
|
+
"client",
|
|
19
|
+
"server",
|
|
16
20
|
"jsx",
|
|
17
21
|
"tsconfig.json",
|
|
18
22
|
"package.json",
|
|
@@ -25,6 +29,8 @@
|
|
|
25
29
|
"vanjs",
|
|
26
30
|
"vite",
|
|
27
31
|
"plugin",
|
|
32
|
+
"router",
|
|
33
|
+
"meta",
|
|
28
34
|
"ssr",
|
|
29
35
|
"ssg",
|
|
30
36
|
"jsx"
|
|
@@ -36,12 +42,27 @@
|
|
|
36
42
|
"default": "./src/index.mjs"
|
|
37
43
|
},
|
|
38
44
|
"./setup": {
|
|
39
|
-
"types": "./setup/
|
|
40
|
-
"import": "./setup/index.mjs"
|
|
41
|
-
|
|
45
|
+
"types": "./setup/types.d.ts",
|
|
46
|
+
"import": "./setup/index.mjs"
|
|
47
|
+
},
|
|
48
|
+
"./router": {
|
|
49
|
+
"types": "./router/types.d.ts",
|
|
50
|
+
"import": "./router/index.mjs"
|
|
51
|
+
},
|
|
52
|
+
"./meta": {
|
|
53
|
+
"types": "./meta/types.d.ts",
|
|
54
|
+
"import": "./meta/index.mjs"
|
|
55
|
+
},
|
|
56
|
+
"./server": {
|
|
57
|
+
"types": "./server/types.d.ts",
|
|
58
|
+
"import": "./server/index.mjs"
|
|
59
|
+
},
|
|
60
|
+
"./client": {
|
|
61
|
+
"types": "./client/types.d.ts",
|
|
62
|
+
"import": "./client/index.mjs"
|
|
42
63
|
},
|
|
43
64
|
"./jsx": {
|
|
44
|
-
"types": "./jsx/
|
|
65
|
+
"types": "./jsx/types.d.ts",
|
|
45
66
|
"import": "./jsx/index.mjs"
|
|
46
67
|
},
|
|
47
68
|
"./jsx-runtime": {
|
|
@@ -61,20 +82,31 @@
|
|
|
61
82
|
},
|
|
62
83
|
"dependencies": {
|
|
63
84
|
"csstype": "^3.1.3",
|
|
64
|
-
"mini-van-plate": "^0.6.
|
|
65
|
-
"vanjs-core": "^1.5.
|
|
85
|
+
"mini-van-plate": "^0.6.3",
|
|
86
|
+
"vanjs-core": "^1.5.3",
|
|
66
87
|
"vanjs-ext": "^0.6.2"
|
|
67
88
|
},
|
|
68
89
|
"devDependencies": {
|
|
90
|
+
"@vitest/browser": "^3.0.4",
|
|
91
|
+
"@vitest/coverage-istanbul": "^3.0.4",
|
|
92
|
+
"@vitest/ui": "^3.0.4",
|
|
93
|
+
"happy-dom": "^16.7.3",
|
|
69
94
|
"typescript": "5.6.2",
|
|
70
|
-
"vite": "^6.0.
|
|
95
|
+
"vite": "^6.0.11",
|
|
96
|
+
"vitest": "^3.0.4"
|
|
97
|
+
},
|
|
98
|
+
"engines": {
|
|
99
|
+
"node": ">=20",
|
|
100
|
+
"pnpm": ">=8.6.0"
|
|
71
101
|
},
|
|
72
102
|
"scripts": {
|
|
73
|
-
"
|
|
74
|
-
"
|
|
103
|
+
"test": "vitest --config vitest.config.mts",
|
|
104
|
+
"test-ui": "vitest --config vitest.config.mts --ui=true",
|
|
105
|
+
"badges": "npx -p dependency-version-badge update-badge vanjs-core mini-van-plate typescript vitest vite",
|
|
106
|
+
"format": "deno fmt src meta router setup client server jsx",
|
|
75
107
|
"lint": "pnpm lint:ts && pnpm check:ts",
|
|
76
|
-
"lint:ts": "deno lint src",
|
|
77
|
-
"fix:ts": "deno lint src --fix",
|
|
108
|
+
"lint:ts": "deno lint src meta router setup client server jsx",
|
|
109
|
+
"fix:ts": "deno lint src meta router setup client server jsx --fix",
|
|
78
110
|
"check:ts": "tsc -noEmit"
|
|
79
111
|
}
|
|
80
112
|
}
|
package/router/a.mjs
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
// router/a.mjs
|
|
2
|
+
import van from "vanjs-core";
|
|
3
|
+
import setup from "@vanjs/setup";
|
|
4
|
+
import { matchRoute } from "./routes.mjs";
|
|
5
|
+
import { executeLifecycle, isCurrentPage, navigate } from "./helpers.mjs";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @param {Partial<HTMLAnchorElement>} props
|
|
9
|
+
* @param {...(Element | Node)[]} children
|
|
10
|
+
* @returns {HTMLAnchorElement}
|
|
11
|
+
*/
|
|
12
|
+
export const A = (props, ...children) => {
|
|
13
|
+
const { href, ...rest } = props;
|
|
14
|
+
const newProps = {
|
|
15
|
+
href,
|
|
16
|
+
...rest,
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
van.derive(() => {
|
|
20
|
+
if (isCurrentPage(href)) {
|
|
21
|
+
newProps["aria-current"] = "page";
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
const anchor = van.tags.a(newProps, ...children);
|
|
26
|
+
/* istanbul ignore else */
|
|
27
|
+
if (!setup.isServer) {
|
|
28
|
+
anchor.addEventListener("click", async (e) => {
|
|
29
|
+
e.preventDefault();
|
|
30
|
+
/* istanbul ignore next */
|
|
31
|
+
if (isCurrentPage(href)) return;
|
|
32
|
+
|
|
33
|
+
const route = matchRoute(href);
|
|
34
|
+
const module = await route.component();
|
|
35
|
+
await executeLifecycle(module, route.params);
|
|
36
|
+
|
|
37
|
+
navigate(href);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
anchor.addEventListener("mouseenter", () => {
|
|
41
|
+
const route = matchRoute(href);
|
|
42
|
+
|
|
43
|
+
/* istanbul ignore else */
|
|
44
|
+
if (route?.component) {
|
|
45
|
+
route.component();
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
return anchor;
|
|
50
|
+
};
|
package/router/cache.mjs
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/** @typedef {import("./types").ComponentModule} ComponentModule */
|
|
2
|
+
/** @typedef {import("./types").GetCachedRoute} GetCachedRoute */
|
|
3
|
+
/** @typedef {import("./types").CacheRoute} CacheRoute */
|
|
4
|
+
|
|
5
|
+
/** @type {Map<string, ComponentModule>} */
|
|
6
|
+
const routeCache = new Map();
|
|
7
|
+
|
|
8
|
+
/** @type {GetCachedRoute} */
|
|
9
|
+
export const getCached = (key) => routeCache.get(key);
|
|
10
|
+
|
|
11
|
+
/** @type {CacheRoute} */
|
|
12
|
+
export const cache = (key, value) => {
|
|
13
|
+
routeCache.set(key, value);
|
|
14
|
+
};
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
declare module "@vanjs/router" {
|
|
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: (
|
|
55
|
+
href: string,
|
|
56
|
+
options?: { replace?: boolean },
|
|
57
|
+
) => void;
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* A client only helper function that reloads the current page.
|
|
61
|
+
*/
|
|
62
|
+
export const reload: () => void;
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* A helper function that redirects the user to the specified href.
|
|
66
|
+
* When called in the server, it will return a function that will redirect the user
|
|
67
|
+
* to the specified href when called.
|
|
68
|
+
* @param {string | undefined} href the URL to redirect to
|
|
69
|
+
*/
|
|
70
|
+
export const redirect: (href?: string) => void | (() => void);
|
|
71
|
+
|
|
72
|
+
export type VanComponent = () => VanNode | VanNode[];
|
|
73
|
+
|
|
74
|
+
// routes.mjs
|
|
75
|
+
export type RouteEntry = {
|
|
76
|
+
path: string;
|
|
77
|
+
component: Promise<ComponentModule>;
|
|
78
|
+
preload?: (params?: Record<string, string>) => void;
|
|
79
|
+
load?: (params?: Record<string, string>) => void;
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
export type RouteProps = {
|
|
83
|
+
path: string;
|
|
84
|
+
component: VanComponent | (() => ComponentModule);
|
|
85
|
+
preload?: (params?: Record<string, string>) => void;
|
|
86
|
+
load?: (params?: Record<string, string>) => void;
|
|
87
|
+
};
|
|
88
|
+
export const routes: RouteEntry[];
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Registers a new route in the router state.
|
|
92
|
+
* @param route the route to register
|
|
93
|
+
*
|
|
94
|
+
* @example
|
|
95
|
+
* import { Route, lazy } from '@vanjs/router';
|
|
96
|
+
* import Home from './pages/Home';
|
|
97
|
+
* import NotFound from './pages/NotFound';
|
|
98
|
+
*
|
|
99
|
+
* Route({ path: '/', component: Home });
|
|
100
|
+
* Route({ path: '/about', component: lazy(() => import("./pages/About.ts")) });
|
|
101
|
+
* Route({ path: '*', component: NotFound });
|
|
102
|
+
*/
|
|
103
|
+
export const Route: (route: RouteProps) => void;
|
|
104
|
+
|
|
105
|
+
// state.mjs
|
|
106
|
+
export type RouterState = {
|
|
107
|
+
pathname: string;
|
|
108
|
+
searchParams: URLSearchParams;
|
|
109
|
+
params: Record<string, string>;
|
|
110
|
+
};
|
|
111
|
+
/**
|
|
112
|
+
* A reactive object that holds the current router state.
|
|
113
|
+
* This state is maintained by both server and client.
|
|
114
|
+
*/
|
|
115
|
+
export const routerState: {
|
|
116
|
+
pathname: van.state<RouterState.pathname>;
|
|
117
|
+
searchParams: van.state<RouterState.searchParams>;
|
|
118
|
+
params?: van.state<RouterState.params>;
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Sets the router state to the specified href.
|
|
123
|
+
* @param href the URL to navigate to
|
|
124
|
+
* @param search the search string
|
|
125
|
+
* @param params the route params object
|
|
126
|
+
*/
|
|
127
|
+
export const setRouterState: (
|
|
128
|
+
href: string,
|
|
129
|
+
search?: string,
|
|
130
|
+
params?: Record<string, string>,
|
|
131
|
+
) => void;
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Merge the children of an Element or an array of elements with an optional array of children
|
|
135
|
+
* into the childen of a single HTMLFragmentElement element.
|
|
136
|
+
* @param source
|
|
137
|
+
* @param {...VanNode[]} children
|
|
138
|
+
*/
|
|
139
|
+
export const unwrap: (
|
|
140
|
+
source: VanNode | VanNode[] | (() => VanNode | VanNode[]),
|
|
141
|
+
...children: VanNode[]
|
|
142
|
+
) => VanNode;
|
|
143
|
+
|
|
144
|
+
export type ComponentModule = {
|
|
145
|
+
component: VanComponent;
|
|
146
|
+
route: Pick<RouteEntry, "load" | "preload">;
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
export type DynamicModule = {
|
|
150
|
+
Page: VanComponent;
|
|
151
|
+
default?: VanComponent;
|
|
152
|
+
route?: Pick<RouteEntry, "load" | "preload">;
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
export type LazyComponent = Promise<DynamicModule>;
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Registers a lazy component.
|
|
159
|
+
* @param importFn
|
|
160
|
+
*/
|
|
161
|
+
export const lazy: (importFn: () => LazyComponent) => () => ComponentModule;
|
|
162
|
+
}
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import setup from "@vanjs/setup";
|
|
2
|
+
import van from "vanjs-core";
|
|
3
|
+
import { routerState, setRouterState } from "./state.mjs";
|
|
4
|
+
import { matchRoute } from "./routes.mjs";
|
|
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
|
+
// };
|