前言

没想到吧,这个系列还能出4,这篇会介绍Webpack中Css的相关内容,一些JavaScript的内容留到下个文章了。

CSS工程化

为什么要进行css工程化

在前端项目开发中,CSS是不可或缺的一部分,在前端项目不断变得庞大,复杂的今天,传统的CSS在开发中也暴露了一些问题,这驱使前端开发者不断寻找CSS工程化的最佳实践

传统开发会使用一个link标签引入CSS样式

1
<link rel="stylesheet" href="index.css">

这种开发模式有下面几个弊端

  1. 类名冲突:当页面复杂时,如果层级太浅,很容易导致类名冲突,但是过深的层级会导致代码的编写,阅读和复用变得困难,在打包时也难以压缩代码体积
  2. 重复样式:在页面中,有一些重复的值(比如主题色)会重复出现在代码中,难以维护和复用
  3. CSS文件细分问题:在大型项目里,我们希望把CSS按照不同的模块来拆分,这样更有利于维护,比如一个轮播图模块,我们希望依赖的css样式只关心轮播图,这就要求css可以按照不同的模块,功能来拆分,css会比过去拆分的更细。

怎么解决上面的问题

这篇文章会介绍下面列出的方法,这里只是进行一个大致的介绍

类名冲突

命名约定

即提供一种命名的标准,来解决冲突,常见的标准有:

  • BEM
  • OOCSS
  • AMCSS
  • SMACSS
CSS in JS

这种方式用js对象来表示样式,然后把样式直接应用到元素的style中,这样一来,css变成了一个一个的对象,就可以利用js的一些特性对css进行操作

  • 通过一个函数返回一个样式对象
  • 把公共的样式提取到公共模块中返回
  • 应用js的各种特性操作对象,比如:混合、提取、拆分

React-Native中就使用了这个方式

css module

非常好用的css模块化方案,这个方案会根据css文件的路径和类名生成一个新类名,在页面上应用这个新类名就可以避免重名了

解决重复样式

CSS in JS

既然是在JS里写CSS,自然可以解决重复样式,因为所有的公共部分都可以提取出来,然后需要时通过函数混合就行

预处理语言

预编译语言,你可以理解成CSS的高级版,它支持变量,函数等语法,然后在打包成项目时,会通过编译器转化成正常的CSS

常见的预处理语言有

  • scss
  • sass
  • less

解决CSS文件细分问题

这部分可以使用构建工具解决,比如webpack

在webpack中使用一些loader或者plugin用来处理,打包,合并,压缩CSS文件

在Webpack处理CSS

使用css-loader和style-loader插入样式

安装css-loaderstyle-loader

1
npm install css-loader style-loader -D

修改webpack.config.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
const path = require("path");
const {CleanWebpackPlugin} = require("clean-webpack-plugin");
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const webpack = require("webpack");
module.exports = {
entry: {
index: "./index.js"
},
mode: "development",
output: {
path: path.resolve(__dirname, "dist"),
filename: "js/[name].[chunkhash:5].js",
publicPath: "/"
},
devServer: {
contentBase: path.resolve(__dirname, 'dist'),
port: 9000,
open : true,
},
context: path.resolve(__dirname, "src"),
module: {
rules: [
{
test: /\.css$/,
// 这里使用style-loader和css-loader来处理css
// 因为loader的处理顺序是后到前,所以这里要倒着写
// 实际上loader的执行顺序是先css-loader,再到style-loader
use: ["style-loader", "css-loader"]
},
{
test: /\.(png)|(gif)|(jpg)$/,
use: [{
loader: "url-loader",
options: {
limit: 10 * 1024,
name: "imgs/[name].[contenthash:5].[ext]"
}
}]
}],
noParse: /jquery/
},
plugins: [
new CleanWebpackPlugin(),
new HtmlWebpackPlugin({
template: "./public/index.html",
filename: "html/index.html",
chunks: ["index"]
}),
new CopyWebpackPlugin({
patterns: [
{
from: "./assets",
to: "./assets"
},
],
}),
new webpack.ProvidePlugin({
$ : 'jquery'
})
],
resolve: {
alias: {
"@": path.resolve(__dirname, 'src')
}},
externals: {
jquery : "$"
}
}

