@swiftbrowser/native 0.0.0-stage → 0.10.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.
Files changed (107) hide show
  1. package/README.md +143 -2
  2. package/bin/swiftbrowser-native.mjs +49 -0
  3. package/dist/bridge.d.ts +50 -0
  4. package/dist/bridge.d.ts.map +1 -0
  5. package/dist/bridge.js +10 -0
  6. package/dist/bridge.js.map +1 -0
  7. package/dist/build.d.ts +47 -0
  8. package/dist/build.d.ts.map +1 -0
  9. package/dist/build.js +156 -0
  10. package/dist/build.js.map +1 -0
  11. package/dist/devBuild.d.ts +34 -0
  12. package/dist/devBuild.d.ts.map +1 -0
  13. package/dist/devBuild.js +133 -0
  14. package/dist/devBuild.js.map +1 -0
  15. package/dist/encoding.d.ts +32 -0
  16. package/dist/encoding.d.ts.map +1 -0
  17. package/dist/encoding.js +136 -0
  18. package/dist/encoding.js.map +1 -0
  19. package/dist/fetch.d.ts +117 -0
  20. package/dist/fetch.d.ts.map +1 -0
  21. package/dist/fetch.js +272 -0
  22. package/dist/fetch.js.map +1 -0
  23. package/dist/index.d.ts +20 -0
  24. package/dist/index.d.ts.map +1 -0
  25. package/dist/index.js +20 -0
  26. package/dist/index.js.map +1 -0
  27. package/dist/native.d.ts +19 -0
  28. package/dist/native.d.ts.map +1 -0
  29. package/dist/native.js +17 -0
  30. package/dist/native.js.map +1 -0
  31. package/dist/refresh.d.ts +18 -0
  32. package/dist/refresh.d.ts.map +1 -0
  33. package/dist/refresh.js +18 -0
  34. package/dist/refresh.js.map +1 -0
  35. package/dist/run.d.ts +27 -0
  36. package/dist/run.d.ts.map +1 -0
  37. package/dist/run.js +76 -0
  38. package/dist/run.js.map +1 -0
  39. package/dist/runtime.d.ts +30 -0
  40. package/dist/runtime.d.ts.map +1 -0
  41. package/dist/runtime.js +193 -0
  42. package/dist/runtime.js.map +1 -0
  43. package/dist/stubs/react-dom-client.d.ts +8 -0
  44. package/dist/stubs/react-dom-client.d.ts.map +1 -0
  45. package/dist/stubs/react-dom-client.js +11 -0
  46. package/dist/stubs/react-dom-client.js.map +1 -0
  47. package/dist/stubs/react-dom.d.ts +9 -0
  48. package/dist/stubs/react-dom.d.ts.map +1 -0
  49. package/dist/stubs/react-dom.js +13 -0
  50. package/dist/stubs/react-dom.js.map +1 -0
  51. package/dist/url.d.ts +48 -0
  52. package/dist/url.d.ts.map +1 -0
  53. package/dist/url.js +213 -0
  54. package/dist/url.js.map +1 -0
  55. package/dist/vendor.d.ts +11 -0
  56. package/dist/vendor.d.ts.map +1 -0
  57. package/dist/vendor.js +27 -0
  58. package/dist/vendor.js.map +1 -0
  59. package/dist/wasmHost.d.ts +35 -0
  60. package/dist/wasmHost.d.ts.map +1 -0
  61. package/dist/wasmHost.js +120 -0
  62. package/dist/wasmHost.js.map +1 -0
  63. package/package.json +73 -4
  64. package/src/bridge.ts +58 -0
  65. package/src/build.ts +185 -0
  66. package/src/devBuild.ts +152 -0
  67. package/src/encoding.ts +130 -0
  68. package/src/fetch.ts +305 -0
  69. package/src/index.ts +20 -0
  70. package/src/native.ts +35 -0
  71. package/src/refresh.ts +29 -0
  72. package/src/run.ts +98 -0
  73. package/src/runtime.ts +208 -0
  74. package/src/stubs/react-dom-client.ts +17 -0
  75. package/src/stubs/react-dom.ts +15 -0
  76. package/src/types/react-refresh.d.ts +38 -0
  77. package/src/url.ts +202 -0
  78. package/src/vendor.ts +30 -0
  79. package/src/wasmHost.ts +147 -0
  80. package/swift/Package.swift +31 -0
  81. package/swift/Sources/SwiftBrowserNative/Host/HostBridge.swift +228 -0
  82. package/swift/Sources/SwiftBrowserNative/Host/JSHost.swift +42 -0
  83. package/swift/Sources/SwiftBrowserNative/Host/JavaScriptCoreHost.swift +102 -0
  84. package/swift/Sources/SwiftBrowserNative/Host/ShimAliases.swift +25 -0
  85. package/swift/Sources/SwiftBrowserNative/Host/WebHost.swift +138 -0
  86. package/swift/Sources/SwiftBrowserNative/Runtime/NativeApp.swift +251 -0
  87. package/swift/Sources/SwiftBrowserNative/Runtime/NativeRegistry.swift +53 -0
  88. package/swift/Sources/SwiftBrowserNative/SwiftBrowserNative.swift +6 -0
  89. package/swift/Sources/SwiftBrowserNative/Views/Conversions.swift +361 -0
  90. package/swift/Sources/SwiftBrowserNative/Views/ElementView.swift +169 -0
  91. package/swift/Sources/SwiftBrowserNative/Views/Kinds/Controls.swift +268 -0
  92. package/swift/Sources/SwiftBrowserNative/Views/Kinds/Lists.swift +175 -0
  93. package/swift/Sources/SwiftBrowserNative/Views/Kinds/More.swift +196 -0
  94. package/swift/Sources/SwiftBrowserNative/Views/Kinds/Navigation.swift +246 -0
  95. package/swift/Sources/SwiftBrowserNative/Views/Kinds/Shapes.swift +108 -0
  96. package/swift/Sources/SwiftBrowserNative/Views/Kinds/Stacks.swift +189 -0
  97. package/swift/Sources/SwiftBrowserNative/Views/Kinds/TextAndImages.swift +102 -0
  98. package/swift/Sources/SwiftBrowserNative/Views/NativeRootView.swift +190 -0
  99. package/swift/Sources/SwiftBrowserNative/Views/StyleModifier.swift +182 -0
  100. package/swift/Sources/SwiftBrowserProtocol/JSONValue.swift +268 -0
  101. package/swift/Sources/SwiftBrowserProtocol/Op.swift +100 -0
  102. package/swift/Sources/SwiftBrowserProtocol/Props.swift +469 -0
  103. package/swift/Sources/SwiftBrowserProtocol/RendererEvent.swift +117 -0
  104. package/swift/Sources/SwiftBrowserProtocol/Style.swift +148 -0
  105. package/swift/Sources/SwiftBrowserProtocol/Values.swift +279 -0
  106. package/swift/Sources/SwiftBrowserTree/ElementNode.swift +65 -0
  107. package/swift/Sources/SwiftBrowserTree/OpTree.swift +168 -0
