17

Building a server side react app and while using Webpack I am having issues with Style-Loader.

I am using version "^0.23.1" and when running a script to bundle and build there is an issue from Style-Loader.

The issue is window is not defined

webpack:///./node_modules/style-loader/lib/addStyles.js?:23
    return window && document && document.all && !window.atob;

Has anyone run into this issue? After looking through Stack and the Github issues for style-loader I am not finding any solution.

Here is my webpack file:

const path = require('path');
const webpack = require('webpack');

module.exports = {
  // webpack to use node
  target: 'node',
  entry: './src/index.js',
  output: {
    filename: 'client-build.js',
    path: path.resolve(__dirname, 'build/public'),
    publicPath: '/build/public'
  },
  module: {
    rules: [
      {
        test: /\.js$|\.jsx$/,
        loader: 'babel-loader',
        exclude: '/node_modules/',
        options: {
          presets: [
            '@babel/preset-react'
          ]
        }
      },
      {
        test: /\.(s*)css$/,
        loader: ['style-loader', 'css-loader', 'sass-loader']
      },
      {
        test: /\.jpeg$|\.gif$|\.png$|\.svg$|\.woff$|\.ttf$|\.wav$|\.mp3$|\.jpg$|\.pdf$/,
        loader: 'file-loader',
        query: {
          name: 'assets/img/[name].[ext]'
        },
      },
    ]
  },
  plugins: [
    new webpack.ProvidePlugin({
      "React": "react",
    }),
  ],
}

If there is anything else you need to see I can post it.

Matt
  • 23,363
  • 39
  • 111
  • 152
T. Evans
  • 959
  • 3
  • 14
  • 27
  • 2
    You need to use a loader that's designed for SSR. https://github.com/kriasoft/isomorphic-style-loader is an option, but it's been quite a while since the last commit. There's also https://github.com/noderaider/universal-style-loader, https://github.com/creeperyang/iso-morphic-style-loader. Good luck! – Christian Scott Dec 27 '18 at 23:25
  • I'm also having this issue, and was following this guide where it appears to be working fine for the author of the post: https://medium.com/@binyamin/creating-a-node-express-webpack-app-with-dev-and-prod-builds-a4962ce51334 – Matt Mar 06 '19 at 13:26
  • 5
    Ok, I figured it out. As @ChristianScott said, `style-loader` is not intended for server-side rendering. @T. Evans, if SSR was not your intention, note the `target: node` setting in your configuration file. Change this to `target: web` to target client-side bundling, not server-side. That will prevent `style-loader` from putting its code into your server-side files that you run with Node.js. Also, in my case, even though I had `web` as my target, I also had `entry: { server: './src/index.js' }`, where the `server` prop should have been `main` instead. When I changed that, everything worked. – Matt Mar 06 '19 at 14:01
  • Kudos for following up @MegaMatt! – Christian Scott Mar 07 '19 at 02:15
  • 3
    One final note. I learned after posting my previous comment that the property name inside the `entry` object doesn't have to be a specific value. `server` and `main` were strings used for a walkthrough I was following, but it can be anything you want. Just note that the value you choose can be referenced elsewhere in your config file by using the special `[name]` syntax. Again, in my case, I had another webpack config file already using an entry called `server`, so having a second one was causing a conflict of some sort. Hence, changing it to `main` (or anything other than `server`) fixed me. – Matt Mar 07 '19 at 14:55

4 Answers4

12

style-loader tries to inject styles into the head of the website (window / document), which will be non-existent on your server on render / execution.

You need to remove this loader from your server-config and replace it with something else (e.g. ExtractTextPlugin or MiniCSSExtractplugin, depending on your webpack version)

Jonathan
  • 3,614
  • 3
  • 21
  • 30
5

I think your problem is, that there is no window object when running js code on a node server. Which also makes sense, as your server has no window where your code is rendered. You can use the global object for global references instead, see this related post here: Does node.js have equivalent to window object in browser