这里我们只修改了css的处理方式,改为使用css-loader和style-loader来处理,运行下面的命令

1
npm run build

打包成功

1
2
3
4
5
6
7
8
9
10
11
12
13
E:\Workspaces\Project\WebPro\WebpackTest>npm run build

> webpacktest@1.0.0 build E:\Workspaces\Project\WebPro\WebpackTest
> webpack
[webpack-cli] Compilation finished
asset assets/img.png 189 KiB [emitted] [from: assets/img.png] [copied]
asset js/index.0a600.js 4.84 KiB [emitted] [immutable] (name: index)
asset html/index.html 299 bytes [emitted]
runtime modules 931 bytes 4 modules
built modules 427 bytes [built]
./index.js 385 bytes [built] [code generated]
external "$" 42 bytes [built] [code generated]
webpack 5.10.1 compiled successfully in 225 ms

查看打包的页面,可以看到页面中多了一个style元素

image-20210113112625237

这样打包就成功了

简单的css-loader和style-loader解析

我们来看看这两个loader的作用吧,先看css-loader

The css-loader interprets @import and url() like import/require() and will resolve them.

从介绍中我们可以看出,css-loader会解析@import和url这样的语法,举个例子你就知道了

1
2
3
.red{
color:"#f40";
}

经过css-loader转换后变成js代码:

1
2
3
module.exports = `.red{
color:"#f40";
}`

再例如:

1
2
3
4
.red{
color:"#f40";
background:url("./bg.png")
}

经过css-loader转换后变成:

1
2
3
4
5
var import1 = require("./bg.png");
module.exports = `.red{
color:"#f40";
background:url("${import1}")
}`;

webpack会把上面的代码转化成AST,然后进行依赖查找,这样Webpack会把依赖./bg.png添加到模块列表,最后把代码转化为

1
2
3
4
5
var import1 = __webpack_require__("./src/bg.png");
module.exports = `.red{
color:"#f40";
background:url("${import1}")
}`;

再例如:

1
2
3
4
5
@import "./reset.css";
.red{
color:"#f40";
background:url("./bg.png")
}

在css-loader处理后会变成

1
2
3
4
5
6
7
var import1 = require("./reset.css");
var import2 = require("./bg.png");
module.exports = `${import1}
.red{
color:"#f40";
background:url("${import2}")
}`;

webpack进行处理后

1
2
3
4
5
6
7
var import1 = __webpack_require__("./reset.css");
var import2 = __webpack_require__("./bg.png");
module.exports = `${import1}
.red{
color:"#f40";
background:url("${import2}")
}`;

上面给出的css-loader导出结果是经过简化过的,实际上css-loader会导出更复杂的结果,但是核心思想还是这个

总结一下,css-loader做了两件事

  1. 将css文件的内容作为字符串导出

  2. 将css中的其他依赖作为require导入,以便webpack分析依赖

    style-loader

Inject CSS into the DOM.

style-loader会把经过css-loader导出的结果经过处理后插入到页面上

例如:

1
2
3
.red{
color:"#f40";
}

经过css-loader转换后变成js代码:

1
2
3
module.exports = `.red{
color:"#f40";
}`

经过style-loader转换后变成:

1
2
3
4
5
6
7
8
module.exports = `.red{
color:"#f40";
}`
var style = module.exports;
var styleElem = document.createElement("style");
styleElem.innerHTML = style;
document.head.appendChild(styleElem);
module.exports = {}

style-loader的处理结果也是简化过的,实际上处理远比比这个复杂

使用mini-css-extract-plugin提取css文件

目前,css代码被css-loader转换后,交给的是style-loader进行处理。

style-loader使用的方式是用一段js代码,将样式加入到style元素中。

而实际的开发中,我们往往希望依赖的样式最终形成一个css文件。

这时候我们可以使用mini-css-extract-plugin这个库

