使用webpack的style-loader时出现“window is not defined”错误

17

构建一个服务器端React应用程序,使用Webpack时遇到了Style-Loader的问题。

我正在使用版本"^0.23.1",在运行打包和构建脚本时,Style-Loader出现了问题。

问题是window is not defined

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

有人遇到过这个问题吗?在查看了Stack和style-loader的Github问题后,我没有找到任何解决方案。

这是我的webpack文件:

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",
    }),
  ],
}

如果您需要查看其他内容,我可以发布它。


2
你需要使用一个专为SSR设计的加载器。https://github.com/kriasoft/isomorphic-style-loader是一个选择,但自上次提交以来已经有一段时间了。还有https://github.com/noderaider/universal-style-loader,https://github.com/creeperyang/iso-morphic-style-loader。祝好运! - Christian Scott
我也遇到了这个问题,并且正在按照这篇指南进行操作,对于帖子的作者来说似乎运行良好:https://medium.com/@binyamin/creating-a-node-express-webpack-app-with-dev-and-prod-builds-a4962ce51334 - Matt
5
好的,我已经理解了。如@ChristianScott所说,style-loader并不适用于服务器端渲染。如果您不打算使用SSR,请注意配置文件中的target: node设置。将其更改为target: web以针对客户端捆绑而非服务器端。这将防止style-loader将其代码放入您使用Node.js运行的服务器端文件中。另外,在我的情况下,即使我将web设为目标,我也有entry: { server: './src/index.js' },其中server属性应改为main。当我更改它时,一切正常。 - Matt
跟进@MegaMatt,赞! - Christian Scott
3
最后补充一点,我发表先前评论后了解到entry对象内的属性名不必是特定值。servermain是我在跟随教程时使用的字符串,但可以是任何您想要的东西。只需注意,您选择的值可以通过使用特殊的[name]语法在配置文件的其他地方引用。同样,在我的情况下,我已经有另一个webpack配置文件正在使用名为server的条目,因此使用第二个会导致某种冲突。因此,将其更改为main(或除server之外的任何其他名称)就可以解决问题。 - Matt
4个回答

13

style-loader试图将样式注入到网站的head (window / document)中,但在渲染/执行时服务器上将不存在该元素。

你需要从你的服务器配置中删除此加载器,并用其他内容替换它(例如,根据你的webpack版本选择ExtractTextPlugin或者MiniCSSExtractplugin)。


5
我认为你的问题在于,当在node服务器上运行js代码时,没有window对象。这也很合理,因为你的服务器没有窗口来渲染你的代码。你可以使用global对象来进行全局引用,参见这篇相关文章:Does node.js have equivalent to window object in browser

2
是的,你说得对。问题在于 OP 的配置通过 target 属性提供的 node 值针对服务器端打包。style-loader 加载器不适用于服务器端打包,如果在此处包含它,由于 style-loader 引用了 window,所以打包将仅包含为 客户端 打包而设计的引用。正如你所说,window 在服务器上不存在,只存在于客户端代码中。因此有两个选项:(1) 将 target 更改为 web(而不是 node),或者 (2) 删除 style-loader 并使用专为服务器端渲染设计的加载器。 - Matt

4
如果我理解正确,您正在尝试使用style-loader来捆绑服务器端代码。如果是这种情况,请尝试改为以下操作而非之前的操作:
    loader: ['style-loader', 'css-loader', 'sass-loader']

试试这个:

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

样式加载程序不应在服务器端代码上使用。因此,我们提供了一种空加载程序来代替css加载程序并删除样式加载程序。我想这应该可以解决问题。


1
这会抛出一个错误 模块未找到:无法解析 'css-loader/locals' - Rich
你应该只在服务器的配置中使用它,将客户端的保留为loader: ['style-loader', 'css-loader', 'sass-loader']。 - Spider
2
这在最近的版本中已经变成了 { loader: 'css-loader', options: { onlyLocals: true} } 或者 exportOnlyLocals(参见 https://webpack.js.org/loaders/css-loader/#exportonlylocals)。然而,在我的情况下,导出的 js 文件链接了源文件中的 scss 样式表,这对我来说似乎很奇怪,但可能是预期的输出? - Ambroise Rabier
1
@AmbroiseRabier 哇,那可是五个小时的研究啊,谢谢你!!! - mslugx
1
这也是我的问题。在我的情况下,我将其切换为等距样式加载器,但后来意识到在我的情况下根本不需要加载器,因此能够完全删除它并继续前进。 - Jason Tarr

0

我遇到了一个问题,需要从组件库中获取一些主题和样式,该组件库使用了webpack和style-loader。
我的项目是纯脚本,并且应该生成一些文件,因此没有浏览器。由于style-loader(以及其他一些库)尝试将样式注入标签中,所以根本无法编译。
最终,我模拟了window和document,以便导入的项目可以编译。

请注意,这在我的情况下有效,因为我只需要组件库中的一小部分,如果您在更复杂的项目中使用它,可能会出现一些奇怪的错误。但它可能会帮助某人解决类似的问题

在实际导入之前运行此操作
由于实际导入会导致问题,因此您需要在导入之前进行修改。

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;
};

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接