├── LICENSE ├── README.md ├── assets ├── demo1.js ├── demo2.js ├── demo3.js ├── global.js └── react.js ├── main.go ├── main_test.go └── renderer.go /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 - 101 Loops UG 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | React.js server-side rendering with Go 2 | ===== 3 | 4 | This experiment is based on [otto](https://github.com/robertkrimen/otto), a Javascript interpreter for Go. 5 | 6 | To run: 7 | ```bash 8 | go build ./... 9 | ./go-reactjs 10 | ``` 11 | 12 | To benchmark: 13 | ```bash 14 | go test -bench=. 15 | ``` 16 | 17 | The results on my MacBook Pro (2.4 GHz i5): 18 | ```bash 19 | BenchmarkRender1 100 17128739 ns/op 20 | BenchmarkRender5 50 47324904 ns/op 21 | BenchmarkRender10 20 79839996 ns/op 22 | BenchmarkRender20 10 164226676 ns/op 23 | BenchmarkRender50 2 612836671 ns/op 24 | BenchmarkRender100 1 1777275883 ns/op 25 | BenchmarkRender200 1 4190131936 ns/op 26 | BenchmarkRender500 1 20789000942 ns/op 27 | 28 | ok github.com/101loops/go-reactjs 39.314s 29 | ``` 30 | -------------------------------------------------------------------------------- /assets/demo1.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Source: https://github.com/robertkrimen/otto/issues/67 3 | */ 4 | var React = self.React; 5 | 6 | var CommentBox = React.createClass({ 7 | render: function () { 8 | return ( 9 | React.DOM.div({ 10 | className: 'commentBox', 11 | children: 'Hello, world! I am a CommentBox.' 12 | }) 13 | ); 14 | } 15 | }); -------------------------------------------------------------------------------- /assets/demo2.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Source: http://facebook.github.io/react/tips/expose-component-functions.html 3 | */ 4 | var React = self.React; 5 | 6 | var HelloWorld = React.createClass({displayName: 'HelloWorld', 7 | render: function() { 8 | return ( 9 | React.DOM.p(null, 10 | "Hello, ", React.DOM.input({type: "text", placeholder: "Your name here"}), "!" 11 | ) 12 | ); 13 | } 14 | }); -------------------------------------------------------------------------------- /assets/demo3.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Source: http://facebook.github.io/react/docs/tutorial.html 3 | */ 4 | 5 | var React = self.React; 6 | 7 | var CommentBox = React.createClass({displayName: 'CommentBox', 8 | render: function () { 9 | var commentList = React.createElement(CommentList, 10 | this.props 11 | ); 12 | return ( 13 | React.createElement( 14 | "div", 15 | {}, 16 | React.DOM.h1(null, "Comments"), 17 | commentList 18 | ) 19 | ); 20 | } 21 | }); 22 | 23 | var CommentList = React.createClass({displayName: 'CommentList', 24 | render: function () { 25 | var commentNodes = this.props.data.map(function (comment) { 26 | return React.createElement( 27 | Comment, 28 | {key: comment.id, author: comment.author}, 29 | comment.text 30 | ); 31 | }); 32 | return ( 33 | React.DOM.div({className: "commentList"}, 34 | commentNodes 35 | ) 36 | ); 37 | } 38 | }); 39 | 40 | var Comment = React.createClass({displayName: 'Comment', 41 | render: function () { 42 | return ( 43 | React.DOM.div({className: "comment"}, 44 | React.DOM.h2({className: "commentAuthor"}, this.props.author), 45 | this.props.children 46 | ) 47 | ); 48 | } 49 | }); -------------------------------------------------------------------------------- /assets/global.js: -------------------------------------------------------------------------------- 1 | var self = {}; -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func main() { 6 | v := newRenderer([]string{"assets/demo1.js"}). 7 | runCmd(` 8 | var component = React.createElement(CommentBox, { foo: 'bar' }); 9 | React.renderToString(component); 10 | `) 11 | fmt.Printf("\n%v\n", v) 12 | 13 | v = newRenderer([]string{"assets/demo2.js"}). 14 | runCmd(` 15 | var component = React.createElement(HelloWorld, { foo: 'bar' }); 16 | React.renderToString(component); 17 | `) 18 | fmt.Printf("\n%v\n", v) 19 | 20 | v = newRenderer([]string{"assets/demo3.js"}). 21 | runCmd(` 22 | var data = [ 23 | {"id": 0, "author": "Anonymous", "text": "This is a comment"}, 24 | {"id": 1, "author": "Anonymous", "text": "This is another comment"}, 25 | ] 26 | var component = React.createElement(CommentBox, {data : data}); 27 | React.renderToString(component); 28 | `) 29 | fmt.Printf("\n%v\n", v) 30 | } 31 | -------------------------------------------------------------------------------- /main_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "strconv" 5 | "testing" 6 | ) 7 | 8 | func benchmarkRender(i int, b *testing.B) { 9 | r := newRenderer([]string{"assets/demo3.js"}) 10 | for n := 0; n < b.N; n++ { 11 | r.runCmd(` 12 | var data = []; 13 | for (i = 0; i < ` + strconv.Itoa(i) + `; i++) { 14 | data.push({"id": i, "author": "Anonymous", "text": "This is comment #" + i}); 15 | } 16 | React.renderComponentToString(CommentBox({data : data})); 17 | `) 18 | } 19 | } 20 | 21 | func BenchmarkRender1(b *testing.B) { benchmarkRender(1, b) } 22 | func BenchmarkRender5(b *testing.B) { benchmarkRender(5, b) } 23 | func BenchmarkRender10(b *testing.B) { benchmarkRender(10, b) } 24 | func BenchmarkRender20(b *testing.B) { benchmarkRender(20, b) } 25 | func BenchmarkRender50(b *testing.B) { benchmarkRender(50, b) } 26 | func BenchmarkRender100(b *testing.B) { benchmarkRender(100, b) } 27 | func BenchmarkRender200(b *testing.B) { benchmarkRender(200, b) } 28 | func BenchmarkRender500(b *testing.B) { benchmarkRender(500, b) } 29 | -------------------------------------------------------------------------------- /renderer.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "io/ioutil" 5 | 6 | "github.com/robertkrimen/otto" 7 | ) 8 | 9 | type renderer struct { 10 | *otto.Otto 11 | } 12 | 13 | func newRenderer(files []string) *renderer { 14 | r := &renderer{otto.New()} 15 | r.runFile("assets/global.js") 16 | r.runFile("assets/react.js") 17 | r.runFiles(files) 18 | return r 19 | } 20 | 21 | func (r *renderer) runCmd(cmd string) otto.Value { 22 | v, err := r.Run(cmd) 23 | if err != nil { 24 | panic(err) 25 | } 26 | return v 27 | } 28 | 29 | func (r *renderer) runFiles(files []string) { 30 | for _, file := range files { 31 | r.runFile(file) 32 | } 33 | } 34 | 35 | func (r *renderer) runFile(path string) otto.Value { 36 | data, err := ioutil.ReadFile(path) 37 | if err != nil { 38 | panic(err) 39 | } 40 | 41 | result, err := r.Run(data) 42 | if err != nil { 43 | panic(err) 44 | } 45 | 46 | return result 47 | } 48 | --------------------------------------------------------------------------------