@live-codes/ocaml 0.1.0 → 0.1.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@live-codes/ocaml",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Run OCaml in the browser with no server: a compiler that emits JavaScript, and a toplevel that runs OCaml directly",
5
5
  "repository": {
6
6
  "type": "git",
@@ -11,11 +11,20 @@
11
11
 
12
12
  const lines = (text) => text.split('\n').filter((line) => line.trim() !== '');
13
13
 
14
- // js_of_ocaml is a command-line program and calls `exit` after writing its
15
- // output -- and in a browser-target build `exit` is not implemented, so it
16
- // raises. The output has already arrived by then, so this is noise rather than
17
- // a diagnostic. Filter it, since the CLI is not ours to change.
18
- const PHANTOM = /Function 'exit' not implemented/;
14
+ // Two kinds of upstream noise reach the worker's stderr, and neither says anything
15
+ // about the program being compiled:
16
+ //
17
+ // - js_of_ocaml is a command-line program and calls `exit` after writing its
18
+ // output -- and in a browser-target build `exit` is not implemented, so it
19
+ // raises. The output has already arrived by then.
20
+ // - js_of_ocaml's runtime tries to `importScripts` its own chunk files, which
21
+ // cannot work once the worker is created from a data URL -- the trick that lets
22
+ // the compiler run from a CDN, since a worker script must otherwise be
23
+ // same-origin. It falls back to the files it was handed and the bundle is
24
+ // correct. A genuinely fatal load leaves no output at all, and that is reported
25
+ // separately by `compile`.
26
+ const PHANTOM =
27
+ /Function 'exit' not implemented|Failed to execute 'importScripts'|^Uncaught NetworkError:/;
19
28
  const diagnosticsOf = (text) => lines(text).filter((line) => !PHANTOM.test(line));
20
29
 
21
30
  // Byte-for-byte, and deliberately not TextDecoder: in browsers "latin1" and
@@ -38,9 +47,25 @@ function hexToBytes(hex) {
38
47
  return bytes;
39
48
  }
40
49
 
41
- function runInWorker(baseUrl, script, files, env, argv, fs = []) {
50
+ // A worker script has to be same-origin with whatever creates it: `new Worker()`
51
+ // refuses a cross-origin URL, and CORS does not help -- it is the script fetch the
52
+ // browser will not make. So the worker is created from a small data URL whose only
53
+ // job is to `importScripts` the harness, which *is* allowed across origins and then
54
+ // loads everything else itself.
55
+ //
56
+ // A data URL rather than a blob because the compiler may itself be running inside a
57
+ // worker, where `URL.createObjectURL` is not dependable. The URL is interpolated
58
+ // into JavaScript, so it is quoted with `JSON.stringify` and base64-encoded: the
59
+ // base64 form needs no escaping of its own.
60
+ const toDataUrl = (content, type = 'text/javascript') =>
61
+ `data:${type};charset=UTF-8;base64,` + btoa(content);
62
+
63
+ const workerDataUrlFor = (script) =>
64
+ toDataUrl(`importScripts(${JSON.stringify(String(script))});`);
65
+
66
+ async function runInWorker(baseUrl, script, files, env, argv, fs = []) {
67
+ const worker = new Worker(workerDataUrlFor(new URL('worker.js', baseUrl).href));
42
68
  return new Promise((resolve, reject) => {
43
- const worker = new Worker(new URL('worker.js', baseUrl));
44
69
  worker.onmessage = (event) => {
45
70
  worker.terminate();
46
71
  resolve(event.data);