gofront 1.4.0 → 1.6.0
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 -403
- package/package.json +16 -19
- package/src/{codegen → backend/js}/expressions.js +265 -77
- package/src/{codegen → backend/js}/index.js +120 -89
- package/src/{codegen → backend/js}/runtime.js +102 -0
- package/src/{codegen → backend/js}/source-map.js +32 -0
- package/src/{codegen → backend/js}/statements.js +139 -64
- package/src/backend/js/stdlib/bits.js +32 -0
- package/src/{codegen → backend/js}/stdlib/errors.js +2 -2
- package/src/{codegen → backend/js}/stdlib/fmt.js +26 -8
- package/src/{codegen → backend/js}/stdlib/index.js +6 -0
- package/src/{codegen → backend/js}/stdlib/maps.js +1 -1
- package/src/{codegen → backend/js}/stdlib/path.js +2 -2
- package/src/backend/js/stdlib/shared.js +17 -0
- package/src/{codegen → backend/js}/stdlib/slices.js +1 -1
- package/src/{codegen → backend/js}/stdlib/sort.js +1 -1
- package/src/backend/js/stdlib/strconv.js +58 -0
- package/src/{codegen → backend/js}/stdlib/strings.js +4 -1
- package/src/{codegen → backend/js}/stdlib/testing.js +2 -2
- package/src/{codegen → backend/js}/stdlib/time.js +3 -3
- package/src/{codegen → backend/js}/templ.js +3 -3
- package/src/backend/wasm/boundary.js +1798 -0
- package/src/backend/wasm/emit-builtins.js +1103 -0
- package/src/backend/wasm/emit-exprs.js +1908 -0
- package/src/backend/wasm/emit-maps.js +1863 -0
- package/src/backend/wasm/emit-shared.js +519 -0
- package/src/backend/wasm/emit-stdlib.js +3006 -0
- package/src/backend/wasm/emit-stmts.js +2472 -0
- package/src/backend/wasm/emit.js +1376 -0
- package/src/backend/wasm/encode.js +937 -0
- package/src/backend/wasm/glue.js +144 -0
- package/src/backend/wasm/index.js +2790 -0
- package/src/backend/wasm/monomorph.js +711 -0
- package/src/backend/wasm/optimize.js +227 -0
- package/src/backend/wasm/types.js +544 -0
- package/src/backend/wasm/wat.js +302 -0
- package/src/cli-core.js +323 -75
- package/src/compiler.js +447 -14
- package/src/dev-server.js +56 -15
- package/src/index.js +99 -120
- package/src/lexer.js +18 -0
- package/src/lower/boxing.js +35 -0
- package/src/lower/captures.js +172 -0
- package/src/lower/embedding.js +30 -0
- package/src/lower/escape.js +164 -0
- package/src/lower/functions.js +302 -0
- package/src/lower/globals.js +316 -0
- package/src/lower/index.js +118 -0
- package/src/lower/ownership.js +137 -0
- package/src/lower/range.js +67 -0
- package/src/lower/walk.js +25 -0
- package/src/minifier.js +2 -0
- package/src/parser/index.js +8 -3
- package/src/resolver.js +3 -1
- package/src/test-runner.js +94 -20
- package/src/typechecker/assignability.js +42 -1
- package/src/typechecker/expressions.js +182 -3
- package/src/typechecker/index.js +261 -5
- package/src/typechecker/resolve.js +35 -6
- package/src/typechecker/stdlib/core.js +34 -19
- package/src/typechecker/stdlib/extended.js +18 -3
- package/src/typechecker/stdlib/shared.js +53 -0
- package/src/typechecker/stdlib.js +2 -0
- package/src/typechecker/types.js +259 -6
- package/src/vendor.js +2 -2
- package/src/codegen/stdlib/strconv.js +0 -33
- /package/src/{codegen → backend/js}/stdlib/builder.js +0 -0
- /package/src/{codegen → backend/js}/stdlib/bytes.js +0 -0
- /package/src/{codegen → backend/js}/stdlib/gom.js +0 -0
- /package/src/{codegen → backend/js}/stdlib/html.js +0 -0
- /package/src/{codegen → backend/js}/stdlib/io.js +0 -0
- /package/src/{codegen → backend/js}/stdlib/math.js +0 -0
- /package/src/{codegen → backend/js}/stdlib/os.js +0 -0
- /package/src/{codegen → backend/js}/stdlib/rand.js +0 -0
- /package/src/{codegen → backend/js}/stdlib/regexp.js +0 -0
- /package/src/{codegen → backend/js}/stdlib/unicode.js +0 -0
- /package/src/{codegen → backend/js}/stdlib/utf8.js +0 -0
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
|
-
|
|
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,168 +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 four-stage compiler written in pure Node.js (no dependencies). Every stage
|
|
58
|
-
operates on the same AST (abstract syntax tree), running in a single pass per stage:
|
|
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
|
-
→ Code Gen AST → JavaScript string
|
|
66
|
-
|
|
67
|
-
source text (.templ files)
|
|
68
|
-
→ TemplLexer dual-mode: Go mode for declarations, HTML mode inside templ bodies
|
|
69
|
-
→ TemplParser extends Parser; produces TemplDecl AST nodes with TemplNode children
|
|
70
|
-
→ Type Checker registers each templ component as func(...) gom.Node
|
|
71
|
-
→ Code Gen TemplDecl → direct DOM calls (createElement / setAttribute / appendChild)
|
|
72
|
-
```
|
|
73
|
-
|
|
74
|
-
### 1. Lexer (`src/lexer.js`)
|
|
75
|
-
|
|
76
|
-
Tokenises the source into a stream of tokens. Implements Go's semicolon insertion rules:
|
|
77
|
-
a semicolon is automatically inserted after a line's final token if that token is an
|
|
78
|
-
identifier, literal, `)`, `]`, `}`, or certain keywords (`return`, `break`, `continue`,
|
|
79
|
-
`fallthrough`). This is why Go doesn't need explicit semicolons — and neither does
|
|
80
|
-
GoFront.
|
|
81
|
-
|
|
82
|
-
### 2. Parser (`src/parser/`)
|
|
83
|
-
|
|
84
|
-
A hand-written recursive-descent parser. No parser generators, no grammar files — just
|
|
85
|
-
straightforward top-down parsing. Produces an AST where every node is a plain JS object
|
|
86
|
-
with a `kind` field (`"FuncDecl"`, `"IfStmt"`, `"BinaryExpr"`, etc.).
|
|
87
|
-
|
|
88
|
-
Operator precedence is handled via a Pratt-style expression parser with numeric
|
|
89
|
-
precedence levels.
|
|
90
|
-
|
|
91
|
-
### 3. Type Checker (`src/typechecker/`)
|
|
92
|
-
|
|
93
|
-
Three-pass type checker operating on the AST:
|
|
94
|
-
|
|
95
|
-
1. **Pass 1 — collect types**: register all `type` declarations (structs, interfaces,
|
|
96
|
-
named types) so they can be referenced before definition.
|
|
97
|
-
2. **Pass 2 — collect functions & vars**: register function signatures and package-level
|
|
98
|
-
variables. Resolve embedded struct fields and promote methods.
|
|
99
|
-
3. **Pass 3 — check bodies**: walk every function body, infer expression types, verify
|
|
100
|
-
assignments and call arguments, and report errors with source location.
|
|
101
|
-
|
|
102
|
-
Types are plain JS objects (`{ kind: "basic", name: "int" }`, `{ kind: "slice",
|
|
103
|
-
elem: ... }`, etc.). The special `any` type acts as a recovery/escape hatch — any
|
|
104
|
-
operation on it is silently permitted, preventing cascading errors.
|
|
105
|
-
|
|
106
|
-
### 4. Code Generator (`src/codegen/`)
|
|
107
|
-
|
|
108
|
-
Walks the typed AST and emits clean, readable JavaScript. No intermediate representation
|
|
109
|
-
— the codegen writes directly to an output buffer with indentation tracking.
|
|
110
|
-
|
|
111
|
-
Runtime helpers (`__len`, `__append`, `__s`, `__sprintf`, `__equal`, `__cmul`, `__cdiv`,
|
|
112
|
-
`__error`, `__errorIs`, `__timeFmt`, `__timeParse`, `__pathClean`, `__sortSlice`,
|
|
113
|
-
`__sclone`, `__ifv`, `__ifp`) are tree-shaken: only emitted when actually used. Optional
|
|
114
|
-
inline source maps are supported via VLQ-encoded mappings.
|
|
115
|
-
|
|
116
|
-
---
|
|
117
|
-
|
|
118
|
-
## Go → JavaScript mapping
|
|
105
|
+
Then open `http://localhost:3000` — the page live-reloads on every save.
|
|
119
106
|
|
|
120
|
-
|
|
121
|
-
readable and debuggable — no name mangling, no opaque wrappers.
|
|
122
|
-
|
|
123
|
-
### Data structures
|
|
124
|
-
|
|
125
|
-
| Go | JavaScript | Notes |
|
|
126
|
-
|---|---|---|
|
|
127
|
-
| `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` |
|
|
128
|
-
| Methods | Class instance methods | Receiver is `this` (typed `*T` for pointer receivers) |
|
|
129
|
-
| Embedded structs | Flattened fields + delegation stubs | `Greet(...a) { return Base.prototype.Greet.call(this, ...a); }` |
|
|
130
|
-
| `[]T` (slice) | `Array` | `append` → `__append`, `len` → `__len` |
|
|
131
|
-
| `[]float32`, `[]int8`…`[]int32`, `[]uint8`/`[]byte`…`[]uint32`, `[]rune` | TypedArray (`Float32Array`, `Int32Array`, `Uint8Array`, …) | `[]float64` and `[]int` stay plain `Array`; slicing → `.subarray` |
|
|
132
|
-
| `map[K]V` | Plain object `{}` | Key access via `[]`, iteration via `Object.entries()` |
|
|
133
|
-
| `nil` | `null` | |
|
|
134
|
-
| `error` | `__error` object | `error("msg")` → `__error("msg")`, `.Error()` → real method call. `toString()` for string context compat |
|
|
135
|
-
| Pointers (`*T`) | `{ value: T }` | `new(T)` allocates a boxed zero value |
|
|
136
|
-
|
|
137
|
-
### Functions
|
|
138
|
-
|
|
139
|
-
| Go | JavaScript |
|
|
140
|
-
|---|---|
|
|
141
|
-
| `func f(a int) int` | `function f(a)` |
|
|
142
|
-
| Multiple returns `return a, b` | `return [a, b]` — destructured at call site: `let [a, b] = f()` |
|
|
143
|
-
| Named returns | Variables pre-declared; bare `return` returns them |
|
|
144
|
-
| Variadic `func f(xs ...int)` | `function f(...xs)` |
|
|
145
|
-
| `func` literal (closure) | Arrow or function expression |
|
|
146
|
-
| `async func` / `await` | `async function` / `await` |
|
|
147
|
-
| `init()` | Emitted as `(function() { ... })()` immediately-invoked |
|
|
148
|
-
| `defer` | `try { ... } finally { deferred() }` |
|
|
149
|
-
|
|
150
|
-
### Control flow
|
|
151
|
-
|
|
152
|
-
| Go | JavaScript |
|
|
153
|
-
|---|---|
|
|
154
|
-
| `for init; cond; post {}` | `for (init; cond; post) {}` |
|
|
155
|
-
| `for cond {}` | `while (cond) {}` |
|
|
156
|
-
| `for {}` | `while (true) {}` |
|
|
157
|
-
| `for i, v := range slice` | `for (const [i, v] of arr.entries())` |
|
|
158
|
-
| `for k, v := range map` | `for (const [k, v] of Object.entries(m))` |
|
|
159
|
-
| `for i, r := range str` | `for (const [i, r] of Array.from(s, (c, i) => [i, c.codePointAt(0)]))` — `r` is a rune integer |
|
|
160
|
-
| `for i := range n` | `for (let i = 0; i < n; i++)` |
|
|
161
|
-
| `switch` / `fallthrough` | `switch` / case fall-through |
|
|
162
|
-
| `switch v := x.(type)` | `if/else if` with `typeof` / `instanceof` checks |
|
|
163
|
-
| `panic(msg)` | `throw new Error(msg)` |
|
|
164
|
-
| `recover()` | Captured in `defer` via `try/catch` |
|
|
165
|
-
|
|
166
|
-
### Builtins
|
|
167
|
-
|
|
168
|
-
| Go | JavaScript |
|
|
169
|
-
|---|---|
|
|
170
|
-
| `len(x)` | `__len(x)` (tree-shaken helper) or `Object.keys(x).length` for maps |
|
|
171
|
-
| `cap(x)` | `x.length` |
|
|
172
|
-
| `append(s, elems...)` | `[...s, ...elems]` |
|
|
173
|
-
| `copy(dst, src)` | Inline splice helper |
|
|
174
|
-
| `make([]T, n)` | `new Array(n).fill(zero)` |
|
|
175
|
-
| `make(map[K]V)` | `{}` |
|
|
176
|
-
| `delete(m, k)` | `delete m[k]` |
|
|
177
|
-
| `new(T)` | `{ value: zeroOf(T) }` |
|
|
178
|
-
| `min` / `max` | `Math.min` / `Math.max` |
|
|
179
|
-
| `clear(x)` | `.length = 0` (slice) or delete-loop (map) |
|
|
180
|
-
| `print` / `println` | `console.log` |
|
|
181
|
-
| `complex(r, i)` | `{ re: r, im: i }` |
|
|
182
|
-
| `real(z)` / `imag(z)` | `z.re` / `z.im` |
|
|
183
|
-
| `fmt.Sprintf` | `__sprintf` tree-shaken helper |
|
|
184
|
-
|
|
185
|
-
### Type system at runtime
|
|
186
|
-
|
|
187
|
-
All type checking happens at compile time. At runtime, types are erased — there are no
|
|
188
|
-
type tags, no reflection, no runtime overhead. Sized integers (`int8`–`int64`,
|
|
189
|
-
`uint8`–`uint64`) are all `number` at runtime. `float32` is `number`. Generic type
|
|
190
|
-
parameters are erased completely — `func Map[T, U any](...)` compiles to `function Map(...)`.
|
|
191
|
-
The type checker enforces correctness; JavaScript doesn't need to know.
|
|
107
|
+
Requires Node.js 20+ (WasmGC requires Node ≥ 22 or a modern browser).
|
|
192
108
|
|
|
193
109
|
---
|
|
194
110
|
|
|
195
111
|
## Examples
|
|
196
112
|
|
|
197
|
-
There are
|
|
113
|
+
There are six example apps. Four implement the same todo app to show different aspects
|
|
198
114
|
of GoFront; the fifth is a WebGL2 3D showcase demonstrating typed graphics APIs and
|
|
199
|
-
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.
|
|
200
117
|
|
|
201
118
|
### Simple (vanilla DOM)
|
|
202
119
|
|
|
@@ -317,6 +234,8 @@ Key syntax features:
|
|
|
317
234
|
- `for _, v := range slice { }` — loop rendering inside template bodies
|
|
318
235
|
- `switch expr { case v: ... default: ... }` — switch rendering inside template bodies
|
|
319
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
|
|
320
239
|
- Components are called like regular functions (`AppView()`, `TodoItem(t)`) and return
|
|
321
240
|
a `gom.Node`-compatible object, so `gom.Mount("#app", AppView())` works directly.
|
|
322
241
|
|
|
@@ -396,289 +315,143 @@ example/webgl/
|
|
|
396
315
|
index.html ← canvas element
|
|
397
316
|
```
|
|
398
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
|
+
|
|
399
336
|
### Build and run
|
|
400
337
|
|
|
401
338
|
```sh
|
|
402
|
-
npm run build
|
|
403
|
-
npm run build:reactive # → example/reactive/app.js
|
|
404
|
-
npm run build:gom # → example/gom/app.js
|
|
405
|
-
npm run build:templ # → example/templ/app.js
|
|
406
|
-
npm run build:webgl # → example/webgl/app.js
|
|
339
|
+
npm run build # compile all example applications
|
|
407
340
|
# open the respective index.html in a browser
|
|
408
341
|
```
|
|
409
342
|
|
|
410
343
|
---
|
|
411
344
|
|
|
412
|
-
##
|
|
413
|
-
|
|
414
|
-
```
|
|
415
|
-
gofront dev [dir] watch + compile + asset sync + live reload (default port 3000)
|
|
416
|
-
gofront dev [dir] --port 8080 use a custom port
|
|
417
|
-
gofront build [dir] clean + compile + minify + vendor → production output
|
|
418
|
-
gofront build [dir] --pwa also generate offline service worker (sw.js) + precache manifest
|
|
419
|
-
gofront build [dir] --source-map include inline source maps in the release bundle
|
|
420
|
-
gofront build [dir] --no-minify skip minification
|
|
421
|
-
gofront build [dir] --no-mangle minify but keep original identifiers
|
|
422
|
-
gofront prep [dir] [--minify] run asset copying + vendor bundling only (alias: gofront vendor)
|
|
423
|
-
gofront check <dir> type-check a single package
|
|
424
|
-
gofront check <dir>/... type-check every package under <dir> (Go-style `./...`)
|
|
425
|
-
gofront test <dir> [--dom] run tests for a single package
|
|
426
|
-
gofront test <dir>/... [--dom] [-v] [-run <regex>] run tests recursively
|
|
427
|
-
gofront <file.go> compile single file → stdout
|
|
428
|
-
gofront <dir> compile all *.go in directory → stdout
|
|
429
|
-
gofront <input> -o out.js write output to file (prints elapsed compile time)
|
|
430
|
-
gofront <input> -o out.js --copy-assets compile + copy static assets
|
|
431
|
-
gofront <input> --check type-check only (single file / directory)
|
|
432
|
-
gofront <input> --watch watch for changes and recompile
|
|
433
|
-
gofront <input> -o out.js --serve watch + serve with live reload (legacy; prefer gofront dev)
|
|
434
|
-
gofront <input> --source-map append inline source map
|
|
435
|
-
gofront <input> --minify minify output (built-in minifier)
|
|
436
|
-
gofront <input> --minify --mangle minify and rename local identifiers
|
|
437
|
-
gofront <file.go> --ast dump AST (debug)
|
|
438
|
-
gofront <file.go> --tokens dump tokens (debug)
|
|
439
|
-
gofront init [dir] scaffold a new project
|
|
440
|
-
gofront --version / -v print version
|
|
441
|
-
gofront --help / -h print this help
|
|
442
|
-
```
|
|
443
|
-
### Project configuration
|
|
444
|
-
|
|
445
|
-
`gofront dev`, `build`, `prep`, `check` and `test` read optional settings from a `gofront.json`
|
|
446
|
-
file or a `"gofront": { … }` object in `package.json`:
|
|
447
|
-
|
|
448
|
-
| Key | Default | Description |
|
|
449
|
-
|---|---|---|
|
|
450
|
-
| `src` | `app/src`, `src` or `main.go` | Package directory (or single file) to compile |
|
|
451
|
-
| `serveDir` | `app`, `.` or `public` (first with `index.html`) | Directory served by `dev` and mirrored into the build |
|
|
452
|
-
| `outDir` | `public` | Release output directory for `build` |
|
|
453
|
-
| `output` | `app/app.js` | Dev-mode compiled bundle path |
|
|
454
|
-
| `port` | `3000` | Dev server port |
|
|
455
|
-
| `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) |
|
|
456
|
-
| `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[] } }` |
|
|
457
|
-
| `assetCopy` | `[]` | Static files copied by `prep`/`build`: `[{ "source": "node_modules/x/font.woff2", "dest": "app/fonts/font.woff2" }]` |
|
|
458
|
-
|
|
459
|
-
All settings live in this one place — top-level `"vendor"` / `"assetCopy"` keys in `package.json` are not read.
|
|
460
|
-
---
|
|
461
|
-
|
|
462
|
-
## Multi-file packages
|
|
345
|
+
## The Hybrid Architecture: JS + WasmGC
|
|
463
346
|
|
|
464
|
-
|
|
465
|
-
The compiler (`src/compiler.js`) orchestrates this:
|
|
466
|
-
|
|
467
|
-
1. Parse each `.go` file in the directory into a separate AST.
|
|
468
|
-
2. Resolve imports: `js:` d.ts files, npm packages (via `node_modules/` and `@types/`),
|
|
469
|
-
and local GoFront sub-packages (`import "./subpkg"`) which are compiled recursively.
|
|
470
|
-
3. Run the type checker across all ASTs as a single unit — types, functions, and
|
|
471
|
-
variables declared in one file are visible in all other files of the same package.
|
|
472
|
-
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:
|
|
473
348
|
|
|
474
349
|
```
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
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
|
|
479
365
|
```
|
|
480
366
|
|
|
481
|
-
|
|
482
|
-
gofront myapp -o myapp/bundle.js
|
|
483
|
-
```
|
|
367
|
+
### Package Targets
|
|
484
368
|
|
|
485
|
-
|
|
369
|
+
Each package declares its target at the top of any of its source files (before the `package` clause):
|
|
486
370
|
|
|
487
371
|
```go
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
x := math.Add(1, 2) // math package inlined into the bundle
|
|
492
|
-
}
|
|
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)
|
|
493
375
|
```
|
|
494
376
|
|
|
495
|
-
|
|
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 |
|
|
496
382
|
|
|
497
|
-
|
|
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.
|
|
498
385
|
|
|
499
|
-
|
|
500
|
-
locations in error messages across multiple files:
|
|
386
|
+
**Choosing a target for a package** — the questions to ask, in order:
|
|
501
387
|
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
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%).
|
|
506
393
|
|
|
507
|
-
|
|
508
|
-
// → Type error in src/main.go at line 5:7: cannot use int as string
|
|
509
|
-
// 5 | greet(42)
|
|
510
|
-
// ^
|
|
511
|
-
```
|
|
394
|
+
### Seamless Compile-Time Boundary
|
|
512
395
|
|
|
513
|
-
|
|
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`.
|
|
514
397
|
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
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.
|
|
518
410
|
|
|
519
|
-
|
|
520
|
-
The resolver (`src/resolver.js`) walks up the directory tree to find `node_modules`,
|
|
521
|
-
checks `package.json` `"types"` / `"typings"` fields, falls back to `index.d.ts`,
|
|
522
|
-
then tries `@types/`. The `.d.ts` parser (`src/dts-parser.js`) extracts type
|
|
523
|
-
signatures into GoFront's internal type representation.
|
|
411
|
+
### Real-World Benchmark: 3D Raycasting
|
|
524
412
|
|
|
525
|
-
|
|
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`):
|
|
526
414
|
|
|
527
|
-
|
|
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 |
|
|
528
419
|
|
|
529
|
-
|
|
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.
|
|
530
423
|
|
|
531
|
-
|
|
532
|
-
|---|---|
|
|
533
|
-
| Variables (`var`, `:=` short re-declaration) | ✓ |
|
|
534
|
-
| Constants (`const`, `iota`, untyped constants) | ✓ |
|
|
535
|
-
| Functions, multiple returns, named returns, variadic | ✓ |
|
|
536
|
-
| `init()` functions | ✓ |
|
|
537
|
-
| Closures / function literals | ✓ |
|
|
538
|
-
| `async func` / `await` expressions | ✓ |
|
|
539
|
-
| `defer`, `panic()` / `recover()` | ✓ |
|
|
540
|
-
| `print` / `println` builtins | ✓ — compile to `console.log` |
|
|
541
|
-
|
|
542
|
-
### Types & data structures
|
|
543
|
-
|
|
544
|
-
| Feature | Status |
|
|
545
|
-
|---|---|
|
|
546
|
-
| Structs + methods (value & pointer receivers) | ✓ |
|
|
547
|
-
| Embedded structs (flattened fields + promoted methods) | ✓ |
|
|
548
|
-
| Anonymous struct types | ✓ — compile to plain JS objects |
|
|
549
|
-
| Interfaces (with embedding) | ✓ |
|
|
550
|
-
| Slices (`append`, `len`, `make`) | ✓ |
|
|
551
|
-
| Maps (`make`, `delete`, comma-ok) | ✓ |
|
|
552
|
-
| Arrays with compile-time enforcement | ✓ — reject `append`, bounds checking, size matching, `[...]T` inference, compile-time `len()` |
|
|
553
|
-
| Slice → array conversion (`[N]T(slice)`) | ✓ — Go 1.20 |
|
|
554
|
-
| Pointers (`&x`, `*p`, `new(T)`) | ✓ — scalar locals boxed as `{ value: T }` for shared mutation |
|
|
555
|
-
| `error` type | ✓ — interface `{ Error() string }`; custom error types, `errors.Is`/`Unwrap`, `%w` wrapping |
|
|
556
|
-
| Complex numbers (`complex64`, `complex128`, `3i`) | ✓ — `complex()`, `real()`, `imag()` builtins; `__cmul`/`__cdiv` helpers |
|
|
557
|
-
| Type definitions, type aliases (`type A = B`) | ✓ |
|
|
558
|
-
| Methods on named non-struct types | ✓ — `type T func(...)` or `type T []E` with methods; emitted as ES6 wrapper classes; satisfies interfaces |
|
|
559
|
-
| Type conversions, type assertions (plain & comma-ok) | ✓ |
|
|
560
|
-
| Type switch (`switch v := x.(type)`) | ✓ — compiles to `if/else if` with `typeof` / `instanceof` |
|
|
561
|
-
| Sized integers (`int8`–`int64`, `uint8`–`uint64`, `float32`) | ✓ — mapped to `number` at runtime |
|
|
562
|
-
| Generics (`func F[T any]`, `type S[T any] struct`) | ✓ — type erasure to JS; generic functions, structs, constraints (`any`, `comparable`, interfaces, unions), type inference |
|
|
563
|
-
| Struct field tags | ✓ — parsed and ignored (no reflection) |
|
|
564
|
-
| Struct and array equality (`a == b`) | ✓ — deep comparison via `__equal` helper |
|
|
565
|
-
|
|
566
|
-
### Control flow
|
|
567
|
-
|
|
568
|
-
| Feature | Status |
|
|
569
|
-
|---|---|
|
|
570
|
-
| `if` / `else if` / `else` (with init statement) | ✓ |
|
|
571
|
-
| `for` (C-style, condition-only, infinite, `range`) | ✓ |
|
|
572
|
-
| `range` over slice, map, string, integer | ✓ |
|
|
573
|
-
| Range over iterator functions (Go 1.23) | ✓ — `func(yield func(K, V) bool)` protocol; break/continue/return propagation |
|
|
574
|
-
| `switch` / `fallthrough` (with init statement) | ✓ |
|
|
575
|
-
| `break` / `continue` / labeled variants | ✓ |
|
|
576
|
-
| Terminating statement analysis | ✓ — missing `return` in non-void functions is a type error |
|
|
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):
|
|
577
425
|
|
|
578
|
-
|
|
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% |
|
|
579
432
|
|
|
580
|
-
|
|
581
|
-
|---|---|
|
|
582
|
-
| Arithmetic, comparison, logical, bitwise operators | ✓ — includes `&^` (bit clear) |
|
|
583
|
-
| Compound assignment, increment / decrement | ✓ |
|
|
584
|
-
| Slice expressions (`s[lo:hi]`, `s[lo:hi:max]`) | ✓ |
|
|
585
|
-
| Variadic spread (`f(slice...)`, `append(a, b...)`) | ✓ |
|
|
586
|
-
| Positional struct literals (`Point{1, 2}`) | ✓ |
|
|
587
|
-
| Method expressions (`T.Method`) / method values (`x.Method`) | ✓ |
|
|
588
|
-
| Multi-value function forwarding (`f(g())`) | ✓ |
|
|
589
|
-
| Raw string literals (backticks), rune literals | ✓ |
|
|
590
|
-
| Numeric separators (`1_000_000`), binary/octal/hex literals | ✓ |
|
|
591
|
-
| `[]byte(s)` / `[]rune(s)` conversions | ✓ |
|
|
592
|
-
|
|
593
|
-
### Standard library shims
|
|
594
|
-
|
|
595
|
-
| Package | Functions |
|
|
596
|
-
|---|---|
|
|
597
|
-
| `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` |
|
|
598
|
-
| `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 |
|
|
599
|
-
| `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 |
|
|
600
|
-
| `strconv` | `Itoa`, `Atoi`, `FormatBool`, `FormatInt`, `FormatFloat`, `ParseFloat`, `ParseInt`, `ParseBool`, `Quote`, `Unquote`, `AppendInt`, `AppendFloat` |
|
|
601
|
-
| `sort` | `Ints`, `Float64s`, `Strings`, `Slice`, `SliceStable`, `SliceIsSorted`, `Search`, `IntsAreSorted`, `Float64sAreSorted`, `StringsAreSorted` |
|
|
602
|
-
| `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` |
|
|
603
|
-
| `math/rand` | `Intn`, `Float64`, `Float32`, `Int`, `Int63`, `Int63n`, `Int31`, `Int31n`, `Seed` (no-op), `Shuffle`, `Perm` |
|
|
604
|
-
| `errors` | `New`, `Is`, `Unwrap` — custom error types via interface satisfaction |
|
|
605
|
-
| `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 |
|
|
606
|
-
| `html` | `EscapeString`, `UnescapeString` |
|
|
607
|
-
| `maps` | `Keys`, `Values`, `Clone`, `Copy`, `Equal`, `EqualFunc`, `Delete`, `DeleteFunc` |
|
|
608
|
-
| `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` |
|
|
609
|
-
| `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. |
|
|
610
|
-
| `unicode` | `IsLetter`, `IsDigit`, `IsSpace`, `IsUpper`, `IsLower`, `IsPunct`, `IsControl`, `IsPrint`, `IsGraphic`, `ToUpper`, `ToLower` |
|
|
611
|
-
| `unicode/utf8` | `RuneCountInString`, `RuneLen`, `ValidString`, `ValidRune`, `DecodeRuneInString`, `DecodeLastRuneInString`, `FullRuneInString`; constants `RuneError`, `MaxRune`, `UTFMax` |
|
|
612
|
-
| `path` | `Base`, `Dir`, `Ext`, `Join`, `Clean`, `IsAbs`, `Split`, `Match`; `"path/filepath"` is an alias |
|
|
613
|
-
| `os` | `Exit`, `Args`, `Getenv` |
|
|
614
|
-
| `io` | `Writer`, `Reader`, `ReadWriter`, `Closer` interface types; `ReadAll`, `EOF`, `Discard`, `WriteString` |
|
|
615
|
-
| `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`, …) |
|
|
616
|
-
| `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`. |
|
|
617
|
-
|
|
618
|
-
### Packages & imports
|
|
619
|
-
|
|
620
|
-
| Feature | Status |
|
|
621
|
-
|---|---|
|
|
622
|
-
| Multi-file packages | ✓ |
|
|
623
|
-
| Cross-package imports | ✓ |
|
|
624
|
-
| Import aliases (`import m "./pkg"`) | ✓ |
|
|
625
|
-
| Side-effect imports (`import _ "pkg"`) | ✓ |
|
|
626
|
-
| Dot imports (`import . "pkg"`) | ✓ |
|
|
627
|
-
| Unused import detection | ✓ |
|
|
628
|
-
| External `.d.ts` types | ✓ |
|
|
629
|
-
| npm package type resolution | ✓ |
|
|
630
|
-
| `.templ` files in packages | ✓ — mix `.go` and `.templ` files freely; templ components and `css` declarations visible across the whole package |
|
|
631
|
-
|
|
632
|
-
---
|
|
633
|
-
|
|
634
|
-
## Go Compatibility
|
|
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.
|
|
635
434
|
|
|
636
|
-
|
|
637
|
-
[Go Language Specification](https://go.dev/ref/spec) (go1.26). It is not aiming for
|
|
638
|
-
byte-level parity — it is a Go-inspired language for the JavaScript platform.
|
|
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)**.
|
|
639
436
|
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
These features are intentional additions for the JavaScript platform:
|
|
437
|
+
---
|
|
643
438
|
|
|
644
|
-
|
|
645
|
-
|---|---|
|
|
646
|
-
| `async func` / `await` | First-class async syntax for frontend work. |
|
|
647
|
-
| 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. |
|
|
648
|
-
| `.d.ts` type imports (`import "js:./types.d.ts"`) | Type-safe interop with JavaScript libraries. |
|
|
649
|
-
| npm package resolution | Import types from `node_modules/` and `@types/` automatically. |
|
|
439
|
+
## Usage & Tooling
|
|
650
440
|
|
|
651
|
-
|
|
441
|
+
GoFront includes a fully-featured CLI for building, dev-serving (with live reload), and type-checking your projects.
|
|
652
442
|
|
|
653
|
-
|
|
654
|
-
|---|---|---|
|
|
655
|
-
| `goto` | No clean JS translation. Rare in idiomatic Go. | Not planned. |
|
|
656
|
-
| Goroutines / channels / `select` | Go's concurrency model has no JS equivalent. A userland scheduler defeats the "no runtime" goal. | Out of scope. |
|
|
657
|
-
| `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)**.
|
|
658
444
|
|
|
659
|
-
|
|
445
|
+
---
|
|
660
446
|
|
|
661
|
-
|
|
662
|
-
constraints. These are **not bugs** — they are deliberate trade-offs documented here so
|
|
663
|
-
you know exactly what to expect.
|
|
447
|
+
## Documentation
|
|
664
448
|
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
| 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. |
|
|
672
|
-
| `nil` | Maps to `null` | Typed nil (distinct per type) | JS has no typed nil concept. |
|
|
673
|
-
| `rune` / `byte` | Treated as `int` | Distinct types with UTF-8 encoding | JS strings are UTF-16. |
|
|
674
|
-
| `range` over string | Rune integers via `.codePointAt()` | Runes (UTF-8 code points) | Close match — indices are byte-sequential, values are code points. |
|
|
675
|
-
| `len()` on strings | JS `.length` (UTF-16 code units) | Byte count (UTF-8) | Matching Go would require `TextEncoder` on every call. |
|
|
676
|
-
| `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. |
|
|
677
|
-
| `defer` | `try`/`finally` with a defer stack | Runtime stack unwinding | Covers most cases but not identical to Go internals. |
|
|
678
|
-
| Struct field tags | Parsed, silently discarded | Available via `reflect` | No reflection = no use for tag values. |
|
|
679
|
-
| 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. |
|
|
680
|
-
| Three-index slice (`a[lo:hi:max]`) | `max` is parsed but ignored | Sets result capacity | JS arrays have no capacity. |
|
|
681
|
-
| 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
|
|
682
455
|
|
|
683
456
|
---
|
|
684
457
|
|
|
@@ -686,33 +459,31 @@ you know exactly what to expect.
|
|
|
686
459
|
|
|
687
460
|
See [`docs/roadmap.md`](docs/roadmap.md) for the full roadmap and release history.
|
|
688
461
|
Design documents for planned features are organised by release under `docs/v*/`
|
|
689
|
-
(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/)).
|
|
690
463
|
|
|
691
464
|
---
|
|
692
465
|
|
|
693
466
|
## Tests
|
|
694
467
|
|
|
695
468
|
```sh
|
|
696
|
-
npm
|
|
697
|
-
npm run test:
|
|
698
|
-
npm run
|
|
699
|
-
npm run test:examples:dom # same, with JSDOM for DOM/gom/templ component testing
|
|
700
|
-
npm run test:e2e # E2E tests (Playwright, headless Chromium)
|
|
701
|
-
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
|
|
702
472
|
```
|
|
703
473
|
|
|
704
|
-
**Unit tests** (~1,
|
|
474
|
+
**Unit tests** (~1,800+) cover language features, type errors, edge cases, DOM (jsdom),
|
|
705
475
|
external `.d.ts`, npm resolver, multi-file compilation, embedded structs, string
|
|
706
476
|
formatting, map iteration order, integer overflow semantics, unused variable detection,
|
|
707
477
|
unused import detection, semantic difference verification, stdlib shim packages, generics,
|
|
708
|
-
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,
|
|
709
480
|
interpolation, boolean attrs, component calls, scoped `css` declarations with class hashing,
|
|
710
481
|
`if/else/else-if` chains, `for range`, `switch/case/default`, `@templ.Raw()` raw HTML injection,
|
|
711
482
|
SVG namespace handling, mixed `.go`+`.templ` packages).
|
|
712
483
|
|
|
713
|
-
**E2E tests** (~
|
|
484
|
+
**E2E tests** (~110, Playwright) run all six example apps in a real browser and verify
|
|
714
485
|
CRUD, filtering, priority mode, persistence (reload), drag-and-drop reordering, and sync
|
|
715
486
|
status. Per-app suites check app-specific behaviour: scoped styles, stats bar, loading
|
|
716
487
|
placeholder, `gom.If` conditional rendering, templ-specific features (scoped `css` injection,
|
|
717
|
-
`if/else` priority hint, `for` loop rendering, conditional bool attributes),
|
|
718
|
-
rendering.
|
|
488
|
+
`if/else` priority hint, `for` loop rendering, conditional bool attributes), the WebGL2 cube
|
|
489
|
+
rendering, and the hybrid JS + WasmGC particle simulation.
|