gromer
git clone https://git.pyrossh.dev/gromer
gromer is a framework and cli to build multipage web apps in golang using htmx and alpinejs.
fdf8772
— pyros2097
2020-12-05T12:21:38+05:30
improve example
Generated files not shown: go.sum
- app_nowasm.go +64 -2
- app_wasm.go +4 -3
- cli/cli.go +360 -351
- example/{pages/about.go → about.go} +0 -0
- example/{config.css → assets/config.css} +0 -0
- example/assets/manifest.json +0 -21
- example/{pages/clock.go → clock.go} +0 -0
- example/{pages/container.go → container.go} +0 -0
- example/{pages/index.go → index.go} +0 -0
- example/main.go +9 -2
- example/makefile +10 -1
- example/pages/main.go +0 -27
- example/readme.md +6 -2
- example/samconfig.toml +9 -0
- example/template.yml +43 -0
- go.mod +3 -1
- utils.go +31 -0
- utils_test.go +22 -0
app_nowasm.go
CHANGED
|
@@ -2,10 +2,72 @@
|
|
|
2
2
|
|
|
3
3
|
package app
|
|
4
4
|
|
|
5
|
+
import (
|
|
6
|
+
"bytes"
|
|
7
|
+
"fmt"
|
|
8
|
+
"net/http"
|
|
9
|
+
"os"
|
|
10
|
+
"path/filepath"
|
|
11
|
+
"strconv"
|
|
12
|
+
"strings"
|
|
13
|
+
|
|
14
|
+
"github.com/akrylysov/algnhsa"
|
|
15
|
+
"github.com/markbates/pkger"
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
func Run(isAwsLambda bool, routes map[string]RenderFunc) {
|
|
19
|
+
wd, err := os.Getwd()
|
|
20
|
+
if err != nil {
|
|
21
|
+
fmt.Printf("could not get wd")
|
|
22
|
+
return
|
|
23
|
+
}
|
|
24
|
+
assetsFileServer := http.FileServer(pkger.Dir(filepath.Join(wd, "assets")))
|
|
25
|
+
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
|
26
|
+
println("route: " + r.URL.Path)
|
|
27
|
+
renderFunc := MatchRoute(routes, r.URL.Path)
|
|
28
|
+
if strings.Contains(r.URL.Path, "/assets") {
|
|
29
|
+
r.URL.Path = strings.Replace(r.URL.Path, "/assets", "", 1)
|
|
30
|
+
assetsFileServer.ServeHTTP(w, r)
|
|
31
|
+
return
|
|
32
|
+
}
|
|
33
|
+
page := createPage("wapp-example", renderFunc(NewRenderContext()))
|
|
34
|
+
w.Header().Set("Content-Length", strconv.Itoa(page.Len()))
|
|
35
|
+
w.Header().Set("Content-Type", "text/html")
|
|
36
|
+
w.WriteHeader(http.StatusOK)
|
|
37
|
+
w.Write(page.Bytes())
|
|
38
|
+
})
|
|
39
|
+
if !isAwsLambda {
|
|
40
|
+
println("Serving on HTTP port: 1234")
|
|
41
|
+
http.ListenAndServe(":1234", nil)
|
|
42
|
+
} else {
|
|
43
|
+
println("algnhsa serving default mux")
|
|
44
|
+
algnhsa.ListenAndServe(http.DefaultServeMux, nil)
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
5
48
|
func Reload() {
|
|
6
49
|
panic("wasm required")
|
|
7
50
|
}
|
|
8
51
|
|
|
9
|
-
func
|
|
10
|
-
|
|
52
|
+
func createPage(title string, ui UI) *bytes.Buffer {
|
|
53
|
+
page := bytes.NewBuffer(nil)
|
|
54
|
+
page.WriteString("<!DOCTYPE html>\n")
|
|
55
|
+
elems := FilterUIElems(ui)
|
|
56
|
+
Html(
|
|
57
|
+
Head(
|
|
58
|
+
Title(title),
|
|
59
|
+
Meta("author", "pyros2097"),
|
|
60
|
+
Meta("description", "Description"),
|
|
61
|
+
Meta("keywords", ""),
|
|
62
|
+
Meta("theme-color", ""),
|
|
63
|
+
Meta("viewport", "width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0, viewport-fit=cover"),
|
|
64
|
+
Link("icon", "/assets/icon.png"),
|
|
65
|
+
Link("apple-touch-icon", "/assets/icon.png"),
|
|
66
|
+
Link("stylesheet", "/assets/styles.css"),
|
|
67
|
+
Link("manifest", "manifest"),
|
|
68
|
+
Script(`const enosys = () => { const a = new Error("not implemented"); return a.code = "ENOSYS", a }; let outputBuf = ""; window.fs = { constants: { O_WRONLY: -1, O_RDWR: -1, O_CREAT: -1, O_TRUNC: -1, O_APPEND: -1, O_EXCL: -1 }, writeSync(a, b) { outputBuf += decoder.decode(b); const c = outputBuf.lastIndexOf("\n"); return -1 != c && (console.log(outputBuf.substr(0, c)), outputBuf = outputBuf.substr(c + 1)), b.length }, write(a, b, c, d, e, f) { if (0 !== c || d !== b.length || null !== e) return void f(enosys()); const g = this.writeSync(a, b); f(null, g) } }; const encoder = new TextEncoder("utf-8"), decoder = new TextDecoder("utf-8"); class Go { constructor() { this.argv = ["js"], this.env = {}, this.exit = a => { 0 !== a && console.warn("exit code:", a) }, this._exitPromise = new Promise(a => { this._resolveExitPromise = a }), this._pendingEvent = null, this._scheduledTimeouts = new Map, this._nextCallbackTimeoutID = 1; const a = (a, b) => { this.mem.setUint32(a + 0, b, !0), this.mem.setUint32(a + 4, Math.floor(b / 4294967296), !0) }, b = a => { const b = this.mem.getUint32(a + 0, !0), c = this.mem.getInt32(a + 4, !0); return b + 4294967296 * c }, c = a => { const b = this.mem.getFloat64(a, !0); if (0 !== b) { if (!isNaN(b)) return b; const c = this.mem.getUint32(a, !0); return this._values[c] } }, d = (a, b) => { const c = 2146959360; if ("number" == typeof b) return isNaN(b) ? (this.mem.setUint32(a + 4, 2146959360, !0), void this.mem.setUint32(a, 0, !0)) : 0 === b ? (this.mem.setUint32(a + 4, 2146959360, !0), void this.mem.setUint32(a, 1, !0)) : void this.mem.setFloat64(a, b, !0); switch (b) { case void 0: return void this.mem.setFloat64(a, 0, !0); case null: return this.mem.setUint32(a + 4, c, !0), void this.mem.setUint32(a, 2, !0); case !0: return this.mem.setUint32(a + 4, c, !0), void this.mem.setUint32(a, 3, !0); case !1: return this.mem.setUint32(a + 4, c, !0), void this.mem.setUint32(a, 4, !0); }let d = this._ids.get(b); d === void 0 && (d = this._idPool.pop(), d === void 0 && (d = this._values.length), this._values[d] = b, this._goRefCounts[d] = 0, this._ids.set(b, d)), this._goRefCounts[d]++; let e = 1; switch (typeof b) { case "string": e = 2; break; case "symbol": e = 3; break; case "function": e = 4; }this.mem.setUint32(a + 4, 2146959360 | e, !0), this.mem.setUint32(a, d, !0) }, e = a => { const c = b(a + 0), d = b(a + 8); return new Uint8Array(this._inst.exports.mem.buffer, c, d) }, f = d => { const e = b(d + 0), f = b(d + 8), g = Array(f); for (let a = 0; a < f; a++)g[a] = c(e + 8 * a); return g }, g = a => { const c = b(a + 0), d = b(a + 8); return decoder.decode(new DataView(this._inst.exports.mem.buffer, c, d)) }, h = Date.now() - performance.now(); this.importObject = { go: { "runtime.wasmExit": a => { const b = this.mem.getInt32(a + 8, !0); this.exited = !0, delete this._inst, delete this._values, delete this._goRefCounts, delete this._ids, delete this._idPool, this.exit(b) }, "runtime.wasmWrite": a => { const c = b(a + 8), d = b(a + 16), e = this.mem.getInt32(a + 24, !0); fs.writeSync(c, new Uint8Array(this._inst.exports.mem.buffer, d, e)) }, "runtime.resetMemoryDataView": () => { this.mem = new DataView(this._inst.exports.mem.buffer) }, "runtime.nanotime1": b => { a(b + 8, 1e6 * (h + performance.now())) }, "runtime.walltime1": b => { const c = new Date().getTime(); a(b + 8, c / 1e3), this.mem.setInt32(b + 16, 1e6 * (c % 1e3), !0) }, "runtime.scheduleTimeoutEvent": a => { const c = this._nextCallbackTimeoutID; this._nextCallbackTimeoutID++, this._scheduledTimeouts.set(c, setTimeout(() => { for (this._resume(); this._scheduledTimeouts.has(c);)console.warn("scheduleTimeoutEvent: missed timeout event"), this._resume() }, b(a + 8) + 1)), this.mem.setInt32(a + 16, c, !0) }, "runtime.clearTimeoutEvent": a => { const b = this.mem.getInt32(a + 8, !0); clearTimeout(this._scheduledTimeouts.get(b)), this._scheduledTimeouts.delete(b) }, "runtime.getRandomData": a => { crypto.getRandomValues(e(a + 8)) }, "syscall/js.finalizeRef": a => { const b = this.mem.getUint32(a + 8, !0); if (this._goRefCounts[b]--, 0 === this._goRefCounts[b]) { const a = this._values[b]; this._values[b] = null, this._ids.delete(a), this._idPool.push(b) } }, "syscall/js.stringVal": a => { d(a + 24, g(a + 8)) }, "syscall/js.valueGet": a => { const b = Reflect.get(c(a + 8), g(a + 16)); a = this._inst.exports.getsp(), d(a + 32, b) }, "syscall/js.valueSet": a => { Reflect.set(c(a + 8), g(a + 16), c(a + 32)) }, "syscall/js.valueDelete": a => { Reflect.deleteProperty(c(a + 8), g(a + 16)) }, "syscall/js.valueIndex": a => { d(a + 24, Reflect.get(c(a + 8), b(a + 16))) }, "syscall/js.valueSetIndex": a => { Reflect.set(c(a + 8), b(a + 16), c(a + 24)) }, "syscall/js.valueCall": a => { try { const b = c(a + 8), e = Reflect.get(b, g(a + 16)), h = f(a + 32), i = Reflect.apply(e, b, h); a = this._inst.exports.getsp(), d(a + 56, i), this.mem.setUint8(a + 64, 1) } catch (b) { d(a + 56, b), this.mem.setUint8(a + 64, 0) } }, "syscall/js.valueInvoke": a => { try { const b = c(a + 8), e = f(a + 16), g = Reflect.apply(b, void 0, e); a = this._inst.exports.getsp(), d(a + 40, g), this.mem.setUint8(a + 48, 1) } catch (b) { d(a + 40, b), this.mem.setUint8(a + 48, 0) } }, "syscall/js.valueNew": a => { try { const b = c(a + 8), e = f(a + 16), g = Reflect.construct(b, e); a = this._inst.exports.getsp(), d(a + 40, g), this.mem.setUint8(a + 48, 1) } catch (b) { d(a + 40, b), this.mem.setUint8(a + 48, 0) } }, "syscall/js.valueLength": b => { a(b + 16, parseInt(c(b + 8).length)) }, "syscall/js.valuePrepareString": b => { const e = encoder.encode(c(b + 8) + ""); d(b + 16, e), a(b + 24, e.length) }, "syscall/js.valueLoadString": a => { const b = c(a + 8); e(a + 16).set(b) }, "syscall/js.valueInstanceOf": a => { this.mem.setUint8(a + 24, c(a + 8) instanceof c(a + 16)) }, "syscall/js.copyBytesToGo": b => { const d = e(b + 8), f = c(b + 32); if (!(f instanceof Uint8Array)) return void this.mem.setUint8(b + 48, 0); const g = f.subarray(0, d.length); d.set(g), a(b + 40, g.length), this.mem.setUint8(b + 48, 1) }, "syscall/js.copyBytesToJS": b => { const d = c(b + 8), f = e(b + 16); if (!(d instanceof Uint8Array)) return void this.mem.setUint8(b + 48, 0); const g = f.subarray(0, d.length); d.set(g), a(b + 40, g.length), this.mem.setUint8(b + 48, 1) }, debug: a => { console.log(a) } } } } async run(a) { this._inst = a, this.mem = new DataView(this._inst.exports.mem.buffer), this._values = [NaN, 0, null, !0, !1, window, this], this._goRefCounts = [], this._ids = new Map, this._idPool = [], this.exited = !1; let b = 4096; const c = a => { const c = b, d = encoder.encode(a + "\0"); return new Uint8Array(this.mem.buffer, b, d.length).set(d), b += d.length, 0 != b % 8 && (b += 8 - b % 8), c }, d = this.argv.length, e = []; this.argv.forEach(a => { e.push(c(a)) }), e.push(0); const f = Object.keys(this.env).sort(); f.forEach(a => { e.push(c(a + "=" + this.env[a])) }), e.push(0); const g = b; e.forEach(a => { this.mem.setUint32(b, a, !0), this.mem.setUint32(b + 4, 0, !0), b += 8 }), this._inst.exports.run(d, g), this.exited && this._resolveExitPromise(), await this._exitPromise } _resume() { if (this.exited) throw new Error("Go program has already exited"); this._inst.exports.resume(), this.exited && this._resolveExitPromise() } _makeFuncWrapper(a) { const b = this; return function () { const c = { id: a, this: this, args: arguments }; return b._pendingEvent = c, b._resume(), c.result } } } const go = new Go; WebAssembly.instantiateStreaming(fetch("/assets/main.wasm"), go.importObject).then(a => go.run(a.instance)).catch(a => console.error("could not load wasm", a));`),
|
|
69
|
+
),
|
|
70
|
+
Body(elems[0]),
|
|
71
|
+
).Html(page)
|
|
72
|
+
return page
|
|
11
73
|
}
|
app_wasm.go
CHANGED
|
@@ -11,7 +11,8 @@ var (
|
|
|
11
11
|
rootPrefix string
|
|
12
12
|
)
|
|
13
13
|
|
|
14
|
-
func Run(
|
|
14
|
+
func Run(isAwsLambda bool, routes map[string]RenderFunc) {
|
|
15
|
+
renderFunc := MatchRoute(routes, js.Window.URL().Path)
|
|
15
16
|
defer func() {
|
|
16
17
|
err := recover()
|
|
17
18
|
// show alert
|
|
@@ -20,10 +21,10 @@ func Run(render RenderFunc) {
|
|
|
20
21
|
|
|
21
22
|
initBody()
|
|
22
23
|
initContent()
|
|
23
|
-
if err := body.replaceChildAt(0,
|
|
24
|
+
if err := body.replaceChildAt(0, renderFunc); err != nil {
|
|
24
25
|
panic("replacing content failed")
|
|
25
26
|
}
|
|
26
|
-
content =
|
|
27
|
+
content = renderFunc
|
|
27
28
|
|
|
28
29
|
for {
|
|
29
30
|
select {
|
cli/cli.go
CHANGED
|
@@ -1,353 +1,362 @@
|
|
|
1
1
|
package cli
|
|
2
2
|
|
|
3
|
-
import (
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
//
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
//
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
//
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
//
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
3
|
+
// import (
|
|
4
|
+
// "bytes"
|
|
5
|
+
// "fmt"
|
|
6
|
+
// "io/ioutil"
|
|
7
|
+
// "log"
|
|
8
|
+
// "net/http"
|
|
9
|
+
// "os"
|
|
10
|
+
// "os/exec"
|
|
11
|
+
// "path/filepath"
|
|
12
|
+
// "plugin"
|
|
13
|
+
// "strconv"
|
|
14
|
+
// "strings"
|
|
15
|
+
// "text/template"
|
|
16
|
+
// "time"
|
|
17
|
+
|
|
18
|
+
// "github.com/akrylysov/algnhsa"
|
|
19
|
+
// "github.com/markbates/pkger"
|
|
20
|
+
// app "github.com/pyros2097/wapp"
|
|
21
|
+
// "gopkg.in/fsnotify.v1"
|
|
22
|
+
// )
|
|
23
|
+
|
|
24
|
+
// const wasmExecTemplate = `const enosys=()=>{const a=new Error("not implemented");return a.code="ENOSYS",a};let outputBuf="";window.fs={constants:{O_WRONLY:-1,O_RDWR:-1,O_CREAT:-1,O_TRUNC:-1,O_APPEND:-1,O_EXCL:-1},writeSync(a,b){outputBuf+=decoder.decode(b);const c=outputBuf.lastIndexOf("\n");return-1!=c&&(console.log(outputBuf.substr(0,c)),outputBuf=outputBuf.substr(c+1)),b.length},write(a,b,c,d,e,f){if(0!==c||d!==b.length||null!==e)return void f(enosys());const g=this.writeSync(a,b);f(null,g)}};const encoder=new TextEncoder("utf-8"),decoder=new TextDecoder("utf-8");class Go{constructor(){this.argv=["js"],this.env={},this.exit=a=>{0!==a&&console.warn("exit code:",a)},this._exitPromise=new Promise(a=>{this._resolveExitPromise=a}),this._pendingEvent=null,this._scheduledTimeouts=new Map,this._nextCallbackTimeoutID=1;const a=(a,b)=>{this.mem.setUint32(a+0,b,!0),this.mem.setUint32(a+4,Math.floor(b/4294967296),!0)},b=a=>{const b=this.mem.getUint32(a+0,!0),c=this.mem.getInt32(a+4,!0);return b+4294967296*c},c=a=>{const b=this.mem.getFloat64(a,!0);if(0!==b){if(!isNaN(b))return b;const c=this.mem.getUint32(a,!0);return this._values[c]}},d=(a,b)=>{const c=2146959360;if("number"==typeof b)return isNaN(b)?(this.mem.setUint32(a+4,2146959360,!0),void this.mem.setUint32(a,0,!0)):0===b?(this.mem.setUint32(a+4,2146959360,!0),void this.mem.setUint32(a,1,!0)):void this.mem.setFloat64(a,b,!0);switch(b){case void 0:return void this.mem.setFloat64(a,0,!0);case null:return this.mem.setUint32(a+4,c,!0),void this.mem.setUint32(a,2,!0);case!0:return this.mem.setUint32(a+4,c,!0),void this.mem.setUint32(a,3,!0);case!1:return this.mem.setUint32(a+4,c,!0),void this.mem.setUint32(a,4,!0);}let d=this._ids.get(b);d===void 0&&(d=this._idPool.pop(),d===void 0&&(d=this._values.length),this._values[d]=b,this._goRefCounts[d]=0,this._ids.set(b,d)),this._goRefCounts[d]++;let e=1;switch(typeof b){case"string":e=2;break;case"symbol":e=3;break;case"function":e=4;}this.mem.setUint32(a+4,2146959360|e,!0),this.mem.setUint32(a,d,!0)},e=a=>{const c=b(a+0),d=b(a+8);return new Uint8Array(this._inst.exports.mem.buffer,c,d)},f=d=>{const e=b(d+0),f=b(d+8),g=Array(f);for(let a=0;a<f;a++)g[a]=c(e+8*a);return g},g=a=>{const c=b(a+0),d=b(a+8);return decoder.decode(new DataView(this._inst.exports.mem.buffer,c,d))},h=Date.now()-performance.now();this.importObject={go:{"runtime.wasmExit":a=>{const b=this.mem.getInt32(a+8,!0);this.exited=!0,delete this._inst,delete this._values,delete this._goRefCounts,delete this._ids,delete this._idPool,this.exit(b)},"runtime.wasmWrite":a=>{const c=b(a+8),d=b(a+16),e=this.mem.getInt32(a+24,!0);fs.writeSync(c,new Uint8Array(this._inst.exports.mem.buffer,d,e))},"runtime.resetMemoryDataView":()=>{this.mem=new DataView(this._inst.exports.mem.buffer)},"runtime.nanotime1":b=>{a(b+8,1e6*(h+performance.now()))},"runtime.walltime1":b=>{const c=new Date().getTime();a(b+8,c/1e3),this.mem.setInt32(b+16,1e6*(c%1e3),!0)},"runtime.scheduleTimeoutEvent":a=>{const c=this._nextCallbackTimeoutID;this._nextCallbackTimeoutID++,this._scheduledTimeouts.set(c,setTimeout(()=>{for(this._resume();this._scheduledTimeouts.has(c);)console.warn("scheduleTimeoutEvent: missed timeout event"),this._resume()},b(a+8)+1)),this.mem.setInt32(a+16,c,!0)},"runtime.clearTimeoutEvent":a=>{const b=this.mem.getInt32(a+8,!0);clearTimeout(this._scheduledTimeouts.get(b)),this._scheduledTimeouts.delete(b)},"runtime.getRandomData":a=>{crypto.getRandomValues(e(a+8))},"syscall/js.finalizeRef":a=>{const b=this.mem.getUint32(a+8,!0);if(this._goRefCounts[b]--,0===this._goRefCounts[b]){const a=this._values[b];this._values[b]=null,this._ids.delete(a),this._idPool.push(b)}},"syscall/js.stringVal":a=>{d(a+24,g(a+8))},"syscall/js.valueGet":a=>{const b=Reflect.get(c(a+8),g(a+16));a=this._inst.exports.getsp(),d(a+32,b)},"syscall/js.valueSet":a=>{Reflect.set(c(a+8),g(a+16),c(a+32))},"syscall/js.valueDelete":a=>{Reflect.deleteProperty(c(a+8),g(a+16))},"syscall/js.valueIndex":a=>{d(a+24,Reflect.get(c(a+8),b(a+16)))},"syscall/js.valueSetIndex":a=>{Reflect.set(c(a+8),b(a+16),c(a+24))},"syscall/js.valueCall":a=>{try{const b=c(a+8),e=Reflect.get(b,g(a+16)),h=f(a+32),i=Reflect.apply(e,b,h);a=this._inst.exports.getsp(),d(a+56,i),this.mem.setUint8(a+64,1)}catch(b){d(a+56,b),this.mem.setUint8(a+64,0)}},"syscall/js.valueInvoke":a=>{try{const b=c(a+8),e=f(a+16),g=Reflect.apply(b,void 0,e);a=this._inst.exports.getsp(),d(a+40,g),this.mem.setUint8(a+48,1)}catch(b){d(a+40,b),this.mem.setUint8(a+48,0)}},"syscall/js.valueNew":a=>{try{const b=c(a+8),e=f(a+16),g=Reflect.construct(b,e);a=this._inst.exports.getsp(),d(a+40,g),this.mem.setUint8(a+48,1)}catch(b){d(a+40,b),this.mem.setUint8(a+48,0)}},"syscall/js.valueLength":b=>{a(b+16,parseInt(c(b+8).length))},"syscall/js.valuePrepareString":b=>{const e=encoder.encode(c(b+8)+"");d(b+16,e),a(b+24,e.length)},"syscall/js.valueLoadString":a=>{const b=c(a+8);e(a+16).set(b)},"syscall/js.valueInstanceOf":a=>{this.mem.setUint8(a+24,c(a+8)instanceof c(a+16))},"syscall/js.copyBytesToGo":b=>{const d=e(b+8),f=c(b+32);if(!(f instanceof Uint8Array))return void this.mem.setUint8(b+48,0);const g=f.subarray(0,d.length);d.set(g),a(b+40,g.length),this.mem.setUint8(b+48,1)},"syscall/js.copyBytesToJS":b=>{const d=c(b+8),f=e(b+16);if(!(d instanceof Uint8Array))return void this.mem.setUint8(b+48,0);const g=f.subarray(0,d.length);d.set(g),a(b+40,g.length),this.mem.setUint8(b+48,1)},debug:a=>{console.log(a)}}}}async run(a){this._inst=a,this.mem=new DataView(this._inst.exports.mem.buffer),this._values=[NaN,0,null,!0,!1,window,this],this._goRefCounts=[],this._ids=new Map,this._idPool=[],this.exited=!1;let b=4096;const c=a=>{const c=b,d=encoder.encode(a+"\0");return new Uint8Array(this.mem.buffer,b,d.length).set(d),b+=d.length,0!=b%8&&(b+=8-b%8),c},d=this.argv.length,e=[];this.argv.forEach(a=>{e.push(c(a))}),e.push(0);const f=Object.keys(this.env).sort();f.forEach(a=>{e.push(c(a+"="+this.env[a]))}),e.push(0);const g=b;e.forEach(a=>{this.mem.setUint32(b,a,!0),this.mem.setUint32(b+4,0,!0),b+=8}),this._inst.exports.run(d,g),this.exited&&this._resolveExitPromise(),await this._exitPromise}_resume(){if(this.exited)throw new Error("Go program has already exited");this._inst.exports.resume(),this.exited&&this._resolveExitPromise()}_makeFuncWrapper(a){const b=this;return function(){const c={id:a,this:this,args:arguments};return b._pendingEvent=c,b._resume(),c.result}}}const go=new Go;WebAssembly.instantiateStreaming(fetch("__path__"),go.importObject).then(a=>go.run(a.instance)).catch(a=>console.error("could not load wasm",a));`
|
|
25
|
+
|
|
26
|
+
// func wasmExecJs(path string) string {
|
|
27
|
+
// return strings.Replace(wasmExecTemplate, "__path__", path, 1)
|
|
28
|
+
// }
|
|
29
|
+
|
|
30
|
+
// var watchDelta = 1000 * time.Millisecond
|
|
31
|
+
|
|
32
|
+
// type Watcher struct {
|
|
33
|
+
// rootdir string
|
|
34
|
+
// watcher *fsnotify.Watcher
|
|
35
|
+
// watchVendor bool
|
|
36
|
+
// update chan string
|
|
37
|
+
// }
|
|
38
|
+
|
|
39
|
+
// // MustRegisterWatcher creates a new Watcher and starts listening to
|
|
40
|
+
// // given folders
|
|
41
|
+
// func MustRegisterWatcher() *Watcher {
|
|
42
|
+
// w := &Watcher{
|
|
43
|
+
// update: make(chan string),
|
|
44
|
+
// watchVendor: false,
|
|
45
|
+
// }
|
|
46
|
+
// var err error
|
|
47
|
+
// w.watcher, err = fsnotify.NewWatcher()
|
|
48
|
+
// if err != nil {
|
|
49
|
+
// log.Fatalf("Could not register watcher: %s", err)
|
|
50
|
+
// }
|
|
51
|
+
// w.watchFolders()
|
|
52
|
+
// return w
|
|
53
|
+
// }
|
|
54
|
+
|
|
55
|
+
// // Watch listens file updates, and sends signal to
|
|
56
|
+
// // update channel when .go and .tmpl files are updated
|
|
57
|
+
// func (w *Watcher) Watch() {
|
|
58
|
+
// eventSent := false
|
|
59
|
+
// for {
|
|
60
|
+
// select {
|
|
61
|
+
// case event := <-w.watcher.Events:
|
|
62
|
+
// // discard chmod events
|
|
63
|
+
// if event.Op&fsnotify.Chmod != fsnotify.Chmod {
|
|
64
|
+
// // test files do not need a rebuild
|
|
65
|
+
// if isTestFile(event.Name) {
|
|
66
|
+
// continue
|
|
67
|
+
// }
|
|
68
|
+
// if !isWatchedFileType(event.Name) {
|
|
69
|
+
// continue
|
|
70
|
+
// }
|
|
71
|
+
// if eventSent {
|
|
72
|
+
// continue
|
|
73
|
+
// }
|
|
74
|
+
// eventSent = true
|
|
75
|
+
// // prevent consequent builds
|
|
76
|
+
// go func() {
|
|
77
|
+
// w.update <- event.Name
|
|
78
|
+
// time.Sleep(watchDelta)
|
|
79
|
+
// eventSent = false
|
|
80
|
+
// }()
|
|
81
|
+
|
|
82
|
+
// }
|
|
83
|
+
// case err := <-w.watcher.Errors:
|
|
84
|
+
// if err != nil {
|
|
85
|
+
// log.Fatalf("Watcher error: %s", err)
|
|
86
|
+
// }
|
|
87
|
+
// return
|
|
88
|
+
// }
|
|
89
|
+
// }
|
|
90
|
+
// }
|
|
91
|
+
|
|
92
|
+
// func isTestFile(fileName string) bool {
|
|
93
|
+
// return strings.HasSuffix(filepath.Base(fileName), "_test.go")
|
|
94
|
+
// }
|
|
95
|
+
|
|
96
|
+
// func isWatchedFileType(fileName string) bool {
|
|
97
|
+
// ext := filepath.Ext(fileName)
|
|
98
|
+
|
|
99
|
+
// return ext == ".go"
|
|
100
|
+
// }
|
|
101
|
+
|
|
102
|
+
// // Close closes the fsnotify watcher channel
|
|
103
|
+
// func (w *Watcher) Close() {
|
|
104
|
+
// w.watcher.Close()
|
|
105
|
+
// close(w.update)
|
|
106
|
+
// }
|
|
107
|
+
|
|
108
|
+
// // watchFolders recursively adds folders that will be watched against the changes,
|
|
109
|
+
// // starting from the working directory
|
|
110
|
+
// func (w *Watcher) watchFolders() {
|
|
111
|
+
// wd, err := os.Getwd()
|
|
112
|
+
|
|
113
|
+
// if err != nil {
|
|
114
|
+
// log.Fatalf("Could not get root working directory: %s", err)
|
|
115
|
+
// }
|
|
116
|
+
|
|
117
|
+
// filepath.Walk(wd, func(path string, info os.FileInfo, err error) error {
|
|
118
|
+
// // skip files
|
|
119
|
+
// if info == nil {
|
|
120
|
+
// log.Fatalf("wrong watcher package: %s", path)
|
|
121
|
+
// }
|
|
122
|
+
|
|
123
|
+
// if !info.IsDir() {
|
|
124
|
+
// return nil
|
|
125
|
+
// }
|
|
126
|
+
|
|
127
|
+
// if !w.watchVendor {
|
|
128
|
+
// // skip vendor directory
|
|
129
|
+
// vendor := fmt.Sprintf("%s/vendor", wd)
|
|
130
|
+
// if strings.HasPrefix(path, vendor) {
|
|
131
|
+
// return filepath.SkipDir
|
|
132
|
+
// }
|
|
133
|
+
// }
|
|
134
|
+
|
|
135
|
+
// // skip hidden folders
|
|
136
|
+
// if len(path) > 1 && strings.HasPrefix(filepath.Base(path), ".") {
|
|
137
|
+
// return filepath.SkipDir
|
|
138
|
+
// }
|
|
139
|
+
|
|
140
|
+
// w.addFolder(path)
|
|
141
|
+
|
|
142
|
+
// return err
|
|
143
|
+
// })
|
|
144
|
+
// }
|
|
145
|
+
|
|
146
|
+
// // addFolder adds given folder name to the watched folders, and starts
|
|
147
|
+
// // watching it for further changes
|
|
148
|
+
// func (w *Watcher) addFolder(name string) {
|
|
149
|
+
// println("watch: " + name)
|
|
150
|
+
// if err := w.watcher.Add(name); err != nil {
|
|
151
|
+
// log.Fatalf("Could not watch folder: %s", err)
|
|
152
|
+
// }
|
|
153
|
+
// }
|
|
154
|
+
|
|
155
|
+
// type RouteInfo struct {
|
|
156
|
+
// Path string
|
|
157
|
+
// FuncName string
|
|
158
|
+
// RenderFunc func(*app.RenderContext) app.UI
|
|
159
|
+
// }
|
|
160
|
+
|
|
161
|
+
// var routesMap = map[string]RouteInfo{}
|
|
162
|
+
|
|
163
|
+
// func getSOPath(p string) string {
|
|
164
|
+
// return "build/" + filepath.Base(filepath.Dir(p)) + ".so"
|
|
165
|
+
// }
|
|
166
|
+
|
|
167
|
+
// func getWasmPath(p string) string {
|
|
168
|
+
// return "build/" + filepath.Base(filepath.Dir(p)) + ".wasm"
|
|
169
|
+
// }
|
|
170
|
+
|
|
171
|
+
// func buildSo(path string) (string, error) {
|
|
172
|
+
// soPath := getSOPath(path)
|
|
173
|
+
// out, err := exec.Command("go", "build", "-buildmode=plugin", "-o", soPath, path).CombinedOutput()
|
|
174
|
+
// if err != nil {
|
|
175
|
+
// println(string(out))
|
|
176
|
+
// println(err.Error())
|
|
177
|
+
// return "", err
|
|
178
|
+
// }
|
|
179
|
+
// fmt.Printf("wrote: %s\n", soPath)
|
|
180
|
+
// return soPath, nil
|
|
181
|
+
// }
|
|
182
|
+
|
|
183
|
+
// func buildWasm(path string) (string, error) {
|
|
184
|
+
// wasmPath := getWasmPath(path)
|
|
185
|
+
// cmd := exec.Command("go", "build", "-o", wasmPath, path)
|
|
186
|
+
// cmd.Env = os.Environ()
|
|
187
|
+
// cmd.Env = append(cmd.Env, "GOOS=js", "GOARCH=wasm")
|
|
188
|
+
// out, err := cmd.CombinedOutput()
|
|
189
|
+
// if err != nil {
|
|
190
|
+
// println(string(out))
|
|
191
|
+
// println(err.Error())
|
|
192
|
+
// return "", err
|
|
193
|
+
// }
|
|
194
|
+
// fmt.Printf("wrote: %s\n", wasmPath)
|
|
195
|
+
// return wasmPath, nil
|
|
196
|
+
// }
|
|
197
|
+
|
|
198
|
+
// func getRoute(basePath, p string) (string, string) {
|
|
199
|
+
// clean := strings.Replace(strings.Replace(p, basePath, "", 1), ".go", "", -1)
|
|
200
|
+
// return strings.Replace(clean, "index", "", -1), strings.Title(strings.Replace(clean, "/", "", -1))
|
|
201
|
+
// }
|
|
202
|
+
|
|
203
|
+
// func writeMainFile(basePath string) {
|
|
204
|
+
// tpl, err := template.New("writeMain").Parse(`// GENERATED FILE DO NOT EDIT
|
|
205
|
+
// package main
|
|
206
|
+
|
|
207
|
+
// import (
|
|
208
|
+
// . "github.com/pyros2097/wapp"
|
|
209
|
+
// "github.com/pyros2097/wapp/js"
|
|
210
|
+
// )
|
|
211
|
+
|
|
212
|
+
// func main() {
|
|
213
|
+
// {{range $key, $element := .}}
|
|
214
|
+
// if js.Window.URL().Path == "{{.Path}}" {
|
|
215
|
+
// Run({{.FuncName}})
|
|
216
|
+
// }
|
|
217
|
+
// {{end}}
|
|
218
|
+
// }
|
|
219
|
+
// `)
|
|
220
|
+
// if err != nil {
|
|
221
|
+
// panic(err)
|
|
222
|
+
// }
|
|
223
|
+
// buf := bytes.NewBuffer(nil)
|
|
224
|
+
// err = tpl.Execute(buf, routesMap)
|
|
225
|
+
// if err != nil {
|
|
226
|
+
// panic(err)
|
|
227
|
+
// }
|
|
228
|
+
// err = ioutil.WriteFile("pages/main.go", buf.Bytes(), 0644)
|
|
229
|
+
// if err != nil {
|
|
230
|
+
// panic(err)
|
|
231
|
+
// }
|
|
232
|
+
// }
|
|
233
|
+
|
|
234
|
+
// func loadRoutes(p *plugin.Plugin, basePath string, dry bool) {
|
|
235
|
+
// err := filepath.Walk(basePath, func(path string, info os.FileInfo, err error) error {
|
|
236
|
+
// if err != nil {
|
|
237
|
+
// fmt.Printf("prevent panic by handling failure accessing a path %q: %v\n", path, err)
|
|
238
|
+
// return err
|
|
239
|
+
// }
|
|
240
|
+
// if !info.IsDir() {
|
|
241
|
+
// routePath, routeFunc := getRoute(basePath, path)
|
|
242
|
+
// if routePath == "/main" {
|
|
243
|
+
// return nil
|
|
244
|
+
// }
|
|
245
|
+
// routesMap[routePath] = RouteInfo{
|
|
246
|
+
// Path: routePath,
|
|
247
|
+
// FuncName: routeFunc,
|
|
248
|
+
// }
|
|
249
|
+
// if dry {
|
|
250
|
+
// // println("Dry")
|
|
251
|
+
// return nil
|
|
252
|
+
// }
|
|
253
|
+
// fmt.Printf("route: %s routeFunc: %s\n", routePath, routeFunc)
|
|
254
|
+
// renderFn, err := p.Lookup(routeFunc)
|
|
255
|
+
// if err != nil {
|
|
256
|
+
// return err
|
|
257
|
+
// }
|
|
258
|
+
// routesMap[routePath] = RouteInfo{
|
|
259
|
+
// Path: routePath,
|
|
260
|
+
// FuncName: routeFunc,
|
|
261
|
+
// RenderFunc: renderFn.(func(*app.RenderContext) app.UI),
|
|
262
|
+
// }
|
|
263
|
+
// // println(createPage(routesMap[routePath](app.NewRenderContext())).String())
|
|
264
|
+
// }
|
|
265
|
+
// return nil
|
|
266
|
+
// })
|
|
267
|
+
// if err != nil {
|
|
268
|
+
// fmt.Printf("error walking the path %q: %v\n", basePath, err)
|
|
269
|
+
// panic(err)
|
|
270
|
+
// }
|
|
271
|
+
// }
|
|
272
|
+
|
|
273
|
+
// func CreatePage(title string, ui app.UI, wasmPath string) *bytes.Buffer {
|
|
274
|
+
// page := bytes.NewBuffer(nil)
|
|
275
|
+
// page.WriteString("<!DOCTYPE html>\n")
|
|
276
|
+
// elems := app.FilterUIElems(ui)
|
|
277
|
+
// app.Html(
|
|
278
|
+
// app.Head(
|
|
279
|
+
// app.Title(title),
|
|
280
|
+
// app.Meta("author", "pyros2097"),
|
|
281
|
+
// app.Meta("description", "Description"),
|
|
282
|
+
// app.Meta("keywords", ""),
|
|
283
|
+
// app.Meta("theme-color", ""),
|
|
284
|
+
// app.Meta("viewport", "width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0, viewport-fit=cover"),
|
|
285
|
+
// app.Link("icon", "/assets/icon.png"),
|
|
286
|
+
// app.Link("apple-touch-icon", "/assets/icon.png"),
|
|
287
|
+
// app.Link("stylesheet", "/assets/styles.css"),
|
|
288
|
+
// app.Link("manifest", "manifest"),
|
|
289
|
+
// app.Script(`const enosys = () => { const a = new Error("not implemented"); return a.code = "ENOSYS", a }; let outputBuf = ""; window.fs = { constants: { O_WRONLY: -1, O_RDWR: -1, O_CREAT: -1, O_TRUNC: -1, O_APPEND: -1, O_EXCL: -1 }, writeSync(a, b) { outputBuf += decoder.decode(b); const c = outputBuf.lastIndexOf("\n"); return -1 != c && (console.log(outputBuf.substr(0, c)), outputBuf = outputBuf.substr(c + 1)), b.length }, write(a, b, c, d, e, f) { if (0 !== c || d !== b.length || null !== e) return void f(enosys()); const g = this.writeSync(a, b); f(null, g) } }; const encoder = new TextEncoder("utf-8"), decoder = new TextDecoder("utf-8"); class Go { constructor() { this.argv = ["js"], this.env = {}, this.exit = a => { 0 !== a && console.warn("exit code:", a) }, this._exitPromise = new Promise(a => { this._resolveExitPromise = a }), this._pendingEvent = null, this._scheduledTimeouts = new Map, this._nextCallbackTimeoutID = 1; const a = (a, b) => { this.mem.setUint32(a + 0, b, !0), this.mem.setUint32(a + 4, Math.floor(b / 4294967296), !0) }, b = a => { const b = this.mem.getUint32(a + 0, !0), c = this.mem.getInt32(a + 4, !0); return b + 4294967296 * c }, c = a => { const b = this.mem.getFloat64(a, !0); if (0 !== b) { if (!isNaN(b)) return b; const c = this.mem.getUint32(a, !0); return this._values[c] } }, d = (a, b) => { const c = 2146959360; if ("number" == typeof b) return isNaN(b) ? (this.mem.setUint32(a + 4, 2146959360, !0), void this.mem.setUint32(a, 0, !0)) : 0 === b ? (this.mem.setUint32(a + 4, 2146959360, !0), void this.mem.setUint32(a, 1, !0)) : void this.mem.setFloat64(a, b, !0); switch (b) { case void 0: return void this.mem.setFloat64(a, 0, !0); case null: return this.mem.setUint32(a + 4, c, !0), void this.mem.setUint32(a, 2, !0); case !0: return this.mem.setUint32(a + 4, c, !0), void this.mem.setUint32(a, 3, !0); case !1: return this.mem.setUint32(a + 4, c, !0), void this.mem.setUint32(a, 4, !0); }let d = this._ids.get(b); d === void 0 && (d = this._idPool.pop(), d === void 0 && (d = this._values.length), this._values[d] = b, this._goRefCounts[d] = 0, this._ids.set(b, d)), this._goRefCounts[d]++; let e = 1; switch (typeof b) { case "string": e = 2; break; case "symbol": e = 3; break; case "function": e = 4; }this.mem.setUint32(a + 4, 2146959360 | e, !0), this.mem.setUint32(a, d, !0) }, e = a => { const c = b(a + 0), d = b(a + 8); return new Uint8Array(this._inst.exports.mem.buffer, c, d) }, f = d => { const e = b(d + 0), f = b(d + 8), g = Array(f); for (let a = 0; a < f; a++)g[a] = c(e + 8 * a); return g }, g = a => { const c = b(a + 0), d = b(a + 8); return decoder.decode(new DataView(this._inst.exports.mem.buffer, c, d)) }, h = Date.now() - performance.now(); this.importObject = { go: { "runtime.wasmExit": a => { const b = this.mem.getInt32(a + 8, !0); this.exited = !0, delete this._inst, delete this._values, delete this._goRefCounts, delete this._ids, delete this._idPool, this.exit(b) }, "runtime.wasmWrite": a => { const c = b(a + 8), d = b(a + 16), e = this.mem.getInt32(a + 24, !0); fs.writeSync(c, new Uint8Array(this._inst.exports.mem.buffer, d, e)) }, "runtime.resetMemoryDataView": () => { this.mem = new DataView(this._inst.exports.mem.buffer) }, "runtime.nanotime1": b => { a(b + 8, 1e6 * (h + performance.now())) }, "runtime.walltime1": b => { const c = new Date().getTime(); a(b + 8, c / 1e3), this.mem.setInt32(b + 16, 1e6 * (c % 1e3), !0) }, "runtime.scheduleTimeoutEvent": a => { const c = this._nextCallbackTimeoutID; this._nextCallbackTimeoutID++, this._scheduledTimeouts.set(c, setTimeout(() => { for (this._resume(); this._scheduledTimeouts.has(c);)console.warn("scheduleTimeoutEvent: missed timeout event"), this._resume() }, b(a + 8) + 1)), this.mem.setInt32(a + 16, c, !0) }, "runtime.clearTimeoutEvent": a => { const b = this.mem.getInt32(a + 8, !0); clearTimeout(this._scheduledTimeouts.get(b)), this._scheduledTimeouts.delete(b) }, "runtime.getRandomData": a => { crypto.getRandomValues(e(a + 8)) }, "syscall/js.finalizeRef": a => { const b = this.mem.getUint32(a + 8, !0); if (this._goRefCounts[b]--, 0 === this._goRefCounts[b]) { const a = this._values[b]; this._values[b] = null, this._ids.delete(a), this._idPool.push(b) } }, "syscall/js.stringVal": a => { d(a + 24, g(a + 8)) }, "syscall/js.valueGet": a => { const b = Reflect.get(c(a + 8), g(a + 16)); a = this._inst.exports.getsp(), d(a + 32, b) }, "syscall/js.valueSet": a => { Reflect.set(c(a + 8), g(a + 16), c(a + 32)) }, "syscall/js.valueDelete": a => { Reflect.deleteProperty(c(a + 8), g(a + 16)) }, "syscall/js.valueIndex": a => { d(a + 24, Reflect.get(c(a + 8), b(a + 16))) }, "syscall/js.valueSetIndex": a => { Reflect.set(c(a + 8), b(a + 16), c(a + 24)) }, "syscall/js.valueCall": a => { try { const b = c(a + 8), e = Reflect.get(b, g(a + 16)), h = f(a + 32), i = Reflect.apply(e, b, h); a = this._inst.exports.getsp(), d(a + 56, i), this.mem.setUint8(a + 64, 1) } catch (b) { d(a + 56, b), this.mem.setUint8(a + 64, 0) } }, "syscall/js.valueInvoke": a => { try { const b = c(a + 8), e = f(a + 16), g = Reflect.apply(b, void 0, e); a = this._inst.exports.getsp(), d(a + 40, g), this.mem.setUint8(a + 48, 1) } catch (b) { d(a + 40, b), this.mem.setUint8(a + 48, 0) } }, "syscall/js.valueNew": a => { try { const b = c(a + 8), e = f(a + 16), g = Reflect.construct(b, e); a = this._inst.exports.getsp(), d(a + 40, g), this.mem.setUint8(a + 48, 1) } catch (b) { d(a + 40, b), this.mem.setUint8(a + 48, 0) } }, "syscall/js.valueLength": b => { a(b + 16, parseInt(c(b + 8).length)) }, "syscall/js.valuePrepareString": b => { const e = encoder.encode(c(b + 8) + ""); d(b + 16, e), a(b + 24, e.length) }, "syscall/js.valueLoadString": a => { const b = c(a + 8); e(a + 16).set(b) }, "syscall/js.valueInstanceOf": a => { this.mem.setUint8(a + 24, c(a + 8) instanceof c(a + 16)) }, "syscall/js.copyBytesToGo": b => { const d = e(b + 8), f = c(b + 32); if (!(f instanceof Uint8Array)) return void this.mem.setUint8(b + 48, 0); const g = f.subarray(0, d.length); d.set(g), a(b + 40, g.length), this.mem.setUint8(b + 48, 1) }, "syscall/js.copyBytesToJS": b => { const d = c(b + 8), f = e(b + 16); if (!(d instanceof Uint8Array)) return void this.mem.setUint8(b + 48, 0); const g = f.subarray(0, d.length); d.set(g), a(b + 40, g.length), this.mem.setUint8(b + 48, 1) }, debug: a => { console.log(a) } } } } async run(a) { this._inst = a, this.mem = new DataView(this._inst.exports.mem.buffer), this._values = [NaN, 0, null, !0, !1, window, this], this._goRefCounts = [], this._ids = new Map, this._idPool = [], this.exited = !1; let b = 4096; const c = a => { const c = b, d = encoder.encode(a + "\0"); return new Uint8Array(this.mem.buffer, b, d.length).set(d), b += d.length, 0 != b % 8 && (b += 8 - b % 8), c }, d = this.argv.length, e = []; this.argv.forEach(a => { e.push(c(a)) }), e.push(0); const f = Object.keys(this.env).sort(); f.forEach(a => { e.push(c(a + "=" + this.env[a])) }), e.push(0); const g = b; e.forEach(a => { this.mem.setUint32(b, a, !0), this.mem.setUint32(b + 4, 0, !0), b += 8 }), this._inst.exports.run(d, g), this.exited && this._resolveExitPromise(), await this._exitPromise } _resume() { if (this.exited) throw new Error("Go program has already exited"); this._inst.exports.resume(), this.exited && this._resolveExitPromise() } _makeFuncWrapper(a) { const b = this; return function () { const c = { id: a, this: this, args: arguments }; return b._pendingEvent = c, b._resume(), c.result } } } const go = new Go; WebAssembly.instantiateStreaming(fetch("/assets/main.wasm"), go.importObject).then(a => go.run(a.instance)).catch(a => console.error("could not load wasm", a));`),
|
|
290
|
+
// ),
|
|
291
|
+
// app.Body(elems[0]),
|
|
292
|
+
// ).Html(page)
|
|
293
|
+
// return page
|
|
294
|
+
// }
|
|
295
|
+
|
|
296
|
+
// func serve(wd string, isDev bool) {
|
|
297
|
+
// assetsFileServer := http.FileServer(pkger.Dir(filepath.Join(wd, "assets")))
|
|
298
|
+
// buildFileServer := http.FileServer(pkger.Dir(filepath.Join(wd, "build")))
|
|
299
|
+
// http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
|
300
|
+
// println("path: " + r.URL.Path)
|
|
301
|
+
// if routeInfo, ok := routesMap[r.URL.Path]; ok {
|
|
302
|
+
// wasmPath := "/build/" + filepath.Base(wd) + ".wasm"
|
|
303
|
+
// page := CreatePage(routeInfo.RenderFunc(app.NewRenderContext()), wasmPath)
|
|
304
|
+
// w.Header().Set("Content-Length", strconv.Itoa(page.Len()))
|
|
305
|
+
// w.Header().Set("Content-Type", "text/html")
|
|
306
|
+
// w.WriteHeader(http.StatusOK)
|
|
307
|
+
// w.Write(page.Bytes())
|
|
308
|
+
// } else if strings.Contains(r.URL.Path, "/build") {
|
|
309
|
+
// r.URL.Path = strings.Replace(r.URL.Path, "/build", "", 1)
|
|
310
|
+
// buildFileServer.ServeHTTP(w, r)
|
|
311
|
+
// } else {
|
|
312
|
+
// r.URL.Path = strings.Replace(r.URL.Path, "/assets", "", 1)
|
|
313
|
+
// assetsFileServer.ServeHTTP(w, r)
|
|
314
|
+
// }
|
|
315
|
+
// })
|
|
316
|
+
// if isDev {
|
|
317
|
+
// println("Serving on HTTP port: 1234")
|
|
318
|
+
// http.ListenAndServe(":1234", nil)
|
|
319
|
+
// } else {
|
|
320
|
+
// println("algnhsa serving default mux")
|
|
321
|
+
// algnhsa.ListenAndServe(http.DefaultServeMux, nil)
|
|
322
|
+
// }
|
|
323
|
+
// }
|
|
324
|
+
|
|
325
|
+
// func buildAll(basePath string) {
|
|
326
|
+
// loadRoutes(nil, basePath, true)
|
|
327
|
+
// writeMainFile(basePath)
|
|
328
|
+
// buildWasm(basePath)
|
|
329
|
+
// soPath, err := buildSo(basePath)
|
|
330
|
+
// if err != nil {
|
|
331
|
+
// println("could not build")
|
|
332
|
+
// panic(err)
|
|
333
|
+
// }
|
|
334
|
+
// p, err := plugin.Open(soPath)
|
|
335
|
+
// if err != nil {
|
|
336
|
+
// println("could not load so plugin")
|
|
337
|
+
// panic(err)
|
|
338
|
+
// }
|
|
339
|
+
// // fmt.Printf("%+v\n", p)
|
|
340
|
+
// loadRoutes(p, basePath, false)
|
|
341
|
+
// }
|
|
342
|
+
|
|
343
|
+
// func Watch(isDev bool) {
|
|
344
|
+
// wd, err := os.Getwd()
|
|
345
|
+
// if err != nil {
|
|
346
|
+
// fmt.Printf("could not get wd")
|
|
347
|
+
// return
|
|
348
|
+
// }
|
|
349
|
+
// if isDev {
|
|
350
|
+
// basePath := filepath.Join(wd, "pages")
|
|
351
|
+
// buildAll(basePath)
|
|
352
|
+
// watcher := MustRegisterWatcher()
|
|
353
|
+
// go watcher.Watch()
|
|
354
|
+
// go serve(wd, isDev)
|
|
355
|
+
// for file := range watcher.update {
|
|
356
|
+
// println("changed: " + file)
|
|
357
|
+
// buildAll(basePath)
|
|
358
|
+
// }
|
|
359
|
+
// } else {
|
|
360
|
+
// serve(wd, isDev)
|
|
361
|
+
// }
|
|
362
|
+
// }
|
example/{pages/about.go → about.go}
RENAMED
|
File without changes
|
example/{config.css → assets/config.css}
RENAMED
|
File without changes
|
example/assets/manifest.json
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"short_name": "{{.ShortName}}",
|
|
3
|
-
"name": "{{.Name}}",
|
|
4
|
-
"icons": [
|
|
5
|
-
{
|
|
6
|
-
"src": "{{.DefaultIcon}}",
|
|
7
|
-
"type": "image/png",
|
|
8
|
-
"sizes": "192x192"
|
|
9
|
-
},
|
|
10
|
-
{
|
|
11
|
-
"src": "{{.LargeIcon}}",
|
|
12
|
-
"type": "image/png",
|
|
13
|
-
"sizes": "512x512"
|
|
14
|
-
}
|
|
15
|
-
],
|
|
16
|
-
"scope": "{{.Scope}}",
|
|
17
|
-
"start_url": "{{.StartURL}}",
|
|
18
|
-
"background_color": "{{.BackgroundColor}}",
|
|
19
|
-
"display": "standalone",
|
|
20
|
-
"theme_color": "{{.ThemeColor}}"
|
|
21
|
-
}
|
example/{pages/clock.go → clock.go}
RENAMED
|
File without changes
|
example/{pages/container.go → container.go}
RENAMED
|
File without changes
|
example/{pages/index.go → index.go}
RENAMED
|
File without changes
|
example/main.go
CHANGED
|
@@ -1,9 +1,16 @@
|
|
|
1
1
|
package main
|
|
2
2
|
|
|
3
3
|
import (
|
|
4
|
-
"github.com/pyros2097/wapp
|
|
4
|
+
app "github.com/pyros2097/wapp"
|
|
5
5
|
)
|
|
6
6
|
|
|
7
|
+
var routes = map[string]app.RenderFunc{
|
|
8
|
+
"/about": About,
|
|
9
|
+
"/clock": Clock,
|
|
10
|
+
"/container": Container,
|
|
11
|
+
"/": Index,
|
|
12
|
+
}
|
|
13
|
+
|
|
7
14
|
func main() {
|
|
8
|
-
|
|
15
|
+
app.Run(false, routes)
|
|
9
16
|
}
|
example/makefile
CHANGED
|
@@ -1,2 +1,11 @@
|
|
|
1
|
+
run:
|
|
2
|
+
go run *.go
|
|
3
|
+
|
|
4
|
+
build:
|
|
5
|
+
go build
|
|
6
|
+
|
|
7
|
+
build-wasm:
|
|
8
|
+
GOOS=js GOARCH=wasm go build -o assets/main.wasm
|
|
9
|
+
|
|
1
10
|
build-css:
|
|
2
|
-
npx tailwindcss-cli@latest build config.css -o assets/styles.css
|
|
11
|
+
npx tailwindcss-cli@latest build assets/config.css -o assets/styles.css
|
example/pages/main.go
DELETED
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
// GENERATED FILE DO NOT EDIT
|
|
2
|
-
package main
|
|
3
|
-
|
|
4
|
-
import (
|
|
5
|
-
. "github.com/pyros2097/wapp"
|
|
6
|
-
"github.com/pyros2097/wapp/js"
|
|
7
|
-
)
|
|
8
|
-
|
|
9
|
-
func main() {
|
|
10
|
-
|
|
11
|
-
if js.Window.URL().Path == "/" {
|
|
12
|
-
Run(Index)
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
if js.Window.URL().Path == "/about" {
|
|
16
|
-
Run(About)
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
if js.Window.URL().Path == "/clock" {
|
|
20
|
-
Run(Clock)
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
if js.Window.URL().Path == "/container" {
|
|
24
|
-
Run(Container)
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
}
|
example/readme.md
CHANGED
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
An example to demostrate the wapp framework
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
1. Compile wasm frontend,
|
|
6
|
+
`GOOS=js GOARCH=wasm go build -o assets/main.wasm`
|
|
6
7
|
|
|
8
|
+
2. Run the server,
|
|
7
|
-
`go run
|
|
9
|
+
`go run *.go`
|
|
10
|
+
|
|
11
|
+
These commands are also available in the makefile for convenience
|
example/samconfig.toml
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
version = 0.1
|
|
2
|
+
[default]
|
|
3
|
+
[default.deploy]
|
|
4
|
+
[default.deploy.parameters]
|
|
5
|
+
stack_name = "go-app"
|
|
6
|
+
s3_bucket = "aws-sam-cli-managed-default-samclisourcebucket-grb3hle00qk9"
|
|
7
|
+
s3_prefix = "go-app"
|
|
8
|
+
region = "us-east-1"
|
|
9
|
+
capabilities = "CAPABILITY_IAM"
|
example/template.yml
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
AWSTemplateFormatVersion: 2010-09-09
|
|
2
|
+
Outputs:
|
|
3
|
+
ApiUrl:
|
|
4
|
+
Export:
|
|
5
|
+
Name: ApiUrl
|
|
6
|
+
Value:
|
|
7
|
+
Fn::Sub: https://${HttpApi}.execute-api.${AWS::Region}.${AWS::URLSuffix}/Prod/
|
|
8
|
+
Resources:
|
|
9
|
+
HttpApi:
|
|
10
|
+
Properties:
|
|
11
|
+
AccessLogSettings:
|
|
12
|
+
DestinationArn:
|
|
13
|
+
Fn::GetAtt:
|
|
14
|
+
- HttpApiAccessLogs
|
|
15
|
+
- Arn
|
|
16
|
+
Format:
|
|
17
|
+
'{"requestId":"$context.requestId", "ip": "$context.identity.sourceIp",
|
|
18
|
+
"caller":"$context.identity.caller", "user":"$context.identity.user", "requestTime":"$context.requestTime","routeKey":"$context.routeKey",
|
|
19
|
+
"status":"$context.status", "error": "$context.integrationErrorMessage"}'
|
|
20
|
+
StageName: Prod
|
|
21
|
+
Type: AWS::Serverless::HttpApi
|
|
22
|
+
HttpApiAccessLogs:
|
|
23
|
+
Properties:
|
|
24
|
+
LogGroupName: /aws/http/py-app
|
|
25
|
+
RetentionInDays: 365
|
|
26
|
+
Type: AWS::Logs::LogGroup
|
|
27
|
+
RouteIndex:
|
|
28
|
+
Properties:
|
|
29
|
+
Handler: ./example
|
|
30
|
+
Events:
|
|
31
|
+
ApiEvent:
|
|
32
|
+
Properties:
|
|
33
|
+
ApiId:
|
|
34
|
+
Ref: HttpApi
|
|
35
|
+
Method: GET
|
|
36
|
+
Path: $default
|
|
37
|
+
PayloadFormatVersion: '1.0'
|
|
38
|
+
Type: HttpApi
|
|
39
|
+
MemorySize: 128
|
|
40
|
+
Runtime: go1.x
|
|
41
|
+
Timeout: 30
|
|
42
|
+
Type: AWS::Serverless::Function
|
|
43
|
+
Transform: AWS::Serverless-2016-10-31
|
go.mod
CHANGED
|
@@ -3,7 +3,9 @@ module github.com/pyros2097/wapp
|
|
|
3
3
|
go 1.15
|
|
4
4
|
|
|
5
5
|
require (
|
|
6
|
+
github.com/akrylysov/algnhsa v0.12.1
|
|
7
|
+
github.com/aws/aws-lambda-go v1.20.0
|
|
6
|
-
github.com/
|
|
8
|
+
github.com/awslabs/goformation/v4 v4.15.6
|
|
7
9
|
github.com/markbates/pkger v0.17.1
|
|
8
10
|
github.com/stretchr/testify v1.6.1
|
|
9
11
|
golang.org/x/sys v0.0.0-20201112073958-5cba982894dd // indirect
|
utils.go
CHANGED
|
@@ -2,6 +2,7 @@ package app
|
|
|
2
2
|
|
|
3
3
|
import (
|
|
4
4
|
"io"
|
|
5
|
+
"regexp"
|
|
5
6
|
"unsafe"
|
|
6
7
|
|
|
7
8
|
"github.com/pyros2097/wapp/js"
|
|
@@ -48,3 +49,33 @@ func btos(b []byte) string {
|
|
|
48
49
|
func stob(s string) []byte {
|
|
49
50
|
return *(*[]byte)(unsafe.Pointer(&s))
|
|
50
51
|
}
|
|
52
|
+
|
|
53
|
+
func matchPath(k, p string) bool {
|
|
54
|
+
validRoute := regexp.MustCompile(k)
|
|
55
|
+
if validRoute.MatchString(p) {
|
|
56
|
+
return true
|
|
57
|
+
}
|
|
58
|
+
return false
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
func MatchRoute(routes map[string]RenderFunc, path string) RenderFunc {
|
|
62
|
+
for key, renderFn := range routes {
|
|
63
|
+
if matchPath(key, path) {
|
|
64
|
+
return renderFn
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
notFound, ok := routes["/notfound"]
|
|
68
|
+
if ok {
|
|
69
|
+
return notFound
|
|
70
|
+
}
|
|
71
|
+
return func(c *RenderContext) UI {
|
|
72
|
+
return Col(
|
|
73
|
+
Row(
|
|
74
|
+
"This is the default 404 - Not Found Route handler",
|
|
75
|
+
),
|
|
76
|
+
Row(
|
|
77
|
+
"Create a notfound.go file and add a func NotFound(c *RenderContext) UI {} to override it",
|
|
78
|
+
),
|
|
79
|
+
)
|
|
80
|
+
}
|
|
81
|
+
}
|
utils_test.go
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
package app
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"testing"
|
|
5
|
+
|
|
6
|
+
"github.com/stretchr/testify/assert"
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
func stubRoute(c *RenderContext) UI {
|
|
10
|
+
return Div(Text("Stub"))
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
var testRoutes = map[string]RenderFunc{
|
|
14
|
+
"/about": stubRoute,
|
|
15
|
+
"/clock": stubRoute,
|
|
16
|
+
"/container": stubRoute,
|
|
17
|
+
"/": stubRoute,
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
func TestMatchPath(t *testing.T) {
|
|
21
|
+
assert.Equal(t, true, matchPath("/", "/about"))
|
|
22
|
+
}
|