@volter/tabnode 0.5.36 → 0.5.38

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/dist/__sw__.js CHANGED
@@ -578,7 +578,7 @@ async function virtualContext(event) {
578
578
  // virtual request asks for its port, so a restart does not send the
579
579
  // preview's own requests to the network.
580
580
  if (hosts.size === 0 && ((client && client.frameType === 'nested') || (event.request.mode === 'navigate' && (event.request.destination === 'iframe' || event.request.destination === 'frame')))) {
581
- await askForInit();
581
+ await askForInit(event.clientId);
582
582
  }
583
583
  const contextUrl = event.request.referrer || (client ? client.url : '');
584
584
  if (contextUrl) {
@@ -607,9 +607,16 @@ function ownPath(pathname) {
607
607
  return pathname.length > 1 && pathname.endsWith('/') ? pathname.slice(0, -1) : pathname;
608
608
  }
609
609
 
610
- /** Asks every window for the page's registrations and waits for them, as a request waits for its port. */
611
- async function askForInit() {
612
- const allClients = await self.clients.matchAll({ type: 'window' });
610
+ /**
611
+ * Asks every window for the page's registrations and waits for them, as a
612
+ * request waits for its port. Only another window can answer: a page's own
613
+ * document and scripts come before its `init`, and a frame being opened is no
614
+ * window yet, so with no other window each waited out the whole ask (a page
615
+ * opened on an origin whose worker had restarted took 5 s per request).
616
+ */
617
+ async function askForInit(requesterId) {
618
+ const allClients = (await self.clients.matchAll({ type: 'window' })).filter((client) => client.id !== requesterId);
619
+ if (allClients.length === 0) return;
613
620
  for (const client of allClients) client.postMessage({ type: 'sw-needs-init' });
614
621
  await new Promise((resolve) => {
615
622
  const check = setInterval(() => { if (hosts.size > 0) { clearInterval(check); resolve(); } }, 50);
@@ -739,7 +746,7 @@ async function withDocumentPrelude(event, response) {
739
746
  */
740
747
  async function handleVirtualRequest(request, port, path, rooted = false) {
741
748
  try {
742
- if (hosts.size === 0) await askForInit();
749
+ if (hosts.size === 0) await askForInit(requestEvents.get(request)?.clientId);
743
750
  const host = await hostForRequest(request, port);
744
751
  const registration = host ? host.flowControlledPorts.get(port) : undefined;
745
752
  // Build headers object
@@ -6235,7 +6235,11 @@ function hexSlice(start, end) {
6235
6235
  }
6236
6236
  return out;
6237
6237
  }
6238
+ const nativeBase64 = typeof Uint8Array.prototype.toBase64 === "function";
6238
6239
  function base64Text(bytes2, url2) {
6240
+ if (nativeBase64) {
6241
+ return bytes2.toBase64({ alphabet: url2 ? "base64url" : "base64", omitPadding: url2 });
6242
+ }
6239
6243
  const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
6240
6244
  let out = "";
6241
6245
  for (let index2 = 0; index2 < bytes2.length; index2 += 3) {
@@ -6259,7 +6263,20 @@ function base64urlSlice(start, end) {
6259
6263
  return base64Text(this.subarray(from, to2), true);
6260
6264
  }
6261
6265
  function byteLengthUtf8(string) {
6262
- return encoder$1.encode(string).length;
6266
+ let length = 0;
6267
+ for (let index2 = 0; index2 < string.length; index2 += 1) {
6268
+ const code2 = string.charCodeAt(index2);
6269
+ if (code2 < 128) length += 1;
6270
+ else if (code2 < 2048) length += 2;
6271
+ else if (code2 >= 55296 && code2 < 56320 && index2 + 1 < string.length) {
6272
+ const next2 = string.charCodeAt(index2 + 1);
6273
+ if (next2 >= 56320 && next2 < 57344) {
6274
+ length += 4;
6275
+ index2 += 1;
6276
+ } else length += 3;
6277
+ } else length += 3;
6278
+ }
6279
+ return length;
6263
6280
  }
6264
6281
  function compare(a, b) {
6265
6282
  return compareRanges(asBytes(a), 0, a.length, asBytes(b), 0, b.length);
@@ -23424,12 +23441,18 @@ function createCryptoModule(require2) {
23424
23441
  function digestEngine(algorithm) {
23425
23442
  return algorithm === "md5" ? md5.create() : SHA(algorithm);
23426
23443
  }
23444
+ const utf8Encoder = new TextEncoder();
23427
23445
  function cryptoBytes(value, encoding, allowArrayBuffer = false) {
23428
23446
  if (typeof value === "string") {
23429
23447
  if ((encoding == null ? void 0 : encoding.toLowerCase()) === "base64url") return buffer.Buffer.from(value, "base64");
23430
23448
  if (encoding !== void 0 && !buffer.Buffer.isEncoding(encoding)) {
23431
23449
  throw new TypeError(`Unknown encoding: ${encoding}`);
23432
23450
  }
23451
+ const lower = encoding == null ? void 0 : encoding.toLowerCase();
23452
+ if (lower === void 0 || lower === "utf8" || lower === "utf-8") {
23453
+ const bytes2 = utf8Encoder.encode(value);
23454
+ return buffer.Buffer.from(bytes2.buffer, bytes2.byteOffset, bytes2.byteLength);
23455
+ }
23433
23456
  return buffer.Buffer.from(value, encoding);
23434
23457
  }
23435
23458
  if (ArrayBuffer.isView(value)) {
@@ -73561,10 +73584,7 @@ function createNodeResolver(options) {
73561
73584
  const packageName = packageNameOf2(specifier);
73562
73585
  const packageRoot = `${nodeModules}/${packageName}`;
73563
73586
  const pkg = isDirectory(packageRoot) ? manifest(packageRoot) : null;
73564
- if (pkg && pkg.exports !== void 0 && pkg.exports !== null) {
73565
- const found = loadPackageExports(packageRoot, pkg, specifier, packageName);
73566
- if (found) return found;
73567
- }
73587
+ if (pkg && pkg.exports !== void 0 && pkg.exports !== null) return loadPackageExports(packageRoot, pkg, specifier, packageName);
73568
73588
  const path2 = `${nodeModules}/${specifier}`;
73569
73589
  return loadAsFile(path2) ?? loadAsDirectory(path2);
73570
73590
  };
@@ -78595,27 +78615,27 @@ function __substrateFollowAwaits(code2) {
78595
78615
  };
78596
78616
  const isFunction = (node) => node.type === "FunctionDeclaration" || node.type === "FunctionExpression" || node.type === "ArrowFunctionExpression";
78597
78617
  const visit = (node, framed, parent) => {
78618
+ const leading = [];
78598
78619
  if (isFunction(node)) {
78599
78620
  framed = node.async && node.body.type === "BlockStatement";
78600
78621
  if (framed) {
78601
78622
  const directives = node.body.body.filter((statement) => statement.directive !== void 0);
78602
- const at3 = directives.length ? directives[directives.length - 1].end : node.body.start + 1;
78603
- insert(at3, FOLLOW_TAKE);
78623
+ leading.push([directives.length ? directives[directives.length - 1].end : node.body.start + 1, FOLLOW_TAKE]);
78604
78624
  }
78605
78625
  } else if (node.type === "AwaitExpression") {
78606
78626
  insert(node.start, framed ? FOLLOW_RESUME_FRAMED : FOLLOW_RESUME_TAKEN);
78607
78627
  insert(node.end, `${FOLLOW_MARK})`);
78608
78628
  } else if (framed && node.type === "CatchClause") {
78609
- insert(node.body.start + 1, FOLLOW_RESTORE);
78629
+ leading.push([node.body.start + 1, FOLLOW_RESTORE]);
78610
78630
  } else if (framed && node.type === "TryStatement" && node.finalizer) {
78611
- insert(node.finalizer.start + 1, FOLLOW_RESTORE);
78631
+ leading.push([node.finalizer.start + 1, FOLLOW_RESTORE]);
78612
78632
  } else if (framed && node.type === "ForOfStatement" && node.await) {
78613
78633
  const whole = (parent == null ? void 0 : parent.type) === "LabeledStatement" ? parent : node;
78614
78634
  insert(whole.start, `{${FOLLOW_MARK}`);
78615
78635
  insert(whole.end, `;${FOLLOW_RESTORE}${FOLLOW_MARK}}`);
78616
- if (node.body.type === "BlockStatement") insert(node.body.start + 1, FOLLOW_RESTORE);
78636
+ if (node.body.type === "BlockStatement") leading.push([node.body.start + 1, FOLLOW_RESTORE]);
78617
78637
  else {
78618
- insert(node.body.start, `{${FOLLOW_MARK}${FOLLOW_RESTORE}`);
78638
+ leading.push([node.body.start, `{${FOLLOW_MARK}${FOLLOW_RESTORE}`]);
78619
78639
  insert(node.body.end, `${FOLLOW_MARK}}`);
78620
78640
  }
78621
78641
  }
@@ -78627,6 +78647,7 @@ function __substrateFollowAwaits(code2) {
78627
78647
  for (const item of child) if (item && typeof item.type === "string") visit(item, framed, node);
78628
78648
  } else if (typeof child.type === "string") visit(child, framed, node);
78629
78649
  }
78650
+ for (const [at3, text] of leading) insert(at3, text);
78630
78651
  };
78631
78652
  visit(ast, false, void 0);
78632
78653
  return edits.length ? applyReplacements(code2, edits) : code2;
@@ -78666,7 +78687,7 @@ function __substratePrepareBody(rawCode, resolvedPath, format2, process2) {
78666
78687
  }
78667
78688
  return __substrateFollowAwaits(code2);
78668
78689
  }
78669
- const PREPARED_MODULES_DIR = "/.tabnode/prepared";
78690
+ const PREPARED_MODULES_DIR = "/opt/.tabnode/prepared";
78670
78691
  const PREPARED_MODULES_FORMAT = "tabnode-prepared-2";
78671
78692
  function preparedModuleKey(rawCode, resolvedPath) {
78672
78693
  var _a3;
package/dist/index.mjs CHANGED
@@ -5946,7 +5946,11 @@ function hexSlice(start, end) {
5946
5946
  }
5947
5947
  return out;
5948
5948
  }
5949
+ const nativeBase64 = typeof Uint8Array.prototype.toBase64 === "function";
5949
5950
  function base64Text(bytes2, url2) {
5951
+ if (nativeBase64) {
5952
+ return bytes2.toBase64({ alphabet: url2 ? "base64url" : "base64", omitPadding: url2 });
5953
+ }
5950
5954
  const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
5951
5955
  let out = "";
5952
5956
  for (let index2 = 0; index2 < bytes2.length; index2 += 3) {
@@ -5970,7 +5974,20 @@ function base64urlSlice(start, end) {
5970
5974
  return base64Text(this.subarray(from, to), true);
5971
5975
  }
5972
5976
  function byteLengthUtf8(string) {
5973
- return encoder$1.encode(string).length;
5977
+ let length = 0;
5978
+ for (let index2 = 0; index2 < string.length; index2 += 1) {
5979
+ const code2 = string.charCodeAt(index2);
5980
+ if (code2 < 128) length += 1;
5981
+ else if (code2 < 2048) length += 2;
5982
+ else if (code2 >= 55296 && code2 < 56320 && index2 + 1 < string.length) {
5983
+ const next = string.charCodeAt(index2 + 1);
5984
+ if (next >= 56320 && next < 57344) {
5985
+ length += 4;
5986
+ index2 += 1;
5987
+ } else length += 3;
5988
+ } else length += 3;
5989
+ }
5990
+ return length;
5974
5991
  }
5975
5992
  function compare(a, b) {
5976
5993
  return compareRanges(asBytes(a), 0, a.length, asBytes(b), 0, b.length);
@@ -23572,12 +23589,18 @@ function createCryptoModule(require2) {
23572
23589
  function digestEngine(algorithm) {
23573
23590
  return algorithm === "md5" ? md5.create() : SHA(algorithm);
23574
23591
  }
23592
+ const utf8Encoder = new TextEncoder();
23575
23593
  function cryptoBytes(value, encoding, allowArrayBuffer = false) {
23576
23594
  if (typeof value === "string") {
23577
23595
  if ((encoding == null ? void 0 : encoding.toLowerCase()) === "base64url") return buffer.Buffer.from(value, "base64");
23578
23596
  if (encoding !== void 0 && !buffer.Buffer.isEncoding(encoding)) {
23579
23597
  throw new TypeError(`Unknown encoding: ${encoding}`);
23580
23598
  }
23599
+ const lower = encoding == null ? void 0 : encoding.toLowerCase();
23600
+ if (lower === void 0 || lower === "utf8" || lower === "utf-8") {
23601
+ const bytes2 = utf8Encoder.encode(value);
23602
+ return buffer.Buffer.from(bytes2.buffer, bytes2.byteOffset, bytes2.byteLength);
23603
+ }
23581
23604
  return buffer.Buffer.from(value, encoding);
23582
23605
  }
23583
23606
  if (ArrayBuffer.isView(value)) {
@@ -33156,10 +33179,7 @@ function createNodeResolver(options) {
33156
33179
  const packageName = packageNameOf2(specifier);
33157
33180
  const packageRoot = `${nodeModules}/${packageName}`;
33158
33181
  const pkg = isDirectory(packageRoot) ? manifest(packageRoot) : null;
33159
- if (pkg && pkg.exports !== void 0 && pkg.exports !== null) {
33160
- const found = loadPackageExports(packageRoot, pkg, specifier, packageName);
33161
- if (found) return found;
33162
- }
33182
+ if (pkg && pkg.exports !== void 0 && pkg.exports !== null) return loadPackageExports(packageRoot, pkg, specifier, packageName);
33163
33183
  const path2 = `${nodeModules}/${specifier}`;
33164
33184
  return loadAsFile(path2) ?? loadAsDirectory(path2);
33165
33185
  };
@@ -38253,27 +38273,27 @@ function __substrateFollowAwaits(code2) {
38253
38273
  };
38254
38274
  const isFunction = (node) => node.type === "FunctionDeclaration" || node.type === "FunctionExpression" || node.type === "ArrowFunctionExpression";
38255
38275
  const visit = (node, framed, parent) => {
38276
+ const leading = [];
38256
38277
  if (isFunction(node)) {
38257
38278
  framed = node.async && node.body.type === "BlockStatement";
38258
38279
  if (framed) {
38259
38280
  const directives = node.body.body.filter((statement) => statement.directive !== void 0);
38260
- const at2 = directives.length ? directives[directives.length - 1].end : node.body.start + 1;
38261
- insert(at2, FOLLOW_TAKE);
38281
+ leading.push([directives.length ? directives[directives.length - 1].end : node.body.start + 1, FOLLOW_TAKE]);
38262
38282
  }
38263
38283
  } else if (node.type === "AwaitExpression") {
38264
38284
  insert(node.start, framed ? FOLLOW_RESUME_FRAMED : FOLLOW_RESUME_TAKEN);
38265
38285
  insert(node.end, `${FOLLOW_MARK})`);
38266
38286
  } else if (framed && node.type === "CatchClause") {
38267
- insert(node.body.start + 1, FOLLOW_RESTORE);
38287
+ leading.push([node.body.start + 1, FOLLOW_RESTORE]);
38268
38288
  } else if (framed && node.type === "TryStatement" && node.finalizer) {
38269
- insert(node.finalizer.start + 1, FOLLOW_RESTORE);
38289
+ leading.push([node.finalizer.start + 1, FOLLOW_RESTORE]);
38270
38290
  } else if (framed && node.type === "ForOfStatement" && node.await) {
38271
38291
  const whole = (parent == null ? void 0 : parent.type) === "LabeledStatement" ? parent : node;
38272
38292
  insert(whole.start, `{${FOLLOW_MARK}`);
38273
38293
  insert(whole.end, `;${FOLLOW_RESTORE}${FOLLOW_MARK}}`);
38274
- if (node.body.type === "BlockStatement") insert(node.body.start + 1, FOLLOW_RESTORE);
38294
+ if (node.body.type === "BlockStatement") leading.push([node.body.start + 1, FOLLOW_RESTORE]);
38275
38295
  else {
38276
- insert(node.body.start, `{${FOLLOW_MARK}${FOLLOW_RESTORE}`);
38296
+ leading.push([node.body.start, `{${FOLLOW_MARK}${FOLLOW_RESTORE}`]);
38277
38297
  insert(node.body.end, `${FOLLOW_MARK}}`);
38278
38298
  }
38279
38299
  }
@@ -38285,6 +38305,7 @@ function __substrateFollowAwaits(code2) {
38285
38305
  for (const item of child) if (item && typeof item.type === "string") visit(item, framed, node);
38286
38306
  } else if (typeof child.type === "string") visit(child, framed, node);
38287
38307
  }
38308
+ for (const [at2, text] of leading) insert(at2, text);
38288
38309
  };
38289
38310
  visit(ast, false, void 0);
38290
38311
  return edits.length ? applyReplacements(code2, edits) : code2;
@@ -38324,7 +38345,7 @@ function __substratePrepareBody(rawCode, resolvedPath, format2, process2) {
38324
38345
  }
38325
38346
  return __substrateFollowAwaits(code2);
38326
38347
  }
38327
- const PREPARED_MODULES_DIR = "/.tabnode/prepared";
38348
+ const PREPARED_MODULES_DIR = "/opt/.tabnode/prepared";
38328
38349
  const PREPARED_MODULES_FORMAT = "tabnode-prepared-2";
38329
38350
  function preparedModuleKey(rawCode, resolvedPath) {
38330
38351
  var _a2;
@@ -40293,7 +40314,7 @@ class WorkerRuntime {
40293
40314
  this.worker = new Worker(
40294
40315
  new URL(
40295
40316
  /* @vite-ignore */
40296
- "/assets/runtime-worker-DEgP1CYS.js",
40317
+ "/assets/runtime-worker-C4lPa6-H.js",
40297
40318
  import.meta.url
40298
40319
  ),
40299
40320
  { type: "module" }
package/dist/runtime.d.ts CHANGED
@@ -16,8 +16,11 @@ export { pendingGuestTimers, stopGuestTimers } from './guest-timers';
16
16
  * from, in this directory of the tab's filesystem, and the loader takes it in
17
17
  * place of the passes when the file it read hashes to one. A body the loader
18
18
  * prepares for a file the image has none for is kept there the same way.
19
+ * Under `/opt`, a root a tab's store keeps, so a host can link the image's
20
+ * bodies there once from a store they share instead of writing each into
21
+ * every process's memory.
19
22
  */
20
- export declare const PREPARED_MODULES_DIR = "/.tabnode/prepared";
23
+ export declare const PREPARED_MODULES_DIR = "/opt/.tabnode/prepared";
21
24
  /** The name a prepared body goes under: the hash of the file as read, and how it is compiled. Undefined for a file no body is prepared for. */
22
25
  export declare function preparedModuleKey(rawCode: string, resolvedPath: string): string | undefined;
23
26
  /** The body the loader would compile for this file, with no load hooks and no type stripping: what the image carries. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@volter/tabnode",
3
- "version": "0.5.36",
3
+ "version": "0.5.38",
4
4
  "description": "Node's own library, in a browser tab.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -205,7 +205,16 @@ export function hexSlice(this: Uint8Array, start?: number, end?: number): string
205
205
  return out;
206
206
  }
207
207
 
208
+ /** The engine's own encoder where it has one (`Uint8Array.prototype.toBase64`): one string, no garbage. */
209
+ const nativeBase64 = typeof (Uint8Array.prototype as { toBase64?: unknown }).toBase64 === 'function';
210
+
208
211
  function base64Text(bytes: Uint8Array, url: boolean): string {
212
+ // Built a character at a time, a sourcemap's base64 was ropes of garbage:
213
+ // 600 MB of a Vite session's 1.4 GB allocated in its first 20 s in a tab.
214
+ if (nativeBase64) {
215
+ return (bytes as Uint8Array & { toBase64(options: { alphabet: string; omitPadding: boolean }): string })
216
+ .toBase64({ alphabet: url ? 'base64url' : 'base64', omitPadding: url });
217
+ }
209
218
  const alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
210
219
  let out = '';
211
220
  for (let index = 0; index < bytes.length; index += 3) {
@@ -235,7 +244,19 @@ export function base64urlSlice(this: Uint8Array, start?: number, end?: number):
235
244
 
236
245
  /** Node's `ByteLengthUtf8`. */
237
246
  export function byteLengthUtf8(string: string): number {
238
- return encoder.encode(string).length;
247
+ // Counted, not encoded: encoding the whole string to read its length
248
+ // allocated every Buffer.from(string) twice.
249
+ let length = 0;
250
+ for (let index = 0; index < string.length; index += 1) {
251
+ const code = string.charCodeAt(index);
252
+ if (code < 0x80) length += 1;
253
+ else if (code < 0x800) length += 2;
254
+ else if (code >= 0xd800 && code < 0xdc00 && index + 1 < string.length) {
255
+ const next = string.charCodeAt(index + 1);
256
+ if (next >= 0xdc00 && next < 0xe000) { length += 4; index += 1; } else length += 3;
257
+ } else length += 3;
258
+ }
259
+ return length;
239
260
  }
240
261
 
241
262
  /** Node's `Compare`: two buffers whole, as `memcmp` orders them. */
@@ -158,14 +158,17 @@ export function createNodeResolver(options: NodeResolverOptions): NodeResolver {
158
158
  return parts[0]!.startsWith("@") && parts.length > 1 ? `${parts[0]}/${parts[1]}` : parts[0]!;
159
159
  };
160
160
  // LOAD_NODE_MODULES from one directory: exports, else the package's own entry or a file under it.
161
+ // A package with `exports` is answered by the map alone, as Node answers it:
162
+ // a subpath the map does not name, or names to a missing file, does not
163
+ // resolve. Falling through to the file under the package made
164
+ // `require.resolve("<pkg>/package.json")` succeed where Node throws, and a
165
+ // program that asks that question to decide what it does (Vite config that
166
+ // excludes a package it cannot resolve) decided differently in the tab.
161
167
  const loadFromNodeModules = (nodeModules: string, specifier: string): string | null => {
162
168
  const packageName = packageNameOf(specifier);
163
169
  const packageRoot = `${nodeModules}/${packageName}`;
164
170
  const pkg = isDirectory(packageRoot) ? manifest(packageRoot) : null;
165
- if (pkg && pkg.exports !== undefined && pkg.exports !== null) {
166
- const found = loadPackageExports(packageRoot, pkg, specifier, packageName);
167
- if (found) return found;
168
- }
171
+ if (pkg && pkg.exports !== undefined && pkg.exports !== null) return loadPackageExports(packageRoot, pkg, specifier, packageName);
169
172
  const path = `${nodeModules}/${specifier}`;
170
173
  return loadAsFile(path) ?? loadAsDirectory(path);
171
174
  };
package/src/runtime.ts CHANGED
@@ -468,28 +468,35 @@ function __substrateFollowAwaits(code: string): string {
468
468
  const isFunction = (node: any): boolean => node.type === 'FunctionDeclaration' || node.type === 'FunctionExpression' || node.type === 'ArrowFunctionExpression';
469
469
  // `framed`: the nearest function is async and holds the frame in ASYNC_FRAME.
470
470
  const visit = (node: any, framed: boolean, parent: any): void => {
471
+ // What opens a block (the frame's take in an async body, the restore
472
+ // at the start of a catch, a finally or a for-await body) goes before
473
+ // anything in it: where the block's first statement is an `await`
474
+ // (`async function f(){await g()}`, `finally{await h()}`, as minified code
475
+ // writes them), both share an offset, and edits at one offset land in
476
+ // reverse of the order they were made. So these are made after the
477
+ // block's own edits, and land first.
478
+ const leading: Array<[number, string]> = [];
471
479
  if (isFunction(node)) {
472
480
  framed = node.async && node.body.type === 'BlockStatement';
473
481
  if (framed) {
474
482
  const directives = node.body.body.filter((statement: any) => statement.directive !== undefined);
475
- const at = directives.length ? directives[directives.length - 1].end : node.body.start + 1;
476
- insert(at, FOLLOW_TAKE);
483
+ leading.push([directives.length ? directives[directives.length - 1].end : node.body.start + 1, FOLLOW_TAKE]);
477
484
  }
478
485
  } else if (node.type === 'AwaitExpression') {
479
486
  insert(node.start, framed ? FOLLOW_RESUME_FRAMED : FOLLOW_RESUME_TAKEN);
480
487
  insert(node.end, `${FOLLOW_MARK})`);
481
488
  } else if (framed && node.type === 'CatchClause') {
482
- insert(node.body.start + 1, FOLLOW_RESTORE);
489
+ leading.push([node.body.start + 1, FOLLOW_RESTORE]);
483
490
  } else if (framed && node.type === 'TryStatement' && node.finalizer) {
484
- insert(node.finalizer.start + 1, FOLLOW_RESTORE);
491
+ leading.push([node.finalizer.start + 1, FOLLOW_RESTORE]);
485
492
  } else if (framed && node.type === 'ForOfStatement' && node.await) {
486
493
  // The loop's own wrap first: where its end is the body's, an insertion
487
494
  // made later lands before it, so the body's brace closes inside.
488
495
  const whole = parent?.type === 'LabeledStatement' ? parent : node;
489
496
  insert(whole.start, `{${FOLLOW_MARK}`);
490
497
  insert(whole.end, `;${FOLLOW_RESTORE}${FOLLOW_MARK}}`);
491
- if (node.body.type === 'BlockStatement') insert(node.body.start + 1, FOLLOW_RESTORE);
492
- else { insert(node.body.start, `{${FOLLOW_MARK}${FOLLOW_RESTORE}`); insert(node.body.end, `${FOLLOW_MARK}}`); }
498
+ if (node.body.type === 'BlockStatement') leading.push([node.body.start + 1, FOLLOW_RESTORE]);
499
+ else { leading.push([node.body.start, `{${FOLLOW_MARK}${FOLLOW_RESTORE}`]); insert(node.body.end, `${FOLLOW_MARK}}`); }
493
500
  }
494
501
  for (const key of Object.keys(node)) {
495
502
  if (key === 'type' || key === 'start' || key === 'end' || key === 'loc' || key === 'range') continue;
@@ -499,6 +506,7 @@ function __substrateFollowAwaits(code: string): string {
499
506
  for (const item of child) if (item && typeof item.type === 'string') visit(item, framed, node);
500
507
  } else if (typeof child.type === 'string') visit(child, framed, node);
501
508
  }
509
+ for (const [at, text] of leading) insert(at, text);
502
510
  };
503
511
  visit(ast, false, undefined);
504
512
  return edits.length ? applyReplacements(code, edits) : code;
@@ -589,8 +597,11 @@ function __substratePrepareBody(rawCode: string, resolvedPath: string, format: s
589
597
  * from, in this directory of the tab's filesystem, and the loader takes it in
590
598
  * place of the passes when the file it read hashes to one. A body the loader
591
599
  * prepares for a file the image has none for is kept there the same way.
600
+ * Under `/opt`, a root a tab's store keeps, so a host can link the image's
601
+ * bodies there once from a store they share instead of writing each into
602
+ * every process's memory.
592
603
  */
593
- export const PREPARED_MODULES_DIR = '/.tabnode/prepared';
604
+ export const PREPARED_MODULES_DIR = '/opt/.tabnode/prepared';
594
605
  const PREPARED_MODULES_FORMAT = 'tabnode-prepared-2';
595
606
  /** The name a prepared body goes under: the hash of the file as read, and how it is compiled. Undefined for a file no body is prepared for. */
596
607
  export function preparedModuleKey(rawCode: string, resolvedPath: string): string | undefined {
@@ -330,6 +330,7 @@ type HostBytes = InstanceType<typeof HostBuffer>;
330
330
  * encodings the guest Buffer polyfill does not (utf16le, latin1) and views a
331
331
  * caller's ArrayBuffer at its offset instead of copying the whole store.
332
332
  */
333
+ const utf8Encoder = new TextEncoder();
333
334
  function cryptoBytes(value: unknown, encoding?: string, allowArrayBuffer = false): HostBytes {
334
335
  if (typeof value === 'string') {
335
336
  // `buffer` 6.0.3 predates base64url; the alphabet is a superset of base64's.
@@ -337,6 +338,14 @@ function cryptoBytes(value: unknown, encoding?: string, allowArrayBuffer = false
337
338
  if (encoding !== undefined && !HostBuffer.isEncoding(encoding)) {
338
339
  throw new TypeError(`Unknown encoding: ${encoding}`);
339
340
  }
341
+ // UTF-8, the default, by the engine's encoder: `buffer`'s own builds an
342
+ // array of numbers per string, and a Vite session hashing every module it
343
+ // served made hundreds of MB of that garbage in its first seconds in a tab.
344
+ const lower = encoding?.toLowerCase();
345
+ if (lower === undefined || lower === 'utf8' || lower === 'utf-8') {
346
+ const bytes = utf8Encoder.encode(value);
347
+ return HostBuffer.from(bytes.buffer as ArrayBuffer, bytes.byteOffset, bytes.byteLength);
348
+ }
340
349
  return HostBuffer.from(value, encoding);
341
350
  }
342
351
  if (ArrayBuffer.isView(value)) {