JamesListener
JamesListener

Reputation: 3

Node+React+Babel+Webpack, Unexpected token error

First of all, I REALLY tried to fix it myself, I found several similar questions here, but none helped me.

Getting this error:

ERROR in ./src/components/App.jsx
Module parse failed: D:\JS projects\habr-app\src\components\App.jsx Unexpected token (54:6)
You may need an appropriate loader to handle this file type.
|   render() {
|     return (
|       <div className='App'>
|         <h1>Hello World!</h1>
|         <div>
 @ ./src/client.js 3:0-42
 @ multi (webpack)-dev-server/client?http://0.0.0.0:8050 webpack/hot/dev-server babel-polyfill ./src/client.js

client.js file:

import React      from 'react';
import ReactDOM   from 'react-dom';
import App        from './components/App';

ReactDOM.render ("<App />", document.getElementById('react-view'));

render function in App.jsx looks like this:

render() {
    return (
      <div className='App'>
        <h1>Hello World!</h1>
        <div>
          <p>Введите Ваше имя:</p>
          <div><input onChange={this.handleNameChange} /></div>
          {this.renderGreetingWidget()}
        </div>
      </div>
    );
  }

webpack.config.js file:

global.Promise         = require('bluebird');

var webpack            = require('webpack');
var path               = require('path');
var ExtractTextPlugin  = require('extract-text-webpack-plugin');
var CleanWebpackPlugin = require('clean-webpack-plugin');

var publicPath         = 'http://localhost:8050/public/assets';
var cssName            = process.env.NODE_ENV === 'production' ? 'styles-[hash].css' : 'styles.css';
var jsName             = process.env.NODE_ENV === 'production' ? 'bundle-[hash].js' : 'bundle.js';

var plugins = [
  new webpack.DefinePlugin({
    'process.env': {
      BROWSER:  JSON.stringify(true),
      NODE_ENV: JSON.stringify(process.env.NODE_ENV || 'development')
    }
  }),
  new ExtractTextPlugin(cssName)
];

if (process.env.NODE_ENV === 'production') {
  plugins.push(
    new CleanWebpackPlugin([ 'public/assets/' ], {
      root: __dirname,
      verbose: true,
      dry: false
    })
  );
  plugins.push(new webpack.optimize.DedupePlugin());
  plugins.push(new webpack.optimize.OccurenceOrderPlugin());
}

module.exports = {
  entry: ['babel-polyfill', './src/client.js'],
  resolve: {
    extensions:         ['.js', '.jsx']
  },
  plugins: [
     new ExtractTextPlugin("styles.css"),
     new webpack.LoaderOptionsPlugin({
       debug: true,
       options: {
         eslint: { configFile: '.eslintrc' }
       }
     })
  ],
  output: {
    path: `${__dirname}/public/assets/`,
    filename: jsName,
    publicPath
  },
  module: {
    rules: [
      {
        test: /\.css$/,
        use: ExtractTextPlugin.extract({
          fallback: "style-loader",
          use: "css-loader"
        })
      }
    ],
    loaders: [
      {
        test: /\.css$/,
        loader: ExtractTextPlugin.extract({fallback: 'style-loader', use: 'css-loader!postcss-loader'})
      },
      {
        test: /\.less$/,
        loader: ExtractTextPlugin.extract({fallback: 'style-loader', use: 'css-loader!postcss-loader!less-loader'})
      },
      { test: /\.gif$/, loader: 'url-loader?limit=10000&mimetype=image/gif' },
      { test: /\.jpg$/, loader: 'url-loader?limit=10000&mimetype=image/jpg' },
      { test: /\.png$/, loader: 'url-loader?limit=10000&mimetype=image/png' },
      { test: /\.svg/, loader: 'url-loader?limit=26000&mimetype=image/svg+xml' },
      { test: /\.(woff|woff2|ttf|eot)/, loader: 'url-loader?limit=1' },
      { test: /\.jsx?$/, loaders: ['react-hot-loader', 'babel-loader?presets[]=react,presets[]=es2015'],
                          exclude: [/node_modules/, /public/] , query: {presets: ['es2015', 'react', 'react-hot']} },
      { test: /\.json$/, loader: 'json-loader' },
    ]
  },
  devtool: process.env.NODE_ENV !== 'production' ? 'source-map' : null,
  devServer: {
    headers: { 'Access-Control-Allow-Origin': '*' }
  }
};

The only way I can fix this problem - is to put quotes around html in render:

render() {
    return (
      `<div className='App'>
        <h1>Hello World!</h1>
        <div>
          <p>Введите Ваше имя:</p>
          <div><input onChange={this.handleNameChange} /></div>
          {this.renderGreetingWidget()}
        </div>
      </div>`
    );
  }

But after that I'm gettin this error in browser and nodemon:

Invariant Violation: App.render(): A valid React element (or null) must be returned. You may have returned undefined, an array or some other invalid object.

I checked and rechecked all dependencies, modules and my files. Still, I cant find an error. Could someone help me, please?

P.S. Sorry for my awful English.

Funny thing. When I start nodemon without those quotes in App.jsx, my page loads, but without css. After that I add quotes in file App.jsx and now webpack-devserver builds everything right, and page gets .css after refresh. JS-script still doesnt work on it, but looks almost like it should... Right until I restart nodemon... It starts to show same error "Invariant Violation: App.render(): A valid React element (or null) must be returned. You may have returned undefined, an array or some other invalid object."

Upvotes: 0

Views: 819

Answers (1)

Michael Jungo
Michael Jungo

Reputation: 33010

The error tells you that you did not define a loader that can handle JSX, although it might look like you did in your webpack config. The problem is that you define both module.rules and module.loaders. When webpack sees module.rules it ignores module.loaders completely (though it still exists for compatibility reason). The fix is simple, just put all loaders under module.rules.

And there is also a problem in your .jsx? rule, because query (which is also deprecated and replaced with options) cannot be used for an array of loaders, but instead should be defined per loader in the array. Since you did it inline as string you don't need it at all.

To get a working config replace the module section with:

module: {
  rules: [
    {
      test: /\.css$/,
      loader: ExtractTextPlugin.extract({fallback: 'style-loader', use: 'css-loader!postcss-loader'})
    },
    {
      test: /\.less$/,
      loader: ExtractTextPlugin.extract({fallback: 'style-loader', use: 'css-loader!postcss-loader!less-loader'})
    },
    { test: /\.gif$/, loader: 'url-loader?limit=10000&mimetype=image/gif' },
    { test: /\.jpg$/, loader: 'url-loader?limit=10000&mimetype=image/jpg' },
    { test: /\.png$/, loader: 'url-loader?limit=10000&mimetype=image/png' },
    { test: /\.svg/, loader: 'url-loader?limit=26000&mimetype=image/svg+xml' },
    { test: /\.(woff|woff2|ttf|eot)/, loader: 'url-loader?limit=1' },
    { test: /\.jsx?$/, use: ['react-hot-loader', 'babel-loader?presets[]=react,presets[]=es2015'],
      exclude: [/node_modules/, /public/] },
    { test: /\.json$/, loader: 'json-loader' },
  ]
},

In case you decide to use options, which is definitely more readable, your .jsx? rule would look like this:

{
  test: /\.jsx?$/,
  use: [
    'react-hot-loader',
    { loader: 'babel-loader', options: { presets: ['react', 'es2015'] } },
  ],
  exclude: [/node_modules/, /public/]
}

As shown in the docs for use.

Upvotes: 1

Related Questions