@ryupold/vode 0.11.0 → 0.12.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.
Files changed (3) hide show
  1. package/package.json +1 -1
  2. package/src/vode.ts +21 -29
  3. package/vode.mjs +20 -21
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ryupold/vode",
3
- "version": "0.11.0",
3
+ "version": "0.12.1",
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: {
@@ -86,9 +85,7 @@ export type ContainerNode<S> = HTMLElement & {
86
85
  liveEffectCount: number,
87
86
  renderPatchCount: number,
88
87
  renderCount: number,
89
- renderTime: number,
90
- queueLengthBeforeRender: number,
91
- queueLengthAfterRender: number,
88
+ lastRenderTime: number,
92
89
  },
93
90
  }
94
91
  };
@@ -97,7 +94,7 @@ export type ContainerNode<S> = HTMLElement & {
97
94
  export function createState<S extends object | unknown>(state: S): PatchableState<S> { return state as PatchableState<S>; }
98
95
 
99
96
  /** 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; }
97
+ export function createPatch<S extends object | unknown>(p: DeepPartial<S> | Effect<S> | NoRenderPatch): typeof p { return p; }
101
98
 
102
99
  /** type-safe way to create a vode. useful for type inference and autocompletion.
103
100
  *
@@ -107,13 +104,10 @@ export function createPatch<S extends object | unknown>(p: DeepPartial<S> | Effe
107
104
  * - identity: `vode(["div", ["span", "bar"]])` => `["div", ["span", "bar"]]` --*rendered*-> `<div><span>bar</span></div>`
108
105
  */
109
106
  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];
107
+ if(!tag) throw new Error("tag must be a string or vode");
108
+ if (Array.isArray(tag)) return tag;
109
+ else if (props) return [tag, props as Props<S>, ...children];
110
+ else return [tag, ...children];
117
111
  }
118
112
 
119
113
  /** create a vode app inside a container element
@@ -124,8 +118,12 @@ export function vode<S extends object | unknown>(tag: Tag | Vode<S>, props?: Pro
124
118
  * @returns a patch function that can be used to update the state
125
119
  */
126
120
  export function app<S extends object | unknown>(container: HTMLElement, initialState: Omit<S, "patch">, dom: Component<S>, ...initialPatches: Patch<S>[]) {
121
+ if (!container) throw new Error("container must be a valid HTMLElement");
122
+ if (!initialState || typeof initialState !== "object") throw new Error("initialState must be an object");
123
+ if (typeof dom !== "function") throw new Error("dom must be a function that returns a vode");
124
+
127
125
  const _vode = {} as ContainerNode<S>["_vode"];
128
- _vode.stats = { renderTime: 0, renderCount: 0, queueLengthBeforeRender: 0, queueLengthAfterRender: 0, liveEffectCount: 0, patchCount: 0, renderPatchCount: 0 };
126
+ _vode.stats = { lastRenderTime: 0, renderCount: 0, liveEffectCount: 0, patchCount: 0, renderPatchCount: 0 };
129
127
 
130
128
  Object.defineProperty(initialState, "patch", {
131
129
  enumerable: false, configurable: true,
@@ -171,7 +169,7 @@ export function app<S extends object | unknown>(container: HTMLElement, initialS
171
169
  _vode.patch!((<EffectFunction<S>>action)(_vode.state));
172
170
  } else {
173
171
  _vode.stats.renderPatchCount++;
174
- _vode.q!.push(<Patch<S>>action);
172
+ _vode.q = mergeState(_vode.q || {}, action);
175
173
  if (!_vode.isRendering) _vode.render!();
176
174
  }
177
175
  }
@@ -180,25 +178,19 @@ export function app<S extends object | unknown>(container: HTMLElement, initialS
180
178
  Object.defineProperty(_vode, "render", {
181
179
  enumerable: false, configurable: true,
182
180
  writable: false, value: () => requestAnimationFrame(() => {
183
- if (_vode.isRendering || _vode.q!.length === 0) return;
181
+ if (_vode.isRendering || !_vode.q) return;
184
182
  _vode.isRendering = true;
185
183
  const sw = Date.now();
186
184
  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
- }
185
+ _vode.state = mergeState(_vode.state, _vode.q);
186
+ _vode.q = null;
194
187
  _vode.vode = render(_vode.state, _vode.patch, container, 0, _vode.vode, dom(_vode.state))!;
195
188
  } finally {
196
189
  _vode.isRendering = false;
197
190
  _vode.stats.renderCount++;
198
- _vode.stats.renderTime = Date.now() - sw;
199
- _vode.stats.queueLengthAfterRender = _vode.q!.length;
200
- if (_vode.q!.length > 0) {
201
- _vode.render!();
191
+ _vode.stats.lastRenderTime = Date.now() - sw;
192
+ if (_vode.q) {
193
+ _vode.render();
202
194
  }
203
195
  }
204
196
  })
@@ -206,7 +198,7 @@ export function app<S extends object | unknown>(container: HTMLElement, initialS
206
198
 
207
199
  _vode.patch = (<PatchableState<S>>initialState).patch;
208
200
  _vode.state = <PatchableState<S>>initialState;
209
- _vode.q = [];
201
+ _vode.q = null;
210
202
 
211
203
  const root = container as ContainerNode<S>;
212
204
  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,17 +6,24 @@ 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
- _vode.stats = { renderTime: 0, renderCount: 0, queueLengthBeforeRender: 0, queueLengthAfterRender: 0, liveEffectCount: 0, patchCount: 0, renderPatchCount: 0 };
26
+ _vode.stats = { lastRenderTime: 0, renderCount: 0, liveEffectCount: 0, patchCount: 0, renderPatchCount: 0 };
21
27
  Object.defineProperty(initialState, "patch", {
22
28
  enumerable: false,
23
29
  configurable: true,
@@ -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
- _vode.stats.renderTime = Date.now() - sw;
96
- _vode.stats.queueLengthAfterRender = _vode.q.length;
97
- if (_vode.q.length > 0) {
96
+ _vode.stats.lastRenderTime = Date.now() - sw;
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,