该库提供了1个plugin和1个loader

  • plugin:负责生成css文件
  • loader:负责记录要生成的css文件的内容,同时导出开启css-module后的样式对象

安装mini-css-extract-plugin

1
npm install mini-css-extract-plugin -D

使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const MiniCssExtractPlugin = require("mini-css-extract-plugin")
module.exports = {
module: {
rules: [
{
test: /\.css$/, use: [MiniCssExtractPlugin.loader, "css-loader"]
}
]
},
plugins: [
new MiniCssExtractPlugin({
// 配置生成的文件名
filename: "css/[name].[contenthash:5].css"
})
]
}

完整的webpack.config.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
const path = require("path");
const {CleanWebpackPlugin} = require("clean-webpack-plugin");
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const MiniCssExtractPlugin = require("mini-css-extract-plugin")
const webpack = require("webpack");
module.exports = {
entry: {
index: "./index.js"
},
mode: "development",
devtool: "source-map",
output: {
path: path.resolve(__dirname, "dist"),
filename: "js/[name].[chunkhash:5].js",
publicPath: "/"
},
devServer: {
contentBase: path.resolve(__dirname, 'dist'),
port: 9000,
open : true,
},
context: path.resolve(__dirname, "src"),
module: {
rules: [
{
test: /\.css$/,
use: [MiniCssExtractPlugin.loader, "css-loader"]
},
{
test: /\.(png)|(gif)|(jpg)$/,
use: [{
loader: "url-loader",
options: {
limit: 10 * 1024,
name: "imgs/[name].[contenthash:5].[ext]"
}
}]
}],
noParse: /jquery/
},
plugins: [
new CleanWebpackPlugin(),
new HtmlWebpackPlugin({
template: "./public/index.html",
filename: "html/index.html",
chunks: ["index"]
}),
new CopyWebpackPlugin({
patterns: [
{
from: "./assets",
to: "./assets"
},
],
}),
new webpack.ProvidePlugin({
$ : 'jquery'
}),
new MiniCssExtractPlugin({
filename: "css/[name].[contenthash:5].css"
})
],
resolve: {
alias: {
"@": path.resolve(__dirname, 'src')
}},
externals: {
jquery : "$"
}
}

运行命令

1
npm run build

打包成功

image-20210113133605920

如果你出现了下面的问题

image-20210113141728692

这是webpack版本的问题,安装mini-css-extract-plugin时我看到有这个东西

image-20210113133427117

嘛,于是我Webpack更新到5.13.0问题就解决了

index.css的内容

1
2
3
4
5
6
:root, body {
height: 100%;
}
body {
background-image: url("./assets/bg.jpg");
}

打包结果

image-20210113140331318

html/index.html

1
2
3
4
5
6
7
8
9
10
11
12
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.5.1/jquery.js"></script>
<script defer src="/js/index.473ec.js"></script>
<link href="/css/index.8d0be.css" rel="stylesheet"></head>
<body>
<img src="../assets/img.png">
</body>
</html>

可以看到,打包后的html通过link标签引入css文件,而要引入哪个模块的css文件,可以在HtmlWebpackPlugin的chunks里进行配置

开启本地服务器

image-20210113141547186

页面正常运行

命名约定

这里我们使用BEM来举例

BEM是一套针对css类样式的命名方法。

其他命名方法还有:OOCSS、AMCSS、SMACSS等等

BEM全称是:Block Element Modifier

三个部分的具体含义为:

  • Block:页面中的大区域,表示最顶级的划分,例如:轮播图(banner)、布局(layout)、文章(article)等等
  • element:区域中的组成部分,例如:轮播图中的横幅图片(banner__img)、轮播图中的容器(banner__container)、布局中的头部(layout__header)、文章中的标题(article_title)
  • modifier:可选。通常表示状态,例如:处于展开状态的布局左边栏(layout__left_expand)、处于选中状态的轮播图小圆点(banner__dot_selected)

例如:banner__dot_selected,可以表示:轮播图中,处于选中状态的小圆点

