diff --git a/app.yaml b/app.yaml new file mode 100644 index 0000000..79375f8 --- /dev/null +++ b/app.yaml @@ -0,0 +1,7 @@ +runtime: nodejs +vm: true +env_variables: + NODE_ENV: production + +skip_files: + - ^(.*/)?.*/node_modules/.*$ \ No newline at end of file diff --git a/package.json b/package.json index ce63795..136d677 100644 --- a/package.json +++ b/package.json @@ -9,16 +9,24 @@ "url": "http://github.com/insin/react-hn.git" }, "scripts": { - "build": "npm run lint && cp node_modules/sw-toolbox/sw-toolbox.js public/sw-toolbox.js && nwb build && npm run precache", + "build": "npm run lint && cp node_modules/sw-toolbox/sw-toolbox.js public/sw-toolbox.js && ./node_modules/.bin/nwb build && npm run precache", + "deploy": "gcloud preview app deploy", "lint": "./node_modules/eslint-config-jonnybuchanan/bin/lint.js src", "lint:fix": "./node_modules/eslint-config-jonnybuchanan/bin/lint.js --fix .", - "start": "nwb serve", - "precache": "sw-precache --root=public --config=sw-precache-config.json" + "start": "node server.js", + "postinstall": "npm run build", + "serve": "./node_modules/.bin/nwb serve", + "precache": "./node_modules/sw-precache/cli.js --root=public --config=sw-precache-config.json" }, + "engines": { + "node": "6.1.0" + }, + "main": "server.js", "dependencies": { + "ejs": "^2.4.1", "events": "1.1.0", + "express": "^4.13.4", "firebase": "2.4.2", - "firetruck.js": "0.1.1", "history": "2.1.1", "isomorphic-fetch": "^2.2.1", "react": "15.0.2", @@ -27,9 +35,8 @@ "react-timeago": "3.0.0", "reactfire": "0.7.0", "scroll-behavior": "0.5.0", - "setimmediate": "1.0.4" - }, - "devDependencies": { + "setimmediate": "1.0.4", + "url-parse": "^1.1.1", "eslint-config-jonnybuchanan": "2.0.3", "nwb": "0.8.1", "sw-precache": "^3.1.1", diff --git a/public/index.html b/public/index-static.html similarity index 100% rename from public/index.html rename to public/index-static.html diff --git a/server.js b/server.js new file mode 100644 index 0000000..dda4625 --- /dev/null +++ b/server.js @@ -0,0 +1,44 @@ +var express = require('express') +var React = require('react') +var renderToString = require('react-dom/server').renderToString +var ReactRouter = require('react-router') + +require('babel/register') +var routes = require('./src/routes') + +var app = express() +app.set('view engine', 'ejs') +app.set('views', process.cwd() + '/src/views') +app.set('port', (process.env.PORT || 5000)) +app.use(express.static('public')) + +app.get('*', function(req, res) { + ReactRouter.match({ + routes: routes, + location: req.url + }, function(err, redirectLocation, props) { + if (err) { + res.status(500).send(err.message) + } + else if (redirectLocation) { + res.redirect(302, redirectLocation.pathname + redirectLocation.search) + } + else if (props) { + var markup = renderToString( + React.createElement(ReactRouter.RouterContext, props, null) + ) + res.render('index', { markup: markup }) + } + else { + res.sendStatus(404) + } + }) +}) + +app.listen(app.get('port'), function(err) { + if (err) { + console.log(err) + return + } + console.log('Running app at localhost:' + app.get('port')) +}) diff --git a/src/App.js b/src/App.js index 9c6427c..15aaa7a 100644 --- a/src/App.js +++ b/src/App.js @@ -1,4 +1,3 @@ -/* global __VERSION__ */ var React = require('react') var Link = require('react-router/lib/Link') @@ -19,10 +18,12 @@ var App = React.createClass({ SettingsStore.load() StoryStore.loadSession() UpdatesStore.loadSession() + if (typeof window === 'undefined') return window.addEventListener('beforeunload', this.handleBeforeUnload) }, componentWillUnmount() { + if (typeof window === 'undefined') return window.removeEventListener('beforeunload', this.handleBeforeUnload) }, @@ -60,7 +61,6 @@ var App = React.createClass({ {this.props.children}
- {`react-hn v${__VERSION__} | `} insin/react-hn
diff --git a/src/Stories.js b/src/Stories.js index 8dc8fda..c60d523 100644 --- a/src/Stories.js +++ b/src/Stories.js @@ -35,7 +35,7 @@ var Stories = React.createClass({ } }, - componentWillMount() { + componentDidMount() { setTitle(this.props.title) this.store = new StoryStore(this.props.type) this.store.addListener('update', this.handleUpdate) diff --git a/src/mixins/ItemMixin.js b/src/mixins/ItemMixin.js index a4fcd6b..6712ade 100644 --- a/src/mixins/ItemMixin.js +++ b/src/mixins/ItemMixin.js @@ -4,18 +4,16 @@ var TimeAgo = require('react-timeago').default var SettingsStore = require('../stores/SettingsStore') var pluralise = require('../utils/pluralise') +var urlParse = require('url-parse') -var parseHost = (function() { - var a = document.createElement('a') - return function(url) { - a.href = url - var parts = a.hostname.split('.').slice(-3) - if (parts[0] === 'www') { - parts.shift() - } - return parts.join('.') +var parseHost = function(url) { + var hostname = (urlParse(url, true)).hostname + var parts = hostname.split('.').slice(-3) + if (parts[0] === 'www') { + parts.shift() } -})() + return parts.join('.') +} /** * Reusable logic for displaying an item. diff --git a/src/stores/StoryStore.js b/src/stores/StoryStore.js index 1765171..2d903b3 100644 --- a/src/stores/StoryStore.js +++ b/src/stores/StoryStore.js @@ -104,6 +104,7 @@ class StoryStore extends EventEmitter { } start() { + if (typeof window === 'undefined') return if (SettingsStore.offlineMode) { HNServiceRest.storiesRef(this.type).then(function(res) { return res.json() @@ -125,6 +126,7 @@ class StoryStore extends EventEmitter { } firebaseRef = null } + if (typeof window === 'undefined') return window.removeEventListener('storage', this.onStorage) } } @@ -142,6 +144,7 @@ extend(StoryStore, { * Deserialise caches from sessionStorage. */ loadSession() { + if (typeof window === 'undefined') return if (SettingsStore.offlineMode) { idCache = parseJSON(window.localStorage.idCache, {}) itemCache = parseJSON(window.localStorage.itemCache, {}) @@ -156,6 +159,7 @@ extend(StoryStore, { * Serialise caches to sessionStorage as JSON. */ saveSession() { + if (typeof window === 'undefined') return if (SettingsStore.offlineMode) { window.localStorage.setItem('idCache', JSON.stringify(idCache)) window.localStorage.setItem('itemCache', JSON.stringify(itemCache)) diff --git a/src/stores/UpdatesStore.js b/src/stores/UpdatesStore.js index 58051bc..dd676c5 100644 --- a/src/stores/UpdatesStore.js +++ b/src/stores/UpdatesStore.js @@ -101,12 +101,14 @@ function handleUpdateItems(items) { var UpdatesStore = extend(new EventEmitter(), { loadSession() { + if (typeof window === 'undefined') return var json = window.sessionStorage.updates updatesCache = (json ? JSON.parse(json) : {comments: {}, stories: {}}) populateUpdates() }, saveSession() { + if (typeof window === 'undefined') return window.sessionStorage.updates = JSON.stringify(updatesCache) }, diff --git a/src/utils/setTitle.js b/src/utils/setTitle.js index e432fe5..34e2faf 100644 --- a/src/utils/setTitle.js +++ b/src/utils/setTitle.js @@ -1,6 +1,7 @@ var {SITE_TITLE} = require('./constants') function setTitle(title) { + if (typeof document === 'undefined') return document.title = (title ? title + ' | ' + SITE_TITLE : SITE_TITLE) } diff --git a/src/utils/storage.js b/src/utils/storage.js index f64187c..da727c4 100644 --- a/src/utils/storage.js +++ b/src/utils/storage.js @@ -1,9 +1,17 @@ module.exports = { get(key, defaultValue) { - var value = window.localStorage[key] - return (typeof value != 'undefined' ? value : defaultValue) + if (typeof window === 'undefined') { + return defaultValue + } + else { + var value = window.localStorage[key] + return (typeof value != 'undefined' ? value : defaultValue) + } }, set(key, value) { - window.localStorage[key] = value + if (typeof window !== 'undefined') { + window.localStorage[key] = value + } } } + diff --git a/src/views/index.ejs b/src/views/index.ejs new file mode 100644 index 0000000..202afd3 --- /dev/null +++ b/src/views/index.ejs @@ -0,0 +1,70 @@ + + + + + + React HN + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
<%- markup %>
+ + + + + \ No newline at end of file