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

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.
@@ -5,9 +5,9 @@ import { r as runtimeDirectory, p as popcorn$1 } from './shared.mjs';
5
5
  import 'node:child_process';
6
6
  import 'node:os';
7
7
  import 'node:util';
8
- import 'node:zlib';
9
8
 
10
- const CORS_HEADERS = {
9
+ /** Headers required by the threaded WebAssembly runtime. */
10
+ const crossOriginIsolationHeaders = {
11
11
  "Cross-Origin-Opener-Policy": "same-origin",
12
12
  "Cross-Origin-Embedder-Policy": "require-corp",
13
13
  };
@@ -22,7 +22,7 @@ const distDir = resolve(dirname(fileURLToPath(import.meta.url)), "..");
22
22
  * import { popcorn } from "@swmansion/popcorn/vite";
23
23
  *
24
24
  * export default defineConfig({
25
- * plugins: [popcorn({ rootDir: "../my_app", app: "my_app" })],
25
+ * plugins: [popcorn({ rootDir: "../my_app" })],
26
26
  * });
27
27
  * ```
28
28
  *
@@ -43,8 +43,8 @@ function popcorn(options) {
43
43
  let dirty = false;
44
44
  let outDir;
45
45
  let assetsDir = "assets";
46
- // Dev/preview: pack once at first request, re-pack lazily after the app's
47
- // compiled beams change. A single in-flight promise dedupes concurrent
46
+ // Dev/preview: pack once at server startup, then re-pack lazily after the
47
+ // app's compiled beams change. A single in-flight promise dedupes concurrent
48
48
  // requests; a change during a re-pack re-sets `dirty` so it isn't lost.
49
49
  const ensurePrepared = () => {
50
50
  if (prepared !== undefined && !dirty)
@@ -95,24 +95,21 @@ function popcorn(options) {
95
95
  const filePath = resolve(assetsRoot, requestPath.slice(requestRoot.length));
96
96
  if (!isUnder(assetsRoot, filePath)) {
97
97
  res.statusCode = 403;
98
- setHeaders(res);
99
98
  res.end("Forbidden");
100
99
  return;
101
100
  }
102
101
  try {
103
102
  const compressible = isCompressible(filePath);
104
103
  const encoding = compressible
105
- ? selectEncoding(req.headers["accept-encoding"]?.toString(), options.brotli ?? false)
104
+ ? selectEncoding(req.headers["accept-encoding"]?.toString())
106
105
  : { name: null, suffix: "" };
107
106
  if (compressible && encoding.name === null) {
108
107
  res.statusCode = 406;
109
- setHeaders(res);
110
108
  res.setHeader("Vary", "Accept-Encoding");
111
109
  res.end("No supported content encoding");
112
110
  return;
113
111
  }
114
112
  const content = await readFile(`${filePath}${encoding.suffix}`);
115
- setHeaders(res);
116
113
  setContentType(res, filePath);
117
114
  res.setHeader("Vary", "Accept-Encoding");
118
115
  if (encoding.name !== null) {
@@ -122,7 +119,6 @@ function popcorn(options) {
122
119
  }
123
120
  catch {
124
121
  res.statusCode = 404;
125
- setHeaders(res);
126
122
  res.end("Not found");
127
123
  }
128
124
  };
@@ -144,10 +140,6 @@ function popcorn(options) {
144
140
  },
145
141
  ],
146
142
  },
147
- // COOP/COEP must be on every response (SharedArrayBuffer/pthreads),
148
- // including the worker/beam files Vite serves from the package itself.
149
- server: { headers: CORS_HEADERS },
150
- preview: { headers: CORS_HEADERS },
151
143
  };
152
144
  },
153
145
  configResolved(config) {
@@ -155,8 +147,9 @@ function popcorn(options) {
155
147
  outDir = resolve(config.root, config.build.outDir);
156
148
  assetsDir = config.build.assetsDir;
157
149
  },
158
- configureServer(server) {
150
+ async configureServer(server) {
159
151
  const rootDir = resolve(options.rootDir);
152
+ server.httpServer?.prependListener("request", setIsolationHeaders);
160
153
  server.watcher.add(rootDir);
161
154
  server.watcher.on("all", (_event, file) => {
162
155
  if (isUnder(rootDir, file))
@@ -164,10 +157,14 @@ function popcorn(options) {
164
157
  });
165
158
  server.middlewares.use(serve);
166
159
  server.httpServer?.once("close", cleanup);
160
+ await ensurePrepared();
161
+ dirty = false;
167
162
  },
168
- configurePreviewServer(server) {
163
+ async configurePreviewServer(server) {
164
+ server.httpServer?.prependListener("request", setIsolationHeaders);
169
165
  server.middlewares.use(serve);
170
166
  server.httpServer?.once("close", cleanup);
167
+ await ensurePrepared();
171
168
  },
172
169
  async closeBundle() {
173
170
  assert(outDir !== undefined, "outDir was not resolved");
@@ -187,8 +184,8 @@ function popcorn(options) {
187
184
  function isCompressible(path) {
188
185
  return path.endsWith(".tar") || path.endsWith(".wasm");
189
186
  }
190
- function selectEncoding(header, useBrotli) {
191
- if (useBrotli && header?.includes("br")) {
187
+ function selectEncoding(header) {
188
+ if (header?.includes("br")) {
192
189
  return { name: "br", suffix: ".br" };
193
190
  }
194
191
  if (header?.includes("gzip"))
@@ -199,8 +196,8 @@ function isUnder(dir, file) {
199
196
  const rel = relative(dir, file);
200
197
  return rel !== "" && !rel.startsWith("..") && !isAbsolute(rel);
201
198
  }
202
- function setHeaders(res) {
203
- for (const [key, value] of Object.entries(CORS_HEADERS)) {
199
+ function setIsolationHeaders(_req, res) {
200
+ for (const [key, value] of Object.entries(crossOriginIsolationHeaders)) {
204
201
  res.setHeader(key, value);
205
202
  }
206
203
  }
@@ -224,4 +221,4 @@ function assert(ok, message) {
224
221
  }
225
222
  }
226
223
 
227
- export { popcorn };
224
+ export { crossOriginIsolationHeaders, popcorn };
package/dist/popcorn.d.ts CHANGED
@@ -123,6 +123,7 @@ export declare class Popcorn<Output extends TtyOutput = "text"> {
123
123
  private readonly eventHandlers;
124
124
  private readonly pendingSends;
125
125
  private readonly pendingCalls;
126
+ private readonly readyWaiters;
126
127
  private callSeq;
127
128
  private readonly trackedValues;
128
129
  private trackedKeySeq;
@@ -137,7 +138,7 @@ export declare class Popcorn<Output extends TtyOutput = "text"> {
137
138
  *
138
139
  * Call {@link boot} to start the VM.
139
140
  **/
140
- constructor(opts: PopcornOpts<Output>);
141
+ constructor(opts?: PopcornOpts<Output>);
141
142
  private spawnWorker;
142
143
  /**
143
144
  * Creates an instance and waits for {@link boot}.
@@ -146,7 +147,7 @@ export declare class Popcorn<Output extends TtyOutput = "text"> {
146
147
  *
147
148
  * @returns Ok tuple or `runtime:eval-unavailable` if the page blocks JavaScript evaluation.
148
149
  */
149
- static init<Output extends TtyOutput = "text">(opts: PopcornOpts<Output>): Promise<Result<Popcorn<Output>>>;
150
+ static init<Output extends TtyOutput = "text">(opts?: PopcornOpts<Output>): Promise<Result<Popcorn<Output>>>;
150
151
  /**
151
152
  * Starts the VM and waits for its bridge and entrypoint application.
152
153
  *
@@ -229,8 +230,11 @@ export declare class Popcorn<Output extends TtyOutput = "text"> {
229
230
  private castBridge;
230
231
  private nextCallId;
231
232
  private runJs;
233
+ private handleReadyRequest;
234
+ private flushReadyWaiters;
235
+ private sendReadyReply;
232
236
  private asRef;
233
- private sendRunJsReply;
237
+ private sendBridgeReply;
234
238
  private jsWithCurrentEnv;
235
239
  private reviveHandles;
236
240
  /** Maps pids and `TrackedValue`s during encoding, collecting handles into