The code which I'm writing here is running at http://isomorphic.uutispuro.fi/.
I had earlier come across Echo, (Echo is a fast HTTP router (zero memory allocation) and micro web framework in Go), which I had to try.
e := echo.New()
// server side rendered pages
e.Get("/", index)
e.Get("/anotherpage", anotherpage)
// static files
e.Static("/public/js", "public/js")
e.Static("/public/css", "public/css")
// backend api proxy
e.Get("/api/frontpage", apiFrontPage)
e.Get("/api/anotherpage", apiAnotherPage)
// server side rendered pages
e.Get("/", index)
e.Get("/anotherpage", anotherpage)
// static files
e.Static("/public/js", "public/js")
e.Static("/public/css", "public/css")
// backend api proxy
e.Get("/api/frontpage", apiFrontPage)
e.Get("/api/anotherpage", apiAnotherPage)
e.Run(":3000")
Quite simple really. Easy to add gzip and other middleware.
A little illustration of what we are doing
First the scheduling:
func tick() {
v := newRenderer([]string{"public/js/frontpage.js", "public/js/common.js"}).
runCmd(`
var data = ` + rss + `;
React.renderToString(News({'data' : data}));
`)
sValue, err := v.ToString()
if err != nil {
fmt.Println(err)
}
frontPageRendered = sValue
}
We make a newRenderer to which we give two needed javascript files and then run the runCmd which interprets the javascript into html. This is done with React ids, so that when the same is done in the client, rendering doesn't need to be done again.
A little illustration of what we are doing
Server side rendering
We will get a RSS feed from BBC scheduled and in the background and save the fetched feed and it's rendered version for later use.First the scheduling:
func tick() {
// schelude ticker for every 70 seconds
ticker := time.NewTicker(70 * time.Second)
for {
ticker := time.NewTicker(70 * time.Second)
for {
// fetch the rss feed
fetchFeed()
fetchFeed()
// render the rss feed
reactIndex()
<-ticker.C
}
}
Rendering:
func reactIndex() {reactIndex()
<-ticker.C
}
}
Rendering:
v := newRenderer([]string{"public/js/frontpage.js", "public/js/common.js"}).
runCmd(`
var data = ` + rss + `;
React.renderToString(News({'data' : data}));
`)
sValue, err := v.ToString()
if err != nil {
fmt.Println(err)
}
frontPageRendered = sValue
}
We make a newRenderer to which we give two needed javascript files and then run the runCmd which interprets the javascript into html. This is done with React ids, so that when the same is done in the client, rendering doesn't need to be done again.
Client side rendering
What we do at the client end to start up React and how do we handle going from page to an other?We add a client.js into use for the browser.
var xmlhttp = new XMLHttpRequest();
var route = function() {
if (window.location.pathname === '/') {
xmlhttp.onreadystatechange = function() {
if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
var json = xmlhttp.responseText;
React.render(News({'data' : JSON.parse(json)}), document.getElementById('body'));
}
}
xmlhttp.open("GET", "/api/frontpage", true);
xmlhttp.send();
}
if (window.location.pathname === '/anotherpage') {
xmlhttp.onreadystatechange = function() {
if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
var json = xmlhttp.responseText;
React.render(Another({'data' : JSON.parse(json)}), document.getElementById('body'));
}
}
xmlhttp.open("GET", "/api/anotherpage", true);
xmlhttp.send();
}
}
route();
We have two api routes, one for root page and one for the second page. Both routes get json from api endpoints. After the function we call for the initial React check (React.render). The rendering would actually be enough after full page load, but this is a simplified solution.
At frontpage.js and anotherpage.js we have the navigation links (duplicate code, should be a components on its own).
React.DOM.div({className: "pure-menu pure-menu-horizontal"},
React.createElement("a", {
className: "pure-menu-heading pure-menu-link",
href: "/",
onClick: clickHandler
}, "Home"),
React.DOM.ul({className: "pure-menu-list"},
React.DOM.li({className: "pure-menu-item"},
React.createElement("a", {
href: "/anotherpage",
onClick: clickHandler
}, "Another page")
)
)
)
Clicks for these navigation links call for clickHandler which is in common.js
var clickHandler = function(e) {
e.preventDefault()
history.pushState({}, "", e.target.href);
route();
}
First we disable making the whole page load, second we make history.pushState for altering the browser location bar, and finally call for route to change the page content according to changed location.
You can view the whole code at https://github.com/jelinden/go-isomorphic-react and see it in action at http://isomorphic.uutispuro.fi/.
If someone uses this for own purposes, bare in mind that there are many unfinished things. Client side is made so that "it works". Doesn't mean that everything is polished or that everything works. For example entering backspace doesn't load the page content as it should.
Another major setback is the fact that the javascript interpreter is way too slow. You can't use it to render server side on the fly. On my Mac rendering front page takes about 4 seconds.
Blogin hallinnoija on poistanut tämän kommentin.
VastaaPoistaThe content is good and very informative and I personally thank you for sharing React.js articles.
VastaaPoistaBlogin hallinnoija on poistanut tämän kommentin.
VastaaPoistaBlogin hallinnoija on poistanut tämän kommentin.
VastaaPoista