@swiftbrowser/native 0.10.4 → 0.11.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 CHANGED
@@ -116,6 +116,30 @@ JavaScriptCore lacks. `test/fastRefresh.test.ts` runs the loop in Node.
116
116
  Xcode scheme or `simctl launch`), as the Fidelity example does to open one
117
117
  screen.
118
118
 
119
+ ## In the browser
120
+
121
+ ```sh
122
+ npx swiftbrowser-native site src/index.tsx --out dist # the app as a static site
123
+ npx swiftbrowser-native site src/index.tsx --out ../web/dist/native --base /native/
124
+ ```
125
+
126
+ The same bundle, drawn by the native views in a browser: the package ships
127
+ the SwiftBrowser Native Swift package compiled for WebAssembly against
128
+ SwiftBrowser's SwiftUI shim (`wasm/SwiftBrowserNative.wasm`, the round trip
129
+ of [docs/native.md](../../docs/native.md)), and `site` writes it, the bundle
130
+ and the web renderer's page as one static directory, the layout
131
+ `swiftbrowser build` writes for a Swift app. The page runs the module, the
132
+ module evaluates the bundle at the page's global scope (so `fetch`, timers
133
+ and `localStorage` are the browser's own, same origin and cookies included),
134
+ the native views draw the app's ops with the shim's SwiftUI, and the web
135
+ renderer draws what the shim emits. One app, three renderers: the browser's
136
+ DOM through `@swiftbrowser/react` directly, real SwiftUI in the iOS app, and
137
+ this: the iOS app's own views, in a page. `--base /path/` when the site is
138
+ served under a path of another site, `--module <file>` for a module built
139
+ from a checkout (`swift build --swift-sdk <wasm sdk> -c release --product
140
+ NativeRoundTrip`), `--name <App>` for the site's app name (the folder
141
+ holding `js/` by default).
142
+
119
143
  ## The escape hatch
120
144
 
121
145
  ```tsx
@@ -3,22 +3,26 @@
3
3
  // same as `swiftbrowser native build`, from @swiftbrowser/cli, which also
4
4
  // writes a new project with `swiftbrowser native init`);
5
5
  // `swiftbrowser-native dev <entry> --out <file> [--port 5173]` serves it with
6
- // rebuild on save for `NativeApp.watch(url:)`.
6
+ // rebuild on save for `NativeApp.watch(url:)`; `swiftbrowser-native site
7
+ // <entry> --out <dir>` writes the app as a static site the web renderer runs
8
+ // (the native views built for WebAssembly, `--module <file>` for one other
9
+ // than the package's; `--base /path/` when the site is not a host's root).
7
10
 
8
- import { resolve } from 'node:path';
11
+ import { basename, resolve } from 'node:path';
9
12
  import { buildApp, serve } from '../dist/build.js';
10
13
 
11
14
  const [command, entry, ...rest] = process.argv.slice(2);
12
15
  const flags = {};
13
16
  for (let i = 0; i < rest.length; i++) {
14
17
  const arg = rest[i];
15
- if (arg === '--out' || arg === '--port') flags[arg.slice(2)] = rest[++i];
18
+ if (arg === '--out' || arg === '--port' || arg === '--module' || arg === '--base' || arg === '--name') flags[arg.slice(2)] = rest[++i];
16
19
  else if (arg.startsWith('--')) flags[arg.slice(2)] = true;
17
20
  }
18
21
 
