@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 +13 -4
- package/dist/events.d.ts +1 -1
- package/dist/index.mjs +26 -4
- package/dist/plugins/vite.mjs +6 -1
- package/dist/popcorn.d.ts +15 -4
- package/dist/runtimes/core/beam.emu.mjs +126 -128
- package/dist/runtimes/core/beam.mjs +126 -128
- package/dist/runtimes/core/beam.wasm +0 -0
- package/dist/runtimes/crypto/beam.emu.mjs +108 -111
- package/dist/runtimes/crypto/beam.mjs +108 -111
- package/dist/runtimes/crypto/beam.wasm +0 -0
- package/dist/types.d.ts +3 -0
- package/dist/worker.mjs +8 -0
- package/package.json +1 -1
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
...
|
|
631
|
-
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
|
-
*
|
|
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
|
*
|
package/dist/plugins/vite.mjs
CHANGED
|
@@ -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/`,
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
*/
|