使用 React 的 CSS 模块指南
有许多不同的方法可以为 React 组件提供样式,例如导入纯 CSS、使用样式组件、JS-in-CSS 或 CSS 模块。这些都有各种优点和缺点。
在我看来,CSS 模块似乎为初学者和中级用户提供了最佳的整体解决方案。我们可以使用标准的 CSS 语法,它允许有效的复制和粘贴编程,并且我们可以确保良好的客户端性能。
在本文中,我将介绍使用 CSS 模块时的一些注意事项。具体来说,我有兴趣以与框架无关的方式来看待这个问题。许多框架,例如Next.js提供了对 CSS 模块的内置支持。但是,我认为值得探索如何在更多 vanilla 设置中使用 CSS 模块。我还将探讨如何在与框架无关的服务器端渲染中使用 CSS 模块。
CSS 模块基础
CSS 模块只是简单的 CSS 文件,我们可以与 React 组件一起开发:
.myclass {
padding: 10px;
}
要在 React 组件中使用 CSS 模块,我们需要“导入”我们想要使用的 CSS 源文件:
import React from 'react';
import styles from './MyComponent.module.css';
然后,我们可以在声明组件时引用 CSS 文件中的样式:
return <div className={styles.myclass}></div>
CSS 模块的神奇之处在于,通用类名例如myclass
被翻译成唯一的类名,保证不会与我们可能希望在页面上加载的任何其他类名发生冲突。例如,myclass
可以转换为mycomponent-myclass-78Sdd1
.
在使用 CSS 模块定义我们的 React 组件时,我们面临两个挑战:
- 我们需要指示我们的捆绑器将 CSS 转换为具有生成的类名的 CSS,并将该 CSS 与我们发送给客户端的其他文件一起包含。
- 我们需要确保在运行我们的 JavaScript 源代码时,正确解析引用的类名(例如访问
styles
上面的导入)。
在下文中,我将描述我们如何在开发和部署的各个阶段应对这些挑战。
CSS 模块 IntelliSense
在编写 React 组件代码时,能够查找我们包含在 CSS 中的类名非常有用。这可以防止我们将 CSS 中的类名复制并粘贴到 JavaScript 中,并避免错误。
为此,我们可以使用typescript-plugin-css-modules库。
只需将此库添加到您的项目中:
yarn add -D typescript-plugin-css-modules
tsconfig.json
然后使用以下插件扩展您的文件:
{
"compilerOptions": {
"plugins": [
{
"name": "typescript-plugin-css-modules"
}
]
}
}
这将支持在各种编辑器(例如 VSCode)中处理 TypeScript/JavaScript 代码时从 CSS 文件中查找类名。
请注意,此插件仅在开发期间生效,不会在编译期间捕获任何错误(请参阅 TypeScript 问题#16607)。
编译 CSS 模块
当我们尝试将文件导入到本身不是 TypeScript 文件的文件中时,TypeScript 编译器会发出.ts
错误.tsx
。
为了解决这个错误,我们需要定义一个.d.ts
模块来指示 TypeScript 如何解析.css
我们要导入的文件:
declare module '*.css';
我们还可以为 TypeScript 提供一些关于导入数据结构的更多提示,例如使用以下声明而不是上面给出的简单声明:
declare module '*.css' {
const classes: { [key: string]: string };
export default classes;
}
确保.d.ts
您声明的文件实际上是由 TypeScript 加载的。最简单的方法是"include"
在文件中扩展数组tsconfig.json
:
{
"include": [
"./src/typings.d.ts"
]
}
用于服务器端渲染的 CSS 模块 (Node.js)
将 TypeScript 代码转换为 JavaScript 后,我们可以在浏览器环境中或使用 Node.js 运行代码。本节讨论如何在 Node.js 中运行引用 CSS 文件的 JavaScript 代码。下一节将讨论如何在浏览器中运行此代码。
随着服务器端渲染的出现,我们很可能需要在服务器环境中运行我们的 React 组件代码。如果我们尝试这样做,我们很可能会遇到SyntaxError
如下情况:
C:\Users\Max\repos\my-awesome-project\src\index.css:1
.myclass {
^
SyntaxError: Unexpected token '.'
at Object.compileFunction (node:vm:352:18)
at wrapSafe (node:internal/modules/cjs/loader:1033:15)
这是因为 Node.js 无法加载我们的 CSS 文件的源代码;因为解释器不了解 CSS 语言。有很多方法可以解决这个问题,但我发现最简单的方法是挂钩 Node 加载所需源文件的机制。
为了使这更容易,我开发了一个简单的库node-css-require。该库可以通过两种方式使用:
首先,我们可以在代码中导入库并运行该register()
方法。这需要在加载任何具有 CSS 导入的源文件之前发生:
import { register } from 'node-css-require';
register();
或者,我们可以定义一个register.js
包含以下内容的文件:
const { register } = require('node-css-require');
register();
然后在调用 Node.js CLI 时手动要求加载此库:
node -r register.js myscript.js
在部署我们的服务器端代码时,我们经常希望使用捆绑器来最小化我们需要部署的代码的大小。流行的打包工具是webpack和esbuild。
我们需要指示我们的捆绑器如何处理导入的 CSS 文件。对于服务器端渲染,我们需要来自捆绑器的两个输出:
- 包含从原始类名到生成的类名的映射的 JavaScript 文件
- 一个 CSS 文件,其中包含 CSS 以及为我们要呈现的页面中包含的所有组件生成的类名
有许多可用的插件可以帮助解决这个问题,例如用于 webpack 的css-loader或用于 esbuild的 esbuild-css-modules-plugin。
然而,我发现现有的插件非常复杂,很难在自定义设置中工作,并且通常专注于为客户端而不是服务器应用程序捆绑 CSS 模块。因此,我创建了另一个小型库esbuild-css-modules-server-plugin。
esbuild -css-modules-server-plugin不到 50 行代码,为我们提供了服务器端渲染所需的一切。
要使用此插件,只需将其安装到您的项目中,然后将其添加到plugins
esbuild 配置中的数组中:
import { build } from 'esbuild';
import cssServerPlugin from 'esbuild-css-modules-server-plugin';
const generatedCss: string[] = [];
const res = await build({
plugins: [
cssServerPlugin({
onCSSGenerated: (css) => {
generatedCss.push(css);
},
}),
],
});
console.log('Generated CSS:');
console.log(generatedCss.join('\n'));
这个插件确保所有 JavaScript 源文件都被正确捆绑,例如,*.css
导入被解析为可以在服务器端渲染期间用于将原始类名解析为生成的类名的对象。通过使用onCSSGenerated
回调,我们可以收集所有生成的 CSS 并将其与我们生成的 JavaScript 一起存储以供服务器使用。
例如,当将代码传送到无服务器函数时,我们可以部署一个bundle.js
包含所有 JavaScript 逻辑的文件,并在其bundle.css
旁边放置一个文件,我们可以在请求时将其发送给客户端。或者,我们也可以上传bundle.css
到静态网站/CDN。
用于客户端捆绑的 CSS 模块
在客户端处理 CSS 模块比在服务器端处理它们要容易得多。与 Node.js 相比,浏览器原生理解 CSS,因此我们很容易根据需要加载 CSS 文件。
但是,我们仍然需要做一些工作才能使我们的代码在浏览器中可执行。为此,我们再次可以使用捆绑器。前面提到的css-loader或esbuild-css-modules-plugin通常适用于客户端捆绑。
但是,我再次编写了一个小型轻量级库,以帮助使用 esbuild 为客户端捆绑我们的代码。
不到 50 行代码的esbuild-css-modules-client-plugin完成了我们客户端捆绑所需的一切。
我们可以按如下方式使用该库:
import { build } from 'esbuild';
import cssPlugin from 'esbuild-css-modules-client-plugin';
const res = await build({
plugins: [cssPlugin()],
});
该插件通过在页面加载时注入所需的 CSS 来工作。理想情况下,我们希望将其与esbuild-css-modules-server-plugin结合使用。当我们在服务器端编译所有 CSS 并将其与我们的前端代码一起发布时,我们只需要在我们的页面上加载一次生成的 CSS。在这种情况下,没有必要在组件加载时加载注入的 CSS。
如果我们已经将生成的 CSS 与我们的包一起提供,我们可以excludeCSSInject
在加载插件时使用该选项:
import { build } from 'esbuild';
import cssPlugin from 'esbuild-css-modules-client-plugin';
const res = await build({
plugins: [
cssPlugin({
excludeCSSInject: true,
}),
],
});
如果您想一次性生成客户端 JavaScript 和捆绑的 CSS,您可以同时使用esbuild-css-modules-server-plugin和 esbuild-css-modules-client-plugin:
import { build } from 'esbuild';
import cssServerPlugin from 'esbuild-css-modules-server-plugin';
import cssPlugin from 'esbuild-css-modules-client-plugin';
const generatedCss: string[] = [];
const res = await build({
plugins: [
cssServerPlugin({
onCSSGenerated: (css) => {
generatedCss.push(css);
},
}),
cssPlugin({
excludeCSSInject: true,
}),
],
});
console.log('Generated CSS:');
console.log(generatedCss.join('\n'));
只需将生成的 CSS 与 esbuild 生成的 JavaScript 文件一起存储,然后一起部署即可。
最后的想法
使用 CSS 模块最简单的方法是利用框架中提供的支持,例如Next.js或Create React App。然而,CSS 模块中有很多隐含的复杂性,可能会导致意外的行为和错误。
在这篇文章中,我的目标是提供一个更底层的 CSS 模块视图。我想证明我们可以用相对较少的代码行来实现我们需要的任何东西。我提供的三个库都非常简单,由一个简短的源文件组成:
虽然这些不可能神奇地解决您所有的问题,但我希望通过探索这些源代码,您可以找到解决您独特问题的方法。
我在为 React 应用程序的无服务器服务器端渲染构建轻量级框架的背景下探索了这一点。这方面的主要挑战是支持捆绑部署到云和本地开发,以及如何在服务器端动态呈现页面。
如果您有兴趣探索包含端到端支持的框架,请参阅 Goldstack服务器端渲染模板。
文章来源:https://mxro.hashnode.dev/a-guide-to-css-modules-with-react