Jan-Luca Klees
  • 516
  • 4
  • 8
  • 2
    Yes, you're correct. The problem is that OP's config is targeting server-side bundling via the `node` value provided for the `target` property. The `style-loader` loader is not designed for server-side bundling, and if it's included here, the bundle will end up with references only designed for *client-side* bundling, due to `style-loader` making references to `window`. And as you said, `window` isn't a thing on the server, only in client code. So 2 options: (1) Change `target` to `web` (instead of `node`), or (2) remove the style-loader and use a loader designed for server-side rendering. – Matt Mar 14 '19 at 00:15
4

If I got it correctly I think you are trying to use style-loader for bundling server side code.If it is the case try doing this instead of doing this:

    loader: ['style-loader', 'css-loader', 'sass-loader']

Try this:

    loader: ['css-loader/locals', 'sass-loader']

Style loader is not supposed to be used on the server side code. So we provide kind of a null-loader instead of css-loader and remove style loader. This should do the trick I guess.

Spider
  • 140
  • 6
  • 1
    This throws an error `Module not found: Can't resolve 'css-loader/locals'` – Rich Sep 15 '20 at 05:53
  • You should only use it in the config for server keep the one for client as loader: ['style-loader', 'css-loader', 'sass-loader'] – Spider Sep 16 '20 at 06:10
  • 2
    This has become `{ loader: 'css-loader', options: { onlyLocals: true} }` or `exportOnlyLocals` (See https://webpack.js.org/loaders/css-loader/#exportonlylocals) in recent versions. However, in my case, the exported js file, link the scss stylesheet that is in the sources, that seem strange to me, but might be the expected output? – Ambroise Rabier Oct 15 '20 at 12:57
  • 1
    @AmbroiseRabier OMG, that was 5 hours of researching, THANK YOU!!! – mslugx Oct 16 '20 at 06:59
  • 1
    This was my issue too. In my case I switched it to an isomophic-style-loader but then realised I did not need the loader at all in my case so was able to remove it completely and move forward. – Jason Tarr Oct 28 '20 at 17:42
0

I had a this problem where I needed some themes and styles from a component-library which in turn used webpack and style-loader.
My project was pure script and is supposed to generate some files and therefore had no browser. It would not compile at all since style-loader(and some other libs) tried to inject styles in the tag.
I ended up mocking window and document so that the imported project could compile.

NOTE that this worked in my case where I only needed a minor part of my component-library, if you use this in a more complicated project there will probably be some weird bugs. But it might help someone figure out a similar problem

Run this before you do the actual import
Since it is the actual import that causes the problem you need to do the hack before importing.

import * as Hack from './hack/StyleLoaderHack';
Hack.runHack();
...
import {X} from 'your library'

StyleLoaderHack.js

class HackStyle {
    position;

    constructor() {
        this.position = [];
    }
}

class HackElement {
    className;
    childNodes;
    style;

    constructor(tag) {
        this.className = tag;
        this.attributes = [];
        this.childNodes = [];
        this.style = new HackStyle();
    }

    appendChild = (child) => {
        let append;
        if (!(child instanceof HackElement)) {
            append = new HackElement(child);
        } else {
            append = child;
        }
        this.childNodes.push(append);
        return append;
    };

    insertBefore = (newChild, refChild) => {
        let insert;
        if (!(newChild instanceof HackElement)) {
            insert = new HackElement(newChild);
        } else {
            insert = child;
        }
        this.childNodes.push(insert);
    };

    setAttribute = (qualifiedName, value) => {
        // sketchy but works
        this.attributes.push(qualifiedName);
        this.attributes.push(value);
    };
}

class HackDocument {
    head;

    constructor() {
        this.head = new HackElement("head");
    }

    createElement = (tagName) => {
        const element = new HackElement(tagName);
        return element;
    };

    querySelector = (target) => {
        const node = new HackElement(target);
        return node;
    };

    querySelectorAll = (target) => {
        if (target === "[data-emotion-css]") {
            return [];
        }
        const node = new HackElement(target);
        return [node];
    };

    createTextNode = (data) => {
        return new HackElement(data);
    };
}


/**
 * Adds some function to global which is needed to load style-loader, emotion, create-emotion and react-table-hoc-fixed-columns.
 */
export const runHack = () => {
    global.window = {};

    const hackDocument = new HackDocument();
    global.document = hackDocument;
};
Joakim Palmkvist
  • 540
  • 2
  • 11