另外,在某些大型工程中,如果使用BEM命名法,还可能会增加一个前缀,来表示类名的用途,常见的前缀有:

  • l: layout,表示这个样式是用于布局的
  • c: component,表示这个样式是一个组件,即一个功能区域
  • u: util,表示这个样式是一个通用的、工具性质的样式
  • j: javascript,表示这个样式没有实际意义,是专门提供给js获取元素使用的

CSS in JS

css in js简单来说就是:用一个JS对象来描述样式

比如下面的对象就描述了一组样式

1
2
3
4
5
6
7
const styles = {
backgroundColor: "#f40",
color: "#fff",
width: "400px",
height: "500px",
margin: "0 auto"
}

由于这种描述样式的方式根本就不存在类名,自然不会有类名冲突

至于如何把样式应用到界面上,不是它所关心的事情,你可以用任何技术、任何框架、任何方式将它应用到界面。

举个例子

修改index.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.5.1/jquery.js"></script>
</head>
<body>
<img src="../assets/img.png">
<div id="app">
app
</div>
</body>
</html>

修改index.js

1
2
3
4
5
6
7
8
9
10
11
import css from "./style/index.css";
import {applyStyles} from "@/util";

const styles = {
width: "400px",
height: "500px",
margin: "0 auto",
background : "#2083e5"
}
let app = document.querySelector("#app");
applyStyles(app, styles);

util/index.js中添加下面的代码

1
2
3
4
5
6
7
8
9
10
11
12
function applyStyles(dom, ...styles) {
let targetStyles = {};
for (const style of styles) {
targetStyles = {
...targetStyles,
...style
}
}
Object.keys(targetStyles).forEach((key) => {
dom.style[key] = targetStyles[key];
});
}

这样我们就可以把用对象描述的css应用到页面上了

你也可以使用jquery,代码会更简单

1
2
3
4
5
6
7
8
const styles = {
width: "400px",
height: "500px",
margin: "0 auto",
background : "#2083e5"
};

$("#app").css(styles);

css module

css module 遵循以下思路解决类名冲突问题:

  1. css的类名冲突往往发生在大型项目中
  2. 大型项目往往会使用构建工具(webpack等)搭建工程
  3. 构建工具允许将css样式切分为更加精细的模块
  4. 同JS的变量一样,每个css模块文件中难以出现冲突的类名,冲突的类名往往发生在不同的css模块文件中
  5. 只需要保证构建工具在合并样式代码后不会出现类名冲突即可

因此,css module引入了局部作用域这个概念,让css样式只能在对应的模块中生效

我们来试一下,修改webpack.config.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
module.exports = {
module: {
rules: [
{
test: /\.css$/,
use: [MiniCssExtractPlugin.loader, {
loader: "css-loader",
options: {
// 开启css-module
modules : true
}
}]
}
],
}
}

新建component1.css

1
2
3
4
5
.box {
/* component1 box*/
background: blueviolet;
height: 30px;
}

新建component2.css

1
2
3
4
5
.box {
/* component2 box */
background: brown;
height: 50px;
}

修改index.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.5.1/jquery.js"></script>
</head>
<body>
<img src="../assets/img.png">
<div class="component1">
<div class="box"></div>
</div>
<div class="component2">
<div class="box"></div>
</div>
</body>
</html>

我们假设component1和component2是两个不同的组件,我们这两个组件的样式是隔离的

在index.js中导入

1
2
3
import "./style/index.css";
import "./style/component1.css"
import "./style/component2.css";

重新打包,查看打包结果

image-20210113170426461

可以看到,最终的代码文件中,不再使用box这个类名,而是分别变成了两个值,这就是css-module的实现局部作用域的方法,css-loader会将样式中的类名进行转换,根据模块路径类名生成一个hash,然后把这个类名进行重命名,因此,不同的css模块,哪怕具有相同的类名,转换后的hash值也不一样。

2020-01-31-14-04-11

但是问题就来了,源代码的类名和最终生成的类名是不一样的,而开发者只知道自己写的源代码中的类名,并不知道最终的类名是什么,那我们必须要想办法把最终的类名应用到元素上

