Gaëtan Boyals
Gaëtan Boyals

Reputation: 1228

Webpack & Babel : Server Side rendering of React Component "Unexpected token '<'"

I've been investigating this issue for 3 days now but cannot get it working.

The full error is :

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)

Here is my 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
};

Now the /server/routes/auth.jsx file :

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;

Finally the /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;

I first added the path.join(__dirname, '/server/routes') in order to tell Webpack and babel to also search for this folder to transpile jsx, but it fails no matter what.

I then replaced return res.send(ReactDOMServer.renderToString(<LoginPage />)); in auth.jsx by :

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

but, by doing this, node gives me another error which is :

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)

which is, again, a transpiling problem.

Note that without that route in auth.jsx, the web App works all fine, except that I can't access /login via URL.

What am I doing wrong ?

I'm using the latest versions of Express, React, React Router and Node. My OS is Windows 7.

Thanks in advance

Upvotes: 3

Views: 3008

Answers (1)

atomrc
atomrc

Reputation: 2583

I think I know what is the problem here.

You indeed compile your jsx file and your webpack.config.js seems perfect (it includes react presets, so it should work).

But you are only compiling for the client it seems, and you are trying to consume the uncompiled file on the server.

But node cannot read jsx on its own.

The option I would suggest here, it to allow babel to also compile server side files.

To do that, you can use babel-cli.

Simply add this in your package.json

"scripts": {
  "start": "babel-node index.js --presets es2015,react"
}

And start you server like this

npm start

Then it should first compile the files with babel and start your server.

Here is an example you can inspire from https://github.com/babel/example-node-server

This should fix your problem

Upvotes: 6

Related Questions