1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107 | ;; shadow-cljs builds for the __NAME__ client (ROOMS stack in CLJS — chat
;; (sueta) is the canon this scaffold mirrors; dev/docs/CLJS.md is the law).
;;
;; The shared rooms core — ardegazu.rooms.js and ardegazu.rooms.lib.* — is NOT
;; in this repo: it is ardegazu-rooms-kit's own sources, compiled off the
;; classpath (client/deps.edn adds node_modules/ardegazu-rooms-kit/src, and the
;; sha pin in package.json decides which sha sits there). shadow compiles them
;; exactly like your own namespaces, so they land in the modules below by the
;; same lowest-common-ancestor rule; nothing here has to name the kit.
;;
;; :app — the browser build. Unlike the kits (external ESM imports), an APP
;; bundles its npm deps: the libp2p/helia/@orbitdb family and the two ardegazu
;; kit dists are plain ESM in node_modules, which shadow's default :browser
;; bundling consumes.
;;
;; :optimizations :simple, NOT :advanced — the safe default for a fresh app, and
;; where board still is. Almost every line below the app boundary is interop
;; with untyped ESM (@orbitdb/core ships no type declarations, and helia/libp2p
;; types never reach the CLJS side); :simple removes the externs risk class
;; entirely. (game1's live stack is :advanced because its surface is small and
;; typed; this one is neither.)
;;
;; chat has since moved this same stack to :advanced and measured main.js
;; 236 KB gz -> 91 KB gz, on the observation that Closure never renames a
;; string-keyed property access, so the whole untyped-ESM surface is
;; renaming-IMMUNE. If you follow it, read chat's shadow-cljs.edn header and its
;; externs.js FIRST: what breaks under :advanced is not the libraries but our
;; own prototype installs (`(unchecked-set proto "handleFrames" …)` called by
;; dot-access), and the failure is a runtime TypeError at room boot, not a build
;; error.
;; NOTE :optimizations must live INSIDE :compiler-options — a top-level one is
;; silently ignored and the target default is used instead.
;;
;; Three modules keep the TS build's two dynamic boundaries REAL, and
;; scripts/build.mjs gates all three:
;; :main the lobby + i18n + config + room crypto — first paint
;; :room the whole p2p stack (libp2p v2, Helia, @orbitdb/core, the mailbox,
;; the room UI), loaded via shadow.lazy only when entering a room
;; :social attachSocial from `ardegazu-social-kit`, the ONLY module
;; referencing the full kit
;;
;; :asset-path is RELATIVE — one build must serve at https://__HOST__/ and
;; /ipfs/<cid>/ alike.
;;
;; :js-options :resolve maps the bare `events` specifier onto the npm package's
;; browser entry. @orbitdb/core (+ lru, abstract-level) import `node:events`,
;; and the browser needs the real EventEmitter, not an empty shim. The
;; `@libp2p/config` entry aliases away a package this app must never pull in —
;; see src/js/libp2p-config-shim.mjs. scripts/build.mjs greps the emitted
;; bundle for both, because a wrong resolution here fails at RUNTIME inside
;; OrbitDB, not at build time.
;;
;; Expect :infer-warning lines from the app and kit namespaces: every one is a
;; `(.method ^js (unchecked-get self "_field") …)` call, where the hint is lost
;; because it sits on a macro form (dev/docs/CLJS.md). They are inert under
;; :simple — nothing is renamed — and they are exactly the list you would have
;; to pin in an externs.js before switching to :advanced. Do not "fix" them by
;; moving the hints onto the calls: that suppresses inference silently.
;;
;; :parallel-build false — many namespaces plus a :simple build is exactly the
;; combination CLJS.md says the gensym normalizer cannot repair. Serial
;; compilation is the only fix; only cold builds pay the time.
;;
;; `npm test` out of the scaffold runs the two gates a day-zero app can run
;; honestly: `cljfmt check` and test/source-hygiene.test.mjs. It does NOT run a
;; :testlib build, because there are no black-box tests yet. When you add them,
;; add a :testlib build here (:target :esm, :optimizations :simple, :js-options
;; {:js-provider :import}) exporting the pure logic, and add it back to the
;; `test` script — chat's shadow-cljs.edn shows the shape, client/test/README.md
;; the steps.
{:deps {:aliases [:dev]}
;; BOTH dev ports serve public/ + dist-dev/ — the WATCH's own output tree,
;; never dist/. dist/ belongs to `ardz build` alone: it holds a RELEASE build
;; (goog.DEBUG=false → the PRODUCTION relay, and a service worker), and the
;; old `4173 {:roots ["dist"]}` arm served exactly that to a dev tab whenever
;; the watcher had not overwritten it yet (or the browser had cached it) —
;; two tabs of one dev session then sat on DIFFERENT relays and never met.
;; scripts/dev.mjs redirects the watch to dist-dev/assets via --config-merge;
;; smoke a release build with a standalone static server over dist/, never
;; through these ports.
;; :use-index-files true — without it shadow's undertow file handler gets NO
;; welcome files (it only sets them from this key), so a directory request
;; ("/", "/id/") falls through to the push-state handler, which 404s any
;; request whose Accept header lacks text/html (curl, fetch). With it, "/"
;; serves index.html from the first root that has one.
:dev-http {5173 {:roots ["public" "dist-dev"] :push-state/index "index.html" :use-index-files true}
4173 {:roots ["public" "dist-dev"] :push-state/index "index.html" :use-index-files true}}
:builds
{:app
{:target :browser
:output-dir "dist/assets"
:asset-path "./assets"
:compiler-options {:optimizations :simple
:source-map false
:parallel-build false}
:js-options {:resolve {"events" {:target :npm :require "events/events.js"}
;; an npm package this app must never pull in — see
;; the shim's own comment
"@libp2p/config" {:target :file
:file "src/js/libp2p-config-shim.mjs"}}}
:module-loader true
:modules
{:main {:init-fn __NAME__.main/init}
:room {:entries [__NAME__.room]
:depends-on #{:main}}
:social {:entries [__NAME__.social]
:depends-on #{:room}}}}}}
|