我在npm run start
上遇到此错误:
TypeError: Cannot read property 'pathname' of undefined
被抛出的是:
ReactDOMServer.renderToString(element)
编辑:获得了更好的日志记录,发现createResolver(fetcher)
正在返回一个对象:
lastQueries: [ undefined, undefined ],
/src/server.js
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}`);
});
};
其他档案:
/package.json
"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\"",
...
},
/start-server.babel
require('babel-register')({ ignore: /\/(build|node_modules)\// });
require('babel-polyfill');
require('./src/start-server.js');
/src/start-server
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);
/webpack.config
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;
/webpack.isomorphic.settings.json
{
"server": {
"input": "./src/server.js",
"output": "./build/dist/server.js"
}
}
/webpack.isomorphic.client.babel.js
import { client } from 'universal-webpack/config';
import settings from './webpack.isomorphic.settings.json';
import configuration from './webpack.config';
export default client(configuration, settings);
/webpack.isomorphic.server.babel.js
import { server } from 'universal-webpack/config';
import settings from './webpack.isomorphic.settings.json';
import configuration from './webpack.config';
export default server(configuration, settings);
/src/fetcher.js
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);
}
}
/src/router.js
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?
},
});
/src/index.js
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);
})();
答案 0 :(得分:-2)
好吧,我。我使用context.router.isActive
时引用react-router-relay
的组件有一些遗留代码。令人讨厌的小错误。