@ryupold/vode 1.8.8 → 1.8.11
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/.github/workflows/publish.yml +5 -0
- package/.github/workflows/tests.yml +1 -0
- package/README.md +36 -2
- package/dist/vode.cjs.min.js +1 -1
- package/dist/vode.d.ts +5 -6
- package/dist/vode.es5.min.js +1 -1
- package/dist/vode.js +54 -44
- package/dist/vode.min.js +1 -1
- package/dist/vode.min.mjs +1 -1
- package/dist/vode.mjs +54 -44
- package/dist/vode.tests.mjs +5475 -0
- package/log.txt +1 -0
- package/package.json +5 -5
- package/src/vode.ts +63 -52
- package/test/helper.ts +299 -146
- package/test/index.ts +2 -64
- package/test/mocks.ts +83 -9
- package/test/run-tests.ts +61 -0
- package/test/tests-app.ts +48 -48
- package/test/tests-catch.ts +15 -15
- package/test/tests-children.ts +31 -31
- package/test/tests-createPatch.ts +12 -12
- package/test/tests-createState.ts +11 -11
- package/test/tests-defuse.ts +18 -18
- package/test/tests-examples.ts +87 -88
- package/test/tests-hydrate.ts +28 -28
- package/test/tests-memo.ts +29 -28
- package/test/tests-mergeClass.ts +31 -31
- package/test/tests-mergeProps.ts +19 -19
- package/test/tests-mergeStyle.ts +28 -14
- package/test/tests-mount-unmount.ts +368 -268
- package/test/tests-patch-advanced.ts +127 -19
- package/test/tests-patch-merge.ts +15 -15
- package/test/tests-props.ts +15 -15
- package/test/tests-state-context.ts +33 -33
- package/test/tests-tag.ts +14 -14
- package/test/tests-vode.ts +6 -6
package/log.txt
CHANGED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
npm warn gitignore-fallback No .npmignore file found, using .gitignore for file exclusion. Consider creating a .npmignore file to explicitly control published files.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ryupold/vode",
|
|
3
|
-
"version": "1.8.
|
|
3
|
+
"version": "1.8.11",
|
|
4
4
|
"description": "a minimalist web framework",
|
|
5
5
|
"author": "Michael Scherbakow (ryupold)",
|
|
6
6
|
"license": "MIT",
|
|
@@ -25,11 +25,10 @@
|
|
|
25
25
|
},
|
|
26
26
|
"homepage": "https://github.com/ryupold/vode#readme",
|
|
27
27
|
"type": "module",
|
|
28
|
-
"main": "./dist/vode.mjs",
|
|
29
|
-
"types": "./dist/vode.d.ts",
|
|
30
28
|
"exports": {
|
|
31
29
|
".": {
|
|
32
30
|
"types": "./dist/vode.d.ts",
|
|
31
|
+
"tests": "./dist/vode.tests.mjs",
|
|
33
32
|
"import": "./dist/vode.min.mjs",
|
|
34
33
|
"require": "./dist/vode.cjs.min.js",
|
|
35
34
|
"default": "./dist/vode.min.js"
|
|
@@ -43,11 +42,12 @@
|
|
|
43
42
|
"build-classic-min": "esbuild index.ts --outfile=dist/vode.min.js --bundle --format=iife --global-name=V --minify",
|
|
44
43
|
"babel": "npx babel dist/vode.mjs --out-file dist/vode.cjs.min.js",
|
|
45
44
|
"babel-classic": "npx babel dist/vode.js --out-file dist/vode.es5.min.js",
|
|
46
|
-
"release": "npm run build && npm run build-min && npm run build-classic && npm run build-classic-min && npm run babel && npm run babel-classic && npm run types",
|
|
45
|
+
"release": "npm run build && npm run build-min && npm run build-classic && npm run build-classic-min && npm run babel && npm run babel-classic && npm run types && npm run build-tests",
|
|
47
46
|
"publish": "npm publish --access public",
|
|
48
47
|
"clean": "tsc -b --clean && rm dist/*",
|
|
49
48
|
"watch": "tsc -b -w",
|
|
50
|
-
"test": "tsc -p tsconfig.test.json && esbuild test/
|
|
49
|
+
"test": "tsc -p tsconfig.test.json && esbuild test/run-tests.ts --bundle --outfile=test/run-tests.js --platform=node && node test/run-tests.js",
|
|
50
|
+
"build-tests": "esbuild test/index.ts --bundle --format=esm --outfile=dist/vode.tests.mjs"
|
|
51
51
|
},
|
|
52
52
|
"devDependencies": {
|
|
53
53
|
"@babel/cli": "7.28.6",
|
package/src/vode.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export type Vode<S = PatchableState> = FullVode<S> | JustTagVode | NoPropsVode<S>;
|
|
2
|
-
export type FullVode<S = PatchableState> = [tag: Tag, props: Props<S>, ...children: ChildVode<S>[]];
|
|
2
|
+
export type FullVode<S = PatchableState> = [tag: Tag, props: Props<S> | ChildVode<S>, ...children: ChildVode<S>[]];
|
|
3
3
|
export type NoPropsVode<S = PatchableState> = [tag: Tag, ...children: ChildVode<S>[]] | (TextVode[]);
|
|
4
4
|
export type JustTagVode = [tag: Tag];
|
|
5
5
|
export type ChildVode<S = PatchableState> = Vode<S> | TextVode | NoVode | Component<S>;
|
|
@@ -37,9 +37,9 @@ export interface Props<S = PatchableState> extends Partial<
|
|
|
37
37
|
class?: ClassProp,
|
|
38
38
|
style?: StyleProp,
|
|
39
39
|
/** called after the element was attached */
|
|
40
|
-
onMount?: MountFunction<S
|
|
40
|
+
onMount?: MountFunction<S> | null | false,
|
|
41
41
|
/** called before the element is detached */
|
|
42
|
-
onUnmount?: MountFunction<S
|
|
42
|
+
onUnmount?: MountFunction<S> | null | false,
|
|
43
43
|
/** used instead of original vode when an error occurs during rendering */
|
|
44
44
|
catch?: ((s: S, error: Error) => ChildVode<S>) | ChildVode<S>;
|
|
45
45
|
};
|
|
@@ -72,7 +72,7 @@ export type PropertyValue<S> =
|
|
|
72
72
|
| StyleProp | ClassProp
|
|
73
73
|
| Patch<S>;
|
|
74
74
|
|
|
75
|
-
export type Dispatch<S> = (action: Patch<S>) => void
|
|
75
|
+
export type Dispatch<S> = (action: Patch<S>) => void | Promise<void>;
|
|
76
76
|
export interface Patchable<S = object> { patch: Dispatch<S>; }
|
|
77
77
|
export type PatchableState<S = object> = S & Patchable<S>;
|
|
78
78
|
|
|
@@ -93,9 +93,8 @@ export interface ContainerNode<S = PatchableState> extends HTMLElement {
|
|
|
93
93
|
renderAsync: () => Promise<unknown>,
|
|
94
94
|
syncRenderer: (cb: () => void) => void,
|
|
95
95
|
asyncRenderer: ((cb: () => void) => ViewTransition) | null | undefined,
|
|
96
|
-
qSync: {} | undefined | null, // next patch aggregate to be applied
|
|
97
96
|
qAsync: {} | undefined | null, // next render-patches to be animated after another
|
|
98
|
-
isRendering:
|
|
97
|
+
isRendering: number,
|
|
99
98
|
isAnimating: boolean,
|
|
100
99
|
/** stats about the overall patches & last render time */
|
|
101
100
|
stats: {
|
|
@@ -146,77 +145,88 @@ export function app<S extends PatchableState = PatchableState>(
|
|
|
146
145
|
const _vode = {} as ContainerNode<S>["_vode"];
|
|
147
146
|
_vode.syncRenderer = globals.requestAnimationFrame;
|
|
148
147
|
_vode.asyncRenderer = globals.startViewTransition;
|
|
149
|
-
_vode.
|
|
148
|
+
_vode.isRendering = 0;
|
|
150
149
|
_vode.qAsync = null;
|
|
151
150
|
_vode.stats = { lastSyncRenderTime: 0, lastAsyncRenderTime: 0, syncRenderCount: 0, asyncRenderCount: 0, liveEffectCount: 0, patchCount: 0, syncRenderPatchCount: 0, asyncRenderPatchCount: 0 };
|
|
152
151
|
|
|
153
|
-
const patchableState = state as PatchableState<S> & { patch: (action: Patch<S>, animate?: boolean) => void };
|
|
152
|
+
const patchableState = state as PatchableState<S> & { patch: (action: Patch<S>, animate?: boolean) => void | Promise<void> };
|
|
154
153
|
|
|
155
154
|
if ("patch" in state && typeof state.patch === "function" && Array.isArray((state as any).patch.initialPatches)) {
|
|
156
155
|
initialPatches = [...(state as any).patch.initialPatches, ...initialPatches];
|
|
157
156
|
}
|
|
158
157
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
158
|
+
async function promisePatch(action: Promise<Patch<S>>, isAnimated?: boolean) {
|
|
159
|
+
_vode.stats.liveEffectCount++;
|
|
160
|
+
try {
|
|
161
|
+
const resolvedPatch = await (action as Promise<S>);
|
|
162
|
+
await patchableState.patch(<Patch<S>>resolvedPatch, isAnimated);
|
|
163
|
+
} finally {
|
|
164
|
+
_vode.stats.liveEffectCount--;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
164
167
|
|
|
165
|
-
|
|
166
|
-
|
|
168
|
+
async function generatorPatch(action: AsyncGenerator<Patch<S>>, isAnimated?: boolean) {
|
|
169
|
+
const generator = action as AsyncGenerator<Patch<S>>;
|
|
170
|
+
_vode.stats.liveEffectCount++;
|
|
171
|
+
try {
|
|
172
|
+
let v = await generator.next();
|
|
173
|
+
while (v.done === false) {
|
|
167
174
|
_vode.stats.liveEffectCount++;
|
|
168
175
|
try {
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
_vode.stats.liveEffectCount++;
|
|
172
|
-
try {
|
|
173
|
-
patchableState.patch(v.value, isAsync);
|
|
174
|
-
v = await generator.next();
|
|
175
|
-
} finally {
|
|
176
|
-
_vode.stats.liveEffectCount--;
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
patchableState.patch(v.value as Patch<S>, isAsync);
|
|
176
|
+
await patchableState.patch(v.value, isAnimated);
|
|
177
|
+
v = await generator.next();
|
|
180
178
|
} finally {
|
|
181
179
|
_vode.stats.liveEffectCount--;
|
|
182
180
|
}
|
|
181
|
+
}
|
|
182
|
+
await patchableState.patch(v.value as Patch<S>, isAnimated);
|
|
183
|
+
} finally {
|
|
184
|
+
_vode.stats.liveEffectCount--;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
Object.defineProperty(state, "patch", {
|
|
189
|
+
enumerable: false, configurable: true,
|
|
190
|
+
writable: false, value: (action: Patch<S>, isAnimated?: boolean): void | Promise<void> => {
|
|
191
|
+
while (typeof action === "function") {
|
|
192
|
+
action = (<(s: S) => unknown>action)(_vode.state);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
if (!action || typeof action !== "object") return;
|
|
196
|
+
|
|
197
|
+
_vode.stats.patchCount++;
|
|
198
|
+
|
|
199
|
+
if ((action as AsyncGenerator<Patch<S>>)?.next) {
|
|
200
|
+
return generatorPatch(action as AsyncGenerator<Patch<S>>, isAnimated);
|
|
183
201
|
} else if ((action as Promise<S>).then) {
|
|
184
|
-
|
|
185
|
-
try {
|
|
186
|
-
const resolvedPatch = await (action as Promise<S>);
|
|
187
|
-
patchableState.patch(<Patch<S>>resolvedPatch, isAsync);
|
|
188
|
-
} finally {
|
|
189
|
-
_vode.stats.liveEffectCount--;
|
|
190
|
-
}
|
|
202
|
+
return promisePatch(action as Promise<S>, isAnimated);
|
|
191
203
|
} else if (Array.isArray(action)) {
|
|
192
204
|
if (action.length > 0) {
|
|
193
205
|
for (const p of action) {
|
|
194
206
|
patchableState.patch(p, !document.hidden && !!_vode.asyncRenderer);
|
|
195
207
|
}
|
|
196
208
|
} else { //when [] is patched: 1. skip current animation 2. merge all queued async patches into synced queue
|
|
197
|
-
|
|
209
|
+
mergeState(_vode.state, _vode.qAsync, true);
|
|
198
210
|
_vode.qAsync = null;
|
|
199
211
|
try { globals.currentViewTransition?.skipTransition(); } catch { }
|
|
200
212
|
_vode.stats.syncRenderPatchCount++;
|
|
201
213
|
_vode.renderSync();
|
|
202
214
|
}
|
|
203
|
-
} else if (typeof action === "function") {
|
|
204
|
-
patchableState.patch((<(s: S) => unknown>action)(_vode.state), isAsync);
|
|
205
215
|
} else {
|
|
206
|
-
if (
|
|
216
|
+
if (isAnimated) {
|
|
207
217
|
_vode.stats.asyncRenderPatchCount++;
|
|
208
218
|
_vode.qAsync = mergeState(_vode.qAsync || {}, action, false);
|
|
209
|
-
|
|
219
|
+
_vode.renderAsync();
|
|
210
220
|
} else {
|
|
211
221
|
_vode.stats.syncRenderPatchCount++;
|
|
212
|
-
|
|
222
|
+
mergeState(_vode.state, action, true);
|
|
213
223
|
_vode.renderSync();
|
|
214
224
|
}
|
|
215
225
|
}
|
|
216
226
|
}
|
|
217
227
|
});
|
|
218
228
|
|
|
219
|
-
function renderDom(
|
|
229
|
+
function renderDom(isAnimated: boolean) {
|
|
220
230
|
const sw = performance.now();
|
|
221
231
|
const vom = dom(_vode.state);
|
|
222
232
|
_vode.vode = render<S>(_vode.state, container.parentElement as Element, 0, 0, _vode.vode, vom)!;
|
|
@@ -226,11 +236,13 @@ export function app<S extends PatchableState = PatchableState>(
|
|
|
226
236
|
(<ContainerNode<S>>container)._vode = _vode
|
|
227
237
|
}
|
|
228
238
|
|
|
229
|
-
if (!
|
|
239
|
+
if (!isAnimated) {
|
|
230
240
|
_vode.stats.lastSyncRenderTime = performance.now() - sw;
|
|
241
|
+
const changesSinceRender = _vode.isRendering !== _vode.stats.syncRenderPatchCount;
|
|
231
242
|
_vode.stats.syncRenderCount++;
|
|
232
|
-
_vode.isRendering =
|
|
233
|
-
if (
|
|
243
|
+
_vode.isRendering = 0;
|
|
244
|
+
if (changesSinceRender)
|
|
245
|
+
_vode.renderSync();
|
|
234
246
|
}
|
|
235
247
|
}
|
|
236
248
|
const sr = renderDom.bind(null, false);
|
|
@@ -239,13 +251,9 @@ export function app<S extends PatchableState = PatchableState>(
|
|
|
239
251
|
Object.defineProperty(_vode, "renderSync", {
|
|
240
252
|
enumerable: false, configurable: true,
|
|
241
253
|
writable: false, value: () => {
|
|
242
|
-
if (_vode.isRendering
|
|
243
|
-
|
|
244
|
-
_vode.isRendering = true;
|
|
245
|
-
|
|
246
|
-
_vode.state = mergeState(_vode.state, _vode.qSync, true);
|
|
247
|
-
_vode.qSync = null;
|
|
254
|
+
if (_vode.isRendering) return;
|
|
248
255
|
|
|
256
|
+
_vode.isRendering = _vode.stats.syncRenderPatchCount;
|
|
249
257
|
_vode.syncRenderer(sr);
|
|
250
258
|
}
|
|
251
259
|
});
|
|
@@ -281,7 +289,8 @@ export function app<S extends PatchableState = PatchableState>(
|
|
|
281
289
|
root._vode = _vode;
|
|
282
290
|
const indexInParent = Array.from(container.parentElement.children).indexOf(container);
|
|
283
291
|
|
|
284
|
-
|
|
292
|
+
const patchCountBefore = _vode.stats.syncRenderPatchCount;
|
|
293
|
+
_vode.isRendering = _vode.stats.syncRenderPatchCount;
|
|
285
294
|
_vode.vode = render(
|
|
286
295
|
<S>state,
|
|
287
296
|
container.parentElement,
|
|
@@ -290,8 +299,10 @@ export function app<S extends PatchableState = PatchableState>(
|
|
|
290
299
|
hydrate<S>(container, true) as AttachedVode<S>,
|
|
291
300
|
dom(<S>state)
|
|
292
301
|
)!;
|
|
293
|
-
_vode.
|
|
294
|
-
|
|
302
|
+
const continueRendering = _vode.stats.syncRenderPatchCount !== patchCountBefore;
|
|
303
|
+
_vode.isRendering = 0;
|
|
304
|
+
_vode.stats.syncRenderCount++;
|
|
305
|
+
if (continueRendering) _vode.renderSync();
|
|
295
306
|
|
|
296
307
|
for (const effect of initialPatches) {
|
|
297
308
|
patchableState.patch(effect);
|