@swmansion/popcorn 0.3.2 → 0.4.0-next.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.
Files changed (47) hide show
  1. package/LICENSE +1 -1
  2. package/NOTICE +12 -0
  3. package/README.md +78 -2
  4. package/dist/beam.d.ts +10 -0
  5. package/dist/errors.d.ts +96 -39
  6. package/dist/etf.d.ts +38 -0
  7. package/dist/events.d.ts +82 -0
  8. package/dist/index.d.ts +7 -3
  9. package/dist/index.mjs +1287 -2
  10. package/dist/plugins/beam_tools/lib/popcorn/beam_tools/beam_patcher.ex +184 -0
  11. package/dist/plugins/beam_tools/lib/popcorn/beam_tools/cli.ex +74 -0
  12. package/dist/plugins/beam_tools/lib/popcorn/beam_tools/packager.ex +540 -0
  13. package/dist/plugins/beam_tools/mix.exs +16 -0
  14. package/dist/plugins/beam_tools/patches/kernel/prim_tty.erl +13 -0
  15. package/dist/plugins/beam_tools/patches/stdlib/beam_lib.erl +27 -0
  16. package/dist/plugins/esbuild.d.ts +10 -2
  17. package/dist/plugins/esbuild.mjs +39 -34
  18. package/dist/plugins/rollup.d.ts +10 -2
  19. package/dist/plugins/rollup.mjs +46 -25
  20. package/dist/plugins/shared.d.ts +54 -4
  21. package/dist/plugins/shared.mjs +207 -0
  22. package/dist/plugins/vite.d.ts +17 -2
  23. package/dist/plugins/vite.mjs +201 -75
  24. package/dist/popcorn.d.ts +237 -110
  25. package/dist/runtimes/core/beam.emu.mjs +141 -0
  26. package/dist/runtimes/core/beam.mjs +141 -0
  27. package/dist/runtimes/core/beam.wasm +0 -0
  28. package/dist/runtimes/core/manifest.json +1 -0
  29. package/dist/runtimes/crypto/beam.emu.mjs +520 -0
  30. package/dist/runtimes/crypto/beam.mjs +520 -0
  31. package/dist/runtimes/crypto/beam.wasm +0 -0
  32. package/dist/runtimes/crypto/manifest.json +1 -0
  33. package/dist/tar.d.ts +4 -0
  34. package/dist/types.d.ts +108 -63
  35. package/dist/utils.d.ts +7 -0
  36. package/dist/worker.d.ts +1 -0
  37. package/dist/worker.mjs +765 -0
  38. package/package.json +20 -28
  39. package/dist/AtomVM.mjs +0 -7992
  40. package/dist/AtomVM.wasm +0 -0
  41. package/dist/bridge.d.ts +0 -22
  42. package/dist/bridge.mjs +0 -66
  43. package/dist/errors.mjs +0 -55
  44. package/dist/iframe.d.ts +0 -1
  45. package/dist/iframe.mjs +0 -215
  46. package/dist/popcorn.mjs +0 -381
  47. package/dist/types.mjs +0 -25
package/LICENSE CHANGED
@@ -198,4 +198,4 @@
198
198
  distributed under the License is distributed on an "AS IS" BASIS,
199
199
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200
200
  See the License for the specific language governing permissions and
201
- limitations under the License.
201
+ limitations under the License.
package/NOTICE ADDED
@@ -0,0 +1,12 @@
1
+ Popcorn includes Erlang/OTP compiled to WebAssembly with Emscripten.
2
+
3
+ Erlang/OTP
4
+ Copyright Ericsson AB and other contributors.
5
+ Licensed under the Apache License, Version 2.0.
6
+ Source: https://github.com/erlang/otp
7
+ Popcorn's runtime includes modifications for WebAssembly and JavaScript interoperability.
8
+
9
+ Emscripten
10
+ Copyright the Emscripten authors.
11
+ Available under the MIT license and the University of Illinois/NCSA Open Source License.
12
+ Source: https://github.com/emscripten-core/emscripten
package/README.md CHANGED
@@ -1,3 +1,79 @@
1
- # @swmansion/popcorn
1
+ # Popcorn OTP
2
2
 
