@vanijs/vani 0.3.0 → 0.5.0

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vanijs/vani",
3
- "version": "0.3.0",
3
+ "version": "0.5.0",
4
4
  "description": "Web-standards-first UI runtime with explicit updates, fine-grained DOM ownership, and zero dependencies. JS-first, transpiler-free, with an optional JSX adapter. Built for SPA, SSR, and SSG.",
5
5
  "keywords": [
6
6
  "ui",
@@ -12,7 +12,7 @@
12
12
  "explicit-rendering",
13
13
  "no-virtual-dom",
14
14
  "js-first",
15
- "jsx-adapter",
15
+ "jsx-runtime",
16
16
  "no-compiler",
17
17
  "ssr",
18
18
  "ssg",
@@ -23,7 +23,7 @@
23
23
  "typescript",
24
24
  "zero-dependencies"
25
25
  ],
26
- "homepage": "https://itsjavi.com/vani",
26
+ "homepage": "https://vanijs.dev",
27
27
  "repository": {
28
28
  "type": "git",
29
29
  "url": "git+https://github.com/itsjavi/vani.git"
@@ -64,20 +64,32 @@
64
64
  "LICENSE"
65
65
  ],
66
66
  "scripts": {
67
- "build": "rm -rf dist && vite build && pnpm run build:lib",
68
- "build:lib": "pnpm tsdown src/vani/index.ts src/vani/jsx-runtime.ts src/vani/jsx-dev-runtime.ts --minify --out-dir dist/lib --dts",
67
+ "bench": "pnpm cleanup && pnpm run --filter benchmarks bench",
68
+ "bench:build": "pnpm run --filter benchmarks build:prod",
69
+ "bench:dev": "pnpm run --filter benchmarks dev",
70
+ "bench:wide": "BENCH_VIEWPORT_WIDTH=1280 BENCH_VIEWPORT_HEIGHT=740 pnpm run --filter benchmarks bench",
71
+ "build": "vite build && pnpm run build:lib",
72
+ "build:all": "pnpm run build && pnpm run build:pages",
73
+ "postbuild:all": "./tests/postbuild.sh dist",
74
+ "build:lib": "pnpm tsdown src/vani/index.ts src/vani/jsx-runtime.ts src/vani/jsx-dev-runtime.ts --minify --out-dir dist/lib --dts --sourcemap",
75
+ "build:pages": "vite build && pnpm run bench:build && cp -r bench/dist ./dist/client/benchmarks && cp -r bench/results ./dist/client/benchmarks/data",
76
+ "cleanup": "rm -rf dist node_modules/.vite node_modules/.vite-temp bench/dist bench/frameworks/*/dist",
69
77
  "dev": "vite",
70
- "dev:debug": "open http://localhost:4555 && PORT=4555 bun run --watch src/debug/*.html",
71
- "format": "prettier --write . && pnpm dlx sort-package-json",
78
+ "format": "prettier --write . && pnpm run format:pkg",
79
+ "format:pkg": "pnpm sort-package-json package.json bench/package.json",
72
80
  "lint": "pnpm run typecheck && pnpm run lint:eslint && pnpm run lint:circular",
73
- "lint:circular": "pnpm dlx madge --circular --extensions ts src/",
81
+ "lint:circular": "pnpm madge --circular --extensions ts src/",
74
82
  "lint:eslint": "eslint .",
75
- "lint:unused": "pnpm dlx knip --exclude exports",
83
+ "lint:unused": "pnpm knip --exclude exports",
76
84
  "prepublishOnly": "pnpm format && pnpm lint && pnpm run build:lib && pnpm publint",
77
- "preview": "vite preview",
78
- "typecheck": "tsc --noEmit"
85
+ "preview": "pnpm run build:pages && vite preview",
86
+ "serve": "sirv ./dist/client",
87
+ "test": "./tests/postbuild.sh dist",
88
+ "typecheck": "tsc --noEmit && cd bench && pnpm run typecheck"
89
+ },
90
+ "dependencies": {
91
+ "sirv-cli": "^3.0.1"
79
92
  },
