/
engine.go
116 lines (100 loc) · 2.82 KB
/
engine.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
package ssr
import (
crand `crypto/rand`
`encoding/binary`
`fmt`
`math/rand`
`time`
`github.com/dop251/goja`
`github.com/dop251/goja_nodejs/eventloop`
)
// Inject callback function with this name to js runtime
var callbackFuncName = "__go_ssr_callback__"
type JSEngine struct {
*eventloop.EventLoop
bundle *ScriptBundle
ch chan *Result
fn goja.Callable
// Maximum render time
Timeout time.Duration
}
func (je *JSEngine) Handle(ctx *Context) <-chan *Result {
je.RunOnLoop(func(vm *goja.Runtime) {
obj, err := ctx.ToMap()
if err != nil {
panic(fmt.Errorf("failed to convert ssr context to map: %v", err))
}
if _, err := je.fn(nil, vm.ToValue(obj), vm.ToValue(callbackFuncName)); err != nil {
panic(fmt.Errorf("failed to execute bundle entry function: %v", err))
}
})
return je.ch
}
func NewJSEngine(bundle *ScriptBundle, timeout time.Duration) (*JSEngine, error) {
je := &JSEngine{
EventLoop: eventloop.NewEventLoop(),
ch: make(chan *Result, 1),
bundle: bundle,
Timeout: timeout,
}
je.Start()
je.initRuntimeRandSource()
je.initRuntimeGlobalProcess()
je.initRuntimeScriptBundle()
je.initRuntimeExtractEntryFunc()
je.initRuntimeInjectCallback()
return je, nil
}
func (je *JSEngine) initRuntimeRandSource() {
je.RunOnLoop(func(vm *goja.Runtime) {
var seed int64
if err := binary.Read(crand.Reader, binary.LittleEndian, &seed); err != nil {
panic(fmt.Errorf("failed to read random bytes: %v", err))
}
vm.SetRandSource(rand.New(rand.NewSource(seed)).Float64)
})
}
func (je *JSEngine) initRuntimeGlobalProcess() {
je.RunOnLoop(func(vm *goja.Runtime) {
if err := vm.Set("process", map[string]interface{}{
"env": map[string]string{
"NODE_ENV": "production",
"VUE_ENV": "server",
},
}); err != nil {
panic(fmt.Errorf("failed to inject globa.process to js runtime: %v", err))
}
})
}
func (je *JSEngine) initRuntimeScriptBundle() {
je.RunOnLoop(func(vm *goja.Runtime) {
if _, err := vm.RunString(string(je.bundle.Content)); err != nil {
panic(fmt.Errorf("failed to execute bundle script: %v", err))
}
})
}
func (je *JSEngine) initRuntimeExtractEntryFunc() {
je.RunOnLoop(func(vm *goja.Runtime) {
if fn, ok := goja.AssertFunction(vm.Get(je.bundle.FuncName)); ok {
je.fn = fn
} else {
panic("failed to extract bundle script entry func")
}
})
}
func (je *JSEngine) initRuntimeInjectCallback() {
je.RunOnLoop(func(vm *goja.Runtime) {
callback := func(call goja.FunctionCall) goja.Value {
obj := call.Argument(0).Export().(map[string]interface{})
res, err := NewResultFromMap(obj)
if err != nil {
panic(fmt.Errorf("failed to convert map to ssr result: %v", err))
}
je.ch <- res
return nil
}
if err := vm.Set(callbackFuncName, callback); err != nil {
panic(fmt.Errorf("failed to inject callback function: %v", err))
}
})
}