2017-02-25 62 views
1

在研究Reactjs服务器端呈现时,在节点Express Web服务器和Webpack中间件上,我发现很难理解为什么根路由“/”匹配组件没有被传递到html中,而嵌套的“/ foobar”工作正常(你也看到父母)。当使用webpack中间件时,根路径“/”与服务器端呈现中reactjs声明的路径不匹配

如果移除了webpack中间件,路径“/”将返回匹配reactjs路由。

请在下面找到源代码,请记住,有很多测试和看到,它不是质量代码。

的发展的WebPack配置文件:

var path = require('path') 
var webpack = require('webpack') 
var HtmlWebpackPlugin = require('html-webpack-plugin') 

module.exports = { 
    devtool: 'inline-source-map', 
    context: path.resolve(__dirname, 'src'), 
    entry: [ 
    'react-hot-loader/patch', 
    'webpack/hot/dev-server', 
    'webpack-hot-middleware/client', 
    'babel-polyfill', 
    './js/index.js' 
    ], 
    module: { 
    rules: [ 
     { 
     test: /\.(js|jsx)$/, 
     exclude: /node_modules/, 
     use: [ 
      'babel-loader' 
     ] 
     }, 
     { 
     test: /\.scss$/, 
     exclude: /node_modules/, 
     use: [ 
      'style-loader', 
      'css-loader', 
      'sass-loader' 
     ] 
     }, 
     { 
     test: /\.(ttf|eot|svg|woff(2)?)(\?[a-z0-9=&.]+)?$/, 
     use: [ 
      'file-loader' 
     ] 
     }, 
     { 
     test: /\.(jpg|png|gif|svg)$/i, 
     use: [ 
      'file-loader' 
     ] 
     } 
    ] 
    }, 
    plugins: [ 
    new HtmlWebpackPlugin({ 
     inject: true, 
     template: path.join(__dirname, '/src/index.html'), 
     filename: 'index.html' 
    }), 
    new webpack.DefinePlugin({ 
     'process.env': { 
     'NODE_ENV': JSON.stringify('development') 
     } 
    }), 
    // enable HMR globally 
    new webpack.HotModuleReplacementPlugin(), 
    // prints more readable module names in the browser console on HMR updates 
    new webpack.NamedModulesPlugin(), 
    new webpack.NoEmitOnErrorsPlugin() 
    ] 
} 

的Reactjs路由器相关的组件。

的Rootjs:

import React from 'react' 
import { Router } from 'react-router' 
import { Provider } from 'react-redux' 
import routes from './routes' 

const Root = ({store, history}) => { 
    return (
    <Provider store={store}> 
     <Router history={history}> 
     { routes } 
     </Router> 
    </Provider> 
) 
} 

export default Root 

路线:

import React from 'react' 
import { Route } from 'react-router' 
import App from './containers/app' 
import Foobar from './containers/foobar' 

export default (
    <Route path='/' component={App}> 
    <Route path='foobar' component={Foobar} /> 
    </Route> 
) 

的Server.js:

import express from 'express' 
import path from 'path' 
import superagent from 'superagent' 
import chalk from 'chalk' 

import React from 'react' 
import { renderToString } from 'react-dom/server' 
import { Provider } from 'react-redux' 
import { match, RouterContext } from 'react-router' 
import routes from './src/js/routes' 

import configureStore from './src/js/store' 
import App from './src/js/containers/app' 

const app = express() 
const router = express.Router() 
const port = process.env.PORT ? process.env.PORT : 3000 
var serverInstance = null 
var dist = path.join(__dirname, ('dist' + (process.env.NODE_ENV ? '/' + process.env.NODE_ENV : 'staging'))) 
var config = null 
var fs = require('fs') 
var htmlTemplateString = '' 

