diff --git a/server/dataLoader.js b/server/dataLoader.js new file mode 100644 index 00000000..4ee65e44 --- /dev/null +++ b/server/dataLoader.js @@ -0,0 +1,5 @@ +const { matchPathname } = require('./importer'); + +exports.loadData = function(requestUrl) { + return Promise.resolve({}); +}; diff --git a/server/importer.js b/server/importer.js new file mode 100644 index 00000000..a09e6de0 --- /dev/null +++ b/server/importer.js @@ -0,0 +1,19 @@ +/** + * This module constructs the path to import what the client build + * bundle exports. + */ + +const path = require('path'); + +// Construct the bundle path where the bundle exports can be imported +const buildPath = path.resolve(__dirname, '..', 'build'); +const manifestPath = path.join(buildPath, 'asset-manifest.json'); +const manifest = require(manifestPath); +const mainJsPath = path.join(buildPath, manifest['main.js']); +const mainJs = require(mainJsPath); + +module.exports = { + renderApp: mainJs.default, + matchPathname: mainJs.matchPathname, + configureStore: mainJs.configureStore, +}; diff --git a/server/index.js b/server/index.js index 72bc3be3..808f1fc4 100644 --- a/server/index.js +++ b/server/index.js @@ -20,76 +20,12 @@ const express = require('express'); const helmet = require('helmet'); const compression = require('compression'); const path = require('path'); -const fs = require('fs'); const qs = require('qs'); -const url = require('url'); -const _ = require('lodash'); -const React = require('react'); -const sagaEffects = require('redux-saga/effects'); const auth = require('./auth'); -const sdk = require('./fakeSDK'); +const renderer = require('./renderer'); +const dataLoader = require('./dataLoader'); -// Construct the bundle path where the server side rendering function -// can be imported. const buildPath = path.resolve(__dirname, '..', 'build'); -const manifestPath = path.join(buildPath, 'asset-manifest.json'); -const manifest = require(manifestPath); -const mainJsPath = path.join(buildPath, manifest['main.js']); -const mainJs = require(mainJsPath); -const renderApp = mainJs.default; -const matchPathname = mainJs.matchPathname; -const configureStore = mainJs.configureStore; - -// The HTML build file is generated from the `public/index.html` file -// and used as a template for server side rendering. The application -// head and body are injected to the template from the results of -// calling the `renderApp` function imported from the bundle above. -const indexHtml = fs.readFileSync(path.join(buildPath, 'index.html'), 'utf-8'); - -const reNoMatch = /($^)/; -const template = _.template(indexHtml, { - // Interpolate variables in the HTML template with the following - // syntax: - // - // This syntax is very intentional: it works as a HTML comment and - // doesn't render anything visual in the dev mode, and in the - // production mode, HtmlWebpackPlugin strips out comments using - // HTMLMinifier except those that aren't explicitly marked as custom - // comments. By default, custom comments are those that begin with a - // ! character. - // - // Note that the variables are _not_ escaped since we only inject - // HTML content. - // - // See: - // - https://github.com/ampedandwired/html-webpack-plugin - // - https://github.com/kangax/html-minifier - // - Plugin options in the production Webpack configuration file - interpolate: //g, - // Disable evaluated and escaped variables in the template - evaluate: reNoMatch, - escape: reNoMatch, -}); - -function loadData(url) { - return Promise.resolve({}); -} - -function render(requestUrl, context, preloadedState) { - const { head, body } = renderApp(requestUrl, context, preloadedState); - - // Preloaded state needs to be passed for client side too. - // For security reasons we ensure that preloaded state is considered as a string - // by replacing '<' character with its unicode equivalent. - // http://redux.js.org/docs/recipes/ServerRendering.html#security-considerations - const serializedState = JSON.stringify(preloadedState).replace(/window.__PRELOADED_STATE__ = ${serializedState}; - `; - - return template({ title: head.title.toString(), preloadedStateScript, body }); -} - const env = process.env.NODE_ENV; const dev = env !== 'production'; const PORT = process.env.PORT || 4000; @@ -113,9 +49,18 @@ app.get('*', (req, res) => { const context = {}; const filters = qs.parse(req.query); - loadData(req.url) + dataLoader + .loadData(req.url) .then(preloadedState => { - const html = render(req.url, context, preloadedState); + const html = renderer.render(req.url, context, preloadedState); + + const debugData = { + url: req.url, + preloadedState, + context, + }; + + console.log(`\nRender info:\n${JSON.stringify(debugData, null, ' ')}`); if (context.forbidden) { // Routes component injects the context.forbidden when the @@ -137,7 +82,7 @@ app.get('*', (req, res) => { } }) .catch(e => { - console.error(e.message); + console.error(e); res.status(500).send(e.message); }); }); diff --git a/server/renderer.js b/server/renderer.js new file mode 100644 index 00000000..415141ee --- /dev/null +++ b/server/renderer.js @@ -0,0 +1,52 @@ +const path = require('path'); +const fs = require('fs'); +const _ = require('lodash'); +const { renderApp } = require('./importer'); + +const buildPath = path.resolve(__dirname, '..', 'build'); + +// The HTML build file is generated from the `public/index.html` file +// and used as a template for server side rendering. The application +// head and body are injected to the template from the results of +// calling the `renderApp` function imported from the bundle above. +const indexHtml = fs.readFileSync(path.join(buildPath, 'index.html'), 'utf-8'); + +const reNoMatch = /($^)/; +const template = _.template(indexHtml, { + // Interpolate variables in the HTML template with the following + // syntax: + // + // This syntax is very intentional: it works as a HTML comment and + // doesn't render anything visual in the dev mode, and in the + // production mode, HtmlWebpackPlugin strips out comments using + // HTMLMinifier except those that aren't explicitly marked as custom + // comments. By default, custom comments are those that begin with a + // ! character. + // + // Note that the variables are _not_ escaped since we only inject + // HTML content. + // + // See: + // - https://github.com/ampedandwired/html-webpack-plugin + // - https://github.com/kangax/html-minifier + // - Plugin options in the production Webpack configuration file + interpolate: //g, + // Disable evaluated and escaped variables in the template + evaluate: reNoMatch, + escape: reNoMatch, +}); + +exports.render = function(requestUrl, context, preloadedState) { + const { head, body } = renderApp(requestUrl, context, preloadedState); + + // Preloaded state needs to be passed for client side too. + // For security reasons we ensure that preloaded state is considered as a string + // by replacing '<' character with its unicode equivalent. + // http://redux.js.org/docs/recipes/ServerRendering.html#security-considerations + const serializedState = JSON.stringify(preloadedState).replace(/window.__PRELOADED_STATE__ = ${serializedState}; + `; + + return template({ title: head.title.toString(), preloadedStateScript, body }); +};