@lynx-js/react 0.110.1 → 0.111.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/CHANGELOG.md +44 -0
- package/components/lib/DeferredListItem.d.ts +7 -0
- package/components/lib/DeferredListItem.jsx +40 -0
- package/components/lib/DeferredListItem.jsx.map +1 -0
- package/components/lib/index.d.ts +1 -0
- package/components/lib/index.js +1 -0
- package/components/lib/index.js.map +1 -1
- package/components/src/DeferredListItem.tsx +56 -0
- package/components/src/index.ts +1 -0
- package/package.json +1 -1
- package/refresh/.turbo/turbo-build.log +1 -1
- package/runtime/lazy/react-lepus.js +1 -0
- package/runtime/lazy/react.js +1 -0
- package/runtime/lepus/index.d.ts +1 -1
- package/runtime/lepus/index.js +44 -0
- package/runtime/lib/backgroundSnapshot.d.ts +2 -1
- package/runtime/lib/backgroundSnapshot.js +62 -40
- package/runtime/lib/backgroundSnapshot.js.map +1 -1
- package/runtime/lib/compat/initData.js +10 -0
- package/runtime/lib/compat/initData.js.map +1 -1
- package/runtime/lib/index.d.ts +2 -2
- package/runtime/lib/index.js +2 -2
- package/runtime/lib/index.js.map +1 -1
- package/runtime/lib/lifecycle/patch/commit.js +5 -5
- package/runtime/lib/lifecycle/patch/commit.js.map +1 -1
- package/runtime/lib/lifecycle/patch/snapshotPatch.d.ts +9 -9
- package/runtime/lib/lifecycle/patch/snapshotPatch.js +9 -10
- package/runtime/lib/lifecycle/patch/snapshotPatch.js.map +1 -1
- package/runtime/lib/lifecycle/patch/updateMainThread.js +7 -8
- package/runtime/lib/lifecycle/patch/updateMainThread.js.map +1 -1
- package/runtime/lib/lifecycleConstant.d.ts +2 -1
- package/runtime/lib/lifecycleConstant.js +1 -0
- package/runtime/lib/lifecycleConstant.js.map +1 -1
- package/runtime/lib/list.js +102 -12
- package/runtime/lib/list.js.map +1 -1
- package/runtime/lib/lynx/calledByNative.js +6 -9
- package/runtime/lib/lynx/calledByNative.js.map +1 -1
- package/runtime/lib/lynx/component.js +11 -14
- package/runtime/lib/lynx/component.js.map +1 -1
- package/runtime/lib/lynx/env.js +1 -2
- package/runtime/lib/lynx/env.js.map +1 -1
- package/runtime/lib/lynx/lazy-bundle.js +48 -21
- package/runtime/lib/lynx/lazy-bundle.js.map +1 -1
- package/runtime/lib/lynx/performance.d.ts +3 -19
- package/runtime/lib/lynx/performance.js +25 -26
- package/runtime/lib/lynx/performance.js.map +1 -1
- package/runtime/lib/lynx/tt.js +10 -5
- package/runtime/lib/lynx/tt.js.map +1 -1
- package/runtime/lib/lynx-api.d.ts +78 -1
- package/runtime/lib/lynx-api.js.map +1 -1
- package/runtime/lib/snapshot.d.ts +2 -0
- package/runtime/lib/snapshot.js +19 -5
- package/runtime/lib/snapshot.js.map +1 -1
- package/runtime/lib/utils.d.ts +1 -0
- package/runtime/lib/utils.js +6 -0
- package/runtime/lib/utils.js.map +1 -1
- package/runtime/src/backgroundSnapshot.ts +74 -55
- package/runtime/src/compat/initData.ts +10 -0
- package/runtime/src/index.ts +2 -0
- package/runtime/src/lifecycle/patch/commit.ts +5 -11
- package/runtime/src/lifecycle/patch/snapshotPatch.ts +9 -9
- package/runtime/src/lifecycle/patch/updateMainThread.ts +7 -8
- package/runtime/src/lifecycleConstant.ts +1 -0
- package/runtime/src/list.ts +118 -15
- package/runtime/src/lynx/calledByNative.ts +6 -8
- package/runtime/src/lynx/component.ts +17 -29
- package/runtime/src/lynx/env.ts +1 -2
- package/runtime/src/lynx/lazy-bundle.ts +55 -20
- package/runtime/src/lynx/performance.ts +26 -27
- package/runtime/src/lynx/tt.ts +10 -11
- package/runtime/src/lynx-api.ts +77 -1
- package/runtime/src/snapshot.ts +20 -5
- package/runtime/src/utils.ts +9 -0
- package/testing-library/dist/index.d.ts +4 -1
- package/testing-library/dist/pure.js +1 -1
- package/testing-library/dist/vitest.config.js +38 -1
- package/testing-library/types/entry.d.ts +3 -2
- package/transform/dist/wasm.cjs +1 -1
- package/types/react.d.ts +21 -1
- package/types/react.docs.d.ts +1 -1
|
@@ -35,8 +35,7 @@ import { onPostWorkletCtx } from './worklet/ctx.js';
|
|
|
35
35
|
export class BackgroundSnapshotInstance {
|
|
36
36
|
constructor(public type: string) {
|
|
37
37
|
this.__snapshot_def = snapshotManager.values.get(type)!;
|
|
38
|
-
|
|
39
|
-
id = this.__id = backgroundSnapshotInstanceManager.nextId += 1;
|
|
38
|
+
const id = this.__id = backgroundSnapshotInstanceManager.nextId += 1;
|
|
40
39
|
backgroundSnapshotInstanceManager.values.set(id, this);
|
|
41
40
|
|
|
42
41
|
__globalSnapshotPatch?.push(SnapshotOperation.CreateElement, type, id);
|
|
@@ -45,6 +44,7 @@ export class BackgroundSnapshotInstance {
|
|
|
45
44
|
__id: number;
|
|
46
45
|
__values: any[] | undefined;
|
|
47
46
|
__snapshot_def: Snapshot;
|
|
47
|
+
__extraProps?: Record<string, unknown> | undefined;
|
|
48
48
|
|
|
49
49
|
private __parent: BackgroundSnapshotInstance | null = null;
|
|
50
50
|
private __firstChild: BackgroundSnapshotInstance | null = null;
|
|
@@ -191,7 +191,7 @@ export class BackgroundSnapshotInstance {
|
|
|
191
191
|
return nodes;
|
|
192
192
|
}
|
|
193
193
|
|
|
194
|
-
setAttribute(key: string | number, value:
|
|
194
|
+
setAttribute(key: string | number, value: unknown): void {
|
|
195
195
|
if (__PROFILE__) {
|
|
196
196
|
console.profile('setAttribute');
|
|
197
197
|
}
|
|
@@ -199,8 +199,12 @@ export class BackgroundSnapshotInstance {
|
|
|
199
199
|
if (__globalSnapshotPatch) {
|
|
200
200
|
const oldValues = this.__values;
|
|
201
201
|
if (oldValues) {
|
|
202
|
-
for (let index = 0; index < value.length; index++) {
|
|
203
|
-
const { needUpdate, valueToCommit } = this.setAttributeImpl(
|
|
202
|
+
for (let index = 0; index < (value as unknown[]).length; index++) {
|
|
203
|
+
const { needUpdate, valueToCommit } = this.setAttributeImpl(
|
|
204
|
+
(value as unknown[])[index],
|
|
205
|
+
oldValues[index],
|
|
206
|
+
index,
|
|
207
|
+
);
|
|
204
208
|
if (needUpdate) {
|
|
205
209
|
__globalSnapshotPatch.push(
|
|
206
210
|
SnapshotOperation.SetAttribute,
|
|
@@ -212,9 +216,9 @@ export class BackgroundSnapshotInstance {
|
|
|
212
216
|
}
|
|
213
217
|
} else {
|
|
214
218
|
const patch = [];
|
|
215
|
-
const length = value.length;
|
|
219
|
+
const length = (value as unknown[]).length;
|
|
216
220
|
for (let index = 0; index < length; ++index) {
|
|
217
|
-
const { valueToCommit } = this.setAttributeImpl(value[index], null, index);
|
|
221
|
+
const { valueToCommit } = this.setAttributeImpl((value as unknown[])[index], null, index);
|
|
218
222
|
patch[index] = valueToCommit;
|
|
219
223
|
}
|
|
220
224
|
__globalSnapshotPatch.push(
|
|
@@ -235,22 +239,24 @@ export class BackgroundSnapshotInstance {
|
|
|
235
239
|
}
|
|
236
240
|
});
|
|
237
241
|
}
|
|
238
|
-
this.__values = value;
|
|
242
|
+
this.__values = value as unknown[];
|
|
239
243
|
if (__PROFILE__) {
|
|
240
244
|
console.profileEnd();
|
|
241
245
|
}
|
|
242
246
|
return;
|
|
243
247
|
}
|
|
244
248
|
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
249
|
+
if (typeof key === 'string') {
|
|
250
|
+
(this.__extraProps ??= {})[key] = value;
|
|
251
|
+
} else {
|
|
252
|
+
// old path (`this.setAttribute(0, xxx)`)
|
|
253
|
+
// is reserved as slow path
|
|
254
|
+
(this.__values ??= [])[key] = value;
|
|
255
|
+
}
|
|
250
256
|
__globalSnapshotPatch?.push(
|
|
251
257
|
SnapshotOperation.SetAttribute,
|
|
252
258
|
this.__id,
|
|
253
|
-
|
|
259
|
+
key,
|
|
254
260
|
value,
|
|
255
261
|
);
|
|
256
262
|
if (__PROFILE__) {
|
|
@@ -341,7 +347,7 @@ export function hydrate(
|
|
|
341
347
|
): SnapshotPatch {
|
|
342
348
|
initGlobalSnapshotPatch();
|
|
343
349
|
|
|
344
|
-
const helper2 = (afters: BackgroundSnapshotInstance[], parentId: number) => {
|
|
350
|
+
const helper2 = (afters: BackgroundSnapshotInstance[], parentId: number, targetId?: number) => {
|
|
345
351
|
for (const child of afters) {
|
|
346
352
|
const id = child.__id;
|
|
347
353
|
__globalSnapshotPatch!.push(SnapshotOperation.CreateElement, child.type, id);
|
|
@@ -350,8 +356,12 @@ export function hydrate(
|
|
|
350
356
|
child.__values = undefined;
|
|
351
357
|
child.setAttribute('values', values);
|
|
352
358
|
}
|
|
359
|
+
const extraProps = child.__extraProps;
|
|
360
|
+
for (const key in extraProps) {
|
|
361
|
+
child.setAttribute(key, extraProps[key]);
|
|
362
|
+
}
|
|
353
363
|
helper2(child.childNodes, id);
|
|
354
|
-
__globalSnapshotPatch!.push(SnapshotOperation.InsertBefore, parentId, id,
|
|
364
|
+
__globalSnapshotPatch!.push(SnapshotOperation.InsertBefore, parentId, id, targetId);
|
|
355
365
|
}
|
|
356
366
|
};
|
|
357
367
|
|
|
@@ -361,35 +371,45 @@ export function hydrate(
|
|
|
361
371
|
) => {
|
|
362
372
|
hydrationMap.set(after.__id, before.id);
|
|
363
373
|
backgroundSnapshotInstanceManager.updateId(after.__id, before.id);
|
|
364
|
-
after.__values?.forEach((value, index) => {
|
|
365
|
-
const old = before.values![index];
|
|
374
|
+
after.__values?.forEach((value: unknown, index) => {
|
|
375
|
+
const old: unknown = before.values![index];
|
|
366
376
|
|
|
367
377
|
if (value) {
|
|
368
|
-
if (value
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
378
|
+
if (typeof value === 'object') {
|
|
379
|
+
if ('__spread' in value) {
|
|
380
|
+
// `value.__spread` my contain event ids using snapshot ids before hydration. Remove it.
|
|
381
|
+
delete value.__spread;
|
|
382
|
+
const __spread = transformSpread(after, index, value);
|
|
383
|
+
for (const key in __spread) {
|
|
384
|
+
const v = __spread[key];
|
|
385
|
+
if (v && typeof v === 'object') {
|
|
386
|
+
if ('_wkltId' in v) {
|
|
387
|
+
onPostWorkletCtx(v as Worklet);
|
|
388
|
+
} else if ('__isGesture' in v) {
|
|
389
|
+
processGestureBackground(v as GestureKind);
|
|
390
|
+
}
|
|
391
|
+
}
|
|
377
392
|
}
|
|
393
|
+
(after.__values![index]! as Record<string, unknown>)['__spread'] = __spread;
|
|
394
|
+
value = __spread;
|
|
395
|
+
} else if ('__ref' in value) {
|
|
396
|
+
// skip patch
|
|
397
|
+
value = old;
|
|
398
|
+
} else if ('_wkltId' in value) {
|
|
399
|
+
onPostWorkletCtx(value as Worklet);
|
|
400
|
+
} else if ('__isGesture' in value) {
|
|
401
|
+
processGestureBackground(value as GestureKind);
|
|
378
402
|
}
|
|
379
|
-
after.__values![index]!.__spread = value;
|
|
380
|
-
} else if (value.__ref) {
|
|
381
|
-
// skip patch
|
|
382
|
-
value = old;
|
|
383
403
|
} else if (typeof value === 'function') {
|
|
384
|
-
value
|
|
404
|
+
if ('__ref' in value) {
|
|
405
|
+
// skip patch
|
|
406
|
+
value = old;
|
|
407
|
+
} else {
|
|
408
|
+
value = `${after.__id}:${index}:`;
|
|
409
|
+
}
|
|
385
410
|
}
|
|
386
411
|
}
|
|
387
412
|
|
|
388
|
-
if (value && value._wkltId) {
|
|
389
|
-
onPostWorkletCtx(value);
|
|
390
|
-
} else if (value && value.__isGesture) {
|
|
391
|
-
processGestureBackground(value);
|
|
392
|
-
}
|
|
393
413
|
if (!isDirectOrDeepEqual(value, old)) {
|
|
394
414
|
__globalSnapshotPatch!.push(
|
|
395
415
|
SnapshotOperation.SetAttribute,
|
|
@@ -400,9 +420,24 @@ export function hydrate(
|
|
|
400
420
|
}
|
|
401
421
|
});
|
|
402
422
|
|
|
423
|
+
if (after.__extraProps) {
|
|
424
|
+
for (const key in after.__extraProps) {
|
|
425
|
+
const value = after.__extraProps[key];
|
|
426
|
+
const old = before.extraProps?.[key];
|
|
427
|
+
if (!isDirectOrDeepEqual(value, old)) {
|
|
428
|
+
__globalSnapshotPatch!.push(
|
|
429
|
+
SnapshotOperation.SetAttribute,
|
|
430
|
+
after.__id,
|
|
431
|
+
key,
|
|
432
|
+
value,
|
|
433
|
+
);
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
|
|
403
438
|
const { slot } = after.__snapshot_def;
|
|
404
439
|
|
|
405
|
-
const beforeChildNodes = before.children
|
|
440
|
+
const beforeChildNodes = before.children ?? [];
|
|
406
441
|
const afterChildNodes = after.childNodes;
|
|
407
442
|
|
|
408
443
|
if (!slot) {
|
|
@@ -433,23 +468,7 @@ export function hydrate(
|
|
|
433
468
|
beforeChildNodes,
|
|
434
469
|
diffResult,
|
|
435
470
|
(node, target) => {
|
|
436
|
-
|
|
437
|
-
SnapshotOperation.CreateElement,
|
|
438
|
-
node.type,
|
|
439
|
-
node.__id,
|
|
440
|
-
);
|
|
441
|
-
helper2(node.childNodes, node.__id);
|
|
442
|
-
const values = node.__values;
|
|
443
|
-
if (values) {
|
|
444
|
-
node.__values = undefined;
|
|
445
|
-
node.setAttribute('values', values);
|
|
446
|
-
}
|
|
447
|
-
__globalSnapshotPatch!.push(
|
|
448
|
-
SnapshotOperation.InsertBefore,
|
|
449
|
-
before.id,
|
|
450
|
-
node.__id,
|
|
451
|
-
target?.id,
|
|
452
|
-
);
|
|
471
|
+
helper2([node], before.id, target?.id);
|
|
453
472
|
return undefined as unknown as SerializedSnapshotInstance;
|
|
454
473
|
},
|
|
455
474
|
node => {
|
|
@@ -5,6 +5,7 @@ import type { ComponentChildren, Consumer, Context, Provider } from 'preact';
|
|
|
5
5
|
import type { ComponentClass } from 'react';
|
|
6
6
|
|
|
7
7
|
import { useLynxGlobalEventListener } from '../hooks/useLynxGlobalEventListener.js';
|
|
8
|
+
import { globalFlushOptions } from '../lifecycle/patch/commit.js';
|
|
8
9
|
|
|
9
10
|
type Getter<T> = {
|
|
10
11
|
[key in keyof T]: () => T[key];
|
|
@@ -33,6 +34,9 @@ export function factory<Data>(
|
|
|
33
34
|
const [__, set] = useState<Data>(lynx[prop] as Data);
|
|
34
35
|
|
|
35
36
|
const handleChange = () => {
|
|
37
|
+
if (prop === '__initData') {
|
|
38
|
+
globalFlushOptions.triggerDataUpdated = true;
|
|
39
|
+
}
|
|
36
40
|
set(lynx[prop] as Data);
|
|
37
41
|
};
|
|
38
42
|
|
|
@@ -52,6 +56,9 @@ export function factory<Data>(
|
|
|
52
56
|
const use = (): Data => {
|
|
53
57
|
const [__, set] = useState(lynx[prop]);
|
|
54
58
|
useChanged(() => {
|
|
59
|
+
if (prop === '__initData') {
|
|
60
|
+
globalFlushOptions.triggerDataUpdated = true;
|
|
61
|
+
}
|
|
55
62
|
set(lynx[prop]);
|
|
56
63
|
});
|
|
57
64
|
|
|
@@ -65,6 +72,7 @@ export function factory<Data>(
|
|
|
65
72
|
};
|
|
66
73
|
|
|
67
74
|
return {
|
|
75
|
+
/* v8 ignore next */
|
|
68
76
|
Context: () => Context,
|
|
69
77
|
Provider: () => Provider,
|
|
70
78
|
Consumer: () => Consumer,
|
|
@@ -100,6 +108,7 @@ export function factory<Data>(
|
|
|
100
108
|
*/
|
|
101
109
|
export function withInitDataInState<P, S>(App: ComponentClass<P, S>): ComponentClass<P, S> {
|
|
102
110
|
const isClassComponent = 'prototype' in App && 'render' in App.prototype;
|
|
111
|
+
/* v8 ignore next 4 */
|
|
103
112
|
if (!isClassComponent) {
|
|
104
113
|
// return as-is when not class component
|
|
105
114
|
return App;
|
|
@@ -119,6 +128,7 @@ export function withInitDataInState<P, S>(App: ComponentClass<P, S>): ComponentC
|
|
|
119
128
|
lynx.getJSModule('GlobalEventEmitter').addListener(
|
|
120
129
|
'onDataChanged',
|
|
121
130
|
this.h = () => {
|
|
131
|
+
globalFlushOptions.triggerDataUpdated = true;
|
|
122
132
|
this.setState(lynx.__initData as S);
|
|
123
133
|
},
|
|
124
134
|
);
|
package/runtime/src/index.ts
CHANGED
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
PureComponent,
|
|
12
12
|
Suspense,
|
|
13
13
|
lazy as backgroundLazy,
|
|
14
|
+
cloneElement,
|
|
14
15
|
createContext,
|
|
15
16
|
createElement,
|
|
16
17
|
createRef,
|
|
@@ -86,6 +87,7 @@ export {
|
|
|
86
87
|
Suspense,
|
|
87
88
|
lazy,
|
|
88
89
|
createElement,
|
|
90
|
+
cloneElement,
|
|
89
91
|
useSyncExternalStore,
|
|
90
92
|
};
|
|
91
93
|
|
|
@@ -23,13 +23,7 @@ import type { VNode } from 'preact';
|
|
|
23
23
|
import { options } from 'preact';
|
|
24
24
|
|
|
25
25
|
import { LifecycleConstant } from '../../lifecycleConstant.js';
|
|
26
|
-
import {
|
|
27
|
-
PerformanceTimingKeys,
|
|
28
|
-
globalPipelineOptions,
|
|
29
|
-
markTiming,
|
|
30
|
-
markTimingLegacy,
|
|
31
|
-
setPipeline,
|
|
32
|
-
} from '../../lynx/performance.js';
|
|
26
|
+
import { globalPipelineOptions, markTiming, markTimingLegacy, setPipeline } from '../../lynx/performance.js';
|
|
33
27
|
import { COMMIT } from '../../renderToOpcodes/constants.js';
|
|
34
28
|
import { applyQueuedRefs } from '../../snapshot/ref.js';
|
|
35
29
|
import { backgroundSnapshotInstanceManager } from '../../snapshot.js';
|
|
@@ -88,8 +82,8 @@ function replaceCommitHook(): void {
|
|
|
88
82
|
}
|
|
89
83
|
|
|
90
84
|
// Mark the end of virtual DOM diffing phase for performance tracking
|
|
91
|
-
markTimingLegacy(
|
|
92
|
-
markTiming(
|
|
85
|
+
markTimingLegacy('updateDiffVdomEnd');
|
|
86
|
+
markTiming('diffVdomEnd');
|
|
93
87
|
|
|
94
88
|
const backgroundSnapshotInstancesToRemove = globalBackgroundSnapshotInstancesToRemove;
|
|
95
89
|
globalBackgroundSnapshotInstancesToRemove = [];
|
|
@@ -167,7 +161,7 @@ function commitPatchUpdate(patchList: PatchList, patchOptions: Omit<PatchOptions
|
|
|
167
161
|
if (__PROFILE__) {
|
|
168
162
|
console.profile('commitChanges');
|
|
169
163
|
}
|
|
170
|
-
markTiming(
|
|
164
|
+
markTiming('packChangesStart');
|
|
171
165
|
const obj: {
|
|
172
166
|
data: string;
|
|
173
167
|
patchOptions: PatchOptions;
|
|
@@ -178,7 +172,7 @@ function commitPatchUpdate(patchList: PatchList, patchOptions: Omit<PatchOptions
|
|
|
178
172
|
reloadVersion: getReloadVersion(),
|
|
179
173
|
},
|
|
180
174
|
};
|
|
181
|
-
markTiming(
|
|
175
|
+
markTiming('packChangesEnd');
|
|
182
176
|
if (globalPipelineOptions) {
|
|
183
177
|
obj.patchOptions.pipelineOptions = globalPipelineOptions;
|
|
184
178
|
setPipeline(undefined);
|
|
@@ -8,16 +8,16 @@
|
|
|
8
8
|
* efficient transmission between threads and application to element tree.
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
export const
|
|
12
|
-
CreateElement,
|
|
13
|
-
InsertBefore,
|
|
14
|
-
RemoveChild,
|
|
15
|
-
SetAttribute,
|
|
16
|
-
SetAttributes,
|
|
11
|
+
export const SnapshotOperation = {
|
|
12
|
+
CreateElement: 0,
|
|
13
|
+
InsertBefore: 1,
|
|
14
|
+
RemoveChild: 2,
|
|
15
|
+
SetAttribute: 3,
|
|
16
|
+
SetAttributes: 4,
|
|
17
17
|
|
|
18
|
-
DEV_ONLY_AddSnapshot
|
|
19
|
-
DEV_ONLY_RegisterWorklet
|
|
20
|
-
}
|
|
18
|
+
DEV_ONLY_AddSnapshot: 100,
|
|
19
|
+
DEV_ONLY_RegisterWorklet: 101,
|
|
20
|
+
} as const;
|
|
21
21
|
|
|
22
22
|
// Operation format definitions:
|
|
23
23
|
//
|
|
@@ -8,7 +8,7 @@ import type { PatchList, PatchOptions } from './commit.js';
|
|
|
8
8
|
import { setMainThreadHydrationFinished } from './isMainThreadHydrationFinished.js';
|
|
9
9
|
import { snapshotPatchApply } from './snapshotPatchApply.js';
|
|
10
10
|
import { LifecycleConstant } from '../../lifecycleConstant.js';
|
|
11
|
-
import {
|
|
11
|
+
import { markTiming, setPipeline } from '../../lynx/performance.js';
|
|
12
12
|
import { __pendingListUpdates } from '../../pendingListUpdates.js';
|
|
13
13
|
import { applyRefQueue } from '../../snapshot/workletRef.js';
|
|
14
14
|
import { __page } from '../../snapshot.js';
|
|
@@ -25,12 +25,12 @@ function updateMainThread(
|
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
setPipeline(patchOptions.pipelineOptions);
|
|
28
|
-
markTiming(
|
|
29
|
-
markTiming(
|
|
28
|
+
markTiming('mtsRenderStart');
|
|
29
|
+
markTiming('parseChangesStart');
|
|
30
30
|
const { patchList, flushOptions = {} } = JSON.parse(data) as PatchList;
|
|
31
31
|
|
|
32
|
-
markTiming(
|
|
33
|
-
markTiming(
|
|
32
|
+
markTiming('parseChangesEnd');
|
|
33
|
+
markTiming('patchChangesStart');
|
|
34
34
|
|
|
35
35
|
for (const { snapshotPatch, workletRefInitValuePatch } of patchList) {
|
|
36
36
|
updateWorkletRefInitValueChanges(workletRefInitValuePatch);
|
|
@@ -42,8 +42,8 @@ function updateMainThread(
|
|
|
42
42
|
// console.debug('********** Lepus updatePatch:');
|
|
43
43
|
// printSnapshotInstance(snapshotInstanceManager.values.get(-1)!);
|
|
44
44
|
}
|
|
45
|
-
markTiming(
|
|
46
|
-
markTiming(
|
|
45
|
+
markTiming('patchChangesEnd');
|
|
46
|
+
markTiming('mtsRenderEnd');
|
|
47
47
|
if (patchOptions.isHydration) {
|
|
48
48
|
setMainThreadHydrationFinished(true);
|
|
49
49
|
}
|
|
@@ -51,7 +51,6 @@ function updateMainThread(
|
|
|
51
51
|
if (patchOptions.pipelineOptions) {
|
|
52
52
|
flushOptions.pipelineOptions = patchOptions.pipelineOptions;
|
|
53
53
|
}
|
|
54
|
-
// TODO: triggerDataUpdated?
|
|
55
54
|
__FlushElementTree(__page, flushOptions);
|
|
56
55
|
}
|
|
57
56
|
|
package/runtime/src/list.ts
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
// Copyright 2024 The Lynx Authors. All rights reserved.
|
|
2
2
|
// Licensed under the Apache License Version 2.0 that can be found in the
|
|
3
3
|
// LICENSE file in the root directory of this source tree.
|
|
4
|
+
import { LifecycleConstant } from './lifecycleConstant.js';
|
|
4
5
|
import { applyRefQueue } from './snapshot/workletRef.js';
|
|
5
6
|
import type { SnapshotInstance } from './snapshot.js';
|
|
7
|
+
import { maybePromise } from './utils.js';
|
|
6
8
|
|
|
7
9
|
export const gSignMap: Record<number, Map<number, SnapshotInstance>> = {};
|
|
8
10
|
export const gRecycleMap: Record<number, Map<string, Map<number, SnapshotInstance>>> = {};
|
|
9
11
|
const gParentWeakMap: WeakMap<SnapshotInstance, unknown> = new WeakMap();
|
|
12
|
+
const resolvedPromise = /* @__PURE__ */ Promise.resolve();
|
|
10
13
|
|
|
11
14
|
export function clearListGlobal(): void {
|
|
12
15
|
for (const key in gSignMap) {
|
|
@@ -38,10 +41,10 @@ export function componentAtIndexFactory(
|
|
|
38
41
|
}
|
|
39
42
|
});
|
|
40
43
|
|
|
41
|
-
const
|
|
44
|
+
const componentAtChildCtx = (
|
|
42
45
|
list: FiberElement,
|
|
43
46
|
listID: number,
|
|
44
|
-
|
|
47
|
+
childCtx: SnapshotInstance,
|
|
45
48
|
operationID: number,
|
|
46
49
|
enableReuseNotification: boolean,
|
|
47
50
|
enableBatchRender: boolean = false,
|
|
@@ -53,13 +56,55 @@ export function componentAtIndexFactory(
|
|
|
53
56
|
throw new Error('componentAtIndex called on removed list');
|
|
54
57
|
}
|
|
55
58
|
|
|
56
|
-
const childCtx = ctx[cellIndex];
|
|
57
|
-
if (!childCtx) {
|
|
58
|
-
throw new Error('childCtx not found');
|
|
59
|
-
}
|
|
60
|
-
|
|
61
59
|
const platformInfo = childCtx.__listItemPlatformInfo ?? {};
|
|
62
60
|
|
|
61
|
+
// The lifecycle of this `__extraProps.isReady`:
|
|
62
|
+
// 0 -> Promise<number> -> 1
|
|
63
|
+
// 0: The initial state, the list-item is not ready yet, we will send a event to background
|
|
64
|
+
// when `componentAtIndex` is called on it
|
|
65
|
+
// Promise<number>: A promise that will be resolved when the list-item is ready
|
|
66
|
+
// 1: The list-item is ready, we can use it to render the list
|
|
67
|
+
if (childCtx.__extraProps?.['isReady'] === 0) {
|
|
68
|
+
if (
|
|
69
|
+
typeof __GetAttributeByName === 'function'
|
|
70
|
+
&& __GetAttributeByName(list, 'custom-list-name') === 'list-container'
|
|
71
|
+
) {
|
|
72
|
+
// we are in supported env
|
|
73
|
+
// do not throw
|
|
74
|
+
} else {
|
|
75
|
+
throw new Error(
|
|
76
|
+
'Unsupported: `<list-item/>` with `defer={true}` must be used with `<list custom-list-name="list-container"/>`',
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
__OnLifecycleEvent([LifecycleConstant.publishEvent, {
|
|
81
|
+
handlerName: `${childCtx.__id}:__extraProps:onComponentAtIndex`,
|
|
82
|
+
data: {},
|
|
83
|
+
}]);
|
|
84
|
+
|
|
85
|
+
let p: Promise<number>;
|
|
86
|
+
return (p = new Promise<number>((resolve) => {
|
|
87
|
+
Object.defineProperty(childCtx.__extraProps, 'isReady', {
|
|
88
|
+
set(isReady) {
|
|
89
|
+
if (isReady === 1) {
|
|
90
|
+
delete childCtx.__extraProps!['isReady'];
|
|
91
|
+
childCtx.__extraProps!['isReady'] = 1;
|
|
92
|
+
|
|
93
|
+
void resolvedPromise.then(() => {
|
|
94
|
+
// the cellIndex may be changed already, but the `childCtx` is the same
|
|
95
|
+
resolve(componentAtChildCtx(list, listID, childCtx, operationID, enableReuseNotification));
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
},
|
|
99
|
+
get() {
|
|
100
|
+
return p;
|
|
101
|
+
},
|
|
102
|
+
});
|
|
103
|
+
}));
|
|
104
|
+
} else if (maybePromise<number>(childCtx.__extraProps?.['isReady'])) {
|
|
105
|
+
throw new Error('componentAtIndex was called on a pending deferred list item');
|
|
106
|
+
}
|
|
107
|
+
|
|
63
108
|
const uniqID = childCtx.type + (platformInfo['reuse-identifier'] ?? '');
|
|
64
109
|
const recycleSignMap = recycleMap.get(uniqID);
|
|
65
110
|
|
|
@@ -102,6 +147,11 @@ export function componentAtIndexFactory(
|
|
|
102
147
|
oldCtx.unRenderElements();
|
|
103
148
|
if (!oldCtx.__id) {
|
|
104
149
|
oldCtx.tearDown();
|
|
150
|
+
} else if (oldCtx.__extraProps?.['isReady'] === 1) {
|
|
151
|
+
__OnLifecycleEvent([LifecycleConstant.publishEvent, {
|
|
152
|
+
handlerName: `${oldCtx.__id}:__extraProps:onRecycleComponent`,
|
|
153
|
+
data: {},
|
|
154
|
+
}]);
|
|
105
155
|
}
|
|
106
156
|
const root = childCtx.__element_root!;
|
|
107
157
|
applyRefQueue();
|
|
@@ -156,25 +206,78 @@ export function componentAtIndexFactory(
|
|
|
156
206
|
return sign;
|
|
157
207
|
};
|
|
158
208
|
|
|
159
|
-
|
|
209
|
+
function componentAtIndex(
|
|
210
|
+
list: FiberElement,
|
|
211
|
+
listID: number,
|
|
212
|
+
cellIndex: number,
|
|
213
|
+
operationID: number,
|
|
214
|
+
enableReuseNotification: boolean,
|
|
215
|
+
) {
|
|
216
|
+
const childCtx = ctx[cellIndex];
|
|
217
|
+
if (!childCtx) {
|
|
218
|
+
throw new Error('childCtx not found');
|
|
219
|
+
}
|
|
220
|
+
const r = componentAtChildCtx(list, listID, childCtx, operationID, enableReuseNotification);
|
|
221
|
+
|
|
222
|
+
/* v8 ignore start */
|
|
223
|
+
if (process.env['NODE_ENV'] === 'test') {
|
|
224
|
+
return r;
|
|
225
|
+
} else {
|
|
226
|
+
return typeof r === 'number' ? r : undefined;
|
|
227
|
+
}
|
|
228
|
+
/* v8 ignore end */
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function componentAtIndexes(
|
|
160
232
|
list: FiberElement,
|
|
161
233
|
listID: number,
|
|
162
234
|
cellIndexes: number[],
|
|
163
235
|
operationIDs: number[],
|
|
164
236
|
enableReuseNotification: boolean,
|
|
165
237
|
asyncFlush: boolean,
|
|
166
|
-
)
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
238
|
+
) {
|
|
239
|
+
let hasUnready = false;
|
|
240
|
+
const p: Array<Promise<number> | number> = [];
|
|
241
|
+
|
|
242
|
+
cellIndexes.forEach((cellIndex, index) => {
|
|
243
|
+
const operationID = operationIDs[index]!;
|
|
244
|
+
const childCtx = ctx[cellIndex];
|
|
245
|
+
if (!childCtx) {
|
|
246
|
+
throw new Error('childCtx not found');
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
const u = componentAtChildCtx(list, listID, childCtx, operationID, enableReuseNotification, true, asyncFlush);
|
|
250
|
+
if (typeof u === 'number') {
|
|
251
|
+
// ready
|
|
252
|
+
} else {
|
|
253
|
+
hasUnready = true;
|
|
254
|
+
}
|
|
255
|
+
p.push(u);
|
|
170
256
|
});
|
|
257
|
+
|
|
258
|
+
// We need __FlushElementTree twice:
|
|
259
|
+
// 1. The first time is sync, we flush the items that are ready, with unready items' uiSign as -1.
|
|
260
|
+
// 2. The second time is async, with all the uiSigns.
|
|
261
|
+
// NOTE: The `operationIDs` passed to __FlushElementTree must be the one passed in,
|
|
262
|
+
// not the one generated by any code here, to workaround a bug of Lynx Engine.
|
|
263
|
+
// So we CANNOT split the `operationIDs` into two parts: one for ready items, one for unready items.
|
|
264
|
+
if (hasUnready) {
|
|
265
|
+
void Promise.all(p).then((uiSigns) => {
|
|
266
|
+
__FlushElementTree(list, {
|
|
267
|
+
triggerLayout: true,
|
|
268
|
+
operationIDs,
|
|
269
|
+
elementIDs: uiSigns,
|
|
270
|
+
listID,
|
|
271
|
+
});
|
|
272
|
+
});
|
|
273
|
+
}
|
|
171
274
|
__FlushElementTree(list, {
|
|
172
275
|
triggerLayout: true,
|
|
173
|
-
operationIDs
|
|
174
|
-
elementIDs:
|
|
276
|
+
operationIDs,
|
|
277
|
+
elementIDs: cellIndexes.map((_, index) => typeof p[index] === 'number' ? p[index] : -1),
|
|
175
278
|
listID,
|
|
176
279
|
});
|
|
177
|
-
}
|
|
280
|
+
}
|
|
178
281
|
return [componentAtIndex, componentAtIndexes] as const;
|
|
179
282
|
}
|
|
180
283
|
|
|
@@ -12,7 +12,7 @@ import { __root, setRoot } from '../root.js';
|
|
|
12
12
|
import { applyRefQueue } from '../snapshot/workletRef.js';
|
|
13
13
|
import { SnapshotInstance, __page, setupPage } from '../snapshot.js';
|
|
14
14
|
import { isEmptyObject } from '../utils.js';
|
|
15
|
-
import {
|
|
15
|
+
import { markTiming, setPipeline } from './performance.js';
|
|
16
16
|
|
|
17
17
|
function ssrEncode() {
|
|
18
18
|
const { __opcodes } = __root;
|
|
@@ -116,13 +116,14 @@ function updatePage(data: Record<string, unknown> | undefined, options?: UpdateP
|
|
|
116
116
|
Object.assign(lynx.__initData, data);
|
|
117
117
|
}
|
|
118
118
|
|
|
119
|
+
const flushOptions = options ?? {};
|
|
119
120
|
if (!isJSReady) {
|
|
120
121
|
const oldRoot = __root;
|
|
121
122
|
setRoot(new SnapshotInstance('root'));
|
|
122
123
|
__root.__jsx = oldRoot.__jsx;
|
|
123
124
|
|
|
124
125
|
setPipeline(options?.pipelineOptions);
|
|
125
|
-
markTiming(
|
|
126
|
+
markTiming('updateDiffVdomStart');
|
|
126
127
|
{
|
|
127
128
|
__pendingListUpdates.clear();
|
|
128
129
|
renderMainThread();
|
|
@@ -138,14 +139,11 @@ function updatePage(data: Record<string, unknown> | undefined, options?: UpdateP
|
|
|
138
139
|
__pendingListUpdates.flush();
|
|
139
140
|
applyRefQueue();
|
|
140
141
|
}
|
|
141
|
-
|
|
142
|
+
flushOptions.triggerDataUpdated = true;
|
|
143
|
+
markTiming('updateDiffVdomEnd');
|
|
142
144
|
}
|
|
143
145
|
|
|
144
|
-
|
|
145
|
-
__FlushElementTree(__page, options);
|
|
146
|
-
} else {
|
|
147
|
-
__FlushElementTree();
|
|
148
|
-
}
|
|
146
|
+
__FlushElementTree(__page, flushOptions);
|
|
149
147
|
}
|
|
150
148
|
|
|
151
149
|
function updateGlobalProps(_data: any, options?: UpdatePageOption): void {
|