0

UPDATE: I tried adding a publicPath option to my webpack.config.js under output, the value being /static/. I also changed my script tag from <script src="/bundle.js"></script> to <script src="/static/bundle.js"></script>. No luck, which is strange because when I take a look at this example redux app, I can reproduce the error I'm getting by deleting their publicPath option from the webpack.config.js.


I'm in the process of trying to render some html server-side with node/express. The app uses react and redux. I ran 'npm run dev' to start the local server, but upon navigating to it in chrome I found this error in the console:

bundle.js:2 Uncaught SyntaxError: Unexpected token <

Upon further inspection it seems that my bundle.js contains nothing but the compiled html from the server. I'm still trying to wrap my head around server side rendering with node, redux, and most of all webpack, so I'm having difficulty seeing how this could be the case. I'm looking at my IDE and it tells me bundle.js contains tons of compiled js.

My file structure

package.json

{
  "name": "serif.nu",
  "version": "1.0.0",
  "description": "Simple. Fast. Visual. Course Planning for Northwestern University.",
  "main": "index.jsx",
  "scripts": {
    "webpack-watch": "webpack -w",
    "express-server": "node ./server/index.js",
    "dev": "concurrently --kill-others \"npm run webpack-watch\" \"npm run express-server\"",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/Joonpark13/serif.nu.git"
  },
  "author": "Joon Park",
  "license": "ISC",
  "bugs": {
    "url": "https://github.com/Joonpark13/serif.nu/issues"
  },
  "homepage": "https://github.com/Joonpark13/serif.nu#readme",
  "devDependencies": {
    "babel-watch": "^2.0.3",
    "concurrently": "^3.1.0",
    "webpack": "^1.13.3"
  },
  "dependencies": {
    "babel-core": "^6.18.2",
    "babel-loader": "^6.2.7",
    "babel-plugin-transform-object-rest-spread": "^6.16.0",
    "babel-polyfill": "^6.16.0",
    "babel-preset-es2015": "^6.18.0",
    "babel-preset-react": "^6.16.0",
    "babel-register": "^6.18.0",
    "es6-promise": "^4.0.5",
    "express": "^4.14.0",
    "isomorphic-fetch": "^2.2.1",
    "material-ui": "^0.16.1",
    "react": "^15.3.2",
    "react-bootstrap": "^0.30.6",
    "react-dom": "^15.3.2",
    "react-redux": "^4.4.6",
    "react-tap-event-plugin": "^1.0.0",
    "redux": "^3.6.0",
    "redux-thunk": "^2.1.0",
    "request": "^2.79.0",
    "request-json": "^0.6.1"
  },
  "babel": {
    "presets": [
      "es2015",
      "react"
    ],
    "plugins": [
      "transform-object-rest-spread"
    ]
  }
}

webpack.config.js

const path = require('path');

module.exports = {
    entry: path.join(__dirname, 'app/index.jsx'),
    output: {
        filename: 'bundle.js',
        path: path.join(__dirname, 'dist')
    },
    module: {
        loaders: [
            {
                test: /\.jsx$/,
                exclude: /node_modules/,
                loader: 'babel-loader'
            },
            {
                test: /\.js$/,
                exclude: /node_modules/,
                loader: 'babel-loader'
            }
        ]
    }
};

index.js

require('babel-register');
require('./server');

server.js

import express from 'express';
import React from 'react';
import { createStore } from 'redux';
import { Provider } from 'react-redux';
import { renderToString } from 'react-dom/server';
import reducer from '../app/reducers';
import App from '../app/components/App';

const app = express();

