catamphetamine / universal-webpack

Isomorphic Webpack: both on client and server

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Node failing when it reaches scss imports in react components

AaronCCWong opened this issue · comments

Hi. I have attempted to set up universal webpack for my project but I cannot seem to get it running. I keep running into the following error:

SyntaxError: /Users/Aaron/Documents/Frontend/src_react/app/common/bootstrap-components/Container.scss: Unexpected token, expected ( (1:8)
> 1 | @import "sass/variables";
    |         ^
  2 | 
  3 | .container,
  4 | .container-fluid {
    at Parser.pp$5.raise (/Users/Aaron/Documents/Frontend/node_modules/babylon/lib/index.js:4215:13)
    at Parser.pp.unexpected (/Users/Aaron/Documents/Frontend/node_modules/babylon/lib/index.js:1599:8)
    at Parser.pp$3.parseExprAtom(/Users/Aaron/Documents/Frontend/node_modules/babylon/lib/index.js:3442:14)
    at Parser.parseExprAtom (/Users/Aaron/Documents/Frontend/node_modules/babylon/lib/index.js:6307:22)
    at Parser.pp$3.parseExprSubscripts (/Users/Aaron/Documents/Frontend/node_modules/babylon/lib/index.js:3305:19)
    at Parser.pp$3.parseMaybeUnary (/Users/Aaron/Documents/Frontend/node_modules/babylon/lib/index.js:3285:19)
    at Parser.pp$3.parseExprOps (/Users/Aaron/Documents/Frontend/node_modules/babylon/lib/index.js:3215:19)
    at Parser.pp$3.parseMaybeConditional (/Users/Aaron/Documents/Frontend/node_modules/babylon/lib/index.js:3192:19)
    at Parser.pp$3.parseMaybeAssign (/Users/Aaron/Documents/Frontend/node_modules/babylon/lib/index.js:3155:19)
    at Parser.parseMaybeAssign (/Users/Aaron/Documents/Frontend/node_modules/babylon/lib/index.js:5603:20)

I import scss files into my react components which is what causes this error. It seems that node gets confused when that happens. How can I fix this?

This is my webpack.config.client.js:

import { clientConfiguration } from 'universal-webpack'
import settings from './universal-webpack-settings'
import configuration from './webpack.config'

export default clientConfiguration(configuration, settings)

This is my webpack.config.server.js:

import { serverConfiguration } from 'universal-webpack'
import settings from './universal-webpack-settings'
import configuration from './webpack.config'

export default serverConfiguration(configuration, settings)

This is my start-server.js:

import { server } from 'universal-webpack'
import settings from './universal-webpack-settings'
import configuration from './webpack.config'

server(configuration, settings);

This is my server.js:

import React from 'react';
import path from 'path';
import http from 'http';
import express from 'express';
import httpProxy from 'http-proxy';
import { match, createRoutes, RouterContext } from 'react-router';
import { renderToString } from 'react-dom/server';
import { Provider } from 'react-redux';

import configureStore from 'app/configureStore';
import HomeRouter from 'app/home/components/HomeRouter';
import template from 'app/template';

export default function(parameters) {
  const app = new express()
  const server = new http.Server(app)
  const routes = createRoutes(HomeRouter());
  const preloadedState = {
    session: {
      currentUser: null
    }
  };
  const store = configureStore(preloadedState);
  app.use(express.static(path.join(__dirname, 'build/assets')))
  const backend_url = process.env.BACKEND_URL || "http://localhost:8000/";
  const proxy = httpProxy.createProxyServer({ target: backend_url, changeOrigin: true })
  app.use('/api', (req, res) => proxy.web(req, res))

  app.use((req, res) => {
    match(
      { routes, location: req.url },
      (error, redirectLocation, renderProps) => {
        if (error) {
            res.status(500)
            return res.send('Server error')
        }
        const page = renderToString(
          <Provider store={store}>
            <RouterContext {...renderProps} />
          </Provider>
        )
        res.status(200)
        res.send(template({
          body: page
        }))
      }
    )
  })

  server.listen()
}

and this is my webpack.config.js:

const path = require('path');
const webpack = require('webpack');
const autoprefixer = require('autoprefixer');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const context = path.resolve(__dirname);

var config = {
  entry: path.join(__dirname, './src_react/app/entry.jsx'),
  context: context,
  output: {
    path: path.join(__dirname, '/build'),
    filename: '[name]-[hash].js',
    publicPath: '/'
  },
  module: {
    loaders: [
      {
        test: /\.json$/,
        loader: 'json'
      },
      {
        test: [/\.jsx?$/, /\.js?$/],
        exclude: /(node_modules|bower_components)/,
        loader: 'babel'
      },
      {
        test: /\.png$/,
        loader: 'url-loader?limit=100000'
      },
      {
        test: /\.jpg$/,
        loader: 'file-loader'
      },
      {
        test: /\.gif$/,
        loaders: [
          'file?hash=sha512&digest=hex&name=[hash].[ext]',
          'image-webpack?bypassOnDebug&optimizationLevel=7&interlaced=false'
        ]
      },
      {
        test: /\.(woff|woff2)(\?v=\d+\.\d+\.\d+)?$/,
        loader: 'url?limit=10000&mimetype=application/font-woff'
      },
      {
        test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/,
        loader: 'url?limit=10000&mimetype=application/octet-stream'
      },
      {
        test: /\.eot(\?v=\d+\.\d+\.\d+)?$/,
        loader: 'file'
      },
      {
        test: /\.svg(\?v=\d+\.\d+\.\d+)?$/,
        loader: 'url?limit=10000&mimetype=image/svg+xml'
      },
      {
        test: /\.otf(\?v=\d+\.\d+\.\d+)?$/,
        loader: 'file-loader?name=fonts/[name].[ext]'
      },
      {
        test: /\.scss$/,
        loaders: ['style', 'css', 'resolve-url', 'sass?sourceMap']
      },
      {
        test: /\.css$/,
        loader: 'style-loader!css-loader'
      }
    ]
  },
  resolve: {
    root: [
      path.resolve('./src_react')
    ],
    extensions: ['', '.js', '.jsx', '.css', '.scss'],
    alias: {
      'ie': 'component-ie'
    }
  },
  sassLoader: {
    includePaths: [path.resolve(__dirname, "./src_react")]
  },
  postcss: [
    autoprefixer
  ],
  plugins: [
    new HtmlWebpackPlugin({
      template: path.join(__dirname, '/src_react/index.tmpl.html')
    }),
    new webpack.DefinePlugin({
      'process.env': { NODE_ENV: JSON.stringify(process.env.NODE_ENV || 'development') }
    }),
    new webpack.ProvidePlugin({
      $: "jquery",
      jQuery: "jquery"
    })
  ]
};

module.exports = config;

Your errors are Parser.pp which means they are babel's, and babel means javascript, which means this file is being compiled by javascript: either you're running it on Node.js without prior compilation with Webpack, or in Webpack you've set up babel-loader to handle this file (which is unlikely).

I just spend a long time trying to figure out a solution to the same kind of stack trace. In my case it was that my app code was not getting included in the webpack bundle. It was as if webpack thought that my app was in node_modules so it did not bundle it.

Following the example above, I think the same thing is happening. Take this import as an example:

import HomeRouter from 'app/home/components/HomeRouter';

The resolve.root configuration value of path.resolve('./src_react') is probably what makes this work. This is a typical pattern because relative imports start to get really hairy when you grow a big app.

For my case I had to mark my internal apps as "not external" which doesn't make a lot of semantic sense but this is a documented gotcha. I did read this gotcha before running into this error and I didn't think it would apply to me. Thus, it would be helpful to document a separate gotcha about doing imports in this manner.

This was my fix:

import { server } from 'universal-webpack/config';
import settings from './universal-webpack-settings';
import configuration from './webpack.dev.config.babel';

// This is just a hack to illustrate the fix:
settings.exclude_from_externals = [
  // These are all of my apps in ./src. Example: ./src/admin, ./src/amo ...
  /^(admin|amo|core|disco|locale|ui)/,
];
export default server(configuration, settings);

@kumar303 This issue is not about resolving at all, it's about missing CSS loaders configuration.
Still I did add some notes on resolve.root in the README.

When import ... 'app/home/components/HomeRouter' gets resolved (in the above example), it was treated as an external dep. Within HomeRouter there is probably some code that does import file from 'app/common/bootstrap-components/Container.scss' and that was triggering the stack trace above.

Note that resolve.root is an older webpack 1 thing. In webpack 2 one is encouraged to put custom module paths in resolve.modules. It has the same effect either way.

It's not always feasible to use resolve.alias to declare all custom modules within your local src directory. What happens when you add a new custom module? You have to remember to add a resolve.alias entry for it.

@kumar303 Oh, I see now, yes, you were right, that's the cause.

You have to remember to add a resolve.alias entry for it.

Yes, and there's no other way around it as far as it can be seen – only the require()d module is passed to configuration.externals and it has no configuration.postExternals hook or something like that.
I'm not an expert in Webpack source code though and if you happen to find a solution you can post it here.