@atolljs/islands 0.1.6 → 0.1.7
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 +2 -2
- package/src/island.ts +54 -18
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@atolljs/islands",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.7",
|
|
4
4
|
"description": "Atoll islands — MonoWorker/PolyWorker hosts rendering framework islands (React/Vue/Svelte/Solid/Angular) over a proxy-DOM op protocol",
|
|
5
5
|
"homepage": "https://jwhenry3.github.io/atolljs/consumer/quickstart/",
|
|
6
6
|
"type": "module",
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
"htmlparser2": "^12.0.0"
|
|
20
20
|
},
|
|
21
21
|
"peerDependencies": {
|
|
22
|
-
"@atolljs/core": "0.1.
|
|
22
|
+
"@atolljs/core": "0.1.7"
|
|
23
23
|
},
|
|
24
24
|
"repository": {
|
|
25
25
|
"type": "git",
|
package/src/island.ts
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
* is least-busy round-robin, so a second worker would receive dispatches for
|
|
15
15
|
* a tree it doesn't hold (sticky routing is future work).
|
|
16
16
|
*/
|
|
17
|
-
import { connectWorker, observe } from '@atolljs/core';
|
|
17
|
+
import { connectWorker, emitDevtools, observe } from '@atolljs/core';
|
|
18
18
|
import type {
|
|
19
19
|
ConnectWorkerConfig,
|
|
20
20
|
SharedSpec,
|
|
@@ -121,6 +121,13 @@ interface MountIslandBaseOptions {
|
|
|
121
121
|
* cross-origin isolation requirement. Switch later with `handle.setMode`.
|
|
122
122
|
*/
|
|
123
123
|
mode?: Mode;
|
|
124
|
+
/**
|
|
125
|
+
* Optional renderer/framework tag — pure observability metadata forwarded
|
|
126
|
+
* to devtools on `island:mount` ('react', 'vue', 'svelte', …). Framework
|
|
127
|
+
* shell bindings can set it automatically; direct mountIsland callers
|
|
128
|
+
* declare it themselves.
|
|
129
|
+
*/
|
|
130
|
+
framework?: string;
|
|
124
131
|
/**
|
|
125
132
|
* Milliseconds the mount handshake (mount + whoami) may take before the
|
|
126
133
|
* returned promise rejects — default 15_000, `0` disables. A worker entry
|
|
@@ -336,7 +343,7 @@ const cssStyleValue = (key: string, value: unknown): string => {
|
|
|
336
343
|
export async function mountIsland(opts: MountIslandOptions): Promise<IslandHandle> {
|
|
337
344
|
const {
|
|
338
345
|
el, onEvent, onActivity, onOps, slots, mode: initialMode, mountTimeout,
|
|
339
|
-
client: givenClient, worker, app: givenApp, props: givenProps,
|
|
346
|
+
client: givenClient, worker, app: givenApp, props: givenProps, framework,
|
|
340
347
|
...workerConfig
|
|
341
348
|
} = opts;
|
|
342
349
|
// Shell→worker callables: callbackProp() markers marshal to {__cb:id}
|
|
@@ -442,9 +449,8 @@ export async function mountIsland(opts: MountIslandOptions): Promise<IslandHandl
|
|
|
442
449
|
);
|
|
443
450
|
// The whole point: an event = one postMessage round-trip. The worker
|
|
444
451
|
// re-renders, we apply whatever ops come back.
|
|
445
|
-
void client
|
|
446
|
-
.
|
|
447
|
-
.then(applyOps)
|
|
452
|
+
void islandCall('dispatch', client.dispatch(handlerId, payload))
|
|
453
|
+
.then((ops) => applyOps(ops, 'dispatch'))
|
|
448
454
|
.catch((err) => console.error(`[island ${instance}] dispatch failed`, err));
|
|
449
455
|
};
|
|
450
456
|
}
|
|
@@ -771,24 +777,53 @@ export async function mountIsland(opts: MountIslandOptions): Promise<IslandHandl
|
|
|
771
777
|
break;
|
|
772
778
|
}
|
|
773
779
|
// Not a DOM mutation — the island→shell channel.
|
|
780
|
+
emitDevtools({ type: 'island:event', instance, name: op.name });
|
|
774
781
|
onEvent?.(op.name, op.payload);
|
|
775
782
|
break;
|
|
776
783
|
}
|
|
777
784
|
}
|
|
778
785
|
}
|
|
779
786
|
|
|
780
|
-
function applyOps(ops: Op[]): void {
|
|
787
|
+
function applyOps(ops: Op[], via: 'mount' | 'updateProps' | 'dispatch' | 'setSize' | 'flush'): void {
|
|
781
788
|
opsApplied += ops.length;
|
|
782
789
|
onActivity?.();
|
|
783
|
-
const t0 =
|
|
790
|
+
const t0 = performance.now();
|
|
784
791
|
for (const op of ops) applyOp(op);
|
|
785
|
-
|
|
792
|
+
const replayMs = performance.now() - t0;
|
|
793
|
+
emitDevtools({ type: 'island:ops', instance, via, count: ops.length, replayMs });
|
|
794
|
+
onOps?.(ops, replayMs);
|
|
786
795
|
}
|
|
787
796
|
|
|
797
|
+
/** Times a main→worker round-trip for the devtools inspector. */
|
|
798
|
+
const islandCall = <T>(
|
|
799
|
+
method: 'mount' | 'whoami' | 'dispatch' | 'setSize' | 'updateProps' | 'flush' | 'unmount',
|
|
800
|
+
p: Promise<T>,
|
|
801
|
+
): Promise<T> => {
|
|
802
|
+
const t0 = performance.now();
|
|
803
|
+
return p.then(
|
|
804
|
+
(r) => {
|
|
805
|
+
emitDevtools({
|
|
806
|
+
type: 'island:task', instance, method,
|
|
807
|
+
ms: performance.now() - t0,
|
|
808
|
+
ops: Array.isArray(r) ? r.length : undefined,
|
|
809
|
+
});
|
|
810
|
+
return r;
|
|
811
|
+
},
|
|
812
|
+
(err) => {
|
|
813
|
+
emitDevtools({
|
|
814
|
+
type: 'island:task', instance, method,
|
|
815
|
+
ms: performance.now() - t0,
|
|
816
|
+
error: err instanceof Error ? err.message : String(err),
|
|
817
|
+
});
|
|
818
|
+
throw err;
|
|
819
|
+
},
|
|
820
|
+
);
|
|
821
|
+
};
|
|
822
|
+
|
|
788
823
|
const doFlush = (): void => {
|
|
789
824
|
if (destroyed) return;
|
|
790
825
|
flushCalls++;
|
|
791
|
-
void client.flush(instance).then(applyOps);
|
|
826
|
+
void islandCall('flush', client.flush(instance)).then((ops) => applyOps(ops, 'flush'));
|
|
792
827
|
};
|
|
793
828
|
|
|
794
829
|
/* ── Transport: push (shared-memory doorbell) vs poll ─────────────────── */
|
|
@@ -842,15 +877,16 @@ export async function mountIsland(opts: MountIslandOptions): Promise<IslandHandl
|
|
|
842
877
|
|
|
843
878
|
let pid: string;
|
|
844
879
|
try {
|
|
845
|
-
applyOps(await raced(client.mount(instance, props)));
|
|
846
|
-
pid = await raced(client.whoami(instance));
|
|
880
|
+
applyOps(await raced(islandCall('mount', client.mount(instance, props))), 'mount');
|
|
881
|
+
pid = await raced(islandCall('whoami', client.whoami(instance)));
|
|
882
|
+
emitDevtools({ type: 'island:mount', instance, app, pid, poolId: client.pool?.poolId, framework });
|
|
847
883
|
} catch (err) {
|
|
848
884
|
// The instance may exist worker-side with ops we never applied — release it.
|
|
849
885
|
// An island-owned client also terminates (stops a crash→respawn loop on
|
|
850
886
|
// a permanently-broken entry); a shared client just drops the instance.
|
|
851
887
|
clientMounts.set(client, (clientMounts.get(client) ?? 1) - 1);
|
|
852
888
|
if (givenClient === undefined) client.terminate();
|
|
853
|
-
else void client.unmount(instance).catch(() => {});
|
|
889
|
+
else void islandCall('unmount', client.unmount(instance)).catch(() => {});
|
|
854
890
|
throw err;
|
|
855
891
|
}
|
|
856
892
|
|
|
@@ -879,7 +915,7 @@ export async function mountIsland(opts: MountIslandOptions): Promise<IslandHandl
|
|
|
879
915
|
if (w === lastW && h === lastH) return;
|
|
880
916
|
lastW = w;
|
|
881
917
|
lastH = h;
|
|
882
|
-
void client.setSize(instance, w, h).then(applyOps);
|
|
918
|
+
void islandCall('setSize', client.setSize(instance, w, h)).then((ops) => applyOps(ops, 'setSize'));
|
|
883
919
|
};
|
|
884
920
|
const resizeObserver =
|
|
885
921
|
typeof ResizeObserver === 'function'
|
|
@@ -918,13 +954,14 @@ export async function mountIsland(opts: MountIslandOptions): Promise<IslandHandl
|
|
|
918
954
|
updateProps: async (next: Record<string, unknown>) => {
|
|
919
955
|
const marshalled = marshalCallbackProps(next, registerCb) as Record<string, unknown>;
|
|
920
956
|
assertCloneableProps(marshalled, `updateProps(${app})`);
|
|
921
|
-
applyOps(await client.updateProps(instance, marshalled));
|
|
957
|
+
applyOps(await islandCall('updateProps', client.updateProps(instance, marshalled)), 'updateProps');
|
|
922
958
|
},
|
|
923
959
|
flush: async () => {
|
|
924
|
-
applyOps(await client.flush(instance));
|
|
960
|
+
applyOps(await islandCall('flush', client.flush(instance)), 'flush');
|
|
925
961
|
},
|
|
926
962
|
destroy: () => {
|
|
927
963
|
destroyed = true;
|
|
964
|
+
emitDevtools({ type: 'island:unmount', instance });
|
|
928
965
|
unsubscribe?.();
|
|
929
966
|
resizeObserver?.disconnect();
|
|
930
967
|
if (pollTimer !== null) clearInterval(pollTimer);
|
|
@@ -936,7 +973,7 @@ export async function mountIsland(opts: MountIslandOptions): Promise<IslandHandl
|
|
|
936
973
|
// detach ops are irrelevant if our element is already gone, so the
|
|
937
974
|
// return batch is dropped. Best-effort: the instance map and its
|
|
938
975
|
// handlers release regardless.
|
|
939
|
-
void client.unmount(instance).catch(() => {});
|
|
976
|
+
void islandCall('unmount', client.unmount(instance)).catch(() => {});
|
|
940
977
|
} else {
|
|
941
978
|
// Last island — still run the instance's teardown first so the app's
|
|
942
979
|
// dispose hook cancels deferred work (library timers, animation
|
|
@@ -944,8 +981,7 @@ export async function mountIsland(opts: MountIslandOptions): Promise<IslandHandl
|
|
|
944
981
|
// kill the thread before unmount lands — equally fine, process
|
|
945
982
|
// death is teardown — but in-process workers need this or the
|
|
946
983
|
// instance zombies on and its pending callbacks mutate foreign docs.
|
|
947
|
-
void client
|
|
948
|
-
.unmount(instance)
|
|
984
|
+
void islandCall('unmount', client.unmount(instance))
|
|
949
985
|
.catch(() => {})
|
|
950
986
|
.finally(() => {
|
|
951
987
|
// A remount may have registered while teardown was in flight
|