Node.js 使用通用网页包找到继电器

Node.js 使用通用网页包找到继电器,node.js,webpack,relay,isomorphic-javascript,Node.js,Webpack,Relay,Isomorphic Javascript,我在npm运行开始时遇到此错误: TypeError: Cannot read property 'pathname' of undefined 这是由以下因素引发的: ReactDOMServer.renderToString(element) 编辑:获得了一些更好的日志记录,发现createResolver(fetcher)正在返回一个具有以下内容的对象: lastQueries: [ undefined, undefined ], /src/server.js /start serv

我在npm运行开始时遇到此错误:

TypeError: Cannot read property 'pathname' of undefined
这是由以下因素引发的:

ReactDOMServer.renderToString(element)
编辑:获得了一些更好的日志记录,发现
createResolver(fetcher)
正在返回一个具有以下内容的对象:

lastQueries: [ undefined, undefined ],
/src/server.js
/start server.babel
/src/start server
/webpack.config
/webpack.isomorphic.settings.json
/webpack.isomorphic.client.babel.js
/webpack.isomorphic.server.babel.js
/src/fetcher.js
/src/router.js

我有一些引用
context.router.isActive的组件的遗留代码,这些代码是从我使用
react router relay
时开始的。解决了这个问题

我锁定了生成错误的表达式,但我不确定这是
found
包中的错误还是配置中的错误。
import path from 'path';
import express from 'express';
import bodyParser from 'body-parser';
import httpProxy from 'http-proxy';
import { getFarceResult } from 'found/lib/server';
import ReactDOMServer from 'react-dom/server';
import serialize from 'serialize-javascript';
import { ServerFetcher } from './fetcher';
import { createResolver, historyMiddlewares, render, routeConfig } from './router';

const {PRIVATE_IP, API_IP, PORT, API_PORT} = process.env;

const publicPath = path.join(__dirname, '/..', 'public');

