Enable server-side prerendering in React+Redux template

This commit is contained in:
SteveSandersonMS
2016-03-07 15:11:13 +00:00
parent cf7a519919
commit c44ceebc12
10 changed files with 54 additions and 22 deletions

View File

@@ -0,0 +1,38 @@
import * as React from 'react';
import { Provider } from 'react-redux';
import { renderToString } from 'react-dom/server';
import { match, RouterContext } from 'react-router';
import createMemoryHistory from 'history/lib/createMemoryHistory';
import routes from './routes';
import configureStore from './configureStore';
export default function (params: any): Promise<{ html: string }> {
return new Promise<{ html: string, globals: { [key: string]: any } }>((resolve, reject) => {
// Match the incoming request against the list of client-side routes
match({ routes, location: params.location }, (error, redirectLocation, renderProps: any) => {
if (error) {
throw error;
}
// Build an instance of the application
const store = configureStore();
const app = (
<Provider store={ store }>
<RouterContext {...renderProps} />
</Provider>
);
// Perform an initial render that will cause any async tasks (e.g., data access) to begin
renderToString(app);
// Once the tasks are done, we can perform the final render
// We also send the redux store state, so the client can continue execution where the server left off
params.domainTasks.then(() => {
resolve({
html: renderToString(app),
globals: { initialReduxState: store.getState() }
});
}, reject); // Also propagate any errors back into the host application
});
});
}

View File

@@ -17,6 +17,7 @@ export default class Home extends React.Component<any, void> {
<li><strong>Webpack dev middleware</strong>. In development mode, there's no need to run the <code>webpack</code> build tool. Your client-side resources are dynamically built on demand. Updates are available as soon as you modify any file.</li> <li><strong>Webpack dev middleware</strong>. In development mode, there's no need to run the <code>webpack</code> build tool. Your client-side resources are dynamically built on demand. Updates are available as soon as you modify any file.</li>
<li><strong>Hot module replacement</strong>. In development mode, you don't even need to reload the page after making most changes. Within seconds of saving changes to files, rebuilt CSS and React components will be injected directly into your running application, preserving its live state.</li> <li><strong>Hot module replacement</strong>. In development mode, you don't even need to reload the page after making most changes. Within seconds of saving changes to files, rebuilt CSS and React components will be injected directly into your running application, preserving its live state.</li>
<li><strong>Efficient production builds</strong>. In production mode, development-time features are disabled, and the <code>webpack</code> build tool produces minified static CSS and JavaScript files.</li> <li><strong>Efficient production builds</strong>. In production mode, development-time features are disabled, and the <code>webpack</code> build tool produces minified static CSS and JavaScript files.</li>
<li><strong>Server-side prerendering</strong>. To optimize startup time, your React application is first rendered on the server. The initial HTML and state is then transferred to the browser, where client-side code picks up where the server left off.</li>
</ul> </ul>
</div>; </div>;
} }

View File

@@ -7,7 +7,8 @@ import { typedToPlain } from 'redux-typed';
export default function configureStore(initialState?: Store.ApplicationState) { export default function configureStore(initialState?: Store.ApplicationState) {
// Build middleware. These are functions that can process the actions before they reach the store. // Build middleware. These are functions that can process the actions before they reach the store.
const thunk = (thunkModule as any).default; // Workaround for TypeScript not importing thunk module as expected const thunk = (thunkModule as any).default; // Workaround for TypeScript not importing thunk module as expected
const devToolsExtension = (window as any).devToolsExtension; // If devTools is installed, connect to it const windowIfDefined = typeof window === 'undefined' ? null : window as any;
const devToolsExtension = windowIfDefined && windowIfDefined.devToolsExtension; // If devTools is installed, connect to it
const createStoreWithMiddleware = compose( const createStoreWithMiddleware = compose(
applyMiddleware(thunk, typedToPlain), applyMiddleware(thunk, typedToPlain),
devToolsExtension ? devToolsExtension() : f => f devToolsExtension ? devToolsExtension() : f => f

View File

@@ -2,7 +2,8 @@
ViewData["Title"] = "Home Page"; ViewData["Title"] = "Home Page";
} }
<div id="react-app">Loading...</div> <div id="react-app" asp-prerender-module="ClientApp/boot-server"
asp-prerender-webpack-config="webpack.config.js">Loading...</div>
@section scripts { @section scripts {
<script src="~/dist/main.js" asp-append-version="true"></script> <script src="~/dist/main.js" asp-append-version="true"></script>

View File

@@ -6,9 +6,7 @@
<title>@ViewData["Title"] - WebApplicationBasic</title> <title>@ViewData["Title"] - WebApplicationBasic</title>
<link rel="stylesheet" href="~/dist/vendor.css" asp-append-version="true" /> <link rel="stylesheet" href="~/dist/vendor.css" asp-append-version="true" />
<environment names="Staging,Production"> <link rel="stylesheet" href="~/dist/site.css" asp-append-version="true" />
<link rel="stylesheet" href="~/dist/site.css" asp-append-version="true" />
</environment>
</head> </head>
<body> <body>
@RenderBody() @RenderBody()

View File

@@ -33,6 +33,8 @@
"react-router-redux": "^4.0.0", "react-router-redux": "^4.0.0",
"redux": "^3.3.1", "redux": "^3.3.1",
"redux-thunk": "^2.0.1", "redux-thunk": "^2.0.1",
"redux-typed": "^1.0.0" "redux-typed": "^1.0.0",
"require-from-string": "^1.1.0",
"webpack-externals-plugin": "^1.0.0"
} }
} }