function renderFullPage(html, preloadedState) {
  return `
    <!DOCTYPE html>
    <html lang="en">
        <head>
            <meta charset="UTF-8">
            <title>Serif</title>

            <link href="https://fonts.googleapis.com/css?family=Roboto:300,400,500" rel="stylesheet">
            <link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
            <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/latest/css/bootstrap.min.css">
            <link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/fullcalendar/3.0.1/fullcalendar.min.css">
            <link rel="stylesheet" href="materialFullCalendar.css">

            <link rel="apple-touch-icon" sizes="57x57" href="https://s3.amazonaws.com/serif-assets/apple-touch-icon-57x57.png">
            <link rel="apple-touch-icon" sizes="60x60" href="https://s3.amazonaws.com/serif-assets/apple-touch-icon-60x60.png">
            <link rel="apple-touch-icon" sizes="72x72" href="https://s3.amazonaws.com/serif-assets/apple-touch-icon-72x72.png">
            <link rel="apple-touch-icon" sizes="76x76" href="https://s3.amazonaws.com/serif-assets/apple-touch-icon-76x76.png">
            <link rel="apple-touch-icon" sizes="114x114" href="https://s3.amazonaws.com/serif-assets/apple-touch-icon-114x114.png">
            <link rel="apple-touch-icon" sizes="120x120" href="https://s3.amazonaws.com/serif-assets/apple-touch-icon-120x120.png">
            <link rel="apple-touch-icon" sizes="144x144" href="https://s3.amazonaws.com/serif-assets/apple-touch-icon-144x144.png">
            <link rel="apple-touch-icon" sizes="152x152" href="https://s3.amazonaws.com/serif-assets/apple-touch-icon-152x152.png">
            <link rel="apple-touch-icon" sizes="180x180" href="https://s3.amazonaws.com/serif-assets/apple-touch-icon-180x180.png">
            <link rel="icon" type="image/png" href="https://s3.amazonaws.com/serif-assets/favicon-32x32.png" sizes="32x32">
            <link rel="icon" type="image/png" href="https://s3.amazonaws.com/serif-assets/favicon-194x194.png" sizes="194x194">
            <link rel="icon" type="image/png" href="https://s3.amazonaws.com/serif-assets/favicon-96x96.png" sizes="96x96">
            <link rel="icon" type="image/png" href="https://s3.amazonaws.com/serif-assets/android-chrome-192x192.png" sizes="192x192">
            <link rel="icon" type="image/png" href="https://s3.amazonaws.com/serif-assets/favicon-16x16.png" sizes="16x16">
            <link rel="manifest" href="https://s3.amazonaws.com/serif-assets/manifest.json">
            <link rel="mask-icon" href="https://s3.amazonaws.com/serif-assets/safari-pinned-tab.svg" color="#520063">
            <link rel="shortcut icon" href="https://s3.amazonaws.com/serif-assets/favicon.ico">
            <meta name="msapplication-TileColor" content="#da532c">
            <meta name="msapplication-TileImage" content="https://s3.amazonaws.com/serif-assets/mstile-144x144.png">
            <meta name="msapplication-config" content="https://s3.amazonaws.com/serif-assets/browserconfig.xml">
            <meta name="theme-color" content="#520063">
        </head>

        <body>
            <div id="app">${html}</div>

            <script
                src="https://code.jquery.com/jquery-3.1.1.min.js"
                integrity="sha256-hVVnYaiADRTO2PzUGmuLJr8BLUSjGIZsDYGmIJLv2b8="
                crossorigin="anonymous">
            </script>
            <script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.16.0/moment.min.js"></script>
            <script src="//cdnjs.cloudflare.com/ajax/libs/fullcalendar/3.0.1/fullcalendar.min.js"></script>
            <script>
              window.__PRELOADED_STATE__ = ${JSON.stringify(preloadedState)};
            </script>
            <script src="/bundle.js"></script>
        </body>
    </html>
  `;
}

function handleRender(req, res) {
  const store = createStore(reducer);

  const html = renderToString(
    <Provider store={store}>
      <App />
    </Provider>
  );

  const preloadedState = store.getState();

  res.send(renderFullPage(html, preloadedState));
}

app.use(handleRender);

const port = process.env.PORT || 3000;
app.listen(port, () => {
    console.log(`Running on port ${port}`);
});

