在上一篇文章中,我们创建了一个使用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
我们需要告诉Webpack如何处理CSS文件。我们需要在dev Webpack的配置中添加几个加载器(在我们的例子中是webpack.dev.config ):
const config: Configuration = {
...
module: {
rules: [
...,
{ test: /\.css$/i, use: ["style-loader", "css-loader"], }, ],
},
...
};
因此,当Webpack遇到一个.css 文件时,它会用css-loader 和style-loader 来处理它(use 数组中的加载器会按照相反的顺序🤔执行)。
css-loader``app.css style-loader 然后把这个CSS内容放到捆绑的html文件中的一个 元素中。虽然 元素在生产中并不理想,但在开发中却很好,因为Webpack开发服务器可以快速对 元素进行修改。style style style
为了使我们的新Webpack配置发挥作用,我们需要安装css-loader 和style-loader 两个包,具体步骤如下:
npm install --save-dev css-loader style-loader
我们将需要停止并重启应用程序,以看到这个动作:
为生产配置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文件:

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

不错 🙂
来自多个组件的样式
让我们重组我们的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页面上的两个样式元素构成的,这符合我们的期望:

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

好极了! 🙂
使用CSS模块
这种方法的问题是,开发人员需要仔细地给CSS类命名,以免它们发生冲突。例如,如果我们把两个CSS类都称为 "text",那么标题和内容的颜色会是什么?
第二个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模块:

为了解决这个错误,我们可以在src 文件夹中的一个叫做custom.d.ts 的文件中添加以下内容:
declare module "*.module.css";
这就解决了TypeScript的错误。
如果我们在开发模式下重启应用程序,我们会看到样式被应用,就像我们预期的那样:
我们看到CSS类的名称被赋予了一个看起来很随机的名字。这确保了不同组件中的样式不会发生冲突。
如果我们回想一下,我们是如何在一个组件中引用类名的,这有点奇怪:
className={fileName.cssClassName}
这允许Webpack进程给类名一个唯一的名字。
如果我们产生一个生产构建(npm run build),我们在生产的CSS文件中也会得到唯一的类名:

很好! 🙂
这就是了!我们的React和TypeScript项目现在已经被设置为使用CSS了。
这段代码可以在GitHub上找到:github.com/carlrip/rea…