| Six verbs, and the last of the nix 2e24e64 nandi 17h ago | 1 | # `web` |
| A third target, and the seam that was already waiting for it f54ca45 nandi 2d ago | 2 | |
| Six verbs, and the last of the nix 2e24e64 nandi 17h ago | 3 | modal run .modal/web/container.py |
| 4 | just modal web |
| A third target, and the seam that was already waiting for it f54ca45 nandi 2d ago | 5 | |
| Six verbs, and the last of the nix 2e24e64 nandi 17h ago | 6 | Defined by `container.toml`; `../_loader.py` is what reads it, and |
| 7 | its comments are the spec. |
| Three tarballs where a devShell was 5ce66d5 nandi yesterday | 8 | Built on `debian:13-slim`. |
| 9 | |
| 10 | No nix, and that is the point of this container rather than an |
| 11 | incidental fact about it. The build is `tools/build-web.sh`, which |
| 12 | gets its Flutter, its JDK and its Clojure CLI from |
| 13 | `tools/toolchain.sh` -- three tarballs pinned by sha256 and unpacked |
| 14 | into a directory. So the image build is one `apt-get install` of |
| 15 | curl, git, rsync, tar and the two unarchivers, and everything that |
| 16 | used to happen before a line of Dart was compiled -- warming a |
| 17 | devShell, printing its environment, caching that against flake.lock, |
| 18 | copying a nix closure back to a volume afterwards -- does not happen |
| 19 | at all. The toolchain lands on the volume and the second run finds |
| 20 | it there. |
| 21 | |
| Six verbs, and the last of the nix 2e24e64 nandi 17h ago | 22 | The `dev` container beside it works the same way now, and so does a |
| 23 | laptop: one script, one pinned set of tarballs, and whatever the |
| 24 | host has to bring for a given target -- GTK and a C++ toolchain for |
| 25 | the desktop build, Google's command-line tools for the APK. |
| Three tarballs where a devShell was 5ce66d5 nandi yesterday | 26 | |
| 27 | Same incremental shape as before -- the working tree, the generated |
| 28 | Dart under `flutter/lib/cljd-out` and Flutter's caches live on the |
| Six verbs, and the last of the nix 2e24e64 nandi 17h ago | 29 | `devshell` volume, under `frq-web/` so the desktop |
| Three tarballs where a devShell was 5ce66d5 nandi yesterday | 30 | container's directory beside it is untouched. None of them is copied |
| 31 | in from the laptop: a checkout's copy of the compiler's output is |
| 32 | not this container's, and overwriting the volume's with it is how an |
| 33 | incremental build stops being one. |
| 34 | |
| 35 | One build mode, not two. A `fast` mode (dart2js -O1, no icon |
| 36 | tree-shaking, no service worker) measured 52.5s against the release |
| 37 | build's 49.8s on the same source change, so what it bought was a |
| 38 | bigger bundle. `--no-wasm-dry-run` is the flag that did pay, and it |
| 39 | is in the one build there is. |
| A third target, and the seam that was already waiting for it f54ca45 nandi 2d ago | 40 | |
| 41 | Runs as a Sandbox on a real VM (kernel 6.x, not gVisor). The command |
| 42 | is the sandbox's own process, so it dies when the command exits. |
| 43 | |
| 44 | To look at what it built, ask for the serve action -- `[network] |
| 45 | ports` tunnels 8080 out, and the URL is printed once the sandbox is |
| 46 | scheduled: |
| 47 | |
| Six verbs, and the last of the nix 2e24e64 nandi 17h ago | 48 | modal run .modal/web/container.py \ |
| 49 | --command 'cd /devshell/frq-web && tools/build-web.sh serve 8080' |
| A third target, and the seam that was already waiting for it f54ca45 nandi 2d ago | 50 | |
| 51 | That blocks until you Ctrl-C it, and it bills until you do. |
| 52 | |
| 53 | ## It compiles; it does not start |
| 54 | |
| 55 | dart2js links the whole app -- `main.dart.js` is 3.6MB and has |
| 56 | `frq`, `irc`, `atproto` and `handshake` all through it, so the |
| 57 | `dart:io` imports under `flutter/src` are not the wall they look |
| 58 | like. What stops it is the first line of `main`: |
| 59 | `getApplicationDocumentsDirectory` is a platform channel, path_provider |
| 60 | ships no web implementation, and the channel with no handler behind it |
| 61 | throws `MissingPluginException`. `main` awaits that before installing |
| 62 | `frq.io.dart`, so no widget is ever built and the page stays white. |
| 63 | |
| 64 | That is the seam doing its job rather than a build problem. The fix is |
| 65 | a `frq.io.web` behind `frq.io` -- the browser's answer for a private |
| 66 | file is IndexedDB or localStorage, not a directory -- and `main` |
| 67 | choosing it the way `flutter/src/frq/main.cljd` chooses the Dart one |
| 68 | now. `frq.net.web` is the next one after it, for the same reason: a |
| 69 | browser has no raw socket, so the IRC connection wants a WebSocket. |
| 70 | |
| 71 | ## Serving it |
| 72 | |
| 73 | `serve.py` is the other half: a Function that mounts the same volume |
| 74 | and hands out `flutter/build/web`, so a rebuild in the Sandbox is |
| 75 | picked up by the next cold start with nothing redeployed. |
| 76 | |
| Six verbs, and the last of the nix 2e24e64 nandi 17h ago | 77 | modal deploy .modal/web/serve.py |
| A third target, and the seam that was already waiting for it f54ca45 nandi 2d ago | 78 | |
| 79 | `[network] ports` tunnels 8080 out of the Sandbox as well, for the |
| 80 | case where you want the build and the server to be one process. |