@swmansion/popcorn 0.4.0-next.3 → 0.4.0-next.5

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
@@ -4,7 +4,7 @@ Run Elixir in a browser with OTP/BEAM compiled to WebAssembly.
4
4
  This prerelease replaces the AtomVM runtime used by Popcorn 0.3.x.
5
5
 
6
6
  Start with the
7
- [versioned Popcorn guide](https://popcorn.hexdocs.pm/0.4.0-next.3/introduction.html).
7
+ [versioned Popcorn guide](https://popcorn.hexdocs.pm/0.4.0-next.5/introduction.html).
8
8
  It includes a complete Mix and Vite tutorial.
9
9
 
10
10
  ## Install
@@ -13,12 +13,12 @@ It includes a complete Mix and Vite tutorial.
13
13
  npm install @swmansion/popcorn@next
14
14
  ```
15
15
 
16
- Add `{:popcorn, "0.4.0-next.3"}` to your Elixir application's dependencies.
16
+ Add `{:popcorn, "0.4.0-next.5"}` to your Elixir application's dependencies.
17
17
  Run `mix deps.get` before building the JavaScript application. The bundler
18
18
  plugins invoke Mix locally to compile and package application and
19
19
  standard-library code.
20
20
 
21
- Use the toolchain pinned in [mise.toml](https://github.com/software-mansion/popcorn/blob/v0.4.0-next.3/mise.toml).
21
+ Use the toolchain pinned in [mise.toml](https://github.com/software-mansion/popcorn/blob/v0.4.0-next.5/mise.toml).
22
22
  The packager checks host OTP compatibility against the selected runtime's manifest.
23
23
 
24
24
  ## Configure Vite
@@ -71,6 +71,15 @@ vm.deinit();
71
71
  `popcorn.genserver.call()` to call a supervised GenServer from JavaScript.
72
72
  Use `popcorn.onEvent()` to receive events from BEAM processes.
73
73
 
74
+ Each VM starts with 64 MiB of Wasm linear memory and can grow to 256 MiB on
75
+ phones/tablets or 2 GiB on desktop. Device detection uses the browser's user
76
+ agent and touch support. Configure the limits with
77
+ `Popcorn.init({ beam: { maxMemory: { mobile: "256M", desktop: "2G" } } })`.
78
+ Omit `maxMemory` to keep these defaults, or override either key separately.
79
+ Only `M` and `G` suffixes are supported; they mean MiB and GiB.
80
+ The limit must be a multiple of 64 KiB between 64 MiB and 2 GiB; JavaScript and
81
+ filesystem memory are additional.
82
+
74
83
  ## Serve in production
75
84
 
76
85
  Serve over HTTPS or localhost. Set these headers on the application and runtime responses:
@@ -85,4 +94,4 @@ Serve `.wasm` as `application/wasm`. Serve compressed `.tar.gz` files with `Cont
85
94
  The package also emits uncompressed tar files and Brotli variants. Brotli uses standard effort by default. Set `brotliEffort: "max"` for maximum compression in release builds.
86
95
 
87
96
  The JavaScript bridge currently requires a Content Security Policy that permits `unsafe-eval`.
88
- See the [versioned Elixir API](https://popcorn.hexdocs.pm/0.4.0-next.3/) for interoperability details.
97
+ See the [versioned Elixir API](https://popcorn.hexdocs.pm/0.4.0-next.5/) for interoperability details.
package/dist/events.d.ts CHANGED
@@ -3,7 +3,7 @@ import { type Mapper } from "./etf";
3
3
  import type { AnyValue, BeamBootOptions, BeamEvent, BeamSendPayload, BeamTarget } from "./types";
4
4
  type BootEvent = {
5
5
  type: "popcorn:boot";
6
- payload: Pick<BeamBootOptions, "emulatorArgs" | "extraArgs" | "env" | "ttySize" | "noshell"> & {
6
+ payload: Pick<BeamBootOptions, "emulatorArgs" | "extraArgs" | "env" | "maxMemoryBytes" | "ttySize" | "noshell"> & {
7
7
  otpAssetsRoot?: string;
8
8
  };
9
9
  };
package/dist/index.mjs CHANGED
@@ -547,6 +547,7 @@ class Popcorn {
547
547
  vmWorker;
548
548
  state = { status: "created" };
549
549
  opts;
550
+ maxMemoryBytes;
550
551
  ttySize;
551
552
  output;
552
553
  requestSeq = 0;
@@ -624,16 +625,19 @@ class Popcorn {
624
625
  check(isValidTtySize(ttySize));
625
626
  check(opts.beam?.otpAssetsRoot === undefined ||
626
627
  opts.beam.otpAssetsRoot.endsWith("/"), "otpAssetsRoot must end with a slash");
628
+ const { maxMemory, ...beam } = opts.beam ?? {};
629
+ this.maxMemoryBytes = resolveMaxMemory(maxMemory);
627
630
  this.opts = {
628
631
  ...opts,
629
632
  beam: {
630
- ...opts.beam,
631
- emulatorArgs: opts.beam?.emulatorArgs ??
632
- schedulers({ base: 1, dirtyCpu: 1, dirtyIo: 1 }),
633
+ ...beam,
634
+ emulatorArgs: beam.emulatorArgs ?? schedulers({ base: 1, dirtyCpu: 1, dirtyIo: 1 }),
633
635
  },
634
636
  };
635
637
  this.ttySize = { ...ttySize };
636
638
  this.output = resolveOutputHandlers(opts);
639
+ if (opts.onEvent)
640
+ this.onEvent(opts.onEvent);
637
641
  this.spawnWorker();
638
642
  }
639
643
  spawnWorker() {
@@ -648,7 +652,7 @@ class Popcorn {
648
652
  /**
649
653
  * Creates an instance and waits for {@link boot}.
650
654
  *
651
- * For startup messages, use the constructor and register {@link onEvent} before boot.
655
+ * Use `opts.onEvent` to receive messages sent during startup.
652
656
  *
653
657
  * @returns Ok tuple or `runtime:eval-unavailable` if the page blocks JavaScript evaluation.
654
658
  */
@@ -749,6 +753,7 @@ class Popcorn {
749
753
  type: "popcorn:boot",
750
754
  payload: {
751
755
  ...this.opts.beam,
756
+ maxMemoryBytes: this.maxMemoryBytes,
752
757
  ttySize: this.ttySize,
753
758
  noshell: this.opts.tty === undefined,
754
759
  },
@@ -1191,6 +1196,23 @@ class Popcorn {
1191
1196
  this.deinit(exitReason(payload));
1192
1197
  }
1193
1198
  }
1199
+ function resolveMaxMemory(limits) {
1200
+ const mobileLimit = parseMemorySize(limits?.mobile ?? "256M");
1201
+ const desktopLimit = parseMemorySize(limits?.desktop ?? "2G");
1202
+ const mobile = /Android|Mobile|iPad|iPhone|iPod/.test(navigator.userAgent);
1203
+ const ipad = /Macintosh/.test(navigator.userAgent) && navigator.maxTouchPoints > 1;
1204
+ return mobile || ipad ? mobileLimit : desktopLimit;
1205
+ }
1206
+ function parseMemorySize(size) {
1207
+ const match = /^(\d+(?:\.\d+)?)([MG])$/.exec(size);
1208
+ check(match !== null, 'maxMemory must use M or G, such as "256M" or "2G"');
1209
+ const bytes = Number(match[1]) * (match[2] === "M" ? 1024 ** 2 : 1024 ** 3);
1210
+ check(Number.isSafeInteger(bytes) &&
1211
+ bytes >= 64 * 1024 * 1024 &&
1212
+ bytes <= 2 * 1024 * 1024 * 1024 &&
1213
+ bytes % 65536 === 0, "maxMemory must be a multiple of 64 KiB between 64 MiB and 2 GiB");
1214
+ return bytes;
1215
+ }
1194
1216
  /**
1195
1217
  * Builds `beam.emulatorArgs` for scheduler counts.
1196
1218
  *
@@ -43,6 +43,7 @@ function popcorn(options) {
43
43
  let dirty = false;
44
44
  let outDir;
45
45
  let assetsDir = "assets";
46
+ let workerDerivedOtpRoot = `/@fs${distDir}/otp/`;
46
47
  // Dev/preview: pack once at server startup, then re-pack lazily after the
47
48
  // app's compiled beams change. A single in-flight promise dedupes concurrent
48
49
  // requests; a change during a re-pack re-sets `dirty` so it isn't lost.
@@ -78,7 +79,7 @@ function popcorn(options) {
78
79
  return;
79
80
  }
80
81
  const requestPath = decodeURIComponent(new URL(url, "http://localhost").pathname);
81
- const requestRoot = [`/${assetsDir}/otp/`, `/@fs${distDir}/otp/`].find((root) => requestPath.startsWith(root));
82
+ const requestRoot = [`/${assetsDir}/otp/`, workerDerivedOtpRoot].find((root) => requestPath.startsWith(root));
82
83
  if (requestRoot === undefined) {
83
84
  next();
84
85
  return;
@@ -146,6 +147,10 @@ function popcorn(options) {
146
147
  config.server.fs.allow.push(distDir);
147
148
  outDir = resolve(config.root, config.build.outDir);
148
149
  assetsDir = config.build.assetsDir;
150
+ const servedDistDir = isUnder(config.root, distDir)
151
+ ? `/${relative(config.root, distDir)}`
152
+ : `/@fs${distDir}`;
153
+ workerDerivedOtpRoot = `${servedDistDir}/otp/`;
149
154
  },
150
155
  async configureServer(server) {
151
156
  const rootDir = resolve(options.rootDir);
package/dist/popcorn.d.ts CHANGED
@@ -1,12 +1,22 @@
1
1
  import { type Result } from "./errors";
2
2
  import { type PopcornEvent } from "./events";
3
3
  import type { AnyValue, BeamBootOptions, OtpErrorPayload, Pid, TtySize } from "./types";
4
+ type MemoryLimits = {
5
+ mobile?: `${number}${"M" | "G"}`;
6
+ desktop?: `${number}${"M" | "G"}`;
7
+ };
4
8
  /** Output type for a terminal. */
5
9
  type TtyOutput = "text" | "bytes";
6
10
  type OutputChunk<Output extends TtyOutput> = Output extends "bytes" ? Uint8Array : string;
7
11
  /** Browser VM configuration. */
8
12
  export type PopcornOpts<Output extends TtyOutput = "text"> = {
9
13
  beam?: Pick<BeamBootOptions, "emulatorArgs" | "extraArgs" | "env"> & {
14
+ /**
15
+ * Maximum Wasm linear memory by platform. Omitted keys default to
16
+ * mobile: "256M", desktop: "2G". M and G mean MiB and GiB.
17
+ * Limits must be in 64 KiB increments between 64 MiB and 2 GiB.
18
+ */
19
+ maxMemory?: MemoryLimits;
10
20
  /**
11
21
  * Asset directory URL.
12
22
  *
@@ -60,6 +70,8 @@ export type PopcornOpts<Output extends TtyOutput = "text"> = {
60
70
  * Defaults to console output.
61
71
  */
62
72
  onError?: (event: OtpErrorPayload) => void;
73
+ /** Receives BEAM message payloads, including messages sent during startup. */
74
+ onEvent?: (event: PopcornEvent) => void;
63
75
  /**
64
76
  * Module worker URL.
65
77
  *
@@ -87,9 +99,7 @@ export type GenServer = {
87
99
  /**
88
100
  * Calls a GenServer by registered name or {@link Pid} and waits for its reply.
89
101
  *
90
- * Requires a running `Popcorn.Proxy`, registered as `popcorn_proxy` by default.
91
- *
92
- * @param opts - `timeoutMs` defaults to 5 000 ms. `proxy` allows to select another registered proxy.
102
+ * @param opts - `timeoutMs` defaults to 5 000 ms. `proxy` allows to select another registered proxy (see `Popcorn.Proxy`).
93
103
  * @returns A {@link Result} with the reply or a bridge, VM, timeout, or GenServer error.
94
104
  *
95
105
  * GenServer failures use `genserver:noproc`, `genserver:exit`, or `genserver:unserializable`.
@@ -116,6 +126,7 @@ export declare class Popcorn<Output extends TtyOutput = "text"> {
116
126
  private vmWorker;
117
127
  private state;
118
128
  private readonly opts;
129
+ private readonly maxMemoryBytes;
119
130
  private readonly ttySize;
120
131
  private output;
121
132
  private requestSeq;
@@ -143,7 +154,7 @@ export declare class Popcorn<Output extends TtyOutput = "text"> {
143
154
  /**
144
155
  * Creates an instance and waits for {@link boot}.
145
156
  *
146
- * For startup messages, use the constructor and register {@link onEvent} before boot.
157
+ * Use `opts.onEvent` to receive messages sent during startup.
147
158
  *
148
159
  * @returns Ok tuple or `runtime:eval-unavailable` if the page blocks JavaScript evaluation.
149
160
  */