42

I have a React application with Components written in ES6 - transpiled via Babel and Webpack.

In some places I would like to include specific CSS files with specific Components, as suggested in react webpack cookbook

However, if in any Component file I require a static CSS asset, eg:

import '../assets/css/style.css';

Then the compilation fails with an error:

SyntaxError: <PROJECT>/assets/css/style.css: Unexpected character '#' (3:0)
    at Parser.pp.raise (<PROJECT>\node_modules\babel-core\lib\acorn\src\location.js:73:13)
    at Parser.pp.getTokenFromCode (<PROJECT>\node_modules\babel-core\lib\acorn\src\tokenize.js:423:8)
    at Parser.pp.readToken (<PROJECT>\node_modules\babel-core\lib\acorn\src\tokenize.js:106:15)
    at Parser.<anonymous> (<PROJECT>\node_modules\babel-core\node_modules\acorn-jsx\inject.js:650:22)
    at Parser.readToken (<PROJECT>\node_modules\babel-core\lib\acorn\plugins\flow.js:694:22)
    at Parser.pp.nextToken (<PROJECT>\node_modules\babel-core\lib\acorn\src\tokenize.js:98:71)
    at Object.parse (<PROJECT>\node_modules\babel-core\lib\acorn\src\index.js:105:5)
    at exports.default (<PROJECT>\node_modules\babel-core\lib\babel\helpers\parse.js:47:19)
    at File.parse (<PROJECT>\node_modules\babel-core\lib\babel\transformation\file\index.js:529:46)
    at File.addCode (<PROJECT>\node_modules\babel-core\lib\babel\transformation\file\index.js:611:24)

It seems that if I try and require a CSS file in a Component file, then the Babel loader will interpret that as another source and try to transpile the CSS into Javascript.

Is this expected? Is there a way to achieve this - allowing transpiled files to explicitly reference static assets that are not to be transpiled?

I have specified loaders for both .js/jsx and CSS assets as follows:

  module: {
    loaders: [
      { test: /\.css$/, loader: "style-loader!css-loader" },
      { test: /\.(js|jsx)$/, exclude: /node_modules/, loader: 'babel'}
    ]
  }

View the full webpack config file

FULL DETAILS BELOW:

webpack.common.js - A base webpack config I use, so I can share properties between dev and production.

Gruntfile.js - Gruntfile used for development. As you can see it requires the webpack config above and adds some development properties to it. Could this be causing the problem?

Html.jsx - My HTML jsx component that tries to import/require the CSS. This is an isomorphic app (using Fluxbile), hence needing to have the actual HTML as a rendered component. Using the require statement seen in this file, in any part of my application, gives the error described.

It seems to be something to do with grunt. If I just compile with webpack --config webpack.common.js then I get no errors.

Short answer: It's a node runtime error. Trying to load CSS on the server in isomorphic apps is not a good idea.

3
  • 2
    Your config is ok. I tried to run it on an app with css inclusion and it worked. Check other stuff - perhaps you run webpack with entirely different config file :) or it's something wrong with the packages. Post more info - your package.json, how you run webpack, etc. Perhaps we'll figure out. Commented May 20, 2015 at 15:04
  • Thanks, I've provided more info above. The issue seems to be caused somewhere in grunt, as compiling directly via webpack is fine. Commented May 20, 2015 at 15:35
  • Might be interesting to console.log() your webpackConfig in the gruntfile Commented May 20, 2015 at 15:35

10 Answers 10

67

You can't require css in the component that you are rendering on the server. One way to deal with it is to check if it's a browser before requiring css.

if (process.env.BROWSER) {
  require("./style.css");
}

In order to make it possible you should set process.env.BROWSER to false (or delete it) on the server server.js

delete process.env.BROWSER;
...
// other server stuff

and set it to true for the browser. You do it with webpack's DefinePlugin in the config - webpack.config.js

plugins: [
    ...
    new webpack.DefinePlugin({
        "process.env": {
            BROWSER: JSON.stringify(true)
        }
    })
]

You can see this in action in gpbl's Isomorphic500 app.

Sign up to request clarification or add additional context in comments.

4 Comments

A million times yes! Thank you.
I think I'm having the same issue, but it seems really ugly to have to wrap .css imports with conditional logic like this. Is there another solution? @snegostup
I believe there is a way to import css etc server-side but requires bundling your server-side code with Webpack using a separate configuration to the one used for your client-side bundle.js I don't like the option of using Decorators and I also don't like the idea of only being able to require non JS modules client-side for situations where the end user has Javascript disabled. I'm currently researching specifics on Webpack Server-Side
stackoverflow.com/questions/60473782/…. Please answer this too or any insight on this query is appreciated.
10

If you're building an isomorphic app with ES6 and want to include CSS when rendering on the server (important so basic styles can be sent down to the client in the first HTTP response) check out the @withStyles ES7 decorator used in React Starter Kit.

This little beauty helps ensure users see your content with styles when the page is first rendered. Here's an example isomorphic app I'm building leveraging this technique. Just search the codebase for @withStyles to see how it's used. It goes a little something like this:

import React, { Component, PropTypes } from 'react';
import styles from './ScheduleList.css';
import withStyles from '../../decorators/withStyles';