css-loader会导出一个包含了描述原类名和最终类名对应关系的对象(导出的对象.locals就是我们要的对象了)

2020-01-31-14-08-49

index.js中的打印一下导出结果(因为MiniCssExtractPlugin会把css-loader导出的对象.locals重新导出,所以这里可以打印)

1
2
3
4
5
6
import index from "./style/index.css";
import style1 from "./style/component1.css"
import style2 from "./style/component2.css";

console.log(style1);
console.log(style2);

image-20210113171628011

我们修改一下index.js

1
2
3
4
5
6
7
8
import index from "./style/index.css";
import style1 from "./style/component1.css"
import style2 from "./style/component2.css";

console.log(style1);
console.log(style2);
$(".component1 .box").addClass(style1.box);
$(".component2 .box").addClass(style2.box);

这样就可以应用了

image-20210113171958206

全局类名

某些类名是全局的、静态的,不需要进行转换,仅需要在类名位置使用一个特殊的语法即可:

1
2
3
:global(.main){
...
}

使用了global的类名不会进行转换,相反的,没有使用global的类名,表示默认使用了local

1
2
3
:local(.main) {
...
}

使用了local的类名表示局部类名,是可能会造成冲突的类名,会被css module进行转换

配置类名

如果你想控制最后输出的类名,可以配置localIdentName这个选项

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
module.exports = {
module: {
rules: [
{
test: /\.css$/,
use: [MiniCssExtractPlugin.loader, {
loader: "css-loader",
options: {
// 开启css-module
modules : {
localIdentName: "[local]-[hash:5]"
}
}
}]
}
],
}
}

打包后代码就会变成这样

1
2
3
4
5
6
7
8
9
10
11
.box-36382 {
/* component1 box*/
background: blueviolet;
height: 30px;
}

.box-95cce {
/* component2 box */
background: brown;
height: 50px;
}

tips

  • css module往往配合构建工具使用
  • css module仅处理顶级类名(第一个类名),尽量不要书写嵌套的类名,也没有这个必要
  • css module仅处理类名,不处理其他选择器
  • css module还会处理id选择器
  • 使用了css module后,只要能做到让类名望文知意即可,不需要遵守其他任何的命名规范

ps

事实上,如果你在一些框架里使用css-module,就会方便许多,比如React

预处理语言

编写css时,受限于css语言本身,常常难以处理一些问题:

  • 重复的样式值:例如常用颜色、常用尺寸
  • 重复的代码段:例如绝对定位居中、清除浮动
  • 重复的嵌套书写

由此,预处理语言出现了,我们可以直接书写预处理语言来书写代码,虽然预处理语言的代码不能被浏览器直接识别,但通过一个编译器,就可以把预处理语言转化成css

2020-02-03-11-50-05

这里我们介绍less和scss

less官网:http://lesscss.org/
less中文文档1(非官方):http://lesscss.cn/
less中文文档2(非官方):https://less.bootcss.com/
sass官网:https://sass-lang.com/
sass中文文档1(非官方):https://www.sass.hk/
sass中文文档2(非官方):https://sass.bootcss.com/

安装less和scss

首先安装less和scss

1
npm install sass less -D

然后新建两个文件less-style.lessscss-style.scss

less-style.less

1
2
3
4
@base-color : #c6538c;
.wrapper {
border: 1px solid @base-color;
}

scss-style.scss

1
2
3
4
$base-color : #c6538c;
.wrapper {
border: 1px solid $base-color;
}

转化less

1
lessc ./src/style/less-style.less ./src/style/less-style.css

转化scss

1
sass ./src/style/scss-style.scss ./src/style/scss-style.css

转化结果

image-20210114001050839

最简单的用法就是这样了,但我们希望能在开发时直接书写预处理语言的文件,最后打包时webpack帮我们转化成css,我们就要进行相应的配置了

在webpack中使用

我们要先安装相应的loader

1
npm install less-loader sass-loader -D