80
- "dependencies": {},
81
93
  "devDependencies": {
82
94
  "@babel/runtime": "^7.28.6",
83
95
  "@codemirror/commands": "^6.10.1",
@@ -85,26 +97,33 @@
85
97
  "@codemirror/state": "^6.5.4",
86
98
  "@codemirror/view": "^6.39.11",
87
99
  "@eslint/js": "^9.39.2",
100
+ "@rollup/pluginutils": "^5.3.0",
88
101
  "@shikijs/langs": "^3.21.0",
89
102
  "@shikijs/themes": "^3.21.0",
90
103
  "@tailwindcss/vite": "^4.1.18",
91
- "@types/bun": "^1.3.6",
92
- "@types/node": "^25.0.9",
104
+ "@types/bun": "catalog:",
105
+ "@types/node": "catalog:",
106
+ "@typescript/native-preview": "catalog:",
93
107
  "@uiw/codemirror-theme-vscode": "^4.25.4",
94
108
  "eslint": "^9.39.2",
95
- "globals": "^17.0.0",
96
- "lucide-static": "^0.562.0",
97
- "prettier": "^3.8.0",
109
+ "globals": "^17.1.0",
110
+ "husky": "^9.1.7",
111
+ "knip": "^5.82.1",
112
+ "lucide-static": "^0.563.0",
113
+ "madge": "^8.0.0",
114
+ "playwright": "catalog:",
115
+ "prettier": "^3.8.1",
98
116
  "prettier-plugin-tailwindcss": "^0.7.2",
99
- "publint": "^0.3.16",
117
+ "publint": "^0.3.17",
100
118
  "shiki": "^3.21.0",
119
+ "sort-package-json": "^3.6.1",
101
120
  "tailwind-merge": "^3.4.0",
102
121
  "tailwindcss": "^4.1.18",
103
- "tsdown": "0.20.0-beta.4",
122
+ "tsdown": "0.20.1",
104
123
  "tw-animate-css": "^1.4.0",
105
- "typescript": "~5.9.3",
124
+ "typescript": "catalog:",
106
125
  "typescript-eslint": "^8.53.1",
107
- "vite": "npm:rolldown-vite@7.3.1"
126
+ "vite": "catalog:"
108
127
  },
109
128
  "publishConfig": {
110
129
  "access": "public",
@@ -122,10 +141,5 @@
122
141
  "default": "./dist/lib/jsx-dev-runtime.mjs"
123
142
  }
124
143
  }
125
- },
126
- "pnpm": {
127
- "overrides": {
128
- "vite": "npm:rolldown-vite@7.3.1"
129
- }
130
144
  }
131
145
  }
