github.com/vugu/vugu@v0.3.5/staticrender/renderer-static.go (about) 1 package staticrender 2 3 import ( 4 "fmt" 5 "io" 6 "strings" 7 "sync" 8 9 // "golang.org/x/net/html" 10 // "golang.org/x/net/html/atom" 11 "github.com/vugu/html" 12 "github.com/vugu/html/atom" 13 "github.com/vugu/vugu" 14 ) 15 16 // caller should be able to just specify a directory, 17 // or provide custom logic for how to give an exact io.Writer to write to for a given output path, 18 // should be callable a bunch of times for different URLs 19 20 // New returns a new instance. w may be nil as long as SetWriter is called with a valid value before rendering. 21 func New(w io.Writer) *StaticRenderer { 22 return &StaticRenderer{ 23 w: w, 24 } 25 } 26 27 // StaticRenderer provides rendering as static HTML to an io.Writer. 28 type StaticRenderer struct { 29 w io.Writer 30 } 31 32 // SetWriter assigns the Writer to be used for subsequent calls to Render. 33 // A Writer must be assigned before rendering (either with this method or in New). 34 func (r *StaticRenderer) SetWriter(w io.Writer) { 35 r.w = w 36 } 37 38 // Render will perform a static render of the given BuildResults and write it to the writer assigned. 39 func (r *StaticRenderer) Render(buildResults *vugu.BuildResults) error { 40 41 n, err := r.renderOne(buildResults, buildResults.Out) 42 if err != nil { 43 return err 44 } 45 46 err = html.Render(r.w, n) 47 if err != nil { 48 return err 49 } 50 51 return nil 52 53 } 54 55 func (r *StaticRenderer) renderOne(br *vugu.BuildResults, bo *vugu.BuildOut) (*html.Node, error) { 56 57 if len(bo.Out) != 1 { 58 return nil, fmt.Errorf("BuildOut must contain exactly one element in Out") 59 } 60 61 vgn := bo.Out[0] 62 63 var visit func(vgn *vugu.VGNode) ([]*html.Node, error) 64 visit = func(vgn *vugu.VGNode) ([]*html.Node, error) { 65 66 // log.Printf("vgn: %#v", vgn) 67 68 // if component then look up BuildOut for it and call renderOne again and return 69 if vgn.Component != nil { 70 cbo := br.ResultFor(vgn.Component) 71 retn, err := r.renderOne(br, cbo) 72 if err != nil { 73 return nil, err 74 } 75 return []*html.Node{retn}, nil 76 // if len(retn) != 1 { 77 // return nil, fmt.Errorf("StaticRenderer.renderOne component renderOne returned unexpected %d nodes", len(retn)) 78 // } 79 // return retn[0], nil 80 } 81 82 // if template then just traverse the children directly and return them in a series, omitting vgn 83 if vgn.IsTemplate() { 84 var retn []*html.Node 85 for vgchild := vgn.FirstChild; vgchild != nil; vgchild = vgchild.NextSibling { 86 nchildren, err := visit(vgchild) 87 if err != nil { 88 return nil, err 89 } 90 retn = append(retn, nchildren...) 91 } 92 return retn, nil 93 } 94 95 // no component set, continue with building this node 96 n := &html.Node{} 97 n.Type = html.NodeType(vgn.Type) // type numbers are the same 98 n.Data = vgn.Data // copy Data over 99 n.DataAtom = atom.Lookup([]byte(vgn.Data)) // lookup atom 100 for _, vgattr := range vgn.Attr { 101 n.Attr = append(n.Attr, html.Attribute{Key: vgattr.Key, Val: vgattr.Val}) 102 } 103 104 // handle InnerHTML 105 106 if vgn.InnerHTML != nil { 107 108 nparts, err := html.ParseFragment(strings.NewReader(*vgn.InnerHTML), n) 109 if err != nil { 110 return nil, err 111 } 112 113 // for _, nc := range nparts { 114 // n.AppendChild(nc) 115 // } 116 appendChildren(n, nparts) 117 118 // InnerHTML precludes other children 119 return []*html.Node{n}, nil 120 } 121 122 // handle children 123 for vgchild := vgn.FirstChild; vgchild != nil; vgchild = vgchild.NextSibling { 124 nchildren, err := visit(vgchild) 125 if err != nil { 126 return nil, err 127 } 128 // n.AppendChild(nchildren) 129 appendChildren(n, nchildren) 130 } 131 132 // special case for <head>, we need to emit the CSS here as that is separate 133 // (Vugu build output does not always have a head tag and multiple components 134 // can each emit it, so we have to keep things like CSS separate) 135 if n.Type == html.ElementNode && n.Data == "head" { 136 for _, css := range bo.CSS { 137 138 // convert each one 139 nchildren, err := visit(css) 140 if err != nil { 141 return nil, err 142 } 143 // n.AppendChild(n2) 144 appendChildren(n, nchildren) 145 } 146 } 147 148 // special case to append JS to end of <body> 149 if n.Type == html.ElementNode && n.Data == "body" { 150 for _, js := range bo.JS { 151 152 // convert each one 153 nchildren, err := visit(js) 154 if err != nil { 155 return nil, err 156 } 157 // n.AppendChild(n2) 158 appendChildren(n, nchildren) 159 } 160 } 161 162 return []*html.Node{n}, nil 163 } 164 165 nret, err := visit(vgn) 166 if err != nil { 167 return nil, err 168 } 169 if len(nret) != 1 { 170 return nil, fmt.Errorf("StaticRenderer.renderOne visit returned unexpected %d nodes", len(nret)) 171 } 172 return nret[0], nil 173 } 174 175 func appendChildren(parent *html.Node, children []*html.Node) { 176 for _, c := range children { 177 parent.AppendChild(c) 178 } 179 } 180 181 // EventEnv returns a simple EventEnv implementation suitable for use with the static renderer. 182 func (r *StaticRenderer) EventEnv() *RWMutexEventEnv { 183 return &RWMutexEventEnv{} 184 } 185 186 // RWMutexEventEnv implements EventEnv interface with a mutex but no render behavior. 187 // (Because static rendering is driven by the caller, there is no way for dynamic logic 188 // to re-render a static page, so UnlockRender is the same as UnlockOnly.) 189 type RWMutexEventEnv struct { 190 sync.RWMutex 191 } 192 193 // UnlockOnly will release the write lock 194 func (ee *RWMutexEventEnv) UnlockOnly() { ee.Unlock() } 195 196 // UnlockRender is an alias for UnlockOnly. 197 func (ee *RWMutexEventEnv) UnlockRender() { ee.Unlock() }