vite-plugin-vanjs 0.0.8 → 0.0.10

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 CHANGED
@@ -7,8 +7,8 @@
7
7
  [![typescript version](https://img.shields.io/badge/typescript-5.6.2-brightgreen)](https://www.typescriptlang.org/)
8
8
  [![vanjs-core version](https://img.shields.io/badge/vanjs--core-1.5.3-brightgreen)](https://github.com/vanjs-org/van)
9
9
  [![mini-van-plate version](https://img.shields.io/badge/mini--van--plate-0.6.3-brightgreen)](https://github.com/vanjs-org/mini-van-plate)
10
- [![vitest version](https://img.shields.io/badge/vitest-3.0.9-brightgreen)](https://www.vitest.dev/)
11
- [![vite version](https://img.shields.io/badge/vite-6.2.2-brightgreen)](https://vite.dev)
10
+ [![vitest version](https://img.shields.io/badge/vitest-3.1.1-brightgreen)](https://www.vitest.dev/)
11
+ [![vite version](https://img.shields.io/badge/vite-6.2.5-brightgreen)](https://vite.dev)
12
12
 
13
13
  A mini meta-framework for [VanJS](https://vanjs.org/) developed around the awesome [Vite](https://vite.dev). The plugin comes with a set of modules to simplify your workflow:
14
14
  * ***@vanjs/router*** - one of the most important part of an application which allows you to split code and lazy load page like components with ease, handles both Client Side Rendering (SSR) and Server Side Rendering (CSR) and makes it really easy to work with;
@@ -20,6 +20,9 @@ A mini meta-framework for [VanJS](https://vanjs.org/) developed around the aweso
20
20
 
21
21
  The plugin will automatically load the appropriate Van or VanX objects depending on the client/server environment with zero configuration needed. It uses the `mini-van-plate/shared` module to register the required objects in an isomorphic enviroment.
22
22
 
23
+ Also in _development_ mode, the plugin will load the `van.debug.js` module to help you better troubleshoot your VanJS application.
24
+
25
+
23
26
  ### Notes
24
27
  * The plugin uses `van-ext` along with `mini-van-plate` so you can have everything ready from the start.
25
28
 
@@ -31,19 +34,19 @@ The plugin will automatically load the appropriate Van or VanX objects depending
31
34
  1) Install the plugin:
32
35
 
33
36
  ```bash
34
- npm install vite-plugin-vanjs
37
+ npm install vite-plugin-vanjs@latest
35
38
  ```
36
39
 
37
40
  ```bash
38
- pnpm add vite-plugin-vanjs
41
+ pnpm add vite-plugin-vanjs@latest
39
42
  ```
40
43
 
41
44
  ```bash
42
- deno add npm:vite-plugin-vanjs
45
+ deno add npm:vite-plugin-vanjs@latest
43
46
  ```
44
47
 
45
48
  ```bash
46
- bun install vite-plugin-vanjs
49
+ bun add vite-plugin-vanjs@latest
47
50
  ```
48
51
 
49
52
 
@@ -127,12 +130,14 @@ Route({ path: '*', component: () => van.tags.div('404 - Page Not Found') });
127
130
 
128
131
  function App() {
129
132
  // the Router is only an outlet
130
- export default Route();
133
+ return Router();
131
134
  }
132
135
 
133
- // render or hydrate the app
136
+ // render the app
134
137
  van.add(document.body, App());
135
138
  ```
139
+ **IMPORTANT** - using lazy components for the main route (`"/"`) is not allowed. This is to prevent hydration waterfalls, especially for SSR apps.
140
+
136
141
 
137
142
  Here's how a page should look like, pay attention to the comments:
138
143
  ```ts
@@ -176,23 +181,17 @@ The **vite-plugin-vanjs** plugin provides metadata management via the exported `
176
181
 
177
182
  Depending on the type of application, it's generally very easy to setup the system to work properly, we only need to make sure that we execute functions in a specific order:
178
183
 
179
- * first we call the `initializeHeadTags` which allows the module to store the current tags that come from either a Vite starter template `index.html` or the server (SSR); if we don't call this function, the existing tags will be removed and some of them are very important (especially `charset` and `viewport`);
180
- * then we define a set of default tags to be used by both client and server (if using an SSR template) on all pages that don't come with any metadata tags;
181
- * lastly, on other pages, we define tags that override both the existing and the default tags.
184
+ * first we define a set of default tags to be used by both client and server (if using an SSR template) on all pages that don't come with any metadata tags;
185
+ * next, on other pages, we define tags that override both the existing and the default tags.
182
186
 
183
187
  Here's a quick example, first let's start again with the `app.ts`:
184
188
  ```ts
185
189
  import van from 'vanjs-core'
186
- import { Style, Title, Meta, initializeHeadTags } from '@vanjs/meta'
190
+ import { Style, Title, Link, Meta } from '@vanjs/meta'
187
191
 
188
192
  function App() {
189
193
  const { div, h1, p } van.tags;
190
194
 
191
- // when app is first time executed
192
- // we need to register tags coming
193
- // from the index.html template
194
- initializeHeadTags();
195
-
196
195
  // a good practice is to define some default tags
197
196
  // they are used on pages where no tags are set
198
197
  Title("VanJS + Vite App");
package/jsx/jsx.mjs CHANGED
@@ -2,7 +2,12 @@ import van from "../setup/van.mjs";
2
2
  import setup from "../setup/index.mjs";
3
3
  import { setAttribute, styleToString } from "../client/index.mjs";
4
4
 
5
- export const jsx = (jsxTag, { children, ref, style, ...props }) => {
5
+ export const jsx = (jsxTag, { children, ref, style, ...rest }) => {
6
+ // filter props with undefined values
7
+ const props = Object.fromEntries(
8
+ Object.entries(rest).filter(([_, val]) => val !== undefined),
9
+ );
10
+
6
11
  if (typeof jsxTag === "string") {
7
12
  const newElement = van.tags[jsxTag](props, children);
8
13
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vite-plugin-vanjs",
3
- "version": "0.0.8",
3
+ "version": "0.0.10",
4
4
  "author": "thednp",
5
5
  "license": "MIT",
6
6
  "description": "A mini meta-framework for VanJS powered by Vite",
@@ -87,15 +87,16 @@
87
87
  "vanjs-ext": "^0.6.2"
88
88
  },
89
89
  "devDependencies": {
90
- "@types/node": "^22.13.10",
91
- "@vitest/browser": "^3.0.9",
92
- "@vitest/coverage-istanbul": "^3.0.9",
93
- "@vitest/ui": "^3.0.9",
90
+ "@types/node": "^22.14.0",
91
+ "@vitest/browser": "^3.1.1",
92
+ "@vitest/coverage-istanbul": "^3.1.1",
93
+ "@vitest/ui": "^3.1.1",
94
94
  "happy-dom": "^16.8.1",
95
95
  "typescript": "5.6.2",
96
- "vite": "^6.2.2",
97
- "vitest": "^3.0.9"
96
+ "vite": "^6.2.5",
97
+ "vitest": "^3.1.1"
98
98
  },
99
+ "packageManager": "pnpm@8.6.12",
99
100
  "engines": {
100
101
  "node": ">=20",
101
102
  "pnpm": ">=8.6.0"
package/router/a.mjs CHANGED
@@ -15,7 +15,7 @@ export const A = (
15
15
  ) => {
16
16
  /* istanbul ignore next - try again later */
17
17
  const props = Object.fromEntries(
18
- Object.entries(rest || {}).filter(([_, val]) => val),
18
+ Object.entries(rest || {}).filter(([_, val]) => val !== undefined),
19
19
  );
20
20
  const newProps = {
21
21
  href,
@@ -129,7 +129,12 @@ declare module "@vanjs/router" {
129
129
 
130
130
  export type RouteProps = {
131
131
  path: string;
132
- component: VanComponent | (() => ComponentModule);
132
+ component:
133
+ | (() =>
134
+ | HTMLElementTagNameMap[unknown]
135
+ | HTMLElementTagNameMap[unknown][])
136
+ | VanComponent
137
+ | (() => ComponentModule);
133
138
  preload?: (params?: Record<string, string>) => void;
134
139
  load?: (params?: Record<string, string>) => void;
135
140
  };
package/router/lazy.mjs CHANGED
@@ -28,7 +28,7 @@ export const lazy = (importFn) => {
28
28
  }
29
29
 
30
30
  let initialized = false;
31
- const component = van.state(() => "");
31
+ const component = van.state(() => "Loading..");
32
32
  const route = van.state({});
33
33
 
34
34
  const load = () => {
package/router/router.mjs CHANGED
@@ -1,16 +1,31 @@
1
1
  import van from "vanjs-core";
2
2
  import setup from "../setup/index.mjs";
3
- import { routerState } from "./state.mjs";
3
+ import { routerState, setRouterState } from "./state.mjs";
4
4
  import { matchRoute } from "./routes.mjs";
5
5
  import { executeLifecycle, unwrap } from "./helpers.mjs";
6
+ import { hydrate } from "../client/index.mjs";
7
+ import { Head, initializeHeadTags } from "@vanjs/meta";
8
+
9
+ let isConnected = false;
10
+ /** @param {Event & {target: globalThis}} e */
11
+ // istanbul ignore next - cannot test
12
+ const popHandler = (e) => {
13
+ const location = e.target.location;
14
+ const oldPath = routerState.pathname._oldVal;
15
+ // istanbul ignore next - cannot test
16
+ if (location.pathname !== oldPath) {
17
+ setRouterState(location.pathname, location.search);
18
+ }
19
+ };
6
20
 
7
21
  export const Router = (initialProps = /* istanbul ignore next */ {}) => {
8
22
  const { div, main } = van.tags;
23
+
9
24
  /* istanbul ignore next - try again later */
10
25
  const props = Object.fromEntries(
11
- Object.entries(initialProps).filter(([_, val]) => val),
26
+ Object.entries(initialProps).filter(([_, val]) => val !== undefined),
12
27
  );
13
- const wrapper = main(props);
28
+ const wrapper = main({ ...props, "data-root": true });
14
29
  const mainLayout = () => {
15
30
  const route = matchRoute(routerState.pathname.val);
16
31
  /* istanbul ignore else */
@@ -22,7 +37,9 @@ export const Router = (initialProps = /* istanbul ignore next */ {}) => {
22
37
  const renderComponent = async () => {
23
38
  try {
24
39
  const module = await route.component();
25
- const component = module.component();
40
+ const component = typeof module.component === "function"
41
+ ? module.component()
42
+ : /* istanbul ignore next */ module.component;
26
43
 
27
44
  await executeLifecycle(module, route.params);
28
45
  return van.add(wrapper, unwrap(component).children);
@@ -37,13 +54,82 @@ export const Router = (initialProps = /* istanbul ignore next */ {}) => {
37
54
  return renderComponent();
38
55
  }
39
56
 
57
+ const root = document.querySelector("[data-root]");
58
+ // istanbul ignore else - cannot test unmount
59
+ if (isConnected || !root) {
60
+ initializeHeadTags();
61
+ globalThis.addEventListener("popstate", popHandler);
62
+ } else {
63
+ globalThis.removeEventListener("popstate", popHandler);
64
+ }
65
+
40
66
  // Client-side lazy component, lifeCycle is already executed on the server
41
67
  // or when A component has been clicked in the client
42
- const module = route.component();
43
- const children = module.component
44
- ? Array.from(unwrap(module.component).children)
45
- : [];
46
- return children.length ? van.add(wrapper, ...children) : wrapper;
68
+ if (root) {
69
+ // this case is when root is server side rendered
70
+ const module = route.component();
71
+ const children = () => {
72
+ // istanbul ignore next - cannot test
73
+ const cp = (Array.isArray(module) || module instanceof Element)
74
+ ? module
75
+ : typeof module.component === "function"
76
+ ? module.component()
77
+ : module.component;
78
+ // istanbul ignore next - cannot test
79
+ const kids = () => cp ? Array.from(unwrap(cp).children) : [];
80
+ const kudos = kids();
81
+
82
+ // istanbul ignore else
83
+ if (document.head) {
84
+ isConnected = true;
85
+ van.hydrate(document.head, (head) => hydrate(head, Head()));
86
+ }
87
+
88
+ return kudos;
89
+ };
90
+
91
+ return van.add(wrapper, ...children());
92
+ }
93
+ // this case is when root is for SPA apps
94
+ const csrRoute = van.derive(() => {
95
+ const p = routerState.pathname.val;
96
+ return matchRoute(p);
97
+ });
98
+
99
+ const children = van.derive(() => {
100
+ const md = csrRoute.val.component();
101
+ // istanbul ignore next - cannot test all cases
102
+ const cp = (Array.isArray(md) || md instanceof Element)
103
+ ? md
104
+ : typeof md.component === "function"
105
+ ? md.component()
106
+ : md.component;
107
+ return cp
108
+ ? Array.from(unwrap(cp).children)
109
+ : /* istanbul ignore next */ [];
110
+ });
111
+
112
+ const component = () => {
113
+ const kids = () => children.val;
114
+ const result = () => {
115
+ return van.derive(() =>
116
+ van.hydrate(wrapper, (el) => {
117
+ const kudos = kids();
118
+ isConnected = true;
119
+ // istanbul ignore else
120
+ if (document.head) {
121
+ van.hydrate(document.head, (head) => hydrate(head, Head()));
122
+ }
123
+ return hydrate(el, kudos);
124
+ })
125
+ ).val;
126
+ };
127
+ return result();
128
+ };
129
+ const finalResult = component();
130
+ return finalResult
131
+ ? /* istanbul ignore next*/ van.add(wrapper, finalResult)
132
+ : wrapper;
47
133
  };
48
134
 
49
135
  return mainLayout();
package/router/routes.mjs CHANGED
@@ -12,17 +12,22 @@ export const routes = [];
12
12
  * @param {RouteProps} routeProps
13
13
  */
14
14
  export const Route = (routeProps) => {
15
- const { component, preload, load, ...rest } = routeProps;
15
+ const { path, component, preload, load, ...rest } = routeProps;
16
+
17
+ // istanbul ignore next - no point testing this error
18
+ if (path === "/" && isLazyComponent(component)) {
19
+ throw new Error("Home component must not be a lazy component");
20
+ }
16
21
 
17
22
  // If component has lifecycle methods but isn't lazy, make it lazy
18
- if (!isLazyComponent(component)) {
23
+ if (!isLazyComponent(component) && path !== "/") {
19
24
  const wrappedComponent = lazy(() =>
20
25
  Promise.resolve({
21
26
  default: component,
22
27
  route: { preload, load },
23
28
  })
24
29
  );
25
- routes.push({ ...rest, component: wrappedComponent });
30
+ routes.push({ ...rest, path, component: wrappedComponent });
26
31
  return;
27
32
  }
28
33
 
package/router/types.d.ts CHANGED
@@ -129,7 +129,10 @@ export type RouteEntry = {
129
129
 
130
130
  export type RouteProps = {
131
131
  path: string;
132
- component: VanComponent | (() => ComponentModule);
132
+ component:
133
+ | (() => HTMLElementTagNameMap[unknown] | HTMLElementTagNameMap[unknown][])
134
+ | VanComponent
135
+ | (() => ComponentModule);
133
136
  preload?: (params?: Record<string, string>) => void;
134
137
  load?: (params?: Record<string, string>) => void;
135
138
  };