用Webpack 5在React和TypeScript中使用CSS

184 阅读4分钟

上一篇文章中,我们创建了一个使用TypeScript和ESLint与Webpack的React应用。在这篇文章中,我们将扩展这个Webpack配置,使该应用可以使用CSS。

在应用程序中引用图片

让我们首先尝试在我们的App 组件中引用一个CSS类:

const App = () => <h1 className="app-heading">My React and TypeScript App!</h1>;

我们将创建一个名为app.css 的CSS文件来保存这个CSS类定义:

.app-heading {
  color: red;
}

让我们在终端运行npm start ,以开发模式启动我们的应用程序。

当然,这样做是不行的--App 组件如何知道app-heading 的定义在哪里?我们可以使用如下的导入语句来做到这一点:

import "./app.css"

Webpack给出了一个*"模块解析失败。Unexpected token "*的错误,因为它还不理解CSS文件的导入:

CSS import error

为开发配置CSS

我们需要告诉Webpack如何处理CSS文件。我们需要在dev Webpack的配置中添加几个加载器(在我们的例子中是webpack.dev.config ):

const config: Configuration = {
  ...
  module: {
    rules: [
      ...,
      {        test: /\.css$/i,        use: ["style-loader", "css-loader"],      },    ],
  },
  ...
};

因此,当Webpack遇到一个.css 文件时,它会用css-loaderstyle-loader 来处理它(use 数组中的加载器会按照相反的顺序🤔执行)。

css-loader``app.css style-loader 然后把这个CSS内容放到捆绑的html文件中的一个 元素中。虽然 元素在生产中并不理想,但在开发中却很好,因为Webpack开发服务器可以快速对 元素进行修改。style style style

为了使我们的新Webpack配置发挥作用,我们需要安装css-loaderstyle-loader 两个包,具体步骤如下:

npm install --save-dev css-loader style-loader

我们将需要停止并重启应用程序,以看到这个动作:

CSS working 为生产配置CSS

在生产中,我们希望将CSS放在一个外部CSS文件中,这样我们的应用就可以利用浏览器的缓存。我们可以通过使用mini-css-extract-plugin ,而不是style-loader 来做到这一点。

让我们先安装mini-css-extract-plugin 及其TypeScript类型:

npm install --save-dev mini-css-extract-plugin @types/mini-css-extract-plugin

我们现在可以将这个加载器添加到我们的生产webpack配置中(在我们的例子中是webpack.prod.config ):

...
import MiniCssExtractPlugin from "mini-css-extract-plugin";
const config: Configuration = {
  ...,
  module: {
    rules: [
      ...,
      {        test: /\.css$/i,        use: [MiniCssExtractPlugin.loader, "css-loader"],      },    ],
  },
  ...,
  plugins: [
    ...,
    new MiniCssExtractPlugin({      filename: "[name].[contenthash].css",    }),  ],
  ...
};

mini-css-extract-plugin 除了将内容加载到现有的捆绑包中,它还需要做更多的工作--它需要创建额外的文件( files)。这就是为什么它是一个插件。.css

我们在CSS文件名中使用了[name] 令牌,以便在我们的应用程序被代码分割时,Webpack可以为文件命名。我们使用了[contenthash] 令牌来改变捆绑文件的名称,当其内容发生变化时,这将破坏浏览器的缓存。

让我们做一个构建:

npm run build

我们会看到在build 文件夹中生成了一个CSS文件:

CSS bundle

如果我们看一下HTML文件,我们会看到引用的外部CSS文件:

CSS reference

不错 🙂

来自多个组件的样式

让我们重组我们的App 组件,让它引用两个子组件,这两个子组件引用不同的CSS文件:

import "./heading.css";
import "./content.css";

const App = () => (
  <>
    <Heading />
    <Content />
  </>
);
const Heading = () => <h1 className="heading">My React and TypeScript App</h1>;

const Content = () => <div className="content">With CSS!</div>;

我们的CSS文件如下:

/* heading.css */
.heading {
  color: red;
}
/* content.css */
.content {
  color: green;
}

如果我们在开发模式下运行应用程序 (npm start),我们会看到组件的样式是由HTML页面上的两个样式元素构成的,这符合我们的期望:

Multiple components in dev

如果我们进行构建(npm run build),我们将看到CSS被捆绑在一个CSS文件中:

Multiple components in prod

好极了! 🙂

使用CSS模块

这种方法的问题是,开发人员需要仔细地给CSS类命名,以免它们发生冲突。例如,如果我们把两个CSS类都称为 "text",那么标题和内容的颜色会是什么?

CSS clash 第二个CSS类优先于第一个,这就导致两块文字都是绿色的。

CSS模块解决了这个问题,它允许我们将CSS的范围扩大到某个特定的组件。让我们调整我们的组件以引用CSS模块:

import heading from "./heading.module.css";
import content from "./content.module.css";
...
const Heading = () => (
  <h1 className={heading.heading}>My React and TypeScript App</h1>
);
const Content = () => <div className={content.content}>With CSS!</div>;

导入语句略有不同。我们引用一个后缀为.module.css 的文件,并从该文件中导入一个变量,该变量将包含所有的CSS类名称。我们的组件引用类名变量--当我们看到输出结果时,我们会明白为什么会这样。

我们将需要把content.css 改为content.module.css ,把heading.css 改为heading.module.css

TypeScript提出了一个*"无法找到模块'.module.css'或其相应的类型声明 "*的错误,因为它不知道如何处理CSS模块:

CSS module error

为了解决这个错误,我们可以在src 文件夹中的一个叫做custom.d.ts 的文件中添加以下内容:

declare module "*.module.css";

这就解决了TypeScript的错误。

如果我们在开发模式下重启应用程序,我们会看到样式被应用,就像我们预期的那样:

CSS modules in dev 我们看到CSS类的名称被赋予了一个看起来很随机的名字。这确保了不同组件中的样式不会发生冲突。

如果我们回想一下,我们是如何在一个组件中引用类名的,这有点奇怪:

className={fileName.cssClassName}

这允许Webpack进程给类名一个唯一的名字。

如果我们产生一个生产构建(npm run build),我们在生产的CSS文件中也会得到唯一的类名:

CSS modules in production

很好! 🙂

这就是了!我们的React和TypeScript项目现在已经被设置为使用CSS了。

这段代码可以在GitHub上找到:github.com/carlrip/rea…