webpack.config.js中导入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
const cssLoaderConfig = {
loader: "css-loader",
options: {
modules : {
localIdentName: "[local]-[hash:5]"
}
}
};
module.exports = {
module: {
rules: [
{
test: /\.css$/,
use: [MiniCssExtractPlugin.loader, cssLoaderConfig]
},
{
test: /\.s[ac]ss$/i,
use: [
MiniCssExtractPlugin.loader,
cssLoaderConfig,
"sass-loader",
]
},
{
test: /\.less$/i,
use: [
MiniCssExtractPlugin.loader,
cssLoaderConfig,
"less-loader",
]
},
{
test: /\.(png)|(gif)|(jpg)$/,
use: [{
loader: "url-loader",
options: {
limit: 10 * 1024,
name: "imgs/[name].[contenthash:5].[ext]"
}
}]
}],
noParse: /jquery/
},
}

打包结果

image-20210114004300434

使用就是这么简单,具体的语法可以去看官方文档,还是挺详细的,而且不需要英文,康康代码就能看懂

PostCSS

官网地址:https://postcss.org/
github地址:https://github.com/postcss/postcss
Api:http://api.postcss.org/

PostCSS是一个代码编译器,可以把你写的代码转化成最终的css代码

听起来是不是有点像sass这样的预处理语言,他们的功能其实有相同之处,不过PostCSS是一个编译器,只负责把源代码转化成AST语法树,然后调用插件处理AST树,最后再把AST语法树转化回代码,简单来说就是,PostCSS只负责代码转化,具体的功能需要单独的插件来使用,你可以自由选择插件,甚至可以自己写一个,sass如同定义,是一个语言,虽然最后要被编译成CSS才能执行

放个图吧

postcss-workflow

这就是PostCSS的工作流,Parser负责把CSS转化成AST,Stringifier负责把AST重新转化为CSS

安装

PostCss是基于node编写的,因此可以使用npm安装

安装postcss和cli工具

1
npm install postcss postcss-cli -D

postcss库提供了对应的Api用于转换代码,postcss-cli允许我们通过命令行调用postcss中的api来完成编译

1
postcss 源文件路径 -o 输出路径

比如

1
postcss ./src/style/postcss-style.pcss -o ./src/style/postcss-style.css

输出结果

image-20210114123558515

配置文件

单纯使用postcss没有意义,因为postcss转化代码需要插件,在执行上面的命令时,postcss给了我们一个提示

1
2
You did not set any plugins, parser, or stringifier. Right now, PostCSS does nothing. Pick plugins for your case on https://www.postcss.parts
/ and use them in postcss.config.js.

所以我们要弄点插件进去,那么要弄插件自然要先有个配置文件,postcss的配置文件默认名称是postcss.config.js

我们新建一个postcss.config.js文件,加入一些配置

1
2
3
4
5
6
module.exports = {
map: false, //关闭source-map,
plugins: {

}
}

使用插件

postcss-preset-env

过去使用postcss的时候,往往会使用大量的插件,它们各自解决一些问题

这样导致的结果是安装插件、配置插件都特别的繁琐

于是出现了这么一个插件postcss-preset-env,它称之为postcss预设环境,大意就是它整合了很多的常用插件到一起,并帮你完成了基本的配置,你只需要安装它一个插件,就相当于安装了很多插件了。

安装插件

1
cnpm install postcss-preset-env -D

安装好该插件后,在postcss配置中加入下面的配置

1
2
3
4
5
6
7
module.exports = {
map : false, // 关闭source-map,
plugins : {
// 括号中可以写插件的配置
"postcss-preset-env" : {}
}
}
自动补全浏览器前缀

某些新的css样式需要在旧版本浏览器中使用厂商前缀方可实现

例如

1
2
3
::placeholder {
color: red;
}

该功能在不同的旧版本浏览器中需要书写为

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
::-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内部包含了该库,自动有了该功能。

我们再次运行一次命令,可以看到输出的文件已经补全了前缀

image-20210114130437753

如果需要调整兼容的浏览器范围,可以通过下面的方式进行配置

方式1:在postcss-preset-env的配置中加入browsers