19
- if (!command || !entry || !flags.out || !['build', 'dev'].includes(command)) {
22
+ if (!command || !entry || !flags.out || !['build', 'dev', 'site'].includes(command)) {
20
23
  console.error('usage: swiftbrowser-native build <entry.tsx> --out <App/app.js> [--minify] [--sourcemap]');
21
24
  console.error(' swiftbrowser-native dev <entry.tsx> --out <App/app.js> [--port 5173] [--sourcemap]');
25
+ console.error(' swiftbrowser-native site <entry.tsx> --out <dir> [--name <App>] [--module <file.wasm>] [--base /path/] [--minify]');
22
26
  process.exit(2);
23
27
  }
24
28
 
@@ -35,6 +39,19 @@ if (command === 'build') {
35
39
  const result = await buildApp(options);
36
40
  for (const warning of result.warnings) console.warn(warning);
37
41
  console.log(`${options.outfile}: ${kb(result.bytes)}`);
42
+ } else if (command === 'site') {
43
+ const { buildNativeSite } = await import('../dist/site.js');
44
+ const { outfile, ...bundleOptions } = options;
45
+ const result = await buildNativeSite({
46
+ ...bundleOptions,
47
+ outDir: outfile,
48
+ ...(flags.name ? { name: flags.name } : {}),
49
+ ...(flags.module ? { module: resolve(flags.module) } : {}),
50
+ ...(flags.base ? { base: flags.base } : {}),
51
+ });
52
+ for (const warning of result.warnings) console.warn(warning);
53
+ console.log(`${result.name}: app.js ${kb(result.bundleBytes)}, ${basename(result.module)} ${kb(result.moduleBytes)}`);
54
+ console.log(`site written to ${result.outDir}`);
38
55
  } else {
39
56
  const port = flags.port ? Number(flags.port) : 5173;
40
57
  await serve({
@@ -1 +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"}
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;AA8CD,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 CHANGED
@@ -10,21 +10,32 @@ import { createHash } from 'node:crypto';
10
10
  import { existsSync } from 'node:fs';
11
11
  import { createServer } from 'node:http';
12
12
  import { mkdir, writeFile } from 'node:fs/promises';
13
- import { dirname } from 'node:path';
13
+ import { dirname, resolve, sep } from 'node:path';
14
14
  import { fileURLToPath } from 'node:url';
15
15
  const here = dirname(fileURLToPath(import.meta.url));
16
16
  /** The runtime's source; the package ships `src` so this resolves from `dist` too. */
17
17
  const runtimeEntry = fileURLToPath(new URL('../src/runtime.ts', import.meta.url));
18
18
  const reactSources = fileURLToPath(new URL('../../react/src/index.ts', import.meta.url));
19
+ /** The repository, when this runs from a checkout (installed, the package has no `packages/react` beside it). */
20
+ const repository = existsSync(reactSources) ? resolve(here, '../../..') : null;
19
21
  const stubs = {
20
22
  'react-dom/client': fileURLToPath(new URL('../src/stubs/react-dom-client.ts', import.meta.url)),
21
23
  'react-dom': fileURLToPath(new URL('../src/stubs/react-dom.ts', import.meta.url)),
22
24
  // This package from its sources, so a bundle never needs its `dist` built first.
23
25
  '@swiftbrowser/native/runtime': runtimeEntry,
24
26
  '@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
27
  };
28
+ /**
29
+ * The aliases for an entry: in this repository, an app of the repository's
30
+ * (the examples) gets @swiftbrowser/react from its sources too (installed,
31
+ * it is a built package). An app outside it (a project built with the
32
+ * checkout's bundler) keeps its own installed copy: the sources would bring
33
+ * the repository's React along, a second copy beside the app's.
34
+ */
35
+ function aliases(entry) {
36
+ const inRepository = repository !== null && resolve(entry).startsWith(repository + sep);
37
+ return { ...stubs, ...(inRepository ? { '@swiftbrowser/react': reactSources } : {}) };
38
+ }
28
39
  function common(options) {
29
40
  const mode = options.mode ?? (options.minify ? 'production' : 'development');
30
41
  return {
@@ -45,7 +56,7 @@ function common(options) {
45
56
  /** Builds the two halves and joins them. */
46
57
  export async function bundle(options) {
47
58
  const runtime = await esbuild.build({ ...common(options), entryPoints: [runtimeEntry] });
48
- const app = await esbuild.build({ ...common(options), entryPoints: [options.entry], alias: stubs });
59
+ const app = await esbuild.build({ ...common(options), entryPoints: [options.entry], alias: aliases(options.entry) });
49
60
  const code = `${text(runtime)}\n${text(app)}`;
50
61
  return {
51
62
  code,
package/dist/build.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"build.js","sourceRoot":"","sources":["../src/build.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,OAAO,MAAM,SAAS,CAAC;AACnC,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAmBzC,MAAM,IAAI,GAAG,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;AACrD,sFAAsF;AACtF,MAAM,YAAY,GAAG,aAAa,CAAC,IAAI,GAAG,CAAC,mBAAmB,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;AAClF,MAAM,YAAY,GAAG,aAAa,CAAC,IAAI,GAAG,CAAC,0BAA0B,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;AACzF,MAAM,KAAK,GAA2B;IACpC,kBAAkB,EAAE,aAAa,CAAC,IAAI,GAAG,CAAC,kCAAkC,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAC/F,WAAW,EAAE,aAAa,CAAC,IAAI,GAAG,CAAC,2BAA2B,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACjF,iFAAiF;IACjF,8BAA8B,EAAE,YAAY;IAC5C,sBAAsB,EAAE,aAAa,CAAC,IAAI,GAAG,CAAC,iBAAiB,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAClF,mGAAmG;IACnG,GAAG,CAAC,UAAU,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,EAAE,qBAAqB,EAAE,YAAY,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;CAC7E,CAAC;AAEF,SAAS,MAAM,CAAC,OAAqB;IACnC,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC;IAC7E,OAAO;QACL,MAAM,EAAE,IAAI;QACZ,KAAK,EAAE,KAAK;QACZ,MAAM,EAAE,MAAM;QACd,QAAQ,EAAE,SAAS;QACnB,qDAAqD;QACrD,MAAM,EAAE,CAAC,UAAU,CAAC;QACpB,MAAM,EAAE,OAAO,CAAC,MAAM,IAAI,KAAK;QAC/B,SAAS,EAAE,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,KAAK;QAC/C,MAAM,EAAE,EAAE,sBAAsB,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE,qBAAqB,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,KAAK,aAAa,CAAC,EAAE;QACvH,GAAG,EAAE,WAAW;QAChB,QAAQ,EAAE,QAAQ;QAClB,aAAa,EAAE,IAAI;KACpB,CAAC;AACJ,CAAC;AAED,4CAA4C;AAC5C,MAAM,CAAC,KAAK,UAAU,MAAM,CAAC,OAAqB;IAChD,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,KAAK,CAAC,EAAE,GAAG,MAAM,CAAC,OAAO,CAAC,EAAE,WAAW,EAAE,CAAC,YAAY,CAAC,EAAE,CAAC,CAAC;IACzF,MAAM,GAAG,GAAG,MAAM,OAAO,CAAC,KAAK,CAAC,EAAE,GAAG,MAAM,CAAC,OAAO,CAAC,EAAE,WAAW,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC;IACpG,MAAM,IAAI,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;IAC9C,OAAO;QACL,IAAI;QACJ,KAAK,EAAE,MAAM,CAAC,UAAU,CAAC,IAAI,CAAC;QAC9B,QAAQ,EAAE,CAAC,GAAG,OAAO,CAAC,QAAQ,EAAE,GAAG,GAAG,CAAC,QAAQ,CAAC,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC;KAChF,CAAC;AACJ,CAAC;AAED,SAAS,IAAI,CAAC,MAA2B;IACvC,OAAO,MAAM,CAAC,WAAW,EAAE,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;AACvE,CAAC;AAED,oCAAoC;AACpC,MAAM,CAAC,KAAK,UAAU,QAAQ,CAAC,OAAqB;IAClD,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,OAAO,CAAC,CAAC;IACrC,MAAM,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC3D,MAAM,SAAS,CAAC,OAAO,CAAC,OAAO,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC;IAC9C,OAAO,MAAM,CAAC;AAChB,CAAC;AAQD;;;;;;;;;;;;;GAaG;AACH,MAAM,CAAC,KAAK,UAAU,KAAK,CAAC,OAAqB;IAC/C,MAAM,EAAE,QAAQ,EAAE,GAAG,MAAM,MAAM,CAAC,eAAe,CAAC,CAAC;IACnD,IAAI,MAAM,GAAG,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC;IACpC,IAAI,GAAG,GAAG,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC;IACjC,MAAM,OAAO,GAAG,KAAK,IAAmB,EAAE;QACxC,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,MAAM,QAAQ,CAAC,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,SAAS,EAAE,OAAO,CAAC,SAAS,IAAI,KAAK,EAAE,CAAC,CAAC;YAC5F,MAAM,GAAG,EAAE,IAAI,EAAE,GAAG,CAAC,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC;YACtD,GAAG,GAAG,EAAE,IAAI,EAAE,GAAG,CAAC,GAAG,EAAE,IAAI,EAAE,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;YAC7C,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,OAAO,CAAC,CAAC;YACrC,OAAO,CAAC,OAAO,EAAE,CAAC,EAAE,GAAG,IAAI,EAAE,QAAQ,EAAE,CAAC,GAAG,IAAI,CAAC,QAAQ,EAAE,GAAG,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;QAChF,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,CAAC,OAAO,EAAE,CAAC,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;QAC/E,CAAC;IACH,CAAC,CAAC;IACF,MAAM,OAAO,EAAE,CAAC;IAEhB,iDAAiD;IACjD,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,OAAO,CAAC;QACpC,GAAG,MAAM,CAAC,OAAO,CAAC;QAClB,WAAW,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC;QAC5B,KAAK,EAAE,KAAK;QACZ,OAAO,EAAE;YACP;gBACE,IAAI,EAAE,6BAA6B;gBACnC,KAAK,CAAC,KAAK;oBACT,IAAI,KAAK,GAAG,IAAI,CAAC;oBACjB,KAAK,CAAC,KAAK,CAAC,GAAG,EAAE;wBACf,IAAI,KAAK,EAAE,CAAC;4BACV,KAAK,GAAG,KAAK,CAAC;4BACd,OAAO;wBACT,CAAC;wBACD,KAAK,OAAO,EAAE,CAAC;oBACjB,CAAC,CAAC,CAAC;gBACL,CAAC;aACF;SACF;KACF,CAAC,CAAC;IACH,MAAM,OAAO,CAAC,KAAK,EAAE,CAAC;IAEtB,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,IAAI,IAAI,CAAC;IAClC,MAAM,MAAM,GAAG,YAAY,CAAC,CAAC,OAAO,EAAE,QAAQ,EAAE,EAAE;QAChD,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,IAAI,GAAG,CAAC;QAC/B,MAAM,IAAI,GAAG,CAAC,IAAY,EAAE,IAAY,EAAE,IAAY,EAAQ,EAAE;YAC9D,QAAQ,CAAC,SAAS,CAAC,cAAc,EAAE,IAAI,CAAC,CAAC;YACzC,QAAQ,CAAC,SAAS,CAAC,eAAe,EAAE,UAAU,CAAC,CAAC;YAChD,QAAQ,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,IAAI,GAAG,CAAC,CAAC;YACxC,IAAI,OAAO,CAAC,OAAO,CAAC,eAAe,CAAC,KAAK,IAAI,IAAI,GAAG,EAAE,CAAC;gBACrD,QAAQ,CAAC,UAAU,GAAG,GAAG,CAAC;gBAC1B,QAAQ,CAAC,GAAG,EAAE,CAAC;gBACf,OAAO;YACT,CAAC;YACD,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,MAAM,KAAK,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;QAC7D,CAAC,CAAC;QACF,IAAI,GAAG,KAAK,gBAAgB,IAAI,GAAG,KAAK,GAAG,EAAE,CAAC;YAC5C,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,IAAI,EAAE,GAAG,EAAE,GAAG,CAAC,IAAI,EAAE,CAAC,EAAE,kBAAkB,EAAE,GAAG,MAAM,CAAC,IAAI,IAAI,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC;QACjH,CAAC;aAAM,IAAI,GAAG,KAAK,YAAY,EAAE,CAAC;YAChC,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,gCAAgC,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC;QACnE,CAAC;aAAM,IAAI,GAAG,KAAK,SAAS,EAAE,CAAC;YAC7B,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,gCAAgC,EAAE,GAAG,CAAC,IAAI,CAAC,CAAC;QAC7D,CAAC;aAAM,CAAC;YACN,QAAQ,CAAC,UAAU,GAAG,GAAG,CAAC;YAC1B,QAAQ,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;QAC5B,CAAC;IACH,CAAC,CAAC,CAAC;IACH,MAAM,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC;IAEnE,OAAO,KAAK,IAAI,EAAE;QAChB,MAAM,OAAO,CAAC,OAAO,EAAE,CAAC;QACxB,MAAM,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;IACtE,CAAC,CAAC;AACJ,CAAC;AAED,SAAS,IAAI,CAAC,IAAY;IACxB,OAAO,UAAU,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;AACpE,CAAC"}
1
+ {"version":3,"file":"build.js","sourceRoot":"","sources":["../src/build.ts"],"names":[],"mappings":"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"}
package/dist/site.d.ts ADDED
@@ -0,0 +1,46 @@
1
+ /**
2
+ * The native app as a static site (`swiftbrowser-native site`): the same
3
+ * bundle `build` writes for the Xcode target, run in the browser by the
4
+ * SwiftBrowser Native module built for WebAssembly (docs/native.md, "The
5
+ * round trip"): the native views compiled against the SwiftUI shim, with the
6
+ * page's JavaScript as the engine. The web renderer draws what the shim
7
+ * emits, as it draws any Swift app. The site is what `swiftbrowser build`
8
+ * writes for a Swift app: the page at `/`, the module, the bundle as the
9
+ * app's one resource (`/bundle/app.js`, which the module evaluates at
10
+ * launch) and the manifest. Node only.
11
+ */
12
+ import { type BuildOptions } from './build.js';
13
+ /** The module as the package ships it (publish.yml builds it; `wasm/` is gitignored in the repository). */
14
+ export declare const PACKAGED_MODULE: string;
15
+ export interface SiteOptions extends Omit<BuildOptions, 'outfile'> {
16
+ /** The directory to write the site into (emptied first). */
17
+ outDir: string;
18
+ /**
19
+ * The site's app name (letters, digits, `_` and `-`): the module's file
20
+ * name and the document title. The entry's project folder's name by default
21
+ * (`js/src/index.tsx` → the folder holding `js`, as `native init` lays an
22
+ * app out), else `App`.
23
+ */
24
+ name?: string;
25
+ /** Shown as the document title and in the web manifest; `name` by default. */
26
+ displayName?: string;
27
+ /** The SwiftBrowser Native module built for WebAssembly; `PACKAGED_MODULE` by default. */
28
+ module?: string;
29
+ /** The path the site is served under when it is not a host's root (`/native/`); `/` by default. */
30
+ base?: string;
31
+ /** Vite's log level while building the site; `warn` by default. */
32
+ logLevel?: 'info' | 'warn' | 'error' | 'silent';
33
+ }
34
+ export interface SiteResult {
35
+ name: string;
36
+ outDir: string;
37
+ module: string;
38
+ moduleBytes: number;
39
+ bundleBytes: number;
40
+ warnings: string[];
41
+ }
42
+ /** The app name an entry suggests: the folder holding its `js/` project, else the entry's own folder. */
43
+ export declare function siteNameFor(entry: string): string;
44
+ /** Bundles the app and writes the site around the module. */
45
+ export declare function buildNativeSite(options: SiteOptions): Promise<SiteResult>;
46
+ //# sourceMappingURL=site.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"site.d.ts","sourceRoot":"","sources":["../src/site.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAOH,OAAO,EAAU,KAAK,YAAY,EAAE,MAAM,YAAY,CAAC;AAEvD,2GAA2G;AAC3G,eAAO,MAAM,eAAe,QAA6E,CAAC;AAE1G,MAAM,WAAW,WAAY,SAAQ,IAAI,CAAC,YAAY,EAAE,SAAS,CAAC;IAChE,4DAA4D;IAC5D,MAAM,EAAE,MAAM,CAAC;IACf;;;;;OAKG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,8EAA8E;IAC9E,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,0FAA0F;IAC1F,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,mGAAmG;IACnG,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,mEAAmE;IACnE,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,QAAQ,CAAC;CACjD;AAED,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAID,yGAAyG;AACzG,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAOjD;AAED,6DAA6D;AAC7D,wBAAsB,eAAe,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,UAAU,CAAC,CAqC/E"}
package/dist/site.js ADDED
@@ -0,0 +1,70 @@
1
+ /**
2
+ * The native app as a static site (`swiftbrowser-native site`): the same
3
+ * bundle `build` writes for the Xcode target, run in the browser by the
4
+ * SwiftBrowser Native module built for WebAssembly (docs/native.md, "The
5
+ * round trip"): the native views compiled against the SwiftUI shim, with the
6
+ * page's JavaScript as the engine. The web renderer draws what the shim
7
+ * emits, as it draws any Swift app. The site is what `swiftbrowser build`
8
+ * writes for a Swift app: the page at `/`, the module, the bundle as the
9
+ * app's one resource (`/bundle/app.js`, which the module evaluates at
10
+ * launch) and the manifest. Node only.
11
+ */
12
+ import { existsSync, mkdtempSync, rmSync, statSync, writeFileSync } from 'node:fs';
13
+ import { tmpdir } from 'node:os';
14
+ import path from 'node:path';
15
+ import { fileURLToPath } from 'node:url';
16
+ import { buildSite } from '@swiftbrowser/web';
17
+ import { bundle } from './build.js';
18
+ /** The module as the package ships it (publish.yml builds it; `wasm/` is gitignored in the repository). */
19
+ export const PACKAGED_MODULE = fileURLToPath(new URL('../wasm/SwiftBrowserNative.wasm', import.meta.url));
20
+ const APP_NAME = /^[A-Za-z0-9_-]+$/;
21
+ /** The app name an entry suggests: the folder holding its `js/` project, else the entry's own folder. */
22
+ export function siteNameFor(entry) {
23
+ const dir = path.dirname(path.resolve(entry));
24
+ const parts = dir.split(path.sep);
25
+ const js = parts.lastIndexOf('js');
26
+ const candidate = js > 0 ? parts[js - 1] : path.basename(dir) === 'src' ? parts[parts.length - 2] : path.basename(dir);
27
+ const cleaned = (candidate ?? '').replace(/[^A-Za-z0-9_-]/g, '');
28
+ return cleaned && APP_NAME.test(cleaned) ? cleaned : 'App';
29
+ }
30
+ /** Bundles the app and writes the site around the module. */
31
+ export async function buildNativeSite(options) {
32
+ const name = options.name ?? siteNameFor(options.entry);
33
+ if (!APP_NAME.test(name))
34
+ throw new Error(`site: the app name must be letters, digits, "_" and "-": ${name}`);
35
+ const module = path.resolve(options.module ?? PACKAGED_MODULE);
36
+ if (!existsSync(module)) {
37
+ throw new Error(module === PACKAGED_MODULE
38
+ ? `site: this @swiftbrowser/native has no module at ${module}; pass one with --module (swift build --swift-sdk <wasm sdk> -c release --product NativeRoundTrip in a SwiftBrowser checkout)`
39
+ : `site: no module at ${module}`);
40
+ }
41
+ if (options.base !== undefined && !/^\/([^/].*\/)?$/.test(options.base))
42
+ throw new Error(`site: --base must start and end with "/": ${options.base}`);
43
+ const { outDir, name: _name, displayName, module: _module, base, logLevel, ...bundleOptions } = options;
44
+ // The bundle is the app's one resource: published at `/bundle/<name>/app.js`
45
+ // and mounted at `/bundle/app.js`, where the module's launch reads it.
46
+ const resources = mkdtempSync(path.join(tmpdir(), 'swiftbrowser-native-site-'));
47
+ try {
48
+ const built = await bundle({ ...bundleOptions, outfile: path.join(resources, 'app.js') });
49
+ writeFileSync(path.join(resources, 'app.js'), built.code);
50
+ await buildSite({
51
+ apps: [{
52
+ name,
53
+ displayName: displayName ?? name,
54
+ description: 'A SwiftBrowser Native app in the browser',
55
+ wasm: module,
56
+ catalogs: [],
57
+ resources: [path.join(resources, 'app.js')],
58
+ }],
59
+ layout: 'app',
60
+ outDir: path.resolve(outDir),
61
+ ...(base !== undefined ? { base } : {}),
62
+ logLevel: logLevel ?? 'warn',
63
+ });
64
+ return { name, outDir: path.resolve(outDir), module, moduleBytes: statSync(module).size, bundleBytes: built.bytes, warnings: built.warnings };
65
+ }
66
+ finally {
67
+ rmSync(resources, { recursive: true, force: true });
68
+ }
69
+ }
70
+ //# sourceMappingURL=site.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"site.js","sourceRoot":"","sources":["../src/site.ts"],"names":[],"mappings":"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"}
@@ -1,35 +1,9 @@
1
1
  /**
2
- * The host's JavaScript as SwiftBrowser Native's engine (docs/native.md,
3
- * "The round trip"): for a module built with the round-trip target
4
- * (`Native/RoundTrip`, the native views compiled against the shim), this
5
- * answers its one import, `sb_native.request`, and exposes the native
6
- * functions Swift installs (`applyOps`, `log`, `now`, …) on `__sbNative`,
7
- * each a call of the module's `sb_native_invoke` export.
8
- *
9
- * Node (scripts/run-wasi.mjs) and a page use it the same way: merge
10
- * `imports` into the module's imports, instantiate, then `attach` the
11
- * instance. Strings cross linear memory as UTF-8; an answer to Swift is a
12
- * buffer from `sb_alloc` holding a little-endian 4-byte length and the
13
- * bytes, which Swift frees, and a result from Swift is the same layout,
14
- * freed with `sb_native_free` once read.
2
+ * `@swiftbrowser/native/wasm-host`: the host's JavaScript as SwiftBrowser
3
+ * Native's engine for a module built with the round-trip target (docs/native.md,
4
+ * "The round trip"). The implementation lives in the web renderer
5
+ * (Web/src/nativeHost.ts), which attaches it to any module that imports
6
+ * `sb_native`; this entry is for Node (scripts/run-wasi.mjs) and tests.
15
7
  */
16
- export interface WasmNativeHostOptions {
17
- /** Runs a script at the global scope (default: an indirect `eval`). */
18
- evaluate?: (script: string, name: string) => void;
19
- /** The global object the bundle's globals live on (default `globalThis`). */
20
- global?: Record<string, unknown>;
21
- }
22
- export interface WasmNativeHost {
23
- /** Merge into the module's import object before instantiating. */
24
- imports: {
25
- sb_native: {
26
- request: (kind: number, kindLength: number, a: number, aLength: number, b: number, bLength: number) => number;
27
- };
28
- };
29
- /** The instance, once instantiated: its memory and the exports the host calls. */
30
- attach(instance: WebAssembly.Instance): void;
31
- /** The `__sbNative` object as it stands (replaced by a reset). */
32
- readonly native: Record<string, (...args: unknown[]) => unknown>;
33
- }
34
- export declare function wasmNativeHost(options?: WasmNativeHostOptions): WasmNativeHost;
8
+ export { wasmNativeHost, type WasmNativeHost, type WasmNativeHostOptions } from '@swiftbrowser/web';
35
9
  //# sourceMappingURL=wasmHost.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"wasmHost.d.ts","sourceRoot":"","sources":["../src/wasmHost.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,MAAM,WAAW,qBAAqB;IACpC,uEAAuE;IACvE,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAClD,6EAA6E;IAC7E,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAClC;AAED,MAAM,WAAW,cAAc;IAC7B,kEAAkE;IAClE,OAAO,EAAE;QACP,SAAS,EAAE;YACT,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,KAAK,MAAM,CAAC;SAC/G,CAAC;KACH,CAAC;IACF,kFAAkF;IAClF,MAAM,CAAC,QAAQ,EAAE,WAAW,CAAC,QAAQ,GAAG,IAAI,CAAC;IAC7C,kEAAkE;IAClE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,OAAO,CAAC,CAAC;CAClE;AAYD,wBAAgB,cAAc,CAAC,OAAO,GAAE,qBAA0B,GAAG,cAAc,CAoGlF"}
1
+ {"version":3,"file":"wasmHost.d.ts","sourceRoot":"","sources":["../src/wasmHost.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EAAE,cAAc,EAAE,KAAK,cAAc,EAAE,KAAK,qBAAqB,EAAE,MAAM,mBAAmB,CAAC"}
package/dist/wasmHost.js CHANGED
@@ -1,120 +1,9 @@
1
1
  /**
2
- * The host's JavaScript as SwiftBrowser Native's engine (docs/native.md,
3
- * "The round trip"): for a module built with the round-trip target
4
- * (`Native/RoundTrip`, the native views compiled against the shim), this
5
- * answers its one import, `sb_native.request`, and exposes the native
6
- * functions Swift installs (`applyOps`, `log`, `now`, …) on `__sbNative`,
7
- * each a call of the module's `sb_native_invoke` export.
8
- *
9
- * Node (scripts/run-wasi.mjs) and a page use it the same way: merge
10
- * `imports` into the module's imports, instantiate, then `attach` the
11
- * instance. Strings cross linear memory as UTF-8; an answer to Swift is a
12
- * buffer from `sb_alloc` holding a little-endian 4-byte length and the
13
- * bytes, which Swift frees, and a result from Swift is the same layout,
14
- * freed with `sb_native_free` once read.
2
+ * `@swiftbrowser/native/wasm-host`: the host's JavaScript as SwiftBrowser
3
+ * Native's engine for a module built with the round-trip target (docs/native.md,
4
+ * "The round trip"). The implementation lives in the web renderer
5
+ * (Web/src/nativeHost.ts), which attaches it to any module that imports
6
+ * `sb_native`; this entry is for Node (scripts/run-wasi.mjs) and tests.
15
7
  */
16
- /** The globals a bundle defines, dropped by a reset (`JSHost.reset`). */
17
- const BUNDLE_GLOBALS = ['__sbDispatch', '__sbTimerFired', '__sbFetchResult', '__sbRefresh', '__sbVendor'];
18
- export function wasmNativeHost(options = {}) {
19
- const g = options.global ?? globalThis;
20
- const evaluate = options.evaluate ?? ((script) => (0, eval)(script));
21
- const encoder = new TextEncoder();
22
- const decoder = new TextDecoder();
23
- let exports = null;
24
- let native = {};
25
- const need = () => {
26
- if (!exports)
27
- throw new Error('wasmNativeHost: attach(instance) before the module runs');
28
- return exports;
29
- };
30
- const read = (pointer, length) => decoder.decode(new Uint8Array(need().memory.buffer, pointer, length));
31
- /** A buffer from `sb_alloc` with the text's length and bytes; Swift frees it. */
32
- const answer = (value) => {
33
- const bytes = encoder.encode(JSON.stringify(value));
34
- const pointer = need().sb_alloc(bytes.length + 4);
35
- // The allocation may have grown the memory: take the view after it.
36
- const view = new DataView(need().memory.buffer);
37
- view.setUint32(pointer, bytes.length, true);
38
- new Uint8Array(need().memory.buffer, pointer + 4, bytes.length).set(bytes);
39
- return pointer;
40
- };
41
- const describe = (error) => (error instanceof Error ? (error.stack ?? error.message) : String(error));
42
- /** A native function's call: into Swift and back with its JSON result. */
43
- const invoke = (name, args) => {
44
- const nameBytes = encoder.encode(name);
45
- const argsBytes = encoder.encode(JSON.stringify(args));
46
- const namePointer = need().sb_alloc(nameBytes.length || 1);
47
- new Uint8Array(need().memory.buffer, namePointer, nameBytes.length).set(nameBytes);
48
- const argsPointer = need().sb_alloc(argsBytes.length || 1);
49
- new Uint8Array(need().memory.buffer, argsPointer, argsBytes.length).set(argsBytes);
50
- const result = need().sb_native_invoke(namePointer, nameBytes.length, argsPointer, argsBytes.length);
51
- if (result === 0)
52
- return undefined;
53
- const length = new DataView(need().memory.buffer).getUint32(result, true);
54
- const text = read(result + 4, length);
55
- need().sb_native_free(result);
56
- const value = JSON.parse(text);
57
- if (value && typeof value === 'object' && 'error' in value && Object.keys(value).length === 1) {
58
- throw new Error(String(value.error));
59
- }
60
- return value;
61
- };
62
- const reset = () => {
63
- for (const name of BUNDLE_GLOBALS)
64
- delete g[name];
65
- native = {};
66
- g['__sbNative'] = native;
67
- };
68
- const request = (kind, kindLength, a, aLength, b, bLength) => {
69
- const what = read(kind, kindLength);
70
- const first = read(a, aLength);
71
- const second = read(b, bLength);
72
- switch (what) {
73
- case 'reset':
74
- reset();
75
- return 0;
76
- case 'install':
77
- native[first] = (...args) => invoke(first, args);
78
- return 0;
79
- case 'eval':
80
- try {
81
- evaluate(first, second);
82
- return 0;
83
- }
84
- catch (error) {
85
- return answer({ error: describe(error) });
86
- }
87
- case 'call': {
88
- const fn = g[first];
89
- if (typeof fn !== 'function')
90
- return answer({ error: `${first} is not defined` });
91
- try {
92
- const result = fn(...JSON.parse(second));
93
- return answer({ ok: result === undefined ? null : result });
94
- }
95
- catch (error) {
96
- return answer({ error: describe(error) });
97
- }
98
- }
99
- case 'has':
100
- return answer({ ok: first in g });
101
- default:
102
- return answer({ error: `unknown request ${what}` });
103
- }
104
- };
105
- reset();
106
- return {
107
- imports: { sb_native: { request } },
108
- attach(instance) {
109
- for (const name of ['memory', 'sb_alloc', 'sb_native_invoke', 'sb_native_free']) {
110
- if (!(name in instance.exports))
111
- throw new Error(`wasmNativeHost: the module does not export ${name}`);
112
- }
113
- exports = instance.exports;
114
- },
115
- get native() {
116
- return native;
117
- },
118
- };
119
- }
8
+ export { wasmNativeHost } from '@swiftbrowser/web';
120
9
  //# sourceMappingURL=wasmHost.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"wasmHost.js","sourceRoot":"","sources":["../src/wasmHost.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"wasmHost.js","sourceRoot":"","sources":["../src/wasmHost.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EAAE,cAAc,EAAmD,MAAM,mBAAmB,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@swiftbrowser/native",
3
- "version": "0.10.4",
3
+ "version": "0.11.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": {
@@ -23,6 +23,10 @@
23
23
  "types": "./dist/build.d.ts",
24
24
  "default": "./dist/build.js"
25
25
  },
26
+ "./site": {
27
+ "types": "./dist/site.d.ts",
28
+ "default": "./dist/site.js"
29
+ },
26
30
  "./wasm-host": {
27
31
  "types": "./dist/wasmHost.d.ts",
28
32
  "default": "./dist/wasmHost.js"
@@ -36,6 +40,7 @@
36
40
  "src",
37
41
  "bin",
38
42
  "swift",
43
+ "wasm",
39
44
  "README.md"
40
45
  ],
41
46
  "sideEffects": [
@@ -54,8 +59,8 @@
54
59
  },
55
60
  "dependencies": {
56
61
  "@babel/core": "^7.29.7",
57
- "@swiftbrowser/react": "0.10.4",
58
- "@swiftbrowser/web": "0.10.4",
62
+ "@swiftbrowser/react": "0.11.0",
63
+ "@swiftbrowser/web": "0.11.0",
59
64
  "esbuild": "^0.28.2",
60
65
  "react-refresh": "^0.17.0"
61
66
  },
package/src/build.ts CHANGED
@@ -11,7 +11,7 @@ import { createHash } from 'node:crypto';
11
11
  import { existsSync } from 'node:fs';
12
12
  import { createServer } from 'node:http';
13
13
  import { mkdir, writeFile } from 'node:fs/promises';
14
- import { dirname } from 'node:path';
14
+ import { dirname, resolve, sep } from 'node:path';
15
15
  import { fileURLToPath } from 'node:url';
16
16
 
17
17
  export interface BuildOptions {
@@ -35,16 +35,28 @@ const here = dirname(fileURLToPath(import.meta.url));
35
35
  /** The runtime's source; the package ships `src` so this resolves from `dist` too. */
36
36
  const runtimeEntry = fileURLToPath(new URL('../src/runtime.ts', import.meta.url));
37
37
  const reactSources = fileURLToPath(new URL('../../react/src/index.ts', import.meta.url));
38
+ /** The repository, when this runs from a checkout (installed, the package has no `packages/react` beside it). */
39
+ const repository = existsSync(reactSources) ? resolve(here, '../../..') : null;
38
40
  const stubs: Record<string, string> = {
39
41
  'react-dom/client': fileURLToPath(new URL('../src/stubs/react-dom-client.ts', import.meta.url)),
40
42
  'react-dom': fileURLToPath(new URL('../src/stubs/react-dom.ts', import.meta.url)),
41
43
  // This package from its sources, so a bundle never needs its `dist` built first.
42
44
  '@swiftbrowser/native/runtime': runtimeEntry,
43
45
  '@swiftbrowser/native': fileURLToPath(new URL('../src/index.ts', import.meta.url)),
44
- // In this repository, @swiftbrowser/react from its sources too (installed, it is a built package).
45
- ...(existsSync(reactSources) ? { '@swiftbrowser/react': reactSources } : {}),
46
46
  };
47
47
 
48
+ /**
49
+ * The aliases for an entry: in this repository, an app of the repository's
50
+ * (the examples) gets @swiftbrowser/react from its sources too (installed,
51
+ * it is a built package). An app outside it (a project built with the
52
+ * checkout's bundler) keeps its own installed copy: the sources would bring
53
+ * the repository's React along, a second copy beside the app's.
54
+ */
55
+ function aliases(entry: string): Record<string, string> {
56
+ const inRepository = repository !== null && resolve(entry).startsWith(repository + sep);
57
+ return { ...stubs, ...(inRepository ? { '@swiftbrowser/react': reactSources } : {}) };
58
+ }
59
+
48
60
  function common(options: BuildOptions): esbuild.BuildOptions {
49
61
  const mode = options.mode ?? (options.minify ? 'production' : 'development');
50
62
  return {
@@ -66,7 +78,7 @@ function common(options: BuildOptions): esbuild.BuildOptions {
66
78
  /** Builds the two halves and joins them. */
67
79
  export async function bundle(options: BuildOptions): Promise<BuildResult> {
68
80
  const runtime = await esbuild.build({ ...common(options), entryPoints: [runtimeEntry] });
69
- const app = await esbuild.build({ ...common(options), entryPoints: [options.entry], alias: stubs });
81
+ const app = await esbuild.build({ ...common(options), entryPoints: [options.entry], alias: aliases(options.entry) });
70
82
  const code = `${text(runtime)}\n${text(app)}`;
71
83
  return {
72
84
  code,
package/src/site.ts ADDED
@@ -0,0 +1,102 @@
1
+ /**
2
+ * The native app as a static site (`swiftbrowser-native site`): the same
3
+ * bundle `build` writes for the Xcode target, run in the browser by the
4
+ * SwiftBrowser Native module built for WebAssembly (docs/native.md, "The
5
+ * round trip"): the native views compiled against the SwiftUI shim, with the
6
+ * page's JavaScript as the engine. The web renderer draws what the shim
7
+ * emits, as it draws any Swift app. The site is what `swiftbrowser build`
8
+ * writes for a Swift app: the page at `/`, the module, the bundle as the
9
+ * app's one resource (`/bundle/app.js`, which the module evaluates at
10
+ * launch) and the manifest. Node only.
11
+ */
12
+
13
+ import { existsSync, mkdtempSync, rmSync, statSync, writeFileSync } from 'node:fs';
14
+ import { tmpdir } from 'node:os';
15
+ import path from 'node:path';
16
+ import { fileURLToPath } from 'node:url';
17
+ import { buildSite } from '@swiftbrowser/web';
18
+ import { bundle, type BuildOptions } from './build.js';
19
+
20
+ /** The module as the package ships it (publish.yml builds it; `wasm/` is gitignored in the repository). */
21
+ export const PACKAGED_MODULE = fileURLToPath(new URL('../wasm/SwiftBrowserNative.wasm', import.meta.url));
22
+
23
+ export interface SiteOptions extends Omit<BuildOptions, 'outfile'> {
24
+ /** The directory to write the site into (emptied first). */
25
+ outDir: string;
26
+ /**
27
+ * The site's app name (letters, digits, `_` and `-`): the module's file
28
+ * name and the document title. The entry's project folder's name by default
29
+ * (`js/src/index.tsx` → the folder holding `js`, as `native init` lays an
30
+ * app out), else `App`.
31
+ */
32
+ name?: string;
33
+ /** Shown as the document title and in the web manifest; `name` by default. */
34
+ displayName?: string;
35
+ /** The SwiftBrowser Native module built for WebAssembly; `PACKAGED_MODULE` by default. */
36
+ module?: string;
37
+ /** The path the site is served under when it is not a host's root (`/native/`); `/` by default. */
38
+ base?: string;
39
+ /** Vite's log level while building the site; `warn` by default. */
40
+ logLevel?: 'info' | 'warn' | 'error' | 'silent';
41
+ }
42
+
43
+ export interface SiteResult {
44
+ name: string;
45
+ outDir: string;
46
+ module: string;
47
+ moduleBytes: number;
48
+ bundleBytes: number;
49
+ warnings: string[];
50
+ }
51
+
52
+ const APP_NAME = /^[A-Za-z0-9_-]+$/;
53
+
54
+ /** The app name an entry suggests: the folder holding its `js/` project, else the entry's own folder. */
55
+ export function siteNameFor(entry: string): string {
56
+ const dir = path.dirname(path.resolve(entry));
57
+ const parts = dir.split(path.sep);
58
+ const js = parts.lastIndexOf('js');
59
+ const candidate = js > 0 ? parts[js - 1] : path.basename(dir) === 'src' ? parts[parts.length - 2] : path.basename(dir);
60
+ const cleaned = (candidate ?? '').replace(/[^A-Za-z0-9_-]/g, '');
61
+ return cleaned && APP_NAME.test(cleaned) ? cleaned : 'App';
62
+ }
63
+
64
+ /** Bundles the app and writes the site around the module. */
65
+ export async function buildNativeSite(options: SiteOptions): Promise<SiteResult> {
66
+ const name = options.name ?? siteNameFor(options.entry);
67
+ if (!APP_NAME.test(name)) throw new Error(`site: the app name must be letters, digits, "_" and "-": ${name}`);
68
+ const module = path.resolve(options.module ?? PACKAGED_MODULE);
69
+ if (!existsSync(module)) {
70
+ throw new Error(
71
+ module === PACKAGED_MODULE
72
+ ? `site: this @swiftbrowser/native has no module at ${module}; pass one with --module (swift build --swift-sdk <wasm sdk> -c release --product NativeRoundTrip in a SwiftBrowser checkout)`
73
+ : `site: no module at ${module}`,
74
+ );
75
+ }
76
+ 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;
78
+ // The bundle is the app's one resource: published at `/bundle/<name>/app.js`
79
+ // and mounted at `/bundle/app.js`, where the module's launch reads it.
80
+ const resources = mkdtempSync(path.join(tmpdir(), 'swiftbrowser-native-site-'));
81
+ try {
82
+ const built = await bundle({ ...bundleOptions, outfile: path.join(resources, 'app.js') });
83
+ writeFileSync(path.join(resources, 'app.js'), built.code);
84
+ await buildSite({
85
+ apps: [{
86
+ name,
87
+ displayName: displayName ?? name,
88
+ description: 'A SwiftBrowser Native app in the browser',
89
+ wasm: module,
90
+ catalogs: [],
91
+ resources: [path.join(resources, 'app.js')],
92
+ }],
93
+ layout: 'app',
94
+ outDir: path.resolve(outDir),
95
+ ...(base !== undefined ? { base } : {}),
96
+ logLevel: logLevel ?? 'warn',
97
+ });
98
+ return { name, outDir: path.resolve(outDir), module, moduleBytes: statSync(module).size, bundleBytes: built.bytes, warnings: built.warnings };
99
+ } finally {
100
+ rmSync(resources, { recursive: true, force: true });
101
+ }
102
+ }
package/src/wasmHost.ts CHANGED
@@ -1,147 +1,8 @@
1
1
  /**
2
- * The host's JavaScript as SwiftBrowser Native's engine (docs/native.md,
3
- * "The round trip"): for a module built with the round-trip target
4
- * (`Native/RoundTrip`, the native views compiled against the shim), this
5
- * answers its one import, `sb_native.request`, and exposes the native
6
- * functions Swift installs (`applyOps`, `log`, `now`, …) on `__sbNative`,
7
- * each a call of the module's `sb_native_invoke` export.
8
- *
9
- * Node (scripts/run-wasi.mjs) and a page use it the same way: merge
10
- * `imports` into the module's imports, instantiate, then `attach` the
11
- * instance. Strings cross linear memory as UTF-8; an answer to Swift is a
12
- * buffer from `sb_alloc` holding a little-endian 4-byte length and the
13
- * bytes, which Swift frees, and a result from Swift is the same layout,
14
- * freed with `sb_native_free` once read.
2
+ * `@swiftbrowser/native/wasm-host`: the host's JavaScript as SwiftBrowser
3
+ * Native's engine for a module built with the round-trip target (docs/native.md,
4
+ * "The round trip"). The implementation lives in the web renderer
5
+ * (Web/src/nativeHost.ts), which attaches it to any module that imports
6
+ * `sb_native`; this entry is for Node (scripts/run-wasi.mjs) and tests.
15
7
  */
16
-
17
- export interface WasmNativeHostOptions {
18
- /** Runs a script at the global scope (default: an indirect `eval`). */
19
- evaluate?: (script: string, name: string) => void;
20
- /** The global object the bundle's globals live on (default `globalThis`). */
21
- global?: Record<string, unknown>;
22
- }
23
-
24
- export interface WasmNativeHost {
25
- /** Merge into the module's import object before instantiating. */
26
- imports: {
27
- sb_native: {
28
- request: (kind: number, kindLength: number, a: number, aLength: number, b: number, bLength: number) => number;
29
- };
30
- };
31
- /** The instance, once instantiated: its memory and the exports the host calls. */
32
- attach(instance: WebAssembly.Instance): void;
33
- /** The `__sbNative` object as it stands (replaced by a reset). */
34
- readonly native: Record<string, (...args: unknown[]) => unknown>;
35
- }
36
-
37
- interface Exports {
38
- memory: WebAssembly.Memory;
39
- sb_alloc: (length: number) => number;
40
- sb_native_invoke: (name: number, nameLength: number, args: number, argsLength: number) => number;
41
- sb_native_free: (pointer: number) => void;
42
- }
43
-
44
- /** The globals a bundle defines, dropped by a reset (`JSHost.reset`). */
45
- const BUNDLE_GLOBALS = ['__sbDispatch', '__sbTimerFired', '__sbFetchResult', '__sbRefresh', '__sbVendor'];
46
-
47
- export function wasmNativeHost(options: WasmNativeHostOptions = {}): WasmNativeHost {
48
- const g = options.global ?? (globalThis as unknown as Record<string, unknown>);
49
- const evaluate = options.evaluate ?? ((script: string) => (0, eval)(script));
50
- const encoder = new TextEncoder();
51
- const decoder = new TextDecoder();
52
- let exports: Exports | null = null;
53
- let native: Record<string, (...args: unknown[]) => unknown> = {};
54
-
55
- const need = (): Exports => {
56
- if (!exports) throw new Error('wasmNativeHost: attach(instance) before the module runs');
57
- return exports;
58
- };
59
- const read = (pointer: number, length: number): string =>
60
- decoder.decode(new Uint8Array(need().memory.buffer, pointer, length));
61
- /** A buffer from `sb_alloc` with the text's length and bytes; Swift frees it. */
62
- const answer = (value: unknown): number => {
63
- const bytes = encoder.encode(JSON.stringify(value));
64
- const pointer = need().sb_alloc(bytes.length + 4);
65
- // The allocation may have grown the memory: take the view after it.
66
- const view = new DataView(need().memory.buffer);
67
- view.setUint32(pointer, bytes.length, true);
68
- new Uint8Array(need().memory.buffer, pointer + 4, bytes.length).set(bytes);
69
- return pointer;
70
- };
71
- const describe = (error: unknown): string => (error instanceof Error ? (error.stack ?? error.message) : String(error));
72
-
73
- /** A native function's call: into Swift and back with its JSON result. */
74
- const invoke = (name: string, args: unknown[]): unknown => {
75
- const nameBytes = encoder.encode(name);
76
- const argsBytes = encoder.encode(JSON.stringify(args));
77
- const namePointer = need().sb_alloc(nameBytes.length || 1);
78
- new Uint8Array(need().memory.buffer, namePointer, nameBytes.length).set(nameBytes);
79
- const argsPointer = need().sb_alloc(argsBytes.length || 1);
80
- new Uint8Array(need().memory.buffer, argsPointer, argsBytes.length).set(argsBytes);
81
- const result = need().sb_native_invoke(namePointer, nameBytes.length, argsPointer, argsBytes.length);
82
- if (result === 0) return undefined;
83
- const length = new DataView(need().memory.buffer).getUint32(result, true);
84
- const text = read(result + 4, length);
85
- need().sb_native_free(result);
86
- const value: unknown = JSON.parse(text);
87
- if (value && typeof value === 'object' && 'error' in value && Object.keys(value).length === 1) {
88
- throw new Error(String((value as { error: unknown }).error));
89
- }
90
- return value;
91
- };
92
-
93
- const reset = (): void => {
94
- for (const name of BUNDLE_GLOBALS) delete g[name];
95
- native = {};
96
- g['__sbNative'] = native;
97
- };
98
-
99
- const request = (kind: number, kindLength: number, a: number, aLength: number, b: number, bLength: number): number => {
100
- const what = read(kind, kindLength);
101
- const first = read(a, aLength);
102
- const second = read(b, bLength);
103
- switch (what) {
104
- case 'reset':
105
- reset();
106
- return 0;
107
- case 'install':
108
- native[first] = (...args: unknown[]) => invoke(first, args);
109
- return 0;
110
- case 'eval':
111
- try {
112
- evaluate(first, second);
113
- return 0;
114
- } catch (error) {
115
- return answer({ error: describe(error) });
116
- }
117
- case 'call': {
118
- const fn = g[first];
119
- if (typeof fn !== 'function') return answer({ error: `${first} is not defined` });
120
- try {
121
- const result: unknown = (fn as (...args: unknown[]) => unknown)(...(JSON.parse(second) as unknown[]));
122
- return answer({ ok: result === undefined ? null : result });
123
- } catch (error) {
124
- return answer({ error: describe(error) });
125
- }
126
- }
127
- case 'has':
128
- return answer({ ok: first in g });
129
- default:
130
- return answer({ error: `unknown request ${what}` });
131
- }
132
- };
133
-
134
- reset();
135
- return {
136
- imports: { sb_native: { request } },
137
- attach(instance) {
138
- for (const name of ['memory', 'sb_alloc', 'sb_native_invoke', 'sb_native_free']) {
139
- if (!(name in instance.exports)) throw new Error(`wasmNativeHost: the module does not export ${name}`);
140
- }
141
- exports = instance.exports as unknown as Exports;
142
- },
143
- get native() {
144
- return native;
145
- },
146
- };
147
- }
8
+ export { wasmNativeHost, type WasmNativeHost, type WasmNativeHostOptions } from '@swiftbrowser/web';
@@ -57,6 +57,7 @@ struct ElementView: View {
57
57
  case "swipeactions": return AnyView(SwipeActionsElement(node: node))
58
58
  // Navigation and presentation
59
59
  case "navstack": return AnyView(NavigationStackElement(node: node))
60
+ case "navsplit": return AnyView(NavigationSplitElement(node: node))
60
61
  case "navscreen": return AnyView(NavigationScreenElement(node: node))
61
62
  case "navlink": return AnyView(NavigationLinkElement(node: node))
62
63
  case "tabview": return AnyView(TabViewElement(node: node))
@@ -15,10 +15,19 @@ struct NavigationStackElement: View {
15
15
  @Environment(\.nativeApp) private var app
16
16
 
17
17
  var body: some View {
18
- let screens = node.children.filter { $0.kind == "navscreen" }
18
+ Self.stack(for: node, app: app)
19
+ }
20
+
21
+ /// The stack itself, with its root screen and the pushed ones as its
22
+ /// path. A split view's detail column is one of these directly (not a
23
+ /// view around one): the shim wraps a detail that is not a
24
+ /// `NavigationStack` in a stack of its own, and a stack nested in a
25
+ /// stack is flattened, its path with it.
26
+ static func stack(for node: ElementNode?, app: NativeApp?) -> NavigationStack<[Int], AnyView> {
27
+ let screens = node?.children.filter { $0.kind == "navscreen" } ?? []
19
28
  let pushed = screens.dropFirst().map(\.id)
20
29
  let path = Binding<[Int]>(
21
- get: { node.children.filter { $0.kind == "navscreen" }.dropFirst().map(\.id) },
30
+ get: { node?.children.filter { $0.kind == "navscreen" }.dropFirst().map(\.id) ?? [] },
22
31
  set: { next in
23
32
  // Only pops come from here (pushes come from the app).
24
33
  for id in pushed where !next.contains(id) {
@@ -26,21 +35,69 @@ struct NavigationStackElement: View {
26
35
  }
27
36
  }
28
37
  )
29
- NavigationStack(path: path) {
30
- Group {
31
- if let root = screens.first {
32
- NavigationScreenElement(node: root)
38
+ return NavigationStack(path: path) {
39
+ AnyView(
40
+ Group {
41
+ if let root = screens.first {
42
+ NavigationScreenElement(node: root)
43
+ }
33
44
  }
34
- }
35
- .navigationDestination(for: Int.self) { id in
36
- if let screen = node.children.first(where: { $0.id == id }) {
37
- NavigationScreenElement(node: screen)
38
- } else {
39
- // Popped by the app already; SwiftUI is on its way out.
40
- Color.clear
45
+ .navigationDestination(for: Int.self) { id in
46
+ if let screen = node?.children.first(where: { $0.id == id }) {
47
+ NavigationScreenElement(node: screen)
48
+ } else {
49
+ // Popped by the app already; SwiftUI is on its way out.
50
+ Color.clear
51
+ }
52
+ }
53
+ )
54
+ }
55
+ }
56
+ }
57
+
58
+ /// `navsplit`: the sidebar's and the detail's stacks (its two `navstack`
59
+ /// children, in that order) as the columns of a split view. The app emits
60
+ /// it in a regular width only; in a compact width it emits one stack rooted
61
+ /// at the sidebar, as iOS shows. `sidebarVisible` is the column visibility
62
+ /// (`detailOnly` hides the sidebar); the sidebar toggle sends `tap` with the
63
+ /// split's id, and the app flips the visibility, as the protocol says.
64
+ struct NavigationSplitElement: View {
65
+ let node: ElementNode
66
+ @Environment(\.nativeApp) private var app
67
+
68
+ var body: some View {
69
+ let columns = node.children.filter { $0.kind == "navstack" }
70
+ let sidebar = columns.first
71
+ let detail = columns.count > 1 ? columns[1] : nil
72
+ let visibility = Binding<NavigationSplitViewVisibility>(
73
+ get: { NavSplitProps(props: node.props).sidebarVisible ? .all : .detailOnly },
74
+ set: { next in
75
+ if (next == .detailOnly) == NavSplitProps(props: node.props).sidebarVisible {
76
+ app?.dispatch(.tap(node.id))
41
77
  }
42
78
  }
79
+ )
80
+ NavigationSplitView(columnVisibility: visibility) {
81
+ sidebarColumn(sidebar)
82
+ } detail: {
83
+ NavigationStackElement.stack(for: detail, app: app)
84
+ }
85
+ }
86
+
87
+ /// The sidebar column: natively its stack (the column pushes within
88
+ /// itself); against the shim its root screen alone, since the shim gives
89
+ /// the sidebar a stack of its own and would flatten this one into it.
90
+ @ViewBuilder
91
+ private func sidebarColumn(_ sidebar: ElementNode?) -> some View {
92
+ #if canImport(FoundationBridge)
93
+ if let root = sidebar?.children.first(where: { $0.kind == "navscreen" }) {
94
+ NavigationScreenElement(node: root)
43
95
  }
96
+ #else
97
+ if let sidebar {
98
+ NavigationStackElement(node: sidebar)
99
+ }
100
+ #endif
44
101
  }
45
102
  }
46
103
 
@@ -183,6 +183,12 @@ public struct NavScreenProps: Equatable, Sendable {
183
183
  }
184
184
  }
185
185
 
186
+ public struct NavSplitProps: Equatable, Sendable {
187
+ /// Whether the sidebar column shows (`false` hides it).
188
+ public var sidebarVisible: Bool
189
+ public init(props: JSONObject) { sidebarVisible = props.bool("sidebarVisible") ?? true }
190
+ }
191
+
186
192
  public struct NavLinkProps: Equatable, Sendable {
187
193
  public var selected: Bool
188
194
  public var plain: Bool
@@ -0,0 +1,4 @@
1
+ [diffend] Oversized file quarantined before diffing.
2
+ name: package/wasm/SwiftBrowserNative.wasm
3
+ size: 49193851 bytes
4
+ sha256: 314a751e10bb902322011666194057cb533dfcdc7f31df7d4c6a7e55f1b8181d