Webpack和Babel:React组件的服务器端渲染“Unexpected token '<'”

3

我已经调查了这个问题3天了,但是还无法解决。

完整的错误信息如下:

C:\Users\XXXXXX\WebstormProjects\XXXX\server\routes\auth.jsx:58
    return res.send(ReactDOMServer.renderToString(<LoginPage />));
                                                  ^
SyntaxError: Unexpected token <
    at createScript (vm.js:56:10)
    at Object.runInThisContext (vm.js:97:10)
    at Module._compile (module.js:542:28)
    at Object.Module._extensions..js (module.js:579:10)
    at Module.load (module.js:487:32)
    at tryModuleLoad (module.js:446:12)
    at Function.Module._load (module.js:438:3)
    at Module.require (module.js:497:17)
    at require (internal/module.js:20:19)
    at Object.<anonymous> (C:\Users\XXXXXX\WebstormProjects\XXXX\index.js:10:20)

这是我的webpack.config.js文件:

const path = require('path');


module.exports = {
    entry: path.join(__dirname, '/client/src/app.jsx'),
    output: {
        path: path.join(__dirname, '/client/dist/js'),
        filename: 'app.js',
        publicPath: "/"
    },

    module: {
        loaders: [{
            test: /\.jsx?$/,
            include: [
                path.join(__dirname, '/client/src'),
                path.join(__dirname, '/server/routes')

        ],
            loader: 'babel-loader',
            query: {
                babelrc: false,
                presets: ['es2015', 'stage-2', 'react']
            }
        }],
    },

    devServer: {
        historyApiFallback: true
    },
    watch: true
};

现在是/server/routes/auth.jsx文件:
const express = require('express');
const validator = require('validator');

const router = new express.Router();
const React = require('react');
const ReactDOMServer = require('react-dom/server');
const LoginPage = require('../../client/src/containers/LoginPage.jsx');


function validateLoginForm(payload) {
    const errors = {};
    let isFormValid = true;
    let message = '';

    if (!payload || typeof payload.email !== 'string' || payload.email.trim().length === 0) {
        isFormValid = false;
        errors.email = 'Please provide your email address.';
    }

    if (!payload || typeof payload.password !== 'string' || payload.password.trim().length === 0) {
        isFormValid = false;
        errors.password = 'Please provide your password.';
    }

    if (!payload || typeof payload.password !== 'string' || payload.password.trim().length <= 8)
    {
        isFormValid = false;
        errors.password = 'Please provide a password that\'s more than 8 char';
    }
    if (!isFormValid) {
        message = 'Check the form for errors.';
    }

    return {
        success: isFormValid,
        message,
        errors
    };
}

router.post('/login', (req, res) => {
    console.log("lol");
    const validationResult = validateLoginForm(req.body);
    if (!validationResult.success) {
        return res.status(400).json({
            success: false,
            message: validationResult.message,
            errors: validationResult.errors
        });
    }
    console.log("Went through validationResult without problems");
    return res.status(200).end();
});


router.get('/login', (req, res) => {
    console.log(req.url);
    return res.send(ReactDOMServer.renderToString(<LoginPage />)); // THE PROBLEM
});

router.get('/', (req, res) => {
    console.log(req.url);
    console.log("lmao")
});


module.exports = router;

最终的 /client/src/containers/LoginPage.jsx :
import React from 'react';
import LoginForm from '../components/LoginForm.jsx';


class LoginPage extends React.Component{

    /**
     * Class constructor.
     */
    constructor(props) {
        super(props);

        // set the initial component state
        this.state = {
            errors: {},
            user: {
                email: '',
                password: ''
            }
        };

        this.processForm = this.processForm.bind(this);
        this.changeUser = this.changeUser.bind(this);
    }

    /**
     * Process the form.
     *
     * @param {object} event - the JavaScript event object
     */
    processForm(event) {
        // prevent default action. in this case, action is the form submission event
        event.preventDefault();

        const email = encodeURIComponent(this.state.user.email);
        const password = encodeURIComponent(this.state.user.password);
        const formData = `email=${email}&password=${password}`;

        // create an AJAX request
        const xhr = new XMLHttpRequest();
        xhr.open('post', '/login');
        xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
        xhr.responseType = 'json';
        xhr.addEventListener('load', () => {
            if (xhr.status === 200) {
                // success

                // change the component-container state
                this.setState({
                    errors: {}
                });

                console.log('The form is valid');
            } else {
                // failure

                // change the component state
                const errors = xhr.response.errors ? xhr.response.errors : {};
                errors.summary = xhr.response.message;

                this.setState({
                    errors
                });
            }
        });

        xhr.send(formData);
    }

    /**
     * Change the user object.
     *
     * @param {object} event - the JavaScript event object
     */
    changeUser(event) {
        const field = event.target.name;
        const user = this.state.user;
        user[field] = event.target.value;

        this.setState({
            user
        });
    }

