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.
- package/README.md +174 -466
- package/package.json +16 -20
- package/src/backend/js/expressions.js +288 -128
- package/src/backend/js/index.js +138 -56
- package/src/backend/js/runtime.js +1055 -53
- package/src/backend/js/source-map.js +32 -0
- package/src/backend/js/statements.js +151 -83
- package/src/backend/js/stdlib/base64.js +12 -0
- package/src/backend/js/stdlib/binary.js +102 -0
- package/src/backend/js/stdlib/bits.js +15 -1
- package/src/backend/js/stdlib/builder.js +28 -9
- package/src/backend/js/stdlib/bytes.js +75 -18
- package/src/backend/js/stdlib/errors.js +22 -3
- package/src/backend/js/stdlib/fmt.js +95 -20
- package/src/backend/js/stdlib/hex.js +17 -0
- package/src/backend/js/stdlib/html.js +1 -1
- package/src/backend/js/stdlib/index.js +19 -0
- package/src/backend/js/stdlib/io.js +105 -3
- package/src/backend/js/stdlib/maps.js +1 -1
- package/src/backend/js/stdlib/math.js +13 -1
- package/src/backend/js/stdlib/path.js +20 -6
- package/src/backend/js/stdlib/rand.js +10 -5
- package/src/backend/js/stdlib/sha256.js +12 -0
- package/src/backend/js/stdlib/shared.js +17 -0
- package/src/backend/js/stdlib/slices.js +53 -27
- package/src/backend/js/stdlib/sort.js +1 -1
- package/src/backend/js/stdlib/strconv.js +52 -11
- package/src/backend/js/stdlib/strings.js +152 -16
- package/src/backend/js/stdlib/sync.js +13 -0
- package/src/backend/js/stdlib/testing.js +2 -2
- package/src/backend/js/stdlib/time.js +93 -22
- package/src/backend/js/stdlib/unicode.js +2 -2
- package/src/backend/js/templ.js +1 -1
- package/src/backend/wasm/boundary.js +1677 -58
- package/src/backend/wasm/const-fold.js +205 -0
- package/src/backend/wasm/emit-builtins.js +1230 -0
- package/src/backend/wasm/emit-exprs.js +2096 -0
- package/src/backend/wasm/emit-maps.js +1999 -0
- package/src/backend/wasm/emit-shared.js +519 -0
- package/src/backend/wasm/emit-stdlib.js +13 -0
- package/src/backend/wasm/emit-stmts.js +2466 -0
- package/src/backend/wasm/emit.js +656 -5031
- package/src/backend/wasm/encode.js +259 -85
- package/src/backend/wasm/glue.js +19 -5
- package/src/backend/wasm/index.js +2432 -86
- package/src/backend/wasm/monomorph.js +711 -0
- package/src/backend/wasm/optimize.js +236 -0
- package/src/backend/wasm/stdlib/bytes.js +5039 -0
- package/src/backend/wasm/stdlib/core.js +8903 -0
- package/src/backend/wasm/stdlib/index.js +40 -0
- package/src/backend/wasm/stdlib/io.js +2161 -0
- package/src/backend/wasm/stdlib/packages.js +2400 -0
- package/src/backend/wasm/stdlib/pure.js +720 -0
- package/src/backend/wasm/stdlib/time.js +1200 -0
- package/src/backend/wasm/types.js +312 -36
- package/src/backend/wasm/wat.js +49 -2
- package/src/cli-core.js +276 -73
- package/src/compiler.js +162 -25
- package/src/dts-parser.js +79 -126
- package/src/index.js +88 -121
- package/src/lexer.js +80 -29
- package/src/lower/boxing.js +4 -6
- package/src/lower/captures.js +12 -23
- package/src/lower/escape.js +4 -9
- package/src/lower/functions.js +277 -5
- package/src/lower/globals.js +7 -15
- package/src/lower/index.js +5 -2
- package/src/lower/ownership.js +35 -19
- package/src/lower/range.js +2 -5
- package/src/lower/walk.js +25 -0
- package/src/minifier.js +20 -68
- package/src/parser/declarations.js +3 -6
- package/src/parser/expressions.js +3 -8
- package/src/parser/index.js +18 -3
- package/src/parser/statements.js +25 -38
- package/src/parser/types.js +1 -2
- package/src/resolver.js +3 -27
- package/src/templ-lexer.js +29 -63
- package/src/templ-parser.js +22 -45
- package/src/test-runner.js +20 -0
- package/src/typechecker/assignability.js +123 -14
- package/src/typechecker/expressions.js +244 -4
- package/src/typechecker/index.js +27 -1
- package/src/typechecker/resolve.js +71 -12
- package/src/typechecker/statements.js +0 -7
- package/src/typechecker/stdlib/core.js +292 -103
- package/src/typechecker/stdlib/extended.js +471 -4
- package/src/typechecker/stdlib/shared.js +53 -0
- package/src/typechecker/stdlib.js +2 -0
- package/src/typechecker/types.js +545 -8
- package/src/vendor.js +2 -2
package/README.md
CHANGED
|
@@ -1,16 +1,67 @@
|
|
|
1
|
+
# GoFront
|
|
2
|
+
[](https://www.npmjs.com/package/gofront) [](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
|
|
8
|
-
WebAssembly (WasmGC)
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
##
|
|
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
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
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
|
-
|
|
545
|
-
gofront myapp -o myapp/bundle.js
|
|
546
|
-
```
|
|
367
|
+
### Package Targets
|
|
547
368
|
|
|
548
|
-
|
|
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
|
-
|
|
552
|
-
|
|
553
|
-
|
|
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
|
-
|
|
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
|
-
|
|
563
|
-
|
|
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
|
-
|
|
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
|
-
|
|
571
|
-
|
|
572
|
-
|
|
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
|
-
|
|
394
|
+
### Seamless Compile-Time Boundary
|
|
577
395
|
|
|
578
|
-
|
|
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
|
-
|
|
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
|
-
###
|
|
411
|
+
### Real-World Benchmark: 3D Raycasting
|
|
593
412
|
|
|
594
|
-
|
|
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
|
-
|
|
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
|
-
|
|
644
|
-
|
|
645
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
437
|
+
---
|
|
706
438
|
|
|
707
|
-
|
|
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
|
-
|
|
441
|
+
GoFront includes a fully-featured CLI for building, dev-serving (with live reload), and type-checking your projects.
|
|
715
442
|
|
|
716
|
-
|
|
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
|
-
|
|
445
|
+
---
|
|
723
446
|
|
|
724
|
-
|
|
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
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
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
|
|
760
|
-
npm run test:
|
|
761
|
-
npm run
|
|
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,
|
|
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,
|
|
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** (~
|
|
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),
|
|
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.
|