@preact-mvc/atoll 0.1.0 → 0.1.1
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/README.md +66 -0
- package/dist/driver.d.ts +77 -0
- package/dist/driver.js +34 -0
- package/{src/index.ts → dist/index.d.ts} +2 -15
- package/dist/index.js +17 -0
- package/{src/worker.ts → dist/worker.d.ts} +6 -43
- package/dist/worker.js +32 -0
- package/package.json +15 -9
- package/src/driver.ts +0 -123
package/README.md
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# @preact-mvc/atoll
|
|
2
|
+
|
|
3
|
+
[](https://github.com/jwhenry3/preact-mvc/actions/workflows/ci.yml)
|
|
4
|
+
[](https://codecov.io/gh/jwhenry3/preact-mvc)
|
|
5
|
+
[](https://badge.socket.dev/npm/package/@preact-mvc/atoll)
|
|
6
|
+
|
|
7
|
+
Framework-free worker-rendered islands, modeled on
|
|
8
|
+
[Atoll](https://jwhenry3.github.io/atolljs/): a facade over
|
|
9
|
+
`@atolljs/islands` + `@atolljs/core` that mounts a worker-hosted UI tree
|
|
10
|
+
as an ordinary element. The worker renders against a proxy DOM; commits
|
|
11
|
+
stream back over `postMessage` as ops the main-thread driver replays as
|
|
12
|
+
real DOM. Nothing in this package knows about the framework — that's what
|
|
13
|
+
`@preact-mvc/atoll-island` adds.
|
|
14
|
+
|
|
15
|
+
**[preact-mvc — islands documentation](https://jwhenry3.github.io/preact-mvc/docs/islands/)**
|
|
16
|
+
|
|
17
|
+
## Install
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npm install @preact-mvc/atoll
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## The contract is the shared module
|
|
24
|
+
|
|
25
|
+
`defineIslandContract` names the registry app, declares emit payloads
|
|
26
|
+
(validated worker-side with zod), and carries the worker factory — the
|
|
27
|
+
one file both threads import:
|
|
28
|
+
|
|
29
|
+
```ts
|
|
30
|
+
// ticker.contract.ts
|
|
31
|
+
import { defineIslandContract, z } from '@preact-mvc/atoll';
|
|
32
|
+
|
|
33
|
+
export const tickerContract = defineIslandContract({
|
|
34
|
+
app: 'ticker',
|
|
35
|
+
events: {
|
|
36
|
+
'worker-ping': z.object({ count: z.number() }),
|
|
37
|
+
},
|
|
38
|
+
// The URL literal stays bundler-detectable — never hoist or compute it.
|
|
39
|
+
worker: () =>
|
|
40
|
+
new Worker(new URL('./ticker.worker.ts', import.meta.url), {
|
|
41
|
+
type: 'module',
|
|
42
|
+
}),
|
|
43
|
+
});
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Shell side
|
|
47
|
+
|
|
48
|
+
```ts
|
|
49
|
+
import { mountIsland, connectIslandWorker, callbackProp } from '@preact-mvc/atoll';
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
- `mountIsland` — mount a registered worker app into a host element.
|
|
53
|
+
- `connectIslandWorker` — drive an already-running worker.
|
|
54
|
+
- `callbackProp` — mark a prop as a function that crosses the wire.
|
|
55
|
+
- `islandApp`, `defineIslandContract`, `isIslandContract`, `z` — the
|
|
56
|
+
registry/contract API re-exported from `@atolljs/islands` +
|
|
57
|
+
`@atolljs/core`.
|
|
58
|
+
- `@preact-mvc/atoll/worker` — the worker-side entry (proxy DOM,
|
|
59
|
+
instance dispatcher); import it only in worker code.
|
|
60
|
+
|
|
61
|
+
Rules of the wire: props and event payloads must be `structuredClone`-able;
|
|
62
|
+
functions cross only via `callbackProp` markers; worker code sees the
|
|
63
|
+
proxy DOM and nothing else — `document` resolves through the instance
|
|
64
|
+
dispatcher, never ambient globals.
|
|
65
|
+
|
|
66
|
+
Published builds ship compiled `dist/` (`.js` + `.d.ts`).
|
package/dist/driver.d.ts
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@preact-mvc/atoll` shell surface — a thin facade over `@atolljs/islands`
|
|
3
|
+
* (docs/atoll/islands.md). The op protocol, proxy DOM, instance lifecycle,
|
|
4
|
+
* and op-replay driver are the real package now; this file only normalizes
|
|
5
|
+
* the worker option (an already-created WorkerLike counts as an entry, the
|
|
6
|
+
* real package wants a bundler-detectable factory or URL) and defaults the
|
|
7
|
+
* transport to message-only so mounts work without COOP/COEP headers —
|
|
8
|
+
* GitHub Pages can't serve them.
|
|
9
|
+
*
|
|
10
|
+
* Push mode still works: pass `mode: 'push'` on a `worker:` mount (builds a
|
|
11
|
+
* doorbell client) or `doorbell: true` on `connectIslandWorker`, and serve
|
|
12
|
+
* Cross-Origin-Opener-Policy: same-origin + Cross-Origin-Embedder-Policy:
|
|
13
|
+
* require-corp.
|
|
14
|
+
*/
|
|
15
|
+
import { callbackProp } from '@atolljs/islands';
|
|
16
|
+
import type { IslandClient, IslandHandle, IslandWorkerOptions, Mode, Op } from '@atolljs/islands';
|
|
17
|
+
export { callbackProp };
|
|
18
|
+
export type { IslandClient, IslandHandle, Mode, Op };
|
|
19
|
+
/**
|
|
20
|
+
* What a `worker:` option accepts: a bundler-detectable factory
|
|
21
|
+
* `() => new Worker(new URL(...))`, a URL, or an already-spawned worker
|
|
22
|
+
* (test doubles like `InProcessWorker` count — the real `Worker` type would
|
|
23
|
+
* reject them, so the surface is structural).
|
|
24
|
+
*/
|
|
25
|
+
export type WorkerLike = Pick<Worker, 'postMessage' | 'addEventListener' | 'removeEventListener' | 'terminate'>;
|
|
26
|
+
export interface ConnectOptions extends IslandWorkerOptions {
|
|
27
|
+
/** Bundler-detectable factory / URL, or an already-spawned worker. */
|
|
28
|
+
worker: WorkerLike | (() => WorkerLike) | URL;
|
|
29
|
+
/**
|
|
30
|
+
* Shared-memory doorbell for push-mode flush — requires cross-origin
|
|
31
|
+
* isolation. Default false (message-only): mounts fall back to the 50ms
|
|
32
|
+
* poll, which needs no special headers.
|
|
33
|
+
*/
|
|
34
|
+
doorbell?: boolean;
|
|
35
|
+
/**
|
|
36
|
+
* Keep the worker thread resident (but idle) when its last island
|
|
37
|
+
* destroys: the instance still unmounts — dispose hooks run, ops stop —
|
|
38
|
+
* but the thread's module graph and worker-side caches survive, so the
|
|
39
|
+
* next mount on this client is a warm start. Explicit `client.terminate()`
|
|
40
|
+
* still kills it.
|
|
41
|
+
*/
|
|
42
|
+
keepAlive?: boolean;
|
|
43
|
+
}
|
|
44
|
+
/** One island client = one dedicated worker. Islands mounted with `client:`
|
|
45
|
+
* share it; each `worker:` mount builds its own. */
|
|
46
|
+
export declare function connectIslandWorker({ worker, doorbell, ...options }: ConnectOptions): IslandClient;
|
|
47
|
+
export interface MountOptions extends IslandWorkerOptions {
|
|
48
|
+
/** Container the island's ops replay into. */
|
|
49
|
+
el: HTMLElement;
|
|
50
|
+
/**
|
|
51
|
+
* Which app to mount: the `apps` registry key, a stamped app
|
|
52
|
+
* (`islandApp`/`viewIsland` result), or a contract. Resolved through
|
|
53
|
+
* `islandAppNameOf` — bare components fall back to the function name.
|
|
54
|
+
*/
|
|
55
|
+
app?: unknown;
|
|
56
|
+
props?: Record<string, unknown>;
|
|
57
|
+
/** Island → shell channel: receives every worker-side `emit(name, payload)`. */
|
|
58
|
+
onEvent?: (name: string, payload: unknown) => void;
|
|
59
|
+
/** `data-atoll-slot` transclusion holes — shell mounts real DOM inside. */
|
|
60
|
+
slots?: Record<string, (el: HTMLElement | null) => void>;
|
|
61
|
+
onActivity?: () => void;
|
|
62
|
+
onOps?: (ops: readonly Op[], elapsedMs: number) => void;
|
|
63
|
+
/**
|
|
64
|
+
* 'push' uses the doorbell SAB (cross-origin isolation required for
|
|
65
|
+
* `worker:` mounts); 'poll' (the default here) needs none.
|
|
66
|
+
*/
|
|
67
|
+
mode?: Mode;
|
|
68
|
+
/** Devtools renderer tag ('preact-mvc'); the adapter sets it. */
|
|
69
|
+
framework?: string;
|
|
70
|
+
mountTimeout?: number;
|
|
71
|
+
/** This mount's destroy() leaves the client resident-but-idle. */
|
|
72
|
+
keepAlive?: boolean;
|
|
73
|
+
/** A shared `connectIslandWorker` client, OR an entry this mount owns. */
|
|
74
|
+
client?: IslandClient;
|
|
75
|
+
worker?: WorkerLike | (() => WorkerLike) | URL;
|
|
76
|
+
}
|
|
77
|
+
export declare function mountIsland(opts: MountOptions): Promise<IslandHandle>;
|
package/dist/driver.js
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@preact-mvc/atoll` shell surface — a thin facade over `@atolljs/islands`
|
|
3
|
+
* (docs/atoll/islands.md). The op protocol, proxy DOM, instance lifecycle,
|
|
4
|
+
* and op-replay driver are the real package now; this file only normalizes
|
|
5
|
+
* the worker option (an already-created WorkerLike counts as an entry, the
|
|
6
|
+
* real package wants a bundler-detectable factory or URL) and defaults the
|
|
7
|
+
* transport to message-only so mounts work without COOP/COEP headers —
|
|
8
|
+
* GitHub Pages can't serve them.
|
|
9
|
+
*
|
|
10
|
+
* Push mode still works: pass `mode: 'push'` on a `worker:` mount (builds a
|
|
11
|
+
* doorbell client) or `doorbell: true` on `connectIslandWorker`, and serve
|
|
12
|
+
* Cross-Origin-Opener-Policy: same-origin + Cross-Origin-Embedder-Policy:
|
|
13
|
+
* require-corp.
|
|
14
|
+
*/
|
|
15
|
+
import { callbackProp, connectIslandWorker as atollConnectIslandWorker, islandAppNameOf, mountIsland as atollMountIsland, } from '@atolljs/islands';
|
|
16
|
+
export { callbackProp };
|
|
17
|
+
const asWorkerEntry = (w) => w instanceof URL ? w : typeof w === 'function' ? w : () => w;
|
|
18
|
+
/** One island client = one dedicated worker. Islands mounted with `client:`
|
|
19
|
+
* share it; each `worker:` mount builds its own. */
|
|
20
|
+
export function connectIslandWorker({ worker, doorbell = false, ...options }) {
|
|
21
|
+
return atollConnectIslandWorker({ ...options, doorbell, worker: asWorkerEntry(worker) });
|
|
22
|
+
}
|
|
23
|
+
export function mountIsland(opts) {
|
|
24
|
+
const { worker, client, app, mode = 'poll', ...rest } = opts;
|
|
25
|
+
const name = islandAppNameOf(app);
|
|
26
|
+
if (app !== undefined && name === undefined) {
|
|
27
|
+
return Promise.reject(new Error('mountIsland: could not resolve an app name — pass the registry key ' +
|
|
28
|
+
'or stamp the app with islandApp(name, app)'));
|
|
29
|
+
}
|
|
30
|
+
const base = { ...rest, app: name, mode };
|
|
31
|
+
return client !== undefined
|
|
32
|
+
? atollMountIsland({ ...base, client })
|
|
33
|
+
: atollMountIsland({ ...base, worker: asWorkerEntry(worker) });
|
|
34
|
+
}
|
|
@@ -13,24 +13,11 @@
|
|
|
13
13
|
*/
|
|
14
14
|
export { islandApp, islandAppNameOf } from '@atolljs/islands';
|
|
15
15
|
export type { IslandAppProps } from '@atolljs/islands';
|
|
16
|
-
export {
|
|
17
|
-
|
|
18
|
-
isIslandContract,
|
|
19
|
-
withContract,
|
|
20
|
-
contractOf,
|
|
21
|
-
contractWorkerOf,
|
|
22
|
-
} from '@atolljs/islands';
|
|
23
|
-
export type {
|
|
24
|
-
IslandContract,
|
|
25
|
-
IslandContractEventHandler,
|
|
26
|
-
IslandContractEvents,
|
|
27
|
-
IslandContractProps,
|
|
28
|
-
IslandWorkerOptions,
|
|
29
|
-
} from '@atolljs/islands';
|
|
16
|
+
export { defineIslandContract, isIslandContract, withContract, contractOf, contractWorkerOf, } from '@atolljs/islands';
|
|
17
|
+
export type { IslandContract, IslandContractEventHandler, IslandContractEvents, IslandContractProps, IslandWorkerOptions, } from '@atolljs/islands';
|
|
30
18
|
export type { EventPayload, Op, WireProps } from '@atolljs/islands';
|
|
31
19
|
export { z } from '@atolljs/core';
|
|
32
20
|
export type { Schema } from '@atolljs/core';
|
|
33
|
-
|
|
34
21
|
export { mountIsland, connectIslandWorker, callbackProp } from './driver';
|
|
35
22
|
export type { ConnectOptions, MountOptions, WorkerLike } from './driver';
|
|
36
23
|
export type { IslandClient, IslandHandle, Mode } from './driver';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@preact-mvc/atoll` — the shell-facing facade over the real Atoll
|
|
3
|
+
* packages (`@atolljs/islands` + `@atolljs/core`). Worker islands render
|
|
4
|
+
* inside Web Workers against a proxy DOM; commits stream back as ops the
|
|
5
|
+
* driver replays onto real DOM.
|
|
6
|
+
*
|
|
7
|
+
* Shell side (this entry): `mountIsland`, `connectIslandWorker`,
|
|
8
|
+
* `callbackProp`, `islandApp`. Worker side (import in worker entries):
|
|
9
|
+
* the `./worker` subpath re-exports `@atolljs/islands/worker` —
|
|
10
|
+
* `definePolyWorker`/`defineMonoWorker`, `emit`, instance scope. The
|
|
11
|
+
* preact-mvc adapter and `<Island/>` shell components live in
|
|
12
|
+
* `@preact-mvc/atoll-island`.
|
|
13
|
+
*/
|
|
14
|
+
export { islandApp, islandAppNameOf } from '@atolljs/islands';
|
|
15
|
+
export { defineIslandContract, isIslandContract, withContract, contractOf, contractWorkerOf, } from '@atolljs/islands';
|
|
16
|
+
export { z } from '@atolljs/core';
|
|
17
|
+
export { mountIsland, connectIslandWorker, callbackProp } from './driver';
|
|
@@ -5,20 +5,7 @@
|
|
|
5
5
|
* import { definePolyWorker } from '@preact-mvc/atoll/worker';
|
|
6
6
|
* export const worker = definePolyWorker({ apps: { ... } });
|
|
7
7
|
*/
|
|
8
|
-
export {
|
|
9
|
-
definePolyWorker,
|
|
10
|
-
defineMonoWorker,
|
|
11
|
-
islandApp,
|
|
12
|
-
islandAppNameOf,
|
|
13
|
-
emit,
|
|
14
|
-
runInInstance,
|
|
15
|
-
getActiveInstance,
|
|
16
|
-
getLastActiveInstance,
|
|
17
|
-
getLastTouchedInstance,
|
|
18
|
-
setActiveInstance,
|
|
19
|
-
installDomShim,
|
|
20
|
-
ProxyElement,
|
|
21
|
-
} from '@atolljs/islands/worker';
|
|
8
|
+
export { definePolyWorker, defineMonoWorker, islandApp, islandAppNameOf, emit, runInInstance, getActiveInstance, getLastActiveInstance, getLastTouchedInstance, setActiveInstance, installDomShim, ProxyElement, } from '@atolljs/islands/worker';
|
|
22
9
|
/**
|
|
23
10
|
* Nested-island surfaces: `mountIsland` is the ONE mount API on both
|
|
24
11
|
* threads — a ProxyElement target (inside a worker-rendered tree) routes
|
|
@@ -30,45 +17,21 @@ export {
|
|
|
30
17
|
* `new Worker` needs an engine that supports it — Chrome/Firefox/Node,
|
|
31
18
|
* NOT Safari workers.
|
|
32
19
|
*/
|
|
33
|
-
export {
|
|
34
|
-
connectIslandWorker,
|
|
35
|
-
mountIsland,
|
|
36
|
-
callbackProp,
|
|
37
|
-
} from './driver';
|
|
20
|
+
export { connectIslandWorker, mountIsland, callbackProp, } from './driver';
|
|
38
21
|
export type { ConnectIslandWorkerConfig, MountIslandOptions } from '@atolljs/islands/worker';
|
|
39
|
-
export {
|
|
40
|
-
|
|
41
|
-
isIslandContract,
|
|
42
|
-
withContract,
|
|
43
|
-
contractOf,
|
|
44
|
-
} from '@atolljs/islands/worker';
|
|
45
|
-
export type {
|
|
46
|
-
ImperativeIslandApp,
|
|
47
|
-
IslandApp,
|
|
48
|
-
IslandContract,
|
|
49
|
-
PolyWorkerRegistry,
|
|
50
|
-
ProxyDocument,
|
|
51
|
-
RenderContext,
|
|
52
|
-
RenderedHandle,
|
|
53
|
-
RenderedIslandApp,
|
|
54
|
-
} from '@atolljs/islands/worker';
|
|
22
|
+
export { defineIslandContract, isIslandContract, withContract, contractOf, } from '@atolljs/islands/worker';
|
|
23
|
+
export type { ImperativeIslandApp, IslandApp, IslandContract, PolyWorkerRegistry, ProxyDocument, RenderContext, RenderedHandle, RenderedIslandApp, } from '@atolljs/islands/worker';
|
|
55
24
|
export type { EventPayload, Op, WireProps } from '@atolljs/islands/worker';
|
|
56
25
|
export type { IslandWorkerDefinition } from '@atolljs/islands';
|
|
57
|
-
|
|
58
|
-
import { setActiveInstance } from '@atolljs/islands/worker';
|
|
59
26
|
import type { IslandWorkerDefinition } from '@atolljs/islands';
|
|
60
|
-
|
|
61
27
|
/** What `definePolyWorker`/`defineMonoWorker` return — the worker def type
|
|
62
28
|
* island clients type-check against. */
|
|
63
29
|
export type IslandWorkerDef = IslandWorkerDefinition;
|
|
64
|
-
|
|
65
30
|
/**
|
|
66
31
|
* Enter `key`'s instance scope, returning the previous key for restore —
|
|
67
32
|
* a named pair around `setActiveInstance` (which returns the prev itself),
|
|
68
33
|
* used by the preact-mvc adapter's out-of-task render scoping.
|
|
69
34
|
*/
|
|
70
|
-
export const enterInstance
|
|
35
|
+
export declare const enterInstance: (key: string) => string;
|
|
71
36
|
/** Restore the scope `enterInstance` returned (`''` when there was none). */
|
|
72
|
-
export const exitInstance
|
|
73
|
-
setActiveInstance(prev ?? '');
|
|
74
|
-
};
|
|
37
|
+
export declare const exitInstance: (prev: string | null) => void;
|
package/dist/worker.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@preact-mvc/atoll/worker` — the worker-side entry, re-exported from
|
|
3
|
+
* `@atolljs/islands/worker`. Import in worker entries only:
|
|
4
|
+
*
|
|
5
|
+
* import { definePolyWorker } from '@preact-mvc/atoll/worker';
|
|
6
|
+
* export const worker = definePolyWorker({ apps: { ... } });
|
|
7
|
+
*/
|
|
8
|
+
export { definePolyWorker, defineMonoWorker, islandApp, islandAppNameOf, emit, runInInstance, getActiveInstance, getLastActiveInstance, getLastTouchedInstance, setActiveInstance, installDomShim, ProxyElement, } from '@atolljs/islands/worker';
|
|
9
|
+
/**
|
|
10
|
+
* Nested-island surfaces: `mountIsland` is the ONE mount API on both
|
|
11
|
+
* threads — a ProxyElement target (inside a worker-rendered tree) routes
|
|
12
|
+
* to the registered sub-mounter and spawns a real sub-worker; a real
|
|
13
|
+
* element drives the main-thread applier. `connectIslandWorker` is
|
|
14
|
+
* thread-agnostic and spawns from whichever thread calls it. Re-exported
|
|
15
|
+
* through our driver facade so the same worker-option normalization and
|
|
16
|
+
* message-only defaults apply on nested mounts. Platform note: nested
|
|
17
|
+
* `new Worker` needs an engine that supports it — Chrome/Firefox/Node,
|
|
18
|
+
* NOT Safari workers.
|
|
19
|
+
*/
|
|
20
|
+
export { connectIslandWorker, mountIsland, callbackProp, } from './driver';
|
|
21
|
+
export { defineIslandContract, isIslandContract, withContract, contractOf, } from '@atolljs/islands/worker';
|
|
22
|
+
import { setActiveInstance } from '@atolljs/islands/worker';
|
|
23
|
+
/**
|
|
24
|
+
* Enter `key`'s instance scope, returning the previous key for restore —
|
|
25
|
+
* a named pair around `setActiveInstance` (which returns the prev itself),
|
|
26
|
+
* used by the preact-mvc adapter's out-of-task render scoping.
|
|
27
|
+
*/
|
|
28
|
+
export const enterInstance = (key) => setActiveInstance(key);
|
|
29
|
+
/** Restore the scope `enterInstance` returned (`''` when there was none). */
|
|
30
|
+
export const exitInstance = (prev) => {
|
|
31
|
+
setActiveInstance(prev ?? '');
|
|
32
|
+
};
|
package/package.json
CHANGED
|
@@ -1,21 +1,26 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@preact-mvc/atoll",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "Framework-free worker-rendered islands: op protocol, proxy DOM, worker runtime, main-thread driver",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
7
|
-
".":
|
|
8
|
-
|
|
7
|
+
".": {
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"default": "./dist/index.js"
|
|
10
|
+
},
|
|
11
|
+
"./worker": {
|
|
12
|
+
"types": "./dist/worker.d.ts",
|
|
13
|
+
"default": "./dist/worker.js"
|
|
14
|
+
},
|
|
9
15
|
"./package.json": "./package.json",
|
|
10
|
-
"./*": "./
|
|
16
|
+
"./*": "./dist/*"
|
|
11
17
|
},
|
|
12
18
|
"files": [
|
|
13
|
-
"
|
|
14
|
-
"!src/**/*.test.ts",
|
|
15
|
-
"!src/**/*.test.tsx"
|
|
19
|
+
"dist"
|
|
16
20
|
],
|
|
17
21
|
"scripts": {
|
|
18
|
-
"typecheck": "tsc --noEmit"
|
|
22
|
+
"typecheck": "tsc --noEmit",
|
|
23
|
+
"build": "tsc -p tsconfig.build.json"
|
|
19
24
|
},
|
|
20
25
|
"publishConfig": {
|
|
21
26
|
"access": "public",
|
|
@@ -30,5 +35,6 @@
|
|
|
30
35
|
"url": "https://github.com/jwhenry3/preact-mvc",
|
|
31
36
|
"directory": "packages/atoll"
|
|
32
37
|
},
|
|
33
|
-
"license": "MIT"
|
|
38
|
+
"license": "MIT",
|
|
39
|
+
"types": "./dist/index.d.ts"
|
|
34
40
|
}
|
package/src/driver.ts
DELETED
|
@@ -1,123 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* `@preact-mvc/atoll` shell surface — a thin facade over `@atolljs/islands`
|
|
3
|
-
* (docs/atoll/islands.md). The op protocol, proxy DOM, instance lifecycle,
|
|
4
|
-
* and op-replay driver are the real package now; this file only normalizes
|
|
5
|
-
* the worker option (an already-created WorkerLike counts as an entry, the
|
|
6
|
-
* real package wants a bundler-detectable factory or URL) and defaults the
|
|
7
|
-
* transport to message-only so mounts work without COOP/COEP headers —
|
|
8
|
-
* GitHub Pages can't serve them.
|
|
9
|
-
*
|
|
10
|
-
* Push mode still works: pass `mode: 'push'` on a `worker:` mount (builds a
|
|
11
|
-
* doorbell client) or `doorbell: true` on `connectIslandWorker`, and serve
|
|
12
|
-
* Cross-Origin-Opener-Policy: same-origin + Cross-Origin-Embedder-Policy:
|
|
13
|
-
* require-corp.
|
|
14
|
-
*/
|
|
15
|
-
import {
|
|
16
|
-
callbackProp,
|
|
17
|
-
connectIslandWorker as atollConnectIslandWorker,
|
|
18
|
-
islandAppNameOf,
|
|
19
|
-
mountIsland as atollMountIsland,
|
|
20
|
-
} from '@atolljs/islands';
|
|
21
|
-
import type {
|
|
22
|
-
IslandClient,
|
|
23
|
-
IslandHandle,
|
|
24
|
-
IslandWorkerOptions,
|
|
25
|
-
Mode,
|
|
26
|
-
Op,
|
|
27
|
-
} from '@atolljs/islands';
|
|
28
|
-
|
|
29
|
-
export { callbackProp };
|
|
30
|
-
export type { IslandClient, IslandHandle, Mode, Op };
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* What a `worker:` option accepts: a bundler-detectable factory
|
|
34
|
-
* `() => new Worker(new URL(...))`, a URL, or an already-spawned worker
|
|
35
|
-
* (test doubles like `InProcessWorker` count — the real `Worker` type would
|
|
36
|
-
* reject them, so the surface is structural).
|
|
37
|
-
*/
|
|
38
|
-
export type WorkerLike = Pick<
|
|
39
|
-
Worker,
|
|
40
|
-
'postMessage' | 'addEventListener' | 'removeEventListener' | 'terminate'
|
|
41
|
-
>;
|
|
42
|
-
|
|
43
|
-
const asWorkerEntry = (
|
|
44
|
-
w: WorkerLike | (() => WorkerLike) | URL,
|
|
45
|
-
): (() => Worker) | URL =>
|
|
46
|
-
w instanceof URL ? w : typeof w === 'function' ? (w as () => Worker) : () => w as Worker;
|
|
47
|
-
|
|
48
|
-
export interface ConnectOptions extends IslandWorkerOptions {
|
|
49
|
-
/** Bundler-detectable factory / URL, or an already-spawned worker. */
|
|
50
|
-
worker: WorkerLike | (() => WorkerLike) | URL;
|
|
51
|
-
/**
|
|
52
|
-
* Shared-memory doorbell for push-mode flush — requires cross-origin
|
|
53
|
-
* isolation. Default false (message-only): mounts fall back to the 50ms
|
|
54
|
-
* poll, which needs no special headers.
|
|
55
|
-
*/
|
|
56
|
-
doorbell?: boolean;
|
|
57
|
-
/**
|
|
58
|
-
* Keep the worker thread resident (but idle) when its last island
|
|
59
|
-
* destroys: the instance still unmounts — dispose hooks run, ops stop —
|
|
60
|
-
* but the thread's module graph and worker-side caches survive, so the
|
|
61
|
-
* next mount on this client is a warm start. Explicit `client.terminate()`
|
|
62
|
-
* still kills it.
|
|
63
|
-
*/
|
|
64
|
-
keepAlive?: boolean;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
/** One island client = one dedicated worker. Islands mounted with `client:`
|
|
68
|
-
* share it; each `worker:` mount builds its own. */
|
|
69
|
-
export function connectIslandWorker({
|
|
70
|
-
worker,
|
|
71
|
-
doorbell = false,
|
|
72
|
-
...options
|
|
73
|
-
}: ConnectOptions): IslandClient {
|
|
74
|
-
return atollConnectIslandWorker({ ...options, doorbell, worker: asWorkerEntry(worker) });
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
export interface MountOptions extends IslandWorkerOptions {
|
|
78
|
-
/** Container the island's ops replay into. */
|
|
79
|
-
el: HTMLElement;
|
|
80
|
-
/**
|
|
81
|
-
* Which app to mount: the `apps` registry key, a stamped app
|
|
82
|
-
* (`islandApp`/`viewIsland` result), or a contract. Resolved through
|
|
83
|
-
* `islandAppNameOf` — bare components fall back to the function name.
|
|
84
|
-
*/
|
|
85
|
-
app?: unknown;
|
|
86
|
-
props?: Record<string, unknown>;
|
|
87
|
-
/** Island → shell channel: receives every worker-side `emit(name, payload)`. */
|
|
88
|
-
onEvent?: (name: string, payload: unknown) => void;
|
|
89
|
-
/** `data-atoll-slot` transclusion holes — shell mounts real DOM inside. */
|
|
90
|
-
slots?: Record<string, (el: HTMLElement | null) => void>;
|
|
91
|
-
onActivity?: () => void;
|
|
92
|
-
onOps?: (ops: readonly Op[], elapsedMs: number) => void;
|
|
93
|
-
/**
|
|
94
|
-
* 'push' uses the doorbell SAB (cross-origin isolation required for
|
|
95
|
-
* `worker:` mounts); 'poll' (the default here) needs none.
|
|
96
|
-
*/
|
|
97
|
-
mode?: Mode;
|
|
98
|
-
/** Devtools renderer tag ('preact-mvc'); the adapter sets it. */
|
|
99
|
-
framework?: string;
|
|
100
|
-
mountTimeout?: number;
|
|
101
|
-
/** This mount's destroy() leaves the client resident-but-idle. */
|
|
102
|
-
keepAlive?: boolean;
|
|
103
|
-
/** A shared `connectIslandWorker` client, OR an entry this mount owns. */
|
|
104
|
-
client?: IslandClient;
|
|
105
|
-
worker?: WorkerLike | (() => WorkerLike) | URL;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
export function mountIsland(opts: MountOptions): Promise<IslandHandle> {
|
|
109
|
-
const { worker, client, app, mode = 'poll', ...rest } = opts;
|
|
110
|
-
const name = islandAppNameOf(app);
|
|
111
|
-
if (app !== undefined && name === undefined) {
|
|
112
|
-
return Promise.reject(
|
|
113
|
-
new Error(
|
|
114
|
-
'mountIsland: could not resolve an app name — pass the registry key ' +
|
|
115
|
-
'or stamp the app with islandApp(name, app)',
|
|
116
|
-
),
|
|
117
|
-
);
|
|
118
|
-
}
|
|
119
|
-
const base = { ...rest, app: name, mode };
|
|
120
|
-
return client !== undefined
|
|
121
|
-
? atollMountIsland({ ...base, client })
|
|
122
|
-
: atollMountIsland({ ...base, worker: asWorkerEntry(worker!) });
|
|
123
|
-
}
|