View File

@@ -1,8 +1,3 @@
module.exports = { module.exports = {
devtool: 'inline-source-map', devtool: 'inline-source-map'
module: {
loaders: [
{ test: /\.css/, loader: 'style!css' }
]
}
}; };

View File

@@ -1,9 +1,11 @@
var path = require('path'); var path = require('path');
var webpack = require('webpack'); var webpack = require('webpack');
var ExtractTextPlugin = require('extract-text-webpack-plugin');
var merge = require('extendify')({ isDeep: true, arrays: 'concat' }); var merge = require('extendify')({ isDeep: true, arrays: 'concat' });
var devConfig = require('./webpack.config.dev'); var devConfig = require('./webpack.config.dev');
var prodConfig = require('./webpack.config.prod'); var prodConfig = require('./webpack.config.prod');
var isDevelopment = process.env.ASPNET_ENV === 'Development'; var isDevelopment = process.env.ASPNET_ENV === 'Development';
var extractCSS = new ExtractTextPlugin('site.css');
module.exports = merge({ module.exports = merge({
resolve: { resolve: {
@@ -12,11 +14,12 @@ module.exports = merge({
module: { module: {
loaders: [ loaders: [
{ test: /\.ts(x?)$/, include: /ClientApp/, loader: 'babel-loader' }, { test: /\.ts(x?)$/, include: /ClientApp/, loader: 'babel-loader' },
{ test: /\.ts(x?)$/, include: /ClientApp/, loader: 'ts-loader' } { test: /\.ts(x?)$/, include: /ClientApp/, loader: 'ts-loader' },
{ test: /\.css/, loader: extractCSS.extract(['css']) }
] ]
}, },
entry: { entry: {
main: ['./ClientApp/boot.tsx'], main: ['./ClientApp/boot-client.tsx'],
}, },
output: { output: {
path: path.join(__dirname, 'wwwroot', 'dist'), path: path.join(__dirname, 'wwwroot', 'dist'),
@@ -24,6 +27,7 @@ module.exports = merge({
publicPath: '/dist/' publicPath: '/dist/'
}, },
plugins: [ plugins: [
extractCSS,
new webpack.DllReferencePlugin({ new webpack.DllReferencePlugin({
context: __dirname, context: __dirname,
manifest: require('./wwwroot/dist/vendor-manifest.json') manifest: require('./wwwroot/dist/vendor-manifest.json')

View File

@@ -1,15 +1,7 @@
var webpack = require('webpack'); var webpack = require('webpack');
var ExtractTextPlugin = require('extract-text-webpack-plugin');
var extractCSS = new ExtractTextPlugin('site.css');
module.exports = { module.exports = {
module: {
loaders: [
{ test: /\.css/, loader: extractCSS.extract(['css']) },
]
},
plugins: [ plugins: [
extractCSS,
new webpack.optimize.OccurenceOrderPlugin(), new webpack.optimize.OccurenceOrderPlugin(),
new webpack.optimize.UglifyJsPlugin({ compress: { warnings: false } }), new webpack.optimize.UglifyJsPlugin({ compress: { warnings: false } }),
new webpack.DefinePlugin({ 'process.env.NODE_ENV': '"production"' }) new webpack.DefinePlugin({ 'process.env.NODE_ENV': '"production"' })