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.

duncanhall
  • 11,035
  • 5
  • 54
  • 86
  • 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. – Viacheslav May 20 '15 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. – duncanhall May 20 '15 at 15:35
  • Might be interesting to `console.log()` your `webpackConfig` in the gruntfile – Michelle Tilley May 20 '15 at 15:35

10 Answers10

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.

jsalonen
  • 29,593
  • 15
  • 91
  • 109
Viacheslav
  • 3,876
  • 2
  • 21
  • 28
  • 1
    A million times yes! Thank you. – duncanhall May 20 '15 at 16:29
  • 1
    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 – Muers Aug 05 '15 at 05:45
  • 1
    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 – Jax Cavalera Jan 22 '16 at 07:26
  • https://stackoverflow.com/questions/60473782/css-and-images-files-not-coming-after-transpiling-package-with-babel. Please answer this too or any insight on this query is appreciated. – Prakhar Mittal Mar 02 '20 at 06:29
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 {
vhs
  • 9,316
  • 3
  • 66
  • 70
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.

koorchik
  • 1,646
  • 2
  • 13
  • 10
  • 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"]} ] ] }) – Viacheslav Feb 14 '18 at 11:06
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

catamphetamine
  • 4,489
  • 30
  • 25
  • Surprised this answer doesn't have more upvotes, seems like webpack-isomorphic-tools is the ideal solution – Isaac Mar 29 '16 at 18:21
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.

Raul Matei
  • 21
  • 1
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.

Anders Ekdahl
  • 22,685
  • 4
  • 70
  • 59
  • 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 `require`and then we see the error, using the `require` in the first place would be expected to produce the same error. – duncanhall May 20 '15 at 11:23
  • 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. – Anders Ekdahl May 20 '15 at 11:25
  • 1
    babel-loader does not resolve requires, webpack does. So it's fine to `import` a css file, provided webpack uses a css loader. – gpbl May 20 '15 at 11:27
  • 1
    @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. – Anders Ekdahl May 20 '15 at 11:29
  • 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. – duncanhall May 20 '15 at 11:35
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" }
     ]
  }
gpbl
  • 4,721
  • 5
  • 31
  • 45
  • 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. – duncanhall May 20 '15 at 11:24
  • 1
    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. – gpbl May 20 '15 at 11:35
  • 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. – duncanhall May 20 '15 at 11:43
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.

duncanhall
  • 11,035
  • 5
  • 54
  • 86
  • 5
    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. – Isaac Mar 29 '16 at 18:13
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!

Chen-Tai
  • 3,435
  • 3
  • 21
  • 34
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

Community
  • 1
  • 1
  • 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. – duncanhall Nov 14 '19 at 15:55