@@ -1 +0,0 @@
1
- function e(e){let t=e;return t.__vaniKeyed||=new Map,t.__vaniKeyed}function t(e){return t=>{let n,r,i=!1,a=t;if(t&&typeof t==`object`){let e=t;if(n=e.key,r=e.ref,i=e.clientOnly,`key`in e||`ref`in e){let{key:t,ref:n,clientOnly:r,...i}=e;a=i}}return{$$vani:`component`,component:e,props:a,key:n,ref:r,clientOnly:i}}}let n=`dom`;function r(e,t){let r=n;n=e;let i=t();return i&&typeof i.finally==`function`?i.finally(()=>{n=r}):(n=r,i)}function i(){return n}function a(e){if(typeof e!=`object`||!e||!(`type`in e))return!1;let t=e;switch(t.type){case`element`:return typeof t.tag==`string`&&typeof t.props==`object`&&Array.isArray(t.children);case`text`:case`comment`:return typeof t.text==`string`;case`fragment`:return Array.isArray(t.children);case`component`:return typeof t.instance==`object`&&t.instance!=null;default:return!1}}function o(e){return a(e)&&e.type===`element`}function s(e){return a(e)&&e.type===`fragment`}const c=new Set([`svg`,`g`,`path`,`circle`,`rect`,`line`,`polyline`,`polygon`,`ellipse`,`defs`,`clipPath`,`mask`,`pattern`,`linearGradient`,`radialGradient`,`stop`,`use`]);function l(e){return n===`dom`?c.has(e)?document.createElementNS(`http://www.w3.org/2000/svg`,e):document.createElement(e):{type:`element`,tag:e,props:{},children:[]}}function u(e){return n===`dom`?document.createTextNode(e):{type:`text`,text:e}}function d(e,t){if(n===`dom`){e.appendChild(t);return}(o(e)||s(e))&&e.children.push(t)}function f(e,t){let n=e.nextSibling;for(;n&&n!==t;){let e=n.nextSibling,t=n;t.__vaniDomRef&&(t.__vaniDomRef.current=null),n.remove(),n=e}}function p(e){if(e==null||e===!1)return document.createComment(`vani:empty`);if(g(e)){let t=document.createDocumentFragment(),n=m(e.component,v(e),t);return e.ref&&(e.ref.current=n),t}if(typeof e==`string`||typeof e==`number`)return document.createTextNode(String(e));if(e instanceof Node)return e;throw Error(`[vani] render returned an unsupported node type in DOM mode`)}function m(e,t,n){let r=[],i=!1,a,o,s=t?.clientOnly===!0;if(I){let e=R;R+=1,a=B(n,e),o=V(a,e)}else a=document.createComment(`vani:start`),o=document.createComment(`vani:end`),n.appendChild(a),n.appendChild(o);let c,l={update(){i||(O?A.has(l)||(j.add(l),N()):(j.delete(l),A.add(l),P()))},updateSync(){if(i)return;let e=a.parentNode;if(!e)return;f(a,o);let t=p(c());e.insertBefore(t,o)},onCleanup(e){r.push(e)},dispose(){if(!i){i=!0,A.delete(l),j.delete(l);for(let e of r)e();r.length=0,f(a,o),a.remove(),o.remove(),c=(()=>document.createComment(`disposed`))}},effect(e){let t=e();typeof t==`function`&&r.push(t)}};if(I&&!s){let n=!1;return c=()=>{if(!n){n=!0;let r=e(t,l);c=r instanceof Promise?()=>document.createComment(`async`):r}return c()},l}let u=e(t,l);return u instanceof Promise?(c=t?.fallback||(()=>document.createComment(`vani:async`)),(!I||s)&&l.update(),u.then(e=>{i||(c=e,l.update())}),l):(c=u,(!I||s)&&l.update(),l)}function h(e,t){if(!t)throw Error(`[vani] root element not found`);let n=[];for(let r of e){if(typeof r==`function`){let e=m(r,{},t);n.push(e);continue}let e=m(r.component,v(r),t);n.push(e)}return n}function g(e){let t=typeof Node<`u`&&e instanceof Node;if(typeof e!=`object`||t)return!1;let n=e;return n.$$vani===`component`&&typeof n.component==`function`}function _(e){let t=typeof Node<`u`&&e instanceof Node;return typeof e==`object`&&!!e&&!t&&!g(e)&&!a(e)}function v(e){return e.clientOnly?{...e.props??{},clientOnly:!0}:e.props}function y(t,r){if(n===`ssr`){for(let e of r)if(!(e==null||e===!1||e===void 0)){if(g(e)){d(t,{type:`component`,instance:e});continue}if(typeof e==`string`||typeof e==`number`){d(t,u(String(e)));continue}if(a(e)){d(t,e);continue}}return}let i=t;for(let t of r)if(!(t==null||t===!1||t===void 0)){if(g(t)){if(t.key!=null){let n=e(i),r=i.__vaniUsedKeys??=new Set,a=n.get(t.key);if(!a){let e=document.createDocumentFragment(),r=m(t.component,v(t),e);t.ref&&(t.ref.current=r),a={fragment:e,handle:r,ref:t.ref},n.set(t.key,a),t.ref&&(t.ref.current=r)}r.add(t.key),i.appendChild(a.fragment);continue}let n=document.createDocumentFragment(),r=m(t.component,v(t),n);t.ref&&(t.ref.current=r),i.appendChild(n);continue}if(typeof t==`string`||typeof t==`number`){i.appendChild(document.createTextNode(String(t)));continue}i.appendChild(t)}let o=i.__vaniKeyed,s=i.__vaniUsedKeys;if(o&&s){for(let[e,t]of o)s.has(e)||(t.handle.dispose(),t.ref&&(t.ref.current=null),o.delete(e));s.clear()}}function b(e){return typeof SVGElement<`u`&&e instanceof SVGElement}function x(e,t){return e.startsWith(`aria`)?`aria-`+e.replace(`aria-`,``).replace(`aria`,``).toLowerCase():e.startsWith(`data`)?`data-`+e.replace(`data-`,``).replace(`data`,``):e.toLowerCase()===`htmlfor`?`for`:t?e:e.toLowerCase()}function S(e,t){let n=o(e)?c.has(e.tag):b(e);for(let r in t){let i=t[r];if(![`key`,`ref`].includes(r)){if(r===`className`){let t=C(i);o(e)?e.props.class=t:n?e.setAttribute(`class`,t):e.className=t;continue}if(r.startsWith(`on`)&&typeof i==`function`)o(e)||(e[r.toLowerCase()]=i);else if(i===!0)o(e)?e.props[r]=!0:e.setAttribute(r,``);else if(i===!1||i==null)continue;else{let t=x(r,n);o(e)?e.props[t]=String(i):e.setAttribute(t,String(i))}}}}function C(...e){return e.map(e=>{if(!(e==null||e===``))return typeof e==`string`?e.trim():Array.isArray(e)?C(...e):Object.entries(e).filter(([e,t])=>t).map(([e])=>e.trim()).join(` `).trim()}).filter(Boolean).join(` `)}function w(e,t,...n){let r=l(e);return _(t)?(t.ref&&(o(r)?t.ref.current=null:(t.ref.current=r,r.__vaniDomRef=t.ref)),S(r,t),y(r,n),r):(y(r,[t,...n]),r)}const T=(...e)=>{if(n===`ssr`){let t={type:`fragment`,children:[]};return y(t,e),t}let t=document.createDocumentFragment();return y(t,e),t};function E(e,t){if(n===`ssr`)return{type:`component`,instance:{$$vani:`component`,component:e,props:t}};let r=document.createDocumentFragment();return m(e,t,r),r}let D=!1,O=!1,k=!1;const A=new Set,j=new Set;function M(e){let t=O;O=!0;try{e()}finally{O=t,N()}}function N(){k||(k=!0,setTimeout(()=>{k=!1,F()},0))}function P(){D||(D=!0,queueMicrotask(()=>{D=!1;for(let e of A)e.updateSync();A.clear()}))}function F(){for(let e of j)e.updateSync();j.clear(),j.size>0&&N()}let I=!1,L=null,R=0;function z(e){U()&&console.warn(`[vani] hydration warning: ${e}`)}function B(e,t){let n=L;(!n||!e.contains(n))&&(n=e.firstChild);let r=!1;for(;n;){if(n.nodeType===Node.COMMENT_NODE&&n.nodeValue===`vani:start`)return r&&z(`Found <!--vani:end--> before <!--vani:start--> for component #${t}. This usually means the server HTML anchor order is incorrect.`),n;n.nodeType===Node.COMMENT_NODE&&n.nodeValue===`vani:end`&&(r=!0),n=n.nextSibling}throw z(`Expected <!--vani:start--> for component #${t}, but none was found. This usually means the server HTML does not match the client component tree.`),Error(`[vani] hydration failed: start anchor not found`)}function V(e,t){let n=e.nextSibling,r=0;for(;n;){if(n.nodeType===Node.COMMENT_NODE){if(n.nodeValue===`vani:start`)r+=1;else if(n.nodeValue===`vani:end`){if(r===0)return L=n.nextSibling,n;--r}}n=n.nextSibling}throw z(`Expected <!--vani:end--> for component #${t}, but none was found. This usually means the server HTML does not match the client component tree.`),Error(`[vani] hydration failed: end anchor not found`)}function H(e,t){let n=[];I=!0,L=t.firstChild,R=0;try{n=h(e,t)}catch(e){console.error(`[vani] hydration failed:`,e)}finally{if(U()&&L){let e=L,t=!1;for(;e;){if(e.nodeType===Node.COMMENT_NODE){let n=e.nodeValue;if(n===`vani:start`||n===`vani:end`){t=!0;break}}e=e.nextSibling}t&&z(`Unused SSR anchors detected after hydration. Some server-rendered DOM was not claimed by the client runtime.`)}I=!1,L=null,R=0}return n}function U(){return`__vaniDevMode`in globalThis?globalThis.__vaniDevMode===!0:import.meta.env?import.meta.env.DEV:typeof process<`u`&&process.env!==void 0?process.env.NODE_ENV===`development`:!1}export{i as a,U as c,M as d,r as f,T as i,E as l,t as n,H as o,w as r,g as s,C as t,h as u};
@@ -1,166 +0,0 @@
1
- //#region src/vani/runtime.d.ts
2
- type SSRNode = {
3
- type: 'element';
4
- tag: string;
5
- props: Record<string, any>;
6
- children: SSRNode[];
7
- } | {
8
- type: 'text';
9
- text: string;
10
- } | {
11
- type: 'comment';
12
- text: string;
13
- } | {
14
- type: 'fragment';
15
- children: SSRNode[];
16
- } | {
17
- type: 'component';
18
- instance: ComponentInstance<any>;
19
- };
20
- type VNode = Node | SSRNode;
21
- interface Handle {
22
- /**
23
- * Schedules a render for the component.
24
- * This triggers a re-render on the next microtask.
25
- */
26
- update(): void;
27
- /**
28
- * Flushes the component render.
29
- * This triggers a re-render immediately.
30
- */
31
- updateSync(): void;
32
- /**
33
- * Disposes the component: removes the component from the DOM and runs all cleanup functions.
34
- */
35
- dispose(): void;
36
- /**
37
- * Adds a cleanup function that is called when the component is disposed.
38
- */
39
- onCleanup(fn: () => void): void;
40
- /**
41
- * This is purely syntatic sugar, as it is basically the same as running the function
42
- * on the setup phase and calling onCleanup to add a cleanup function.
43
- *
44
- * Using effects is necessary in SSR mode, for side effects to not run on the server
45
- * (e.g. timers, subscriptions, DOM usage, etc.)
46
- *
47
- * Runs a side effect function when the component is mounted.
48
- * The returning function may be a cleanup function that is called when the component is disposed.
49
- *
50
- */
51
- effect(fn: () => void | (() => void)): void;
52
- }
53
- type RenderFn = () => VChild;
54
- type Component<Props = any> = (props: Props, handle: Handle) => RenderFn | Promise<RenderFn>;
55
- type ComponentInstance<Props = any> = {
56
- $$vani: 'component';
57
- component: Component<Props>;
58
- props: Props;
59
- /**
60
- * A key is used to identify the component when it is re-rendered.
61
- * If a key is provided, the component will be re-rendered only if the key changes.
62
- */
63
- key?: string | number;
64
- /**
65
- * A ref is used to get a reference to the component instance.
66
- * The ref is set to the component instance when the component is mounted.
67
- * The ref is set to null when the component is disposed.
68
- */
69
- ref?: ComponentRef;
70
- clientOnly?: boolean;
71
- };
72
- type ComponentInput<Props> = Props & {
73
- key?: string | number;
74
- ref?: ComponentRef;
75
- };
76
- type ComponentMetaProps = {
77
- key?: string | number;
78
- ref?: ComponentRef;
79
- fallback?: RenderFn;
80
- clientOnly?: boolean;
81
- };
82
- type VChild = VNode | ComponentInstance<any> | string | number | null | undefined | false;
83
- type DataAttribute = `data-${string}` | `data${Capitalize<string>}`;
84
- type HtmlTagName = keyof HTMLElementTagNameMap;
85
- type SvgTagName = keyof SVGElementTagNameMap;
86
- type ElementTagName = HtmlTagName | SvgTagName;
87
- type ElementByTag<T extends ElementTagName> = T extends HtmlTagName ? HTMLElementTagNameMap[T] : T extends SvgTagName ? SVGElementTagNameMap[T] : Element;
88
- type SvgProps<T extends SvgTagName = SvgTagName> = BaseProps<T> & {
89
- [key: string]: string | number | boolean | undefined | null | ((...args: any[]) => any);
90
- };
91
- type BaseProps<T extends ElementTagName> = {
92
- className?: ClassName;
93
- style?: string;
94
- ref?: DomRef<ElementByTag<T>>;
95
- } & {
96
- [key: DataAttribute]: string | number | boolean | undefined | null;
97
- };
98
- type HtmlProps<T extends HtmlTagName = HtmlTagName> = BaseProps<T> & Partial<Omit<ElementByTag<T>, 'children' | 'className' | 'style'>>;
99
- type ElementProps<T extends ElementTagName> = T extends SvgTagName ? SvgProps<T> : HtmlProps<Extract<T, HtmlTagName>>;
100
- type ClassName = string | undefined | null | {
101
- [key: string]: boolean | undefined | null;
102
- } | ClassName[];
103
- type ComponentRef = {
104
- current: Handle | null;
105
- };
106
- type DomRef<T extends Element = Element> = {
107
- current: T | null;
108
- };
109
- type RenderMode = 'dom' | 'ssr';
110
- declare function component(fn: Component<void>): (props?: ComponentMetaProps) => ComponentInstance<void>;
111
- declare function component<Props>(fn: Component<Props>): (props: Props & ComponentMetaProps) => ComponentInstance<Props>;
112
- declare function withRenderMode<T>(mode: RenderMode, fn: () => T): T;
113
- declare function getRenderMode(): RenderMode;
114
- declare function renderToDOM(components: Array<Component<any> | ComponentInstance<any>>, root: HTMLElement): Handle[];
115
- declare function isComponentInstance(child: VChild): child is ComponentInstance<any>;
116
- declare function classNames(...classes: ClassName[]): string;
117
- declare function el<E extends ElementTagName>(tag: E, props?: ElementProps<E> | VChild | null, ...children: VChild[]): VNode;
118
- declare const fragment: (...children: VChild[]) => {
119
- type: "fragment";
120
- children: SSRNode[];
121
- } | DocumentFragment;
122
- declare function mount<Props>(component: Component<Props>, props: Props): VNode;
123
- /**
124
- * Marks all updates triggered inside the callback as a "transition".
125
- *
126
- * A transition represents non-urgent UI work that can be deferred
127
- * to keep the application responsive.
128
- *
129
- * Updates scheduled inside `startTransition`:
130
- * - do NOT block user interactions
131
- * - are batched separately from urgent updates
132
- * - may be flushed later (e.g. after the current event or during idle time)
133
- *
134
- * Transitions are NOT animations.
135
- * They do not control how updates look, only *when* they are applied.
136
- *
137
- * Typical use cases:
138
- * - Filtering or sorting large lists
139
- * - Rendering expensive subtrees
140
- * - Applying async results that are not immediately visible
141
- *
142
- * Example:
143
- * ```ts
144
- * button({
145
- * onclick: () => {
146
- * // urgent update
147
- * setOpen(true)
148
- * handle.update()
149
- *
150
- * // non-urgent update
151
- * startTransition(() => {
152
- * setItems(filter(items))
153
- * handle.update()
154
- * })
155
- * },
156
- * })
157
- * ```
158
- *
159
- * If multiple transitions are triggered, they are automatically batched.
160
- * Transition updates never interrupt urgent updates.
161
- */
162
- declare function startTransition(fn: () => void): void;
163
- declare function hydrateToDOM(components: Array<Component<any> | ComponentInstance<any>>, root: HTMLElement): Handle[];
164
- declare function isDevMode(): boolean;
165
- //#endregion
166
- export { isDevMode as C, withRenderMode as D, startTransition as E, isComponentInstance as S, renderToDOM as T, component as _, ComponentRef as a, getRenderMode as b, ElementProps as c, RenderFn as d, SSRNode as f, classNames as g, VNode as h, ComponentInstance as i, Handle as l, VChild as m, Component as n, DataAttribute as o, SvgProps as p, ComponentInput as r, DomRef as s, ClassName as t, HtmlProps as u, el as v, mount as w, hydrateToDOM as x, fragment as y };