@preact-mvc/atoll 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +32 -4
- package/src/driver.ts +123 -0
- package/src/index.ts +36 -0
- package/src/worker.ts +74 -0
- package/README.md +0 -3
package/package.json
CHANGED
|
@@ -1,6 +1,34 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@preact-mvc/atoll",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Framework-free worker-rendered islands: op protocol, proxy DOM, worker runtime, main-thread driver",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"exports": {
|
|
7
|
+
".": "./src/index.ts",
|
|
8
|
+
"./worker": "./src/worker.ts",
|
|
9
|
+
"./package.json": "./package.json",
|
|
10
|
+
"./*": "./src/*"
|
|
11
|
+
},
|
|
12
|
+
"files": [
|
|
13
|
+
"src",
|
|
14
|
+
"!src/**/*.test.ts",
|
|
15
|
+
"!src/**/*.test.tsx"
|
|
16
|
+
],
|
|
17
|
+
"scripts": {
|
|
18
|
+
"typecheck": "tsc --noEmit"
|
|
19
|
+
},
|
|
20
|
+
"publishConfig": {
|
|
21
|
+
"access": "public",
|
|
22
|
+
"registry": "https://registry.npmjs.org"
|
|
23
|
+
},
|
|
24
|
+
"dependencies": {
|
|
25
|
+
"@atolljs/core": "0.1.11",
|
|
26
|
+
"@atolljs/islands": "0.1.11"
|
|
27
|
+
},
|
|
28
|
+
"repository": {
|
|
29
|
+
"type": "git",
|
|
30
|
+
"url": "https://github.com/jwhenry3/preact-mvc",
|
|
31
|
+
"directory": "packages/atoll"
|
|
32
|
+
},
|
|
33
|
+
"license": "MIT"
|
|
34
|
+
}
|
package/src/driver.ts
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
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
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
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 type { IslandAppProps } from '@atolljs/islands';
|
|
16
|
+
export {
|
|
17
|
+
defineIslandContract,
|
|
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';
|
|
30
|
+
export type { EventPayload, Op, WireProps } from '@atolljs/islands';
|
|
31
|
+
export { z } from '@atolljs/core';
|
|
32
|
+
export type { Schema } from '@atolljs/core';
|
|
33
|
+
|
|
34
|
+
export { mountIsland, connectIslandWorker, callbackProp } from './driver';
|
|
35
|
+
export type { ConnectOptions, MountOptions, WorkerLike } from './driver';
|
|
36
|
+
export type { IslandClient, IslandHandle, Mode } from './driver';
|
package/src/worker.ts
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
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 {
|
|
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';
|
|
22
|
+
/**
|
|
23
|
+
* Nested-island surfaces: `mountIsland` is the ONE mount API on both
|
|
24
|
+
* threads — a ProxyElement target (inside a worker-rendered tree) routes
|
|
25
|
+
* to the registered sub-mounter and spawns a real sub-worker; a real
|
|
26
|
+
* element drives the main-thread applier. `connectIslandWorker` is
|
|
27
|
+
* thread-agnostic and spawns from whichever thread calls it. Re-exported
|
|
28
|
+
* through our driver facade so the same worker-option normalization and
|
|
29
|
+
* message-only defaults apply on nested mounts. Platform note: nested
|
|
30
|
+
* `new Worker` needs an engine that supports it — Chrome/Firefox/Node,
|
|
31
|
+
* NOT Safari workers.
|
|
32
|
+
*/
|
|
33
|
+
export {
|
|
34
|
+
connectIslandWorker,
|
|
35
|
+
mountIsland,
|
|
36
|
+
callbackProp,
|
|
37
|
+
} from './driver';
|
|
38
|
+
export type { ConnectIslandWorkerConfig, MountIslandOptions } from '@atolljs/islands/worker';
|
|
39
|
+
export {
|
|
40
|
+
defineIslandContract,
|
|
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';
|
|
55
|
+
export type { EventPayload, Op, WireProps } from '@atolljs/islands/worker';
|
|
56
|
+
export type { IslandWorkerDefinition } from '@atolljs/islands';
|
|
57
|
+
|
|
58
|
+
import { setActiveInstance } from '@atolljs/islands/worker';
|
|
59
|
+
import type { IslandWorkerDefinition } from '@atolljs/islands';
|
|
60
|
+
|
|
61
|
+
/** What `definePolyWorker`/`defineMonoWorker` return — the worker def type
|
|
62
|
+
* island clients type-check against. */
|
|
63
|
+
export type IslandWorkerDef = IslandWorkerDefinition;
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Enter `key`'s instance scope, returning the previous key for restore —
|
|
67
|
+
* a named pair around `setActiveInstance` (which returns the prev itself),
|
|
68
|
+
* used by the preact-mvc adapter's out-of-task render scoping.
|
|
69
|
+
*/
|
|
70
|
+
export const enterInstance = (key: string): string => setActiveInstance(key);
|
|
71
|
+
/** Restore the scope `enterInstance` returned (`''` when there was none). */
|
|
72
|
+
export const exitInstance = (prev: string | null): void => {
|
|
73
|
+
setActiveInstance(prev ?? '');
|
|
74
|
+
};
|
package/README.md
DELETED