1
2
3
4
5
6
7
8
9
10
module.exports = {
plugins: {
"postcss-preset-env": {
browsers: [
"last 2 version",
"> 1%"
]
}
}
}

方式2:添加 .browserslistrc 文件

这个方式比较推荐,因为这个文件,在以后需要做其他的浏览器兼容性处理时,其他插件也会读取这个文件,比如babel也会读取这个文件

创建文件.browserslistrc,填写配置内容

1
2
last 2 version
> 1%

方式3:在package.json的配置中加入browserslist

这个方式也是比较推荐的,理由同上

1
2
3
4
"browserslist": [
"last 2 version",
"> 1%"
]

browserslist是一个多行的(数组形式的)标准字符串。

它的书写规范多而繁琐,详情见:https://github.com/browserslist/browserslist

一般情况下,大部分网站都使用下面的格式进行书写

1
2
3
last 2 version
> 1% in CN
not ie <= 8
  • last 2 version: 浏览器的兼容最近期的两个版本
  • > 1% in CN: 匹配中国大于1%的人使用的浏览器, in CN可省略
  • not ie <= 8: 排除掉版本号小于等于8的IE浏览器

默认情况下,匹配的结果求的是并集

你可以通过网站:https://browserl.ist/ 对配置结果覆盖的浏览器进行查询,查询时,多行之间使用英文逗号分割

browserlist的数据来自于CanIUse网站,由于数据并非实时的,所以不会特别准确

未来的CSS语法

CSS的某些前沿语法正在制定过程中,没有形成真正的标准,如果希望使用这部分语法,为了浏览器兼容性,需要进行编译。过去,完成该语法编译的是cssnext库,不过有了postcss-preset-env后,它自动包含了该功能。

你可以通过postcss-preset-envstage配置,告知postcss-preset-env需要对哪个阶段的css语法进行兼容处理,它的默认值为2

1
2
3
"postcss-preset-env": {
stage: 0
}

一共有5个阶段可配置:

  • Stage 0: Aspirational - 只是一个早期草案,极其不稳定
  • Stage 1: Experimental - 仍然极其不稳定,但是提议已被W3C公认
  • Stage 2: Allowable - 虽然还是不稳定,但已经可以使用了
  • Stage 3: Embraced - 比较稳定,可能将来会发生一些小的变化,它即将成为最终的标准
  • Stage 4: Standardized - 所有主流浏览器都应该支持的W3C标准

下面我们来试一下

1
2
3
4
5
6
7
8
9
:root {
--lightColor: #ddd;
--darkColor: #333;
}

a {
color: var(--lightColor);
background: var(--darkColor);
}

编译后

1
2
3
4
5
6
7
8
9
10
11
:root{
--lightColor: #ddd;
--darkColor: #333;
}

a {
color: #ddd;
color: var(--lightColor);
background: #333;
background: var(--darkColor);
}

编译后,仍然可以看到原语法,因为某些新语法的存在并不会影响浏览器的渲染,尽管浏览器可能不认识
如果不希望在结果中看到新语法,可以配置postcss-preset-envpreservefalse

postcss-apply

安装

1
npm install postcss-apply -D

该插件可以支持在css中书写属性集

类似于LESS中的混入,可以利用CSS的新语法定义一个CSS代码片段,然后在需要的时候应用它

1
2
3
4
5
6
7
8
9
10
11
12
:root {
--center: {
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
};
}

.item{
@apply --center;
}

编译后

1
2
3
4
5
6
7
.item{
position: absolute;
left: 50%;
top: 50%;
-webkit-transform: translate(-50%, -50%);
transform: translate(-50%, -50%);
}

postcss-color-function

安装

1
npm install postcss-color-function -D

该插件支持在源码中使用一些颜色函数

1
2
3
4
5
6
7
8
9
10
11
12
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%));
}

编译后

