From c44ceebc129d70e80088c6b87bb721e997715e7c Mon Sep 17 00:00:00 2001 From: SteveSandersonMS Date: Mon, 7 Mar 2016 15:11:13 +0000 Subject: [PATCH] Enable server-side prerendering in React+Redux template --- .../ClientApp/{boot.tsx => boot-client.tsx} | 0 .../ReactReduxSpa/ClientApp/boot-server.tsx | 38 +++++++++++++++++++ .../ClientApp/components/Home.tsx | 1 + .../ReactReduxSpa/ClientApp/configureStore.ts | 3 +- .../ReactReduxSpa/Views/Home/Index.cshtml | 3 +- .../ReactReduxSpa/Views/Shared/_Layout.cshtml | 4 +- templates/ReactReduxSpa/package.json | 4 +- templates/ReactReduxSpa/webpack.config.dev.js | 7 +--- templates/ReactReduxSpa/webpack.config.js | 8 +++- .../ReactReduxSpa/webpack.config.prod.js | 8 ---- 10 files changed, 54 insertions(+), 22 deletions(-) rename templates/ReactReduxSpa/ClientApp/{boot.tsx => boot-client.tsx} (100%) create mode 100644 templates/ReactReduxSpa/ClientApp/boot-server.tsx diff --git a/templates/ReactReduxSpa/ClientApp/boot.tsx b/templates/ReactReduxSpa/ClientApp/boot-client.tsx similarity index 100% rename from templates/ReactReduxSpa/ClientApp/boot.tsx rename to templates/ReactReduxSpa/ClientApp/boot-client.tsx diff --git a/templates/ReactReduxSpa/ClientApp/boot-server.tsx b/templates/ReactReduxSpa/ClientApp/boot-server.tsx new file mode 100644 index 0000000..3c2355f --- /dev/null +++ b/templates/ReactReduxSpa/ClientApp/boot-server.tsx @@ -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 = ( + + + + ); + + // 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 + }); + }); +} diff --git a/templates/ReactReduxSpa/ClientApp/components/Home.tsx b/templates/ReactReduxSpa/ClientApp/components/Home.tsx index 084a6de..f681e2e 100644 --- a/templates/ReactReduxSpa/ClientApp/components/Home.tsx +++ b/templates/ReactReduxSpa/ClientApp/components/Home.tsx @@ -17,6 +17,7 @@ export default class Home extends React.Component {
  • Webpack dev middleware. In development mode, there's no need to run the webpack build tool. Your client-side resources are dynamically built on demand. Updates are available as soon as you modify any file.
  • Hot module replacement. 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.
  • Efficient production builds. In production mode, development-time features are disabled, and the webpack build tool produces minified static CSS and JavaScript files.
  • +
  • Server-side prerendering. 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.
  • ; } diff --git a/templates/ReactReduxSpa/ClientApp/configureStore.ts b/templates/ReactReduxSpa/ClientApp/configureStore.ts index e4e7bf3..3ce1431 100644 --- a/templates/ReactReduxSpa/ClientApp/configureStore.ts +++ b/templates/ReactReduxSpa/ClientApp/configureStore.ts @@ -7,7 +7,8 @@ import { typedToPlain } from 'redux-typed'; export default function configureStore(initialState?: Store.ApplicationState) { // 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 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( applyMiddleware(thunk, typedToPlain), devToolsExtension ? devToolsExtension() : f => f diff --git a/templates/ReactReduxSpa/Views/Home/Index.cshtml b/templates/ReactReduxSpa/Views/Home/Index.cshtml index 139ed8a..752d8d9 100644 --- a/templates/ReactReduxSpa/Views/Home/Index.cshtml +++ b/templates/ReactReduxSpa/Views/Home/Index.cshtml @@ -2,7 +2,8 @@ ViewData["Title"] = "Home Page"; } -
    Loading...
    +
    Loading...
    @section scripts { diff --git a/templates/ReactReduxSpa/Views/Shared/_Layout.cshtml b/templates/ReactReduxSpa/Views/Shared/_Layout.cshtml index a770ceb..6733882 100644 --- a/templates/ReactReduxSpa/Views/Shared/_Layout.cshtml +++ b/templates/ReactReduxSpa/Views/Shared/_Layout.cshtml @@ -6,9 +6,7 @@ @ViewData["Title"] - WebApplicationBasic - - - + @RenderBody() diff --git a/templates/ReactReduxSpa/package.json b/templates/ReactReduxSpa/package.json index eb19061..372fde5 100644 --- a/templates/ReactReduxSpa/package.json +++ b/templates/ReactReduxSpa/package.json @@ -33,6 +33,8 @@ "react-router-redux": "^4.0.0", "redux": "^3.3.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" } } diff --git a/templates/ReactReduxSpa/webpack.config.dev.js b/templates/ReactReduxSpa/webpack.config.dev.js index fd41ce6..719de1f 100644 --- a/templates/ReactReduxSpa/webpack.config.dev.js +++ b/templates/ReactReduxSpa/webpack.config.dev.js @@ -1,8 +1,3 @@ module.exports = { - devtool: 'inline-source-map', - module: { - loaders: [ - { test: /\.css/, loader: 'style!css' } - ] - } + devtool: 'inline-source-map' }; diff --git a/templates/ReactReduxSpa/webpack.config.js b/templates/ReactReduxSpa/webpack.config.js index f245d8e..855d001 100644 --- a/templates/ReactReduxSpa/webpack.config.js +++ b/templates/ReactReduxSpa/webpack.config.js @@ -1,9 +1,11 @@ var path = require('path'); var webpack = require('webpack'); +var ExtractTextPlugin = require('extract-text-webpack-plugin'); var merge = require('extendify')({ isDeep: true, arrays: 'concat' }); var devConfig = require('./webpack.config.dev'); var prodConfig = require('./webpack.config.prod'); var isDevelopment = process.env.ASPNET_ENV === 'Development'; +var extractCSS = new ExtractTextPlugin('site.css'); module.exports = merge({ resolve: { @@ -12,11 +14,12 @@ module.exports = merge({ module: { loaders: [ { 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: { - main: ['./ClientApp/boot.tsx'], + main: ['./ClientApp/boot-client.tsx'], }, output: { path: path.join(__dirname, 'wwwroot', 'dist'), @@ -24,6 +27,7 @@ module.exports = merge({ publicPath: '/dist/' }, plugins: [ + extractCSS, new webpack.DllReferencePlugin({ context: __dirname, manifest: require('./wwwroot/dist/vendor-manifest.json') diff --git a/templates/ReactReduxSpa/webpack.config.prod.js b/templates/ReactReduxSpa/webpack.config.prod.js index 94bbedf..0731f00 100644 --- a/templates/ReactReduxSpa/webpack.config.prod.js +++ b/templates/ReactReduxSpa/webpack.config.prod.js @@ -1,15 +1,7 @@ var webpack = require('webpack'); -var ExtractTextPlugin = require('extract-text-webpack-plugin'); -var extractCSS = new ExtractTextPlugin('site.css'); module.exports = { - module: { - loaders: [ - { test: /\.css/, loader: extractCSS.extract(['css']) }, - ] - }, plugins: [ - extractCSS, new webpack.optimize.OccurenceOrderPlugin(), new webpack.optimize.UglifyJsPlugin({ compress: { warnings: false } }), new webpack.DefinePlugin({ 'process.env.NODE_ENV': '"production"' })