gofront 1.5.1 → 1.6.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.
Files changed (91) hide show
  1. package/README.md +174 -466
  2. package/package.json +16 -20
  3. package/src/backend/js/expressions.js +288 -128
  4. package/src/backend/js/index.js +138 -56
  5. package/src/backend/js/runtime.js +1055 -53
  6. package/src/backend/js/source-map.js +32 -0
  7. package/src/backend/js/statements.js +151 -83
  8. package/src/backend/js/stdlib/base64.js +12 -0
  9. package/src/backend/js/stdlib/binary.js +102 -0
  10. package/src/backend/js/stdlib/bits.js +15 -1
  11. package/src/backend/js/stdlib/builder.js +28 -9
  12. package/src/backend/js/stdlib/bytes.js +75 -18
  13. package/src/backend/js/stdlib/errors.js +22 -3
  14. package/src/backend/js/stdlib/fmt.js +95 -20
  15. package/src/backend/js/stdlib/hex.js +17 -0
  16. package/src/backend/js/stdlib/html.js +1 -1
  17. package/src/backend/js/stdlib/index.js +19 -0
  18. package/src/backend/js/stdlib/io.js +105 -3
  19. package/src/backend/js/stdlib/maps.js +1 -1
  20. package/src/backend/js/stdlib/math.js +13 -1
  21. package/src/backend/js/stdlib/path.js +20 -6
  22. package/src/backend/js/stdlib/rand.js +10 -5
  23. package/src/backend/js/stdlib/sha256.js +12 -0
  24. package/src/backend/js/stdlib/shared.js +17 -0
  25. package/src/backend/js/stdlib/slices.js +53 -27
  26. package/src/backend/js/stdlib/sort.js +1 -1
  27. package/src/backend/js/stdlib/strconv.js +52 -11
  28. package/src/backend/js/stdlib/strings.js +152 -16
  29. package/src/backend/js/stdlib/sync.js +13 -0
  30. package/src/backend/js/stdlib/testing.js +2 -2
  31. package/src/backend/js/stdlib/time.js +93 -22
  32. package/src/backend/js/stdlib/unicode.js +2 -2
  33. package/src/backend/js/templ.js +1 -1
  34. package/src/backend/wasm/boundary.js +1677 -58
  35. package/src/backend/wasm/const-fold.js +205 -0
  36. package/src/backend/wasm/emit-builtins.js +1230 -0
  37. package/src/backend/wasm/emit-exprs.js +2096 -0
  38. package/src/backend/wasm/emit-maps.js +1999 -0
  39. package/src/backend/wasm/emit-shared.js +519 -0
  40. package/src/backend/wasm/emit-stdlib.js +13 -0
  41. package/src/backend/wasm/emit-stmts.js +2466 -0
  42. package/src/backend/wasm/emit.js +656 -5031
  43. package/src/backend/wasm/encode.js +259 -85
  44. package/src/backend/wasm/glue.js +19 -5
  45. package/src/backend/wasm/index.js +2432 -86
  46. package/src/backend/wasm/monomorph.js +711 -0
  47. package/src/backend/wasm/optimize.js +236 -0
  48. package/src/backend/wasm/stdlib/bytes.js +5039 -0
  49. package/src/backend/wasm/stdlib/core.js +8903 -0
  50. package/src/backend/wasm/stdlib/index.js +40 -0
  51. package/src/backend/wasm/stdlib/io.js +2161 -0
  52. package/src/backend/wasm/stdlib/packages.js +2400 -0
  53. package/src/backend/wasm/stdlib/pure.js +720 -0
  54. package/src/backend/wasm/stdlib/time.js +1200 -0
  55. package/src/backend/wasm/types.js +312 -36
  56. package/src/backend/wasm/wat.js +49 -2
  57. package/src/cli-core.js +276 -73
  58. package/src/compiler.js +162 -25
  59. package/src/dts-parser.js +79 -126
  60. package/src/index.js +88 -121
  61. package/src/lexer.js +80 -29
  62. package/src/lower/boxing.js +4 -6
  63. package/src/lower/captures.js +12 -23
  64. package/src/lower/escape.js +4 -9
  65. package/src/lower/functions.js +277 -5
  66. package/src/lower/globals.js +7 -15
  67. package/src/lower/index.js +5 -2
  68. package/src/lower/ownership.js +35 -19
  69. package/src/lower/range.js +2 -5
  70. package/src/lower/walk.js +25 -0
  71. package/src/minifier.js +20 -68
  72. package/src/parser/declarations.js +3 -6
  73. package/src/parser/expressions.js +3 -8
  74. package/src/parser/index.js +18 -3
  75. package/src/parser/statements.js +25 -38
  76. package/src/parser/types.js +1 -2
  77. package/src/resolver.js +3 -27
  78. package/src/templ-lexer.js +29 -63
  79. package/src/templ-parser.js +22 -45
  80. package/src/test-runner.js +20 -0
  81. package/src/typechecker/assignability.js +123 -14
  82. package/src/typechecker/expressions.js +244 -4
  83. package/src/typechecker/index.js +27 -1
  84. package/src/typechecker/resolve.js +71 -12
  85. package/src/typechecker/statements.js +0 -7
  86. package/src/typechecker/stdlib/core.js +292 -103
  87. package/src/typechecker/stdlib/extended.js +471 -4
  88. package/src/typechecker/stdlib/shared.js +53 -0
  89. package/src/typechecker/stdlib.js +2 -0
  90. package/src/typechecker/types.js +545 -8
  91. package/src/vendor.js +2 -2
