Skip to content

Commit b2eefd2

Browse files
committed
Merge pull request #35 from insin/isomorphic-shell
Adds isomorphic/server-side React application-shell rendering
2 parents 3aba35e + bac1f38 commit b2eefd2

File tree

12 files changed

+164
-23
lines changed

12 files changed

+164
-23
lines changed

app.yaml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
runtime: nodejs
2+
vm: true
3+
env_variables:
4+
NODE_ENV: production
5+
6+
skip_files:
7+
- ^(.*/)?.*/node_modules/.*$

package.json

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,24 @@
99
"url": "http://github.com/insin/react-hn.git"
1010
},
1111
"scripts": {
12-
"build": "npm run lint && cp node_modules/sw-toolbox/sw-toolbox.js public/sw-toolbox.js && nwb build && npm run precache",
12+
"build": "npm run lint && cp node_modules/sw-toolbox/sw-toolbox.js public/sw-toolbox.js && ./node_modules/.bin/nwb build && npm run precache",
13+
"deploy": "gcloud preview app deploy",
1314
"lint": "./node_modules/eslint-config-jonnybuchanan/bin/lint.js src",
1415
"lint:fix": "./node_modules/eslint-config-jonnybuchanan/bin/lint.js --fix .",
15-
"start": "nwb serve",
16-
"precache": "sw-precache --root=public --config=sw-precache-config.json"
16+
"start": "node server.js",
17+
"postinstall": "npm run build",
18+
"serve": "./node_modules/.bin/nwb serve",
19+
"precache": "./node_modules/sw-precache/cli.js --root=public --config=sw-precache-config.json"
1720
},
21+
"engines": {
22+
"node": "6.1.0"
23+
},
24+
"main": "server.js",
1825
"dependencies": {
26+
"ejs": "^2.4.1",
1927
"events": "1.1.0",
28+
"express": "^4.13.4",
2029
"firebase": "2.4.2",
21-
"firetruck.js": "0.1.1",
2230
"history": "2.1.1",
2331
"isomorphic-fetch": "^2.2.1",
2432
"react": "15.0.2",
@@ -27,9 +35,8 @@
2735
"react-timeago": "3.0.0",
2836
"reactfire": "0.7.0",
2937
"scroll-behavior": "0.5.0",
30-
"setimmediate": "1.0.4"
31-
},
32-
"devDependencies": {
38+
"setimmediate": "1.0.4",
39+
"url-parse": "^1.1.1",
3340
"eslint-config-jonnybuchanan": "2.0.3",
3441
"nwb": "0.8.1",
3542
"sw-precache": "^3.1.1",
File renamed without changes.

server.js

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
var express = require('express')
2+
var React = require('react')
3+
var renderToString = require('react-dom/server').renderToString
4+
var ReactRouter = require('react-router')
5+
6+
require('babel/register')
7+
var routes = require('./src/routes')
8+
9+
var app = express()
10+
app.set('view engine', 'ejs')
11+
app.set('views', process.cwd() + '/src/views')
12+
app.set('port', (process.env.PORT || 5000))
13+
app.use(express.static('public'))
14+
15+
app.get('*', function(req, res) {
16+
ReactRouter.match({
17+
routes: routes,
18+
location: req.url
19+
}, function(err, redirectLocation, props) {
20+
if (err) {
21+
res.status(500).send(err.message)
22+
}
23+
else if (redirectLocation) {
24+
res.redirect(302, redirectLocation.pathname + redirectLocation.search)
25+
}
26+
else if (props) {
27+
var markup = renderToString(
28+
React.createElement(ReactRouter.RouterContext, props, null)
29+
)
30+
res.render('index', { markup: markup })
31+
}
32+
else {
33+
res.sendStatus(404)
34+
}
35+
})
36+
})
37+
38+
app.listen(app.get('port'), function(err) {
39+
if (err) {
40+
console.log(err)
41+
return
42+
}
43+
console.log('Running app at localhost:' + app.get('port'))
44+
})

src/App.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
/* global __VERSION__ */
21
var React = require('react')
32
var Link = require('react-router/lib/Link')
43

@@ -19,10 +18,12 @@ var App = React.createClass({
1918
SettingsStore.load()
2019
StoryStore.loadSession()
2120
UpdatesStore.loadSession()
21+
if (typeof window === 'undefined') return
2222
window.addEventListener('beforeunload', this.handleBeforeUnload)
2323
},
2424

2525
componentWillUnmount() {
26+
if (typeof window === 'undefined') return
2627
window.removeEventListener('beforeunload', this.handleBeforeUnload)
2728
},
2829

@@ -60,7 +61,6 @@ var App = React.createClass({
6061
{this.props.children}
6162
</div>
6263
<div className="App__footer">
63-
{`react-hn v${__VERSION__} | `}
6464
<a href="https://github.com/insin/react-hn">insin/react-hn</a>
6565
</div>
6666
</div>

src/Stories.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ var Stories = React.createClass({
3535
}
3636
},
3737

38-
componentWillMount() {
38+
componentDidMount() {
3939
setTitle(this.props.title)
4040
this.store = new StoryStore(this.props.type)
4141
this.store.addListener('update', this.handleUpdate)

src/mixins/ItemMixin.js

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,16 @@ var TimeAgo = require('react-timeago').default
44

55
var SettingsStore = require('../stores/SettingsStore')
66
var pluralise = require('../utils/pluralise')
7+
var urlParse = require('url-parse')
78

8-
var parseHost = (function() {
9-
var a = document.createElement('a')
10-
return function(url) {
11-
a.href = url
12-
var parts = a.hostname.split('.').slice(-3)
13-
if (parts[0] === 'www') {
14-
parts.shift()
15-
}
16-
return parts.join('.')
9+
var parseHost = function(url) {
10+
var hostname = (urlParse(url, true)).hostname
11+
var parts = hostname.split('.').slice(-3)
12+
if (parts[0] === 'www') {
13+
parts.shift()
1714
}
18-
})()
15+
return parts.join('.')
16+
}
1917

2018
/**
2119
* Reusable logic for displaying an item.

src/stores/StoryStore.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ class StoryStore extends EventEmitter {
104104
}
105105

106106
start() {
107+
if (typeof window === 'undefined') return
107108
if (SettingsStore.offlineMode) {
108109
HNServiceRest.storiesRef(this.type).then(function(res) {
109110
return res.json()
@@ -125,6 +126,7 @@ class StoryStore extends EventEmitter {
125126
}
126127
firebaseRef = null
127128
}
129+
if (typeof window === 'undefined') return
128130
window.removeEventListener('storage', this.onStorage)
129131
}
130132
}
@@ -142,6 +144,7 @@ extend(StoryStore, {
142144
* Deserialise caches from sessionStorage.
143145
*/
144146
loadSession() {
147+
if (typeof window === 'undefined') return
145148
if (SettingsStore.offlineMode) {
146149
idCache = parseJSON(window.localStorage.idCache, {})
147150
itemCache = parseJSON(window.localStorage.itemCache, {})
@@ -156,6 +159,7 @@ extend(StoryStore, {
156159
* Serialise caches to sessionStorage as JSON.
157160
*/
158161
saveSession() {
162+
if (typeof window === 'undefined') return
159163
if (SettingsStore.offlineMode) {
160164
window.localStorage.setItem('idCache', JSON.stringify(idCache))
161165
window.localStorage.setItem('itemCache', JSON.stringify(itemCache))

src/stores/UpdatesStore.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,12 +101,14 @@ function handleUpdateItems(items) {
101101

102102
var UpdatesStore = extend(new EventEmitter(), {
103103
loadSession() {
104+
if (typeof window === 'undefined') return
104105
var json = window.sessionStorage.updates
105106
updatesCache = (json ? JSON.parse(json) : {comments: {}, stories: {}})
106107
populateUpdates()
107108
},
108109

109110
saveSession() {
111+
if (typeof window === 'undefined') return
110112
window.sessionStorage.updates = JSON.stringify(updatesCache)
111113
},
112114

src/utils/setTitle.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
var {SITE_TITLE} = require('./constants')
22

33
function setTitle(title) {
4+
if (typeof document === 'undefined') return
45
document.title = (title ? title + ' | ' + SITE_TITLE : SITE_TITLE)
56
}
67

0 commit comments

Comments
 (0)