package/README.md CHANGED
@@ -1,3 +1,144 @@
1
- # Temporary Holding Version
1
+ # @swiftbrowser/native
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Runs a [`@swiftbrowser/react`](../react/README.md) app natively: inside an
4
+ iOS app, in JavaScriptCore, drawn by real SwiftUI through the
5
+ [SwiftBrowser Native](../../Native/README.md) Swift package. The package is
6
+ three things:
7
+
8
+ - **`run(<App />)`**, the entry an app calls: a `@swiftbrowser/react` root
9
+ whose ops go to the native renderer and whose events come back from it,
10
+ the native counterpart of `mount`.
11
+ - **The runtime**: the web globals bare JavaScriptCore lacks, built on the
12
+ bridge's primitives (`setTimeout` and friends, `requestAnimationFrame`,
13
+ `console`, `performance`, `fetch` with `Headers`, `Request`, `Response`
14
+ and `AbortController`, `URL`, `URLSearchParams`, `TextEncoder`,
15
+ `TextDecoder`, `atob`, `btoa`, `crypto.getRandomValues` and `randomUUID`,
16
+ `localStorage`, `structuredClone`, `navigator`). React's scheduler reads
17
+ some of these when it loads, so the bundle starts with the runtime.
18
+ - **The bundler**: `swiftbrowser-native build` writes one `app.js` for the
19
+ Xcode target, and `swiftbrowser-native dev` serves it with rebuild on
20
+ save for the app's `NativeApp.watch(url:)`.
21
+
22
+ The design is in [docs/native.md](../../docs/native.md). The package is
23
+ experimental, like `@swiftbrowser/react`.
24
+
25
+ The package also carries the Swift side: `swift/` is the
26
+ [SwiftBrowser Native](../../Native/README.md) Swift package (its
27
+ `Package.swift` and `Sources`, without the tests), so an Xcode project can
28
+ depend on `node_modules/@swiftbrowser/native/swift` as a local package and
29
+ build against the exact version of the renderer this JavaScript was written
30
+ for. `swiftbrowser native init` lays a project out that way:
31
+
32
+ ```sh
33
+ npx @swiftbrowser/cli native init MyApp # MyApp.xcodeproj, MyApp/MyAppApp.swift, js/
34
+ cd MyApp/js && npm install # React, @swiftbrowser/react and this package
35
+ npm run build # ../MyApp/app.js, the bundle the app ships
36
+ open ../MyApp.xcodeproj # run it on a simulator
37
+ ```
38
+
39
+ For a hand-made project:
40
+
41
+ ```sh
42
+ npm install @swiftbrowser/native @swiftbrowser/react react react-reconciler
43
+ ```
44
+
45
+ ## An app
46
+
47
+ ```tsx
48
+ // src/index.tsx
49
+ import { run } from '@swiftbrowser/native';
50
+ import { List, NavigationStack, Section, Text, Toggle } from '@swiftbrowser/react';
51
+ import { useState } from 'react';
52
+
53
+ function App() {
54
+ const [on, setOn] = useState(false);
55
+ return (
56
+ <NavigationStack>
57
+ <List navigationTitle="Settings">
58
+ <Section header="General">
59
+ <Toggle title="Notifications" isOn={[on, setOn]} />
60
+ <Text foregroundStyle="secondary">{on ? 'On' : 'Off'}</Text>
61
+ </Section>
62
+ </List>
63
+ </NavigationStack>
64
+ );
65
+ }
66
+
67
+ run(<App />);
68
+ ```
69
+
70
+ ```sh
71
+ npx swiftbrowser-native build src/index.tsx --out ../MyApp/app.js # the bundle Xcode ships
72
+ npx swiftbrowser-native dev src/index.tsx --out ../MyApp/app.js # http://localhost:5173/, Fast Refresh on save
73
+ ```
74
+
75
+ On the Swift side, `NativeApp().load()` runs the bundled `app.js` and
76
+ `NativeRootView(app:)` draws it (see the Swift package's README). The
77
+ examples under `Native/Examples` are complete apps laid out this way.
78
+
79
+ ## What runs where
80
+
81
+ Every call across the bridge is synchronous. A tap is one call chain:
82
+ SwiftUI's button action calls `__sbDispatch(json)`, the handler runs,
83
+ React flushes the pass, the sink calls `__sbNative.applyOps(json)`, the Swift
84
+ tree mutates, and everything returns. A controlled `TextField` therefore
85
+ never shows a stale frame: when its binding's setter returns, the app's
86
+ state already holds the new text.
87
+
88
+ There is no `window` and no `document`. Code that checks for them takes its
89
+ headless path, which is right here; `@swiftbrowser/react`'s `Host` (React DOM
90
+ inside the tree) renders nothing, as `react-dom` is aliased to a stub.
91
+
92
+ `fetch` sends the request over the bridge and `URLSession` performs it;
93
+ bodies are whole (no streams, no `Blob`). `URL` is a small parser, enough for
94
+ absolute and relative URLs and query strings, not the whole WHATWG algorithm.
95
+
96
+ ## The dev loop and Fast Refresh
97
+
98
+ `swiftbrowser-native dev` serves the bundle in two halves and a manifest:
99
+
100
+ | path | what |
101
+ |---|---|
102
+ | `/manifest.json` | `{ "vendor": <hash>, "app": <hash> }`, which `NativeApp.watch(url:)` polls every second |
103
+ | `/vendor.js` | the runtime, React Fast Refresh's runtime, React, `@swiftbrowser/react` and this package: evaluated once |
104
+ | `/app.js` | the app's own modules, each run through `react-refresh/babel`, importing React and the packages through shims that read `__sbVendor`: evaluated again on every save |
105
+
106
+ On a save the app half is evaluated in the running engine: its modules
107
+ register their components with the refresh runtime, and `run`, called again
108
+ at its end, performs the refresh and renders the new tree into the same
109
+ root. A component whose hooks are unchanged keeps its state; one whose
110
+ hooks changed remounts, as React Fast Refresh decides from the signatures
111
+ the transform records. A new vendor half (a dependency changed) starts the
112
+ app over. No module loader or WebSocket is involved, which bare
113
+ JavaScriptCore lacks. `test/fastRefresh.test.ts` runs the loop in Node.
114
+
115
+ `launchArguments()` reads the process's arguments (`-screen list` from an
116
+ Xcode scheme or `simctl launch`), as the Fidelity example does to open one
117
+ screen.
118
+
119
+ ## The escape hatch
120
+
121
+ ```tsx
122
+ import { Native } from '@swiftbrowser/native';
123
+
124
+ <Native name="MapView" props={{ region }} onEvent={(value) => console.log('map said', value)}>
125
+ <Text>Children are SwiftUI views inside the native view</Text>
126
+ </Native>
127
+ ```
128
+
129
+ The Swift app registers the view once:
130
+
131
+ ```swift
132
+ NativeRegistry.register("MapView") { context in
133
+ MapView(region: context.props["region"]) { context.send(["tapped": true]) }
134
+ }
135
+ ```
136
+
137
+ ## Testing without a device
138
+
139
+ `installRuntime(native, target)` installs the runtime on any object over
140
+ any bridge, and `run(element, { native })` renders through one; the tests
141
+ in `test/` drive both with a fake bridge. `test/bundle.test.ts` builds the
142
+ example apps and runs the bundles in a bare `vm` context with only
143
+ `__sbNative`, as JavaScriptCore runs them, and replays the Todos example's
144
+ snapshot script against the tree the ops build.
@@ -0,0 +1,49 @@
1
+ #!/usr/bin/env node
2
+ // `swiftbrowser-native build <entry> --out <file>` writes the app bundle (the
3
+ // same as `swiftbrowser native build`, from @swiftbrowser/cli, which also
4
+ // writes a new project with `swiftbrowser native init`);
5
+ // `swiftbrowser-native dev <entry> --out <file> [--port 5173]` serves it with
6
+ // rebuild on save for `NativeApp.watch(url:)`.
7
+
8
+ import { resolve } from 'node:path';
9
+ import { buildApp, serve } from '../dist/build.js';
10
+
11
+ const [command, entry, ...rest] = process.argv.slice(2);
12
+ const flags = {};
13
+ for (let i = 0; i < rest.length; i++) {
14
+ const arg = rest[i];
15
+ if (arg === '--out' || arg === '--port') flags[arg.slice(2)] = rest[++i];
16
+ else if (arg.startsWith('--')) flags[arg.slice(2)] = true;
17
+ }
18
+
19
+ if (!command || !entry || !flags.out || !['build', 'dev'].includes(command)) {
20
+ console.error('usage: swiftbrowser-native build <entry.tsx> --out <App/app.js> [--minify] [--sourcemap]');
21
+ console.error(' swiftbrowser-native dev <entry.tsx> --out <App/app.js> [--port 5173] [--sourcemap]');
22
+ process.exit(2);
23
+ }
24
+
25
+ const options = {
26
+ entry: resolve(entry),
27
+ outfile: resolve(flags.out),
28
+ minify: Boolean(flags.minify),
29
+ sourcemap: Boolean(flags.sourcemap),
30
+ };
31
+
32
+ const kb = (bytes) => `${(bytes / 1024).toFixed(0)} kB`;
33
+
34
+ if (command === 'build') {
35
+ const result = await buildApp(options);
36
+ for (const warning of result.warnings) console.warn(warning);
37
+ console.log(`${options.outfile}: ${kb(result.bytes)}`);
38
+ } else {
39
+ const port = flags.port ? Number(flags.port) : 5173;
40
+ await serve({
41
+ ...options,
42
+ port,
43
+ onBuild: (result) => {
44
+ if (result instanceof Error) console.error(result.message);
45
+ else console.log(`${new Date().toLocaleTimeString()} built ${kb(result.bytes)} → ${options.outfile}`);
46
+ },
47
+ });
48
+ console.log(`serving http://localhost:${port}/ (NativeApp.watch(url:) in a Debug build loads vendor.js once and refreshes app.js on every save)`);
49
+ }
@@ -0,0 +1,50 @@
1
+ /**
2
+ * The native side of the bridge, as Swift installs it on `__sbNative`
3
+ * (Native/Sources/SwiftBrowserNative/Host/HostBridge.swift). Every call is
4
+ * synchronous; the ones that answer later do so by calling a global this
5
+ * package defines (`__sbTimerFired`, `__sbFetchResult`).
6
+ */
7
+ export interface NativeBridge {
8
+ /** Applies a render pass's ops (a JSON array). Returns an error message, or nothing. */
9
+ applyOps(json: string): string | null | undefined;
10
+ log(level: string, message: string): void;
11
+ /** Milliseconds, monotonic. */
12
+ now(): number;
13
+ setTimer(id: number, delayMs: number, repeats: boolean): void;
14
+ clearTimer(id: number): void;
15
+ /** Starts a request; the answer arrives at `__sbFetchResult(id, result)`. */
16
+ fetch(id: number, request: string | NativeFetchRequest): void;
17
+ storageGet(key: string): string | null;
18
+ storageSet(key: string, value: string): void;
19
+ storageRemove(key: string): void;
20
+ storageKeys(): string[];
21
+ storageClear(): void;
22
+ /** Base64 of `count` random bytes. */
23
+ randomBytes(count: number): string;
24
+ openURL(url: string): boolean;
25
+ /** The viewer's locale identifier (`en_US`). */
26
+ locale(): string;
27
+ /** The process's launch arguments (`-screen list` from an Xcode scheme or `simctl launch`). */
28
+ launchArguments?(): string[];
29
+ }
30
+ /** The app's launch arguments, or none outside the native app. */
31
+ export declare function launchArguments(): string[];
32
+ export interface NativeFetchRequest {
33
+ url: string;
34
+ method: string;
35
+ headers: Record<string, string>;
36
+ /** Base64, or null. */
37
+ body: string | null;
38
+ }
39
+ export interface NativeFetchResult {
40
+ status?: number;
41
+ statusText?: string;
42
+ headers?: Record<string, string>;
43
+ url?: string;
44
+ /** Base64. */
45
+ body?: string;
46
+ error?: string;
47
+ }
48
+ /** The host object, or undefined outside the native app (tests, a bundler's dry run). */
49
+ export declare function nativeBridge(g?: typeof globalThis): NativeBridge | undefined;
50
+ //# sourceMappingURL=bridge.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bridge.d.ts","sourceRoot":"","sources":["../src/bridge.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,MAAM,WAAW,YAAY;IAC3B,wFAAwF;IACxF,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;IAClD,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1C,+BAA+B;IAC/B,GAAG,IAAI,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,IAAI,CAAC;IAC9D,UAAU,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,6EAA6E;IAC7E,KAAK,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,kBAAkB,GAAG,IAAI,CAAC;IAC9D,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;IACvC,UAAU,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7C,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,WAAW,IAAI,MAAM,EAAE,CAAC;IACxB,YAAY,IAAI,IAAI,CAAC;IACrB,sCAAsC;IACtC,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAAC;IACnC,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;IAC9B,gDAAgD;IAChD,MAAM,IAAI,MAAM,CAAC;IACjB,+FAA+F;IAC/F,eAAe,CAAC,IAAI,MAAM,EAAE,CAAC;CAC9B;AAED,kEAAkE;AAClE,wBAAgB,eAAe,IAAI,MAAM,EAAE,CAG1C;AAED,MAAM,WAAW,kBAAkB;IACjC,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,uBAAuB;IACvB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;CACrB;AAED,MAAM,WAAW,iBAAiB;IAChC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,cAAc;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,yFAAyF;AACzF,wBAAgB,YAAY,CAAC,CAAC,GAAE,OAAO,UAAuB,GAAG,YAAY,GAAG,SAAS,CAExF"}
package/dist/bridge.js ADDED
@@ -0,0 +1,10 @@
1
+ /** The app's launch arguments, or none outside the native app. */
2
+ export function launchArguments() {
3
+ const native = nativeBridge();
4
+ return native?.launchArguments?.() ?? [];
5
+ }
6
+ /** The host object, or undefined outside the native app (tests, a bundler's dry run). */
7
+ export function nativeBridge(g = globalThis) {
8
+ return g.__sbNative;
9
+ }
10
+ //# sourceMappingURL=bridge.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bridge.js","sourceRoot":"","sources":["../src/bridge.ts"],"names":[],"mappings":"AA8BA,kEAAkE;AAClE,MAAM,UAAU,eAAe;IAC7B,MAAM,MAAM,GAAG,YAAY,EAAE,CAAC;IAC9B,OAAO,MAAM,EAAE,eAAe,EAAE,EAAE,IAAI,EAAE,CAAC;AAC3C,CAAC;AAoBD,yFAAyF;AACzF,MAAM,UAAU,YAAY,CAAC,IAAuB,UAAU;IAC5D,OAAQ,CAA8C,CAAC,UAAU,CAAC;AACpE,CAAC"}
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Builds `app.js` for an Xcode target: the runtime first (./runtime, so the
3
+ * web globals exist before React's scheduler reads them), then the app, as
4
+ * one script JavaScriptCore evaluates with `NativeApp.load`. `react-dom` is
5
+ * aliased to stubs (no DOM here); `@swiftbrowser/react` and React are
6
+ * bundled in. Node only (the CLI in ../bin and build scripts use it).
7
+ */
8
+ export interface BuildOptions {
9
+ /** The app's entry module, which calls `run(<App />)`. */
10
+ entry: string;
11
+ /** Where to write the bundle (`<App>/app.js` in the Xcode target's folder). */
12
+ outfile: string;
13
+ minify?: boolean;
14
+ sourcemap?: boolean;
15
+ /** `process.env.NODE_ENV`; `production` when minifying, else `development`. */
16
+ mode?: 'development' | 'production';
17
+ }
18
+ export interface BuildResult {
19
+ code: string;
20
+ bytes: number;
21
+ warnings: string[];
22
+ }
23
+ /** Builds the two halves and joins them. */
24
+ export declare function bundle(options: BuildOptions): Promise<BuildResult>;
25
+ /** Builds and writes the bundle. */
26
+ export declare function buildApp(options: BuildOptions): Promise<BuildResult>;
27
+ export interface ServeOptions extends BuildOptions {
28
+ port?: number;
29
+ /** Called after every build (a log line). */
30
+ onBuild?: (result: BuildResult | Error) => void;
31
+ }
32
+ /**
33
+ * The dev loop: serves the dev bundle (devBuild.ts) for `NativeApp.watch(url:)`:
34
+ *
35
+ * | path | what |
36
+ * |---|---|
37
+ * | `/manifest.json` | `{ "vendor": <hash>, "app": <hash> }`, polled; `Cache-Control: no-store` |
38
+ * | `/vendor.js` | the vendor half, evaluated once (again when its hash changes) |
39
+ * | `/app.js` | the app half, evaluated again on every change: Fast Refresh |
40
+ *
41
+ * Rebuilds when the sources change (esbuild's watcher over the app's
42
+ * inputs), and writes the plain single bundle to `outfile` too, so an app
43
+ * built from Xcode without the server gets the latest. Returns a function
44
+ * that stops everything.
45
+ */
46
+ export declare function serve(options: ServeOptions): Promise<() => Promise<void>>;
47
+ //# sourceMappingURL=build.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"build.d.ts","sourceRoot":"","sources":["../src/build.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAUH,MAAM,WAAW,YAAY;IAC3B,0DAA0D;IAC1D,KAAK,EAAE,MAAM,CAAC;IACd,+EAA+E;IAC/E,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,+EAA+E;IAC/E,IAAI,CAAC,EAAE,aAAa,GAAG,YAAY,CAAC;CACrC;AAED,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAkCD,4CAA4C;AAC5C,wBAAsB,MAAM,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,WAAW,CAAC,CASxE;AAMD,oCAAoC;AACpC,wBAAsB,QAAQ,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,WAAW,CAAC,CAK1E;AAED,MAAM,WAAW,YAAa,SAAQ,YAAY;IAChD,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,6CAA6C;IAC7C,OAAO,CAAC,EAAE,CAAC,MAAM,EAAE,WAAW,GAAG,KAAK,KAAK,IAAI,CAAC;CACjD;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAsB,KAAK,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC,CAuE/E"}
package/dist/build.js ADDED
@@ -0,0 +1,156 @@
1
+ /**
2
+ * Builds `app.js` for an Xcode target: the runtime first (./runtime, so the
3
+ * web globals exist before React's scheduler reads them), then the app, as
4
+ * one script JavaScriptCore evaluates with `NativeApp.load`. `react-dom` is
5
+ * aliased to stubs (no DOM here); `@swiftbrowser/react` and React are
6
+ * bundled in. Node only (the CLI in ../bin and build scripts use it).
7
+ */
8
+ import * as esbuild from 'esbuild';
9
+ import { createHash } from 'node:crypto';
10
+ import { existsSync } from 'node:fs';
11
+ import { createServer } from 'node:http';
12
+ import { mkdir, writeFile } from 'node:fs/promises';
13
+ import { dirname } from 'node:path';
14
+ import { fileURLToPath } from 'node:url';
15
+ const here = dirname(fileURLToPath(import.meta.url));
16
+ /** The runtime's source; the package ships `src` so this resolves from `dist` too. */
17
+ const runtimeEntry = fileURLToPath(new URL('../src/runtime.ts', import.meta.url));
18
+ const reactSources = fileURLToPath(new URL('../../react/src/index.ts', import.meta.url));
19
+ const stubs = {
20
+ 'react-dom/client': fileURLToPath(new URL('../src/stubs/react-dom-client.ts', import.meta.url)),
21
+ 'react-dom': fileURLToPath(new URL('../src/stubs/react-dom.ts', import.meta.url)),
22
+ // This package from its sources, so a bundle never needs its `dist` built first.
23
+ '@swiftbrowser/native/runtime': runtimeEntry,
24
+ '@swiftbrowser/native': fileURLToPath(new URL('../src/index.ts', import.meta.url)),
25
+ // In this repository, @swiftbrowser/react from its sources too (installed, it is a built package).
26
+ ...(existsSync(reactSources) ? { '@swiftbrowser/react': reactSources } : {}),
27
+ };
28
+ function common(options) {
29
+ const mode = options.mode ?? (options.minify ? 'production' : 'development');
30
+ return {
31
+ bundle: true,
32
+ write: false,
33
+ format: 'iife',
34
+ platform: 'browser',
35
+ // JavaScriptCore on iOS 17 has every ES2022 feature.
36
+ target: ['safari16'],
37
+ minify: options.minify ?? false,
38
+ sourcemap: options.sourcemap ? 'inline' : false,
39
+ define: { 'process.env.NODE_ENV': JSON.stringify(mode), 'import.meta.env.DEV': JSON.stringify(mode === 'development') },
40
+ jsx: 'automatic',
41
+ logLevel: 'silent',
42
+ absWorkingDir: here,
43
+ };
44
+ }
45
+ /** Builds the two halves and joins them. */
46
+ export async function bundle(options) {
47
+ const runtime = await esbuild.build({ ...common(options), entryPoints: [runtimeEntry] });
48
+ const app = await esbuild.build({ ...common(options), entryPoints: [options.entry], alias: stubs });
49
+ const code = `${text(runtime)}\n${text(app)}`;
50
+ return {
51
+ code,
52
+ bytes: Buffer.byteLength(code),
53
+ warnings: [...runtime.warnings, ...app.warnings].map((warning) => warning.text),
54
+ };
55
+ }
56
+ function text(result) {
57
+ return result.outputFiles?.map((file) => file.text).join('\n') ?? '';
58
+ }
59
+ /** Builds and writes the bundle. */
60
+ export async function buildApp(options) {
61
+ const result = await bundle(options);
62
+ await mkdir(dirname(options.outfile), { recursive: true });
63
+ await writeFile(options.outfile, result.code);
64
+ return result;
65
+ }
66
+ /**
67
+ * The dev loop: serves the dev bundle (devBuild.ts) for `NativeApp.watch(url:)`:
68
+ *
69
+ * | path | what |
70
+ * |---|---|
71
+ * | `/manifest.json` | `{ "vendor": <hash>, "app": <hash> }`, polled; `Cache-Control: no-store` |
72
+ * | `/vendor.js` | the vendor half, evaluated once (again when its hash changes) |
73
+ * | `/app.js` | the app half, evaluated again on every change: Fast Refresh |
74
+ *
75
+ * Rebuilds when the sources change (esbuild's watcher over the app's
76
+ * inputs), and writes the plain single bundle to `outfile` too, so an app
77
+ * built from Xcode without the server gets the latest. Returns a function
78
+ * that stops everything.
79
+ */
80
+ export async function serve(options) {
81
+ const { buildDev } = await import('./devBuild.js');
82
+ let vendor = { code: '', hash: '' };
83
+ let app = { code: '', hash: '' };
84
+ const rebuild = async () => {
85
+ try {
86
+ const dev = await buildDev({ entry: options.entry, sourcemap: options.sourcemap ?? false });
87
+ vendor = { code: dev.vendor, hash: hash(dev.vendor) };
88
+ app = { code: dev.app, hash: hash(dev.app) };
89
+ const full = await buildApp(options);
90
+ options.onBuild?.({ ...full, warnings: [...full.warnings, ...dev.warnings] });
91
+ }
92
+ catch (error) {
93
+ options.onBuild?.(error instanceof Error ? error : new Error(String(error)));
94
+ }
95
+ };
96
+ await rebuild();
97
+ // esbuild's watcher over the app build's inputs.
98
+ const context = await esbuild.context({
99
+ ...common(options),
100
+ entryPoints: [options.entry],
101
+ alias: stubs,
102
+ plugins: [
103
+ {
104
+ name: 'swiftbrowser-native-rebuild',
105
+ setup(build) {
106
+ let first = true;
107
+ build.onEnd(() => {
108
+ if (first) {
109
+ first = false;
110
+ return;
111
+ }
112
+ void rebuild();
113
+ });
114
+ },
115
+ },
116
+ ],
117
+ });
118
+ await context.watch();
119
+ const port = options.port ?? 5173;
120
+ const server = createServer((request, response) => {
121
+ const url = request.url ?? '/';
122
+ const send = (body, type, etag) => {
123
+ response.setHeader('Content-Type', type);
124
+ response.setHeader('Cache-Control', 'no-store');
125
+ response.setHeader('ETag', `"${etag}"`);
126
+ if (request.headers['if-none-match'] === `"${etag}"`) {
127
+ response.statusCode = 304;
128
+ response.end();
129
+ return;
130
+ }
131
+ response.end(request.method === 'HEAD' ? undefined : body);
132
+ };
133
+ if (url === '/manifest.json' || url === '/') {
134
+ send(JSON.stringify({ vendor: vendor.hash, app: app.hash }), 'application/json', `${vendor.hash}-${app.hash}`);
135
+ }
136
+ else if (url === '/vendor.js') {
137
+ send(vendor.code, 'text/javascript; charset=utf-8', vendor.hash);
138
+ }
139
+ else if (url === '/app.js') {
140
+ send(app.code, 'text/javascript; charset=utf-8', app.hash);
141
+ }
142
+ else {
143
+ response.statusCode = 404;
144
+ response.end('not found');
145
+ }
146
+ });
147
+ await new Promise((resolve) => server.listen(port, resolve));
148
+ return async () => {
149
+ await context.dispose();
150
+ await new Promise((resolve) => server.close(() => resolve()));
151
+ };
152
+ }
153
+ function hash(text) {
154
+ return createHash('sha1').update(text).digest('hex').slice(0, 12);
155
+ }
156
+ //# sourceMappingURL=build.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"build.js","sourceRoot":"","sources":["../src/build.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,34 @@
1
+ /**
2
+ * The dev bundle in two halves, for Fast Refresh (docs/native.md):
3
+ *
4
+ * - `vendor.js`: vendor.ts (the runtime, the refresh runtime, React,
5
+ * `@swiftbrowser/react`, this package), evaluated once per vendor change.
6
+ * - `app.js`: the app's own modules, with `react`, `react/jsx-runtime`,
7
+ * `@swiftbrowser/react` and `@swiftbrowser/native` resolved to shims that
8
+ * read `__sbVendor`, and every module run through `react-refresh/babel`
9
+ * so its components register with the refresh runtime. Evaluated again
10
+ * on every save; `run` then refreshes the mounted components in place.
11
+ *
12
+ * Node only (serve in build.ts uses it).
13
+ */
14
+ export interface DevBuildOptions {
15
+ entry: string;
16
+ sourcemap?: boolean;
17
+ }
18
+ export interface DevBundle {
19
+ vendor: string;
20
+ app: string;
21
+ warnings: string[];
22
+ }
23
+ /** The vendor half: vendor.ts with its dependencies. */
24
+ export declare function buildVendor(options: DevBuildOptions): Promise<{
25
+ code: string;
26
+ warnings: string[];
27
+ }>;
28
+ /** The app half: the entry's own modules over the vendor shims, with the refresh transform. */
29
+ export declare function buildApp(options: DevBuildOptions): Promise<{
30
+ code: string;
31
+ warnings: string[];
32
+ }>;
33
+ export declare function buildDev(options: DevBuildOptions): Promise<DevBundle>;
34
+ //# sourceMappingURL=devBuild.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"devBuild.d.ts","sourceRoot":"","sources":["../src/devBuild.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAiBH,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAED,MAAM,WAAW,SAAS;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AA0CD,wDAAwD;AACxD,wBAAsB,WAAW,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,EAAE,CAAA;CAAE,CAAC,CAWzG;AAED,+FAA+F;AAC/F,wBAAsB,QAAQ,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,EAAE,CAAA;CAAE,CAAC,CAmDtG;AAED,wBAAsB,QAAQ,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,SAAS,CAAC,CAG3E"}
@@ -0,0 +1,133 @@
1
+ /**
2
+ * The dev bundle in two halves, for Fast Refresh (docs/native.md):
3
+ *
4
+ * - `vendor.js`: vendor.ts (the runtime, the refresh runtime, React,
5
+ * `@swiftbrowser/react`, this package), evaluated once per vendor change.
6
+ * - `app.js`: the app's own modules, with `react`, `react/jsx-runtime`,
7
+ * `@swiftbrowser/react` and `@swiftbrowser/native` resolved to shims that
8
+ * read `__sbVendor`, and every module run through `react-refresh/babel`
9
+ * so its components register with the refresh runtime. Evaluated again
10
+ * on every save; `run` then refreshes the mounted components in place.
11
+ *
12
+ * Node only (serve in build.ts uses it).
13
+ */
14
+ import { transformAsync } from '@babel/core';
15
+ import * as esbuild from 'esbuild';
16
+ import { createRequire } from 'node:module';
17
+ import { dirname, extname } from 'node:path';
18
+ import { fileURLToPath } from 'node:url';
19
+ const require = createRequire(import.meta.url);
20
+ const here = dirname(fileURLToPath(import.meta.url));
21
+ const vendorEntry = fileURLToPath(new URL('../src/vendor.ts', import.meta.url));
22
+ const ownIndex = fileURLToPath(new URL('../src/index.ts', import.meta.url));
23
+ const reactSources = fileURLToPath(new URL('../../react/src/index.ts', import.meta.url));
24
+ /** The modules the app half takes from the vendor half. */
25
+ const VENDORED = ['react', 'react/jsx-runtime', 'react/jsx-dev-runtime', '@swiftbrowser/react', '@swiftbrowser/native'];
26
+ function common(options) {
27
+ return {
28
+ bundle: true,
29
+ write: false,
30
+ format: 'iife',
31
+ platform: 'browser',
32
+ target: ['safari16'],
33
+ sourcemap: options.sourcemap ? 'inline' : false,
34
+ define: { 'process.env.NODE_ENV': '"development"', 'import.meta.env.DEV': 'true' },
35
+ jsx: 'automatic',
36
+ logLevel: 'silent',
37
+ absWorkingDir: here,
38
+ };
39
+ }
40
+ /** The export names of a module, for a shim with static exports. */
41
+ async function exportNames(name) {
42
+ if (name === '@swiftbrowser/native')
43
+ return names(ownIndex);
44
+ if (name === '@swiftbrowser/react')
45
+ return names(reactSources);
46
+ // CommonJS (React): its exports are enumerable once required.
47
+ return Object.keys(require(name)).filter((key) => key !== 'default' && key !== '__esModule');
48
+ }
49
+ /** The export names of an ES module (its sources), from esbuild's metafile. */
50
+ async function names(entry) {
51
+ const result = await esbuild.build({
52
+ entryPoints: [entry],
53
+ bundle: true,
54
+ write: false,
55
+ format: 'esm',
56
+ metafile: true,
57
+ platform: 'browser',
58
+ logLevel: 'silent',
59
+ absWorkingDir: here,
60
+ external: ['react', 'react-dom', 'react-dom/client', 'react-reconciler', 'react-reconciler/constants', 'react-refresh/runtime', '@babel/core', 'esbuild', 'node:*'],
61
+ alias: { '@swiftbrowser/react': reactSources },
62
+ });
63
+ return Object.values(result.metafile.outputs).flatMap((output) => output.exports).filter((key) => key !== 'default');
64
+ }
65
+ /** The vendor half: vendor.ts with its dependencies. */
66
+ export async function buildVendor(options) {
67
+ const result = await esbuild.build({
68
+ ...common(options),
69
+ entryPoints: [vendorEntry],
70
+ alias: {
71
+ 'react-dom/client': fileURLToPath(new URL('../src/stubs/react-dom-client.ts', import.meta.url)),
72
+ 'react-dom': fileURLToPath(new URL('../src/stubs/react-dom.ts', import.meta.url)),
73
+ '@swiftbrowser/react': reactSources,
74
+ },
75
+ });
76
+ return { code: result.outputFiles.map((file) => file.text).join('\n'), warnings: result.warnings.map((w) => w.text) };
77
+ }
78
+ /** The app half: the entry's own modules over the vendor shims, with the refresh transform. */
79
+ export async function buildApp(options) {
80
+ const shims = new Map();
81
+ for (const name of VENDORED) {
82
+ const exports = await exportNames(name);
83
+ shims.set(name, [
84
+ `const m = globalThis.__sbVendor[${JSON.stringify(name)}];`,
85
+ `if (!m) throw new Error(${JSON.stringify(`vendor.js was not loaded before app.js (no ${name})`)});`,
86
+ 'export default m.default ?? m;',
87
+ ...exports.map((key) => `export const ${key} = m[${JSON.stringify(key)}];`),
88
+ ].join('\n'));
89
+ }
90
+ const refreshPlugin = require.resolve('react-refresh/babel');
91
+ const result = await esbuild.build({
92
+ ...common(options),
93
+ entryPoints: [options.entry],
94
+ plugins: [
95
+ {
96
+ name: 'swiftbrowser-native-dev',
97
+ setup(build) {
98
+ const filter = new RegExp(`^(${VENDORED.map((name) => name.replace(/[/]/g, '\\/')).join('|')})$`);
99
+ build.onResolve({ filter }, (args) => ({ path: args.path, namespace: 'sb-vendor' }));
100
+ build.onLoad({ filter: /.*/, namespace: 'sb-vendor' }, (args) => ({ contents: shims.get(args.path), loader: 'js' }));
101
+ // The app's own modules: TypeScript and JSX compiled by esbuild, then
102
+ // the refresh transform, with the registration functions in scope.
103
+ build.onLoad({ filter: /\.[jt]sx?$/ }, async (args) => {
104
+ if (args.path.includes('/node_modules/'))
105
+ return null;
106
+ const { readFile } = await import('node:fs/promises');
107
+ const source = await readFile(args.path, 'utf8');
108
+ const loader = extname(args.path).slice(1);
109
+ const compiled = await esbuild.transform(source, { loader, jsx: 'automatic', format: 'esm', sourcefile: args.path, target: 'es2022' });
110
+ const refreshed = await transformAsync(compiled.code, {
111
+ filename: args.path,
112
+ configFile: false,
113
+ babelrc: false,
114
+ sourceType: 'module',
115
+ plugins: [[refreshPlugin, { skipEnvCheck: true }]],
116
+ });
117
+ const prelude = [
118
+ `const $RefreshReg$ = (type, id) => globalThis.__sbRefresh.register(type, ${JSON.stringify(args.path)} + ' ' + id);`,
119
+ 'const $RefreshSig$ = globalThis.__sbRefresh.createSignatureFunctionForTransform;',
120
+ ].join('\n');
121
+ return { contents: `${prelude}\n${refreshed?.code ?? compiled.code}`, loader: 'js' };
122
+ });
123
+ },
124
+ },
125
+ ],
126
+ });
127
+ return { code: result.outputFiles.map((file) => file.text).join('\n'), warnings: result.warnings.map((w) => w.text) };
128
+ }
129
+ export async function buildDev(options) {
130
+ const [vendor, app] = await Promise.all([buildVendor(options), buildApp(options)]);
131
+ return { vendor: vendor.code, app: app.code, warnings: [...vendor.warnings, ...app.warnings] };
132
+ }
133
+ //# sourceMappingURL=devBuild.js.map