1
2
3
4
5
6
7
8
9
10
11
12
body {
/* 使用颜色#aabbcc,不做任何处理,等同于直接书写 #aabbcc */
color: rgb(170, 187, 204);
/* 将颜色#aabbcc透明度设置为90% */
color: rgba(170, 187, 204, 0.9);
/* 将颜色#aabbcc的红色部分设置为90% */
color: rgb(230, 187, 204);
/* 将颜色#aabbcc调亮50%(更加趋近于白色),类似于less中的lighten函数 */
color: rgb(213, 221, 230);
/* 将颜色#aabbcc调暗50%(更加趋近于黑色),类似于less中的darken函数 */
color: rgb(85, 94, 102);
}

postcss-import

该插件可以让你在postcss文件中导入其他样式代码,通过该插件可以将它们合并

安装

1
npm install postcss-import -D

应用

1
2
3
4
5
6
7
8
9
10
11
12
module.exports = {
map : false,
plugins : {
"postcss-preset-env" : {
stage : 0,
preserve : false
},
"postcss-apply" : {},
"postcss-color-function" : {},
"postcss-import" : {}
}
}

在Webpack中使用

安装postcss-loader

1
npm install postcss-loader -D

配置

1
2
3
4
5
6
7
8
9
10
11
12
module.exports = {
module : {
{
test: /\.pcss$/i,
use: [
MiniCssExtractPlugin.loader,
cssLoaderConfig,
"postcss-loader",
]
}
}
}

我们把postcss-loader加到css-loader前,postcss-loader会调用插件处理源代码,然后再把生成的css代码传给css-loader

用文档的原话:

After setting up your postcss.config.js, add postcss-loader to your webpack.config.js. You can use it standalone or in conjunction with css-loader (recommended).

Use it before css-loader and style-loader, but after other preprocessor loaders like e.g sass|less|stylus-loader, if you use any (since webpack loaders evaluate right to left/bottom to top).

所以如果你要给在sass里用,把postcss-loader放到sass-loader前面就可以了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
const path = require("path");
const {CleanWebpackPlugin} = require("clean-webpack-plugin");
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const MiniCssExtractPlugin = require("mini-css-extract-plugin")
const webpack = require("webpack");
const cssLoaderConfig = {
loader: "css-loader",
options: {
modules : {
localIdentName: "[local]-[hash:5]"
}
}
};
module.exports = {
// 修改入口路径
entry: {
index: "./index.js"
},
mode: "development",
// devtool: "source-map",
output: {
path: path.resolve(__dirname, "dist"),
filename: "js/[name].[chunkhash:5].js",
publicPath: "/"
},
devServer: {
contentBase: path.resolve(__dirname, 'dist'),
port: 9000,
open : true,
},
context: path.resolve(__dirname, "src"),
module: {
rules: [
{
test: /\.s[ac]ss$/i,
use: [
MiniCssExtractPlugin.loader,
cssLoaderConfig,
"postcss-loader",
"sass-loader",
]
},
{
test: /\.less$/i,
use: [
MiniCssExtractPlugin.loader,
cssLoaderConfig,
"postcss-loader",
"less-loader",
]
},
{
test: /\.p?css$/i,
use: [
MiniCssExtractPlugin.loader,
cssLoaderConfig,
"postcss-loader",
]
},
{
test: /\.(png)|(gif)|(jpg)$/,
use: [{
loader: "url-loader",
options: {
limit: 10 * 1024,
name: "imgs/[name].[contenthash:5].[ext]"
}
}]
}],
noParse: /jquery/
},
plugins: [
new CleanWebpackPlugin(),
new HtmlWebpackPlugin({
template: "./public/index.html",
filename: "html/index.html",
chunks: ["index"]
}),
new CopyWebpackPlugin({
patterns: [
{
from: "./assets",
to: "./assets"
},
],
}),
new webpack.ProvidePlugin({
$ : 'jquery'
}),
new MiniCssExtractPlugin({
filename: "css/[name].[contenthash:5].css"
})
],
resolve: {
alias: {
"@": path.resolve(__dirname, 'src')
}},
externals: {
jquery : "$"
}
}

后记

啊,CSS的部分就这样告一段落了,下一篇我们看看针对JS的基本配置,不咕的话,明天或者后天写完