在同构的React组件中导入CSS文件

42

我有一个用ES6编写组件,通过Babel和Webpack进行转义的React应用程序。

在某些地方,我想在具体组件中包含特定的CSS文件,就像react webpack cookbook中建议的那样。

然而,如果在任何组件文件中我需要静态的CSS资产,例如:

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

那么编译将会失败,并显示以下错误:

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)

看起来如果我尝试在组件文件中引入CSS文件,那么Babel加载器将会将其解释为另一个源,并试图将CSS转换为Javascript。

这是预期的吗?有没有办法实现这一点-允许已转换的文件明确引用不需要转换的静态资源?

我已经为.js/jsx和CSS资产指定了加载器,如下所示:

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

查看完整的webpack配置文件

以下是详细信息:

webpack.common.js - 我使用的基础webpack配置,以便在开发和生产之间共享属性。

Gruntfile.js - 用于开发的Gruntfile。正如您所看到的,它需要上面的webpack配置,并添加了一些开发属性。这可能会导致问题吗?

Html.jsx - 我的HTML jsx组件尝试导入/要求CSS。这是一个同构应用程序(使用Fluxbile),因此需要将实际的HTML作为呈现的组件。在应用程序的任何部分中使用此文件中看到的要求语句会导致上述错误。

似乎与grunt有关。如果我只使用webpack --config webpack.common.js编译,则不会出现错误。

短回答:它是一个节点运行时错误。在等同应用程序中尝试在服务器上加载CSS不是一个好主意。

2
你的配置没问题。我尝试在一个包含 CSS 的应用程序上运行它,它可以正常工作。检查其他东西 - 也许你使用了完全不同的 webpack 配置文件 :) 或者是包有问题。发布更多信息 - 你的 package.json,如何运行 webpack 等等。也许我们可以找出问题所在。 - Viacheslav
谢谢,我在上面提供了更多的信息。问题似乎是由grunt的某个地方引起的,因为直接通过webpack编译是没有问题的。 - duncanhall
在gruntfile中,将你的webpackConfig输出到控制台可能会很有趣。 - Michelle Tilley
10个回答

67

您不能在服务器端渲染的组件中引用CSS。解决办法之一是在引用CSS之前检查是否为浏览器。

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

为了使其可行,您应该在服务器上将 process.env.BROWSER 设置为 false(或删除它)。 server.js

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

并将其设置为 true 以供浏览器使用。您可以在配置文件中使用 webpack 的 DefinePlugin 进行操作 - webpack.config.js

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

你可以在gpbl的Isomorphic500应用程序中看到这个实现过程。


1
一百万次的肯定!谢谢。 - duncanhall
1
我认为我遇到了同样的问题,但是这样用条件逻辑来包装.css导入看起来真的很丑陋。还有其他解决方案吗?@snegostup - Muers
1
我相信有一种方法可以在服务器端导入CSS等文件,但需要使用Webpack将服务器端代码与客户端bundle.js使用的不同配置捆绑在一起。我不喜欢使用装饰器的选项,也不喜欢仅能在客户端要求非JS模块的想法,因为这种情况下最终用户禁用了JavaScript。我目前正在研究Webpack服务器端的具体细节。 - Jax Cavalera
https://dev59.com/pbnoa4cB1Zd3GeqPSo2B。请回答此问题或提供任何有关此查询的见解。 - Prakhar Mittal

10

如果您正在使用ES6构建同构应用程序,并希望在服务器端渲染时包含CSS(这很重要,因为基本样式可以在第一个HTTP响应中发送到客户端),请查看React Starter Kit中使用的ES7装饰器@withStyles

这个小东西有助于确保用户在页面首次呈现时看到您的带有样式的内容。这里有一个我正在使用这个技术构建的同构应用示例,只需在代码库中搜索@withStyles即可查看它的使用方法。它像这样:

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

