@swiftbrowser/native 0.11.0 → 0.13.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/README.md +38 -1
- package/bin/swiftbrowser-native.mjs +27 -8
- package/dist/bridge.d.ts +7 -0
- package/dist/bridge.d.ts.map +1 -1
- package/dist/bridge.js.map +1 -1
- package/dist/build.d.ts +22 -2
- package/dist/build.d.ts.map +1 -1
- package/dist/build.js +25 -8
- package/dist/build.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/runtime.d.ts +7 -3
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +16 -3
- package/dist/runtime.js.map +1 -1
- package/dist/site.d.ts +8 -0
- package/dist/site.d.ts.map +1 -1
- package/dist/site.js +24 -5
- package/dist/site.js.map +1 -1
- package/dist/worker.d.ts +87 -0
- package/dist/worker.d.ts.map +1 -0
- package/dist/worker.js +213 -0
- package/dist/worker.js.map +1 -0
- package/package.json +3 -3
- package/src/bridge.ts +12 -0
- package/src/build.ts +40 -8
- package/src/index.ts +1 -0
- package/src/runtime.ts +16 -3
- package/src/site.ts +31 -5
- package/src/worker.ts +236 -0
- package/swift/Package.swift +1 -0
- package/swift/Sources/SwiftBrowserNative/Host/HostBridge.swift +46 -2
- package/swift/Sources/SwiftBrowserNative/Host/JavaScriptCoreHost.swift +2 -1
- package/swift/Sources/SwiftBrowserNative/Host/WorkerHost.swift +279 -0
- package/swift/Sources/SwiftBrowserNative/Views/ElementView.swift +8 -1
- package/swift/Sources/SwiftBrowserNative/Views/Kinds/Charts.swift +340 -0
- package/swift/Sources/SwiftBrowserNative/Views/Kinds/Inspector.swift +29 -0
- package/swift/Sources/SwiftBrowserNative/Views/Kinds/Navigation.swift +8 -1
- package/swift/Sources/SwiftBrowserNative/Views/Kinds/Stacks.swift +58 -6
- package/swift/Sources/SwiftBrowserNative/Views/StyleModifier.swift +25 -7
- package/swift/Sources/SwiftBrowserProtocol/Props.swift +170 -0
- package/swift/Sources/SwiftBrowserProtocol/RendererEvent.swift +2 -0
- package/swift/Sources/SwiftBrowserProtocol/Style.swift +41 -0
- package/wasm/SwiftBrowserNative.wasm +2 -2
package/dist/worker.d.ts
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Workers for a JavaScriptCore app: a second context on its own thread, as
|
|
3
|
+
* a page has Web Workers. The host (Native/Sources/SwiftBrowserNative/Host/WorkerHost.swift)
|
|
4
|
+
* runs each one in a `JSVirtualMachine` of its own with a run loop, the
|
|
5
|
+
* runtime's globals (timers, `fetch`, `console`, `TextDecoder`) and the
|
|
6
|
+
* bundle `swiftbrowser-native build --worker <name>=<entry>` wrote next to
|
|
7
|
+
* app.js (`<name>.worker.js`). Messages cross as text: JSON, with typed
|
|
8
|
+
* arrays, `ArrayBuffer`s and `Error`s encoded (`encodeMessage`), since
|
|
9
|
+
* values cannot be shared between virtual machines. Drawn in a page
|
|
10
|
+
* (`swiftbrowser-native site`), the same app code gets the page's own
|
|
11
|
+
* `Worker` over the same bundle, published with the site.
|
|
12
|
+
*
|
|
13
|
+
* ```ts
|
|
14
|
+
* import { createWorker } from '@swiftbrowser/native';
|
|
15
|
+
* const worker = createWorker('render'); // render.worker.js
|
|
16
|
+
* worker.onmessage = (event) => console.log(event.data);
|
|
17
|
+
* worker.postMessage({ draw: 'something' });
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
import { type NativeBridge } from './bridge';
|
|
21
|
+
/** What `Worker` and a worker's `self` have in common, as an app uses them. */
|
|
22
|
+
export interface WorkerLike {
|
|
23
|
+
postMessage(data: unknown, transfer?: unknown[]): void;
|
|
24
|
+
terminate(): void;
|
|
25
|
+
onmessage: ((event: WorkerMessageEvent) => void) | null;
|
|
26
|
+
onerror: ((event: WorkerErrorEvent) => void) | null;
|
|
27
|
+
addEventListener(type: 'message', listener: (event: WorkerMessageEvent) => void): void;
|
|
28
|
+
addEventListener(type: 'error', listener: (event: WorkerErrorEvent) => void): void;
|
|
29
|
+
removeEventListener(type: 'message' | 'error', listener: (event: never) => void): void;
|
|
30
|
+
}
|
|
31
|
+
export interface WorkerMessageEvent {
|
|
32
|
+
type: 'message';
|
|
33
|
+
data: unknown;
|
|
34
|
+
}
|
|
35
|
+
export interface WorkerErrorEvent {
|
|
36
|
+
type: 'error';
|
|
37
|
+
message: string;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* A message as text: JSON, with a typed array or `ArrayBuffer` as
|
|
41
|
+
* `{ $sbBytes, $sbType }` (base64) and an `Error` as `{ $sbError }`. What
|
|
42
|
+
* JSON drops (functions, `undefined` in an object, a `Map`) is dropped here
|
|
43
|
+
* too; a `Date` arrives as its ISO string.
|
|
44
|
+
*/
|
|
45
|
+
export declare function encodeMessage(data: unknown): string;
|
|
46
|
+
/** The value `encodeMessage` encoded. */
|
|
47
|
+
export declare function decodeMessage(text: string): unknown;
|
|
48
|
+
type Listener<E> = (event: E) => void;
|
|
49
|
+
/** `Worker` over the bridge, in the app's context. */
|
|
50
|
+
export declare class NativeWorker implements WorkerLike {
|
|
51
|
+
#private;
|
|
52
|
+
private static nextId;
|
|
53
|
+
private static readonly live;
|
|
54
|
+
onmessage: Listener<WorkerMessageEvent> | null;
|
|
55
|
+
onerror: Listener<WorkerErrorEvent> | null;
|
|
56
|
+
/** `name` is the bundle's: `<name>.worker.js` next to app.js. A URL's last path segment serves as the name (`render.worker.js` → `render`). */
|
|
57
|
+
constructor(name: string | {
|
|
58
|
+
href: string;
|
|
59
|
+
} | URL, native?: NativeBridge | undefined);
|
|
60
|
+
postMessage(data: unknown): void;
|
|
61
|
+
terminate(): void;
|
|
62
|
+
addEventListener(type: 'message', listener: Listener<WorkerMessageEvent>): void;
|
|
63
|
+
addEventListener(type: 'error', listener: Listener<WorkerErrorEvent>): void;
|
|
64
|
+
removeEventListener(type: 'message' | 'error', listener: Listener<never>): void;
|
|
65
|
+
/** The bridge's `__sbWorkerMessage(id, text)`. */
|
|
66
|
+
static receive(id: number, text: string): void;
|
|
67
|
+
/** The bridge's `__sbWorkerError(id, message)`: an uncaught exception in the worker. */
|
|
68
|
+
static fail(id: number, message: string): void;
|
|
69
|
+
}
|
|
70
|
+
/** The bundle name a worker was asked for: `render`, or the `render` of `.../render.worker.js`. */
|
|
71
|
+
export declare function workerName(name: string | {
|
|
72
|
+
href: string;
|
|
73
|
+
} | URL): string;
|
|
74
|
+
/**
|
|
75
|
+
* A worker from the bundle `<name>.worker.js`: the page's own `Worker` when
|
|
76
|
+
* the app is drawn in a page (the bundle published with the site), else the
|
|
77
|
+
* host's over the bridge.
|
|
78
|
+
*/
|
|
79
|
+
export declare function createWorker(name: string): WorkerLike;
|
|
80
|
+
/**
|
|
81
|
+
* A worker's own globals over its bridge: `self`, `postMessage`, `onmessage`
|
|
82
|
+
* and `onerror` as properties the script assigns, `addEventListener`,
|
|
83
|
+
* `close`, and `__sbWorkerReceive`, which the host calls with each message.
|
|
84
|
+
*/
|
|
85
|
+
export declare function installWorkerScope(native: NativeBridge, target: Record<string, unknown>): string[];
|
|
86
|
+
export {};
|
|
87
|
+
//# sourceMappingURL=worker.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"worker.d.ts","sourceRoot":"","sources":["../src/worker.ts"],"names":[],"mappings":"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"}
|
package/dist/worker.js
ADDED
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Workers for a JavaScriptCore app: a second context on its own thread, as
|
|
3
|
+
* a page has Web Workers. The host (Native/Sources/SwiftBrowserNative/Host/WorkerHost.swift)
|
|
4
|
+
* runs each one in a `JSVirtualMachine` of its own with a run loop, the
|
|
5
|
+
* runtime's globals (timers, `fetch`, `console`, `TextDecoder`) and the
|
|
6
|
+
* bundle `swiftbrowser-native build --worker <name>=<entry>` wrote next to
|
|
7
|
+
* app.js (`<name>.worker.js`). Messages cross as text: JSON, with typed
|
|
8
|
+
* arrays, `ArrayBuffer`s and `Error`s encoded (`encodeMessage`), since
|
|
9
|
+
* values cannot be shared between virtual machines. Drawn in a page
|
|
10
|
+
* (`swiftbrowser-native site`), the same app code gets the page's own
|
|
11
|
+
* `Worker` over the same bundle, published with the site.
|
|
12
|
+
*
|
|
13
|
+
* ```ts
|
|
14
|
+
* import { createWorker } from '@swiftbrowser/native';
|
|
15
|
+
* const worker = createWorker('render'); // render.worker.js
|
|
16
|
+
* worker.onmessage = (event) => console.log(event.data);
|
|
17
|
+
* worker.postMessage({ draw: 'something' });
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
import { nativeBridge } from './bridge';
|
|
21
|
+
import { base64Decode, base64Encode } from './encoding';
|
|
22
|
+
// MARK: Messages
|
|
23
|
+
const TYPED_ARRAYS = {
|
|
24
|
+
Uint8Array,
|
|
25
|
+
Uint8ClampedArray,
|
|
26
|
+
Int8Array,
|
|
27
|
+
Uint16Array,
|
|
28
|
+
Int16Array,
|
|
29
|
+
Uint32Array,
|
|
30
|
+
Int32Array,
|
|
31
|
+
Float32Array,
|
|
32
|
+
Float64Array,
|
|
33
|
+
};
|
|
34
|
+
/**
|
|
35
|
+
* A message as text: JSON, with a typed array or `ArrayBuffer` as
|
|
36
|
+
* `{ $sbBytes, $sbType }` (base64) and an `Error` as `{ $sbError }`. What
|
|
37
|
+
* JSON drops (functions, `undefined` in an object, a `Map`) is dropped here
|
|
38
|
+
* too; a `Date` arrives as its ISO string.
|
|
39
|
+
*/
|
|
40
|
+
export function encodeMessage(data) {
|
|
41
|
+
return JSON.stringify(data === undefined ? null : data, (_key, value) => {
|
|
42
|
+
if (value instanceof ArrayBuffer)
|
|
43
|
+
return { $sbBytes: base64Encode(new Uint8Array(value)), $sbType: 'ArrayBuffer' };
|
|
44
|
+
if (ArrayBuffer.isView(value)) {
|
|
45
|
+
const name = value.constructor.name;
|
|
46
|
+
const bytes = new Uint8Array(value.buffer, value.byteOffset, value.byteLength);
|
|
47
|
+
return { $sbBytes: base64Encode(bytes), $sbType: name && name in TYPED_ARRAYS ? name : 'Uint8Array' };
|
|
48
|
+
}
|
|
49
|
+
if (value instanceof Error)
|
|
50
|
+
return { $sbError: { name: value.name, message: value.message, stack: value.stack ?? null } };
|
|
51
|
+
return value;
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
/** The value `encodeMessage` encoded. */
|
|
55
|
+
export function decodeMessage(text) {
|
|
56
|
+
return JSON.parse(text, (_key, value) => {
|
|
57
|
+
if (value && typeof value === 'object') {
|
|
58
|
+
const record = value;
|
|
59
|
+
if (typeof record['$sbBytes'] === 'string') {
|
|
60
|
+
const bytes = base64Decode(record['$sbBytes']);
|
|
61
|
+
const type = record['$sbType'];
|
|
62
|
+
if (type === 'ArrayBuffer')
|
|
63
|
+
return bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength);
|
|
64
|
+
const Constructor = TYPED_ARRAYS[type in TYPED_ARRAYS ? type : 'Uint8Array'];
|
|
65
|
+
// A view over the decoded bytes, aligned for the element size.
|
|
66
|
+
const buffer = bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength);
|
|
67
|
+
return new Constructor(buffer);
|
|
68
|
+
}
|
|
69
|
+
if (record['$sbError'] && typeof record['$sbError'] === 'object') {
|
|
70
|
+
const spec = record['$sbError'];
|
|
71
|
+
const error = new Error(spec.message ?? '');
|
|
72
|
+
if (spec.name)
|
|
73
|
+
error.name = spec.name;
|
|
74
|
+
if (spec.stack)
|
|
75
|
+
error.stack = spec.stack;
|
|
76
|
+
return error;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return value;
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
/** `Worker` over the bridge, in the app's context. */
|
|
83
|
+
export class NativeWorker {
|
|
84
|
+
static nextId = 1;
|
|
85
|
+
static live = new Map();
|
|
86
|
+
#id;
|
|
87
|
+
#native;
|
|
88
|
+
#terminated = false;
|
|
89
|
+
#messageListeners = new Set();
|
|
90
|
+
#errorListeners = new Set();
|
|
91
|
+
onmessage = null;
|
|
92
|
+
onerror = null;
|
|
93
|
+
/** `name` is the bundle's: `<name>.worker.js` next to app.js. A URL's last path segment serves as the name (`render.worker.js` → `render`). */
|
|
94
|
+
constructor(name, native = nativeBridge()) {
|
|
95
|
+
if (!native?.workerStart || !native.workerPost || !native.workerTerminate) {
|
|
96
|
+
throw new Error('Worker: this host has no workers (SwiftBrowser Native 0.13 or later)');
|
|
97
|
+
}
|
|
98
|
+
this.#native = native;
|
|
99
|
+
this.#id = NativeWorker.nextId++;
|
|
100
|
+
NativeWorker.live.set(this.#id, this);
|
|
101
|
+
const failure = native.workerStart(this.#id, workerName(name));
|
|
102
|
+
if (failure) {
|
|
103
|
+
NativeWorker.live.delete(this.#id);
|
|
104
|
+
throw new Error(failure);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
postMessage(data) {
|
|
108
|
+
if (this.#terminated)
|
|
109
|
+
return;
|
|
110
|
+
this.#native.workerPost(this.#id, encodeMessage(data));
|
|
111
|
+
}
|
|
112
|
+
terminate() {
|
|
113
|
+
if (this.#terminated)
|
|
114
|
+
return;
|
|
115
|
+
this.#terminated = true;
|
|
116
|
+
NativeWorker.live.delete(this.#id);
|
|
117
|
+
this.#native.workerTerminate(this.#id);
|
|
118
|
+
}
|
|
119
|
+
addEventListener(type, listener) {
|
|
120
|
+
if (type === 'message')
|
|
121
|
+
this.#messageListeners.add(listener);
|
|
122
|
+
else
|
|
123
|
+
this.#errorListeners.add(listener);
|
|
124
|
+
}
|
|
125
|
+
removeEventListener(type, listener) {
|
|
126
|
+
if (type === 'message')
|
|
127
|
+
this.#messageListeners.delete(listener);
|
|
128
|
+
else
|
|
129
|
+
this.#errorListeners.delete(listener);
|
|
130
|
+
}
|
|
131
|
+
/** The bridge's `__sbWorkerMessage(id, text)`. */
|
|
132
|
+
static receive(id, text) {
|
|
133
|
+
const worker = NativeWorker.live.get(id);
|
|
134
|
+
if (!worker)
|
|
135
|
+
return;
|
|
136
|
+
const event = { type: 'message', data: decodeMessage(text) };
|
|
137
|
+
worker.onmessage?.(event);
|
|
138
|
+
for (const listener of worker.#messageListeners)
|
|
139
|
+
listener(event);
|
|
140
|
+
}
|
|
141
|
+
/** The bridge's `__sbWorkerError(id, message)`: an uncaught exception in the worker. */
|
|
142
|
+
static fail(id, message) {
|
|
143
|
+
const worker = NativeWorker.live.get(id);
|
|
144
|
+
if (!worker)
|
|
145
|
+
return;
|
|
146
|
+
const event = { type: 'error', message };
|
|
147
|
+
worker.onerror?.(event);
|
|
148
|
+
for (const listener of worker.#errorListeners)
|
|
149
|
+
listener(event);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
/** The bundle name a worker was asked for: `render`, or the `render` of `.../render.worker.js`. */
|
|
153
|
+
export function workerName(name) {
|
|
154
|
+
const text = typeof name === 'string' ? name : name.href;
|
|
155
|
+
const last = text.split('/').pop() ?? text;
|
|
156
|
+
return last.replace(/\.(m?[jt]sx?)$/, '').replace(/\.worker$/, '');
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* A worker from the bundle `<name>.worker.js`: the page's own `Worker` when
|
|
160
|
+
* the app is drawn in a page (the bundle published with the site), else the
|
|
161
|
+
* host's over the bridge.
|
|
162
|
+
*/
|
|
163
|
+
export function createWorker(name) {
|
|
164
|
+
const base = typeof __SB_WORKER_BASE__ === 'string' ? __SB_WORKER_BASE__ : undefined;
|
|
165
|
+
const PageWorker = globalThis.Worker;
|
|
166
|
+
if (typeof PageWorker === 'function' && base !== undefined)
|
|
167
|
+
return new PageWorker(`${base}${workerName(name)}.worker.js`);
|
|
168
|
+
if (nativeBridge()?.workerStart)
|
|
169
|
+
return new NativeWorker(name);
|
|
170
|
+
if (typeof PageWorker === 'function')
|
|
171
|
+
return new PageWorker(`${workerName(name)}.worker.js`);
|
|
172
|
+
throw new Error(`createWorker: no host for "${name}" (not in a SwiftBrowser Native app or a page)`);
|
|
173
|
+
}
|
|
174
|
+
// MARK: The worker's side
|
|
175
|
+
/**
|
|
176
|
+
* A worker's own globals over its bridge: `self`, `postMessage`, `onmessage`
|
|
177
|
+
* and `onerror` as properties the script assigns, `addEventListener`,
|
|
178
|
+
* `close`, and `__sbWorkerReceive`, which the host calls with each message.
|
|
179
|
+
*/
|
|
180
|
+
export function installWorkerScope(native, target) {
|
|
181
|
+
const installed = [];
|
|
182
|
+
const define = (name, value) => {
|
|
183
|
+
Object.defineProperty(target, name, { value, writable: true, configurable: true, enumerable: false });
|
|
184
|
+
installed.push(name);
|
|
185
|
+
};
|
|
186
|
+
const messageListeners = new Set();
|
|
187
|
+
const errorListeners = new Set();
|
|
188
|
+
define('self', target);
|
|
189
|
+
define('postMessage', (data) => native.workerPostMessage(encodeMessage(data)));
|
|
190
|
+
define('addEventListener', (type, listener) => {
|
|
191
|
+
if (type === 'message')
|
|
192
|
+
messageListeners.add(listener);
|
|
193
|
+
else if (type === 'error')
|
|
194
|
+
errorListeners.add(listener);
|
|
195
|
+
});
|
|
196
|
+
define('removeEventListener', (type, listener) => {
|
|
197
|
+
if (type === 'message')
|
|
198
|
+
messageListeners.delete(listener);
|
|
199
|
+
else if (type === 'error')
|
|
200
|
+
errorListeners.delete(listener);
|
|
201
|
+
});
|
|
202
|
+
define('close', () => native.log('warn', 'close(): a worker ends when the app terminates it'));
|
|
203
|
+
define('__sbWorkerReceive', (text) => {
|
|
204
|
+
const event = { type: 'message', data: decodeMessage(text) };
|
|
205
|
+
const handler = target['onmessage'];
|
|
206
|
+
if (typeof handler === 'function')
|
|
207
|
+
handler(event);
|
|
208
|
+
for (const listener of messageListeners)
|
|
209
|
+
listener(event);
|
|
210
|
+
});
|
|
211
|
+
return installed;
|
|
212
|
+
}
|
|
213
|
+
//# sourceMappingURL=worker.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"worker.js","sourceRoot":"","sources":["../src/worker.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@swiftbrowser/native",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Run a @swiftbrowser/react app natively: the runtime a JavaScriptCore-hosted app needs (timers, console, fetch and the other web globals over the SwiftBrowser Native bridge), the `run` entry that renders into the native SwiftUI renderer, and the bundler that builds app.js for the Xcode target.",
|
|
6
6
|
"repository": {
|
|
@@ -59,8 +59,8 @@
|
|
|
59
59
|
},
|
|
60
60
|
"dependencies": {
|
|
61
61
|
"@babel/core": "^7.29.7",
|
|
62
|
-
"@swiftbrowser/react": "0.
|
|
63
|
-
"@swiftbrowser/web": "0.
|
|
62
|
+
"@swiftbrowser/react": "0.13.0",
|
|
63
|
+
"@swiftbrowser/web": "0.13.0",
|
|
64
64
|
"esbuild": "^0.28.2",
|
|
65
65
|
"react-refresh": "^0.17.0"
|
|
66
66
|
},
|
package/src/bridge.ts
CHANGED
|
@@ -26,6 +26,18 @@ export interface NativeBridge {
|
|
|
26
26
|
locale(): string;
|
|
27
27
|
/** The process's launch arguments (`-screen list` from an Xcode scheme or `simctl launch`). */
|
|
28
28
|
launchArguments?(): string[];
|
|
29
|
+
|
|
30
|
+
// Workers (worker.ts): a second context on its own thread, from a bundle
|
|
31
|
+
// `swiftbrowser-native build --worker <name>=<entry>` wrote next to app.js
|
|
32
|
+
// (`<name>.worker.js`). The app's context has the first three; a worker's
|
|
33
|
+
// context has the last and no `applyOps`.
|
|
34
|
+
/** Starts worker `id` from `<name>.worker.js`; returns an error message, or nothing. Its messages arrive at `__sbWorkerMessage(id, text)`, its errors at `__sbWorkerError(id, message)`. */
|
|
35
|
+
workerStart?(id: number, name: string): string | null | undefined;
|
|
36
|
+
/** Delivers an encoded message (worker.ts) to worker `id`'s `__sbWorkerReceive(text)`. */
|
|
37
|
+
workerPost?(id: number, text: string): void;
|
|
38
|
+
workerTerminate?(id: number): void;
|
|
39
|
+
/** In a worker's context: an encoded message to the app's context. */
|
|
40
|
+
workerPostMessage?(text: string): void;
|
|
29
41
|
}
|
|
30
42
|
|
|
31
43
|
/** The app's launch arguments, or none outside the native app. */
|
package/src/build.ts
CHANGED
|
@@ -23,14 +23,31 @@ export interface BuildOptions {
|
|
|
23
23
|
sourcemap?: boolean;
|
|
24
24
|
/** `process.env.NODE_ENV`; `production` when minifying, else `development`. */
|
|
25
25
|
mode?: 'development' | 'production';
|
|
26
|
+
/**
|
|
27
|
+
* Worker bundles by name (`{ render: 'src/render.worker.ts' }`): each a
|
|
28
|
+
* script of its own, the runtime first, written as `<name>.worker.js` next
|
|
29
|
+
* to the app bundle, which `createWorker(name)` runs on a thread of its own
|
|
30
|
+
* (worker.ts).
|
|
31
|
+
*/
|
|
32
|
+
workers?: Record<string, string>;
|
|
33
|
+
/**
|
|
34
|
+
* Where the worker bundles are published when the app is drawn in a page
|
|
35
|
+
* (`/native/bundle/App/`), as `createWorker` finds them there; absent in an
|
|
36
|
+
* Xcode bundle, whose host loads them by name.
|
|
37
|
+
*/
|
|
38
|
+
workerBase?: string;
|
|
26
39
|
}
|
|
27
40
|
|
|
28
41
|
export interface BuildResult {
|
|
29
42
|
code: string;
|
|
30
43
|
bytes: number;
|
|
31
44
|
warnings: string[];
|
|
45
|
+
/** The worker bundles, by name. */
|
|
46
|
+
workers: Record<string, { code: string; bytes: number }>;
|
|
32
47
|
}
|
|
33
48
|
|
|
49
|
+
const WORKER_NAME = /^[A-Za-z0-9_-]+$/;
|
|
50
|
+
|
|
34
51
|
const here = dirname(fileURLToPath(import.meta.url));
|
|
35
52
|
/** The runtime's source; the package ships `src` so this resolves from `dist` too. */
|
|
36
53
|
const runtimeEntry = fileURLToPath(new URL('../src/runtime.ts', import.meta.url));
|
|
@@ -68,34 +85,49 @@ function common(options: BuildOptions): esbuild.BuildOptions {
|
|
|
68
85
|
target: ['safari16'],
|
|
69
86
|
minify: options.minify ?? false,
|
|
70
87
|
sourcemap: options.sourcemap ? 'inline' : false,
|
|
71
|
-
define: {
|
|
88
|
+
define: {
|
|
89
|
+
'process.env.NODE_ENV': JSON.stringify(mode),
|
|
90
|
+
'import.meta.env.DEV': JSON.stringify(mode === 'development'),
|
|
91
|
+
__SB_WORKER_BASE__: options.workerBase === undefined ? 'undefined' : JSON.stringify(options.workerBase),
|
|
92
|
+
},
|
|
72
93
|
jsx: 'automatic',
|
|
73
94
|
logLevel: 'silent',
|
|
74
95
|
absWorkingDir: here,
|
|
75
96
|
};
|
|
76
97
|
}
|
|
77
98
|
|
|
78
|
-
/** Builds the two halves and joins them. */
|
|
99
|
+
/** Builds the two halves and joins them, and each worker the same way. */
|
|
79
100
|
export async function bundle(options: BuildOptions): Promise<BuildResult> {
|
|
80
101
|
const runtime = await esbuild.build({ ...common(options), entryPoints: [runtimeEntry] });
|
|
81
102
|
const app = await esbuild.build({ ...common(options), entryPoints: [options.entry], alias: aliases(options.entry) });
|
|
82
103
|
const code = `${text(runtime)}\n${text(app)}`;
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
104
|
+
const warnings = [...runtime.warnings, ...app.warnings].map((warning) => warning.text);
|
|
105
|
+
const workers: BuildResult['workers'] = {};
|
|
106
|
+
for (const [name, entry] of Object.entries(options.workers ?? {})) {
|
|
107
|
+
if (!WORKER_NAME.test(name)) throw new Error(`worker names are letters, digits, "_" and "-": ${name}`);
|
|
108
|
+
const worker = await esbuild.build({ ...common(options), entryPoints: [resolve(entry)], alias: aliases(entry) });
|
|
109
|
+
const workerCode = `${text(runtime)}\n${text(worker)}`;
|
|
110
|
+
workers[name] = { code: workerCode, bytes: Buffer.byteLength(workerCode) };
|
|
111
|
+
warnings.push(...worker.warnings.map((warning) => warning.text));
|
|
112
|
+
}
|
|
113
|
+
return { code, bytes: Buffer.byteLength(code), warnings, workers };
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** Where a worker bundle goes: `<name>.worker.js` beside the app bundle. */
|
|
117
|
+
export function workerOutfile(outfile: string, name: string): string {
|
|
118
|
+
return resolve(dirname(outfile), `${name}.worker.js`);
|
|
88
119
|
}
|
|
89
120
|
|
|
90
121
|
function text(result: esbuild.BuildResult): string {
|
|
91
122
|
return result.outputFiles?.map((file) => file.text).join('\n') ?? '';
|
|
92
123
|
}
|
|
93
124
|
|
|
94
|
-
/** Builds and writes the bundle. */
|
|
125
|
+
/** Builds and writes the bundle, and the worker bundles beside it. */
|
|
95
126
|
export async function buildApp(options: BuildOptions): Promise<BuildResult> {
|
|
96
127
|
const result = await bundle(options);
|
|
97
128
|
await mkdir(dirname(options.outfile), { recursive: true });
|
|
98
129
|
await writeFile(options.outfile, result.code);
|
|
130
|
+
for (const [name, worker] of Object.entries(result.workers)) await writeFile(workerOutfile(options.outfile, name), worker.code);
|
|
99
131
|
return result;
|
|
100
132
|
}
|
|
101
133
|
|
package/src/index.ts
CHANGED
|
@@ -18,3 +18,4 @@ export { nativeBridge, launchArguments, type NativeBridge, type NativeFetchReque
|
|
|
18
18
|
export { SimpleHeaders, SimpleRequest, SimpleResponse, SimpleAbortController, SimpleAbortSignal, createFetch } from './fetch';
|
|
19
19
|
export { SimpleURL, SimpleURLSearchParams } from './url';
|
|
20
20
|
export { SimpleTextEncoder, SimpleTextDecoder, base64Encode, base64Decode, utf8Encode, utf8Decode } from './encoding';
|
|
21
|
+
export { createWorker, NativeWorker, encodeMessage, decodeMessage, workerName, type WorkerLike, type WorkerMessageEvent, type WorkerErrorEvent } from './worker';
|
package/src/runtime.ts
CHANGED
|
@@ -11,15 +11,20 @@
|
|
|
11
11
|
* `atob`, `btoa`, `fetch` with `Headers`, `Request`, `Response`,
|
|
12
12
|
* `AbortController` and `AbortSignal`, `URL`, `URLSearchParams`, `crypto`
|
|
13
13
|
* (`getRandomValues`, `randomUUID`), `localStorage`, `structuredClone`,
|
|
14
|
-
* `navigator`,
|
|
15
|
-
*
|
|
16
|
-
*
|
|
14
|
+
* `navigator`, `Worker` (worker.ts) with the bridge's `__sbWorkerMessage`
|
|
15
|
+
* and `__sbWorkerError`, and the bridge's callbacks `__sbTimerFired` and
|
|
16
|
+
* `__sbFetchResult`. In a worker's own context (its bridge has
|
|
17
|
+
* `workerPostMessage`), `self`, `postMessage`, `addEventListener` and
|
|
18
|
+
* `__sbWorkerReceive` instead of `Worker`. `window` and `document` are not
|
|
19
|
+
* defined: code that checks for them takes its headless path, as it should
|
|
20
|
+
* here.
|
|
17
21
|
*/
|
|
18
22
|
|
|
19
23
|
import { nativeBridge, type NativeBridge } from './bridge';
|
|
20
24
|
import { atobPolyfill, base64Decode, btoaPolyfill, SimpleTextDecoder, SimpleTextEncoder } from './encoding';
|
|
21
25
|
import { createFetch } from './fetch';
|
|
22
26
|
import { SimpleURL, SimpleURLSearchParams } from './url';
|
|
27
|
+
import { installWorkerScope, NativeWorker } from './worker';
|
|
23
28
|
|
|
24
29
|
type AnyGlobal = Record<string, unknown>;
|
|
25
30
|
|
|
@@ -199,6 +204,14 @@ export function installRuntime(native: NativeBridge, target: AnyGlobal = globalT
|
|
|
199
204
|
hardwareConcurrency: 1,
|
|
200
205
|
});
|
|
201
206
|
|
|
207
|
+
if (native.workerPostMessage) {
|
|
208
|
+
installed.push(...installWorkerScope(native, target));
|
|
209
|
+
} else if (native.workerStart) {
|
|
210
|
+
define('Worker', NativeWorker);
|
|
211
|
+
define('__sbWorkerMessage', NativeWorker.receive);
|
|
212
|
+
define('__sbWorkerError', NativeWorker.fail);
|
|
213
|
+
}
|
|
214
|
+
|
|
202
215
|
return installed;
|
|
203
216
|
}
|
|
204
217
|
|
package/src/site.ts
CHANGED
|
@@ -36,6 +36,12 @@ export interface SiteOptions extends Omit<BuildOptions, 'outfile'> {
|
|
|
36
36
|
module?: string;
|
|
37
37
|
/** The path the site is served under when it is not a host's root (`/native/`); `/` by default. */
|
|
38
38
|
base?: string;
|
|
39
|
+
/**
|
|
40
|
+
* Asset catalogs (`*.xcassets` directories) the app's named images and
|
|
41
|
+
* colors come from, served as the site's catalog; the Xcode target's, or
|
|
42
|
+
* one kept for the site (a `.symbolset` holding any SVG draws as a symbol).
|
|
43
|
+
*/
|
|
44
|
+
catalogs?: string[];
|
|
39
45
|
/** Vite's log level while building the site; `warn` by default. */
|
|
40
46
|
logLevel?: 'info' | 'warn' | 'error' | 'silent';
|
|
41
47
|
}
|
|
@@ -46,6 +52,8 @@ export interface SiteResult {
|
|
|
46
52
|
module: string;
|
|
47
53
|
moduleBytes: number;
|
|
48
54
|
bundleBytes: number;
|
|
55
|
+
/** The worker bundles' sizes, by name. */
|
|
56
|
+
workers: Record<string, number>;
|
|
49
57
|
warnings: string[];
|
|
50
58
|
}
|
|
51
59
|
|
|
@@ -74,28 +82,46 @@ export async function buildNativeSite(options: SiteOptions): Promise<SiteResult>
|
|
|
74
82
|
);
|
|
75
83
|
}
|
|
76
84
|
if (options.base !== undefined && !/^\/([^/].*\/)?$/.test(options.base)) throw new Error(`site: --base must start and end with "/": ${options.base}`);
|
|
77
|
-
const { outDir, name: _name, displayName, module: _module, base, logLevel, ...bundleOptions } = options;
|
|
85
|
+
const { outDir, name: _name, displayName, module: _module, base, logLevel, catalogs, ...bundleOptions } = options;
|
|
86
|
+
for (const catalog of catalogs ?? []) {
|
|
87
|
+
if (!existsSync(catalog)) throw new Error(`site: no asset catalog at ${catalog}`);
|
|
88
|
+
}
|
|
78
89
|
// The bundle is the app's one resource: published at `/bundle/<name>/app.js`
|
|
79
90
|
// and mounted at `/bundle/app.js`, where the module's launch reads it.
|
|
80
91
|
const resources = mkdtempSync(path.join(tmpdir(), 'swiftbrowser-native-site-'));
|
|
81
92
|
try {
|
|
82
|
-
|
|
93
|
+
// The worker bundles are resources beside it, where the page's `Worker` loads them from.
|
|
94
|
+
const built = await bundle({ ...bundleOptions, outfile: path.join(resources, 'app.js'), workerBase: `${base ?? '/'}bundle/${name}/` });
|
|
83
95
|
writeFileSync(path.join(resources, 'app.js'), built.code);
|
|
96
|
+
const files = [path.join(resources, 'app.js')];
|
|
97
|
+
for (const [workerName, worker] of Object.entries(built.workers)) {
|
|
98
|
+
const file = path.join(resources, `${workerName}.worker.js`);
|
|
99
|
+
writeFileSync(file, worker.code);
|
|
100
|
+
files.push(file);
|
|
101
|
+
}
|
|
84
102
|
await buildSite({
|
|
85
103
|
apps: [{
|
|
86
104
|
name,
|
|
87
105
|
displayName: displayName ?? name,
|
|
88
106
|
description: 'A SwiftBrowser Native app in the browser',
|
|
89
107
|
wasm: module,
|
|
90
|
-
catalogs: [],
|
|
91
|
-
resources:
|
|
108
|
+
catalogs: (catalogs ?? []).map((catalog) => path.resolve(catalog)),
|
|
109
|
+
resources: files,
|
|
92
110
|
}],
|
|
93
111
|
layout: 'app',
|
|
94
112
|
outDir: path.resolve(outDir),
|
|
95
113
|
...(base !== undefined ? { base } : {}),
|
|
96
114
|
logLevel: logLevel ?? 'warn',
|
|
97
115
|
});
|
|
98
|
-
return {
|
|
116
|
+
return {
|
|
117
|
+
name,
|
|
118
|
+
outDir: path.resolve(outDir),
|
|
119
|
+
module,
|
|
120
|
+
moduleBytes: statSync(module).size,
|
|
121
|
+
bundleBytes: built.bytes,
|
|
122
|
+
workers: Object.fromEntries(Object.entries(built.workers).map(([workerName, worker]) => [workerName, worker.bytes])),
|
|
123
|
+
warnings: built.warnings,
|
|
124
|
+
};
|
|
99
125
|
} finally {
|
|
100
126
|
rmSync(resources, { recursive: true, force: true });
|
|
101
127
|
}
|