index.jsx

import 'babel-polyfill'; // http://redux.js.org/docs/advanced/AsyncActions.html#note-on-fetch

import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import store from './store';

import App from './components/App.jsx';

ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('app')
);

store.js

import { createStore, compose, applyMiddleware } from 'redux';
import thunkMiddleware from 'redux-thunk';
import reducer from './reducers';

const preloadedState = window.__PRELOADED_STATE__;

const store = createStore(
    reducer,
    preloadedState,
    compose(
        applyMiddleware(thunkMiddleware),
        window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
    )
);

export default store;

App.jsx

import injectTapEventPlugin from 'react-tap-event-plugin'; // Needed for onTouchTap

import React from 'react';
import getMuiTheme from 'material-ui/styles/getMuiTheme';
import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider';
import { grey500, white, fullBlack } from 'material-ui/styles/colors';
import { fade } from 'material-ui/utils/colorManipulator';
import colors from '../colors';
import NavBar from './NavBar.jsx';
import Serif from './Serif.jsx';

// http://stackoverflow.com/a/34015469/988941
injectTapEventPlugin();

const muiTheme = getMuiTheme({
  palette: {
    primary1Color: colors.northwesternPurple,
    primary2Color: colors.northwesternPurple120,
    primary3Color: grey500,
    accent1Color: colors.northwesternPurple30,
    accent2Color: colors.richBlack10,
    accent3Color: colors.richBlack50,
    textColor: colors.richBlack80,
    alternateTextColor: white,
    canvasColor: white,
    borderColor: colors.richBlack20,
    disabledColor: fade(colors.richBlack80, 0.3),
    pickerHeaderColor: colors.northwesternPurple,
    clockCircleColor: fade(colors.richBlack80, 0.07),
    shadowColor: fullBlack
  }
});

export default class App extends React.Component {
  render() {
    return (
      <MuiThemeProvider muiTheme={muiTheme}>
        <div> {/* MuiThemeProvider requires stricly one child element */}
          <NavBar />
          <Serif />
        </div>
      </MuiThemeProvider>
    );
  }
}

index.html

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Serif</title>

        <link href="https://fonts.googleapis.com/css?family=Roboto:300,400,500" rel="stylesheet">
        <link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
        <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/latest/css/bootstrap.min.css">
        <link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/fullcalendar/3.0.1/fullcalendar.min.css">
        <link rel="stylesheet" href="materialFullCalendar.css">

        <link rel="apple-touch-icon" sizes="57x57" href="https://s3.amazonaws.com/serif-assets/apple-touch-icon-57x57.png">
        <link rel="apple-touch-icon" sizes="60x60" href="https://s3.amazonaws.com/serif-assets/apple-touch-icon-60x60.png">
        <link rel="apple-touch-icon" sizes="72x72" href="https://s3.amazonaws.com/serif-assets/apple-touch-icon-72x72.png">
        <link rel="apple-touch-icon" sizes="76x76" href="https://s3.amazonaws.com/serif-assets/apple-touch-icon-76x76.png">
        <link rel="apple-touch-icon" sizes="114x114" href="https://s3.amazonaws.com/serif-assets/apple-touch-icon-114x114.png">
        <link rel="apple-touch-icon" sizes="120x120" href="https://s3.amazonaws.com/serif-assets/apple-touch-icon-120x120.png">
        <link rel="apple-touch-icon" sizes="144x144" href="https://s3.amazonaws.com/serif-assets/apple-touch-icon-144x144.png">
        <link rel="apple-touch-icon" sizes="152x152" href="https://s3.amazonaws.com/serif-assets/apple-touch-icon-152x152.png">
        <link rel="apple-touch-icon" sizes="180x180" href="https://s3.amazonaws.com/serif-assets/apple-touch-icon-180x180.png">
        <link rel="icon" type="image/png" href="https://s3.amazonaws.com/serif-assets/favicon-32x32.png" sizes="32x32">
        <link rel="icon" type="image/png" href="https://s3.amazonaws.com/serif-assets/favicon-194x194.png" sizes="194x194">
        <link rel="icon" type="image/png" href="https://s3.amazonaws.com/serif-assets/favicon-96x96.png" sizes="96x96">
        <link rel="icon" type="image/png" href="https://s3.amazonaws.com/serif-assets/android-chrome-192x192.png" sizes="192x192">
        <link rel="icon" type="image/png" href="https://s3.amazonaws.com/serif-assets/favicon-16x16.png" sizes="16x16">
        <link rel="manifest" href="https://s3.amazonaws.com/serif-assets/manifest.json">
        <link rel="mask-icon" href="https://s3.amazonaws.com/serif-assets/safari-pinned-tab.svg" color="#520063">
        <link rel="shortcut icon" href="https://s3.amazonaws.com/serif-assets/favicon.ico">
        <meta name="msapplication-TileColor" content="#da532c">
        <meta name="msapplication-TileImage" content="https://s3.amazonaws.com/serif-assets/mstile-144x144.png">
        <meta name="msapplication-config" content="https://s3.amazonaws.com/serif-assets/browserconfig.xml">
        <meta name="theme-color" content="#520063">
    </head>

    <body>
        <div id="app"></div>

        <script
            src="https://code.jquery.com/jquery-3.1.1.min.js"
            integrity="sha256-hVVnYaiADRTO2PzUGmuLJr8BLUSjGIZsDYGmIJLv2b8="
            crossorigin="anonymous">
        </script>
        <script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.16.0/moment.min.js"></script>
        <script src="//cdnjs.cloudflare.com/ajax/libs/fullcalendar/3.0.1/fullcalendar.min.js"></script>
        <script src="/bundle.js"></script>
    </body>