@withStyles(styles)
class ScheduleList extends Component {

7
我们的同构应用程序也遇到了类似的问题(以及许多其他问题,您可以在此处找到详细信息)。至于CSS导入问题,起初我们使用process.env.BROWSER。后来我们切换到babel-plugin-transform-require-ignore,它与babel6完美配合。
你只需要在.babelrc文件中添加以下内容:
"env": {
   "node": {
     "plugins": [
       [
         "babel-plugin-transform-require-ignore", { "extensions": [".less", ".css"] }
       ]
     ]
   }
}

之后使用BABEL_ENV='node'运行您的应用程序。如下所示:

BABEL_ENV='node' node app.js.

这是一个生产配置的示例,点击这里查看。

你可以在服务器加载的 'babel-register' 选项中启用它 (以避免干扰环境变量): require("babel-register")({ plugins: [ [ "transform-require-ignore", {"extensions": [".css"]} ] ] }) - Viacheslav

6

很惊讶这个答案没有更多的赞,看起来webpack-isomorphic-tools是理想的解决方案。 - Isaac

2
我曾经使用过这个babel插件,链接在此,成功地解决了与less、svg和图片相关的类似问题。但它也适用于任何非js资源。
该插件将所有资源导入重写为变量,只要您在服务器上运行编译后的代码并为客户端构建了一个webpack捆绑包,就应该没问题。
唯一的缺点是它只适用于命名导入,所以你需要:
import styles from './styles.css';

为了让它工作,需要进行以下操作。

1
你的Webpack配置文件可能存在错误,因为你正在对所有文件使用,而不仅仅是.js文件。你需要为.css文件使用css loader。
但是,除了JavaScript模块之外,您不应该使用import来加载任何其他模块,因为一旦浏览器实现了导入,您只能导入JavaScript文件。在需要Webpack特定功能的情况下,请改用require
Webpack使用require,而Babel允许您使用ES6的import,它们大多数情况下执行相同的操作(并且Babel将导入转换为require语句),但它们不能互换使用。Webpack的require函数不仅可以指定模块名称,还可以指定加载器,这是ES6 import无法做到的。因此,如果要加载CSS文件,则应使用require而不是import

原因是Babel只是ES6的转译器,而ES6不允许您导入CSS文件。因此,Babel也不会允许您这样做。


谢谢 - 不幸的是,情况似乎并非如此。用 require 语句替换 import 语句会产生完全相同的错误。这大多是我所期望的 - 如果 Babel 将 import 转译为 require,然后我们看到错误,那么在第一次使用 require 的情况下产生相同的错误是可以预料的。 - duncanhall
你可能在Webpack配置中犯了一个错误,即对所有文件使用babel-loader,而它只应该用于.js文件。 - Anders Ekdahl
1
babel-loader 不会解析 requires,而 webpack 会。因此,只要 webpack 使用 css loader,就可以安全地导入 css 文件。 - gpbl
1
@gpbl非常正确,是我的错误。我会更新帖子,但是事实仍然是importrequire不可互换,因为一旦支持真正的ES6导入,您就不能使用它们了。 - Anders Ekdahl
谢谢,请查看我编辑的原始帖子,展示我的加载器。我有两种文件类型的加载器,但仍然在使用require/import时看到错误。 - duncanhall

1

确保在您的webpack配置中使用了加载器:

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

谢谢,问题不在于缺少加载器,我已经安装好了。问题在于Babel看到import/require语句并尝试将CSS作为可转译的源资产导入。 - duncanhall
1
没有一些代码/配置很难说,但看起来webpack正在将.css文件传递给babel-loader:这不应该发生。如果确实发生了,我认为您的配置有问题。 - gpbl
谢谢,我已经在原帖中添加了完整配置文件的链接。如果您能够发现其中的任何问题,我们将不胜感激。 - duncanhall

0
我终于意识到这个错误不是在编译阶段产生的,而是在运行时产生的。因为这是一个同构应用程序,组件及其任何依赖项将首先在服务器上解析(即在node中)。正是这个原因导致了错误。
感谢所有的建议,如果/当我找出如何在同构应用程序中拥有每个组件样式表,我会发布更多信息。

5
最终你选择了什么解决方案——装饰器、条件逻辑还是其他方法?我认为条件逻辑似乎有些笨拙,而装饰器则有些不恰当。我觉得可能存在一种通过配置 Babel 来处理此问题的解决方案。 - Isaac

0

当我想进行服务器端渲染时,我也遇到了同样的问题。

所以我编写了一个 postcss 插件,postcss-hash-classname

你不需要直接引入 css。

你需要引入你的 css classname js 文件。

因为你所需的都是 js 文件,所以可以像往常一样进行服务器端渲染。

此外,这个插件还使用了你的 classname 和文件路径生成唯一的哈希来解决 css 作用域问题。

你可以试一试!


0

用这个解决了...

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

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

在 .babelrc 中引入插件

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

导入 CSS……像这样随时随地放置

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

除了 CSS 文件之外,还可以查看以下内容... ........................................................。 https://www.npmjs.com/package/babel-plugin-transform-assets


这并没有解决我最初描述的问题,而且据我所知可能会让情况变得更糟。你不应该将CSS注入到服务器端运行时。 - duncanhall

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