@ryupold/vode 0.11.0 → 0.12.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.
Files changed (3) hide show
  1. package/package.json +1 -1
  2. package/src/vode.ts +18 -24
  3. package/vode.mjs +18 -19
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ryupold/vode",
3
- "version": "0.11.0",
3
+ "version": "0.12.0",
4
4
  "description": "Small web framework for minimal websites",
5
5
  "author": "Michael Scherbakow (ryupold)",
6
6
  "license": "MIT",
package/src/vode.ts CHANGED
@@ -11,10 +11,9 @@ export type Component<S> = (s: S) => ChildVode<S>;
11
11
 
12
12
  export type Patch<S> =
13
13
  | NoRenderPatch // ignored
14
- | typeof EmptyPatch | DeepPartial<S> // render patches
14
+ | {} | DeepPartial<S> // render patches
15
15
  | Promise<Patch<S>> | Effect<S>; // effects resulting in patches
16
16
 
17
- export const EmptyPatch = {} as const; // smallest patch to cause a render without any changes
18
17
  export type NoRenderPatch = undefined | null | number | boolean | bigint | string | symbol | void;
19
18
 
20
19
  export type DeepPartial<S> = { [P in keyof S]?: S[P] extends Array<infer I> ? Array<Patch<I>> : Patch<S[P]> };