</html>

bundle.js https://gist.github.com/Joonpark13/2b9c3bb7b8e07e8ee898650c7eeb1b88

Any help would be much appreciated. Thank you!

Joon Park
  • 191
  • 4
  • 16

2 Answers2

1

I think the problem rely on your server.js

app.use(handleRender);

That line tells express server to handle all request using handleRender and handleRender is configured to always return html content so when your page send resource request to server, the server respond with html of your handleRender method. Try to set resource folder before this line

app.use(handleRender);

Something like app.use(express.static(<resource folder>)

kasongoyo
  • 1,748
  • 1
  • 14
  • 20
0

Try inverting the order of babel presets... in your package.json Instead of doing:

"presets": [
    "es2015",
    "react"
],

Try this:

"presets": [
   "react",
   "es2015"
],

UPDATE

First of all... When you use a example as a skeleton for your project...COPY EVERYTHING from that example and then you modify as you wish..

  1. server.js

These lines are important...

import webpack from 'webpack'
import webpackDevMiddleware from 'webpack-dev-middleware'
import webpackHotMiddleware from 'webpack-hot-middleware'

...

// Use this middleware to set up hot module reloading via webpack.
const compiler = webpack(webpackConfig)
app.use(webpackDevMiddleware(compiler, { noInfo: true, publicPath: webpackConfig.output.publicPath }))
app.use(webpackHotMiddleware(compiler))

That's not just hot reloading... Apparently they fix the problem with token <

But it not just that...

  1. webpack.config.js

You forget to add this loader:

{
   test: /\.js$/,
   exclude: /node_modules/,
   loader: 'babel-loader'
}

Because it is not just jsx you want to transpile ... js files still have ES6 declarations right?

  1. .babelrc

In the example, it uses .babelrc file, not a package.json configuration for babel... Don't know if those loaders read package.json... So I stick with the example.

{
 "presets": [
   "react",
   "es2015"
 ],
 "plugins": [
   "transform-object-rest-spread"
 ]
}

I think this is it... Hope it helps... If yes... Check this as your answer..to help reputation... =)

Lucas Katayama
  • 4,445
  • 27
  • 34