/** 
* Environment settings 
*/ 
if (['staging', 'production'].indexOf(process.env.NODE_ENV) > -1) { 
    console.log('break 1') 
    dist = path.resolve(__dirname, process.env.NODE_ENV) 
    config = require('../config') 
    htmlTemplateString = fs.readFileSync(dist + '/index.html', 'utf-8') 
} else { 
    console.log('break 2') 
    config = require('./config') 
    htmlTemplateString = fs.readFileSync('./dist/production/index.html', 'utf-8') 
} 

/** 
* Process error handling 
*/ 
process.on('uncaughtException', (err) => { 
    throw err 
}) 

process.on('SIGINT',() => { 
    serverInstance.close() 
    process.exit(0) 
}) 

/** 
* The Cross origin resource sharing rules 
*/ 
app.use((req, res, next) => { 
    res.setHeader('Access-Control-Allow-Origin', '*') 
    res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS, PUT, PATCH, DELETE') 
    res.setHeader('Access-Control-Allow-Headers', 'X-Requested-With,content-type') 
    res.setHeader('Access-Control-Allow-Credentials', true) 
    next() 
}) 

/** 
* Health check 
*/ 
app.use('/healthcheck', (req, res) => { 
    res.json({ 
    'env': { 
     'NODE_ENV': process.env.NODE_ENV 
    } 
    }) 
    res.end() 
}) 

router.use('/api/test', (req, res) => { 
    superagent 
    .get('https://jsonip.com/') 
    .end((err, response) => { 
     if (err) { 
     console.log('api test err', err) 
     } 
     res.send(response.body) 
    }) 
}) 

// HMR only in development 
if (process.env.NODE_ENV !== 'production' && process.env.NODE_ENV !== 'staging') { 
    console.log('Development environment: Starting webPack middleware...') 

    const webpack = require('webpack') 
    const webpackHotMiddleware = require('webpack-hot-middleware') 
    const webpackDevConfig = require('./webpack.dev.config') 
    const compiler = webpack(webpackDevConfig) 

    var webpackDevMiddleware = require('webpack-dev-middleware') 
    var devMiddleware = webpackDevMiddleware(compiler, { 
    noInfo: true, 
    publicPath: webpackDevConfig.output.publicPath, 
    stats: { 
     colors: true 
    } 
    }) 

    router.use(devMiddleware) 

    router.use(webpackHotMiddleware(compiler, { 
    log: console.log 
    })) 

    // Production needs physical files! (built via separate process) 
    router.use('/assets', express.static(dist)) 

    // any other is mapped here 
    router.get('*', (req, res, next) => { 
    console.log('req.url: ', req.url) 
    match({ routes, location: req.url }, (err, redirect, props) => { 
     if (props) { 
     const preloadedState = {'foobar': 1} 
      // Create a new Redux store instance 
     const store = configureStore(preloadedState) 
      // Render the component to a string 
     const myAppHtml = renderToString(<RouterContext {...props} />) 

      // Grab the initial state from our Redux store 
     const finalState = store.getState() 
      // Send the rendered page back to the client 
     let html = htmlTemplateString.replace('<div id="app">', '<div id="app">' + myAppHtml) 

      // Paste the state into the html 
     const preloadedStateScript = `<script>window.__PRELOADED_STATE__ = ${JSON.stringify(finalState).replace(/</g, '\\x3c')}</script>` 
     html = html.replace('</head>', preloadedStateScript) 
     res.send(html) 
     } else { 
     res.status(404).send('Not found') 
     } 
    }) 
    }) 
} 

app.disable('x-powered-by') 

app.use('/', router) 

serverInstance = app.listen(port, (error) => { 
    if (error) { 
    console.log(error) // eslint-disable-line no-console 
    } 
    console.log(chalk.green('[' + config.build_name + '] listening on port ' + port + '!')) 
}) 

回答

0

我最初以为有一些东西需要与其中的WebPack中间件的集,但是在初始代码中有一个错误的地方,所以不得不重构并且现在它运行在服务器端:

