-
Notifications
You must be signed in to change notification settings - Fork 0
/
env-static-html.go
232 lines (191 loc) · 5.63 KB
/
env-static-html.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
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
package vugu
import (
"bytes"
"fmt"
"io"
"golang.org/x/net/html/atom"
"golang.org/x/net/html"
)
var _ Env = (*StaticHTMLEnv)(nil) // assert type
// StaticHTMLEnv is an environment that renders to static HTML. Can be used to implement "server side rendering"
// as well as during testing.
type StaticHTMLEnv struct {
reg ComponentTypeMap
// ComponentTypeMap map[string]ComponentType // TODO: probably make this it's own type and have a global instance where things can register
rootInst *ComponentInst
out io.Writer
}
// NewStaticHTMLEnv returns a new instance of StaticHTMLEnv initialized properly.
// The out and rootInst are required, components may be nil.
func NewStaticHTMLEnv(out io.Writer, rootInst *ComponentInst, components ComponentTypeMap) *StaticHTMLEnv {
if components == nil {
components = make(ComponentTypeMap)
}
return &StaticHTMLEnv{
reg: components,
rootInst: rootInst,
out: out,
}
}
// RegisterComponentType sets a component type with its tag name.
func (e *StaticHTMLEnv) RegisterComponentType(tagName string, ct ComponentType) {
e.reg[tagName] = ct
}
// Render performs redering to static HTML. The logic used is similar to JSEnv.Render
// however it will discard DOM events and is less careful about managing component lifecycle:
// components are simply created when needed and discarded after.
func (e *StaticHTMLEnv) Render() error {
c := e.rootInst
out := e.out
vdom, css, err := c.Type.BuildVDOM(c.Data)
if err != nil {
return err
}
// addCss := func(vcss *VGNode) {
// css.AppendChild(vcss.FirstChild)
// }
// walk the vdom and handle components along the way;
// since this is a static render, we can be pretty naive
// about how and when we instantiate the components
// compInstMap := make(map[*VGNode]*ComponentInst)
err = vdom.Walk(func(vgn *VGNode) error {
// must be element
if vgn.Type != ElementNode {
return nil
}
// element name must match a component
ct, ok := e.reg[vgn.Data]
if !ok {
return nil
}
// copy props and merge in static attributes where they don't conflict
props := vgn.Props.Clone()
for _, a := range vgn.Attr {
if _, ok := props[a.Key]; !ok {
props[a.Key] = a.Val
}
}
// just make a new instance each time - this is static html output
compInst, err := New(ct, props)
if err != nil {
return err
}
cdom, ccss, err := ct.BuildVDOM(compInst.Data)
if err != nil {
return err
}
if ccss != nil && ccss.FirstChild != nil {
css.AppendChild(ccss.FirstChild)
}
// make cdom replace vgn
// point Parent on each child of cdom to vgn
for cn := cdom.FirstChild; cn != nil; cn = cn.NextSibling {
cn.Parent = vgn
}
// replace vgn with cdom but preserve vgn.Parent, vgn.PrevSibling, vgn.NextSibling
*vgn, vgn.Parent, vgn.PrevSibling, vgn.NextSibling = *cdom, vgn.Parent, vgn.PrevSibling, vgn.NextSibling
return nil
})
// The basic strategy is to build an equivalent html.Node tree from our vdom, expanding InnerHTML along
// the way, and then tell the html package to write it out
// output css
if css != nil && css.FirstChild != nil {
cssn := &html.Node{
Type: html.ElementNode,
Data: "style",
DataAtom: atom.Style,
}
cssn.AppendChild(&html.Node{
Type: html.TextNode,
Data: css.FirstChild.Data,
})
err = html.Render(out, cssn)
if err != nil {
return err
}
}
ptrMap := make(map[*VGNode]*html.Node)
var conv func(*VGNode) (*html.Node, error)
conv = func(vgn *VGNode) (*html.Node, error) {
if vgn == nil {
return nil, nil
}
// see if it's already in map, if so just return it
if n := ptrMap[vgn]; n != nil {
return n, nil
}
var err error
n := &html.Node{}
// assign this first thing, so that everything below when it recurses will just point to the same instance
ptrMap[vgn] = n
// for all node pointers we recursively call conv, which will convert them or just return the pointer if already done
// Parent
n.Parent, err = conv(vgn.Parent)
if err != nil {
return n, err
}
// FirstChild
n.FirstChild, err = conv(vgn.FirstChild)
if err != nil {
return n, err
}
// LastChild
n.LastChild, err = conv(vgn.LastChild)
if err != nil {
return n, err
}
// PrevSibling
n.PrevSibling, err = conv(vgn.PrevSibling)
if err != nil {
return n, err
}
// NextSibling
n.NextSibling, err = conv(vgn.NextSibling)
if err != nil {
return n, err
}
// copy the other type and attr info
n.Type = html.NodeType(vgn.Type)
n.DataAtom = atom.Atom(vgn.DataAtom)
n.Data = vgn.Data
n.Namespace = vgn.Namespace
for _, vgnAttr := range vgn.Attr {
n.Attr = append(n.Attr, html.Attribute{Namespace: vgnAttr.Namespace, Key: vgnAttr.Key, Val: vgnAttr.Val})
}
// for bound properties we fmt.Sprint and assign as attrs
propKeys := vgn.Props.OrderedKeys()
propAttrLoop:
for _, k := range propKeys {
for i := range n.Attr {
if n.Attr[i].Key == k { // if attr is there, overwrite
n.Attr[i].Val = fmt.Sprint(vgn.Props[k])
continue propAttrLoop
}
}
// attr not there, add
n.Attr = append(n.Attr, html.Attribute{Key: k, Val: fmt.Sprint(vgn.Props[k])})
}
// parse and expand InnerHTML if present
if vgn.InnerHTML != "" {
innerNs, err := html.ParseFragment(bytes.NewReader([]byte(vgn.InnerHTML)), cruftBody)
if err != nil {
return nil, err
}
// FIXME: do we just append all of this, what about case where there is already something inside?
for _, innerN := range innerNs {
n.AppendChild(innerN)
}
}
return n, nil
}
outn, err := conv(vdom)
if err != nil {
return err
}
// log.Printf("outn: %#v", outn)
err = html.Render(out, outn)
if err != nil {
return err
}
return nil
}