利用webpack拆分css
要拆分css,就必须把css看作向js那样的模块
要把css当成模块,就必须使用到构建工具(例如webpack)
webpack本身可以读取css文件的内容,但会将其内容当作JS代码进行分析,因此会导致错误
因此就必须有一个loader,能够将css代码转换为js代码
css-loader
css-loader的作用,就是将css代码转换为js代码字符串并导出
例如:
.red{
color: #f40;
}
经过css-loader转换后会形成js代码
module.exports = `.red{
color:"#f40";
}`
如果css文件中的某条样式规则使用到了其它外部文件,则css-loader会将引用的位置替换为一个js形式的导入语句
例如:
.red{
color: #f40;
background: url(./bg.png);
}
经过css-loader转换后会形成js代码
module.exports = `.red{
color:"#f40";
background:url("${require("./bg.png")}")
}`;
以上述代码为例,webpack在编译时发现css中导入了图片文件,于是对图片进行编译,期间就会对图片内容进行语法分析,但由于图片的内容本身是一串二进制数据,因此webpack直接分析图片源数据时会导致语法解析错误
因此,css-loader需要配合file-loader或url-loader一起使用,file-loader或url-loader可以在图片模块中导出一个路径或一个base64字符串,这样webpack就能顺利地编译图片文件
// webpack.config.js
module.exports = {
module: {
rules: [
{
test: /\.css$/,
use: ["css-loader"]
},
{
test: /\.(jpg|png|gif)$/,
use: ["url-loader"]
}
]
}
}
在css文件中也可以使用@import导入其他css文件,css-loader也会将@import指令替换为js形式的导入语句
例如:
@import "./reset.css";
.red{
color:"#f40";
background:url("./bg.png")
}
经过css-loader转换后会形成js代码
module.exports = `${require("./reset.css")}
.red{
color:"#f40";
background:url("${require("./bg.png")}")
}`;
style-loader
style-loader可以将css-loader转换后的代码进行进一步处理,具体就是创建一个style元素,并将css-loader导出的字符串作为style元素的内容,最后再将style元素加入到页面的中(如果开起来css module,则style-loader还能在模块中导出一个原始类名与转换后的类名的映射关系对象)
// webpack.config.js
module.exports = {
module: {
rules: [
{
test: /\.css$/,
use: ["style-loader", "css-loader"] // 注意使用的先后顺序
},
{
test: /\.(jpg|png|gif)$/,
use: ["url-loader"]
}
]
}
}
例如:
.red {
color:"#f40";
}
经过css-loader转换后会形成js代码
module.exports = `.red{
color:"#f40";
}`;
上面的代码再经过style-loader转换后变成:
module.exports = `.red{
color:"#f40";
}`;
var style = module.exports;
var styleElem = document.createElement("style");
styleElem.innerHTML = style;
document.head.appendChild(styleElem);
module.exports = {}; // 没有开启css module
BEM
BEM是一套针对css类样式的命名方式
其他命名方式还有:OOCSS、AMCSS、SMACSS等等
BEM全称是:Block Element Modifier
一个完整的BEM类名:block__element_modifier,例如:banner__dot_selected,可以表示:轮播图中,处于选中状态的小圆点
三个部分的具体含义为:
- Block:页面中的大区域,表示最顶级的划分,例如:轮播图(
banner)、布局(layout)、文章(article)等等 - element:大区域中的组成部分,例如:轮播图中的横幅图片(
banner__img)、轮播图中的容器(banner__container)、布局中的头部(layout__header)、文章中的标题(article__title) - modifier:可选。通常表示状态,例如:处于展开状态的布局左边栏(
layout__left_expand)、处于选中状态的轮播图小圆点(banner__dot_selected)
在某些大型工程中,如果使用BEM命名法,还可能会增加一个前缀,来表示类名的用途,常见的前缀有:
- l:layout,表示这个样式是用于布局的
- c:component,表示这个样式是一个组件,即一个功能区域
- u:util,表示这个样式是一个通用的、工具性质的样式
- j:javascript,表示这个样式没有实际意义,是专门提供给js获取元素使用的
css in js
css in js 的核心思想是:用一个JS对象来描述样式,而不是使用css样式表
例如下面的对象就是一个用于描述样式的对象:
const styles = {
backgroundColor: "#f40",
color: "#fff",
width: "400px",
height: "500px",
margin: "0 auto"
}
由于这种描述样式的方式根本就不存在类名,自然不会有类名冲突
至于如何把样式应用到界面上,不是它所关心的事情,你可以用任何技术、任何框架、任何方式将它应用到界面
vue、react都支持css in js,可以非常轻松的应用到界面
css in js的特点:
- 绝无冲突的可能:由于它根本不存在类名,所以绝不可能出现类名冲突
- 更加灵活:可以充分利用JS语言灵活的特点,用各种方式来处理样式
- 应用面更广:只要支持js语言,就可以支持css in js,因此,在一些用JS语言开发移动端应用的时候非常好用,因为移动端应用很有可能并不支持css
- 书写不便:书写样式,特别是公共样式的时候,处理起来不是很方便
- 在页面中增加了大量冗余内容:在页面中处理css in js时,往往是将样式加入到元素的style属性中,会大量增加元素的内联样式,并且可能会有大量重复,不易阅读最终的页面代码
css module
css module的出现,解决了不同css模块之间的类名冲突,但无法解决在一个css模块内部的类名冲突,因此在这种情况下,对于有冲突的两个css类,应该将它书写在不同的css模块之中
在css-loader中可以通过设置modules配置为true来开启css module
// webpack.config.js
module.exports = {
modules: {
rules: [
{
test: /\.css$/,
use: [
{
loader: "css-loader",
options: {
modules: true
}
}
]
}
]
}
}
当开启了css module后,css-loader会将样式中的类名进行转换,转换为一个唯一的hash值(之后再将样式内容转换为js代码字符串)
该hash值是根据css文件的模块ID以及css规则中的原始类名生成出来的,因此不同css文件中的同名类名,经过转换后得到的hash也不一样,自然也就做到了合并后不会发生冲突
样式应用
由于经过css module处理后类名发生了改变,因此不能直接在页面中使用开发者在源代码中定义的类名来使用样式
为了解决这个问题,css-loader将原始类名和转换后的hash类名的映射关系导出,让开发者可以在js中导入css模块来得到该映射关系
css-loader导出的内容除了类名的映射关系外,还包含很多其他冗余信息,这些冗余信息是开发者不会使用到的(是给之后的loader使用的)
style-loader除了会将css-loader转换后的css样式作为style元素的innerHTML,然后将style元素加入到页面中外,还可以将css-loader导出的信息简化:style-loader会去除css-loader导出内容中的冗余信息,仅保留有用的映射关系,之后开发者导入css模块就可以得到简洁的映射关系对象
其他操作
全局类名
某些类名是全局的、静态的,不需要css-loader进行转换
对于这些类名,只需要在类名位置使用一个特殊的语法即可:
:global(.类名){
样式规则;
}
对于没有进行转换的类名,自然也就没有对应的映射关系,导入时也就得不到该类名的信息
使用了global的类名不会进行转换,相反的,没有使用global的类名,表示默认使用了local,表示要对其进行转换:
:local(.类名){
样式规则;
}
控制最终的类名
可以设置css-loader的options中的modules.localIdentName属性来控制转换后的类名的形式
// webpack.config.js
module.exports = {
rules: [
{
test: /\.css$/,
use: [
{
loader: "css-loader",
options: {
modules: {
localIdentName: "[name]-[local]-[hash:5]"
}
}
}
]
}
]
}
[name]会被替换为css模块的模块名称
[local]会被替换为原始的类名
[hash]中的hash是css-loader根据css模块的模块ID和规则中的原始类名生成的hash,和webpack编译过程产生的hash是两回事
其他注意事项
-
css module需要配合构建工具来使用
-
css module仅转换顶级类名,因此尽量不要书写嵌套的类名(嵌套的类名不会被转换)
.outer .inner .xxx{ ...; }.outer就是顶级类名,.inner和.xxx就是嵌套的类名
-
css module仅处理类名和ID选择器,不会处理其他选择器
预编译器less
基本原理
由于css自身的特性,导致在css代码中难以做到样式的复用、混合以及计算等操作
预编译器可以解决这个问题
开发者可以使用一种类似于css但比css更加强大的其他语言来书写样式代码,但这些代码不能被浏览器识别,需要让它们经预编译器编译过后,形成浏览器可以识别的css代码
目前,最流行的预编译器有less和sass,由于它们两者特别相似,因此仅学习一种即可(本课程学习less)
less官网:lesscss.org/
less中文文档1(非官方):lesscss.cn/
less中文文档2(非官方):less.bootcss.com/
sass官网:sass-lang.com/
sass中文文档1(非官方):www.sass.hk/
sass中文文档2(非官方):sass.bootcss.com/
less的安装和应用
安装less:
npm i -D less
less提供了一个CLI工具lessc,通过该工具即可完成编译
lessc less代码文件所在位置 编译后的文件放置的位置
命令中的相对路径都是以CWD为基准的
/* index.less */ @red: #f40; .redcolor { color: @red; }运行命令:
lessc index.less index.css编译之后的代码:
/* index.css */ .redcolor { color: #f40; }
less的基本使用
具体的使用见文档:less.bootcss.com/
变量
less允许在代码中定义变量,并且后面定义的变量会覆盖之前的同名变量
@width: 10px;
@width: 20px;
@height: @width + 10px; /* 可以进行运算 */
#header {
width: @width;
height: @height;
}
编译过后:
#header {
width: 20px;
height: 30px;
}
混合
.center(@type: absolute) {
position: @type;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
}
a {
color: #f40;
.center(fixed);
}
编译过后:
a {
color: #f40;
position: fixed;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
}
嵌套
#header {
color: #000;
>.navigation {
font-size: 12px;
}
.logo {
width: 300px;
}
&::before{
content: "abc";
}
}
编译过后:
#header {
color: #000;
}
#header>.navigation {
font-size: 12px;
}
#header .logo {
width: 300px;
}
#header::before {
content: "abc";
}
运算
@width: 500px;
div {
width: @width * 2;
}
编译过后:
div {
width: 1000px;
}
函数
less提供许多实用函数供开发者使用
div {
margin: if(2 > 1, 50px, 100px);
}
编译过后:
div {
margin: 50px;
}
作用域
less中定义的变量只能在当前作用域以及其内部使用
如果外部作用域中也有同名变量,则优先使用最近作用域中的变量
@height: 200px;
#header {
@height: 100px;
height: @height;
.menu {
height: @height;
}
}
#footer {
height: @height;
}
编译过后:
#header {
height: 100px;
}
#header .menu {
height: 100px;
}
#footer {
height: 200px;
}
注释
less提供了两种注释:单行注释和多行注释
多行注释会在编译结果中出现,单行注释不会
/*
多行注释写法和css中的注释一样
*/
// 单行注释写法和js中的单行注释一样
body {
margin: 0;
}
编译过后:
/*
多行注释
*/
body {
margin: 0;
}
导入
less中使用@import来导入其他less模块或css模块
对于导入的是less模块,相当于将模块中的less代码复制到导入位置
对于导入的是css模块,编译过后的css文件中仍然会存在该导入语句
// color.less
@color: #f40;
// index.less
@import "color"; // 默认后缀名就是.less
@import "test.css";
body {
color: @color;
}
相当于:
@import "test.css";
@color: #f40;
body {
color: @color;
}
编译过后:
@import "test.css";
body {
color: #f40;
}
在webpack中使用less
在webpack中使用less-loader将less代码编译为css代码
安装less,less-loader:
npm i -D less less-loader
less-loader只是一个函数,而该函数中需要使用到less库中提供的功能,因此还需要安装less库
应用less-loader:
// webpack.config.js
module.exports = {
module: {
rules: [
{
test: /\.less$/,
use: ["style-loader", "css-loader", "less-loader"]
}
]
}
}
less-loader应该放在最后面,因为它需要被最先使用到
less其实并没有解决类名冲突的问题,因此可以将less和css module配合起来使用
// webpack.config.js
module.exports = {
module: {
rules: [
{
test: /\.less$/,
use: ["style-loader",
{
loader: "css-loader",
options: {
modules: {
localIdentName: "[name]-[local]-[hash:5]"
}
}
},
"less-loader"
]
}
]
}
}
PostCSS
官网地址:postcss.org/
github地址:github.com/postcss/pos…
css的工程化其实存在很多问题,但之前介绍的BEM、css in js、css module、less等都只能某个方面的问题
PostCSS借鉴了webpack的思想,它可以对css工程化中遇到的问题进行统一处理
PostCSS类似于less,它也是一个编译器,css源代码经过PostCSS处理过后,会形成新的css代码
PostCSS中也可以使用很多第三方编写的插件,PostCSS会将上一阶段的插件的处理结果作为下一阶段的输入传递给下一阶段的插件
PostCSS中插件(Plugin)的作用和webpack中的loader和plugins一样
其实PostCSS本身并没有做太多事情,它只是将开发者写的css源代码进行分析,并将分析结果传递给插件进行处理,最后再将插件处理的结果返回,即中间具体的处理过程是交给插件来完成的
安装PostCSS
npm i -D postcss
npm i -D postcss-cli
postcss-cli提供了一些CLI命令来让开发者方便地使用postcss,开发者可以通过输入命令来完成css代码的编译
postcss本身也提供了对应的js api来转换代码,如果想使用postcss的高级功能,或手动编写postcss插件,就需要调用api来使用postcss,具体参见文档:api.postcss.org/
命令的使用方式:
postcss 源代码文件所在的位置 -o 输出文件要放置的位置 [-w]
-w是可选参数,和webpack中的--watch作用一致
源代码文件可以是以.css为后缀的普通css文件,也可以是以.pcss或.postcss为后缀的文件
例如:
postcss ./src/index.css -o ./src/xxx.css
配置文件
postcss也有自己的配置文件,配置文件中的配置会影响postcss的编译行为
配置文件的默认名称为postcss.config.js
// postcss.config.js
module.exports = {
map: false, // 关闭sourcemap(css也可以有源码地图)
plugins: {
"...": {}
}
}
插件
仅使用postcss本身提供的功能并没有什么意义,要让它真正的发挥作用,需要使用到插件
postcss的插件市场:www.postcss.parts/
下面罗列一些postcss的常用插件
postcss-preset-env
安装:
npm i -D postcss-preset-env
在postcss中使用postcss-preset-env:
// postcss.config.js
module.exports = {
plugins: {
"postcss-preset-env": {} // 属性名为插件的名称,属性值为插件的具体配置对象
}
}
postcss-preset-env提供了许多小功能,安装了postcss-preset-env就相当于安装了很多小功能的插件
常用的功能有:
- 自动厂商前缀
- 未来的css语法
自动厂商前缀
某些新的css样式在旧版本浏览器中使用时需要加上厂商前缀方
例如:
::placeholder {
color: red;
}
该功能在不同的旧版本浏览器中需要书写为
::-webkit-input-placeholder {
color: red;
}
::-moz-placeholder {
color: red;
}
:-ms-input-placeholder {
color: red;
}
::-ms-input-placeholder {
color: red;
}
::placeholder {
color: red;
}
要完成这件事情,需要使用autoprefixer库,但postcss-preset-env内部包含了该库,因此也包含了该功能
如果需要调整浏览器兼容的范围,可以通过下面的方式进行配置:
-
在postcss-preset-env的配置中加入browsers
// postcss.config.js module.exports = { plugins: { "postcss-preset-env": { browsers: [ "last 2 version", "> 1%" ] } } } -
在工程的根目录中创建文件
.browserslistrc,填写配置内容last 2 version > 1% -
在工程的package.json文件中加入browserslist配置
// package.json { "browserslist": [ "last 2 version", "> 1%" ] }
browserslist是一个多行的(或数组形式的)字符串,它的书写规范多而繁琐,具体参见:github.com/browserslis…
常见写法:
last 2 version
> 1% in CN
not ie <= 8
last 2 version:匹配最近两个版本的浏览器> 1% in CN:匹配国内市场占有率大于1%的浏览器not ie <= 8:不匹配版本号小于等于8的IE浏览器
默认情况下,匹配的结果是取三者的交集(即要...也要...还要...)
可以通过网站 browserl.ist/ 对配置结果覆盖的浏览器进行查询,查询时,多行之间使用英文逗号分割
未来的CSS语法
CSS的某些前沿语法正在制定过程中,没有形成真正的标准,如果希望使用这部分语法,就需要进行编译
过去完成该语法编译的是cssnext库,不过有了postcss-preset-env后,它自动包含了该功能
可以通过postcss-preset-env的stage配置,告知postcss-preset-env需要对哪个阶段的css语法进行兼容处理,它的默认值为2
// postcss.config.js
module.exports = {
plugins: {
"postcss-preset-env": {
stage: 2
}
}
}
一共有5个阶段可配置:
- Stage 0: Aspirational —— 只是一个早期草案,极其不稳定
- Stage 1: Experimental —— 仍然极其不稳定,但是提议已被W3C公认
- Stage 2: Allowable —— 虽然还是不稳定,但已经可以使用了
- Stage 3: Embraced —— 比较稳定,可能将来会发生一些小的变化,它即将成为最终的标准
- Stage 4: Standardized —— 所有主流浏览器都应该支持的W3C标准
常见的前沿css语法有:
-
变量
变量需要在
:root{}中定义,并需要使用--前缀命名变量:root{ --lightColor: #ddd; --darkColor: #333; } a{ color: var(--lightColor); background: var(--darkColor); }默认情况下,编译后的代码中会保留有使用了新语法的代码,如果不希望在结果中看到新语法,可以配置
postcss-preset-env.preserve为false -
自定义选择器
@custom-selector :--heading h1, h2, h3, h4, h5, h6; @custom-selector :--enter :focus,:hover; a:--enter{ color: #f40; } :--heading{ font-weight:bold; } :--heading.active{ font-weight:bold; } -
嵌套
与less相同,只不过嵌套的选择器前必须使用符号&
.a { color: red; &.b { color: green; } &>.b { color: blue; } &:hover { color: #000; } }编译后:
.a { color: red } .a .b { color: green; } .a>.b { color: blue; } .a:hover { color: #000; }
postcss-apply
npm i -D postcss-apply
// postcss.config.js
module.exports = {
plugins: {
"postcss-apply": {}
}
}
类似于less中的混合,postcss-apply允许定义一个CSS代码片段,然后在需要的时候应用它
代码片段需要在:root{}中进行定义,片段名称需要加上--前缀
:root {
--center: {
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
}
}
.item{
@apply --center;
}
编译后:
.item{
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
}
postcss-color-function
npm i -D postcss-color-function
// postcss.config.js
module.exports = {
plugins: {
"postcss-color-function": {} // 属性名为插件的名称,属性值为插件的具体配置对象
}
}
该插件提供了许多关于颜色转换的函数,并支持在源码中使用这些函数
例如:
body {
/* 使用颜色#aabbcc,不做任何处理,等同于直接书写 #aabbcc */
color: color(#aabbcc);
/* 将颜色#aabbcc透明度设置为90% */
color: color(#aabbcc a(90%));
/* 将颜色#aabbcc的红色部分设置为90% */
color: color(#aabbcc red(90%));
/* 将颜色#aabbcc调亮50%(更加趋近于白色),类似于less中的lighten函数 */
color: color(#aabbcc tint(50%));
/* 将颜色#aabbcc调暗50%(更加趋近于黑色),类似于less中的darken函数 */
color: color(#aabbcc shade(50%));
}
编译后:
body {
color: rgb(170, 187, 204);
color: rgba(170, 187, 204, 0.9);
color: rgb(230, 187, 204);
color: rgb(213, 221, 230);
color: rgb(85, 94, 102);
}
stylelint
npm i -D stylelint
// postcss.config.js
module.exports = {
plugins: {
"stylelint": {}
}
}
该插件用于规范开发者书写css代码
当开发者书写了不规范的css代码时,stylelint会发现错误并提示给开发者
注意:该插件只是将不规范的地方显示给开发者看,并不会导致编译失败,最终还是能够形成目标css文件
由于不同的企业可能使用的css代码规范不同,因此stylelint本身并没有提供具体的代码规则,这需要我们安装其他第三方库,并将第三方库中的代码书写规范应用到stylelint中,让它能根据规范找出错误
通常会使用stylelint-config-standard库
npm i -D stylelint-config-standard
库安装好后,需要将该库应用到stylelint中,应用的方式如下:
在工程的根目录中创建stylelint的配置文件.stylelintrc,并书写下面的内容
// .stylelintrc
{
"extends": "stylelint-config-standard"
}
stylelint-config-standard中提供了很多规则,同时也允许开发者手动更改或取消一部分的规则
设置的方式如下:
// .stylelintrc
{
"extends": "stylelint-config-standard",
"rules": {
"indentation": 4, // 表示限制缩进的数量为4,默认值是2
"indentation": null // 取消缩进限制规则
}
}
在webpack中使用postcss
在webpack中使用postcss-loader来应用postcss
安装postcss和postcss-loader:
npm i -D postcss postcss-loader
应用postcss-loader:
// webpack.config.js
module.exports = {
module: {
rules: [
{
test: /\.(css|pcss|postcss)$/,
use: ["style-loader", "css-loader", "postcss-loader"] // 注意顺序
}
]
}
}
抽离css文件
抽离css文件就是将css文件单独作为一个资源输出到打包结果中,而不是生成一个style元素后并将style元素添加到html页面中
style-loader就是将css代码转换为style元素的元素内容然后加入到页面中,因此要想抽离css文件就不能使用style-loader
这时就需要使用到另一个第三方库:mini-css-extract-plugin
该库提供了一个loader和一个plugin
loader负责记录所匹配的css文件中的内容,若开发者开启了css module,则loader还会导出一个原始类名与hash类名的映射对象提供给开发者使用
plugin会在每个chunk的资源列表中都加入一个css资源,该css资源的内容是plugin根据chunk中所使用到的一个或多个css文件,将这些css文件在loader中的对应记录按照文件导入的先后顺序全部合并起来的结果
具体的配置方式如下所示:
// webpack.config.js
var MiniCssExtractPlugin = require("mini-css-extract-plugin");
module.exports = {
module: {
rules: [
{
test: /\.css$/,
use: [
MiniCssExtractPlugin.loader,
"css-loader?modules"
]
}
]
},
plugins: [
new MiniCssExtractPlugin()
]
}
默认情况下,plugin所添加的css文件资源的文件名称是以chunk的name来命名的,这可以通过plugin的filename配置来更改
// webpack.config.js
var MiniCssExtractPlugin = require("mini-css-extract-plugin");
module.exports = {
module: {
rules: [
{
test: /\.css$/,
use: [
MiniCssExtractPlugin.loader,
"css-loader?modules"
]
}
]
},
plugins: [
new MiniCssExtractPlugin({
filename: "css/[chunkhash:5].css" // 规则字符串中的规则都来自于chunk
})
]
}
当chunk的资源列表中包含有css文件资源时,html-webpack-plugin除了会将chunk中的js资源引入到html中,还会将css资源引入到html页面中
而默认情况下html-webpack-plugin会将所有chunk中的资源都进行引用
// webpack.config.js
var MiniCssExtractPlugin = require("mini-css-extract-plugin");
module.exports = {
entry: {
main: "./src/main.js",
other: "./src/other.js"
},
module: {
rules: [
{
test: /\.css$/,
use: [
MiniCssExtractPlugin.loader,
"css-loader?modules"
]
}
]
},
plugins: [
new HtmlWebpackPlugin(), // 生成的html文件中会引入两个css文件
new MiniCssExtractPlugin({
filename: "css/[hash:5].css"
})
]
}
可以通过设置插件的chunks配置来限制html中引入的js资源和css资源
// webpack.config.js
var MiniCssExtractPlugin = require("mini-css-extract-plugin");
module.exports = {
entry: {
main: "./src/main.js",
other: "./src/other.js"
},
module: {
rules: [
{
test: /\.css$/,
use: [
MiniCssExtractPlugin.loader,
"css-loader?modules"
]
}
]
},
plugins: [
new HtmlWebpack4Plugin({
chunks: ["main"] // html中只会引入main chunk中的css文件
}),
new MiniCssExtractPlugin({
filename: "css/[hash:5].css"
})
]
}