package/README.md CHANGED
@@ -1,16 +1,67 @@
1
+ # GoFront
2
+ [![npm version](https://img.shields.io/npm/v/gofront.svg)](https://www.npmjs.com/package/gofront) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
3
+
4
+
5
+ > Go syntax and type safety, compiling to a seamless hybrid of JavaScript (ES modules) and WebAssembly (WasmGC).
6
+
1
7
  ## About
2
8
 
3
9
  Go for the backend: simple, type-safe, no nonsense. JavaScript for the frontend: runs
4
10
  everywhere, no setup. The problem is JavaScript's loose typing — and TypeScript never
5
11
  quite felt like home either.
6
12
 
7
- So I built GoFront. Go syntax and type safety, compiling to plain ES modules and
8
- WebAssembly (WasmGC). One language front and back, no runtime, no framework, no tsconfig.json.
13
+ So I built GoFront. Go syntax and type safety, compiling to a **seamless hybrid of
14
+ JavaScript (ES modules) and WebAssembly (WasmGC)**. One language across the entire
15
+ stack, no runtime, no framework, no tsconfig.json.
9
16
 
10
- With built-in support for declarative DOM rendering via the [`gom`](https://www.gomponents.com) standard library, JSX-like [`.templ`](https://templ.guide/) files, and seamless integration with external JavaScript libraries through TypeScript definition files (`.d.ts`), GoFront is designed specifically as a frontend development target. Build complex, reactive user interfaces entirely in Go.
17
+ ### Why Hybrid?
18
+
19
+ Traditional web architectures force a painful choice:
20
+ - **Pure JavaScript:** Excellent DOM ergonomics and dynamic UI, but plagued by IEEE-754 float quirks, JIT deoptimizations, and garbage-collection micro-stutters during heavy compute.
21
+ - **Pure WebAssembly:** Predictable, high-performance execution for tight loops and math, but manipulating the DOM across host calls is notoriously slow, awkward, and requires heavy glue-code runtimes.
22
+
23
+ **GoFront gives you the best of both worlds in a single codebase:**
24
+ - **UI and DOM stay in JavaScript:** `.templ`, `gom`, event listeners, and browser APIs compile to native ES modules with zero-cost DOM access.
25
+ - **Compute runs in WebAssembly:** Simulation, physics, collision detection, and tight math loops compile to WasmGC with true integers, real `float32`, and near-zero memory churn.
26
+ - **The compiler owns the boundary:** It sees both sides and generates all the glue automatically. You write standard Go imports (`import "./physics"`) and the compiler handles the rest.
11
27
 
12
28
  Probably not useful. Definitely fun to build.
13
29
 
30
+ ```go
31
+ // physics/sim.go — compiled to WebAssembly (WasmGC)
32
+ //gofront:target wasm
33
+ package physics
34
+
35
+ type Body struct {
36
+ X, Y, VX, VY float32
37
+ }
38
+
39
+ func Step(b *Body, dt float32) {
40
+ b.X += b.VX * dt
41
+ b.Y += b.VY * dt
42
+ }
43
+ ```
44
+
45
+ ```go
46
+ // main.go — compiled to clean JavaScript (ES module)
47
+ //gofront:target js
48
+ package main
49
+
50
+ import (
51
+ "./physics"
52
+ "gom"
53
+ )
54
+
55
+ var body = physics.Body{X: 0, Y: 0, VX: 10, VY: 5}
56
+
57
+ func onTick() {
58
+ physics.Step(&body, 0.016) // calls directly into WasmGC with zero manual glue code
59
+ render()
60
+ }
61
+ ```
62
+
63
+ With built-in support for declarative DOM rendering via the [`gom`](https://www.gomponents.com) standard library, JSX-like [`.templ`](https://templ.guide/) files, and seamless integration with external JavaScript libraries through TypeScript definition files (`.d.ts`), GoFront is designed specifically as a frontend development target. Build complex, reactive user interfaces entirely in Go.
64
+
14
65
  ```go
15
66
  package main
16
67
 
@@ -35,229 +86,34 @@ func main() {
35
86
  }
36
87
  ```
37
88
 
38
- Compiles to clean, readable JavaScript — no runtime, no framework.
89
+ Compiles to clean, readable JavaScript and WebAssembly — no runtime, no framework.
39
90
 
40
91
  GoFront source files use the `.go` extension so editors automatically apply Go syntax
41
92
  highlighting, bracket matching, and indentation rules without any extra configuration.
42
93
 
43
94
  ---
44
95
 
45
- ## Install
96
+ ## Install & Quick Start
46
97
 
47
98
  ```sh
48
99
  npm install -g gofront
100
+ mkdir my-app && cd my-app
101
+ gofront init
102
+ gofront dev
49
103
  ```
50
104
 
51
- Requires Node.js 20+.
52
-
53
- ---
54
-
55
- ## How it works
56
-
57
- GoFront is a five-stage compiler written in pure Node.js (no dependencies). Every stage
58
- operates on the AST (abstract syntax tree):
59
-
60
- ```
61
- source text (.go files)
62
- → Lexer tokenize + Go-style semicolon insertion
63
- → Parser recursive-descent → AST
64
- → Type Checker annotate AST with types + collect errors
65
- → Lowering ownership/clone elision, escape analysis, capture analysis
66
- → Code Gen AST → JavaScript ES module or WebAssembly GC module (.wasm)
67
-
68
- source text (.templ files)
69
- → TemplLexer dual-mode: Go mode for declarations, HTML mode inside templ bodies
70
- → TemplParser extends Parser; produces TemplDecl AST nodes with TemplNode children
71
- → Type Checker registers each templ component as func(...) gom.Node
72
- → Code Gen TemplDecl → direct DOM calls (createElement / setAttribute / appendChild)
73
- ```
74
-
75
- ### 1. Lexer (`src/lexer.js`)
76
-
77
- Tokenises the source into a stream of tokens. Implements Go's semicolon insertion rules:
78
- a semicolon is automatically inserted after a line's final token if that token is an
79
- identifier, literal, `)`, `]`, `}`, or certain keywords (`return`, `break`, `continue`,
80
- `fallthrough`). This is why Go doesn't need explicit semicolons — and neither does
81
- GoFront.
82
-
83
- ### 2. Parser (`src/parser/`)
84
-
85
- A hand-written recursive-descent parser. No parser generators, no grammar files — just
86
- straightforward top-down parsing. Produces an AST where every node is a plain JS object
87
- with a `kind` field (`"FuncDecl"`, `"IfStmt"`, `"BinaryExpr"`, etc.).
88
-
89
- Operator precedence is handled via a Pratt-style expression parser with numeric
90
- precedence levels.
91
-
92
- ### 3. Type Checker (`src/typechecker/`)
93
-
94
- Three-pass type checker operating on the AST:
95
-
96
- 1. **Pass 1 — collect types**: register all `type` declarations (structs, interfaces,
97
- named types) so they can be referenced before definition.
98
- 2. **Pass 2 — collect functions & vars**: register function signatures and package-level
99
- variables. Resolve embedded struct fields and promote methods.
100
- 3. **Pass 3 — check bodies**: walk every function body, infer expression types, verify
101
- assignments and call arguments, and report errors with source location.
102
-
103
- Types are plain JS objects (`{ kind: "basic", name: "int" }`, `{ kind: "slice",
104
- elem: ... }`, etc.). The special `any` type acts as a recovery/escape hatch — any
105
- operation on it is silently permitted, preventing cascading errors.
106
-
107
- ### 4. Code Generator (`src/backend/js/`)
108
-
109
- Walks the typed AST and emits clean, readable JavaScript. No intermediate representation
110
- — the codegen writes directly to an output buffer with indentation tracking.
111
-
112
- Runtime helpers (`__len`, `__append`, `__s`, `__sprintf`, `__equal`, `__cmul`, `__cdiv`,
113
- `__error`, `__errorIs`, `__timeFmt`, `__timeParse`, `__pathClean`, `__sortSlice`,
114
- `__sclone`, `__ifv`, `__ifp`) are tree-shaken: only emitted when actually used. Optional
115
- inline source maps are supported via VLQ-encoded mappings.
116
-
117
- ### 5. Lowering & WASM backend (`src/lower/`, `src/backend/wasm/`) — experimental
118
-
119
- `src/lower/` runs analyses shared by both backends (ownership/clone elision, address-taken
120
- boxing, range shape, named returns/`defer`, embedded-method stubs, closure captures, pointer
121
- escape) and stores the results in side tables keyed by AST node.
122
-
123
- A package that starts with `//gofront:target wasm` (before the `package` clause) is compiled
124
- by `src/backend/wasm/` to a WebAssembly GC module instead of JavaScript.
125
- `//gofront:target both` marks a package that must compile under either backend: it may only
126
- import other `both` packages, may not use `gom`, may not mutate package-level variables, and
127
- its JS output uses strict Go numeric semantics (sized-integer wrapping, `float32` rounding,
128
- integer divide-by-zero panics). Running WASM output requires a WasmGC-capable runtime
129
- (Node ≥ 22 or a current Chrome/Firefox/Safari).
130
-
131
- **Hybrid builds.** Every `wasm`/`both` package a build reaches is linked into a single
132
- `app.wasm`, written next to `app.js` by `gofront build`, `gofront dev` and `-o`. The JS
133
- bundle contains a generated facade in place of the wasm packages: exported functions,
134
- methods, literal constants and structs are callable from JS code as if they were compiled
135
- to JS. Primitives convert directly, strings and `any` pass through, `both` struct values
136
- are copied into their JS class, `wasm` structs become opaque handle classes with stable
137
- identity, slices/arrays are copied element-wise (TypedArrays accepted) and func values are
138
- wrapped in both directions. Maps, `error`, non-empty interfaces and pointers to non-structs
139
- are not yet supported at the boundary and are rejected at compile time. The loader fetches
140
- `app.wasm` relative to the page (override with `globalThis.__GOFRONT_WASM_URL`, or
141
- pre-supply bytes via `globalThis.__GOFRONT_WASM_BYTES`). `--emit-wat` additionally writes
142
- a textual `app.wat`.
143
-
144
- ```
145
- GoFront Source Code
146
- ┌───────────────────────┬───────────────────────┐
147
- │ //gofront:target js │ //gofront:target wasm │
148
- │ UI, DOM, WebGL, Game │ Collision, Raycasting │
149
- └───────────┬───────────┴───────────┬───────────┘
150
- │ │
151
- │ GoFront Compiler │
152
- ▼ ▼
153
- ┌───────────┐ ┌───────────┐
154
- │ app.js │ ◄───────► │ app.wasm │
155
- └─────┬─────┘ Boundary └─────┬─────┘
156
- │ Facade │
157
- ▼ ▼
158
- DOM & Browser Tight Loops & Math
159
- Dynamic & Ergonomic Predictable & Fast
160
- ```
161
-
162
- `gofront test` runs the tests of a `wasm` package inside the linked module (`*testing.T`
163
- stays a JS object) and runs the tests of a `both` package twice — once per backend,
164
- reported as `pkg [js]` and `pkg [wasm]` — so both must agree.
165
-
166
- **Performance.** Splitting an app into high-level JavaScript orchestration and low-level WebAssembly compute delivers the best of both worlds. On a real-world Möller–Trumbore raycast benchmark (131,072 triangles, 100,000 rays; `npm run bench:raycast`):
167
-
168
- | Target | Throughput | Allocation | Engine Stability |
169
- | :--- | :--- | :--- | :--- |
170
- | **Pure JS** | `26,720 rays/s` ▰▰▰▰▰▰▰▰▱▱ | `6.20 B / ray` | Subject to periodic V8 young-gen GC pauses |
171
- | **Hybrid WASM** | `31,293 rays/s` ▰▰▰▰▰▰▰▰▰▰ | `0.86 B / ray` | **Near-zero alloc (7.2× less)**, smooth 60 FPS |
172
-
173
- - **+17.1% higher throughput:** Direct WasmGC typed arrays, local-cached scratch globals, and hardware-trapped nil dereferences outperform JIT compiled JS.
174
- - **86% memory churn reduction:** Dropping allocations from 6.2 B/ray to 0.86 B/ray prevents garbage collection pauses from causing micro-stutter in 60 FPS loops.
175
- - **Minimal boundary overhead:** The boundary trampoline consumes only ~0.5% of total runtime, ensuring batch computations cross between JS and WASM with virtually zero penalty.
176
-
177
- ---
178
-
179
- ## Go → JavaScript mapping
105
+ Then open `http://localhost:3000` — the page live-reloads on every save.
180
106
 
181
- Every Go construct compiles to a specific JavaScript pattern. The output is designed to be
182
- readable and debuggable — no name mangling, no opaque wrappers.
183
-
184
- ### Data structures
185
-
186
- | Go | JavaScript | Notes |
187
- |---|---|---|
188
- | `struct` | ES6 `class` | Positional constructor with zero-value defaults: `new Point(1, 2)`; a generated `__clone()` gives struct values Go copy semantics on assignment, call, return, `append`, and `range` |
189
- | Methods | Class instance methods | Receiver is `this` (typed `*T` for pointer receivers) |
190
- | Embedded structs | Flattened fields + delegation stubs | `Greet(...a) { return Base.prototype.Greet.call(this, ...a); }` |
191
- | `[]T` (slice) | `Array` | `append` → `__append`, `len` → `__len` |
192
- | `[]float32`, `[]int8`…`[]int32`, `[]uint8`/`[]byte`…`[]uint32`, `[]rune` | TypedArray (`Float32Array`, `Int32Array`, `Uint8Array`, …) | `[]float64` and `[]int` stay plain `Array`; slicing → `.subarray` |
193
- | `map[K]V` | Plain object `{}` | Key access via `[]`, iteration via `Object.entries()` |
194
- | `nil` | `null` | |
195
- | `error` | `__error` object | `error("msg")` → `__error("msg")`, `.Error()` → real method call. `toString()` for string context compat |
196
- | Pointers (`*T`) | `{ value: T }` | `new(T)` allocates a boxed zero value |
197
-
198
- ### Functions
199
-
200
- | Go | JavaScript |
201
- |---|---|
202
- | `func f(a int) int` | `function f(a)` |
203
- | Multiple returns `return a, b` | `return [a, b]` — destructured at call site: `let [a, b] = f()` |
204
- | Named returns | Variables pre-declared; bare `return` returns them |
205
- | Variadic `func f(xs ...int)` | `function f(...xs)` |
206
- | `func` literal (closure) | Arrow or function expression |
207
- | `async func` / `await` | `async function` / `await` |
208
- | `init()` | Emitted as `(function() { ... })()` immediately-invoked |
209
- | `defer` | `try { ... } finally { deferred() }` |
210
-
211
- ### Control flow
212
-
213
- | Go | JavaScript |
214
- |---|---|
215
- | `for init; cond; post {}` | `for (init; cond; post) {}` |
216
- | `for cond {}` | `while (cond) {}` |
217
- | `for {}` | `while (true) {}` |
218
- | `for i, v := range slice` | `for (const [i, v] of arr.entries())` |
219
- | `for k, v := range map` | `for (const [k, v] of Object.entries(m))` |
220
- | `for i, r := range str` | `for (const [i, r] of Array.from(s, (c, i) => [i, c.codePointAt(0)]))` — `r` is a rune integer |
221
- | `for i := range n` | `for (let i = 0; i < n; i++)` |
222
- | `switch` / `fallthrough` | `switch` / case fall-through |
223
- | `switch v := x.(type)` | `if/else if` with `typeof` / `instanceof` checks |
224
- | `panic(msg)` | `throw new Error(msg)` |
225
- | `recover()` | Captured in `defer` via `try/catch` |
226
-
227
- ### Builtins
228
-
229
- | Go | JavaScript |
230
- |---|---|
231
- | `len(x)` | `__len(x)` (tree-shaken helper) or `Object.keys(x).length` for maps |
232
- | `cap(x)` | `x.length` |
233
- | `append(s, elems...)` | `[...s, ...elems]` |
234
- | `copy(dst, src)` | Inline splice helper |
235
- | `make([]T, n)` | `new Array(n).fill(zero)` |
236
- | `make(map[K]V)` | `{}` |
237
- | `delete(m, k)` | `delete m[k]` |
238
- | `new(T)` | `{ value: zeroOf(T) }` |
239
- | `min` / `max` | `Math.min` / `Math.max` |
240
- | `clear(x)` | `.length = 0` (slice) or delete-loop (map) |
241
- | `print` / `println` | `console.log` |
242
- | `complex(r, i)` | `{ re: r, im: i }` |
243
- | `real(z)` / `imag(z)` | `z.re` / `z.im` |
244
- | `fmt.Sprintf` | `__sprintf` tree-shaken helper |
245
-
246
- ### Type system at runtime
247
-
248
- All type checking happens at compile time. At runtime, types are erased — there are no
249
- type tags, no reflection, no runtime overhead. Sized integers (`int8`–`int64`,
250
- `uint8`–`uint64`) are all `number` at runtime. `float32` is `number`. Generic type
251
- parameters are erased completely — `func Map[T, U any](...)` compiles to `function Map(...)`.
252
- The type checker enforces correctness; JavaScript doesn't need to know.
107
+ Requires Node.js 20+ (WasmGC requires Node ≥ 22 or a modern browser).
253
108
 
254
109
  ---
255
110
 
256
111
  ## Examples
257
112
 
258
- There are five example apps. Four implement the same todo app to show different aspects
113
+ There are six example apps. Four implement the same todo app to show different aspects
259
114
  of GoFront; the fifth is a WebGL2 3D showcase demonstrating typed graphics APIs and
260
- zero-allocation rendering.
115
+ zero-allocation rendering; the sixth is a hybrid JS + WasmGC particle simulation
116
+ communicating across the boundary via zero-copy `gofront/shared` buffers.
261
117
 
262
118
  ### Simple (vanilla DOM)
263
119
 
@@ -378,6 +234,8 @@ Key syntax features:
378
234
  - `for _, v := range slice { }` — loop rendering inside template bodies
379
235
  - `switch expr { case v: ... default: ... }` — switch rendering inside template bodies
380
236
  - `@templ.Raw(htmlStr)` — inject raw/trusted HTML (uses `insertAdjacentHTML`)
237
+ - `ref="name"` — capture the element at mount time into an optional `refs map[string]any`
238
+ passed as `gom.Mount("#app", AppView(), refs)`; no `querySelector` needed afterwards
381
239
  - Components are called like regular functions (`AppView()`, `TodoItem(t)`) and return
382
240
  a `gom.Node`-compatible object, so `gom.Mount("#app", AppView())` works directly.
383
241
 
@@ -457,291 +315,143 @@ example/webgl/
457
315
  index.html ← canvas element
458
316
  ```
459
317
 
318
+ ### Hybrid (JS + WasmGC particle simulation)
319
+
320
+ A particle simulation stepped inside a `wasm` package (`sim`) and rendered to a 2D canvas
321
+ from JavaScript through a zero-copy `gofront/shared` `Float32` buffer, paired with `.templ`
322
+ UI controls.
323
+
324
+ ```
325
+ example/hybrid/
326
+ src/
327
+ controls.templ ← UI controls (step, reset, gravity toggle, particle count)
328
+ main.go ← canvas rendering loop reading sim.Positions + DOM event wiring
329
+ sim/
330
+ sim.go ← //gofront:target wasm · particle physics & shared.NewFloat32 buffer
331
+ app.js ← generated JS bundle + boundary facade
332
+ app.wasm ← generated WasmGC module
333
+ index.html ← canvas + controls shell
334
+ ```
335
+
460
336
  ### Build and run
461
337
 
462
338
  ```sh
463
- npm run build:simple # → example/simple/app.js
464
- npm run build:reactive # → example/reactive/app.js
465
- npm run build:gom # → example/gom/app.js
466
- npm run build:templ # → example/templ/app.js
467
- npm run build:webgl # → example/webgl/app.js
339
+ npm run build # compile all example applications
468
340
  # open the respective index.html in a browser
469
341
  ```
470
342
 
471
343
  ---
472
344
 
473
- ## CLI
345
+ ## The Hybrid Architecture: JS + WasmGC
474
346
 
475
- ```
476
- gofront dev [dir] watch + compile + asset sync + live reload (hybrid projects emit app.wasm; default port 3000)
477
- gofront dev [dir] --port 8080 use a custom port
478
- gofront build [dir] clean + compile + minify + vendor → production output (hybrid projects emit app.wasm)
479
- gofront build [dir] --pwa also generate offline service worker (sw.js) + precache manifest
480
- gofront build [dir] --source-map include inline source maps in the release bundle
481
- gofront build [dir] --no-minify skip minification
482
- gofront build [dir] --no-mangle minify but keep original identifiers
483
- gofront build [dir] --emit-wat also write app.wat next to app.wasm (hybrid projects)
484
- gofront prep [dir] [--minify] run asset copying + vendor bundling only (alias: gofront vendor)
485
- gofront check <dir> type-check a single package
486
- gofront check <dir>/... type-check every package under <dir> (Go-style `./...`)
487
- gofront test <dir> [--dom] run tests for a single package
488
- gofront test <dir>/... [--dom] [-v] [-run <regex>] run tests recursively
489
- gofront <file.go> compile single file → stdout
490
- gofront <dir> compile all *.go in directory → stdout
491
- gofront <input> -o out.js write output to file (prints elapsed compile time; hybrid projects also write app.wasm)
492
- gofront <input> -o out.js --emit-wat also write app.wat
493
- gofront <input> -o out.js --copy-assets compile + copy static assets
494
- gofront <input> --check type-check only (single file / directory)
495
- gofront <input> --watch watch for changes and recompile
496
- gofront <input> -o out.js --serve watch + serve with live reload (legacy; prefer gofront dev)
497
- gofront <input> --source-map append inline source map
498
- gofront <input> --minify minify output (built-in minifier)
499
- gofront <input> --minify --mangle minify and rename local identifiers
500
- gofront <file.go> --ast dump AST (debug)
501
- gofront <file.go> --tokens dump tokens (debug)
502
- gofront init [dir] scaffold a new project
503
- gofront --version / -v print version
504
- gofront --help / -h print this help
505
- ```
506
- ### Project configuration
507
-
508
- `gofront dev`, `build`, `prep`, `check` and `test` read optional settings from a `gofront.json`
509
- file or a `"gofront": { … }` object in `package.json`:
510
-
511
- | Key | Default | Description |
512
- |---|---|---|
513
- | `src` | `app/src`, `src` or `main.go` | Package directory (or single file) to compile |
514
- | `serveDir` | `app`, `.` or `public` (first with `index.html`) | Directory served by `dev` and mirrored into the build |
515
- | `outDir` | `public` | Release output directory for `build` |
516
- | `output` | `app/app.js` | Dev-mode compiled bundle path |
517
- | `port` | `3000` | Dev server port |
518
- | `assetExtensions` | `[]` | Extra file extensions (e.g. `[".bmesh", ".mat"]`) copied from `serveDir` into `outDir` on `build`, in addition to the built-in web asset list (html, css, js, json, images, fonts, audio, video, wasm) |
519
- | `vendor` | `app/vendor.js` | Vendor bundle written by `prep`/`build` from `package.json` `dependencies`. Either a destination path string or `{ "dest": string \| string[], "packages": string[], "minify": boolean, "globals": { "<pkg>": string \| string[] } }` |
520
- | `assetCopy` | `[]` | Static files copied by `prep`/`build`: `[{ "source": "node_modules/x/font.woff2", "dest": "app/fonts/font.woff2" }]` |
521
-
522
- All settings live in this one place — top-level `"vendor"` / `"assetCopy"` keys in `package.json` are not read.
523
- ---
524
-
525
- ## Multi-file packages
526
-
527
- All `.go` files in a directory share the same namespace and are compiled as one unit.
528
- The compiler (`src/compiler.js`) orchestrates this:
529
-
530
- 1. Parse each `.go` file in the directory into a separate AST.
531
- 2. Resolve imports: `js:` d.ts files, npm packages (via `node_modules/` and `@types/`),
532
- and local GoFront sub-packages (`import "./subpkg"`) which are compiled recursively.
533
- 3. Run the type checker across all ASTs as a single unit — types, functions, and
534
- variables declared in one file are visible in all other files of the same package.
535
- 4. Generate code for each AST and concatenate. Sub-package code is inlined as a preamble.
347
+ GoFront apps can run **partly as JavaScript and partly as WebAssembly (WasmGC)**, chosen per package:
536
348
 
537
349
  ```
538
- myapp/
539
- types.go ← type Point struct { X, Y int }
540
- utils.go ← func distance(a, b Point) float64 { ... }
541
- main.go ← func main() { ... }
350
+ GoFront Source Code
351
+ ┌───────────────────────┬───────────────────────┐
352
+ │ //gofront:target js │ //gofront:target wasm │
353
+ │ UI, DOM, WebGL, Game │ Collision, Raycasting │
354
+ └───────────┬───────────┴───────────┬───────────┘
355
+ │ │
356
+ │ GoFront Compiler │
357
+ ▼ ▼
358
+ ┌───────────┐ ┌───────────┐
359
+ │ app.js │ ◄───────► │ app.wasm │
360
+ └─────┬─────┘ Boundary └─────┬─────┘
361
+ │ Facade │
362
+ ▼ ▼
363
+ DOM & Browser Tight Loops & Math
364
+ Dynamic & Ergonomic Predictable & Fast
542
365
  ```
543
366
 
544
- ```sh
545
- gofront myapp -o myapp/bundle.js
546
- ```
367
+ ### Package Targets
547
368
 
548
- Cross-package imports are supported via relative paths:
369
+ Each package declares its target at the top of any of its source files (before the `package` clause):
549
370
 
550
371
  ```go
551
- import "./mathpkg"
552
-
553
- func main() {
554
- x := math.Add(1, 2) // math package inlined into the bundle
555
- }
372
+ //gofront:target wasm // compiled to WebAssembly only
373
+ //gofront:target both // compiled to both JS and WASM; each side uses its own copy
374
+ package physics // no directive = js (default)
556
375
  ```
557
376
 
558
- ---
559
-
560
- ## Type checking
377
+ | Target | Compiled to | May import | Best for |
378
+ |---|---|---|---|
379
+ | `js` (default) | JavaScript ES module | anything | DOM manipulation, UI components (`gom`, `.templ`), browser APIs, `async`/`await` |
380
+ | `wasm` | WebAssembly GC module | `wasm`, `both`, stdlib (WASM subset) | Physics, spatial indexing, raycasting, simulation, tight numeric loops |
381
+ | `both` | JS *and* WASM | `both`, stdlib (WASM subset) | Shared math/utility libraries (e.g. `mathx`, `vec3`). Strict Go numeric semantics; no package-level mutable state |
561
382
 
562
- GoFront performs static type checking before emitting any code, accurately tracking source
563
- locations in error messages across multiple files:
383
+ - **Zero Boundary Overhead for Shared Types:** `both` packages never create a boundary overhead — JS callers use the JS copy and WASM callers use the WASM copy.
384
+ - **Strict Go Numeric Semantics:** Code compiled to `wasm` (and JS emitted for `both` packages) follows strict Go numeric rules: sized-integer wrapping, true `float32` rounding, and integer divide-by-zero panics.
564
385
 
565
- ```go
566
- func greet(name string) {
567
- console.log("Hello, " + name)
568
- }
386
+ **Choosing a target for a package** — the questions to ask, in order:
569
387
 
570
- greet(42)
571
- // → Type error in src/main.go at line 5:7: cannot use int as string
572
- // 5 | greet(42)
573
- // ^
574
- ```
388
+ 1. **Does it touch the browser** (DOM, WebGPU, `fetch`, PeerJS, `gom`, `.templ`)? → `js`. There is no other option.
389
+ 2. **Is it a leaf that only imports `math`/`mathx`-style helpers and spends its time in numeric loops?** → candidate for `wasm`. Profile first; moving code that is not hot buys nothing.
390
+ 3. **What crosses the boundary per frame?** Scalars, handles and `shared` buffers are free; a slice result (`[]Mat4`, `[]float32`) is copied out on every call. If the package's main output is a slice the renderer reads each frame, it will measure at parity with JS — write that output into a `gofront/shared` buffer instead.
391
+ 4. **Do two `wasm` packages (or JS and WASM) both need the same types?** → make that package `both`, so neither side pays a boundary call for it.
392
+ 5. **Can't decide?** Compile both ways and measure: `gofront build --js-only` gives the all-JS build, or pin per-package targets in `gofront.json` (`"targets": { "engine/physics": "wasm" }`) without editing source. Ship with `--release` so `wasm-opt` shrinks `app.wasm` (~21–23%).
575
393
 
576
- External TypeScript type definitions are supported via `js:` imports:
394
+ ### Seamless Compile-Time Boundary
577
395
 
578
- ```go
579
- import "js:./dom.d.ts"
580
- ```
581
-
582
- npm package types are resolved automatically from `node_modules/` and `@types/`.
583
- The resolver (`src/resolver.js`) walks up the directory tree to find `node_modules`,
584
- checks `package.json` `"types"` / `"typings"` fields, falls back to `index.d.ts`,
585
- then tries `@types/`. The `.d.ts` parser (`src/dts-parser.js`) extracts type
586
- signatures into GoFront's internal type representation.
587
-
588
- ---
396
+ Every `wasm` and `both` package an application imports is automatically linked into a single `app.wasm` module, emitted right alongside `app.js` by `gofront build`, `gofront dev`, and `-o`.
589
397
 
590
- ## Language features
398
+ The compiler analyzes both sides of the boundary and synthesizes a facade into `app.js`:
399
+ - **Functions & Methods:** WASM functions, methods, and literal constants can be invoked directly from JS code as if they were native JS functions.
400
+ - **Primitives & Strings:** Numbers, booleans, strings, and `any` pass through or convert directly across the boundary.
401
+ - **Structs & Memory:**
402
+ - `both` struct values cross by copy into their matching JS classes (with write-back for `*T` parameters).
403
+ - `wasm` structs become opaque handle classes with stable identity. Reading aggregate fields through handles (`b.Pos.X`, `b.Tags[0]`) provides live views into WASM memory.
404
+ - **Slices & Arrays:** Slices and arrays copy element-wise in both directions; TypedArrays (`Float32Array`, `Int32Array`, etc.) are natively accepted.
405
+ - **Closures & Interfaces:** Function values, callbacks, and named non-empty interfaces cross the boundary transparently.
406
+ - **Shared Linear-Memory Buffers:** `gofront/shared` (`shared.NewFloat32`, etc.) exposes zero-copy TypedArray views over linear memory for bulk per-frame data.
407
+ - **WASM Loader:** The generated JS bundle automatically fetches `app.wasm` relative to `app.js` (`import.meta.url`, customizable via `globalThis.__GOFRONT_WASM_URL` or `globalThis.__GOFRONT_WASM_BYTES`).
408
+ - **WAT Inspection:** Passing `--emit-wat` writes human-readable `app.wat` alongside the binary module.
409
+ - **Binaryen Optimization:** Passing `--release` or `--wasm-opt` optimizes `app.wasm` via Binaryen (`-O3` + GUFA), achieving ~23% smaller binaries and generating `app.wasm.map` source maps.
591
410
 
592
- ### Core language
411
+ ### Real-World Benchmark: 3D Raycasting
593
412
 
594
- | Feature | Status |
595
- |---|---|
596
- | Variables (`var`, `:=` short re-declaration) | ✓ |
597
- | Constants (`const`, `iota`, untyped constants) | ✓ |
598
- | Functions, multiple returns, named returns, variadic | ✓ |
599
- | `init()` functions | ✓ |
600
- | Closures / function literals | ✓ |
601
- | `async func` / `await` expressions | ✓ |
602
- | `defer`, `panic()` / `recover()` | ✓ |
603
- | `print` / `println` builtins | ✓ — compile to `console.log` |
604
-
605
- ### Types & data structures
606
-
607
- | Feature | Status |
608
- |---|---|
609
- | Structs + methods (value & pointer receivers) | ✓ |
610
- | Embedded structs (flattened fields + promoted methods) | ✓ |
611
- | Anonymous struct types | ✓ — compile to plain JS objects |
612
- | Interfaces (with embedding) | ✓ |
613
- | Slices (`append`, `len`, `make`) | ✓ |
614
- | Maps (`make`, `delete`, comma-ok) | ✓ |
615
- | Arrays with compile-time enforcement | ✓ — reject `append`, bounds checking, size matching, `[...]T` inference, compile-time `len()` |
616
- | Slice → array conversion (`[N]T(slice)`) | ✓ — Go 1.20 |
617
- | Pointers (`&x`, `*p`, `new(T)`) | ✓ — scalar locals boxed as `{ value: T }` for shared mutation |
618
- | `error` type | ✓ — interface `{ Error() string }`; custom error types, `errors.Is`/`Unwrap`, `%w` wrapping |
619
- | Complex numbers (`complex64`, `complex128`, `3i`) | ✓ — `complex()`, `real()`, `imag()` builtins; `__cmul`/`__cdiv` helpers |
620
- | Type definitions, type aliases (`type A = B`) | ✓ |
621
- | Methods on named non-struct types | ✓ — `type T func(...)` or `type T []E` with methods; emitted as ES6 wrapper classes; satisfies interfaces |
622
- | Type conversions, type assertions (plain & comma-ok) | ✓ |
623
- | Type switch (`switch v := x.(type)`) | ✓ — compiles to `if/else if` with `typeof` / `instanceof` |
624
- | Sized integers (`int8`–`int64`, `uint8`–`uint64`, `float32`) | ✓ — mapped to `number` at runtime |
625
- | Generics (`func F[T any]`, `type S[T any] struct`) | ✓ — type erasure to JS; generic functions, structs, constraints (`any`, `comparable`, interfaces, unions), type inference |
626
- | Struct field tags | ✓ — parsed and ignored (no reflection) |
627
- | Struct and array equality (`a == b`) | ✓ — deep comparison via `__equal` helper |
628
-
629
- ### Control flow
630
-
631
- | Feature | Status |
632
- |---|---|
633
- | `if` / `else if` / `else` (with init statement) | ✓ |
634
- | `for` (C-style, condition-only, infinite, `range`) | ✓ |
635
- | `range` over slice, map, string, integer | ✓ |
636
- | Range over iterator functions (Go 1.23) | ✓ — `func(yield func(K, V) bool)` protocol; break/continue/return propagation |
637
- | `switch` / `fallthrough` (with init statement) | ✓ |
638
- | `break` / `continue` / labeled variants | ✓ |
639
- | Terminating statement analysis | ✓ — missing `return` in non-void functions is a type error |
413
+ Splitting an app into high-level JavaScript orchestration and low-level WebAssembly compute delivers the best of both worlds. On a real-world Möller–Trumbore raycast benchmark (131,072 triangles, 100,000 rays; run via `npm run bench`):
640
414
 
641
- ### Expressions & literals
415
+ | Target | Throughput | Allocation | Engine Stability |
416
+ | :--- | :--- | :--- | :--- |
417
+ | **Pure JS** | `26,720 rays/s` ▰▰▰▰▰▰▰▰▱▱ | `6.20 B / ray` | Subject to periodic V8 young-gen GC pauses |
418
+ | **Hybrid WASM** | `31,293 rays/s` ▰▰▰▰▰▰▰▰▰▰ | `0.86 B / ray` | **Near-zero alloc (7.2× less)**, smooth 60 FPS |
642
419
 
643
- | Feature | Status |
644
- |---|---|
645
- | Arithmetic, comparison, logical, bitwise operators | ✓ — includes `&^` (bit clear) |
646
- | Compound assignment, increment / decrement | ✓ |
647
- | Slice expressions (`s[lo:hi]`, `s[lo:hi:max]`) | ✓ |
648
- | Variadic spread (`f(slice...)`, `append(a, b...)`) | ✓ |
649
- | Positional struct literals (`Point{1, 2}`) | ✓ |
650
- | Method expressions (`T.Method`) / method values (`x.Method`) | ✓ |
651
- | Multi-value function forwarding (`f(g())`) | ✓ |
652
- | Raw string literals (backticks), rune literals | ✓ |
653
- | Numeric separators (`1_000_000`), binary/octal/hex literals | ✓ |
654
- | `[]byte(s)` / `[]rune(s)` conversions | ✓ |
655
-
656
- ### Standard library shims
657
-
658
- | Package | Functions |
659
- |---|---|
660
- | `fmt` | `Sprintf`, `Printf`, `Println`, `Print`, `Errorf`, `Fprintf`, `Fprintln`, `Fprint` — format verbs: `%v`, `%d`, `%s`, `%t`, `%x`, `%o`, `%b`, `%q`, `%e`, `%g`, `%w`, width/precision; **scanning**: `Sscan`, `Sscanln`, `Sscanf` |
661
- | `strings` | `Contains`, `HasPrefix`, `HasSuffix`, `Index`, `LastIndex`, `Count`, `Repeat`, `Replace`, `ReplaceAll`, `ToUpper`, `ToLower`, `TrimSpace`, `Trim`, `TrimPrefix`, `TrimSuffix`, `TrimLeft`, `TrimRight`, `Split`, `Join`, `EqualFold`, `Fields`, `Cut`, `CutPrefix`, `CutSuffix`, `SplitN`, `SplitAfter`, `SplitAfterN`, `IndexAny`, `LastIndexAny`, `ContainsAny`, `ContainsRune`, `IndexRune`, `IndexByte`, `LastIndexByte`, `Map`, `Title`, `ToTitle`, `TrimFunc`, `TrimLeftFunc`, `TrimRightFunc`, `IndexFunc`, `LastIndexFunc`, `NewReplacer`; **`Builder`** type; **`NewReader`** → reader shim |
662
- | `bytes` | `Contains`, `HasPrefix`, `HasSuffix`, `Index`, `Join`, `Split`, `Replace`, `ToUpper`, `ToLower`, `TrimSpace`, `Equal`, `Count`, `Repeat`, `ReplaceAll`, `TrimPrefix`, `TrimSuffix`, `TrimLeft`, `TrimRight`, `TrimFunc`, `IndexByte`, `LastIndex`, `LastIndexByte`, `Fields`, `Cut`, `ContainsAny`, `ContainsRune`, `Map`, `SplitN`; **`Buffer`** type; **`NewReader`** → reader shim |
663
- | `strconv` | `Itoa`, `Atoi`, `FormatBool`, `FormatInt`, `FormatFloat`, `ParseFloat`, `ParseInt`, `ParseBool`, `Quote`, `Unquote`, `AppendInt`, `AppendFloat` |
664
- | `sort` | `Ints`, `Float64s`, `Strings`, `Slice`, `SliceStable`, `SliceIsSorted`, `Search`, `IntsAreSorted`, `Float64sAreSorted`, `StringsAreSorted` |
665
- | `math` | `Abs`, `Floor`, `Ceil`, `Round`, `Sqrt`, `Cbrt`, `Pow`, `Log`, `Log2`, `Log10`, `Sin`, `Cos`, `Tan`, `Atan`, `Atan2`, `Asin`, `Acos`, `Exp`, `Exp2`, `Trunc`, `Hypot`, `Signbit`, `Copysign`, `Dim`, `Remainder`, `Min`, `Max`, `Mod`, `Inf`, `IsNaN`, `IsInf`, `NaN` + `Pi`, `E`, `MaxFloat64`, `SmallestNonzeroFloat64`, `MaxInt`, `MinInt` |
666
- | `math/rand` | `Intn`, `Float64`, `Float32`, `Int`, `Int63`, `Int63n`, `Int31`, `Int31n`, `Seed` (no-op), `Shuffle`, `Perm` |
667
- | `errors` | `New`, `Is`, `Unwrap` — custom error types via interface satisfaction |
668
- | `time` | `Now` → `time.Time`, `Since`, `Sleep`, `Parse`, `Unix`, `Date`; **`time.Time`** methods: `Format`, `String`, `Year`, `Month`, `Day`, `Hour`, `Minute`, `Second`, `Weekday`, `Unix`, `UnixMilli`, `Add`, `Sub`, `Before`, `After`, `Equal`; layout constants: `RFC3339`, `RFC3339Nano`, `DateOnly`, `TimeOnly`, `DateTime`; duration constants: `Millisecond`, `Second`, `Minute`, `Hour`; month/weekday constants |
669
- | `html` | `EscapeString`, `UnescapeString` |
670
- | `maps` | `Keys`, `Values`, `Clone`, `Copy`, `Equal`, `EqualFunc`, `Delete`, `DeleteFunc` |
671
- | `slices` | `Contains`, `Index`, `Equal`, `Compare`, `Sort`, `SortFunc`, `SortStableFunc`, `IsSorted`, `IsSortedFunc`, `Reverse`, `Max`, `Min`, `MaxFunc`, `MinFunc`, `Clone`, `Compact`, `CompactFunc`, `Concat`, `Delete`, `DeleteFunc`, `Insert`, `Replace`, `Grow`, `Clip` |
672
- | `regexp` | `MustCompile`, `Compile`, `MatchString`, `QuoteMeta`; **`*Regexp`** methods: `MatchString`, `FindString`, `FindStringIndex`, `FindAllString`, `FindStringSubmatch`, `FindAllStringSubmatch`, `ReplaceAllString`, `ReplaceAllLiteralString`, `Split`, `String`. Inline flags (`(?i)`, `(?m)`, `(?s)`) are extracted automatically into the JS `RegExp` constructor. |
673
- | `unicode` | `IsLetter`, `IsDigit`, `IsSpace`, `IsUpper`, `IsLower`, `IsPunct`, `IsControl`, `IsPrint`, `IsGraphic`, `ToUpper`, `ToLower` |
674
- | `unicode/utf8` | `RuneCountInString`, `RuneLen`, `ValidString`, `ValidRune`, `DecodeRuneInString`, `DecodeLastRuneInString`, `FullRuneInString`; constants `RuneError`, `MaxRune`, `UTFMax` |
675
- | `path` | `Base`, `Dir`, `Ext`, `Join`, `Clean`, `IsAbs`, `Split`, `Match`; `"path/filepath"` is an alias |
676
- | `os` | `Exit`, `Args`, `Getenv` |
677
- | `io` | `Writer`, `Reader`, `ReadWriter`, `Closer` interface types; `ReadAll`, `EOF`, `Discard`, `WriteString` |
678
- | `gom` | Browser-native declarative DOM component library. **Types**: `Node` (interface), `NodeFunc`, `Group`. **Core**: `El(tag, children...)`, `Text(s)`, `Mount(sel, node)`, `MountTo(sel, node)`. **Attributes**: `Attr`, `Class`, `Href`, `Type`, `Src`, `Placeholder`, `DataAttr`, `Style`, `For`, `Name`, `Value`, `Target`, `Rel`, `Alt`, `Title`, `Draggable`, `Role`, `AriaLabel`, `StyleAttr`; boolean: `Disabled`, `Checked`, `Selected`, `Readonly`. **Logic**: `If(cond, node)`, `Map(slice, fn)`. **Elements**: full HTML element set (`Div`, `Span`, `Button`, `Input`, `Ul`, `Li`, `Table`, `Form`, `Img`, `A`, `H1`–`H6`, …) |
679
- | `testing` | **`testing.T`**: `Error`, `Errorf`, `Fatal`, `Fatalf`, `Fail`, `Failed`, `FailNow`, `Log`, `Logf`, `Skip`, `Skipf`, `Skipped`, `Helper`, `Run`, `Name`. **Package functions**: `Short()`, `Verbose()`. Tests are `func TestXxx(t *testing.T)` in `*_test.go` files, excluded from normal compilation and run via `gofront test`. |
680
-
681
- ### Packages & imports
682
-
683
- | Feature | Status |
684
- |---|---|
685
- | Multi-file packages | ✓ |
686
- | Cross-package imports | ✓ |
687
- | Import aliases (`import m "./pkg"`) | ✓ |
688
- | Side-effect imports (`import _ "pkg"`) | ✓ |
689
- | Dot imports (`import . "pkg"`) | ✓ |
690
- | Unused import detection | ✓ |
691
- | External `.d.ts` types | ✓ |
692
- | npm package type resolution | ✓ |
693
- | `.templ` files in packages | ✓ — mix `.go` and `.templ` files freely; templ components and `css` declarations visible across the whole package |
420
+ - **+17.1% higher throughput:** Direct WasmGC typed arrays, local-cached scratch globals, and hardware-trapped nil dereferences outperform JIT-compiled JS.
421
+ - **86% memory churn reduction:** Dropping allocations from 6.2 B/ray to 0.86 B/ray prevents garbage collection pauses from causing micro-stutter in 60 FPS loops.
422
+ - **Minimal boundary overhead:** The boundary trampoline consumes only ~0.5% of total runtime, ensuring batch computations cross between JS and WASM with virtually zero penalty.
694
423
 
695
- ---
424
+ **Full application: [simplefps](https://github.com/seriva/simplefps).** The game ships `mathx` as `both`, `collision`, `physics` and `animation` as `wasm`, and everything else (WebGPU renderer, scene, UI) as `js`. Its `npm run bench` compiles `physics` and `animation` once with `--js-only` and once hybrid and runs the same workload against both (Node 25):
696
425
 
697
- ## Go Compatibility
426
+ | Workload | Pure JS | Hybrid WASM | Ratio |
427
+ | :--- | :--- | :--- | :--- |
428
+ | Closest-hit raycasts, 2048-triangle octree (200k casts) | ~240k casts/s | ~263k casts/s | **1.11–1.15×** |
429
+ | FPS-controller fixed step across the boundary (pose in → `Update` + `MoveWithCamera` + `SyncCamera` → pose out, 50k frames) | ~46k frames/s | ~108k frames/s | **2.4×**, 0.2 B/frame |
430
+ | 64-joint skinned-character frame (`AnimationPlayer.Update` + `ComputeSkinningMatrices` + palette copy-out via `gofront/shared`) | ~141k frames/s | ~179k frames/s | **1.27×** |
431
+ | `app.wasm` (`mathx` + `collision` + `physics` + `animation`) | — | 98.5 kB unoptimised → **79.7 kB** with `--release` | −19% |
698
432
 
699
- GoFront implements a practical subset of the
700
- [Go Language Specification](https://go.dev/ref/spec) (go1.26). It is not aiming for
701
- byte-level parity — it is a Go-inspired language for the JavaScript platform.
433
+ The controller step is where WASM wins most: each fixed step issues several raycasts, and keeping `physics` next to `collision` means those never cross the boundary — only the camera pose does. `animation` is the cautionary tale for step 3: compiled as `wasm` while `ComputeSkinningMatrices` still returned a `[]Mat4`, it measured at parity with JS (the slice was copied out every frame); writing the palette into a `gofront/shared` buffer instead turned that into a 1.27× win.
702
434
 
703
- ### GoFront extensions (not in Go)
435
+ For the full rules — `both`-package restrictions, strict numeric mode, which types can cross the boundary, loader overrides, and how `gofront test` runs hybrid packages — see the **[Hybrid JS + WebAssembly Guide](docs/hybrid-wasm.md)**.
704
436
 
705
- These features are intentional additions for the JavaScript platform:
437
+ ---
706
438
 
707
- | Feature | Purpose |
708
- |---|---|
709
- | `async func` / `await` | First-class async syntax for frontend work. |
710
- | Browser globals (`document`, `console`, etc.) | Predeclared as `any` for practical DOM access. `WebGL2RenderingContext`, `WebGLRenderingContext`, `GPUDevice`, `GPUAdapter`, `GPUQueue`, `ArrayBuffer`, `DataView`, and TypedArrays have full static typings with method-level checking. |
711
- | `.d.ts` type imports (`import "js:./types.d.ts"`) | Type-safe interop with JavaScript libraries. |
712
- | npm package resolution | Import types from `node_modules/` and `@types/` automatically. |
439
+ ## Usage & Tooling
713
440
 
714
- ### What's not implemented
441
+ GoFront includes a fully-featured CLI for building, dev-serving (with live reload), and type-checking your projects.
715
442
 
716
- | Feature | Reason | Prospect |
717
- |---|---|---|
718
- | `goto` | No clean JS translation. Rare in idiomatic Go. | Not planned. |
719
- | Goroutines / channels / `select` | Go's concurrency model has no JS equivalent. A userland scheduler defeats the "no runtime" goal. | Out of scope. |
720
- | `unsafe`, `reflect`, `cgo` | Require memory model or runtime type metadata that JS cannot provide. | Out of scope. |
443
+ For detailed command flags, project configuration (`gofront.json`), multi-file package resolution, and type-checking rules, see the **[Usage & Tooling Guide](docs/usage.md)**.
721
444
 
722
- ### Semantic differences
445
+ ---
723
446
 
724
- These features are implemented but behave differently due to fundamental JS runtime
725
- constraints. These are **not bugs** — they are deliberate trade-offs documented here so
726
- you know exactly what to expect.
447
+ ## Documentation
727
448
 
728
- | Feature | GoFront | Go | Why |
729
- |---|---|---|---|
730
- | Map iteration order | Insertion-order (`Object.entries`) | Randomised | JS objects preserve insertion order. |
731
- | Integer overflow | IEEE 754 float64 semantics | Wraps at type boundary (e.g. `int32`) | All JS numbers are float64. |
732
- | Integer precision | Safe up to 2⁵³ | Full width per type (`int64` = 64 bits) | JS `number` limitation. |
733
- | `cap()` | Always equals `len()` | May exceed `len()` | JS arrays have no separate capacity. |
734
- | Fixed-size arrays (`[n]T`) | Compile-time enforcement (bounds, append rejected, size matching); plain JS arrays at runtime | Fixed at compile time, value-type copy semantics | Runtime enforcement adds overhead; compile-time checks catch most errors. |
735
- | `nil` | Maps to `null` | Typed nil (distinct per type) | JS has no typed nil concept. |
736
- | `rune` / `byte` | Treated as `int` | Distinct types with UTF-8 encoding | JS strings are UTF-16. |
737
- | `range` over string | Rune integers via `.codePointAt()` | Runes (UTF-8 code points) | Close match — indices are byte-sequential, values are code points. |
738
- | `len()` on strings | JS `.length` (UTF-16 code units) | Byte count (UTF-8) | Matching Go would require `TextEncoder` on every call. |
739
- | `error` type | Interface `{ Error() string }` with `__error` runtime objects | Interface `{ Error() string }` | Close match. Custom error types, `errors.Is`/`Unwrap`, `%w` wrapping all work. `toString()` added for JS string context compat. |
740
- | `defer` | `try`/`finally` with a defer stack | Runtime stack unwinding | Covers most cases but not identical to Go internals. |
741
- | Struct field tags | Parsed, silently discarded | Available via `reflect` | No reflection = no use for tag values. |
742
- | Pointers (`&x`, `*p`) | Address-taken scalars boxed as `{ value: T }`; structs/slices/maps are reference types (no boxing) | True memory indirection | Shared mutation works for scalars; struct fields and slice elements not yet addressable. |
743
- | Three-index slice (`a[lo:hi:max]`) | `max` is parsed but ignored | Sets result capacity | JS arrays have no capacity. |
744
- | Exported / unexported | Enforced for GoFront packages; external `.d.ts`/npm namespaces are exempt | Access enforced uniformly | External JS APIs use lowercase names by convention. |
449
+ * [Usage & Tooling Guide](docs/usage.md) — CLI, project config, testing, multi-file packages
450
+ * [Hybrid JS + WebAssembly Guide](docs/hybrid-wasm.md) — package targets, `both` rules, boundary types, loader
451
+ * [Language Features & Stdlib](docs/language-support.md) — what is implemented, stdlib shims
452
+ * [Go to JS Mapping](docs/compilation-mapping.md) — what each construct compiles to
453
+ * [Go Compatibility Differences](docs/go-compatibility.md) — where GoFront deliberately differs from Go
454
+ * [Compiler Architecture & Internals](docs/architecture.md) — pipeline stages and source layout
745
455
 
746
456
  ---
747
457
 
@@ -749,33 +459,31 @@ you know exactly what to expect.
749
459
 
750
460
  See [`docs/roadmap.md`](docs/roadmap.md) for the full roadmap and release history.
751
461
  Design documents for planned features are organised by release under `docs/v*/`
752
- (e.g. [`docs/v0.0.8/`](docs/v0.0.8/), [`docs/v1.2.0/`](docs/v1.2.0/), [`docs/v1.3.0/`](docs/v1.3.0/)).
462
+ (e.g. [`docs/v0.0.8/`](docs/v0.0.8/), [`docs/v1.2.0/`](docs/v1.2.0/), [`docs/v1.3.0/`](docs/v1.3.0/), [`docs/v1.6.0/`](docs/v1.6.0/)).
753
463
 
754
464
  ---
755
465
 
756
466
  ## Tests
757
467
 
758
468
  ```sh
759
- npm run test:unit # unit tests only (~1400 tests, no browser required)
760
- npm run test:perf # zero-allocation benchmark (100k ray-triangle intersections, 0 bytes/frame)
761
- npm run test:examples # GoFront-native unit tests across all example apps
762
- npm run test:examples:dom # same, with JSDOM for DOM/gom/templ component testing
763
- npm run test:e2e # E2E tests (Playwright, headless Chromium)
764
- npm run test:all # all of the above
469
+ npm test # unit, zero-alloc perf, and example tests (~1,800+ tests, no browser required)
470
+ npm run test:e2e # E2E browser tests (Playwright, headless Chromium)
471
+ npm run bench # Möller–Trumbore raycast benchmark comparing pure JS vs hybrid WASM
765
472
  ```
766
473
 
767
- **Unit tests** (~1,400+) cover language features, type errors, edge cases, DOM (jsdom),
474
+ **Unit tests** (~1,800+) cover language features, type errors, edge cases, DOM (jsdom),
768
475
  external `.d.ts`, npm resolver, multi-file compilation, embedded structs, string
769
476
  formatting, map iteration order, integer overflow semantics, unused variable detection,
770
477
  unused import detection, semantic difference verification, stdlib shim packages, generics,
771
- the `testing` framework itself, and `.templ` file compilation (element rendering,
478
+ the `testing` framework itself, the WasmGC backend and JS↔WASM boundary, `gofront/shared`
479
+ linear-memory buffers, Binaryen optimization, and `.templ` file compilation (element rendering,
772
480
  interpolation, boolean attrs, component calls, scoped `css` declarations with class hashing,
773
481
  `if/else/else-if` chains, `for range`, `switch/case/default`, `@templ.Raw()` raw HTML injection,
774
482
  SVG namespace handling, mixed `.go`+`.templ` packages).
775
483
 
776
- **E2E tests** (~105, Playwright) run all five example apps in a real browser and verify
484
+ **E2E tests** (~110, Playwright) run all six example apps in a real browser and verify
777
485
  CRUD, filtering, priority mode, persistence (reload), drag-and-drop reordering, and sync
778
486
  status. Per-app suites check app-specific behaviour: scoped styles, stats bar, loading
779
487
  placeholder, `gom.If` conditional rendering, templ-specific features (scoped `css` injection,
780
- `if/else` priority hint, `for` loop rendering, conditional bool attributes), and the WebGL2 cube
781
- rendering.
488
+ `if/else` priority hint, `for` loop rendering, conditional bool attributes), the WebGL2 cube
489
+ rendering, and the hybrid JS + WasmGC particle simulation.