共计 4817 个字符,预计需要花费 13 分钟才能阅读完成。
什么是 CSS Modules
套用 CSS Modules 官网的话来说,一个 css 文件代表一个模块,这个 css 文件中的类名(class names)和 动画名(animation names)默认都有一个局部作用域,简单来说就是 css 也有作用域了(其实这里不是很准确,只是利用了名字转换,让 css 像是拥有了作用域一般)。
Just show me the code
原始代码
/*style.css*/
.header {
color: red;
font-size: 20px;
}
/* 或者 */
:lcoal(.header) {
color: red;
font-size: 20px;
}
import style from './style.css';
...
render() {
return <div className="page">
<section className={style.header}>
header
</section>
</div>
}
转换之后代码
.style__header___YHKJLJH {
color: red;
font-size: 20px;
}
<div class="page">
<div class="style__header___YHKJLJH">
header
</div>
</div>
为什么要用 CSS Modules
个人觉得主要是 CSS Modules 用于提供 css 局部作用域的能力,让 css 更加可控,避免污染全局样式。以往开发过程中往往只能靠命名规范,已经程序员自觉遵守团队的命名规范来避免样式污染。当项目越来越大,团队成员越来越多,就很难避免出现样式被污染的情况。此时改起样式难免有点束手束脚了,那么如果现在有一种方式(CSS Modules)能让你写的样式完全与其他的样式隔离,是多么爽的事情。
先来了解 css-loader 中与 CSS Modules 相关的配置
CSS Modules 其实并不是官方的功能,而是项目在编译打包阶段来修改类名,替换对应的 class,实质上 webpack 打包时是依赖 css-loader 来进行处理,让 CSS Modules 生效的。
- modules: 默认值为 false;true 表示开启 CSS Modules
- sourceMap:默认为 false,不开启 sourceMap; true 开启 sourceMap,开发环境下开启比较实用
- getLocalIdent:默认为 undefined,用 function 自定义生成的类名
- localIdentName:默认 [path]___[name]__[local]___[hash:base64:5],可以自定义了类名的模板,可以进行适当修改
开始实战吧
这里我们就以 reactjs 为例,来开启 CSS Modules 之旅。
这里我们做一个默认的约定,使用.module.css 或者 module.scss 作为文件后缀,区分全局的样式和局部样式。
- 首先使用 create-react-app 创建一个 my-app 项目, 然后运行项目 (创建过程需要一定时间,因为这里会把依赖包都安装好。)
npx create-react-app my-app
npm run start
演示项目使用的版本信息
{
"name": "my-app",
"version": "0.1.0",
"private": true,
"dependencies": {
"react": "^16.9.0",
"react-dom": "^16.9.0",
"react-scripts": "3.1.1"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
"eslintConfig": {"extends": "react-app"},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
}
}
- 不做任何修改即可使用 CSS Modules
在 my-app/src 目录添加一个 style.module.css 文件
.title {font-size: 25px;}
import React from 'react';
import logo from './logo.svg';
import './App.css';
import style from './style.module.css';
function App() {
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<p className={style.title}>Hello CSS Modules</p>
</header>
</div>
);
}
export default App;
保存后查看浏览器,可以在控制台中看到 p 标签的 class=”style_title__2t5Z0″,对应的样式类名 title 也转换成 style_title__2t5Z0。可见使用 create-react-app, 默认已经支持 CSS Modules。
- 查看 webpack 配置
新版本 create-react-app 创建的项目把打包构建的脚本都放在 npm 包中了,所以需要用 npm run eject 命令解压出相关文件夹, 运行成功后项目目录下会增加两个文件夹 config,scripts,我们主要查看 config/webpack.config.js,直接查看 oneOf 里面的内容中的这一块
/*css*/
{
test: cssModuleRegex,
use: getStyleLoaders({
importLoaders: 1,
sourceMap: isEnvProduction && shouldUseSourceMap,
modules: true,
getLocalIdent: getCSSModuleLocalIdent,
}),
},
/*scss*/
{
test: sassModuleRegex,
use: getStyleLoaders(
{
importLoaders: 2,
sourceMap: isEnvProduction && shouldUseSourceMap,
modules: true,
getLocalIdent: getCSSModuleLocalIdent,
},
'sass-loader'
),
},
//cssModuleRegex 是匹配 cssModule 的正则,/\.module\.css$/
//getStyleLoaders 是用于获取对应的 loader,我们只需关注其中的 css-loader,css-loader 使用的 options 就是 getStyleLoaders 的第一个参数,modules: true 打开 css modules,其实 css modules 是由 css-loader 提供支持
const getStyleLoaders = (cssOptions, preProcessor) => {
const loaders = [
...
{loader: require.resolve('css-loader'),
options: cssOptions,
},
...
return loaders;
}
可见在 create-react-app 创建的项目中 CSS Modules 已经开箱可用了(支持 css,scss),只需要修改一下使用样式的方式(import), 以及使用 module.css 和 module.scss(sass) 后缀
- 已有项目中引入 CSS Modules
实际情况中,我们经常是需要在自己搭建的脚手架中引入 CSS Modules,那也是非常简单的事情,只需要修改 css-loader 相关配置即可。sourceMap 只在开发环境下打开,localIdentName 在开发环境下显示比较详细,可以自由配置,而生产环境下直接显示编码后的 hash。推荐使用 module.scss,module.css 作为 CSS Modules 文件的后缀,和已有的样式文件区分处理。
// 是否开发环境
const isDevelop = true;
...
{
test: /\.module\.scss$/,
use: [
'style-loader',
{
'css-loader',
options: {
sourceMap: isDevelop,
modules: true,
localIdentName: dev ? '[path]___[name]__[local]___[hash:base64:5]' : '[hash:base64]',
}
}
'postcss-loader',
'sass-loader'
},
- 终极武器 babel-plugin-react-css-modules
现在已经解决了在样式文件中不再需要:local 来标志哪些类名需要进行转换的问题,那么在使用的时候还是有一些问题,CSS Modules 推荐使用驼峰命名,What?
show me code
import style from './style.module.css';
<div className={style.pageHeader}></div>
<div className={style['page-header']}></div>
很明显使用驼峰简洁许多了,官方推荐也不是没有道理的。除此之外,每次写 className 时,都要用 a.b 方式,能不能更简单一些?当然可以:
import './style.module.css';
...
render() {
return (<div styleName="title"></div>)
}
这样是不是感觉简单高效了许多。不过还需要借助一个 babel 插件 babel-plugin-react-css-modules,这个插件会在打包阶段把 styleName 属性的值转换为对应的名字(generateScopedName 定义的格式),之后把 styleName 属性值加到已有 className 中(如果没有 className 则创建)。
npm install babel-plugin-react-css-modules --save-dev
// 用于支持 scss
npm install postcss-scss
需要在 webpack 配置文件的 babel-loader 添加配置如下:
{test: /\.(js|mjs|jsx|ts|tsx)$/,
loader: 'babel-loader',
options: {
plugins: [
...
[
'react-css-modules',
{
webpackHotModuleReloading: true,
autoResolveMultipleImports: true,
generateScopedName: '[path]___[name]__[local]___[hash:base64:5]',
filetypes: {
'.scss': {syntax: 'postcss-scss',}
}
}
]
]
}
}
其中 generateScopedName 如果配置,则需要和 style-loader 中的 localIdentName 保持一致,否则会导致 styleName 使用的名字与实际生成的 classname 不一致,样式无效!filetypes 的配置是为了支持 scss,对于 scss 样式会先由 postcss-scss 做一次处理。
总结
以上就是个人关于 CSS Modules 初次探索,并且已经开始在项目中使用 CSS Modules,写起样式来可以随心所欲,再也不用担心样式污染了。