import express from 'express' 
import path from 'path' 
import superagent from 'superagent' 
import chalk from 'chalk' 

import React from 'react' 
import { renderToString } from 'react-dom/server' 
import { match, RouterContext } from 'react-router' 
import routes from './src/js/routes' 

import configureStore from './src/js/store' 

const app = express() 
const port = process.env.PORT ? process.env.PORT : 3000 
var serverInstance = null 
var dist = path.join(__dirname, ('dist' + (process.env.NODE_ENV ? '/' + process.env.NODE_ENV : 'staging'))) 
var config = null 
var fs = require('fs') 
var htmlTemplateString = '' 

const webpack = require('webpack') 
const webpackHotMiddleware = require('webpack-hot-middleware') 
const webpackDevConfig = require('./webpack.dev.config') 
const compiler = webpack(require('./webpack.dev.config')) 
var webpackDevMiddleware = require('webpack-dev-middleware') 

config = require('./config') 
htmlTemplateString = fs.readFileSync('./dist/production/index.html', 'utf-8') 

/** 
* Process error handling 
*/ 
process.on('uncaughtException', (err) => { 
    throw err 
}) 

process.on('SIGINT',() => { 
    serverInstance.close() 
    process.exit(0) 
}) 

app.use(webpackDevMiddleware(compiler, { 
    noInfo: true, 
    publicPath: webpackDevConfig.output.publicPath, 
    stats: { 
    colors: true 
    } 
})) 

app.use(webpackHotMiddleware(compiler, { 
    log: console.log 
})) 

/** 
* The Cross origin resource sharing rules 
*/ 
app.use((req, res, next) => { 
    res.setHeader('Access-Control-Allow-Origin', '*') 
    res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS, PUT, PATCH, DELETE') 
    res.setHeader('Access-Control-Allow-Headers', 'X-Requested-With,content-type') 
    res.setHeader('Access-Control-Allow-Credentials', true) 
    next() 
}) 

/** 
* Health check 
*/ 
app.use('/healthcheck', (req, res) => { 
    res.json({ 
    'env': { 
     'NODE_ENV': process.env.NODE_ENV 
    } 
    }) 
    res.end() 
}) 

app.use('/api/test', (req, res) => { 
    superagent 
    .get('https://jsonip.com/') 
    .end((err, response) => { 
     if (err) { 
     console.log('api test err', err) 
     } 
     res.send(response.body) 
    }) 
}) 

app.use('/assets', express.static(dist)) 

// any other is mapped here 
app.get('*', (req, res, next) => { 
    console.log('req.url', req.url) 
    match({ routes: routes, location: req.url }, (error, redirectLocation, props) => { 
    if (error) { 
     res.status(500).send(error.message) 
    } else if (redirectLocation) { 
     res.redirect(302, redirectLocation.pathname + redirectLocation.search) 
    } else if (props) { 
     const preloadedState = {'foobar': 1} 
      // Create a new Redux store instance 
     const store = configureStore(preloadedState) 
      // Render the component to a string 
     const myAppHtml = renderToString(<RouterContext {...props} />) 

      // Grab the initial state from our Redux store 
     const finalState = store.getState() 
      // Send the rendered page back to the client 
     let html = htmlTemplateString.replace('<div id="app">', '<div id="app">' + myAppHtml) 

      // Paste the state into the html 
     const preloadedStateScript = `<script>window.__PRELOADED_STATE__ = ${JSON.stringify(finalState).replace(/</g, '\\x3c')}</script>` 
     html = html.replace('</head>', preloadedStateScript) 
     res.status(200).send(html) 
    } else { 
     res.status(404).send('Not found') 
    } 
    }) 
}) 

serverInstance = app.listen(port, (error) => { 
    if (error) { 
    console.log(error) // eslint-disable-line no-console 
    } 
    console.log(chalk.green('[' + config.build_name + '] listening on port ' + port + '!')) 
})