@atolljs/react-island 0.1.12 → 0.1.13
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/dist/chunk-Q7NIXFLZ.js +226 -0
- package/dist/hostConfig.d.ts +203 -0
- package/dist/index.d.ts +165 -0
- package/dist/index.js +12 -0
- package/dist/reactInstance.d.ts +42 -0
- package/dist/slot.d.ts +18 -0
- package/dist/subIsland.d.ts +37 -0
- package/dist/worker.d.ts +87 -0
- package/dist/worker.js +450 -0
- package/package.json +16 -6
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
// src/index.tsx
|
|
2
|
+
import { useEffect, useRef, useState } from "react";
|
|
3
|
+
import { createPortal } from "react-dom";
|
|
4
|
+
import {
|
|
5
|
+
connectIslandWorker,
|
|
6
|
+
contractWorkerOf,
|
|
7
|
+
islandAppNameOf,
|
|
8
|
+
mountIsland
|
|
9
|
+
} from "@atolljs/islands";
|
|
10
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
11
|
+
function Island({
|
|
12
|
+
app,
|
|
13
|
+
worker,
|
|
14
|
+
client: clientProp,
|
|
15
|
+
workerOptions,
|
|
16
|
+
mode,
|
|
17
|
+
framework,
|
|
18
|
+
props,
|
|
19
|
+
onEvent,
|
|
20
|
+
onActivity,
|
|
21
|
+
slots,
|
|
22
|
+
onReady,
|
|
23
|
+
onError,
|
|
24
|
+
ref,
|
|
25
|
+
...rest
|
|
26
|
+
}) {
|
|
27
|
+
const containerRef = useRef(null);
|
|
28
|
+
const islandRef = useRef(null);
|
|
29
|
+
const [slotTargets, setSlotTargets] = useState({});
|
|
30
|
+
const propsRef = useRef(props);
|
|
31
|
+
const onEventRef = useRef(onEvent);
|
|
32
|
+
const onActivityRef = useRef(onActivity);
|
|
33
|
+
const slotsRef = useRef(slots);
|
|
34
|
+
const onReadyRef = useRef(onReady);
|
|
35
|
+
const onErrorRef = useRef(onError);
|
|
36
|
+
propsRef.current = props;
|
|
37
|
+
onEventRef.current = onEvent;
|
|
38
|
+
onActivityRef.current = onActivity;
|
|
39
|
+
slotsRef.current = slots;
|
|
40
|
+
onReadyRef.current = onReady;
|
|
41
|
+
onErrorRef.current = onError;
|
|
42
|
+
const setRefs = (el) => {
|
|
43
|
+
containerRef.current = el;
|
|
44
|
+
if (typeof ref === "function") ref(el);
|
|
45
|
+
else if (ref != null) ref.current = el;
|
|
46
|
+
};
|
|
47
|
+
const appName = app === void 0 ? "main" : islandAppNameOf(app);
|
|
48
|
+
useEffect(() => {
|
|
49
|
+
const el = containerRef.current;
|
|
50
|
+
if (el === null) return;
|
|
51
|
+
let cancelled = false;
|
|
52
|
+
let island = null;
|
|
53
|
+
const report = (err) => {
|
|
54
|
+
if (onErrorRef.current !== void 0) onErrorRef.current(err);
|
|
55
|
+
else console.error("[Island] mount failed:", err);
|
|
56
|
+
};
|
|
57
|
+
if (appName === void 0) {
|
|
58
|
+
report(
|
|
59
|
+
new Error(
|
|
60
|
+
"<Island> could not resolve an app name \u2014 pass the registry key or stamp the app with islandApp(name, app)"
|
|
61
|
+
)
|
|
62
|
+
);
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
const resolvedWorker = worker ?? contractWorkerOf(app);
|
|
66
|
+
const client = clientProp ?? (resolvedWorker !== void 0 ? connectIslandWorker({
|
|
67
|
+
name: appName,
|
|
68
|
+
worker: resolvedWorker,
|
|
69
|
+
doorbell: mode !== "poll",
|
|
70
|
+
...workerOptions
|
|
71
|
+
}) : void 0);
|
|
72
|
+
if (client === void 0) {
|
|
73
|
+
report(new Error("<Island> requires either `worker` or `client`"));
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
const slotProxy = new Proxy({}, {
|
|
77
|
+
get: (_t, name) => (e) => {
|
|
78
|
+
setSlotTargets((prev) => prev[name] === e ? prev : { ...prev, [name]: e });
|
|
79
|
+
},
|
|
80
|
+
has: (_t, name) => slotsRef.current?.[name] != null
|
|
81
|
+
});
|
|
82
|
+
void (async () => {
|
|
83
|
+
try {
|
|
84
|
+
const handle = await mountIsland({
|
|
85
|
+
client,
|
|
86
|
+
el,
|
|
87
|
+
app: appName,
|
|
88
|
+
mode,
|
|
89
|
+
framework,
|
|
90
|
+
// Serialized across the wire either way — cast preserves inference
|
|
91
|
+
// for props types without an index signature.
|
|
92
|
+
props: propsRef.current ?? {},
|
|
93
|
+
onEvent: (name, payload) => onEventRef.current?.(name, payload),
|
|
94
|
+
onActivity: () => onActivityRef.current?.(),
|
|
95
|
+
slots: slotProxy
|
|
96
|
+
});
|
|
97
|
+
if (cancelled) {
|
|
98
|
+
handle.destroy();
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
island = handle;
|
|
102
|
+
islandRef.current = handle;
|
|
103
|
+
onReadyRef.current?.(handle);
|
|
104
|
+
} catch (err) {
|
|
105
|
+
if (!cancelled) report(err);
|
|
106
|
+
}
|
|
107
|
+
})();
|
|
108
|
+
return () => {
|
|
109
|
+
cancelled = true;
|
|
110
|
+
islandRef.current = null;
|
|
111
|
+
island?.destroy();
|
|
112
|
+
};
|
|
113
|
+
}, [app, appName, clientProp]);
|
|
114
|
+
const propsJson = JSON.stringify(props ?? {});
|
|
115
|
+
useEffect(() => {
|
|
116
|
+
const island = islandRef.current;
|
|
117
|
+
if (island === null) return;
|
|
118
|
+
island.updateProps(props ?? {}).catch((err) => {
|
|
119
|
+
if (onErrorRef.current !== void 0) onErrorRef.current(err);
|
|
120
|
+
else console.error("[Island] updateProps failed:", err);
|
|
121
|
+
});
|
|
122
|
+
}, [propsJson]);
|
|
123
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
124
|
+
/* @__PURE__ */ jsx("div", { ref: setRefs, ...rest }),
|
|
125
|
+
Object.entries(slotTargets).map(
|
|
126
|
+
([name, el]) => el && slotsRef.current?.[name] != null ? createPortal(slotsRef.current[name], el, `atoll-slot-${name}`) : null
|
|
127
|
+
)
|
|
128
|
+
] });
|
|
129
|
+
}
|
|
130
|
+
var SHELL_PROP_KEYS = /* @__PURE__ */ new Set([
|
|
131
|
+
"worker",
|
|
132
|
+
"client",
|
|
133
|
+
"workerOptions",
|
|
134
|
+
"mode",
|
|
135
|
+
"framework",
|
|
136
|
+
"onEvent",
|
|
137
|
+
"onActivity",
|
|
138
|
+
"onReady",
|
|
139
|
+
"onError",
|
|
140
|
+
"slots",
|
|
141
|
+
"fallback",
|
|
142
|
+
"containerProps"
|
|
143
|
+
]);
|
|
144
|
+
var splitProxyProps = (raw) => {
|
|
145
|
+
const shell = {};
|
|
146
|
+
const islandProps = {};
|
|
147
|
+
for (const [key, value] of Object.entries(raw)) {
|
|
148
|
+
if (SHELL_PROP_KEYS.has(key)) shell[key] = value;
|
|
149
|
+
else islandProps[key] = value;
|
|
150
|
+
}
|
|
151
|
+
return { shell, islandProps };
|
|
152
|
+
};
|
|
153
|
+
var lazyCache = /* @__PURE__ */ new WeakMap();
|
|
154
|
+
function useLazyApp(loader) {
|
|
155
|
+
let state = lazyCache.get(loader);
|
|
156
|
+
if (state === void 0) {
|
|
157
|
+
state = {
|
|
158
|
+
promise: Promise.resolve().then(loader).then((mod) => {
|
|
159
|
+
const unwrapped = mod !== null && typeof mod === "object" && "default" in mod ? mod.default : mod;
|
|
160
|
+
const isContract = unwrapped !== null && typeof unwrapped === "object" && "app" in unwrapped;
|
|
161
|
+
const resolvedApp = isContract ? unwrapped.app : unwrapped;
|
|
162
|
+
const resolvedWorker = isContract ? unwrapped.worker : void 0;
|
|
163
|
+
state.value = resolvedApp;
|
|
164
|
+
state.worker = resolvedWorker;
|
|
165
|
+
return resolvedApp;
|
|
166
|
+
}).catch((err) => {
|
|
167
|
+
state.error = err;
|
|
168
|
+
throw err;
|
|
169
|
+
})
|
|
170
|
+
};
|
|
171
|
+
lazyCache.set(loader, state);
|
|
172
|
+
}
|
|
173
|
+
if (state.error !== void 0) throw state.error;
|
|
174
|
+
if (state.value === void 0) throw state.promise;
|
|
175
|
+
return { app: state.value, worker: state.worker };
|
|
176
|
+
}
|
|
177
|
+
function ProxyIsland({
|
|
178
|
+
source,
|
|
179
|
+
...raw
|
|
180
|
+
}) {
|
|
181
|
+
const resolved = source.kind === "static" ? { app: source.app } : useLazyApp(source.loader);
|
|
182
|
+
const { shell, islandProps } = splitProxyProps(raw);
|
|
183
|
+
const [ready, setReady] = useState(false);
|
|
184
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
185
|
+
!ready ? shell.fallback : null,
|
|
186
|
+
/* @__PURE__ */ jsx(
|
|
187
|
+
Island,
|
|
188
|
+
{
|
|
189
|
+
app: resolved.app,
|
|
190
|
+
worker: shell.worker ?? resolved.worker,
|
|
191
|
+
client: shell.client,
|
|
192
|
+
workerOptions: shell.workerOptions,
|
|
193
|
+
mode: shell.mode,
|
|
194
|
+
framework: shell.framework,
|
|
195
|
+
props: islandProps,
|
|
196
|
+
onEvent: shell.onEvent,
|
|
197
|
+
onActivity: shell.onActivity,
|
|
198
|
+
slots: shell.slots,
|
|
199
|
+
onReady: (h) => {
|
|
200
|
+
shell.onReady?.(h);
|
|
201
|
+
setReady(true);
|
|
202
|
+
},
|
|
203
|
+
onError: shell.onError,
|
|
204
|
+
...shell.containerProps
|
|
205
|
+
}
|
|
206
|
+
)
|
|
207
|
+
] });
|
|
208
|
+
}
|
|
209
|
+
function islandComponent(app) {
|
|
210
|
+
const Proxy2 = (props) => /* @__PURE__ */ jsx(ProxyIsland, { source: { kind: "static", app }, ...props });
|
|
211
|
+
Proxy2.displayName = `IslandComponent(${islandAppNameOf(app) ?? "main"})`;
|
|
212
|
+
return Proxy2;
|
|
213
|
+
}
|
|
214
|
+
function lazyIsland(loader) {
|
|
215
|
+
const Proxy2 = (props) => /* @__PURE__ */ jsx(ProxyIsland, { source: { kind: "lazy", loader }, ...props });
|
|
216
|
+
Proxy2.displayName = "LazyIsland";
|
|
217
|
+
return Proxy2;
|
|
218
|
+
}
|
|
219
|
+
var index_default = Island;
|
|
220
|
+
|
|
221
|
+
export {
|
|
222
|
+
Island,
|
|
223
|
+
islandComponent,
|
|
224
|
+
lazyIsland,
|
|
225
|
+
index_default
|
|
226
|
+
};
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* React-reconciler host adapter for the worker DOM backend.
|
|
3
|
+
*
|
|
4
|
+
* The generic instance state (op queues, instance ids, handler registry,
|
|
5
|
+
* doorbell, geometry) lives in `@atolljs/islands/worker`'s instance module.
|
|
6
|
+
* This file only wires the reconciler's create/append/update/remove/commit
|
|
7
|
+
* hooks to that backend.
|
|
8
|
+
*
|
|
9
|
+
* react-reconciler@0.34 reads ~150 fields off the config object. Most are
|
|
10
|
+
* stubs for features this renderer doesn't implement (hydration,
|
|
11
|
+
* persistence, resources, singletons, view transitions, test selectors,
|
|
12
|
+
* scope/hydratable APIs) — they exist so nothing throws when the reconciler
|
|
13
|
+
* destructures them; they are never invoked behind the `supports*` flags.
|
|
14
|
+
*/
|
|
15
|
+
import { bumpOpsVersion, type ElementInstance, type HostInstance, type TextInstance } from '@atolljs/islands/worker';
|
|
16
|
+
declare const noop: () => void;
|
|
17
|
+
declare const NULL: () => null;
|
|
18
|
+
declare const FALSE: () => boolean;
|
|
19
|
+
declare const TRUE: () => boolean;
|
|
20
|
+
interface HostContext {
|
|
21
|
+
ns?: string;
|
|
22
|
+
}
|
|
23
|
+
export declare const hostConfig: {
|
|
24
|
+
rendererVersion: string;
|
|
25
|
+
rendererPackageName: string;
|
|
26
|
+
extraDevToolsConfig: null;
|
|
27
|
+
isPrimaryRenderer: boolean;
|
|
28
|
+
warnsIfNotActing: boolean;
|
|
29
|
+
supportsMutation: boolean;
|
|
30
|
+
supportsPersistence: boolean;
|
|
31
|
+
supportsHydration: boolean;
|
|
32
|
+
supportsResources: boolean;
|
|
33
|
+
supportsSingletons: boolean;
|
|
34
|
+
supportsMicrotasks: boolean;
|
|
35
|
+
supportsTestSelectors: boolean;
|
|
36
|
+
getPublicInstance: (instance: HostInstance) => unknown;
|
|
37
|
+
getRootHostContext: (container: unknown) => HostContext;
|
|
38
|
+
getChildHostContext: (parent: HostContext, type: string) => HostContext;
|
|
39
|
+
prepareForCommit: typeof NULL;
|
|
40
|
+
resetAfterCommit: typeof bumpOpsVersion;
|
|
41
|
+
createInstance: (type: string, props: Record<string, unknown>, rootContainer: unknown, hostContext: unknown, _internalHandle: unknown) => ElementInstance;
|
|
42
|
+
createTextInstance: (text: string, rootContainer: unknown, _hostContext: unknown, _internalHandle: unknown) => TextInstance;
|
|
43
|
+
finalizeInitialChildren: typeof FALSE;
|
|
44
|
+
shouldSetTextContent: typeof FALSE;
|
|
45
|
+
appendInitialChild: (parent: HostInstance, child: HostInstance) => void;
|
|
46
|
+
appendChild: (parent: HostInstance, child: HostInstance) => void;
|
|
47
|
+
appendChildToContainer: (container: unknown, child: HostInstance) => void;
|
|
48
|
+
insertBefore: (parent: HostInstance, child: HostInstance, before: HostInstance) => void;
|
|
49
|
+
insertInContainerBefore: (container: unknown, child: HostInstance, before: HostInstance) => void;
|
|
50
|
+
removeChild: (_parent: HostInstance, child: HostInstance) => void;
|
|
51
|
+
removeChildFromContainer: (_container: unknown, child: HostInstance) => void;
|
|
52
|
+
clearContainer: () => void;
|
|
53
|
+
commitUpdate: (instance: ElementInstance, _type: string, _oldProps: Record<string, unknown>, newProps: Record<string, unknown>, _internalHandle: unknown) => void;
|
|
54
|
+
commitTextUpdate: (instance: TextInstance, _oldText: string, newText: string) => void;
|
|
55
|
+
commitMount: typeof noop;
|
|
56
|
+
resetTextContent: (instance: ElementInstance) => void;
|
|
57
|
+
hideInstance: (instance: ElementInstance) => void;
|
|
58
|
+
unhideInstance: (instance: ElementInstance) => void;
|
|
59
|
+
hideTextInstance: (instance: TextInstance) => void;
|
|
60
|
+
unhideTextInstance: (instance: TextInstance, text: string) => void;
|
|
61
|
+
detachDeletedInstance: (instance: HostInstance) => void;
|
|
62
|
+
scheduleTimeout: typeof setTimeout;
|
|
63
|
+
cancelTimeout: typeof clearTimeout;
|
|
64
|
+
noTimeout: number;
|
|
65
|
+
scheduleMicrotask: (fn: () => void) => void;
|
|
66
|
+
setCurrentUpdatePriority: (p: number) => void;
|
|
67
|
+
getCurrentUpdatePriority: () => number;
|
|
68
|
+
resolveUpdatePriority: () => number;
|
|
69
|
+
resolveEventType: typeof NULL;
|
|
70
|
+
resolveEventTimeStamp: () => number;
|
|
71
|
+
trackSchedulerEvent: typeof noop;
|
|
72
|
+
shouldAttemptEagerTransition: typeof FALSE;
|
|
73
|
+
getInstanceFromNode: typeof NULL;
|
|
74
|
+
beforeActiveInstanceBlur: typeof noop;
|
|
75
|
+
afterActiveInstanceBlur: typeof noop;
|
|
76
|
+
preparePortalMount: typeof noop;
|
|
77
|
+
prepareScopeUpdate: typeof noop;
|
|
78
|
+
getInstanceFromScope: typeof NULL;
|
|
79
|
+
requestPostPaintCallback: typeof noop;
|
|
80
|
+
maySuspendCommit: typeof FALSE;
|
|
81
|
+
maySuspendCommitOnUpdate: typeof FALSE;
|
|
82
|
+
maySuspendCommitInSyncRender: typeof FALSE;
|
|
83
|
+
preloadInstance: typeof TRUE;
|
|
84
|
+
startSuspendingCommit: typeof noop;
|
|
85
|
+
suspendInstance: typeof noop;
|
|
86
|
+
suspendOnActiveViewTransition: typeof noop;
|
|
87
|
+
waitForCommitToBeReady: typeof NULL;
|
|
88
|
+
getSuspendedCommitReason: () => number;
|
|
89
|
+
NotPendingTransition: Readonly<{
|
|
90
|
+
pending: false;
|
|
91
|
+
data: null;
|
|
92
|
+
method: null;
|
|
93
|
+
action: null;
|
|
94
|
+
}>;
|
|
95
|
+
HostTransitionContext: import("react").Context<Readonly<{
|
|
96
|
+
pending: false;
|
|
97
|
+
data: null;
|
|
98
|
+
method: null;
|
|
99
|
+
action: null;
|
|
100
|
+
}>>;
|
|
101
|
+
resetFormInstance: typeof noop;
|
|
102
|
+
bindToConsole: (methodName: string, args: unknown[]) => (...args: unknown[]) => void;
|
|
103
|
+
createContainerChildSet: () => never[];
|
|
104
|
+
appendChildToContainerChildSet: typeof noop;
|
|
105
|
+
finalizeContainerChildren: typeof noop;
|
|
106
|
+
replaceContainerChildren: typeof noop;
|
|
107
|
+
cloneInstance: typeof NULL;
|
|
108
|
+
cloneMutableInstance: (instance: HostInstance) => HostInstance;
|
|
109
|
+
cloneMutableTextInstance: (instance: HostInstance) => HostInstance;
|
|
110
|
+
cloneHiddenInstance: (instance: HostInstance) => HostInstance;
|
|
111
|
+
cloneHiddenTextInstance: (instance: HostInstance) => HostInstance;
|
|
112
|
+
createFragmentInstance: () => {};
|
|
113
|
+
updateFragmentInstanceFiber: typeof noop;
|
|
114
|
+
commitNewChildToFragmentInstance: typeof noop;
|
|
115
|
+
deleteChildFromFragmentInstance: typeof noop;
|
|
116
|
+
createViewTransitionInstance: (name: string) => {
|
|
117
|
+
name: string;
|
|
118
|
+
autoName: null;
|
|
119
|
+
};
|
|
120
|
+
applyViewTransitionName: typeof noop;
|
|
121
|
+
restoreViewTransitionName: typeof noop;
|
|
122
|
+
cancelViewTransitionName: typeof noop;
|
|
123
|
+
cancelRootViewTransitionName: typeof noop;
|
|
124
|
+
restoreRootViewTransitionName: typeof noop;
|
|
125
|
+
cloneRootViewTransitionContainer: typeof noop;
|
|
126
|
+
removeRootViewTransitionClone: typeof noop;
|
|
127
|
+
measureInstance: typeof NULL;
|
|
128
|
+
measureClonedInstance: typeof NULL;
|
|
129
|
+
wasInstanceInViewport: typeof FALSE;
|
|
130
|
+
hasInstanceChanged: typeof FALSE;
|
|
131
|
+
hasInstanceAffectedParent: typeof FALSE;
|
|
132
|
+
startViewTransition: typeof NULL;
|
|
133
|
+
startGestureTransition: typeof NULL;
|
|
134
|
+
stopViewTransition: typeof noop;
|
|
135
|
+
addViewTransitionFinishedListener: typeof noop;
|
|
136
|
+
getCurrentGestureOffset: () => number;
|
|
137
|
+
isSuspenseInstancePending: typeof FALSE;
|
|
138
|
+
isSuspenseInstanceFallback: typeof FALSE;
|
|
139
|
+
getSuspenseInstanceFallbackErrorDetails: () => {};
|
|
140
|
+
registerSuspenseInstanceRetry: typeof noop;
|
|
141
|
+
getNextHydratableSibling: typeof NULL;
|
|
142
|
+
getNextHydratableSiblingAfterSingleton: typeof NULL;
|
|
143
|
+
getFirstHydratableChild: typeof NULL;
|
|
144
|
+
getFirstHydratableChildWithinContainer: typeof NULL;
|
|
145
|
+
getFirstHydratableChildWithinActivityInstance: typeof NULL;
|
|
146
|
+
getFirstHydratableChildWithinSingleton: typeof NULL;
|
|
147
|
+
getFirstHydratableChildWithinSuspenseInstance: typeof NULL;
|
|
148
|
+
canHydrateInstance: typeof NULL;
|
|
149
|
+
canHydrateTextInstance: typeof NULL;
|
|
150
|
+
canHydrateActivityInstance: typeof NULL;
|
|
151
|
+
canHydrateSuspenseInstance: typeof NULL;
|
|
152
|
+
canHydrateFormStateMarker: typeof NULL;
|
|
153
|
+
isFormStateMarkerMatching: typeof FALSE;
|
|
154
|
+
hydrateInstance: typeof noop;
|
|
155
|
+
hydrateTextInstance: typeof noop;
|
|
156
|
+
hydrateActivityInstance: typeof noop;
|
|
157
|
+
hydrateSuspenseInstance: typeof noop;
|
|
158
|
+
getNextHydratableInstanceAfterActivityInstance: typeof NULL;
|
|
159
|
+
getNextHydratableInstanceAfterSuspenseInstance: typeof NULL;
|
|
160
|
+
commitHydratedInstance: typeof noop;
|
|
161
|
+
commitHydratedContainer: typeof noop;
|
|
162
|
+
commitHydratedSuspenseInstance: typeof noop;
|
|
163
|
+
finalizeHydratedChildren: typeof FALSE;
|
|
164
|
+
flushHydrationEvents: typeof noop;
|
|
165
|
+
clearActivityBoundary: typeof FALSE;
|
|
166
|
+
clearSuspenseBoundary: typeof FALSE;
|
|
167
|
+
clearActivityBoundaryFromContainer: typeof FALSE;
|
|
168
|
+
clearSuspenseBoundaryFromContainer: typeof FALSE;
|
|
169
|
+
hideDehydratedBoundary: typeof noop;
|
|
170
|
+
unhideDehydratedBoundary: typeof noop;
|
|
171
|
+
shouldDeleteUnhydratedTailInstances: typeof FALSE;
|
|
172
|
+
diffHydratedPropsForDevWarnings: typeof NULL;
|
|
173
|
+
diffHydratedTextForDevWarnings: typeof NULL;
|
|
174
|
+
describeHydratableInstanceForDevWarnings: typeof NULL;
|
|
175
|
+
validateHydratableInstance: typeof TRUE;
|
|
176
|
+
validateHydratableTextInstance: typeof TRUE;
|
|
177
|
+
isHostHoistableType: typeof FALSE;
|
|
178
|
+
getHoistableRoot: typeof NULL;
|
|
179
|
+
getResource: typeof NULL;
|
|
180
|
+
acquireResource: typeof noop;
|
|
181
|
+
releaseResource: typeof noop;
|
|
182
|
+
hydrateHoistable: typeof noop;
|
|
183
|
+
mountHoistable: typeof noop;
|
|
184
|
+
unmountHoistable: typeof noop;
|
|
185
|
+
createHoistableInstance: typeof NULL;
|
|
186
|
+
prepareToCommitHoistables: typeof noop;
|
|
187
|
+
mayResourceSuspendCommit: typeof FALSE;
|
|
188
|
+
preloadResource: typeof TRUE;
|
|
189
|
+
suspendResource: typeof noop;
|
|
190
|
+
resolveSingletonInstance: typeof NULL;
|
|
191
|
+
acquireSingletonInstance: typeof noop;
|
|
192
|
+
releaseSingletonInstance: typeof noop;
|
|
193
|
+
isHostSingletonType: typeof FALSE;
|
|
194
|
+
isSingletonScope: typeof FALSE;
|
|
195
|
+
findFiberRoot: typeof NULL;
|
|
196
|
+
getBoundingRect: typeof NULL;
|
|
197
|
+
getTextContent: typeof NULL;
|
|
198
|
+
isHiddenSubtree: typeof FALSE;
|
|
199
|
+
matchAccessibilityRole: typeof FALSE;
|
|
200
|
+
setFocusIfFocusable: typeof FALSE;
|
|
201
|
+
setupIntersectionObserver: typeof noop;
|
|
202
|
+
};
|
|
203
|
+
export {};
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import type { HTMLAttributes, ReactElement, ReactNode, Ref } from 'react';
|
|
2
|
+
import type { IslandAppLike, IslandAppProps, IslandClient, IslandContract, IslandContractEventHandler, IslandContractProps, IslandHandle, IslandWorkerOptions, Mode } from '@atolljs/islands';
|
|
3
|
+
export type { IslandAppLike, IslandAppProps, IslandHandle };
|
|
4
|
+
/** What `app` accepts: the registry name, or a component/def to resolve. */
|
|
5
|
+
export type IslandAppRef<A> = A | string;
|
|
6
|
+
export interface IslandProps<A = string> extends Omit<HTMLAttributes<HTMLDivElement>, 'children'> {
|
|
7
|
+
/**
|
|
8
|
+
* Which app to mount — the `apps` registry key ('charts') or the app
|
|
9
|
+
* itself (`ChartsApp`, an `islandApp(...)`-stamped def, `{ imperative }`).
|
|
10
|
+
* A reference infers `props` from its own signature. Optional against a
|
|
11
|
+
* `defineMonoWorker` (1:1) worker — its single app mounts regardless.
|
|
12
|
+
*/
|
|
13
|
+
app?: IslandAppRef<A>;
|
|
14
|
+
/**
|
|
15
|
+
* How to reach the worker — either a bundler-detectable factory
|
|
16
|
+
* `() => new Worker(new URL('./x.worker.ts', import.meta.url), { type: 'module' })`
|
|
17
|
+
* (the common case) or a pre-connected `IslandClient`. Exactly one is required.
|
|
18
|
+
*/
|
|
19
|
+
worker?: (() => Worker) | URL;
|
|
20
|
+
client?: IslandClient;
|
|
21
|
+
/**
|
|
22
|
+
* Extra client options — taskTimeout, respawn, lazy… (always one
|
|
23
|
+
* dedicated worker, no pool). `doorbell: false` builds the shorthand client message-only — no
|
|
24
|
+
* SharedArrayBuffer, no COOP/COEP requirement (same as `mode: 'poll'` on
|
|
25
|
+
* a direct `mountIsland` shorthand mount).
|
|
26
|
+
*/
|
|
27
|
+
workerOptions?: IslandWorkerOptions & {
|
|
28
|
+
doorbell?: boolean;
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* Initial flush mode — 'push' (default) drives op replay off the
|
|
32
|
+
* shared-memory doorbell (needs cross-origin isolation); 'poll' drains on
|
|
33
|
+
* a 50ms interval and, for `worker`-shorthand mounts, builds the client
|
|
34
|
+
* doorbell-free — no SharedArrayBuffer, no COOP/COEP requirement.
|
|
35
|
+
* Mount-time only; switch later via the handle's `setMode`.
|
|
36
|
+
*/
|
|
37
|
+
mode?: Mode;
|
|
38
|
+
/** Renderer tag reported to devtools on mount ('react', 'vue', …). */
|
|
39
|
+
framework?: string;
|
|
40
|
+
/** Initial + updated root props — serialized to the worker. */
|
|
41
|
+
props?: IslandAppProps<A>;
|
|
42
|
+
/** Island → shell channel: every `emit` op lands here. */
|
|
43
|
+
onEvent?: (name: string, payload: unknown) => void;
|
|
44
|
+
/**
|
|
45
|
+
* Transclusion slots — a worker `<div data-atoll-slot="name">` becomes a
|
|
46
|
+
* React portal mount point. The element's contents are rendered by React
|
|
47
|
+
* on the main thread; the worker only owns the empty anchor.
|
|
48
|
+
*/
|
|
49
|
+
slots?: Record<string, ReactNode>;
|
|
50
|
+
/** Fired after each applied op batch — stats hooks. */
|
|
51
|
+
onActivity?: () => void;
|
|
52
|
+
/** The mounted handle (pid, updateProps, flush, setMode…) once ready. */
|
|
53
|
+
onReady?: (island: IslandHandle) => void;
|
|
54
|
+
/** Mount/update errors surface here instead of an unhandled rejection. */
|
|
55
|
+
onError?: (err: unknown) => void;
|
|
56
|
+
/** The container div (React 19 ref-as-prop). */
|
|
57
|
+
ref?: Ref<HTMLDivElement>;
|
|
58
|
+
}
|
|
59
|
+
export declare function Island<A = string>({ app, worker, client: clientProp, workerOptions, mode, framework, props, onEvent, onActivity, slots, onReady, onError, ref, ...rest }: IslandProps<A>): ReactElement;
|
|
60
|
+
/**
|
|
61
|
+
* Props consumed by a proxy component itself — everything else forwards to
|
|
62
|
+
* the island as its props.
|
|
63
|
+
*/
|
|
64
|
+
export interface IslandShellProps {
|
|
65
|
+
/** How to reach the worker — see IslandProps.worker/client. */
|
|
66
|
+
worker?: (() => Worker) | URL;
|
|
67
|
+
client?: IslandClient;
|
|
68
|
+
/** See IslandProps.workerOptions — `doorbell` included. */
|
|
69
|
+
workerOptions?: IslandWorkerOptions & {
|
|
70
|
+
doorbell?: boolean;
|
|
71
|
+
};
|
|
72
|
+
/** Initial flush mode — see IslandProps.mode. */
|
|
73
|
+
mode?: Mode;
|
|
74
|
+
/** Devtools renderer tag — see IslandProps.framework. */
|
|
75
|
+
framework?: string;
|
|
76
|
+
/** Island → shell channel: every `emit` op lands here. */
|
|
77
|
+
onEvent?: (name: string, payload: unknown) => void;
|
|
78
|
+
/** Fired after each applied op batch. */
|
|
79
|
+
onActivity?: () => void;
|
|
80
|
+
/** The mounted handle (pid, updateProps, flush, setMode…) once ready. */
|
|
81
|
+
onReady?: (island: IslandHandle) => void;
|
|
82
|
+
/** Mount/update errors surface here instead of an unhandled rejection. */
|
|
83
|
+
onError?: (err: unknown) => void;
|
|
84
|
+
/** Transclusion slots — see IslandProps.slots. */
|
|
85
|
+
slots?: Record<string, ReactNode>;
|
|
86
|
+
/** Rendered while the worker mounts — the lazy-side fallback. */
|
|
87
|
+
fallback?: ReactNode;
|
|
88
|
+
/**
|
|
89
|
+
* Attributes for the island's container div (className/id/style/data-*).
|
|
90
|
+
* `onError` is omitted — it collides with the mount-error callback.
|
|
91
|
+
*/
|
|
92
|
+
containerProps?: Omit<HTMLAttributes<HTMLDivElement>, 'children' | 'onError'>;
|
|
93
|
+
}
|
|
94
|
+
type LazyModule<A> = A | {
|
|
95
|
+
default: A;
|
|
96
|
+
} | {
|
|
97
|
+
app: A | string;
|
|
98
|
+
worker?: (() => Worker) | URL;
|
|
99
|
+
};
|
|
100
|
+
/**
|
|
101
|
+
* The app a lazy module resolves to — unwraps `{ default: A }` and the
|
|
102
|
+
* contract-module shape `{ app: A, worker? }`, passes bare A through.
|
|
103
|
+
*/
|
|
104
|
+
export type LazyResolvedApp<T extends Promise<unknown>> = [
|
|
105
|
+
Awaited<T>
|
|
106
|
+
] extends [never] ? string : Awaited<T> extends {
|
|
107
|
+
readonly islandContract: true;
|
|
108
|
+
} ? Awaited<T> : Awaited<T> extends {
|
|
109
|
+
app: infer A;
|
|
110
|
+
} ? A : Awaited<T> extends {
|
|
111
|
+
default: infer D;
|
|
112
|
+
} ? D : Awaited<T>;
|
|
113
|
+
/**
|
|
114
|
+
* `islandComponent<P>('charts')` — a proxy component for a worker app that
|
|
115
|
+
* the shell NEVER imports. `P` is the contract (usually an `import type` of
|
|
116
|
+
* the worker component's props); the string is the registry key. Against a
|
|
117
|
+
* `defineMonoWorker` (1:1) worker the name can be omitted entirely.
|
|
118
|
+
*
|
|
119
|
+
* ```tsx
|
|
120
|
+
* import type { TableProps } from './worker/apps.js';
|
|
121
|
+
* const TableIsland = islandComponent<TableProps>('data-table');
|
|
122
|
+
* <TableIsland worker={renderWorker} filter={filter} desc={desc} />
|
|
123
|
+
* ```
|
|
124
|
+
*/
|
|
125
|
+
/**
|
|
126
|
+
* `islandComponent(checkoutContract)` — the cross-framework facade: a
|
|
127
|
+
* worker app described by `defineIslandContract` mounts by the contract's
|
|
128
|
+
* `app` key, props come from its schema, and `onEvent` narrows to the
|
|
129
|
+
* declared event vocabulary. The shell imports the CONTRACT module only —
|
|
130
|
+
* never the component or its framework.
|
|
131
|
+
*/
|
|
132
|
+
export declare function islandComponent<C extends IslandContract>(app: C): (props: IslandContractProps<C> & Omit<IslandShellProps, 'onEvent'> & {
|
|
133
|
+
onEvent?: IslandContractEventHandler<C>;
|
|
134
|
+
}) => ReactElement;
|
|
135
|
+
export declare function islandComponent<P extends object = Record<string, unknown>>(app?: string): (props: P & IslandShellProps) => ReactElement;
|
|
136
|
+
export declare function islandComponent<A extends IslandAppLike>(app: A): (props: IslandAppProps<A> & IslandShellProps) => ReactElement;
|
|
137
|
+
/**
|
|
138
|
+
* `lazyIsland(() => import('./worker/apps.js').then(m => ({ default: m.ChartsApp })))`
|
|
139
|
+
* — the `React.lazy` mirror for worker apps. The returned component suspends
|
|
140
|
+
* while the module loads (wrap it in `<Suspense>`), then proxies all props to
|
|
141
|
+
* the island. The dynamic import gives bundlers a split point, so the worker
|
|
142
|
+
* component's dependencies only load when the island mounts.
|
|
143
|
+
*
|
|
144
|
+
* ```tsx
|
|
145
|
+
* const ChartsIsland = lazyIsland(() => import('./worker/apps.js').then(m => ({ default: m.ChartsApp })));
|
|
146
|
+
* <Suspense fallback="loading…"><ChartsIsland width={520} worker={renderWorker} /></Suspense>
|
|
147
|
+
* ```
|
|
148
|
+
*
|
|
149
|
+
* CONTRACT MODULES: for `defineMonoWorker` (1:1) topologies the loader can
|
|
150
|
+
* resolve `{ app, worker }` — the island then carries its own worker factory
|
|
151
|
+
* and call sites need no `worker` prop at all:
|
|
152
|
+
*
|
|
153
|
+
* ```ts
|
|
154
|
+
* // worker/map.island.ts — shell-safe contract (never the .worker.ts entry
|
|
155
|
+
* // itself: defining a worker installs instance globals, don't import it)
|
|
156
|
+
* export { mapApp as app } from './map.js';
|
|
157
|
+
* export const worker = () => new Worker(new URL('./map.worker.ts', import.meta.url), { type: 'module' });
|
|
158
|
+
*
|
|
159
|
+
* // shell.tsx
|
|
160
|
+
* const MapIsland = lazyIsland(() => import('./worker/map.island.js'));
|
|
161
|
+
* <MapIsland /> // app + worker both from the contract
|
|
162
|
+
* ```
|
|
163
|
+
*/
|
|
164
|
+
export declare function lazyIsland<T extends Promise<LazyModule<IslandAppLike>>>(loader: () => T): (props: IslandAppProps<LazyResolvedApp<T>> & IslandShellProps) => ReactElement;
|
|
165
|
+
export default Island;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The React-runtime half of a React island — `react`, `react-reconciler`
|
|
3
|
+
* and the host config live HERE so a worker bundle whose registry holds only
|
|
4
|
+
* non-React apps (Vue/Svelte/Solid/Angular/imperative) never pulls the
|
|
5
|
+
* reconciler in. `@atolljs/react-island/worker` reaches this module through
|
|
6
|
+
* `reactIslandApp`'s mount, inside the RenderedIslandApp contract.
|
|
7
|
+
*/
|
|
8
|
+
import type { ReactElement } from 'react';
|
|
9
|
+
import Reconciler from 'react-reconciler';
|
|
10
|
+
/**
|
|
11
|
+
* A mounted React tree — carries its own reconciler + container and the
|
|
12
|
+
* `render`/`unmountTree`/`flush` verbs the adapter maps onto the
|
|
13
|
+
* RenderedHandle contract. Identity (key/app/pid) lives on the core
|
|
14
|
+
* Instance — this object is only the renderer half.
|
|
15
|
+
*/
|
|
16
|
+
export interface ReactInstance {
|
|
17
|
+
reconciler: ReturnType<typeof Reconciler>;
|
|
18
|
+
container: unknown;
|
|
19
|
+
/**
|
|
20
|
+
* Commit synchronously around `fn`.
|
|
21
|
+
*
|
|
22
|
+
* In 0.34, `updateContainer`/setState only *schedule* work — the actual
|
|
23
|
+
* render+commit happens when the root scheduler task runs (a macrotask via
|
|
24
|
+
* the `scheduler` package). `flushSyncFromReconciler` pins the update
|
|
25
|
+
* priority to the discrete/sync lane for the duration of `fn` and
|
|
26
|
+
* `flushSyncWork` flushes pending sync work, so by the time it returns the
|
|
27
|
+
* mutation hooks have run and the instance's op queue is full.
|
|
28
|
+
*/
|
|
29
|
+
sync(fn: () => void): void;
|
|
30
|
+
/** Schedule a mount/re-render of `el` — call inside `sync`. */
|
|
31
|
+
render(el: ReactElement): void;
|
|
32
|
+
/** Schedule the detach — emits the remove ops; call inside `sync`. */
|
|
33
|
+
unmountTree(): void;
|
|
34
|
+
/** Passive effects + pending sync work — the `flush` task's React branch. */
|
|
35
|
+
flush(): void;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Legacy root (tag 0) — no concurrent features. Container creation is
|
|
39
|
+
* per-instance, not module-level, so a second mount() can't collide with the
|
|
40
|
+
* first instance's tree.
|
|
41
|
+
*/
|
|
42
|
+
export declare function createReactInstance(instance: string): ReactInstance;
|
package/dist/slot.d.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A transclusion slot — renders a leaf element marked `data-atoll-slot`.
|
|
3
|
+
* The element's BOX belongs to this worker tree (layout/style ops apply as
|
|
4
|
+
* usual); its CONTENTS belong to the shell: when the create op lands on the
|
|
5
|
+
* main thread, the island's `slots[name]` callback gets the real element
|
|
6
|
+
* and can mount anything inside — a canvas, Monaco, a main-thread React
|
|
7
|
+
* root — all real DOM with real events and zero wire traffic.
|
|
8
|
+
*
|
|
9
|
+
* Must stay a LEAF in this tree: if worker-side children were appended here
|
|
10
|
+
* their ops would interleave with shell-owned content. There is also no
|
|
11
|
+
* measurement channel — the island knows the box it laid out, not the
|
|
12
|
+
* pixel size the shell put inside it.
|
|
13
|
+
*/
|
|
14
|
+
import type { ReactElement } from 'react';
|
|
15
|
+
export declare function Slot({ name, style }: {
|
|
16
|
+
name: string;
|
|
17
|
+
style?: Record<string, unknown>;
|
|
18
|
+
}): ReactElement;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `SubIsland` — the original name for a nested mount, kept as an alias.
|
|
3
|
+
* New code uses `Island` from `@atolljs/react-island/worker`, which is the
|
|
4
|
+
* same component (and props) the page uses: inside a worker its container
|
|
5
|
+
* is a proxy element, so `mountIsland` routes to the nested path.
|
|
6
|
+
*
|
|
7
|
+
* const subWorker = () =>
|
|
8
|
+
* new Worker(new URL('./counter.worker.tsx', import.meta.url), { type: 'module' });
|
|
9
|
+
*
|
|
10
|
+
* function Dashboard() {
|
|
11
|
+
* return <Island worker={subWorker} app="counter" props={{ label: 'nested' }} />;
|
|
12
|
+
* }
|
|
13
|
+
*
|
|
14
|
+
* `SubIsland` differs only in defaults: it tags devtools mounts as
|
|
15
|
+
* `framework: 'react'` and stamps `data-atoll-sub-island` on its host div.
|
|
16
|
+
*/
|
|
17
|
+
import type { CSSProperties, ReactElement } from 'react';
|
|
18
|
+
import type { IslandClient, IslandHandle, Mode } from '@atolljs/islands/worker';
|
|
19
|
+
export interface SubIslandProps {
|
|
20
|
+
/** Bundler-detectable sub-worker entry, evaluated INSIDE this worker. */
|
|
21
|
+
worker?: (() => Worker) | URL;
|
|
22
|
+
/** A shared `connectIslandWorker` client built inside this worker. */
|
|
23
|
+
client?: IslandClient;
|
|
24
|
+
/** Registry app name — a key of the sub-worker's `apps` map. */
|
|
25
|
+
app?: string;
|
|
26
|
+
/** Wire props for the sub-app's root — serializable data only. */
|
|
27
|
+
props?: Record<string, unknown>;
|
|
28
|
+
/** Sub-island emit channel — a plain worker-side call. */
|
|
29
|
+
onEvent?: (name: string, payload: unknown) => void;
|
|
30
|
+
/** Flush transport — 'push' (default) or 'poll'. */
|
|
31
|
+
mode?: Mode;
|
|
32
|
+
className?: string;
|
|
33
|
+
style?: CSSProperties;
|
|
34
|
+
/** Receives the mounted handle (pid, instance, updateProps, destroy). */
|
|
35
|
+
onReady?: (handle: IslandHandle) => void;
|
|
36
|
+
}
|
|
37
|
+
export declare function SubIsland(props: SubIslandProps): ReactElement;
|
package/dist/worker.d.ts
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@atolljs/react-island/worker` — the worker-side half of React
|
|
3
|
+
* islands: `reactIslandApp(Component)` wraps a React component as a islands
|
|
4
|
+
* `RenderedIslandApp`, so it can sit in a `definePolyWorker` `apps`
|
|
5
|
+
* registry beside Vue/Svelte/Solid/Angular and imperative apps:
|
|
6
|
+
*
|
|
7
|
+
* // render.worker.ts — React-only registry
|
|
8
|
+
* import { defineReactPolyWorker } from '@atolljs/react-island/worker';
|
|
9
|
+
* export const renderWorker = defineReactPolyWorker({
|
|
10
|
+
* apps: { controls: ControlsApp, charts: ChartsApp },
|
|
11
|
+
* });
|
|
12
|
+
*
|
|
13
|
+
* // one-app worker
|
|
14
|
+
* import { defineReactMonoWorker } from '@atolljs/react-island/worker';
|
|
15
|
+
* export const worker = defineReactMonoWorker(ChartsApp);
|
|
16
|
+
*
|
|
17
|
+
* // mixed-framework registry — compose the adapters directly
|
|
18
|
+
* import { definePolyWorker } from '@atolljs/islands/worker';
|
|
19
|
+
* import { reactIslandApp } from '@atolljs/react-island/worker';
|
|
20
|
+
* export const renderWorker = definePolyWorker({ apps: {
|
|
21
|
+
* charts: reactIslandApp(ChartsApp),
|
|
22
|
+
* badge: vueIslandApp(VueBadge),
|
|
23
|
+
* }});
|
|
24
|
+
*
|
|
25
|
+
* The reconciler + host config live in `reactInstance.ts`/`hostConfig.ts`:
|
|
26
|
+
* importing THIS module is what pulls `react` + `react-reconciler` into a
|
|
27
|
+
* worker bundle (once, shared across every React app in the registry).
|
|
28
|
+
* `@atolljs/islands/worker` itself stays framework-neutral — non-React
|
|
29
|
+
* registries never see this code.
|
|
30
|
+
*/
|
|
31
|
+
import { type ComponentType } from 'react';
|
|
32
|
+
import type { SharedMemory, WorkerDefinition } from '@atolljs/core';
|
|
33
|
+
import type { DoorbellSpec, ImperativeIslandApp, IslandContract, IslandWorkerMethods, RenderedIslandApp } from '@atolljs/islands/worker';
|
|
34
|
+
export { emit, runInInstance } from '@atolljs/islands/worker';
|
|
35
|
+
export { Slot } from './slot.js';
|
|
36
|
+
export { Island, islandComponent, lazyIsland } from './index.js';
|
|
37
|
+
export type { IslandProps, IslandShellProps, IslandAppRef } from './index.js';
|
|
38
|
+
export { connectIslandWorker } from '@atolljs/islands/worker';
|
|
39
|
+
export { SubIsland } from './subIsland.js';
|
|
40
|
+
export type { SubIslandProps } from './subIsland.js';
|
|
41
|
+
type AnyComponent = ComponentType<Record<string, unknown>>;
|
|
42
|
+
/**
|
|
43
|
+
* Wrap a React component as a `RenderedIslandApp` — `mount` creates the
|
|
44
|
+
* instance's reconciler + container and renders the initial tree inside the
|
|
45
|
+
* sync lane; `update` re-renders with new props (the reconciler diffs, only
|
|
46
|
+
* changed ops emit); `sync` is the dispatch-time commit pin; `dispose`
|
|
47
|
+
* schedules the unmount; `flush` drains passive effects + pending sync work
|
|
48
|
+
* for the `flush` task.
|
|
49
|
+
*
|
|
50
|
+
* A stamped component (`islandApp('name', Comp)`) keeps its stamp through
|
|
51
|
+
* the wrap, so registry warn-checks and `defineMonoWorker` name resolution
|
|
52
|
+
* still see the shell-facing name.
|
|
53
|
+
*/
|
|
54
|
+
export declare function reactIslandApp(App: AnyComponent, contract?: IslandContract): RenderedIslandApp;
|
|
55
|
+
/** Stamp + wrap in one step — `reactIsland('charts', ChartsApp)` yields the
|
|
56
|
+
* registry value AND the name the shell mounts by. */
|
|
57
|
+
export declare const reactIsland: (name: string, App: AnyComponent, contract?: IslandContract) => RenderedIslandApp & {
|
|
58
|
+
readonly islandAppName: string;
|
|
59
|
+
};
|
|
60
|
+
export interface ReactPolyWorkerRegistry {
|
|
61
|
+
/**
|
|
62
|
+
* Name → React component (or `{ imperative }` app) registry, mirroring
|
|
63
|
+
* `definePolyWorker({ apps })`. Components pass through `reactIslandApp`;
|
|
64
|
+
* imperative defs and already-wrapped apps (e.g. `reactIsland()` results)
|
|
65
|
+
* register as-is.
|
|
66
|
+
*/
|
|
67
|
+
apps: Record<string, AnyComponent | ImperativeIslandApp | RenderedIslandApp>;
|
|
68
|
+
/** Doorbell contract override — forwarded to `definePolyWorker`. */
|
|
69
|
+
sharedMemory?: SharedMemory<DoorbellSpec>;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* `definePolyWorker` for React apps — maps each component in the registry
|
|
73
|
+
* through `reactIslandApp` and delegates. One worker, many React islands;
|
|
74
|
+
* for a mixed-framework registry call `definePolyWorker` with the per-app
|
|
75
|
+
* adapters instead.
|
|
76
|
+
*/
|
|
77
|
+
export declare function defineReactPolyWorker(registry: ReactPolyWorkerRegistry, options?: {
|
|
78
|
+
sharedMemory?: SharedMemory<DoorbellSpec>;
|
|
79
|
+
}): WorkerDefinition<DoorbellSpec, IslandWorkerMethods>;
|
|
80
|
+
/**
|
|
81
|
+
* `defineMonoWorker` for React apps — one worker pinned to a single
|
|
82
|
+
* component (or `{ imperative }` app), the isolated-bundle host shape.
|
|
83
|
+
*/
|
|
84
|
+
export declare function defineReactMonoWorker(app: AnyComponent | ImperativeIslandApp | RenderedIslandApp, options?: {
|
|
85
|
+
sharedMemory?: SharedMemory<DoorbellSpec>;
|
|
86
|
+
contract?: IslandContract;
|
|
87
|
+
}): WorkerDefinition<DoorbellSpec, IslandWorkerMethods>;
|
package/dist/worker.js
ADDED
|
@@ -0,0 +1,450 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Island,
|
|
3
|
+
islandComponent,
|
|
4
|
+
lazyIsland
|
|
5
|
+
} from "./chunk-Q7NIXFLZ.js";
|
|
6
|
+
|
|
7
|
+
// src/worker.ts
|
|
8
|
+
import { createElement } from "react";
|
|
9
|
+
import {
|
|
10
|
+
defineMonoWorker,
|
|
11
|
+
definePolyWorker,
|
|
12
|
+
islandApp,
|
|
13
|
+
islandAppNameOf,
|
|
14
|
+
withContract
|
|
15
|
+
} from "@atolljs/islands/worker";
|
|
16
|
+
|
|
17
|
+
// src/reactInstance.ts
|
|
18
|
+
import Reconciler from "react-reconciler";
|
|
19
|
+
|
|
20
|
+
// src/hostConfig.ts
|
|
21
|
+
import { createContext } from "react";
|
|
22
|
+
import {
|
|
23
|
+
bumpOpsVersion,
|
|
24
|
+
docForInstance,
|
|
25
|
+
getActiveInstance,
|
|
26
|
+
getLastActiveInstance,
|
|
27
|
+
instances,
|
|
28
|
+
newElement,
|
|
29
|
+
newText,
|
|
30
|
+
pushOp,
|
|
31
|
+
serializeProps,
|
|
32
|
+
unregisterHandler
|
|
33
|
+
} from "@atolljs/islands/worker";
|
|
34
|
+
var currentUpdatePriority = 0;
|
|
35
|
+
var DEFAULT_EVENT_PRIORITY = 32;
|
|
36
|
+
var NotPendingTransition = Object.freeze({ pending: false, data: null, method: null, action: null });
|
|
37
|
+
var HostTransitionContext = createContext(NotPendingTransition);
|
|
38
|
+
var noop = () => {
|
|
39
|
+
};
|
|
40
|
+
var NULL = () => null;
|
|
41
|
+
var FALSE = () => false;
|
|
42
|
+
var TRUE = () => true;
|
|
43
|
+
var publicInstanceFor = (instance) => docForInstance(instance.instance).adopt(instance);
|
|
44
|
+
var containerParentId = (container) => {
|
|
45
|
+
const inst = container?.instance;
|
|
46
|
+
if (inst && typeof inst.id === "number") return inst.id;
|
|
47
|
+
const id = container?.id;
|
|
48
|
+
return typeof id === "number" ? id : 0;
|
|
49
|
+
};
|
|
50
|
+
var containerInstance = (container) => {
|
|
51
|
+
const inner = container?.instance;
|
|
52
|
+
if (typeof inner === "string") return inner;
|
|
53
|
+
return inner?.instance ?? (getActiveInstance() !== "" ? getActiveInstance() : getLastActiveInstance());
|
|
54
|
+
};
|
|
55
|
+
var SVG_NS = "http://www.w3.org/2000/svg";
|
|
56
|
+
var MATH_NS = "http://www.w3.org/1998/Math/MathML";
|
|
57
|
+
var HTML_CTX = Object.freeze({});
|
|
58
|
+
var SVG_CTX = Object.freeze({ ns: SVG_NS });
|
|
59
|
+
var MATH_CTX = Object.freeze({ ns: MATH_NS });
|
|
60
|
+
var childHostContextFor = (parent, type) => {
|
|
61
|
+
if (type === "svg") return SVG_CTX;
|
|
62
|
+
if (type === "math") return MATH_CTX;
|
|
63
|
+
if (type === "foreignObject" || type === "desc" || type === "title") return HTML_CTX;
|
|
64
|
+
return parent;
|
|
65
|
+
};
|
|
66
|
+
var hostConfig = {
|
|
67
|
+
// Identity / capabilities
|
|
68
|
+
rendererVersion: "0.34.0",
|
|
69
|
+
rendererPackageName: "@atolljs/react-island",
|
|
70
|
+
extraDevToolsConfig: null,
|
|
71
|
+
isPrimaryRenderer: true,
|
|
72
|
+
warnsIfNotActing: false,
|
|
73
|
+
supportsMutation: true,
|
|
74
|
+
supportsPersistence: false,
|
|
75
|
+
supportsHydration: false,
|
|
76
|
+
supportsResources: false,
|
|
77
|
+
supportsSingletons: false,
|
|
78
|
+
supportsMicrotasks: false,
|
|
79
|
+
supportsTestSelectors: false,
|
|
80
|
+
getPublicInstance: (instance) => publicInstanceFor(instance),
|
|
81
|
+
// Host contexts must be non-null objects (the reconciler pushes them on a
|
|
82
|
+
// context stack and warns "Expected host context to exist" on null). This
|
|
83
|
+
// renderer carries no per-subtree context — echo a single frozen sentinel.
|
|
84
|
+
// Host contexts must be non-null objects (the reconciler warns on null).
|
|
85
|
+
// Ours carries only the element namespace: <svg>/<math> enter their
|
|
86
|
+
// namespaces; foreignObject/desc/title are HTML-integration points that
|
|
87
|
+
// flip children back to HTML; everything else inherits the parent's.
|
|
88
|
+
// For portals, this becomes the context the portal subtree renders under —
|
|
89
|
+
// derived from the container's own namespace so children of an SVG portal
|
|
90
|
+
// target (recharts zIndex <g> layers) stay in the SVG namespace.
|
|
91
|
+
getRootHostContext: (container) => {
|
|
92
|
+
const ns = container?.instance?.ns;
|
|
93
|
+
if (ns === SVG_NS) return SVG_CTX;
|
|
94
|
+
if (ns === MATH_NS) return MATH_CTX;
|
|
95
|
+
return HTML_CTX;
|
|
96
|
+
},
|
|
97
|
+
getChildHostContext: (parent, type) => childHostContextFor(parent, type),
|
|
98
|
+
prepareForCommit: NULL,
|
|
99
|
+
// The doorbell: every commit bumps opsVersion once — the main thread's
|
|
100
|
+
// observe() wakes (Atomics.waitAsync) and flushes the op queue, so commits
|
|
101
|
+
// made outside task calls (effects, timers, async setState) arrive as a
|
|
102
|
+
// push instead of waiting on a poll. Task-returned ops bump too; the
|
|
103
|
+
// follow-up flush just finds an empty queue.
|
|
104
|
+
resetAfterCommit: bumpOpsVersion,
|
|
105
|
+
// Creation — emit ops
|
|
106
|
+
createInstance: (type, props, rootContainer, hostContext, _internalHandle) => {
|
|
107
|
+
const ctx = childHostContextFor(hostContext, type);
|
|
108
|
+
const instance = newElement(type, props, ctx.ns, containerInstance(rootContainer));
|
|
109
|
+
pushOp(instance.instance, {
|
|
110
|
+
t: "create",
|
|
111
|
+
id: instance.id,
|
|
112
|
+
type,
|
|
113
|
+
props: instance.props,
|
|
114
|
+
ns: instance.ns
|
|
115
|
+
});
|
|
116
|
+
return instance;
|
|
117
|
+
},
|
|
118
|
+
createTextInstance: (text, rootContainer, _hostContext, _internalHandle) => {
|
|
119
|
+
const instance = newText(text, containerInstance(rootContainer));
|
|
120
|
+
pushOp(instance.instance, { t: "text", id: instance.id, text });
|
|
121
|
+
return instance;
|
|
122
|
+
},
|
|
123
|
+
finalizeInitialChildren: FALSE,
|
|
124
|
+
// Always false: string/number children become real text instances, so the
|
|
125
|
+
// tree arrives as linear ops and `utext` handles every text update. (The
|
|
126
|
+
// `true` path would expect commitUpdate to set textContent from
|
|
127
|
+
// props.children — which we deliberately never serialize.)
|
|
128
|
+
shouldSetTextContent: FALSE,
|
|
129
|
+
// Tree wiring — emit ops
|
|
130
|
+
appendInitialChild: (parent, child) => {
|
|
131
|
+
pushOp(parent.instance, { t: "append", parent: parent.id, child: child.id });
|
|
132
|
+
},
|
|
133
|
+
appendChild: (parent, child) => {
|
|
134
|
+
pushOp(parent.instance, { t: "append", parent: parent.id, child: child.id });
|
|
135
|
+
},
|
|
136
|
+
appendChildToContainer: (container, child) => {
|
|
137
|
+
pushOp(child.instance, { t: "append", parent: containerParentId(container), child: child.id });
|
|
138
|
+
},
|
|
139
|
+
insertBefore: (parent, child, before) => {
|
|
140
|
+
pushOp(parent.instance, { t: "append", parent: parent.id, child: child.id, before: before.id });
|
|
141
|
+
},
|
|
142
|
+
insertInContainerBefore: (container, child, before) => {
|
|
143
|
+
pushOp(child.instance, {
|
|
144
|
+
t: "append",
|
|
145
|
+
parent: containerParentId(container),
|
|
146
|
+
child: child.id,
|
|
147
|
+
before: before.id
|
|
148
|
+
});
|
|
149
|
+
},
|
|
150
|
+
removeChild: (_parent, child) => {
|
|
151
|
+
pushOp(child.instance, { t: "remove", child: child.id });
|
|
152
|
+
},
|
|
153
|
+
removeChildFromContainer: (_container, child) => {
|
|
154
|
+
pushOp(child.instance, { t: "remove", child: child.id });
|
|
155
|
+
},
|
|
156
|
+
// No instance argument — routed by the task's active instance (only reached
|
|
157
|
+
// inside mount/remount's syncCommit).
|
|
158
|
+
clearContainer: () => {
|
|
159
|
+
pushOp(getActiveInstance(), { t: "clear" });
|
|
160
|
+
},
|
|
161
|
+
// Updates — emit ops
|
|
162
|
+
commitUpdate: (instance, _type, _oldProps, newProps, _internalHandle) => {
|
|
163
|
+
instance.props = serializeProps(instance, newProps);
|
|
164
|
+
pushOp(instance.instance, { t: "update", id: instance.id, props: instance.props });
|
|
165
|
+
},
|
|
166
|
+
commitTextUpdate: (instance, _oldText, newText2) => {
|
|
167
|
+
instance.text = newText2;
|
|
168
|
+
pushOp(instance.instance, { t: "utext", id: instance.id, text: newText2 });
|
|
169
|
+
},
|
|
170
|
+
commitMount: noop,
|
|
171
|
+
// Unreachable while shouldSetTextContent is always false; still emit the
|
|
172
|
+
// spec'd op for completeness.
|
|
173
|
+
resetTextContent: (instance) => {
|
|
174
|
+
instance.props = {};
|
|
175
|
+
pushOp(instance.instance, { t: "update", id: instance.id, props: {} });
|
|
176
|
+
},
|
|
177
|
+
// Suspense visibility — `hidden` is a global HTML attribute the main
|
|
178
|
+
// thread applies like any other prop.
|
|
179
|
+
hideInstance: (instance) => {
|
|
180
|
+
pushOp(instance.instance, { t: "update", id: instance.id, props: { ...instance.props, hidden: true } });
|
|
181
|
+
},
|
|
182
|
+
unhideInstance: (instance) => {
|
|
183
|
+
pushOp(instance.instance, { t: "update", id: instance.id, props: { ...instance.props } });
|
|
184
|
+
},
|
|
185
|
+
hideTextInstance: (instance) => {
|
|
186
|
+
pushOp(instance.instance, { t: "utext", id: instance.id, text: "" });
|
|
187
|
+
},
|
|
188
|
+
unhideTextInstance: (instance, text) => {
|
|
189
|
+
pushOp(instance.instance, { t: "utext", id: instance.id, text });
|
|
190
|
+
},
|
|
191
|
+
// GC hook — drop the record and its handler slots.
|
|
192
|
+
detachDeletedInstance: (instance) => {
|
|
193
|
+
instances.delete(instance.id);
|
|
194
|
+
if (instance.kind === "element") {
|
|
195
|
+
for (const hid of Object.values(instance.listenerSlots)) unregisterHandler(hid);
|
|
196
|
+
}
|
|
197
|
+
},
|
|
198
|
+
// Scheduler
|
|
199
|
+
scheduleTimeout: setTimeout,
|
|
200
|
+
cancelTimeout: clearTimeout,
|
|
201
|
+
noTimeout: -1,
|
|
202
|
+
// supportsMicrotasks is false, so the reconciler schedules root work via
|
|
203
|
+
// the `scheduler` package (MessageChannel in a worker). This export is
|
|
204
|
+
// still destructured — provide a real one for completeness.
|
|
205
|
+
scheduleMicrotask: (fn) => queueMicrotask(fn),
|
|
206
|
+
// Event priorities (see note above — values are lane numbers).
|
|
207
|
+
setCurrentUpdatePriority: (p) => {
|
|
208
|
+
currentUpdatePriority = p;
|
|
209
|
+
},
|
|
210
|
+
getCurrentUpdatePriority: () => currentUpdatePriority,
|
|
211
|
+
resolveUpdatePriority: () => currentUpdatePriority !== 0 ? currentUpdatePriority : DEFAULT_EVENT_PRIORITY,
|
|
212
|
+
resolveEventType: NULL,
|
|
213
|
+
resolveEventTimeStamp: () => -1.1,
|
|
214
|
+
trackSchedulerEvent: noop,
|
|
215
|
+
shouldAttemptEagerTransition: FALSE,
|
|
216
|
+
// Events/portals/scopes — no DOM event system exists worker-side.
|
|
217
|
+
getInstanceFromNode: NULL,
|
|
218
|
+
beforeActiveInstanceBlur: noop,
|
|
219
|
+
afterActiveInstanceBlur: noop,
|
|
220
|
+
preparePortalMount: noop,
|
|
221
|
+
prepareScopeUpdate: noop,
|
|
222
|
+
getInstanceFromScope: NULL,
|
|
223
|
+
// React 19 "suspend on commit" — never suspend.
|
|
224
|
+
requestPostPaintCallback: noop,
|
|
225
|
+
maySuspendCommit: FALSE,
|
|
226
|
+
maySuspendCommitOnUpdate: FALSE,
|
|
227
|
+
maySuspendCommitInSyncRender: FALSE,
|
|
228
|
+
preloadInstance: TRUE,
|
|
229
|
+
startSuspendingCommit: noop,
|
|
230
|
+
suspendInstance: noop,
|
|
231
|
+
suspendOnActiveViewTransition: noop,
|
|
232
|
+
waitForCommitToBeReady: NULL,
|
|
233
|
+
getSuspendedCommitReason: () => 0,
|
|
234
|
+
// Transitions / forms / console
|
|
235
|
+
NotPendingTransition,
|
|
236
|
+
HostTransitionContext,
|
|
237
|
+
resetFormInstance: noop,
|
|
238
|
+
bindToConsole: (methodName, args) => console[methodName]?.bind(console, ...args) ?? noop,
|
|
239
|
+
// Persistence (supportsPersistence: false — destructured, never called)
|
|
240
|
+
createContainerChildSet: () => [],
|
|
241
|
+
appendChildToContainerChildSet: noop,
|
|
242
|
+
finalizeContainerChildren: noop,
|
|
243
|
+
replaceContainerChildren: noop,
|
|
244
|
+
cloneInstance: NULL,
|
|
245
|
+
cloneMutableInstance: (instance) => instance,
|
|
246
|
+
cloneMutableTextInstance: (instance) => instance,
|
|
247
|
+
cloneHiddenInstance: (instance) => instance,
|
|
248
|
+
cloneHiddenTextInstance: (instance) => instance,
|
|
249
|
+
// Fragment instances (only used for `<Fragment ref={…}>` — unimplemented)
|
|
250
|
+
createFragmentInstance: () => ({}),
|
|
251
|
+
updateFragmentInstanceFiber: noop,
|
|
252
|
+
commitNewChildToFragmentInstance: noop,
|
|
253
|
+
deleteChildFromFragmentInstance: noop,
|
|
254
|
+
// View transitions (React 19) — all stubs
|
|
255
|
+
createViewTransitionInstance: (name) => ({ name, autoName: null }),
|
|
256
|
+
applyViewTransitionName: noop,
|
|
257
|
+
restoreViewTransitionName: noop,
|
|
258
|
+
cancelViewTransitionName: noop,
|
|
259
|
+
cancelRootViewTransitionName: noop,
|
|
260
|
+
restoreRootViewTransitionName: noop,
|
|
261
|
+
cloneRootViewTransitionContainer: noop,
|
|
262
|
+
removeRootViewTransitionClone: noop,
|
|
263
|
+
measureInstance: NULL,
|
|
264
|
+
measureClonedInstance: NULL,
|
|
265
|
+
wasInstanceInViewport: FALSE,
|
|
266
|
+
hasInstanceChanged: FALSE,
|
|
267
|
+
hasInstanceAffectedParent: FALSE,
|
|
268
|
+
startViewTransition: NULL,
|
|
269
|
+
startGestureTransition: NULL,
|
|
270
|
+
stopViewTransition: noop,
|
|
271
|
+
addViewTransitionFinishedListener: noop,
|
|
272
|
+
getCurrentGestureOffset: () => 0,
|
|
273
|
+
// Hydration (supportsHydration: false — destructured, never called)
|
|
274
|
+
isSuspenseInstancePending: FALSE,
|
|
275
|
+
isSuspenseInstanceFallback: FALSE,
|
|
276
|
+
getSuspenseInstanceFallbackErrorDetails: () => ({}),
|
|
277
|
+
registerSuspenseInstanceRetry: noop,
|
|
278
|
+
getNextHydratableSibling: NULL,
|
|
279
|
+
getNextHydratableSiblingAfterSingleton: NULL,
|
|
280
|
+
getFirstHydratableChild: NULL,
|
|
281
|
+
getFirstHydratableChildWithinContainer: NULL,
|
|
282
|
+
getFirstHydratableChildWithinActivityInstance: NULL,
|
|
283
|
+
getFirstHydratableChildWithinSingleton: NULL,
|
|
284
|
+
getFirstHydratableChildWithinSuspenseInstance: NULL,
|
|
285
|
+
canHydrateInstance: NULL,
|
|
286
|
+
canHydrateTextInstance: NULL,
|
|
287
|
+
canHydrateActivityInstance: NULL,
|
|
288
|
+
canHydrateSuspenseInstance: NULL,
|
|
289
|
+
canHydrateFormStateMarker: NULL,
|
|
290
|
+
isFormStateMarkerMatching: FALSE,
|
|
291
|
+
hydrateInstance: noop,
|
|
292
|
+
hydrateTextInstance: noop,
|
|
293
|
+
hydrateActivityInstance: noop,
|
|
294
|
+
hydrateSuspenseInstance: noop,
|
|
295
|
+
getNextHydratableInstanceAfterActivityInstance: NULL,
|
|
296
|
+
getNextHydratableInstanceAfterSuspenseInstance: NULL,
|
|
297
|
+
commitHydratedInstance: noop,
|
|
298
|
+
commitHydratedContainer: noop,
|
|
299
|
+
commitHydratedSuspenseInstance: noop,
|
|
300
|
+
finalizeHydratedChildren: FALSE,
|
|
301
|
+
flushHydrationEvents: noop,
|
|
302
|
+
clearActivityBoundary: FALSE,
|
|
303
|
+
clearSuspenseBoundary: FALSE,
|
|
304
|
+
clearActivityBoundaryFromContainer: FALSE,
|
|
305
|
+
clearSuspenseBoundaryFromContainer: FALSE,
|
|
306
|
+
hideDehydratedBoundary: noop,
|
|
307
|
+
unhideDehydratedBoundary: noop,
|
|
308
|
+
shouldDeleteUnhydratedTailInstances: FALSE,
|
|
309
|
+
diffHydratedPropsForDevWarnings: NULL,
|
|
310
|
+
diffHydratedTextForDevWarnings: NULL,
|
|
311
|
+
describeHydratableInstanceForDevWarnings: NULL,
|
|
312
|
+
validateHydratableInstance: TRUE,
|
|
313
|
+
validateHydratableTextInstance: TRUE,
|
|
314
|
+
// Host resources (stylesheets/scripts — supportsResources: false)
|
|
315
|
+
isHostHoistableType: FALSE,
|
|
316
|
+
getHoistableRoot: NULL,
|
|
317
|
+
getResource: NULL,
|
|
318
|
+
acquireResource: noop,
|
|
319
|
+
releaseResource: noop,
|
|
320
|
+
hydrateHoistable: noop,
|
|
321
|
+
mountHoistable: noop,
|
|
322
|
+
unmountHoistable: noop,
|
|
323
|
+
createHoistableInstance: NULL,
|
|
324
|
+
prepareToCommitHoistables: noop,
|
|
325
|
+
mayResourceSuspendCommit: FALSE,
|
|
326
|
+
preloadResource: TRUE,
|
|
327
|
+
suspendResource: noop,
|
|
328
|
+
// Singletons (supportsSingletons: false)
|
|
329
|
+
resolveSingletonInstance: NULL,
|
|
330
|
+
acquireSingletonInstance: noop,
|
|
331
|
+
releaseSingletonInstance: noop,
|
|
332
|
+
isHostSingletonType: FALSE,
|
|
333
|
+
isSingletonScope: FALSE,
|
|
334
|
+
// Test selectors (supportsTestSelectors: false)
|
|
335
|
+
findFiberRoot: NULL,
|
|
336
|
+
getBoundingRect: NULL,
|
|
337
|
+
getTextContent: NULL,
|
|
338
|
+
isHiddenSubtree: FALSE,
|
|
339
|
+
matchAccessibilityRole: FALSE,
|
|
340
|
+
setFocusIfFocusable: FALSE,
|
|
341
|
+
setupIntersectionObserver: noop
|
|
342
|
+
};
|
|
343
|
+
|
|
344
|
+
// src/reactInstance.ts
|
|
345
|
+
function createReactInstance(instance) {
|
|
346
|
+
const reconciler = Reconciler(hostConfig);
|
|
347
|
+
const container = reconciler.createContainer(
|
|
348
|
+
// Instance-stamped root container — createInstance reads `.instance` off it,
|
|
349
|
+
// binding every element to this instance even in out-of-task commits.
|
|
350
|
+
{ id: 0, instance },
|
|
351
|
+
0,
|
|
352
|
+
null,
|
|
353
|
+
false,
|
|
354
|
+
null,
|
|
355
|
+
"",
|
|
356
|
+
console.error,
|
|
357
|
+
// onUncaughtError
|
|
358
|
+
console.error,
|
|
359
|
+
// onCaughtError
|
|
360
|
+
console.error,
|
|
361
|
+
// onRecoverableError
|
|
362
|
+
null
|
|
363
|
+
// onDefaultTransitionIndicator
|
|
364
|
+
);
|
|
365
|
+
return {
|
|
366
|
+
reconciler,
|
|
367
|
+
container,
|
|
368
|
+
sync: (fn) => {
|
|
369
|
+
reconciler.flushSyncFromReconciler(fn);
|
|
370
|
+
reconciler.flushSyncWork();
|
|
371
|
+
},
|
|
372
|
+
render: (el) => reconciler.updateContainer(el, container, null, null),
|
|
373
|
+
unmountTree: () => reconciler.updateContainer(null, container, null, null),
|
|
374
|
+
flush: () => {
|
|
375
|
+
reconciler.flushPassiveEffects();
|
|
376
|
+
reconciler.flushSyncWork();
|
|
377
|
+
}
|
|
378
|
+
};
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
// src/worker.ts
|
|
382
|
+
import { emit, runInInstance } from "@atolljs/islands/worker";
|
|
383
|
+
|
|
384
|
+
// src/slot.tsx
|
|
385
|
+
import { jsx } from "react/jsx-runtime";
|
|
386
|
+
function Slot({ name, style }) {
|
|
387
|
+
return /* @__PURE__ */ jsx("div", { "data-atoll-slot": name, style });
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
// src/worker.ts
|
|
391
|
+
import { connectIslandWorker } from "@atolljs/islands/worker";
|
|
392
|
+
|
|
393
|
+
// src/subIsland.tsx
|
|
394
|
+
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
395
|
+
function SubIsland(props) {
|
|
396
|
+
return /* @__PURE__ */ jsx2(Island, { framework: "react", "data-atoll-sub-island": "", ...props });
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
// src/worker.ts
|
|
400
|
+
function reactIslandApp(App, contract) {
|
|
401
|
+
const app = {
|
|
402
|
+
renderer: "react",
|
|
403
|
+
mount({ instance, props }) {
|
|
404
|
+
const react = createReactInstance(instance);
|
|
405
|
+
const render = (next) => react.sync(() => react.render(createElement(App, next)));
|
|
406
|
+
render(props);
|
|
407
|
+
return {
|
|
408
|
+
update: render,
|
|
409
|
+
sync: (fn) => react.sync(fn),
|
|
410
|
+
dispose: () => react.sync(() => react.unmountTree()),
|
|
411
|
+
flush: () => react.flush()
|
|
412
|
+
};
|
|
413
|
+
}
|
|
414
|
+
};
|
|
415
|
+
const stamp = islandAppNameOf(App);
|
|
416
|
+
if (stamp !== void 0) app.islandAppName = stamp;
|
|
417
|
+
return contract !== void 0 ? withContract(contract, app) : app;
|
|
418
|
+
}
|
|
419
|
+
var reactIsland = (name, App, contract) => islandApp(name, reactIslandApp(App, contract));
|
|
420
|
+
function defineReactPolyWorker(registry, options) {
|
|
421
|
+
const apps = {};
|
|
422
|
+
for (const [key, app] of Object.entries(registry.apps)) {
|
|
423
|
+
apps[key] = typeof app === "function" ? reactIslandApp(app) : app;
|
|
424
|
+
}
|
|
425
|
+
return definePolyWorker({
|
|
426
|
+
apps,
|
|
427
|
+
sharedMemory: registry.sharedMemory ?? options?.sharedMemory
|
|
428
|
+
});
|
|
429
|
+
}
|
|
430
|
+
function defineReactMonoWorker(app, options) {
|
|
431
|
+
const resolved = typeof app === "function" ? reactIslandApp(app) : app;
|
|
432
|
+
return defineMonoWorker(
|
|
433
|
+
options?.contract !== void 0 ? withContract(options.contract, resolved) : resolved,
|
|
434
|
+
options
|
|
435
|
+
);
|
|
436
|
+
}
|
|
437
|
+
export {
|
|
438
|
+
Island,
|
|
439
|
+
Slot,
|
|
440
|
+
SubIsland,
|
|
441
|
+
connectIslandWorker,
|
|
442
|
+
defineReactMonoWorker,
|
|
443
|
+
defineReactPolyWorker,
|
|
444
|
+
emit,
|
|
445
|
+
islandComponent,
|
|
446
|
+
lazyIsland,
|
|
447
|
+
reactIsland,
|
|
448
|
+
reactIslandApp,
|
|
449
|
+
runInInstance
|
|
450
|
+
};
|
package/package.json
CHANGED
|
@@ -1,23 +1,33 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@atolljs/react-island",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.13",
|
|
4
4
|
"description": "React shell components for atoll-islands islands — <Island/>, islandComponent, lazyIsland",
|
|
5
5
|
"homepage": "https://jwhenry3.github.io/atolljs/consumer/quickstart/",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"exports": {
|
|
8
|
-
".":
|
|
9
|
-
|
|
8
|
+
".": {
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"default": "./dist/index.js"
|
|
11
|
+
},
|
|
12
|
+
"./worker": {
|
|
13
|
+
"types": "./dist/worker.d.ts",
|
|
14
|
+
"default": "./dist/worker.js"
|
|
15
|
+
}
|
|
10
16
|
},
|
|
11
17
|
"files": [
|
|
12
|
-
"src"
|
|
18
|
+
"src",
|
|
19
|
+
"dist"
|
|
13
20
|
],
|
|
21
|
+
"scripts": {
|
|
22
|
+
"build": "node ../../scripts/build-lib.mjs"
|
|
23
|
+
},
|
|
14
24
|
"publishConfig": {
|
|
15
25
|
"access": "public",
|
|
16
26
|
"registry": "https://registry.npmjs.org"
|
|
17
27
|
},
|
|
18
28
|
"peerDependencies": {
|
|
19
|
-
"@atolljs/core": "0.1.
|
|
20
|
-
"@atolljs/islands": "0.1.
|
|
29
|
+
"@atolljs/core": "0.1.13",
|
|
30
|
+
"@atolljs/islands": "0.1.13",
|
|
21
31
|
"react": "^19.3.0",
|
|
22
32
|
"react-dom": "^19.3.0",
|
|
23
33
|
"react-reconciler": "^0.34.0"
|