diff --git a/.gitignore b/.gitignore index ad8ad8cf1..6ecd206e7 100644 --- a/.gitignore +++ b/.gitignore @@ -154,4 +154,4 @@ Procfile config/assets.json npm-shrinkwrap.json -.isomorphic-loader-config.json +.isomorphic-loader-config.* diff --git a/.isomorphic-loader-config.lock b/.isomorphic-loader-config.lock deleted file mode 100644 index 19104f172..000000000 --- a/.isomorphic-loader-config.lock +++ /dev/null @@ -1 +0,0 @@ -lock \ No newline at end of file diff --git a/client/app.jsx b/client/app.jsx index 540e26c12..ab3896879 100644 --- a/client/app.jsx +++ b/client/app.jsx @@ -1,29 +1,25 @@ import React from "react"; +import {render} from "react-dom"; import { routes } from "./routes"; import { Router, browserHistory } from "react-router"; import { createStore, compose } from "redux"; -import { Resolver } from "react-resolver"; import { Provider } from "react-redux"; import "styles/base.css"; -import rootReducer from "./reducers/index"; +import rootReducer from "./reducers"; import DevTools from "../client/devtools"; -const initialState = window.__PRELOADED_STATE__; - -// const rootReducer = (s, a) => s; // eslint-disable-line no-unused-vars - const enhancer = compose( // Add middlewares you want to use in development: // applyMiddleware(d1, d2, d3), DevTools.instrument() ); -const store = createStore(rootReducer, initialState, enhancer); window.webappStart = () => { - Resolver.render( - () => + const initialState = window.__PRELOADED_STATE__; + const store = createStore(rootReducer, initialState, enhancer); + render(
{routes} diff --git a/client/components/above-the-fold.jsx b/client/components/above-the-fold.jsx index b8ea9ebbc..9d7bd4a6c 100644 --- a/client/components/above-the-fold.jsx +++ b/client/components/above-the-fold.jsx @@ -1,28 +1,91 @@ -import React from "react"; +import React, {PropTypes} from "react"; import {AboveTheFoldOnlyServerRender} from "above-the-fold-only-server-render"; +import {connect} from "react-redux"; +import smileyPng from "../images/718smiley.png"; +import peaceSmileyPng from "../images/peace-smiley.png"; + +/* eslint-disable max-len */ export class AboveFold extends React.Component { render() { return (
-

Above-the-fold-only-server-render: Increase Your Performance

- -
-

This will skip server rendering if the 'AboveTheFoldOnlyServerRender' - lines are present, or uncommented out.

-

This will be rendered on the server and visible - if the 'AboveTheFoldOnlyServerRender' lines are commented out.

-

Try manually toggling this component to see it in action

-

- Read more about this module and see our live demo - -

-
+
+

Above-the-fold-only-server-render: Increase Your Performance. Note: This demo uses CSS3.

+

This page demonstrates the AboveTheFoldOnlyServerRender component.

+

+ + Read more about this module and see our live demo + +

+
+
+

This content block is here to fill up the browser view port as Above The Fold content and it always will be + rendered on server side.

+

To verify, use your browser's view source to see the original HTML of this page and see this being part of + the SSR content

+

You should see this fill up your browser screen to push content below the browser view port, which are + wrapped in the AboveTheFoldOnlyServerRender component.

+ +

Scroll down to see the content below

+
+ {this.props.skip ? ( +
In the page source you should NOT see any more HTML after this except a few empty divs +
) + : + (
In the page source you should SEE the HTML for the wrapped component after this.
) + } + +
+

This content block is wrapped inside the AboveTheFoldOnlyServerRender component, with the + skip prop set to {`${this.props.skip}`}.

+ {this.props.skip ? +

It will not be rendered on the server side, but you should see it in the browser.

+

To verify, check the page source to see this not being part of the SSR content.

+ : +

It is also rendered on the server side since skip is {`${this.props.skip}`}

+

To verify, check the page source to see this being part of the SSR content.

+ } + +
-

This is below the 'Above the fold closing tag'

); } } + +AboveFold.propTypes = { + skip: PropTypes.bool +}; + +export default connect((state) => { + return {skip: state.skip}; +})(AboveFold); diff --git a/client/components/home.jsx b/client/components/home.jsx index 5a16b06a1..2941ec170 100644 --- a/client/components/home.jsx +++ b/client/components/home.jsx @@ -1,10 +1,11 @@ -import React, { PropTypes } from "react"; -import { connect } from "react-redux"; +import React, {PropTypes} from "react"; +import {connect} from "react-redux"; +import electrodeLogo from "../images/electrode.svg"; class HomeWrapper extends React.Component { render() { return ( - + ); } } @@ -13,17 +14,33 @@ HomeWrapper.propTypes = { data: PropTypes.string }; +/* eslint-disable max-len */ + export class Home extends React.Component { render() { return (
-

Hello Electrode

+
+ Electrode Logo + +

Demonstration Components

  • CSRF protection using electrode-csrf-jwt
  • - - Above the Fold Render - increase your App's performance by using a skip prop + + Above the Fold Render with skip=true - increase your App's performance by using a skip prop + +
  • +
  • + + Above the Fold Render with skip=false - increase your App's performance by using a skip prop
  • SSR Caching Simple Type Example
  • @@ -40,7 +57,7 @@ Home.propTypes = { }; const mapStateToProps = (state) => ({ - data: state.data + data: state && state.data }); export default connect( diff --git a/client/images/718smiley.png b/client/images/718smiley.png new file mode 100644 index 000000000..c057e107d Binary files /dev/null and b/client/images/718smiley.png differ diff --git a/client/images/electrode.svg b/client/images/electrode.svg new file mode 100644 index 000000000..050719671 --- /dev/null +++ b/client/images/electrode.svg @@ -0,0 +1,121 @@ + + + + +Created by potrace 1.13, written by Peter Selinger 2001-2015 + + + + + + + + + + + + + + + + diff --git a/client/images/peace-smiley.png b/client/images/peace-smiley.png new file mode 100644 index 000000000..ce465a382 Binary files /dev/null and b/client/images/peace-smiley.png differ diff --git a/client/reducers/count-reducer.js b/client/reducers/count-reducer.js deleted file mode 100644 index 95f9e4554..000000000 --- a/client/reducers/count-reducer.js +++ /dev/null @@ -1,8 +0,0 @@ -const countReducer = (state = 0, action) => { - switch (action.type) { - default: - return state; - } -}; - -export default countReducer; diff --git a/client/reducers/data-reducer.js b/client/reducers/data-reducer.js deleted file mode 100644 index 3e413f2b1..000000000 --- a/client/reducers/data-reducer.js +++ /dev/null @@ -1,8 +0,0 @@ -const dataReducer = (state = {}, action) => { - switch (action.type) { - default: - return state; - } -}; - -export default dataReducer; diff --git a/client/reducers/index.js b/client/reducers/index.js deleted file mode 100644 index 1d176845b..000000000 --- a/client/reducers/index.js +++ /dev/null @@ -1,10 +0,0 @@ -import { combineReducers } from "redux"; -import dataReducer from "./data-reducer"; -import countReducer from "./count-reducer"; - -const rootReducer = combineReducers({ - data: dataReducer, - count: countReducer -}); - -export default rootReducer; diff --git a/client/reducers/index.jsx b/client/reducers/index.jsx new file mode 100644 index 000000000..dd31e88f2 --- /dev/null +++ b/client/reducers/index.jsx @@ -0,0 +1,5 @@ +const rootReducer = (state) => { + return state || {}; +}; + +export default rootReducer; diff --git a/client/routes.jsx b/client/routes.jsx index 140b09e31..c124de8ed 100644 --- a/client/routes.jsx +++ b/client/routes.jsx @@ -1,17 +1,17 @@ import React from "react"; -import { Route, IndexRoute} from "react-router"; +import {Route, IndexRoute} from "react-router"; import Home from "./components/home"; import SSRCachingTemplateType from "./components/ssr-caching-template-type"; import SSRCachingSimpleType from "./components/ssr-caching-simple-type"; -import { CSRF } from "./components/csrf"; -import { AboveFold } from "./components/above-the-fold"; +import {CSRF} from "./components/csrf"; +import AboveFold from "./components/above-the-fold"; export const routes = ( - - - - - + + + + + ); diff --git a/config/default.json b/config/default.json index 3aa2f4845..5c9bf7e95 100644 --- a/config/default.json +++ b/config/default.json @@ -10,6 +10,7 @@ "webapp": { "module": "./server/plugins/webapp", "options": { + "pageTitle": "Electrode Boilerplate Universal React App", "paths": { "/{args*}": { "content": { diff --git a/package.json b/package.json index 81e7b4987..a58420b26 100644 --- a/package.json +++ b/package.json @@ -25,10 +25,10 @@ "dependencies": { "above-the-fold-only-server-render": "^1.0.2", "bluebird": "^3.4.6", + "electrode-archetype-react-app": "^1.0.0", "electrode-csrf-jwt": "^1.0.0", "electrode-react-ssr-caching": "^0.1.3", "electrode-redux-router-engine": "^1.0.0", - "electrode-router-resolver-engine": "^1.0.0", "electrode-server": "^1.0.0", "electrode-static-paths": "^1.0.0", "lodash": "^4.10.1" @@ -37,7 +37,6 @@ "redux-devtools": "^3.3.1", "redux-devtools-dock-monitor": "^1.1.1", "redux-devtools-log-monitor": "^1.0.11", - "electrode-archetype-react-app": "^1.0.0", "electrode-archetype-react-app-dev": "^1.0.0", "gulp": "^3.9.1" } diff --git a/server/index.js b/server/index.js index 87bc03d87..e4bc6846f 100644 --- a/server/index.js +++ b/server/index.js @@ -8,12 +8,18 @@ process.on("SIGINT", () => { const config = require("electrode-confippet").config; const staticPathsDecor = require("electrode-static-paths"); +const supports = require("electrode-archetype-react-app/supports"); require.extensions[".css"] = () => { return; }; -require("babel-register")({ +/** + * Use babel register to transpile any JSX code on the fly to run + * in server mode, and also transpile react code to apply process.env.NODE_ENV + * removal to improve performance in production mode. + */ +supports.babelRegister({ ignore: /node_modules\/(?!react\/)/ }); @@ -33,4 +39,22 @@ const cacheConfig = { SSRCaching.enableCaching(); SSRCaching.setCachingConfig(cacheConfig); -require("electrode-server")(config, [staticPathsDecor()]); +/** + * css-modules-require-hook: handle css-modules on node.js server. + * similar to Babel's babel/register it compiles CSS modules in runtime. + * + * generateScopedName - Short alias for the postcss-modules-scope plugin's option. + * Helps you to specify the custom way to build generic names for the class selectors. + * You may also use a string pattern similar to the webpack's css-loader. + * + * https://github.com/css-modules/css-modules-require-hook#generatescopedname-function + * https://github.com/webpack/css-loader#local-scope + * https://github.com/css-modules/postcss-modules-scope + */ +supports.cssModuleHook({ + generateScopedName: "[name]__[local]___[hash:base64:5]" +}); + +supports.isomorphicExtendRequire().then(() => { + require("electrode-server")(config, [staticPathsDecor()]); // eslint-disable-line +}); diff --git a/server/plugins/webapp/index.js b/server/plugins/webapp/index.js index c18776b8c..18283b832 100644 --- a/server/plugins/webapp/index.js +++ b/server/plugins/webapp/index.js @@ -152,7 +152,7 @@ const registerRoutes = (server, options, next) => { return content; }; - const pluginOptions = _.defaultsDeep({}, pluginOptionsDefaults, options); + const pluginOptions = _.defaultsDeep({}, options, pluginOptionsDefaults); return Promise.try(() => loadAssetsFromStats(pluginOptions.stats)) .then((assets) => { diff --git a/server/views/index-view.jsx b/server/views/index-view.jsx index 2bff87f1d..bb8ff0862 100644 --- a/server/views/index-view.jsx +++ b/server/views/index-view.jsx @@ -1,11 +1,10 @@ import ReduxRouterEngine from 'electrode-redux-router-engine'; import React from 'react'; -import ReactDOM from 'react-dom/server'; import { routes } from "../../client/routes"; const Promise = require("bluebird"); import { createStore } from "redux"; -let rootReducer = (s, a) => s; +import rootReducer from "../../client/reducers"; function storeInitializer(req) { let initialState; @@ -21,12 +20,15 @@ function storeInitializer(req) { initialState = { count: 100 }; + } else if (req.path === "/above-the-fold") { + initialState = { + skip: req.query.skip === "true" + } } else { - let initialState = {}; + initialState = {}; } - const store = createStore(rootReducer, initialState); - return store; + return createStore(rootReducer, initialState); } function createReduxStore(req, match) { @@ -39,11 +41,21 @@ function createReduxStore(req, match) { }); } -module.exports = (req) => { +// +// This function is exported as the content for the webapp plugin. +// +// See config/default.json under plugins.webapp on specifying the content. +// +// When the Web server hits the routes handler installed by the webapp plugin, it +// will call this function to retrieve the content for SSR if it's enabled. +// +// - if (!req.server.app.routesEngine) { - req.server.app.routesEngine = new ReduxRouterEngine({ routes, createReduxStore }); +module.exports = (req) => { + const app = req.server && req.server.app || req.app; + if (!app.routesEngine) { + app.routesEngine = new ReduxRouterEngine({routes, createReduxStore}); } - return req.server.app.routesEngine.render(req); + return app.routesEngine.render(req); };