@@ -78,7 +77,7 @@ export type ContainerNode<S> = HTMLElement & {
78
77
  vode: AttachedVode<S>, //don't touch this
79
78
  patch: Dispatch<S>, // can't touch this
80
79
  render: () => void, // can't touch this
81
- q: Patch<S>[], // this will change in the future, so don't touch it
80
+ q: object | null, // next patch aggregate to be applied
82
81
  isRendering: boolean, // under no circumstances touch this
83
82
  /** stats about the overall patches & last render time */
84
83
  stats: {
@@ -97,7 +96,7 @@ export type ContainerNode<S> = HTMLElement & {
97
96
  export function createState<S extends object | unknown>(state: S): PatchableState<S> { return state as PatchableState<S>; }
98
97
 
99
98
  /** type safe way to create a patch. useful for type inference and autocompletion. */
100
- export function createPatch<S extends object | unknown>(p: DeepPartial<S> | Effect<S> | NoRenderPatch): Patch<S> { return p; }
99
+ export function createPatch<S extends object | unknown>(p: DeepPartial<S> | Effect<S> | NoRenderPatch): typeof p { return p; }
101
100
 
102
101
  /** type-safe way to create a vode. useful for type inference and autocompletion.
103
102
  *
@@ -107,13 +106,10 @@ export function createPatch<S extends object | unknown>(p: DeepPartial<S> | Effe
107
106
  * - identity: `vode(["div", ["span", "bar"]])` => `["div", ["span", "bar"]]` --*rendered*-> `<div><span>bar</span></div>`
108
107
  */
109
108
  export function vode<S extends object | unknown>(tag: Tag | Vode<S>, props?: Props<S> | ChildVode<S>, ...children: ChildVode<S>[]): Vode<S> {
110
- if (Array.isArray(tag)) {
111
- return tag;
112
- }
113
- if (props) {
114
- return [tag, props as Props<S>, ...children];
115
- }
116
- return [tag, ...children];
109
+ if(!tag) throw new Error("tag must be a string or vode");
110
+ if (Array.isArray(tag)) return tag;
111
+ else if (props) return [tag, props as Props<S>, ...children];
112
+ else return [tag, ...children];
117
113
  }
118
114
 
119
115
  /** create a vode app inside a container element
@@ -124,6 +120,10 @@ export function vode<S extends object | unknown>(tag: Tag | Vode<S>, props?: Pro
124
120
  * @returns a patch function that can be used to update the state
125
121
  */
126
122
  export function app<S extends object | unknown>(container: HTMLElement, initialState: Omit<S, "patch">, dom: Component<S>, ...initialPatches: Patch<S>[]) {
123
+ if (!container) throw new Error("container must be a valid HTMLElement");
124
+ if (!initialState || typeof initialState !== "object") throw new Error("initialState must be an object");
125
+ if (typeof dom !== "function") throw new Error("dom must be a function that returns a vode");
126
+
127
127
  const _vode = {} as ContainerNode<S>["_vode"];
128
128
  _vode.stats = { renderTime: 0, renderCount: 0, queueLengthBeforeRender: 0, queueLengthAfterRender: 0, liveEffectCount: 0, patchCount: 0, renderPatchCount: 0 };
129
129
 
@@ -171,7 +171,7 @@ export function app<S extends object | unknown>(container: HTMLElement, initialS
171
171
  _vode.patch!((<EffectFunction<S>>action)(_vode.state));
172
172
  } else {
173
173
  _vode.stats.renderPatchCount++;
174
- _vode.q!.push(<Patch<S>>action);
174
+ _vode.q = mergeState(_vode.q || {}, action);
175
175
  if (!_vode.isRendering) _vode.render!();
176
176
  }
177
177
  }
@@ -180,25 +180,19 @@ export function app<S extends object | unknown>(container: HTMLElement, initialS
180
180
  Object.defineProperty(_vode, "render", {
181
181
  enumerable: false, configurable: true,
182
182
  writable: false, value: () => requestAnimationFrame(() => {
183
- if (_vode.isRendering || _vode.q!.length === 0) return;
183
+ if (_vode.isRendering || !_vode.q) return;
184
184
  _vode.isRendering = true;
185
185
  const sw = Date.now();
186
186
  try {
187
- _vode.stats.queueLengthBeforeRender = _vode.q!.length;
188
-
189
- while (_vode.q!.length > 0) {
190
- const patch = _vode.q!.shift();
191
- if (patch === EmptyPatch) continue;
192
- mergeState(_vode.state, patch);
193
- }
187
+ _vode.state = mergeState(_vode.state, _vode.q);
188
+ _vode.q = null;
194
189
  _vode.vode = render(_vode.state, _vode.patch, container, 0, _vode.vode, dom(_vode.state))!;
195
190
  } finally {
196
191
  _vode.isRendering = false;
197
192
  _vode.stats.renderCount++;
198
193
  _vode.stats.renderTime = Date.now() - sw;
199
- _vode.stats.queueLengthAfterRender = _vode.q!.length;
200
- if (_vode.q!.length > 0) {
201
- _vode.render!();
194
+ if (_vode.q) {
195
+ _vode.render();
202
196
  }
203
197
  }
204
198
  })
@@ -206,7 +200,7 @@ export function app<S extends object | unknown>(container: HTMLElement, initialS
206
200
 
207
201
  _vode.patch = (<PatchableState<S>>initialState).patch;
208
202
  _vode.state = <PatchableState<S>>initialState;
209
- _vode.q = [];
203
+ _vode.q = null;
210
204
 
211
205
  const root = container as ContainerNode<S>;
212
206
  root._vode = _vode;
package/vode.mjs CHANGED
@@ -1,5 +1,4 @@
1
1
  // src/vode.ts
2
- var EmptyPatch = {};
3
2
  function createState(state) {
4
3
  return state;
5
4
  }
@@ -7,15 +6,22 @@ function createPatch(p) {
7
6
  return p;
8
7
  }
9
8
  function vode(tag, props, ...children) {
10
- if (Array.isArray(tag)) {
9
+ if (!tag)
10
+ throw new Error("tag must be a string or vode");
11
+ if (Array.isArray(tag))
11
12
  return tag;
12
- }
13
- if (props) {
13
+ else if (props)
14
14
  return [tag, props, ...children];
15
- }
16
- return [tag, ...children];
15
+ else
16
+ return [tag, ...children];
17
17
  }
18
18
  function app(container, initialState, dom, ...initialPatches) {
19
+ if (!container)
20
+ throw new Error("container must be a valid HTMLElement");
21
+ if (!initialState || typeof initialState !== "object")
22
+ throw new Error("initialState must be an object");
23
+ if (typeof dom !== "function")
24
+ throw new Error("dom must be a function that returns a vode");
19
25
  const _vode = {};
20
26
  _vode.stats = { renderTime: 0, renderCount: 0, queueLengthBeforeRender: 0, queueLengthAfterRender: 0, liveEffectCount: 0, patchCount: 0, renderPatchCount: 0 };
21
27
  Object.defineProperty(initialState, "patch", {
@@ -65,7 +71,7 @@ function app(container, initialState, dom, ...initialPatches) {
65
71
  _vode.patch(action(_vode.state));
66
72
  } else {
67
73
  _vode.stats.renderPatchCount++;
68
- _vode.q.push(action);
74
+ _vode.q = mergeState(_vode.q || {}, action);
69
75
  if (!_vode.isRendering)
70
76
  _vode.render();
71
77
  }
@@ -76,25 +82,19 @@ function app(container, initialState, dom, ...initialPatches) {
76
82
  configurable: true,
77
83
  writable: false,
78
84
  value: () => requestAnimationFrame(() => {
79
- if (_vode.isRendering || _vode.q.length === 0)
85
+ if (_vode.isRendering || !_vode.q)
80
86
  return;
81
87
  _vode.isRendering = true;
82
88
  const sw = Date.now();
83
89
  try {
84
- _vode.stats.queueLengthBeforeRender = _vode.q.length;
85
- while (_vode.q.length > 0) {
86
- const patch = _vode.q.shift();
87
- if (patch === EmptyPatch)
88
- continue;
89
- mergeState(_vode.state, patch);
90
- }
90
+ _vode.state = mergeState(_vode.state, _vode.q);
91
+ _vode.q = null;
91
92
  _vode.vode = render(_vode.state, _vode.patch, container, 0, _vode.vode, dom(_vode.state));
92
93
  } finally {
93
94
  _vode.isRendering = false;
94
95
  _vode.stats.renderCount++;
95
96
  _vode.stats.renderTime = Date.now() - sw;
96
- _vode.stats.queueLengthAfterRender = _vode.q.length;
97
- if (_vode.q.length > 0) {
97
+ if (_vode.q) {
98
98
  _vode.render();
99
99
  }
100
100
  }
@@ -102,7 +102,7 @@ function app(container, initialState, dom, ...initialPatches) {
102
102
  });
103
103
  _vode.patch = initialState.patch;
104
104
  _vode.state = initialState;
105
- _vode.q = [];
105
+ _vode.q = null;
106
106
  const root = container;
107
107
  root._vode = _vode;
108
108
  const initialVode = dom(initialState);
@@ -894,7 +894,6 @@ export {
894
894
  FECOMPONENTTRANSFER,
895
895
  FECOLORMATRIX,
896
896
  FEBLEND,
897
- EmptyPatch,
898
897
  EMBED,
899
898
  EM,
900
899
  ELLIPSE,