React SSR ReferenceError:未定义文档

时间:2017-11-03 15:32:28

标签: node.js reactjs webpack server-rendering react-universal

我无法呈现反应服务器端。我总是得到            ^

  

没有定义ReferenceError:文档

     

var root = document.getElementById('root');

我知道什么是讨厌该节点不了解浏览器文档对象。但我似乎无法解决它。我在webpack或服务器或app.js中做错了什么。

这是我的代码

server.js

import express from 'express';
import path from 'path';
import React from 'react';
import { renderToString } from 'react-dom/server';
import { StaticRouter } from 'react-router-dom';
import { Provider } from 'react-redux';
import configureStore from './src/store/configureStore';
import routes from './src';

const app = express();

app.use(express.static(path.join(__dirname, 'build')));

/**
 * React application route, supports server side rendering.
 */
app.get('/*', function(req, res) {
  // Create a context for <StaticRouter>, which will allow us to
  // query for the results of the render.
  const reactRouterContext = {};

  // Compile an initial state
  const preloadedState = {};

  // Create a new Redux store instance
  const store = configureStore(preloadedState);

  // Declare our React application.
  const app = (
    <Provider store={store}>
      <StaticRouter location={req.url} context={reactRouterContext}>
        {routes}
      </StaticRouter>
    </Provider>
  );
  const appString = renderToString(app);
  // Load contents of index.html
  fs.readFile(
    path.join(__dirname, 'build', 'index.html'),
    'utf8',
    (err, data) => {
      if (err) throw err;

      // Inserts the rendered React HTML into our main div
      const document = data.replace(
        /<main id="root"><\/main>/,
        `<main id="root">${app}</main>`
      );

      // Sends the response back to the client
      res.status(200).send();
    }
  );
});

app.listen(9000);

webpack.config.node.js

const fs = require('fs');
const path = require('path');
const webpack = require('webpack');
const UglifyJSPlugin = require('uglifyjs-webpack-plugin');

// http://jlongster.com/Backend-Apps-with-Webpack--Part-I
let nodeModules = {};
fs
  .readdirSync('node_modules')
  .filter(function(f) {
    return f.charAt(0) !== '.';
  })
  .forEach(function(f) {
    nodeModules[f] = 'commonjs ' + f;
  });

module.exports = {
  target: 'node',
  entry: './server.js',
  output: {
    path: path.join(__dirname, 'build'),
    filename: 'server.js',
    libraryTarget: 'commonjs2',
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        loader: 'babel-loader',
        query: {
          presets: ['es2015', 'react', 'stage-0'],
        },
      },
      {
        test: /\.css$/,
        exclude: /node_modules/,
        loader: 'css-loader',
      },
      {
        test: /\.(gif|png|jpe?g|svg)$/i,
        use: [
          'file-loader',
          {
            loader: 'image-webpack-loader',
            options: {
              bypassOnDebug: true,
            },
          },
        ],
      },
    ],
  },
  plugins: [
    new webpack.DefinePlugin({
      PRODUCTION: JSON.stringify(true),
    }),
    new webpack.LoaderOptionsPlugin({
      minimize: true,
    }),
    //new UglifyJSPlugin(),
  ],
  externals: nodeModules,
};

最后这是我的app.js

import React from 'react';
import { hydrate, render } from 'react-dom'; // eslint-disable-line
import { Provider } from 'react-redux';
import { BrowserRouter, Route } from 'react-router-dom';
import configureStore from './store/configureStore';
import Home from './routes/home';
import Profile from './routes/profile';
import Contact from './routes/contact';
import RouterWrapper from './routeWrapper';
import './index.css';

const store = configureStore();
window.store = store;

// Does the user's browser support the HTML5 history API?
// If the user's browser doesn't support the HTML5 history API then we
// will force full page refreshes on each page change.
const supportsHistory =
  window && window.history && 'pushState' in window.history ? true : false;

const routes = (
  <div>
    <RouterWrapper>
      <Route exact path="/" component={Home} />
      <Route path="/profile" component={Profile} />
      <Route path="/contact" component={Contact} />
    </RouterWrapper>
  </div>
);

const app = (
  <Provider store={store}>
    <BrowserRouter forceRefresh={!supportsHistory}>{routes}</BrowserRouter>
  </Provider>
);

const root = document.getElementById('root');

const renderApp = newRoot => {
  if (process.env.NODE_ENV === 'development') {
    render(app, newRoot);
  } else {
    hydrate(app, newRoot);
  }
};

renderApp(root);

export default routes;

3 个答案:

答案 0 :(得分:2)

根本原因:插件样式加载程序会将CSS输入CSS到js中以在js中生成样式标签,例如document.createelement ("style");但服务器没有文档api。因此它崩溃了。

尝试使用mini-css-extract-plugin将CSS代码从js文件中拉出。 避免在js文件中使用document.creatEelement ("style")
发布我的配置的一部分

const MiniCssExtractPlugin = require('mini-css-extract-plugin');

module.exports = {
  mode: process.env.NODE_ENV === 'development' ? 'development' : 'production',
  module: {
    rules: [
      {
        test: /\.css$/,
        exclude: /node_modules/,
        use: [MiniCssExtractPlugin.loader, 'css-loader']
        // use: ['style-loader', 'css-loader']
      }
    ],
  },
  plugins: [new MiniCssExtractPlugin()],

};

如有任何疑问,请告诉我

答案 1 :(得分:0)

只需将library(raster) library(rasterVis) r <- raster(system.file("external/test.grd", package="raster")) levelplot(r, at=seq(100, 1850, by = 250)) # calculating the latitudinal means rows <- init(r, v='y') yAve <- zonal(r, rows, fun='mean',na.rm=TRUE) summary(yAve) 移至其他文件即可。导入它们时,将执行整个文件,从而导致错误。

答案 2 :(得分:-1)

导入文件时,将执行文件的全部内容,包括const root = document.getElementById('root');行。

如果您将routes声明移到单独的文件并从那里导出,您应该能够在server.jsapp.js中导入该文件,您应该没问题。