How to do it...

Let's get started with the implementation:

  1. Include the react-hot-loader plugin in our .babelrc file just for the development environment:
  {
"presets": ["env", "react"],
"env": {
"development": {
"plugins": [
"react-hot-loader/babel"
]
}
}
}
File: .babelrc
  1. Create an Express Server; you need to create a file at src/server/index.js:
  // Dependencies
import express from 'express';
import path from 'path';
import webpackDevMiddleware from 'webpack-dev-middleware';
import webpackHotMiddleware from 'webpack-hot-middleware';
import webpack from 'webpack';

// Webpack Configuration
import webpackConfig from '../../webpack.config.babel';

// Client Render
import clientRender from './render/clientRender';

// Utils
import { isMobile } from '../shared/utils/device';

// Environment
const isProduction = process.env.NODE_ENV === 'production';

// Express Application
const app = express();

// Webpack Compiler
const compiler = webpack(webpackConfig);

// Webpack Middleware
if (!isProduction) {
// Hot Module Replacement
app.use(webpackDevMiddleware(compiler));
app.use(webpackHotMiddleware(compiler));
} else {
// Public directory
app.use(express.static(path.join(__dirname, '../../public')));

// GZip Compression just for Production
app.get('*.js', (req, res, next) => {
req.url = `${req.url}.gz`;
res.set('Content-Encoding', 'gzip');
next();
});
}

// Device Detection
app.use((req, res, next) => {
req.isMobile = isMobile(req.headers['user-agent']);
next();
});

// Client Side Rendering
app.use(clientRender());

// Disabling x-powered-by
app.disable('x-powered-by');

// Listen Port 3000...
app.listen(3000);
File: src/server/index.js
  1. We included a device detection with Node.js to use in our initialState for Redux. we can create this util file for this purpose:
  export function getCurrentDevice(ua) {
return /mobile/i.test(ua) ? 'mobile' : 'desktop';
}
export function isDesktop(ua) {
return !/mobile/i.test(ua);
}
export function isMobile(ua) {
return /mobile/i.test(ua);
}
File: src/shared/utils/device.js
  1. You will need the device reducer as well:
  export default function deviceReducer(state = {}) {
return state;
}
File: src/shared/reducers/deviceReducer.js
  1. We need to create index.js in our reducers folders, in the place where we are going to combine our reducers:
  // Dependencies
import { combineReducers } from 'redux';

// Shared Reducers
import device from './deviceReducer';

const rootReducer = combineReducers({
device
});

export default rootReducer;
File: src/shared/reducers/index.js
  1. Let's create our initialState file. This is where we are going to get the device information from the req object:
  export default req => ({
device: {
isMobile: req.isMobile
}
});
  1. Redux needs a store to save all our reducers and our initialState; this will be our configureStore:
  // Dependencies
import { createStore } from 'redux';

// Root Reducer
import rootReducer from '../reducers';

export default function configureStore(initialState) {
return createStore(
rootReducer,
initialState
);
}
File: src/shared/redux/configureStore.js
  1. In the last recipes, we were using the html-webpack-plugin package to render the initial HTML template; now we have to do that in Node. For this, you need to create the src/server/render/html.js file:
  // Dependencies
import
serialize from 'serialize-javascript';

// Environment

const isProduction = process.env.NODE_ENV === 'production';

export default function html(options) {
const { title, initialState } = options;
let path = '/';
let link = '';

if (isProduction) {
path = '/app/';
link = `<link rel="stylesheet" href="${path}css/main.css" />`;
}

return `
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>${title}</title>
${link}
</head>
<body>
<div id="root"></div>

<script>
window.initialState = ${serialize(initialState)};
</script>
<script src="${path}vendor.js"></script>
<script src="${path}main.js"></script>
</body>
</html>
`;
}
File: src/server/render/html.js

  1. Create a function to render the HTML; I called this the clientRender.js file:
  // HTML
import html from './html';

// Initial State
import initialState from './initialState';

export default function clientRender() {
return (req, res) => res.send(html({
title: 'Codejobs',
initialState: initialState(req)
}));
}
File: src/server/render/clientRender.js
  1. After we've created our server files, we need to add our main entry file for the client. In this file, we are going to wrap our main App component inside the React Hot Loader App Container:
  // Dependencies
import React from 'react';
import { render } from 'react-dom';
import { Provider } from 'react-redux';
import { AppContainer } from 'react-hot-loader';

// Redux Store
import configureStore from './shared/redux/configureStore';

// Components
import App from './client/App';

// Configuring Redux Store
const store = configureStore(window.initialState);

