vite-plugin-vanjs 0.0.10 → 0.1.1
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 +19 -221
- package/client/global.d.ts +3 -3
- package/client/index.mjs +141 -7
- package/client/types.d.ts +3 -3
- package/jsx/jsx.d.ts +2 -0
- package/jsx/jsx.mjs +4 -4
- package/meta/Head.mjs +3 -3
- package/meta/helpers.mjs +3 -5
- package/package.json +19 -19
- package/plugin/helpers.mjs +271 -0
- package/plugin/index.mjs +206 -0
- package/plugin/types.d.ts +23 -0
- package/router/a.mjs +18 -19
- package/router/cache.mjs +4 -3
- package/router/global.d.ts +74 -28
- package/router/helpers.mjs +32 -27
- package/router/lazy.mjs +22 -11
- package/router/router.mjs +28 -21
- package/router/routes.mjs +38 -9
- package/router/state.mjs +3 -3
- package/router/types.d.ts +103 -29
- package/server/index.mjs +32 -9
- package/server/types.d.ts +56 -0
- package/setup/global.d.ts +41 -39
- package/setup/helpers.mjs +7 -0
- package/setup/index-debug.mjs +8 -20
- package/setup/index-ssr.mjs +5 -0
- package/setup/index.mjs +4 -20
- package/setup/isServer.mjs +2 -0
- package/setup/package.json +16 -0
- package/setup/van-debug.mjs +2 -2
- package/setup/van-ssr..d.ts +1 -0
- package/setup/van-ssr.mjs +51 -0
- package/setup/van.mjs +44 -2
- package/setup/vanX-ssr.d.ts +1 -0
- package/setup/vanX-ssr.mjs +12 -0
- package/setup/vanX.mjs +23 -4
- package/tsconfig.json +1 -1
- package/src/index.mjs +0 -79
- package/src/types.d.ts +0 -28
package/README.md
CHANGED
|
@@ -7,32 +7,19 @@
|
|
|
7
7
|
[](https://www.typescriptlang.org/)
|
|
8
8
|
[](https://github.com/vanjs-org/van)
|
|
9
9
|
[](https://github.com/vanjs-org/mini-van-plate)
|
|
10
|
-
[](https://www.vitest.dev/)
|
|
11
|
+
[](https://vite.dev)
|
|
12
12
|
|
|
13
|
-
A mini meta-framework for [VanJS](https://vanjs.org/) developed around the awesome [
|
|
13
|
+
A mini meta-framework for [VanJS](https://vanjs.org/) developed around the awesome [vite](https://vite.dev) and tested with [vitest](https://vitest.dev). The plugin comes with a set of modules to streamline 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;
|
|
15
15
|
* ***@vanjs/meta*** - allows you to create metadata for your pages as well as load additional assets with ease;
|
|
16
16
|
* ***@vanjs/jsx*** - enables JSX transformation;
|
|
17
17
|
* ***@vanjs/setup*** - enables loading VanJS modules isomorphically;
|
|
18
|
-
* ***@vanjs/server*** - provides various tools for Server Side Rendering;
|
|
19
|
-
* ***@vanjs/client*** - provides various tools for Client Side Rendering.
|
|
20
|
-
|
|
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
|
-
|
|
23
|
-
Also in _development_ mode, the plugin will load the `van.debug.js` module to help you better troubleshoot your VanJS application.
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
### Notes
|
|
27
|
-
* The plugin uses `van-ext` along with `mini-van-plate` so you can have everything ready from the start.
|
|
28
|
-
|
|
29
|
-
* Kickstart your VanJS project with `npm create vanjs@latest`. Some starter templates feature this plugin and most essential tools.
|
|
18
|
+
* ***@vanjs/server*** - provides various tools for Server Side Rendering (SSR);
|
|
19
|
+
* ***@vanjs/client*** - provides various tools for Client Side Rendering (CSR).
|
|
30
20
|
|
|
31
21
|
|
|
32
22
|
### Install
|
|
33
|
-
|
|
34
|
-
1) Install the plugin:
|
|
35
|
-
|
|
36
23
|
```bash
|
|
37
24
|
npm install vite-plugin-vanjs@latest
|
|
38
25
|
```
|
|
@@ -49,212 +36,23 @@ deno add npm:vite-plugin-vanjs@latest
|
|
|
49
36
|
bun add vite-plugin-vanjs@latest
|
|
50
37
|
```
|
|
51
38
|
|
|
39
|
+
### Wiki
|
|
52
40
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
### Usage
|
|
62
|
-
|
|
63
|
-
Update your `vite.config.ts` file:
|
|
64
|
-
```ts
|
|
65
|
-
// vite.config.mts
|
|
66
|
-
import { defineConfig } from 'vite';
|
|
67
|
-
import vanjs from 'vite-plugin-vanjs';
|
|
68
|
-
|
|
69
|
-
export default defineConfig({
|
|
70
|
-
plugins: [vanjs()],
|
|
71
|
-
});
|
|
72
|
-
```
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
**Example**:
|
|
76
|
-
|
|
77
|
-
While the plugin will resolve the appropriate modules automatically depending on the environment, for your convenience, you can also import the `@vanjs/van` and `@vanjs/vanX` virtual modules, so the plugin makes sure to load the right modules where needed.
|
|
78
|
-
|
|
79
|
-
```ts
|
|
80
|
-
// my-component.ts
|
|
81
|
-
import van from '@vanjs/van';
|
|
82
|
-
|
|
83
|
-
// use van as usual
|
|
84
|
-
const MyComponent = () => {
|
|
85
|
-
return van.tags.div("Hello from VanJS!");
|
|
86
|
-
};
|
|
87
|
-
```
|
|
88
|
-
Using `vanX` in your code:
|
|
89
|
-
|
|
90
|
-
```ts
|
|
91
|
-
// my-list.ts
|
|
92
|
-
import van from '@vanjs/van';
|
|
93
|
-
import vanX from '@vanjs/vanX';
|
|
94
|
-
|
|
95
|
-
type ListItem = { text: string, done: boolean };
|
|
96
|
-
|
|
97
|
-
// use VanJS as usual
|
|
98
|
-
const MyList = () => {
|
|
99
|
-
const items = vanX.reactive<ListItem[]>([]);
|
|
100
|
-
const { div, button, span, a, input, del } = van.tags;
|
|
101
|
-
const inputDom = input({ type: "text", placeholder: "your new item" });
|
|
102
|
-
return div(
|
|
103
|
-
inputDom,
|
|
104
|
-
button({onclick: () => items.push({text: inputDom.value, done: false})}, "Add"),
|
|
105
|
-
vanX.list(div, items, ({val: v}, deleter) => div(
|
|
106
|
-
input({type: "checkbox", checked: () => v.done, onclick: (e) => v.done = e.target.checked}),
|
|
107
|
-
() => (v.done ? del : span)(v.text),
|
|
108
|
-
button({ onclick: deleter }, "Remove"),
|
|
109
|
-
)),
|
|
110
|
-
)
|
|
111
|
-
};
|
|
112
|
-
```
|
|
113
|
-
**Note**: Anywhere you have **vite-plugin-vanjs** enabled in your Vite apps, all imports from `"vanjs-core"` or `"vanjs-ext"` will be replaced with `"@vanjs/van"` and `"@vanjs/vanX"` respectivelly. In addition you have the `isServer` getter in the `@vanjs/setup` module, something you can use to exclude code execution in server/client environment.
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
### Router
|
|
117
|
-
The **vite-plugin-vanjs** plugin provides a router with load and preload capability, code splitting and lazy loading, all via the exported `@vanjs/router` module. This functionality is still in early stages, but it manages to work with both Server Side Rendering (SSR) when using an appropriate starter template, as well as Client Side Rendering (CSR/SPA - your classic VanJS app), as we'll see in the example below.
|
|
118
|
-
|
|
119
|
-
Here's a basic example, let's start with the `app.ts`:
|
|
120
|
-
|
|
121
|
-
```ts
|
|
122
|
-
// src/app.ts
|
|
123
|
-
import van from "vanjs-core";
|
|
124
|
-
import { Router, Route } from "@vanjs/router";
|
|
125
|
-
|
|
126
|
-
// define routes
|
|
127
|
-
Route({ path: '/', component: () => van.tags.div('Hello VanJS') });
|
|
128
|
-
Route({ path: '/about', component: lazy(() => import('./pages/about'))});
|
|
129
|
-
Route({ path: '*', component: () => van.tags.div('404 - Page Not Found') });
|
|
130
|
-
|
|
131
|
-
function App() {
|
|
132
|
-
// the Router is only an outlet
|
|
133
|
-
return Router();
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
// render the app
|
|
137
|
-
van.add(document.body, App());
|
|
138
|
-
```
|
|
139
|
-
**IMPORTANT** - using lazy components for the main route (`"/"`) is not allowed. This is to prevent hydration waterfalls, especially for SSR apps.
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
Here's how a page should look like, pay attention to the comments:
|
|
143
|
-
```ts
|
|
144
|
-
// src/pages/about.ts
|
|
145
|
-
import van from "vanjs-core";
|
|
146
|
-
import { A, navigate } from "@vanjs/router";
|
|
147
|
-
|
|
148
|
-
// define routes
|
|
149
|
-
export const route = {
|
|
150
|
-
preload: async () => {
|
|
151
|
-
// in most cases you may want to enforce user access control
|
|
152
|
-
console.log('About preload triggered');
|
|
153
|
-
},
|
|
154
|
-
load: async () => {
|
|
155
|
-
// Load data if needed
|
|
156
|
-
// you might want to cache this data
|
|
157
|
-
console.log('About load triggered');
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
// you must export your page component as either
|
|
162
|
-
// a named export "Page" or a default export
|
|
163
|
-
export function Page() {
|
|
164
|
-
const { div, h1, p, button } = van.tags;
|
|
165
|
-
return div(
|
|
166
|
-
h1('About'),
|
|
167
|
-
p('This is the about page'),
|
|
168
|
-
A({ href: "/" }, "Back to Home"),
|
|
169
|
-
button({ onclick: () => navigate('/about-details') }, "Learn more..")
|
|
170
|
-
)
|
|
171
|
-
}
|
|
172
|
-
```
|
|
173
|
-
**Notes**
|
|
174
|
-
- when hovering the `A` component, if it links to a lazy component it will trigger that page component preload, but not preload any data;
|
|
175
|
-
- if you use the regular `a` from `van.tags` instead of the `A` component, your application will work just like a classic Multi-Page App (MPA);
|
|
176
|
-
- the `navigate` tool is used by the `A` component and you can also use it to navigate to different routes, something you can use with your API/logic.
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
### Metadata
|
|
180
|
-
The **vite-plugin-vanjs** plugin provides metadata management via the exported `@vanjs/meta` module. This module works with both SSR and CSR, and makes it easy to work with.
|
|
181
|
-
|
|
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:
|
|
183
|
-
|
|
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.
|
|
186
|
-
|
|
187
|
-
Here's a quick example, first let's start again with the `app.ts`:
|
|
188
|
-
```ts
|
|
189
|
-
import van from 'vanjs-core'
|
|
190
|
-
import { Style, Title, Link, Meta } from '@vanjs/meta'
|
|
191
|
-
|
|
192
|
-
function App() {
|
|
193
|
-
const { div, h1, p } van.tags;
|
|
194
|
-
|
|
195
|
-
// a good practice is to define some default tags
|
|
196
|
-
// they are used on pages where no tags are set
|
|
197
|
-
Title("VanJS + Vite App");
|
|
198
|
-
Meta({ name: "description", content: "Sample app description" });
|
|
199
|
-
Meta({ property: "og:description", content: "Some open graph description for your app" });
|
|
200
|
-
Link({ href: 'path-to/your-style.css', rel: "stylesheet" });
|
|
201
|
-
Script({ src: 'path-to/your-script.js' });
|
|
202
|
-
Style({ id: "app-style" },
|
|
203
|
-
`p { margin-bottom: 1rem }`
|
|
204
|
-
);
|
|
205
|
-
|
|
206
|
-
return div(
|
|
207
|
-
h1('Hello VanJS!'),
|
|
208
|
-
p('Sample paragraph.')
|
|
209
|
-
);
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
// render or hydrate the app
|
|
213
|
-
van.add(document.body, App());
|
|
214
|
-
```
|
|
215
|
-
**Note**
|
|
216
|
-
- the `Style` component doesn't have any unique attribute so we must use a unique ID to prevent duplicates;
|
|
217
|
-
- all provided metadata components don't return any markup, their function is to register new tags or update existing ones.
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
### JSX Transformation
|
|
221
|
-
To enable JSX transformation, you don't need to do anything except to add Typescript support if you need to. So edit your `tsconfig.json` as follows:
|
|
222
|
-
|
|
223
|
-
```json
|
|
224
|
-
{
|
|
225
|
-
"compilerOptions": {
|
|
226
|
-
/** other compilerOptions */
|
|
227
|
-
"jsx": "preserve",
|
|
228
|
-
"jsxImportSource": "@vanjs/jsx"
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
```
|
|
41
|
+
For a complete guide on how to use the plugin, be sure to check the wiki:
|
|
42
|
+
* [Quick Start](https://github.com/thednp/vite-plugin-vanjs/wiki/Quick-Start): the right place to get started;
|
|
43
|
+
* [Routing](https://github.com/thednp/vite-plugin-vanjs/wiki/Routing): a complete guide on how to configure routing;
|
|
44
|
+
* [Metadata](https://github.com/thednp/vite-plugin-vanjs/wiki/Metadata): the quick guide to help you manage your app meta tags;
|
|
45
|
+
* [JSX](https://github.com/thednp/vite-plugin-vanjs/wiki/JSX): a complete guide on how to use JSX transformation;
|
|
46
|
+
* [Isomorphic](https://github.com/thednp/vite-plugin-vanjs/wiki/Isomorphic): a quick guide on isomorphism in VanJS;
|
|
47
|
+
* [Hydration](https://github.com/thednp/vite-plugin-vanjs/wiki/Hydration): a quick note on how the plugin handles hydration;
|
|
48
|
+
* [About](https://github.com/thednp/vite-plugin-vanjs/wiki/About): some note on the project.
|
|
232
49
|
|
|
233
|
-
**Example**:
|
|
234
|
-
```tsx
|
|
235
|
-
// App.tsx
|
|
236
|
-
import van from '@vanjs/van';
|
|
237
50
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
<button ref={btnRef} onClick={() => count.val++}>{count}</button>
|
|
244
|
-
);
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
const root = document.getElementById("app") as HTMLElement;
|
|
248
|
-
|
|
249
|
-
van.add(root, <App /> as HTMLElement);
|
|
250
|
-
```
|
|
251
|
-
|
|
252
|
-
**Notes**:
|
|
253
|
-
* in cases like this one, enforcing a certain typescript type via `as` might be in good order depending on who renders your component;
|
|
254
|
-
* you can use `ref` as a `van.state`, `class` attribute instead of `className`, `for` attribute instead of `htmlFor`;
|
|
255
|
-
* you can use style as both an object and a string;
|
|
256
|
-
* for a JSX starter template, check out the [vite-starter-vanjs-ssr-jsx](https://github.com/thednp/vite-starter-vanjs-ssr-jsx).
|
|
257
|
-
* for a pure vanilla starter template, check out the [vite-starter-vanjs-ssr](https://github.com/thednp/vite-starter-vanjs-ssr).
|
|
51
|
+
### Notes
|
|
52
|
+
* 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 `mini-van-plate/van-plate` on the server.
|
|
53
|
+
* Also in _development_ mode, the plugin will load the `van.debug.js` module to help you better troubleshoot your VanJS application. However JSX transformation is not compatible with the `van.debug.js` module.
|
|
54
|
+
* The plugin uses `van-ext` along with `mini-van-plate` so you can have everything ready from the start.
|
|
55
|
+
* Kickstart your VanJS project with `npm create vanjs@latest`. Some starter templates feature this plugin and most essential tools.
|
|
258
56
|
|
|
259
57
|
|
|
260
58
|
### Credits
|
package/client/global.d.ts
CHANGED
|
@@ -34,12 +34,12 @@ declare module "@vanjs/client" {
|
|
|
34
34
|
* @param target the target element
|
|
35
35
|
* @param content the element(s) to hydrate
|
|
36
36
|
*/
|
|
37
|
-
export const hydrate: (
|
|
38
|
-
target:
|
|
37
|
+
export const hydrate: <T = HTMLElement>(
|
|
38
|
+
target: T,
|
|
39
39
|
content:
|
|
40
40
|
| ChildElement
|
|
41
41
|
| ChildElement[]
|
|
42
42
|
| JSX.Element
|
|
43
43
|
| Promise<ChildElement | ChildElement[] | JSX.Element>,
|
|
44
|
-
) =>
|
|
44
|
+
) => T;
|
|
45
45
|
}
|
package/client/index.mjs
CHANGED
|
@@ -36,6 +36,121 @@ 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) {
|
|
41
|
+
// Quick initial checks before recursing
|
|
42
|
+
// istanbul ignore else
|
|
43
|
+
if (
|
|
44
|
+
!(el2 instanceof HTMLElement) ||
|
|
45
|
+
el1.tagName !== el2.tagName ||
|
|
46
|
+
el1.id !== el2.id ||
|
|
47
|
+
el1.className !== el2.className
|
|
48
|
+
) {
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const childNodes1 = Array.from(el1.childNodes);
|
|
53
|
+
const childNodes2 = Array.from(el2.childNodes);
|
|
54
|
+
|
|
55
|
+
// istanbul ignore else
|
|
56
|
+
if (childNodes1.length !== childNodes2.length) {
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Only recurse if necessary (has childNodes with data-hk)
|
|
61
|
+
const hasHydratedChildren = childNodes1.some((child) =>
|
|
62
|
+
child instanceof HTMLElement && (child.hasAttribute("data-hk") ||
|
|
63
|
+
child.querySelector("[data-hk]"))
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
if (!hasHydratedChildren) {
|
|
67
|
+
return true; // If no hydrated children, elements match based on initial checks
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return childNodes1.every((child, idx) =>
|
|
71
|
+
elementsMatch(child, childNodes2[idx])
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function createHydrationContext() {
|
|
76
|
+
/** @type {WeakMap<Element, Element>} */
|
|
77
|
+
const parentCache = new WeakMap();
|
|
78
|
+
|
|
79
|
+
/** @type {(element: HTMLElement, root: HTMLElement) => HTMLElement | null} */
|
|
80
|
+
function getParent(element, root) {
|
|
81
|
+
const cacheKey = element;
|
|
82
|
+
// istanbul ignore if - must be connected to a read DOM
|
|
83
|
+
if (parentCache.has(cacheKey)) {
|
|
84
|
+
const cached = parentCache.get(cacheKey);
|
|
85
|
+
// Verify the cached parent is still valid for this root
|
|
86
|
+
if (cached && cached.isConnected && root.contains(cached)) {
|
|
87
|
+
return cached;
|
|
88
|
+
}
|
|
89
|
+
// If not valid, remove from cache
|
|
90
|
+
parentCache.delete(cacheKey);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const chain = [];
|
|
94
|
+
let current = element;
|
|
95
|
+
|
|
96
|
+
while (current !== root && current) {
|
|
97
|
+
chain.push(current);
|
|
98
|
+
current = current.parentElement;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const parent = chain.slice(-1)[0];
|
|
102
|
+
// istanbul ignore else
|
|
103
|
+
if (parent) {
|
|
104
|
+
parentCache.set(cacheKey, parent);
|
|
105
|
+
}
|
|
106
|
+
return parent;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** @type {(oldDom: HTMLElement, newDom: HTMLElement) => HTMLElement} */
|
|
110
|
+
function diffAndHydrate(oldDom, newDom) {
|
|
111
|
+
// SPA mode
|
|
112
|
+
// istanbul ignore else
|
|
113
|
+
if (!oldDom.children.length && !elementsMatch(oldDom, newDom)) {
|
|
114
|
+
return oldDom.replaceChildren(...unwrap(newDom).children);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// SSR Mode
|
|
118
|
+
/** @type {Set<HTMLElement>} */
|
|
119
|
+
const oldSet = new Set();
|
|
120
|
+
/** @type {Set<HTMLElement>} */
|
|
121
|
+
const newSet = new Set();
|
|
122
|
+
|
|
123
|
+
const processElements = (root, set) => {
|
|
124
|
+
const elements = root.querySelectorAll("[data-hk]");
|
|
125
|
+
let lastParent = null;
|
|
126
|
+
|
|
127
|
+
elements.forEach((el) => {
|
|
128
|
+
const parent = getParent(el, root);
|
|
129
|
+
if (parent && parent !== lastParent) {
|
|
130
|
+
set.add(parent);
|
|
131
|
+
lastParent = parent;
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
processElements(oldDom, oldSet);
|
|
137
|
+
processElements(newDom, newSet);
|
|
138
|
+
|
|
139
|
+
// istanbul ignore else
|
|
140
|
+
if (newSet.size > 0) {
|
|
141
|
+
const newArray = Array.from(newSet);
|
|
142
|
+
oldSet.forEach((el) => {
|
|
143
|
+
const match = newArray.find((m) => elementsMatch(m, el));
|
|
144
|
+
// istanbul ignore else
|
|
145
|
+
if (match) {
|
|
146
|
+
el.replaceWith(match);
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return { diffAndHydrate };
|
|
153
|
+
}
|
|
39
154
|
/**
|
|
40
155
|
* @param {Element} target the root element
|
|
41
156
|
* @param {Element | Element[] | Promise<Element | Element[]>} content the element(s) to hydrate
|
|
@@ -43,10 +158,16 @@ export const styleToString = (style) => {
|
|
|
43
158
|
export const hydrate = (target, content) => {
|
|
44
159
|
if (content instanceof Promise) {
|
|
45
160
|
content.then((res) => {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
161
|
+
if (!target.hasAttribute("data-h")) {
|
|
162
|
+
const { diffAndHydrate } = createHydrationContext();
|
|
163
|
+
diffAndHydrate(target, res);
|
|
164
|
+
target.setAttribute("data-h", "");
|
|
165
|
+
} else {
|
|
166
|
+
const wrapper = unwrap(res);
|
|
167
|
+
target.replaceChildren(
|
|
168
|
+
...(Array.from(wrapper.children)),
|
|
169
|
+
);
|
|
170
|
+
}
|
|
50
171
|
});
|
|
51
172
|
return target;
|
|
52
173
|
}
|
|
@@ -60,6 +181,12 @@ export const hydrate = (target, content) => {
|
|
|
60
181
|
const isLink = (tag) => tag instanceof HTMLLinkElement;
|
|
61
182
|
|
|
62
183
|
if (target.tagName.toLowerCase() === "head") {
|
|
184
|
+
// Keep current tags on first hydration
|
|
185
|
+
if (!target.hasAttribute("data-h")) {
|
|
186
|
+
target.setAttribute("data-h", "");
|
|
187
|
+
return target;
|
|
188
|
+
}
|
|
189
|
+
|
|
63
190
|
// Handle non-style/link tags first
|
|
64
191
|
const regularTags = newChildren.filter((child) => !isStyleLink(child));
|
|
65
192
|
|
|
@@ -106,6 +233,7 @@ export const hydrate = (target, content) => {
|
|
|
106
233
|
}
|
|
107
234
|
|
|
108
235
|
// For link tags, add with disabled state first
|
|
236
|
+
/* istanbul ignore else - try again later */
|
|
109
237
|
if (isLink(newChild)) {
|
|
110
238
|
const temp = newChild.cloneNode();
|
|
111
239
|
temp.disabled = true;
|
|
@@ -125,17 +253,23 @@ export const hydrate = (target, content) => {
|
|
|
125
253
|
|
|
126
254
|
target.appendChild(temp);
|
|
127
255
|
} // For style tags, add new one first
|
|
128
|
-
|
|
256
|
+
else if (isStyle(newChild)) {
|
|
129
257
|
target.appendChild(newChild);
|
|
130
258
|
/* istanbul ignore next - try again later */
|
|
131
259
|
if (existing && existing.parentNode === target) {
|
|
132
260
|
// Remove old one in next frame
|
|
133
|
-
|
|
261
|
+
existing.remove();
|
|
134
262
|
}
|
|
135
263
|
}
|
|
136
264
|
});
|
|
137
265
|
} else {
|
|
138
|
-
target.
|
|
266
|
+
if (!target.hasAttribute("data-h")) {
|
|
267
|
+
const { diffAndHydrate } = createHydrationContext();
|
|
268
|
+
diffAndHydrate(target, content);
|
|
269
|
+
target.setAttribute("data-h", "");
|
|
270
|
+
} else {
|
|
271
|
+
target.replaceChildren(...newChildren);
|
|
272
|
+
}
|
|
139
273
|
}
|
|
140
274
|
|
|
141
275
|
return target;
|
package/client/types.d.ts
CHANGED
|
@@ -28,11 +28,11 @@ export const styleToString: (source: string | CSS.Properties) => string;
|
|
|
28
28
|
* @param target the root element
|
|
29
29
|
* @param content the element(s) to hydrate
|
|
30
30
|
*/
|
|
31
|
-
export const hydrate: (
|
|
32
|
-
target:
|
|
31
|
+
export const hydrate: <T = HTMLElement>(
|
|
32
|
+
target: T,
|
|
33
33
|
content:
|
|
34
34
|
| HTMLElement
|
|
35
35
|
| HTMLElement[]
|
|
36
36
|
| JSX.Element
|
|
37
37
|
| Promise<HTMLElement | HTMLElement[] | JSX.Element>,
|
|
38
|
-
) =>
|
|
38
|
+
) => T;
|
package/jsx/jsx.d.ts
CHANGED
|
@@ -752,6 +752,8 @@ declare namespace JSX {
|
|
|
752
752
|
draggable?: FunctionMaybe<boolean>;
|
|
753
753
|
hidden?: FunctionMaybe<boolean>;
|
|
754
754
|
id?: FunctionMaybe<string>;
|
|
755
|
+
is?: FunctionMaybe<string>;
|
|
756
|
+
inert?: FunctionMaybe<boolean>;
|
|
755
757
|
lang?: FunctionMaybe<string>;
|
|
756
758
|
spellcheck?: FunctionMaybe<boolean>;
|
|
757
759
|
style?: FunctionMaybe<CSSProperties | string>;
|
package/jsx/jsx.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import van from "
|
|
2
|
-
import
|
|
1
|
+
import van from "vanjs-core";
|
|
2
|
+
import isServer from "../setup/isServer.mjs";
|
|
3
3
|
import { setAttribute, styleToString } from "../client/index.mjs";
|
|
4
4
|
|
|
5
5
|
export const jsx = (jsxTag, { children, ref, style, ...rest }) => {
|
|
@@ -17,7 +17,7 @@ export const jsx = (jsxTag, { children, ref, style, ...rest }) => {
|
|
|
17
17
|
const styleProp = typeof style === "function" ? style() : style;
|
|
18
18
|
const styleValue = styleToString(styleProp);
|
|
19
19
|
|
|
20
|
-
if (
|
|
20
|
+
if (isServer) {
|
|
21
21
|
newElement.propsStr += ` style="${styleValue}"`;
|
|
22
22
|
} else {
|
|
23
23
|
newElement.style.cssText = styleValue;
|
|
@@ -26,7 +26,7 @@ export const jsx = (jsxTag, { children, ref, style, ...rest }) => {
|
|
|
26
26
|
});
|
|
27
27
|
|
|
28
28
|
// on server it's good enough to return here
|
|
29
|
-
if (
|
|
29
|
+
if (isServer) return newElement;
|
|
30
30
|
|
|
31
31
|
// on the client, we sure do need to set the attributes
|
|
32
32
|
for (const [k, value] of Object.entries(props)) {
|
package/meta/Head.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import isServer from "../setup/isServer.mjs";
|
|
2
2
|
import { getTagKey } from "./helpers.mjs";
|
|
3
3
|
|
|
4
4
|
/** @typedef {typeof import("./types.d.ts").getHeadTags} GetTags */
|
|
@@ -20,7 +20,7 @@ const createHeadTags = () => new Map();
|
|
|
20
20
|
* @type {GetTags}
|
|
21
21
|
*/
|
|
22
22
|
const getHeadTags = (() => {
|
|
23
|
-
if (
|
|
23
|
+
if (isServer) {
|
|
24
24
|
// On server, create one Map per request scope
|
|
25
25
|
let serverHeadTags;
|
|
26
26
|
return () => {
|
|
@@ -51,7 +51,7 @@ export const resetHeadTags = () => {
|
|
|
51
51
|
export const initializeHeadTags = () => {
|
|
52
52
|
const tags = getHeadTags();
|
|
53
53
|
/* istanbul ignore else */
|
|
54
|
-
if (!tags.size && !
|
|
54
|
+
if (!tags.size && !isServer) {
|
|
55
55
|
Array.from(document.head.children).forEach((tag) => {
|
|
56
56
|
tags.set(getTagKey(tag), tag);
|
|
57
57
|
});
|
package/meta/helpers.mjs
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import
|
|
2
|
-
// import { getAttributes } from "@thednp/domparser/parser";
|
|
1
|
+
import isServer from "../setup/isServer.mjs";
|
|
3
2
|
|
|
4
3
|
/** @type {typeof import('./types.d.ts').parseAttributes} */
|
|
5
4
|
export const parseAttributes = (attributeString) => {
|
|
@@ -33,7 +32,7 @@ const getTagAttribute = (tag) => {
|
|
|
33
32
|
];
|
|
34
33
|
|
|
35
34
|
for (const attr of attributes) {
|
|
36
|
-
const value =
|
|
35
|
+
const value = isServer ? tag[attr] : tag.getAttribute(attr);
|
|
37
36
|
|
|
38
37
|
if (value) return value;
|
|
39
38
|
}
|
|
@@ -42,9 +41,8 @@ const getTagAttribute = (tag) => {
|
|
|
42
41
|
|
|
43
42
|
/** @type {typeof import('./types.d.ts').getTagKey} */
|
|
44
43
|
export const getTagKey = (tag) => {
|
|
45
|
-
const normalizedTag =
|
|
44
|
+
const normalizedTag = isServer
|
|
46
45
|
? { tagName: tag.name.toUpperCase(), ...parseAttributes(tag.propsStr) }
|
|
47
|
-
// ? { tagName: tag.name.toUpperCase(), ...getAttributes(tag.propsStr) }
|
|
48
46
|
: tag;
|
|
49
47
|
|
|
50
48
|
return normalizedTag.tagName +
|
package/package.json
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vite-plugin-vanjs",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"author": "thednp",
|
|
5
5
|
"license": "MIT",
|
|
6
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,
|
|
10
|
-
"main": "./
|
|
11
|
-
"module": "./
|
|
12
|
-
"types": "./
|
|
10
|
+
"main": "./plugin/index.mjs",
|
|
11
|
+
"module": "./plugin/index.mjs",
|
|
12
|
+
"types": "./plugin/types.d.ts",
|
|
13
13
|
"files": [
|
|
14
|
-
"
|
|
14
|
+
"plugin",
|
|
15
15
|
"setup",
|
|
16
16
|
"router",
|
|
17
17
|
"meta",
|
|
@@ -37,9 +37,9 @@
|
|
|
37
37
|
],
|
|
38
38
|
"exports": {
|
|
39
39
|
".": {
|
|
40
|
-
"types": "./
|
|
41
|
-
"import": "./
|
|
42
|
-
"default": "./
|
|
40
|
+
"types": "./plugin/types.d.ts",
|
|
41
|
+
"import": "./plugin/index.mjs",
|
|
42
|
+
"default": "./plugin/index.mjs"
|
|
43
43
|
},
|
|
44
44
|
"./setup": {
|
|
45
45
|
"types": "./setup/types.d.ts",
|
|
@@ -87,14 +87,14 @@
|
|
|
87
87
|
"vanjs-ext": "^0.6.2"
|
|
88
88
|
},
|
|
89
89
|
"devDependencies": {
|
|
90
|
-
"@types/node": "^22.
|
|
91
|
-
"@vitest/browser": "^3.1.
|
|
92
|
-
"@vitest/coverage-istanbul": "^3.1.
|
|
93
|
-
"@vitest/ui": "^3.1.
|
|
90
|
+
"@types/node": "^22.15.2",
|
|
91
|
+
"@vitest/browser": "^3.1.2",
|
|
92
|
+
"@vitest/coverage-istanbul": "^3.1.2",
|
|
93
|
+
"@vitest/ui": "^3.1.2",
|
|
94
94
|
"happy-dom": "^16.8.1",
|
|
95
95
|
"typescript": "5.6.2",
|
|
96
|
-
"vite": "^6.
|
|
97
|
-
"vitest": "^3.1.
|
|
96
|
+
"vite": "^6.3.3",
|
|
97
|
+
"vitest": "^3.1.2"
|
|
98
98
|
},
|
|
99
99
|
"packageManager": "pnpm@8.6.12",
|
|
100
100
|
"engines": {
|
|
@@ -102,13 +102,13 @@
|
|
|
102
102
|
"pnpm": ">=8.6.0"
|
|
103
103
|
},
|
|
104
104
|
"scripts": {
|
|
105
|
-
"test": "vitest --config vitest.config.
|
|
106
|
-
"test-ui": "vitest --config vitest.config.
|
|
105
|
+
"test": "vitest --config vitest.config.ts",
|
|
106
|
+
"test-ui": "vitest --config vitest.config.ts --ui=true",
|
|
107
107
|
"badges": "npx -p dependency-version-badge update-badge vanjs-core mini-van-plate typescript vitest vite",
|
|
108
|
-
"format": "deno fmt
|
|
108
|
+
"format": "deno fmt plugin meta router setup client server jsx",
|
|
109
109
|
"lint": "pnpm lint:ts && pnpm check:ts",
|
|
110
|
-
"lint:ts": "deno lint
|
|
111
|
-
"fix:ts": "deno lint
|
|
110
|
+
"lint:ts": "deno lint plugin meta router setup client server jsx",
|
|
111
|
+
"fix:ts": "deno lint plugin meta router setup client server jsx --fix",
|
|
112
112
|
"check:ts": "tsc -noEmit"
|
|
113
113
|
}
|
|
114
114
|
}
|