export default parameters => {
  const app = express();

  const proxy = httpProxy.createProxyServer({ ignorePath: true });

  const proxyOptions = {
    target: `http://${API_IP}:${API_PORT}/graphql-api`,
    ignorePath: true,
  };

  function getFromProxy (req, res) {
    req.removeAllListeners('data');
    req.removeAllListeners('end');

    process.nextTick(_ => {
      if (req.body) {
        req.emit('data', JSON.stringify(req.body));
      }
      req.emit('end');
    });

    proxy.web(req, res, proxyOptions);
  }

  app.use(express.static(publicPath));

  app.use(bodyParser.json({ limit: '1mb' }));

  app.use('/graphql-api', getFromProxy);

  app.use(async (req, res) => {
    const fetcher = new ServerFetcher(`http://${PRIVATE_IP}:${PORT}/graphql-api`);

    const { redirect, status, element } = await getFarceResult({
      url: req.url,
      historyMiddlewares,
      routeConfig,
      resolver: createResolver(fetcher),
      render,
    });

    if (redirect) {
      res.redirect(302, redirect.url);
      return;
    }

    res.status(status).send(`
<!DOCTYPE html>
<html lang="en">
  ...
  <body>
    <div id="root">${ReactDOMServer.renderToString(element)}</div>
    <script>
      window.__RELAY_PAYLOADS__ = ${serialize(fetcher, { isJSON: true })};
    </script>
    <script src="/bundle.js"></script>
  </body>
</html>
    `);
  });

  app.listen(PORT, PRIVATE_IP, err => {
    if (err) {
      console.log(`[Error]: ${err}`);
    }
    console.info(`[express server]: listening on ${PRIVATE_IP}:${PORT}`);
  });
};
"scripts": {
    "schema": "gulp load-schema",
    "relay": "relay-compiler --src ./src --schema ./data/schema.graphql",
    "start": "npm-run-all schema relay prepare-server-build start-development-workflow",
    "start-development-workflow": "npm-run-all --parallel development-webpack-build-for-client development-webpack-build-for-server development-start-server",
    "prepare-server-build": "universal-webpack --settings ./webpack.isomorphic.settings.json prepare",
    "development-webpack-build-for-client": "webpack-dev-server --hot --inline --config \"./webpack.isomorphic.client.babel.js\" --port 8080 --colors",
    "development-webpack-build-for-server": "webpack --watch --config \"./webpack.isomorphic.server.babel.js\" --colors",
    "development-start-server": "nodemon \"./start-server.babel\" --watch \"./build/dist/server\"",
    ...
},
require('babel-register')({ ignore: /\/(build|node_modules)\// });
require('babel-polyfill');

require('./src/start-server.js');
import 'source-map-support/register';

import startServer from 'universal-webpack/server';
import settings from '../webpack.isomorphic.settings.json';
import configuration from '../webpack.config';

startServer(configuration, settings);
import webpack from 'webpack';
import path from 'path';
import env from 'gulp-env';
import CopyWebpackPlugin from 'copy-webpack-plugin';
import ExtractTextPlugin from 'extract-text-webpack-plugin';

// process.traceDeprecation = true;

if (!process.env.NODE_ENV) {
  env({file: './.env', type: 'ini'});
}
const {
  NODE_ENV,
  PRIVATE_IP,
  API_IP,
  PORT,
  API_PORT,
  GOOGLE_ANALYTICS_KEY,
} = process.env;

const PATHS = {
  root: path.join(__dirname),
  src: path.join(__dirname, 'src'),
  public: path.join(__dirname, 'build', 'public'),
  shared: path.join(__dirname, 'src', 'shared'),
  fonts: path.join(__dirname, 'src', 'shared', 'fonts'),
  robots: path.join(__dirname, 'src', 'robots.txt'),
};

let devtool;
const plugins = [
  new webpack.DefinePlugin({
    'process.env': {
      NODE_ENV: JSON.stringify(NODE_ENV),
      PRIVATE_IP: JSON.stringify(PRIVATE_IP),
      API_IP: JSON.stringify(API_IP),
      PORT: JSON.stringify(PORT),
      API_PORT: JSON.stringify(API_PORT),
      GOOGLE_ANALYTICS_KEY: JSON.stringify(GOOGLE_ANALYTICS_KEY),
    },
  }),
  new ExtractTextPlugin('styles.css'),
];

if (NODE_ENV === 'production') {
  devtool = 'source-map';
  plugins.push(
    new webpack.optimize.UglifyJsPlugin({
      compress: {
        warnings: false,
        screw_ie8: true,
      },
    }),
    new webpack.NoErrorsPlugin(),
    new CopyWebpackPlugin([
      { from: PATHS.robots, to: PATHS.public },
    ]),
  );
} else {
  devtool = 'eval-source-map';
  plugins.push(
    new webpack.NamedModulesPlugin(),
  );
}

const config = {
  devtool,
  context: PATHS.root,
  entry: [
    PATHS.src,
  ],
  output: {
    path: PATHS.public,
    filename: 'bundle.js',
    publicPath: '/',
  },
  plugins,
  module: {
    rules: [
      {
        test: /\.js$/,
        include: PATHS.src,
        loader: 'babel-loader',
      },
      {
        test: /\.css$/,
        include: PATHS.src,
        use: ExtractTextPlugin.extract({
          fallback: 'style-loader',
          use: {
            loader: 'css-loader',
            options: {
              modules: true,
              localIdentName: '[name]_[local]__[hash:base64:5]',
            },
          },
        }),
      },
      {
        test: /\.svg$/,
        include: PATHS.src,
        use: [
          { loader: 'url-loader', options: { limit: 10000 } },
        ],
      },
      {
        test: /\.png$/,
        include: PATHS.src,
        use: [
          { loader: 'url-loader', options: { limit: 65000 } },
        ],
      },
      {
        test: /\.(woff|woff2)$/,
        include: PATHS.fonts,
        loader: 'url-loader',
        options: {
          name: 'font/[hash].[ext]',
          limit: 50000,
          mimetype: 'application/font-woff',
        },
      },
    ],
  },
  resolve: {
    modules: [
      PATHS.src,
      'node_modules',
    ],
    alias: {
      root: PATHS.root,
    },
  },
};

export default config;
{
  "server": {
    "input": "./src/server.js",
    "output": "./build/dist/server.js"
  }
}
import { client } from 'universal-webpack/config';
import settings from './webpack.isomorphic.settings.json';
import configuration from './webpack.config';

export default client(configuration, settings);
import { server } from 'universal-webpack/config';
import settings from './webpack.isomorphic.settings.json';
import configuration from './webpack.config';

export default server(configuration, settings);
import 'isomorphic-fetch';

// TODO: Update this when someone releases a real, production-quality solution
// for handling universal rendering with Relay Modern. For now, this is just
// enough to get things working.

class FetcherBase {
  constructor (url) {
    this.url = url;
  }

  async fetch (operation, variables) {
    const response = await fetch(this.url, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({ query: operation.text, variables }),
    });
    return response.json();
  }
}

export class ServerFetcher extends FetcherBase {
  constructor (url) {
    super(url);

    this.payloads = [];
  }

  async fetch (...args) {
    const i = this.payloads.length;
    this.payloads.push(null);
    const payload = await super.fetch(...args);
    this.payloads[i] = payload;
    return payload;
  }

  toJSON () {
    return this.payloads;
  }
}

export class ClientFetcher extends FetcherBase {
  constructor (url, payloads) {
    super(url);

    this.payloads = payloads;
  }

  async fetch (...args) {
    if (this.payloads.length) {
      return this.payloads.shift();
    }

    return super.fetch(...args);
  }
}
import queryMiddleware from 'farce/lib/queryMiddleware';
import createRender from 'found/lib/createRender';
import makeRouteConfig from 'found/lib/makeRouteConfig';
import Route from 'found/lib/Route';
import { Resolver } from 'found-relay';
import React from 'react';
import { Environment, Network, RecordSource, Store } from 'relay-runtime';

// static
import CorePage from 'core/components/CorePage';
import LoadingComponent from 'core/components/LoadingComponent';
import ErrorComponent from 'core/components/ErrorComponent';
import HomePage from 'home/components/HomePage';
import NotFound from 'not-found/components/NotFoundPage';

// user
import UserContainer from 'user/containers/UserContainer';
import UserContainerQuery from 'user/queries/UserContainerQuery';

export const historyMiddlewares = [queryMiddleware];

export function createResolver (fetcher) {
  const environment = new Environment({
    network: Network.create((...args) => fetcher.fetch(...args)),
    store: new Store(new RecordSource()),
  });

  return new Resolver(environment);
}

export const routeConfig = makeRouteConfig(
  <Route path={'/'} Component={CorePage}>
    <Route Component={HomePage} />
    <Route
      path={'user/:userId'}
      Component={UserContainer}
      query={UserContainerQuery}
    />
    <Route path={'*'} component={NotFound} />
  </Route>,
);

export const render = createRender({
  renderPending: _ => <LoadingComponent />,
  renderError: error => {
    console.error(`Relay renderer ${error}`);
    return <ErrorComponent />; // renderArgs.retry?
  },
});
import BrowserProtocol from 'farce/lib/BrowserProtocol';
import createInitialFarceRouter from 'found/lib/createInitialFarceRouter';
import React from 'react';
import ReactDOM from 'react-dom';
import injectTapEventPlugin from 'react-tap-event-plugin';
import { AppContainer } from 'react-hot-loader';
import { ClientFetcher } from './fetcher';
import { createResolver, historyMiddlewares, render, routeConfig } from './router';

injectTapEventPlugin();

(async () => {
  // eslint-disable-next-line no-underscore-dangle
  const fetcher = new ClientFetcher('/graphql-api', window.__RELAY_PAYLOADS__);
  const resolver = createResolver(fetcher);

  const Router = await createInitialFarceRouter({
    historyProtocol: new BrowserProtocol(),
    historyMiddlewares,
    routeConfig,
    resolver,
    render,
  });

  const rootRender = Component => {
    ReactDOM.render(
      <AppContainer>
        <Component resolver={resolver} />
      </AppContainer>,
      document.getElementById('root'),
    );
  };

  rootRender(Router);
})();