// Root element
const rootElement = document.querySelector('#root');

// App Wrapper
const renderApp = Component => {
render(
<AppContainer>
<Provider store={store}>
<Component />
</Provider>
</AppContainer>,
rootElement
);
};

// Rendering app
renderApp(App);

// Hot Module Replacement
if (module.hot) {
module.hot.accept('./client/App', () => {
renderApp(require('./client/App').default);
});
}
File: src/index.jsx
  1. Let's create a directory for our client files. The first file we need to create is App.jsx, where we are going to include our component's routes:
  // Dependencies
import React from 'react';
import { BrowserRouter, Switch, Route } from 'react-router-dom';

// Components
import About from './components/About';
import Home from './components/Home';

const App = () => (
<BrowserRouter>
<Switch>
<Route exact path="/" component={Home} />
<Route exact path="/about" component={About} />
</Switch>
</BrowserRouter>
);

export default App;
File: src/client/App.jsx

  1. To test our routes and our Redux state (isMobile), let's create the About component:
  import React from 'react';
import { bool } from 'prop-types';
import { connect } from 'react-redux';
import styles from './About.scss';

const About = ({ isMobile }) => (
<h1 className={styles.About}>About - {isMobile ? 'mobile' : 'desktop'}</h1>
);

About.propTypes = {
isMobile: bool
};

export default connect(({ device }) => ({
isMobile: device.isMobile
}))(About);
File: src/client/components/About/index.jsx
  1. Add basic styles for this component:
  $color: green;

.About {
color: $color;
}
File: src/client/components/About/About.scss
  1. When we want to use the React Hot Loader to refresh the page every time we make a change, we need to add an entry for our webpack-hot-middleware and one for react-hot-loader to connect to the HMR (Hot Module Replacement):
  const isProduction = process.env.NODE_ENV === 'production';
const entry = [];

if (!isProduction) {
entry.push(
'webpack-hot-middleware/client?
path=http://localhost:3000/__webpack_hmr&reload=true',
'react-hot-loader/patch',
'./src/index.jsx'
);
} else {
entry.push('./src/index.jsx');
}

export default entry;
File: webpack/configuration/entry.js
  1. Create the output.js file to specify where our Webpack should save the files:
  // Dependencies
import path from 'path';

export default {
filename: '[name].js',
path: path.resolve(__dirname, '../../public/app'),
publicPath: '/'
};
  1. You need to import these files into our index.js:
  // Configuration
import devtool from './devtool';
import entry from './entry';
import mode from './mode';
import module from './module';
import optimization from './optimization';
import output from './output';
import plugins from './plugins';
import resolve from './resolve';

export {
devtool,
entry,
mode,
module,
optimization,
output,
plugins,
resolve
};
File: webpack/configuration/index.js
  1. We need to create a mode.js file as well, and handle the environment mode from our JS file because we are going to change our start script and we won't specify the mode directly anymore:
  const isProduction = process.env.NODE_ENV === 'production';

export default !isProduction ? 'development' : 'production';
File: webpack/configuration/mode.js
  1. Add HotModuleReplacementPlugin into our plugins file for development and CompressionPlugin for production:
  import ExtractTextPlugin from 'extract-text-webpack-plugin';
import WebpackNotifierPlugin from 'webpack-notifier';
import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer';
import CompressionPlugin from 'compression-webpack-plugin';
import webpack from 'webpack';
const isProduction = process.env.NODE_ENV === 'production';
const plugins = [];
if (isProduction) {
plugins.push(
new ExtractTextPlugin({
allChunks: true,
filename: './css/[name].css'
}),
new CompressionPlugin({
asset: '[path].gz[query]',
algorithm: 'gzip',
test: /.js$/,
threshold: 10240,
minRatio: 0.8
})
);
} else {
plugins.push(
new webpack.HotModuleReplacementPlugin(),
new BundleAnalyzerPlugin(),
new WebpackNotifierPlugin({
title: 'CodeJobs'
})
);
}
export default plugins;
File: webpack/configuration/plugins.js
  1. In package.json, the new start script should look like this:
    "scripts": {
"build": "NODE_ENV=production webpack",
"clean": "rm -rf public/app",
"start": "npm run clean && NODE_ENV=development nodemon src/server --watch src/server --exec babel-node --presets es2015",
"start-production": "npm run clean && npm run build && NODE_ENV=production babel-node src/server --presets es2015"
}
File: package.json
If you use Windows, you have to use the SET keyword to specify NODE_ENV. For example, SET NODE_ENV=development or SET NODE_ENV=production otherwise won't work in your machine.
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset