React / Webpack / Express渲染服务器端的问题

我试图将服务器端渲染到我现有的React / Redux应用程序中。 几次失败的尝试后,我想我几乎在那里,但我遇到了几个问题。 我对React / Redux / Express / Webpack相当陌生,所以如果我犯了一些愚蠢的错误,我很抱歉!

问题1 – 窗口未定义

当我使用Redux时,我需要跟踪初始状态并将其加载到我的应用程序中。 内部server/index.js我从商店获取初始状态,并使用window.__initialData__ = ${serialize(initialData)}存储在window.__initialData__ = ${serialize(initialData)}

服务器代码如下:

 import express from 'express'; import React from 'react'; import { renderToString } from 'react-dom/server'; import { Provider } from 'react-redux'; import { StaticRouter, matchPath } from 'react-router-dom'; import serialize from 'serialize-javascript'; import routes from 'routes/routes'; import store from 'store'; import AppContainer from 'containers/app-container/app-container'; const app = express(); app.use(express.static('static')); app.get('*', (req, res, next) => { const promises = routes.reduce((accumulator, route) => { if (matchPath(req.url, route) && route.component && route.component.initialAction) { accumulator.push(Promise.resolve(store.dispatch(route.component.initialAction()))); } return accumulator; }, []); Promise.all(promises) .then(() => { const context = {}; const markup = renderToString( <Provider store={store}> <StaticRouter location={req.url} context={context}> <AppContainer /> </StaticRouter> </Provider> ); const initialData = store.getState(); res.send(` <!DOCTYPE html> <html> <head> <title>W Combinator</title> <script src='/static/assets/app.js' defer></script> <script src='/static/assets/vendor.js' defer></script> <script>window.__initialData__ = ${serialize(initialData)}</script> </head> <body> <div id='root'>${markup}</div> </body> </html> `); }) .catch(next); }); app.listen(process.env.PORT || 3000, () => { console.log('Server is listening'); }); 

当我在前端创build我的商店时,我尝试访问这个窗口variables,像这样const initialState = window.__initialData__; 。 我得到的问题是在terminal的错误被抛出说窗口没有定义。 我试图通过在Webpack中设置process.env.BROWSER来解决这个问题,但它似乎没有工作。

Webpack代码如下:

 const path = require('path'); const webpack = require('webpack'); const CleanWebpackPlugin = require('clean-webpack-plugin'); const nodeExternals = require('webpack-node-externals'); const ExtractTextPlugin = require('extract-text-webpack-plugin'); const HtmlWebpackPlugin = require('html-webpack-plugin'); const StyleLintPlugin = require('stylelint-webpack-plugin'); const WriteFilePlugin = require('write-file-webpack-plugin'); const basePath = path.join(__dirname, 'app'); const distPath = path.join(__dirname, 'static/assets'); const browserConfig = { devtool: 'cheap-module-eval-source-map', entry: { vendor: './app/client/vendor.js', app: './app/client/index.jsx' }, output: { path: distPath, publicPath: '/assets/', filename: '[name].js' }, plugins: [ new CleanWebpackPlugin(['static']), new webpack.optimize.CommonsChunkPlugin({ name: 'vendor' }), new HtmlWebpackPlugin({ title: 'Default Template', template: './app/client/index.html', filename: 'index.html' }), new ExtractTextPlugin({ filename: 'style.css' }), new WriteFilePlugin(), new StyleLintPlugin({ files: ['app/**/*.scss'], syntax: 'scss' }), new webpack.DefinePlugin({ 'process.env.BROWSER': JSON.stringify(true) }) ], resolve: { extensions: ['.js', '.jsx'], modules: [ path.resolve(`${basePath}/client`), path.resolve(`${basePath}/server`), path.resolve('node_modules/') ] }, module: { rules: [ { test: /\.(js|jsx)$/, exclude: /(node_modules)/, loader: 'babel-loader', include: [basePath] }, { test: /\.global\.scss$/, exclude: /node_modules/, include: basePath, use: ExtractTextPlugin.extract({ fallback: 'style-loader', use: ['css-loader', 'postcss-loader', { loader: 'sass-loader', options: { includePaths: [basePath] } }] }) }, { test: /\.scss$/, exclude: [/node_modules/, /\.global\.scss$/], include: basePath, use: ExtractTextPlugin.extract({ fallback: 'style-loader', use: ['css-loader?modules&localIdentName=[local]-__-[hash:base64:5]', 'postcss-loader', { loader: 'sass-loader', options: { includePaths: [basePath] } }] }) }, { test: /\.svg$/, use: [ 'svg-sprite-loader', 'svgo-loader' ] }, { test: /\.(png|jpg|gif)$/, include: basePath, use: [ 'file-loader' ] }, { test: /\.(woff|woff2|eot|ttf|otf)$/, include: basePath, use: [ 'file-loader' ] } ] } }; const serverConfig = { entry: './app/server/index.js', target: 'node', externals: [nodeExternals()], output: { path: basePath, filename: 'server.js', libraryTarget: 'commonjs2' }, devtool: 'cheap-module-source-map', module: { rules: [ { test: /\.(js|jsx)$/, exclude: /(node_modules)/, loader: 'babel-loader', include: [basePath] } ] }, plugins: [ new webpack.DefinePlugin({ 'process.env.BROWSER': JSON.stringify(false) }) ], resolve: { extensions: ['.js', '.jsx'], modules: [ path.resolve(`${basePath}/client`), path.resolve(`${basePath}/server`), path.resolve('node_modules/') ] } }; module.exports = [browserConfig, serverConfig]; 

问题2 – 意外的标记<

如果我手动设置初始状态,我可以让页面呈现服务器端,我遇到的问题是,我得到一个控制台错误,说Unexpected token < 。 我已经做了一些阅读,我的猜测是我错过了我的快递服务器的某种configuration设置,但我看不到它的工作。

问题3 – 错误:子编译失败:

 ERROR in ./node_modules/html-webpack-plugin/lib/loader.js!./app/client/index.ejs Module build failed: Error: ENOENT: no such file or directory app/client/index.ejs 

如果我手动设置初始状态,并忽略上述错误,那么我可以看到我的内容呈现在页面上没有任何错误terminal。 当我但是在我的应用程序中的文件进行更改并保存,然后我得到上述错误出现在terminal,它不会编译。 该错误表明它似乎正在寻找一个index.ejs文件,它似乎无法find。 我目前没有在我的应用程序中使用任何模板引擎,我只是提供了一个静态index.html文件,你可以在上面的webpackconfiguration中看到。

再次我的猜测是我没有正确设置或我错过了一个configuration选项,我花了一段时间试图解决这些,但看不出来找出根本原因。

如果有人知道如何解决这些问题,那么非常感谢!

Babelrc

  { "presets": ["es2015", "react"], "plugins": ["transform-object-rest-spread"] } 

编辑

问题1我已经设法解决,我没有正确创build我的商店内的服务器,添加此const store = configureStore(); 之后app.get(*...和调整我的店铺设置稍微修复这个。作为参考,我用这个链接来帮助设置。

编辑

问题2是由于JS文件没有被作为静态文件提供的。 app.use(express.static('./static/assets'))解决了这个问题。