    /**
     * Render the component.
     */
    render() {
        return (
            <LoginForm
                onSubmit={this.processForm}
                onChange={this.changeUser}
                errors={this.state.errors}
                user={this.state.user}
            />
        );
    }

}

export default LoginPage;

我首先添加了path.join(__dirname, '/server/routes'),以告诉Webpack和Babel还要搜索这个文件夹以转译JSX,但不管怎样都失败了。

然后我将auth.jsx中的 return res.send(ReactDOMServer.renderToString(<LoginPage />)); 替换为:

var html = ReactDOMServer.renderToString(React.createElement(LoginPage));
return res.send(ReactDOMServer.renderToString('base', html));

但是,这样做会导致 Node 报错,错误信息如下:
C:\Users\XXXXXX\WebstormProjects\XXXX\client\src\containers\LoginPage.jsx:1
(function (exports, require, module, __filename, __dirname) { import React from 'react';
                                                              ^^^^^^
SyntaxError: Unexpected token import
    at createScript (vm.js:56:10)
    at Object.runInThisContext (vm.js:97:10)
    at Module._compile (module.js:542:28)
    at Object.Module._extensions..js (module.js:579:10)
    at Module.load (module.js:487:32)
    at tryModuleLoad (module.js:446:12)
    at Function.Module._load (module.js:438:3)
    at Module.require (module.js:497:17)
    at require (internal/module.js:20:19)
    at Object.<anonymous> (C:\Users\XXXXXX\WebstormProjects\XXXX\server\routes\auth.jsx:8:19)

再次强调,这是一个转码的问题。

请注意,在auth.jsx中没有这条路由,Web应用程序可以正常工作,但我无法通过URL访问/login。

我做错了什么?

我正在使用最新版本的Express、React、React Router和Node。我的操作系统是Windows 7。

提前致谢。


你展示的是 webpack.config.js,不是 package.json,对吗? - atomrc
@atomrc 抱歉,已经更正了。谢谢 :) - Gaëtan Boyals
可能是因为.jsx文件包含非ES2015代码,而webpack无法读取。 - Krunal Limbad
@KrunalLimbad 那不是 Babel 的工作吗?将 ES6 代码转换为 Webpack 可以读取的内容? - Gaëtan Boyals
很高兴能帮忙,但很抱歉给你带来了误导 :) - Krunal Limbad
显示剩余2条评论
1个回答

6
我认为我知道这里的问题所在。
你确实编译了你的jsx文件,而且你的webpack.config.js看起来很完美(它包含react预设,所以应该可以工作)。
但是你好像只为客户端进行了编译,并且正在尝试在服务器上使用未编译的文件。
但node本身无法读取jsx。
我建议的选项是,允许babel编译服务器端文件。
要做到这一点,你可以使用babel-cli。
只需在你的package.json中添加以下内容即可。
"scripts": {
  "start": "babel-node index.js --presets es2015,react"
}

您可以这样启动服务器

npm start

然后它应该首先使用babel编译文件并启动服务器。

这是一个示例,您可以从中获得灵感:https://github.com/babel/example-node-server

这应该解决您的问题。


谢谢你的回答。你发布的代码的第二部分确实解决了第一个问题,即“Unexpected token <”。 代码的第一部分应该解决第二个问题,“Unexpected token import”,但是我对React和Javascript都很陌生,所以我不知道如何让Webpack同时编译多个文件。我应该按照这里所说的做吗: https://webpack.github.io/docs/multiple-entry-points.html? - Gaëtan Boyals
我更新了我的答案。这个解决方案应该可以解决你的两个问题,而不需要在任何源文件中更改任何位 :) - atomrc
抱歉出现了重复的评论,但我尝试安装node-jsx,仍然出现相同的错误但是错误信息略有不同:Error transforming C:\Users\XXXXXX\WebstormProjects\XXXX\client\src\components\LoginPage.jsx to JS: Error: Parse Error: Line 1: Illegal import declaration。这让我感觉错误是由我的代码引起的,但无论我如何反复阅读它,都找不到错在哪里。 - Gaëtan Boyals
不,这个解决方案没有考虑到 import 部分。我的更新答案应该可以解决这个问题。 - atomrc
是的,它成功了!我之前使用 nodemon --use_strict index.js 作为 start 脚本,但我想我不得不放弃它。现在我遇到了另一个错误,但它与这个问题无关。非常感谢,你救了我的一天 :p 编辑:算了,写评论之前没有点击链接。现在它可以使用 nodemon 和 babel-cli :) - Gaëtan Boyals
1
哦,你仍然可以使用 nodemon :) nodemon --use_stict index.js --exec babel-node --presets es2015,react :) - atomrc

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接