@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/dist/AtomVM.wasm DELETED
Binary file
package/dist/bridge.d.ts DELETED
@@ -1,22 +0,0 @@
1
- import type { IframeRequest, IframeResponse, AnySerializable } from "./types";
2
- export type IframeBridgeArgs = {
3
- container: HTMLElement;
4
- config: Record<string, string>;
5
- script: {
6
- url: string;
7
- entrypoint: string;
8
- };
9
- debug: boolean;
10
- onMessage: (data: IframeResponse) => void;
11
- };
12
- export declare function sendIframeResponse(type: string, data: AnySerializable): void;
13
- export declare class IframeBridge {
14
- private iframe;
15
- private handlerRef;
16
- private debug;
17
- private onMessage;
18
- constructor(args: IframeBridgeArgs);
19
- sendIframeRequest(data: IframeRequest): void;
20
- deinit(): void;
21
- private messageHandler;
22
- }
package/dist/bridge.mjs DELETED
@@ -1,66 +0,0 @@
1
- import { isMessageType } from './types.mjs';
2
- import { throwError } from './errors.mjs';
3
-
4
- const STYLE_HIDDEN = "visibility: hidden; width: 0px; height: 0px; border: none";
5
- class IframeBridge {
6
- iframe;
7
- handlerRef;
8
- // @ts-expect-error TODO: use for tracing
9
- debug;
10
- onMessage;
11
- constructor(args) {
12
- const { container, config, script, debug, onMessage } = args;
13
- this.debug = debug;
14
- this.onMessage = onMessage;
15
- this.iframe = document.createElement("iframe");
16
- this.iframe.srcdoc = `
17
- <html lang="en" dir="ltr">
18
- <head>
19
- ${metaTagsFrom(config)}
20
- </head>
21
- <body>
22
- <script type="module" defer>
23
- import { ${script.entrypoint} } from "${script.url}";
24
- ${script.entrypoint}();
25
- </script>
26
- </body>
27
- </html>`;
28
- this.iframe.style = STYLE_HIDDEN;
29
- this.handlerRef = this.messageHandler.bind(this);
30
- window.addEventListener("message", this.handlerRef);
31
- // mount
32
- container.appendChild(this.iframe);
33
- }
34
- sendIframeRequest(data) {
35
- const w = this.iframe.contentWindow;
36
- if (w === null)
37
- throwError({ t: "assert" });
38
- w.postMessage(data);
39
- }
40
- deinit() {
41
- window.removeEventListener("message", this.handlerRef);
42
- this.iframe.remove();
43
- }
44
- messageHandler(event) {
45
- const fromOurIframe = event.source === this.iframe.contentWindow;
46
- if (!fromOurIframe || !isIframeResponse(event.data))
47
- return;
48
- this.onMessage(event.data);
49
- }
50
- }
51
- function isIframeResponse(payload) {
52
- if (typeof payload !== "object" || payload === null)
53
- return false;
54
- if (!Object.hasOwn(payload, "type") || !Object.hasOwn(payload, "value"))
55
- return false;
56
- if (typeof payload.type !== "string")
57
- return false;
58
- return isMessageType(payload.type);
59
- }
60
- function metaTagsFrom(config) {
61
- return Object.entries(config)
62
- .map(([key, value]) => `<meta name="${key}" content="${value}" />`)
63
- .join("\n");
64
- }
65
-
66
- export { IframeBridge };
package/dist/errors.mjs DELETED
@@ -1,55 +0,0 @@
1
- const defaultErrorMessages = {
2
- timeout: "Promise timeout",
3
- deinitialized: "Call cancelled due to instance deinit",
4
- reload: "Call cancelled due to iframe reload",
5
- };
6
- /** Recoverable error returned in CallResult (never thrown) */
7
- class PopcornError extends Error {
8
- code;
9
- constructor(code, message) {
10
- super(message ?? defaultErrorMessages[code]);
11
- this.code = code;
12
- this.name = "PopcornError";
13
- }
14
- }
15
- const INIT_VM_TIMEOUT_MS = 30_000;
16
- /** Non-recoverable error indicating a bug or library misuse (always thrown) */
17
- class PopcornInternalError extends Error {
18
- code;
19
- constructor(code, message) {
20
- super(message ?? `Internal error: ${code}`);
21
- this.code = code;
22
- this.name = "PopcornInternalError";
23
- }
24
- }
25
- function buildError(error) {
26
- switch (error.t) {
27
- case "assert":
28
- return new PopcornInternalError("assert", "Assertion error");
29
- case "private_constructor":
30
- return new PopcornInternalError("private_constructor", "Don't construct the Popcorn object directly, use Popcorn.init() instead");
31
- case "bad_call":
32
- return new PopcornInternalError("bad_call", "Response for non-existent call");
33
- case "no_acked_call":
34
- return new PopcornInternalError("no_acked_call", "Response for non-acknowledged call");
35
- case "bad_ack":
36
- return new PopcornInternalError("bad_ack", "Ack for non-existent call");
37
- case "already_mounted":
38
- return new PopcornInternalError("already_mounted", "Iframe already mounted");
39
- case "unmounted":
40
- return new PopcornInternalError("unmounted", "WASM iframe not mounted");
41
- case "bad_target":
42
- return new PopcornInternalError("bad_target", "Unspecified target process");
43
- case "bad_status":
44
- return new PopcornInternalError("bad_status", `Operation not allowed: instance in "${error.status}" state, expected "${error.expectedStatus}"`);
45
- case "app_ready_timeout":
46
- return new PopcornInternalError("app_ready_timeout", `Elixir app did not call Popcorn.Wasm.ready() within ${INIT_VM_TIMEOUT_MS}ms`);
47
- case "bundle_not_found":
48
- return new PopcornInternalError("bundle_not_found", `Could not find a valid .avm bundle at "${error.primary}" or fallback "${error.fallback}"`);
49
- }
50
- }
51
- function throwError(error) {
52
- throw buildError(error);
53
- }
54
-
55
- export { PopcornError, PopcornInternalError, buildError, throwError };
package/dist/iframe.d.ts DELETED
@@ -1 +0,0 @@
1
- export declare function initVm(): Promise<void>;
package/dist/iframe.mjs DELETED
@@ -1,215 +0,0 @@
1
- import init from './AtomVM.mjs';
2
-
3
- const HEARTBEAT_INTERVAL_MS = 500;
4
- const MESSAGES = {
5
- EVENT: "popcorn-event",
6
- CALL: "popcorn-call",
7
- CAST: "popcorn-cast",
8
- CALL_ACK: "popcorn-callAck",
9
- STDOUT: "popcorn-stdout",
10
- STDERR: "popcorn-stderr",
11
- HEARTBEAT: "popcorn-heartbeat",
12
- RELOAD: "popcorn-reload",
13
- };
14
- new Set(Object.values(MESSAGES));
15
-
16
- function sendIframeResponse(type, data) {
17
- window.parent.postMessage({ type, value: data });
18
- }
19
-
20
- // @ts-expect-error atomvm doesn't have types yet
21
- let Module = null;
22
- class TrackedValue {
23
- key;
24
- value;
25
- constructor({ key, value }) {
26
- if (typeof key !== "number") {
27
- throw new Error("key property in TrackedValue must be a number");
28
- }
29
- this.key = key;
30
- this.value = value;
31
- }
32
- }
33
- globalThis.TrackedValue = TrackedValue;
34
- async function initVm() {
35
- const bundlePaths = Array.from(document.querySelectorAll('meta[name^="bundle-path-"]'), (el) => el.content);
36
- const bundles = await Promise.all(bundlePaths.map(async (p) => {
37
- const resp = await fetch(p);
38
- const buf = await resp.arrayBuffer();
39
- return new Int8Array(buf);
40
- }));
41
- await startVm(bundles);
42
- window.addEventListener("message", async ({ data }) => {
43
- const type = data.type;
44
- if (type === MESSAGES.CALL) {
45
- await handleCall(data.value);
46
- }
47
- else if (type === MESSAGES.CAST) {
48
- handleCast(data.value);
49
- }
50
- });
51
- setInterval(() => sendIframeResponse(MESSAGES.HEARTBEAT, null), HEARTBEAT_INTERVAL_MS);
52
- }
53
- async function startVm(avmBundles) {
54
- const bundleFilePaths = avmBundles.map((_, i) => `/data/bundle-${i}.avm`);
55
- const moduleInstance = await init({
56
- preRun: [
57
- function ({ FS }) {
58
- FS.mkdir("/data");
59
- avmBundles.forEach((bundle, i) => {
60
- FS.writeFile(bundleFilePaths[i], bundle);
61
- });
62
- },
63
- ],
64
- arguments: bundleFilePaths,
65
- print(text) {
66
- sendIframeResponse(MESSAGES.STDOUT, text);
67
- },
68
- printErr(text) {
69
- sendIframeResponse(MESSAGES.STDERR, text);
70
- },
71
- onAbort() {
72
- // Timeout so that error logs are (hopefully) printed
73
- // before we terminate
74
- setTimeout(() => sendIframeResponse(MESSAGES.RELOAD, null), 100);
75
- },
76
- });
77
- Module = moduleInstance;
78
- moduleInstance["serialize"] = JSON.stringify;
79
- moduleInstance["deserialize"] = deserialize;
80
- moduleInstance["cleanupFunctions"] = new Map();
81
- moduleInstance["onTrackedObjectDelete"] = (key) => {
82
- const fns = moduleInstance["cleanupFunctions"];
83
- const fn = fns.get(key);
84
- fns.delete(key);
85
- try {
86
- fn?.();
87
- }
88
- catch (e) {
89
- console.error(e);
90
- }
91
- finally {
92
- moduleInstance["trackedObjectsMap"].delete(key);
93
- }
94
- };
95
- const origCast = moduleInstance["cast"];
96
- const origCall = moduleInstance["call"];
97
- moduleInstance["cast"] = (process, args) => {
98
- const serialized = moduleInstance.serialize(args);
99
- origCast(process, serialized);
100
- };
101
- moduleInstance["call"] = (process, args) => {
102
- const serialized = moduleInstance.serialize(args);
103
- return origCall(process, serialized);
104
- };
105
- moduleInstance["onRunTrackedJs"] = (scriptString, isDebug) => {
106
- const trackValue = (tracked) => {
107
- const getKey = moduleInstance["nextTrackedObjectKey"];
108
- const map = moduleInstance["trackedObjectsMap"];
109
- if (tracked instanceof TrackedValue) {
110
- map.set(tracked.key, tracked.value);
111
- return tracked.key;
112
- }
113
- const key = getKey();
114
- map.set(key, tracked);
115
- return key;
116
- };
117
- let fn;
118
- try {
119
- const indirectEval = eval;
120
- fn = indirectEval(scriptString);
121
- }
122
- catch (e) {
123
- // TODO: send onEvalError for Popcorn object
124
- console.error(e);
125
- return null;
126
- }
127
- if (isDebug)
128
- ensureFunctionEval(fn);
129
- let result;
130
- try {
131
- result = fn?.(moduleInstance);
132
- }
133
- catch (e) {
134
- // TODO: send onEvalError for Popcorn object
135
- console.error(e);
136
- return null;
137
- }
138
- if (isDebug)
139
- ensureResultKeyList(result);
140
- return result?.map(trackValue) ?? [];
141
- };
142
- moduleInstance["onGetTrackedObjects"] = (keys) => {
143
- const getTrackedObject = (key) => {
144
- const serialize = moduleInstance["serialize"];
145
- const map = moduleInstance["trackedObjectsMap"];
146
- return serialize(map.get(key));
147
- };
148
- return keys.map(getTrackedObject);
149
- };
150
- moduleInstance["sendEvent"] = (eventName, payload) => {
151
- sendIframeResponse(MESSAGES.EVENT, { eventName, payload });
152
- };
153
- }
154
- async function handleCall(request) {
155
- if (!Module) {
156
- throw new Error("Module not initialized");
157
- }
158
- const { requestId, process, args } = request;
159
- sendIframeResponse(MESSAGES.CALL_ACK, { requestId });
160
- try {
161
- const result = await Module.call(process, args);
162
- sendIframeResponse(MESSAGES.CALL, {
163
- requestId,
164
- data: Module.deserialize(result),
165
- });
166
- }
167
- catch (error) {
168
- if (error == "noproc") {
169
- sendIframeResponse(MESSAGES.RELOAD, null);
170
- console.error("Runtime VM crashed, popcorn iframe reloaded.");
171
- return;
172
- }
173
- sendIframeResponse(MESSAGES.CALL, {
174
- requestId,
175
- error: Module.deserialize(error),
176
- });
177
- }
178
- }
179
- function handleCast(request) {
180
- if (!Module) {
181
- throw new Error("Module not initialized");
182
- }
183
- const { process, args } = request;
184
- Module.cast(process, args);
185
- }
186
- function ensureFunctionEval(maybeFunction) {
187
- if (typeof maybeFunction !== "function") {
188
- throw new Error("Script passed to onRunTrackedJs() is not wrapped in a function");
189
- }
190
- }
191
- function ensureResultKeyList(result) {
192
- if (!Array.isArray(result) && result !== undefined) {
193
- throw new Error("Script passed to onRunTrackedJs() returned invalid value, accepted values are arrays and undefined");
194
- }
195
- }
196
- // `json` selects the realm whose JSON.parse builds the result. By default that
197
- // is the iframe's own JSON, but run_js passes the parent window's JSON so the
198
- // deserialized terms (arrays/objects) belong to the parent realm — otherwise
199
- // cross-realm arrays sent out of the iframe fail `instanceof Array` checks in
200
- // the parent (e.g. phoenix_live_view's diff renderer). Tracked `popcorn_ref`
201
- // values are resolved by reference and keep their own realm regardless.
202
- function deserialize(message, json = JSON) {
203
- return json.parse(message, (_key, value) => {
204
- const isRef = typeof value === "object" &&
205
- value !== null &&
206
- Object.hasOwn(value, "popcorn_ref") &&
207
- Object.getOwnPropertyNames(value).length == 1;
208
- if (!isRef) {
209
- return value;
210
- }
211
- return Module?.trackedObjectsMap.get(value.popcorn_ref);
212
- });
213
- }
214
-
215
- export { initVm };