@atolljs/react-island 0.1.5 → 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 +3 -3
- package/src/index.tsx +37 -6
- package/src/worker.ts +17 -7
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@atolljs/react-island",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.7",
|
|
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",
|
|
@@ -16,8 +16,8 @@
|
|
|
16
16
|
"registry": "https://registry.npmjs.org"
|
|
17
17
|
},
|
|
18
18
|
"peerDependencies": {
|
|
19
|
-
"@atolljs/core": "0.1.
|
|
20
|
-
"@atolljs/islands": "0.1.
|
|
19
|
+
"@atolljs/core": "0.1.7",
|
|
20
|
+
"@atolljs/islands": "0.1.7",
|
|
21
21
|
"react": "^19.3.0",
|
|
22
22
|
"react-dom": "^19.3.0",
|
|
23
23
|
"react-reconciler": "^0.34.0"
|
package/src/index.tsx
CHANGED
|
@@ -47,6 +47,7 @@ import { createPortal } from 'react-dom';
|
|
|
47
47
|
import type { HTMLAttributes, ReactElement, ReactNode, Ref } from 'react';
|
|
48
48
|
import {
|
|
49
49
|
connectIslandWorker,
|
|
50
|
+
contractWorkerOf,
|
|
50
51
|
islandAppNameOf,
|
|
51
52
|
mountIsland,
|
|
52
53
|
} from '@atolljs/islands';
|
|
@@ -54,6 +55,9 @@ import type {
|
|
|
54
55
|
IslandAppLike,
|
|
55
56
|
IslandAppProps,
|
|
56
57
|
IslandClient,
|
|
58
|
+
IslandContract,
|
|
59
|
+
IslandContractEventHandler,
|
|
60
|
+
IslandContractProps,
|
|
57
61
|
IslandHandle,
|
|
58
62
|
IslandWorkerOptions,
|
|
59
63
|
Mode,
|
|
@@ -183,9 +187,14 @@ export function Island<A = string>({
|
|
|
183
187
|
return;
|
|
184
188
|
}
|
|
185
189
|
|
|
190
|
+
// A contract `app` can carry its own worker factory — the contract
|
|
191
|
+
// module is then the whole connection, call sites pass no worker.
|
|
192
|
+
const resolvedWorker = worker ?? contractWorkerOf(app);
|
|
186
193
|
const client =
|
|
187
194
|
clientProp ??
|
|
188
|
-
(
|
|
195
|
+
(resolvedWorker !== undefined
|
|
196
|
+
? connectIslandWorker({ worker: resolvedWorker, ...workerOptions })
|
|
197
|
+
: undefined);
|
|
189
198
|
if (client === undefined) {
|
|
190
199
|
report(new Error('<Island> requires either `worker` or `client`'));
|
|
191
200
|
return;
|
|
@@ -423,11 +432,18 @@ function ProxyIsland({
|
|
|
423
432
|
* contract-module shape `{ app: A, worker? }`, passes bare A through.
|
|
424
433
|
*/
|
|
425
434
|
export type LazyResolvedApp<T extends Promise<unknown>> =
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
435
|
+
// A contract object IS a lazy module (`{ app, worker? }` + brand) — match
|
|
436
|
+
// it before `{ app: infer A }` so props infer `P`, not `app`'s string.
|
|
437
|
+
// The never guard keeps a `Promise.reject` loader on the untyped path.
|
|
438
|
+
[Awaited<T>] extends [never]
|
|
439
|
+
? string
|
|
440
|
+
: Awaited<T> extends { readonly islandContract: true }
|
|
441
|
+
? Awaited<T>
|
|
442
|
+
: Awaited<T> extends { app: infer A }
|
|
443
|
+
? A
|
|
444
|
+
: Awaited<T> extends { default: infer D }
|
|
445
|
+
? D
|
|
446
|
+
: Awaited<T>;
|
|
431
447
|
|
|
432
448
|
/**
|
|
433
449
|
* `islandComponent<P>('charts')` — a proxy component for a worker app that
|
|
@@ -441,6 +457,21 @@ export type LazyResolvedApp<T extends Promise<unknown>> =
|
|
|
441
457
|
* <TableIsland worker={renderWorker} filter={filter} desc={desc} />
|
|
442
458
|
* ```
|
|
443
459
|
*/
|
|
460
|
+
/**
|
|
461
|
+
* `islandComponent(checkoutContract)` — the cross-framework facade: a
|
|
462
|
+
* worker app described by `defineIslandContract` mounts by the contract's
|
|
463
|
+
* `app` key, props come from its schema, and `onEvent` narrows to the
|
|
464
|
+
* declared event vocabulary. The shell imports the CONTRACT module only —
|
|
465
|
+
* never the component or its framework.
|
|
466
|
+
*/
|
|
467
|
+
export function islandComponent<C extends IslandContract>(
|
|
468
|
+
app: C,
|
|
469
|
+
): (
|
|
470
|
+
props: IslandContractProps<C> &
|
|
471
|
+
Omit<IslandShellProps, 'onEvent'> & {
|
|
472
|
+
onEvent?: IslandContractEventHandler<C>;
|
|
473
|
+
},
|
|
474
|
+
) => ReactElement;
|
|
444
475
|
export function islandComponent<P extends object = Record<string, unknown>>(
|
|
445
476
|
app?: string,
|
|
446
477
|
): (props: P & IslandShellProps) => ReactElement;
|
package/src/worker.ts
CHANGED
|
@@ -35,10 +35,12 @@ import {
|
|
|
35
35
|
definePolyWorker,
|
|
36
36
|
islandApp,
|
|
37
37
|
islandAppNameOf,
|
|
38
|
+
withContract,
|
|
38
39
|
} from '@atolljs/islands/worker';
|
|
39
40
|
import type {
|
|
40
41
|
DoorbellSpec,
|
|
41
42
|
ImperativeIslandApp,
|
|
43
|
+
IslandContract,
|
|
42
44
|
IslandWorkerMethods,
|
|
43
45
|
RenderContext,
|
|
44
46
|
RenderedHandle,
|
|
@@ -65,7 +67,10 @@ type AnyComponent = ComponentType<Record<string, unknown>>;
|
|
|
65
67
|
* the wrap, so registry warn-checks and `defineMonoWorker` name resolution
|
|
66
68
|
* still see the shell-facing name.
|
|
67
69
|
*/
|
|
68
|
-
export function reactIslandApp(
|
|
70
|
+
export function reactIslandApp(
|
|
71
|
+
App: AnyComponent,
|
|
72
|
+
contract?: IslandContract,
|
|
73
|
+
): RenderedIslandApp {
|
|
69
74
|
const app: RenderedIslandApp = {
|
|
70
75
|
mount({ instance, props }: RenderContext): RenderedHandle {
|
|
71
76
|
const react = createReactInstance(instance);
|
|
@@ -82,7 +87,9 @@ export function reactIslandApp(App: AnyComponent): RenderedIslandApp {
|
|
|
82
87
|
};
|
|
83
88
|
const stamp = islandAppNameOf(App);
|
|
84
89
|
if (stamp !== undefined) (app as { islandAppName?: string }).islandAppName = stamp;
|
|
85
|
-
|
|
90
|
+
// Contract stamp — the engine validates mount/updateProps props and
|
|
91
|
+
// declared emit payloads against it.
|
|
92
|
+
return contract !== undefined ? withContract(contract, app) : app;
|
|
86
93
|
}
|
|
87
94
|
|
|
88
95
|
/** Stamp + wrap in one step — `reactIsland('charts', ChartsApp)` yields the
|
|
@@ -90,8 +97,9 @@ export function reactIslandApp(App: AnyComponent): RenderedIslandApp {
|
|
|
90
97
|
export const reactIsland = (
|
|
91
98
|
name: string,
|
|
92
99
|
App: AnyComponent,
|
|
100
|
+
contract?: IslandContract,
|
|
93
101
|
): RenderedIslandApp & { readonly islandAppName: string } =>
|
|
94
|
-
islandApp(name, reactIslandApp(App));
|
|
102
|
+
islandApp(name, reactIslandApp(App, contract));
|
|
95
103
|
|
|
96
104
|
export interface ReactPolyWorkerRegistry {
|
|
97
105
|
/**
|
|
@@ -131,9 +139,11 @@ export function defineReactPolyWorker(
|
|
|
131
139
|
*/
|
|
132
140
|
export function defineReactMonoWorker(
|
|
133
141
|
app: AnyComponent | ImperativeIslandApp | RenderedIslandApp,
|
|
134
|
-
options?: { sharedMemory?: SharedMemory<DoorbellSpec
|
|
142
|
+
options?: { sharedMemory?: SharedMemory<DoorbellSpec>; contract?: IslandContract },
|
|
135
143
|
): WorkerDefinition<DoorbellSpec, IslandWorkerMethods> {
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
144
|
+
const resolved = typeof app === 'function' ? reactIslandApp(app) : app;
|
|
145
|
+
return defineMonoWorker(
|
|
146
|
+
options?.contract !== undefined ? withContract(options.contract, resolved) : resolved,
|
|
147
|
+
options,
|
|
148
|
+
);
|
|
139
149
|
}
|