3
- A package exposing Popcorn to the browser.
3
+ Run Elixir in a browser with OTP/BEAM compiled to WebAssembly.
4
+ This prerelease replaces the AtomVM runtime used by Popcorn 0.3.x.
5
+
6
+ ## Install
7
+
8
+ ```sh
9
+ npm install @swmansion/popcorn@next
10
+ ```
11
+
12
+ Add `{:popcorn, "0.4.0-next.0"}` to your Elixir application's dependencies.
13
+ Run `mix deps.get` and `mix compile` before building the JavaScript application.
14
+ The bundler plugins invoke Mix locally to package application and standard-library code.
15
+
16
+ Use the toolchain pinned in [popcorn/mise.toml](https://github.com/software-mansion/popcorn/blob/v0.4.0-next.0/popcorn/mise.toml).
17
+ The packager checks host OTP compatibility against the selected runtime's manifest.
18
+
19
+ ## Configure Vite
20
+
21
+ ```ts
22
+ import { defineConfig } from "vite";
23
+ import { popcorn } from "@swmansion/popcorn/vite";
24
+
25
+ export default defineConfig({
26
+ plugins: [
27
+ popcorn({
28
+ rootDir: "../",
29
+ app: "my_app",
30
+ }),
31
+ ],
32
+ });
33
+ ```
34
+
35
+ Set `rootDir` to the compiled Mix project's directory and `app` to its OTP application name.
36
+ Use `app: null` to package the base runtime without starting an application.
37
+
38
+ The npm package contains two variants:
39
+
40
+ - `core`: without native crypto support.
41
+ - `crypto`: includes native crypto and ASN.1 support for applications that depend on crypto, public_key, or ssl.
42
+
43
+ The plugin emits only the selected variant. The browser does not download both.
44
+ Both variants share one JavaScript API and one Hex package.
45
+ The plugin selects `crypto` when the application's dependencies or `extraApps` require it; otherwise it selects `core`.
46
+ Set `runtimeVariant: "core"` or `runtimeVariant: "crypto"` to override this choice.
47
+ An explicit `"core"` selection produces a build error if the application requires crypto.
48
+
49
+ Rollup and esbuild plugins accept the same options through `@swmansion/popcorn/rollup` and `@swmansion/popcorn/esbuild`.
50
+ Use ESM output with those bundlers.
51
+
52
+ ## Start the runtime
53
+
54
+ ```ts
55
+ import { Popcorn } from "@swmansion/popcorn";
56
+
57
+ const result = await Popcorn.init({});
58
+ if (!result.ok) throw result.error;
59
+
60
+ const vm = result.data;
61
+ // Stop the runtime when the application no longer needs it.
62
+ vm.deinit();
63
+ ```
64
+
65
+ ## Serve in production
66
+
67
+ Serve over HTTPS or localhost. Set these headers on the application and runtime responses:
68
+
69
+ ```text
70
+ Cross-Origin-Opener-Policy: same-origin
71
+ Cross-Origin-Embedder-Policy: require-corp
72
+ ```
73
+
74
+ Vite sets these headers for development and preview. Configure your production server separately.
75
+ Serve `.wasm` as `application/wasm`. Serve compressed `.tar.gz` files with `Content-Encoding: gzip` for `.tar` requests.
76
+ The package also emits uncompressed tar files. Brotli variants require `brotli: true` and the corresponding server configuration.
77
+
78
+ The JavaScript bridge currently requires a Content Security Policy that permits `unsafe-eval`.
79
+ See the [versioned Elixir API](https://popcorn.hexdocs.pm/0.4.0-next.0/) for interoperability details.
package/dist/beam.d.ts ADDED
@@ -0,0 +1,10 @@
1
+ import { type Result } from "./errors";
2
+ import type { BeamBootOptions, BeamSendPayload } from "./types";
3
+ export type Beam = {
4
+ boot: Promise<Result<null>>;
5
+ vmReady: Promise<void>;
6
+ send: (message: BeamSendPayload) => Result<null>;
7
+ writeStdin: (chunk: Uint8Array) => void;
8
+ resizeTty: (columns: number, rows: number) => void;
9
+ };
10
+ export declare function start(options: BeamBootOptions): Beam;
package/dist/errors.d.ts CHANGED
@@ -1,45 +1,102 @@
1
- /** Error codes for recoverable errors returned in CallResult */
2
- export type PopcornErrorCode = "timeout" | "deinitialized" | "reload";
3
- /** Recoverable error returned in CallResult (never thrown) */
4
- export declare class PopcornError extends Error {
5
- readonly code: PopcornErrorCode;
6
- constructor(code: PopcornErrorCode, message?: string);
7
- }
8
- /** Error codes for internal errors that indicate bugs or misuse */
9
- export type PopcornInternalErrorCode = "assert" | "private_constructor" | "bad_call" | "no_acked_call" | "bad_ack" | "already_mounted" | "unmounted" | "bad_target" | "bad_status" | "app_ready_timeout" | "bundle_not_found";
10
- /** Non-recoverable error indicating a bug or library misuse (always thrown) */
11
- export declare class PopcornInternalError extends Error {
12
- readonly code: PopcornInternalErrorCode;
13
- constructor(code: PopcornInternalErrorCode, message?: string);
14
- }
15
- /** Internal errors - indicate bugs, protocol violations, or library misuse */
16
- type ErrorData = {
17
- t: "assert";
18
- } | {
19
- t: "private_constructor";
20
- } | {
21
- t: "bad_call";
22
- } | {
23
- t: "no_acked_call";
24
- } | {
25
- t: "bad_ack";
1
+ /**
2
+ * An operation result. Check `ok` before accessing `data` or `error`.
3
+ * Expected failures use the error branch. Invalid usage can still throw.
4
+ */
5
+ export type Result<T, E extends Tag = Tag> = {
6
+ ok: true;
7
+ data: T;
26
8
  } | {
27
- t: "already_mounted";
28
- } | {
29
- t: "unmounted";
30
- } | {
31
- t: "bad_target";
9
+ ok: false;
10
+ error: PopcornError<E>;
11
+ };
12
+ type Tag = keyof PopcornErrors;
13
+ /** Error tags and their structured details. Match tags instead of message text. */
14
+ export type PopcornErrors = {
15
+ "timeout:init": {
16
+ timeoutMs: number;
17
+ };
18
+ "timeout:send": {
19
+ timeoutMs: number;
20
+ };
21
+ "timeout:call": {
22
+ timeoutMs: number;
23
+ };
24
+ "worker:load": {
25
+ message: string;
26
+ };
27
+ "vm:exited": VmExitedData;
28
+ "bridge:not-started": EmptyData;
29
+ "bridge:invalid-target": EmptyData;
30
+ "bridge:unserializable": UnserializableData;
31
+ "bridge:listener-not-found": {
32
+ targetName: string;
33
+ };
34
+ "genserver:noproc": {
35
+ target: string;
36
+ };
37
+ "genserver:exit": {
38
+ reason: string;
39
+ };
40
+ "genserver:unserializable": EmptyData;
41
+ "stdio:overflow": {
42
+ capacityBytes: number;
43
+ attemptedBytes: number;
44
+ };
45
+ "beam:missing-boot-script": {
46
+ url: string;
47
+ };
48
+ "beam:missing-manifest": {
49
+ url: string;
50
+ };
51
+ "beam:missing-tarball": {
52
+ name: string;
53
+ all: string[];
54
+ };
55
+ "internal:check": {
56
+ detail?: string;
57
+ };
58
+ "internal:unreachable": EmptyData;
59
+ "runtime:eval-unavailable": EmptyData;
60
+ };
61
+ /** Error tag and details without the Error instance or stack. */
62
+ export type SerializedError<T extends Tag = Tag> = {
63
+ [K in T]: {
64
+ t: K;
65
+ data: PopcornErrors[K];
66
+ };
67
+ }[T];
68
+ type EmptyData = Record<never, never>;
69
+ export type UnserializableReason = "cyclic-object" | "non-plain-object" | "lossy-int" | "non-finite-float" | "unsupported";
70
+ type UnserializableData = {
71
+ data: unknown;
72
+ part: unknown;
73
+ reason: UnserializableReason;
74
+ };
75
+ type VmExitedData = {
76
+ reason: "deinit";
32
77
  } | {
33
- t: "bad_status";
34
- status: string;
35
- expectedStatus: string;
78
+ reason: "abort";
79
+ data: string;
36
80
  } | {
37
- t: "app_ready_timeout";
81
+ reason: "error";
82
+ data: string;
38
83
  } | {
39
- t: "bundle_not_found";
40
- primary: string;
41
- fallback: string;
84
+ reason: "exit";
85
+ data: number;
42
86
  };
43
- export declare function buildError(error: ErrorData): PopcornInternalError;
44
- export declare function throwError(error: ErrorData): never;
87
+ export declare function err<T extends Tag>(t: T, data: PopcornErrors[T]): PopcornError<T>;
88
+ export declare function isErr<T extends Tag = Tag>(error: unknown, t?: T): error is PopcornError<T>;
89
+ /** @hidden */
90
+ export declare class PopcornError<T extends Tag = Tag> extends Error {
91
+ readonly cause: SerializedError<T>;
92
+ private readonly serialized;
93
+ constructor(cause: SerializedError<T>);
94
+ get t(): T;
95
+ get data(): PopcornErrors[T];
96
+ /** Returns the tag and a shallow copy of its details. */
97
+ serialize(): SerializedError<T>;
98
+ /** Restores a serialized error. Throws if validation fails. */
99
+ static deserialize(value: unknown): PopcornError;
100
+ }
101
+ export declare function isUnserializableReason(value: unknown): value is UnserializableReason;
45
102
  export {};
package/dist/etf.d.ts ADDED
@@ -0,0 +1,38 @@
1
+ import { type Result } from "./errors";
2
+ declare class AtomTerm {
3
+ readonly name: string;
4
+ constructor(name: string);
5
+ }
6
+ declare class TupleTerm {
7
+ readonly entries: unknown[];
8
+ constructor(entries: unknown[]);
9
+ }
10
+ /**
11
+ * Creates a BEAM atom value.
12
+ *
13
+ * The atom must already exist in the receiving VM. Plain strings encode as binaries.
14
+ */
15
+ export declare function atom(name: string): AtomTerm;
16
+ /** Alias for {@link atom}. */
17
+ export declare const a: typeof atom;
18
+ /**
19
+ * Creates a BEAM tuple.
20
+ *
21
+ * Plain arrays encode as lists.
22
+ */
23
+ export declare function tuple(first: unknown, second: unknown, ...rest: unknown[]): TupleTerm;
24
+ /** Alias for {@link tuple}. */
25
+ export declare const t: typeof tuple;
26
+ /** Pre-encoded ETF sub-term bytes (no version prefix), spliced verbatim. */
27
+ export declare class RawTerm {
28
+ readonly bytes: Uint8Array;
29
+ constructor(bytes: Uint8Array);
30
+ /**
31
+ * Wraps a full external term (`term_to_binary` output) as a spliceable
32
+ * sub-term by dropping its leading version byte.
33
+ */
34
+ static fromExternal(external: Uint8Array): RawTerm;
35
+ }
36
+ export type Mapper = (value: object) => object;
37
+ export declare function encode(data: unknown, mapper?: Mapper): Result<Uint8Array<ArrayBuffer>, "bridge:unserializable">;
38
+ export {};
@@ -0,0 +1,82 @@
1
+ import { type Result, type SerializedError } from "./errors";
2
+ import { type Mapper } from "./etf";
3
+ import type { AnyValue, BeamBootOptions, BeamEvent, BeamSendPayload, BeamTarget } from "./types";
4
+ type BootEvent = {
5
+ type: "popcorn:boot";
6
+ payload: Pick<BeamBootOptions, "emulatorArgs" | "extraArgs" | "env" | "ttySize"> & {
7
+ otpAssetsRoot?: string;
8
+ };
9
+ };
10
+ type StdinEvent = {
11
+ type: "popcorn:stdin";
12
+ payload: {
13
+ chunk: Uint8Array;
14
+ };
15
+ };
16
+ type TtyResizeEvent = {
17
+ type: "popcorn:tty-resize";
18
+ payload: {
19
+ columns: number;
20
+ rows: number;
21
+ };
22
+ };
23
+ type SendEvent = {
24
+ type: "popcorn:send";
25
+ payload: SendRequestPayload;
26
+ };
27
+ export type RunJsReplyPayload = {
28
+ message: BeamSendPayload;
29
+ };
30
+ type RunJsReplyEvent = {
31
+ type: "popcorn:run-js-reply";
32
+ payload: RunJsReplyPayload;
33
+ };
34
+ export type SendRequestPayload = {
35
+ id: string;
36
+ message: BeamSendPayload;
37
+ };
38
+ export type SerializedSendResult = {
39
+ ok: true;
40
+ data: null;
41
+ } | {
42
+ ok: false;
43
+ error: SerializedError;
44
+ };
45
+ export type SendCompletionPayload = {
46
+ id: string;
47
+ result: SerializedSendResult;
48
+ };
49
+ type SendEndEvent = {
50
+ type: "popcorn:send-end";
51
+ payload: SendCompletionPayload;
52
+ };
53
+ type BootEndEvent = {
54
+ type: "popcorn:boot-vm-ready";
55
+ payload: {};
56
+ } | {
57
+ type: "popcorn:boot-end";
58
+ payload: {};
59
+ } | {
60
+ type: "popcorn:boot-fail";
61
+ payload: SerializedError;
62
+ };
63
+ export type MainToVmEvent = BootEvent | SendEvent | RunJsReplyEvent | StdinEvent | TtyResizeEvent;
64
+ /**
65
+ * A decoded BEAM message payload.
66
+ *
67
+ * Includes restored PID handles and tracked JavaScript values.
68
+ */
69
+ export type PopcornEvent = AnyValue;
70
+ type RuntimeEvent = BeamEvent | SendEndEvent;
71
+ export type VmToMainEvent = RuntimeEvent | BootEndEvent;
72
+ export declare function readMainEvent(value: unknown): MainToVmEvent;
73
+ export declare function readWorkerEvent(value: unknown): VmToMainEvent;
74
+ export declare function serializeSendPayload(target: BeamTarget, payload: AnyValue, mapper?: Mapper): Result<BeamSendPayload, "bridge:unserializable">;
75
+ export declare function deserializeBridgeMessage(text: string): Extract<BeamEvent, {
76
+ type: "otp:message" | "otp:error" | "otp:run_js";
77
+ }> | null;
78
+ /** Usable only from main context. */
79
+ export declare function toVm(worker: Worker, event: MainToVmEvent, transfer?: Transferable[]): void;
80
+ /** Usable only from webworkers. */
81
+ export declare function toMain(event: VmToMainEvent): void;
82
+ export {};
package/dist/index.d.ts CHANGED
@@ -1,3 +1,7 @@
1
- export { Popcorn, PopcornError, PopcornInternalError } from "./popcorn";
2
- export type { PopcornErrorCode, PopcornInternalErrorCode, PopcornInitOptions, CastOptions, CallOptions, } from "./popcorn";
3
- export type { AnySerializable } from "./types";
1
+ export { PopcornError } from "./errors";
2
+ export { a, atom, t, tuple } from "./etf";
3
+ export { Popcorn, schedulers } from "./popcorn";
4
+ export type { PopcornOpts, SchedulerOptions, GenServer } from "./popcorn";
5
+ export type { PopcornEvent } from "./events";
6
+ export type { AnyValue, Pid, OtpErrorPayload, TtySize } from "./types";
7
+ export type { PopcornErrors, Result, SerializedError } from "./errors";