From fd3fc0f62089b60c60b77a7c9546a94d012c8d94 Mon Sep 17 00:00:00 2001 From: bill bittner Date: Wed, 21 Feb 2018 08:05:41 -0800 Subject: [PATCH] added handleRender and renderFullPage and updated client --- README.md | 2 +- helpers/handleRender.jsx | 32 +++++++++++++ helpers/renderFullPage.js | 21 +++++++++ speech.js => index.js | 10 +--- package.json | 6 ++- react/{index.js => client.js} | 18 +++++--- routes/page-routes.js | 1 + webpack.config.js | 87 ++++++++++++++++++++++++----------- 8 files changed, 134 insertions(+), 43 deletions(-) create mode 100644 helpers/handleRender.jsx create mode 100644 helpers/renderFullPage.js rename speech.js => index.js (89%) rename react/{index.js => client.js} (58%) diff --git a/README.md b/README.md index 974834d1..fe4bc9a3 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ spee.ch is a single-serving site that reads and publishes images and videos to a * create your `speechConfig.js` file * copy `speechConfig.js.example` and name it `speechConfig.js` * replace the `null` values in the config file with the appropriate values for your environement - * to start the server, from your command line run `node speech.js` + * to start the server, from your command line run `node serverindex.js` * To run hot, use `nodemon` instead of `node` * visit [localhost:3000](http://localhost:3000) diff --git a/helpers/handleRender.jsx b/helpers/handleRender.jsx new file mode 100644 index 00000000..ee5a4388 --- /dev/null +++ b/helpers/handleRender.jsx @@ -0,0 +1,32 @@ +import { renderToString } from 'react-dom/server'; +import { createStore } from 'redux'; +import Reducer from '../react/reducers'; +import renderFullPage from './renderFullPage.js'; + +import StaticRouter from 'react-router-dom/StaticRouter'; +import GAListener from '../react/components/GAListener'; +import App from '../react/app'; + +module.exports = (req, res) => { + let context = {}; + + // create a new Redux store instance + const store = createStore(Reducer); + + // render component to a string + const html = renderToString( + + + + + + + + ); + + // get the initial state from our Redux store + const preloadedState = store.getState(); + + // send the rendered page back to the client + res.send(renderFullPage(html, preloadedState)); +}; diff --git a/helpers/renderFullPage.js b/helpers/renderFullPage.js new file mode 100644 index 00000000..986c3c28 --- /dev/null +++ b/helpers/renderFullPage.js @@ -0,0 +1,21 @@ +module.exports = (html, preloadedState) => { + // take the html and preloadedState and return the full page + return ` + + + + + + + +
+
${html}
+
+ + + + + `; +}; diff --git a/speech.js b/index.js similarity index 89% rename from speech.js rename to index.js index 524cb5f6..0a3d8621 100644 --- a/speech.js +++ b/index.js @@ -7,7 +7,6 @@ const handlebarsHelpers = require('./helpers/handlebarsHelpers.js'); const { populateLocalsDotUser, serializeSpeechUser, deserializeSpeechUser } = require('./helpers/authHelpers.js'); const config = require('./config/speechConfig.js'); const logger = require('winston'); -const { getDownloadDirectory } = require('./helpers/lbryApi'); const helmet = require('helmet'); const PORT = 3000; // set port const app = express(); // create an Express application @@ -67,14 +66,7 @@ app.use(populateLocalsDotUser); // start the server db.sequelize .sync() // sync sequelize - .then(() => { // get the download directory from the daemon - logger.info('Retrieving daemon download directory...'); - return getDownloadDirectory(); - }) - .then(hostedContentPath => { - // add the hosted content folder at a static path - app.use('/media', express.static(hostedContentPath)); - // require routes + .then(() => { // require routes require('./routes/auth-routes.js')(app); require('./routes/api-routes.js')(app); require('./routes/page-routes.js')(app); diff --git a/package.json b/package.json index 96861b3f..6fef9160 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "scripts": { "test": "mocha --recursive", "test-all": "mocha --recursive", - "start": "node speech.js", + "start": "node server.js", "lint": "eslint .", "fix": "eslint . --fix", "precommit": "eslint .", @@ -57,6 +57,7 @@ "sequelize-cli": "^3.0.0-3", "sleep": "^5.1.1", "universal-analytics": "^0.4.13", + "webpack-node-externals": "^1.6.0", "whatwg-fetch": "^2.0.3", "winston": "^2.3.1", "winston-slack-webhook": "billbitt/winston-slack-webhook" @@ -64,12 +65,15 @@ "devDependencies": { "babel-core": "^6.26.0", "babel-loader": "^7.1.2", + "babel-plugin-transform-object-rest-spread": "^6.26.0", "babel-polyfill": "^6.26.0", "babel-preset-es2015": "^6.24.1", "babel-preset-react": "^6.24.1", "babel-preset-stage-2": "^6.24.1", + "babel-register": "^6.26.0", "chai": "^4.1.2", "chai-http": "^3.0.0", + "css-loader": "^0.28.9", "eslint": "3.19.0", "eslint-config-standard": "10.2.1", "eslint-plugin-import": "^2.2.0", diff --git a/react/index.js b/react/client.js similarity index 58% rename from react/index.js rename to react/client.js index 8ef02cf4..01117eb2 100644 --- a/react/index.js +++ b/react/client.js @@ -10,18 +10,24 @@ import rootSaga from 'sagas'; import GAListener from 'components/GAListener'; import App from './app'; +// get the state from a global variable injected into the server-generated HTML +const preloadedState = window.__PRELOADED_STATE__ || null; + +// Allow the passed state to be garbage-collected +delete window.__PRELOADED_STATE__ + +// create and apply middleware const sagaMiddleware = createSagaMiddleware(); const middleware = applyMiddleware(sagaMiddleware); +const reduxMiddleware = window.__REDUX_DEVTOOLS_EXTENSION__ ? compose(middleware, window.__REDUX_DEVTOOLS_EXTENSION__()) : middleware; -const enhancer = window.__REDUX_DEVTOOLS_EXTENSION__ ? compose(middleware, window.__REDUX_DEVTOOLS_EXTENSION__()) : middleware; - -let store = createStore( - Reducer, - enhancer, -); +// create teh store +let store = createStore(Reducer, preloadedState, reduxMiddleware); +// run the saga middlweare sagaMiddleware.run(rootSaga); +// render the app render( diff --git a/routes/page-routes.js b/routes/page-routes.js index 9bb05b03..afc00b18 100644 --- a/routes/page-routes.js +++ b/routes/page-routes.js @@ -1,4 +1,5 @@ const { site } = require('../config/speechConfig.js'); +// const handleRender = require('../helpers/handleRender.jsx'); module.exports = (app) => { // route for the home page diff --git a/webpack.config.js b/webpack.config.js index a0447013..a9999013 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -1,32 +1,67 @@ const Path = require('path'); - +const nodeExternals = require('webpack-node-externals'); const REACT_ROOT = Path.resolve(__dirname, 'react/'); -module.exports = { - entry : ['babel-polyfill', 'whatwg-fetch', './react/index.js'], - output: { - path : Path.join(__dirname, '/public/bundle/'), - filename: 'bundle.js', - }, - watch : true, - module: { - loaders: [ - { - test : /.jsx?$/, - loader : 'babel-loader', - exclude: /node_modules/, - query : { - presets: ['es2015', 'react', 'stage-2'], +console.log('REACT_ROOT:', REACT_ROOT); + +module.exports = [ + { + target: 'web', + entry : ['babel-polyfill', 'whatwg-fetch', './react/client.js'], + output: { + path : Path.join(__dirname, 'public/bundle/'), + publicPath: 'public/bundle/', + filename : 'bundle.js', + }, + watch : true, + module: { + loaders: [ + { + test : /.jsx?$/, + loader : 'babel-loader', + exclude: /node_modules/, + query : { + presets: ['es2015', 'react', 'stage-2'], + }, }, - }, - ], + ], + }, + resolve: { + modules: [ + REACT_ROOT, + 'node_modules', + __dirname, + ], + extensions: ['.js', '.jsx', '.scss'], + }, }, - resolve: { - modules: [ - REACT_ROOT, - 'node_modules', - __dirname, - ], - extensions: ['.js', '.jsx', '.scss'], + { + target : 'node', + externals: nodeExternals(), + entry : ['./index.js'], + output : { + path : __dirname, + publicPath: '', + filename : 'server.js', + }, + watch : true, + module: { + loaders: [ + { + loader : 'babel-loader', + exclude: /node_modules/, + query : { + presets: ['es2015'], + }, + }, + ], + }, + resolve: { + modules: [ + 'node_modules', + __dirname, + ], + extensions: ['.js'], + }, }, -}; +];