@withStyles(styles)
class ScheduleList extends Component {

Comments

7

We had a similar problem with our isomorphic app (and a lot of other problems, you can find details here). As for the problem with CSS import, at first, we were using process.env.BROWSER. Later we've switched to babel-plugin-transform-require-ignore. It works perfectly with babel6.

All you need is to have the following section in your .babelrc

"env": {
   "node": {
     "plugins": [
       [
         "babel-plugin-transform-require-ignore", { "extensions": [".less", ".css"] }
       ]
     ]
   }
}

After that run your app with BABEL_ENV='node'. Like that:

BABEL_ENV='node' node app.js.

Here is an example of how a production config can look like.

1 Comment

or you can enable it in 'babel-register's options that you load for server (to not mess with env. variable): require("babel-register")({ plugins: [ [ "transform-require-ignore", {"extensions": [".css"]} ] ] })
6

You can also try this https://github.com/halt-hammerzeit/webpack-isomorphic-tools

or this https://github.com/halt-hammerzeit/webpack-react-redux-isomorphic-render-example

1 Comment

Surprised this answer doesn't have more upvotes, seems like webpack-isomorphic-tools is the ideal solution
2

I used this babel plugin with success to solve a similar issue with less, svg and images. But it should work with any non js assets.

It rewrites all assets imports into variables, so as long as you run the compiled code just on the server and have a bundle built with webpack for the client, it should be fine.

The only drawback is that it onlyworks with named imports, so you'll have to:

import styles from './styles.css';

in order to make it work.

Comments

1

Make sure you are using the loaders in your webpack config:

  module: {
     loaders: [
        { test: /\.jsx$/, exclude: /node_modules/, loader: "babel" },
        { test: /\.css$/, loader: "style!css" }
     ]
  }

3 Comments

Thanks, the problem is not with a missing laoder, I have these in place. The problem is that Babel is seeing the import / require statement and trying to import the CSS as a 'transpilable' source asset.
Without some code/config is hard to say, but it seems that webpack is passing the .css file to the babel-loader: it should not. If it does, I believe there's something wrong in your config.
Thanks, I've added a link to the full config file in the original post. If you're able to see any problems in that it's appreciated.
1

You probably have an error in your Webpack config where you're using the babel-loader for all files, and not just .js files. You want to use a css loader for .css files.

But you shouldn't use import for loading any other module than Javascript modules, because once imports are implemented in browsers, you will only be able to import Javascript files. Use require instead in cases where you need Webpack specific functionality.

ORIGINAL POST

Webpack uses require and Babel lets you use import from ES6 which mostly do the same thing (and Babel transpiles the import to a require statement), but they are not interchangable. Webpacks require function lets you specify more than just a module name, it lets you specify loaders as well, which you cannot do with ES6 imports. So if you want to load a CSS file, you should use require instead of import.

The reason is that Babel is just a transpiler for what's coming in ES6, and ES6 will not allow you to import CSS files. So Babel won't allow you to do that either.

5 Comments

Thanks - unfortunately this does not seem to be the case. Replacing the import statement with a require statement gives exactly the same error. This is mostly what I would expect - if Babel transpiles an import to a requireand then we see the error, using the require in the first place would be expected to produce the same error.
You probably have an error in your Webpack configuration where you use the babel-loader for all files, when it should only be used for .js files.
babel-loader does not resolve requires, webpack does. So it's fine to import a css file, provided webpack uses a css loader.
@gpbl Very true, my mistake. I'll update the post, but it's still true that import and require aren't interchangeable since you don't be able to use real ES6 imports once they're supported.
Thanks, please see edit to original post showing my loaders. I have loaders for both file types and am still seeing the error for both require/import.
0

I've finally realised that this error is not originating at the compile stage, but rather at runtime. Because this is an ismorphic app, the components and any dependencies they have will first be parsed on the server (ie, in node). It is this that is causing the error.

Thanks for all the suggestions, I will post more if/when I figure out how to have per-component stylesheets in an isomorphic application.

1 Comment

What solution did you settle on in the end - the decorator, conditional logic or something else? Imo the conditional logic seems a bit hacky and the decorator just seems a little off.. I feel like there could be a solution out there that just handles this issue through configuring babel.
0

I also met the same problem when I want to do the server-side render.

So I write a postcss plugin, postcss-hash-classname.

You don't require css directly.

You require your css classname js file.

Because all you require is js file, you can do the server-side render as usual.

Besides, this plugin also use your classname and file path to generate unique hash to solve css scope problem.

You can try it!

Comments

0

Solved With This...

https://github.com/michalkvasnicak/babel-plugin-css-modules-transform

$ npm install --save-dev babel-plugin-css-modules-transform

Include plugin in .babelrc

{
    "plugins": ["css-modules-transform"]
}

And Import Css..... Like This Anywhere You Want

const styles = require('./test.css');
OR
import css from './styles.css'

Also See This... Apart From Css Files........................................................ . https://www.npmjs.com/package/babel-plugin-transform-assets

1 Comment

This does not solve the issue I originally described, and from what I can tell would potentially make it worse. You should not be injecting CSS into server-side run times.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.