CSS工程化主要包括 组织,优化,构建和维护。这里主要介绍两个工具:PostCSS和
PostCSS
post是 后面,后置 的意思,它跟预处理器的概念是相对的。预处理器是把不是css的语言 经过处理后 变成css,也就是先有预处理语言再有css。postcss是把css变成css,是先有css再对代码进行处理。
这里需要说明的是,前面提到postcss是先有css然后postcss再处理转换变成新的css,这个过程现在来讲并不完全准确,postcss官方也有澄清,现在和预处理语言本质上没有很大的区别,都会做一些代码解析转换的工作。
postcss本身只有解析能力,就是一段css代码它会解析成一个结构化的css,它会告诉你这个地方有个选择器,选择器里面有一些样式,样式里面又有一些属性和属性值,它本身只有解析的能力。那些变换的能力(把一段css变成另一段css)都来自插件,各种神奇的特性全靠插件,目前至少有200多个插件。常用的插件有:
- import 这个插件主要做模块合并。css里面虽然自带import语句,但是css本身的import在加载时会一个个文件去加载,如果我们在构建时把这些需要一个个加载的文件提前合并就可以减少http请求,提高加载性能。
- autoprefixier 自动添加前缀。比如写
box-shadow
,它会自动帮你添加-webkit-
,-moz-
前缀。 - cssnano 用来压缩css代码。它不仅仅是把代码压缩成一行,它还会分析代码是否有无用的代码和重复或者可以简写的属性,它的会帮你简写和压缩代码。
- cssnext 它让你可以使用css新特性。css规范会不断提出新特性,但是浏览器支持不会那么快,有些新特性通过cssnext编译就会在浏览器中使用。
- precss 命名和postcss是相对的,前面提过,不管是post还是pre,不管是后处理器还是预处理器,现在只是名字不同而已。它也提供了像预处理一样有的 变量、mixin、循环等。
postcss的使用
postcss有很多使用方式,可以和各种构件工具相结合,它自己也有一个cli工具。
先使用postcss自己的cli工具:首先安装postcss-cli,在新建文件夹中运行npm install postcss-cli
。
目录结构如下:
01-postcss.css代码:
*{
padding:0;
margin:0;
}
.box{
box-shadow: 0 0 3px rgba(255,255,255,.3);
}
当前目录中运行./node_modules/.bin/postcss 01-postcss.css -o build/01-postcss.css
后输出
生成的代码:
*{
padding:0;
margin:0;
}
.box{
box-shadow: 0 0 3px rgba(255,255,255,.3);
}
生成的文件跟我们编写的源码一样,没有任何改动。这是因为postcss本身没有处理代码的能力,它只是把css代码解析出来。
现在来借助autoprefixer插件为代码添加前缀,首先安装autoprefixer插件npm install autoprefixer
,接着新建 postcss.config.js文件。
postcss.config.js文件:
const autoprefixer = require('autoprefixer');
module.exports = {
plugins: [
autoprefixer({
overrideBrowserslist:['last 2 versions']
})
]
}
运行./node_modules/.bin/postcss 02-plugin-main.css -o build/02-plugin-main.css
后在build目录下生成新的02-plugin-main.css文件,对比编译前后的代码:多个可前缀。
在使用postcss-import插件之前有两个文件:
此时的postcss.config.js配置如下:
const autoprefixer = require('autoprefixer');
// const atImport = require('postcss-import');
module.exports = {
plugins: [
// atImport,
autoprefixer({
overrideBrowserslist:['last 2 versions']
})
]
}
02-plugin-module.css文件:
*{
padding:0;
margin:0;
}
02-plugin-main.css文件:
@import "./02-plugin-module.css";
.box{
box-shadow: 0 0 3px rgba(255,255,255,.3);
}
编译代码后生成:
02-plugin-main.css文件:
@import "./02-plugin-module.css";
.box{
-webkit-box-shadow: 0 0 3px rgba(255,255,255,.3);
box-shadow: 0 0 3px rgba(255,255,255,.3);
}
使用postcss-import插件之后重新编译(使用前需要先安装npm install postcss-import
):
此时的postcss.config.js配置如下:
const autoprefixer = require('autoprefixer');
const atImport = require('postcss-import');
module.exports = {
plugins: [
atImport,
autoprefixer({
overrideBrowserslist:['last 2 versions']
})
]
}
运行./node_modules/.bin/postcss 02-plugin-main.css -o build/02-plugin-main.css
重新编译后的02-plugin-main.css文件:
*{
padding:0;
margin:0;
}
.box{
-webkit-box-shadow: 0 0 3px rgba(255,255,255,.3);
box-shadow: 0 0 3px rgba(255,255,255,.3);
}
postcss-import插件会把import的css内联进来。
接着使用一下cssnano插件(使用前需要先安装npm install cssnano
),在配置项中cssnano要放在最后,因为在前面会把css代码进行处理,处理完后才进行压缩。
此时postcss.config.js文件:
const autoprefixer = require('autoprefixer');
const atImport = require('postcss-import');
const cssnano = require('cssnano');
module.exports = {
plugins: [
atImport,
autoprefixer({
overrideBrowserslist:['last 2 versions']
}),
cssnano
]
}
02-plugin-module.css文件:
*{
padding:0;
margin:0;
}
body{
margin:10px 20px 10px 20px;
font-size:12px;
}
body{
background: red;
}
运行./node_modules/.bin/postcss 02-plugin-module.css -o build/02-plugin-module.css
后生成的build/02-plugin-module.css文件:
*{padding:0;margin:0}body{margin:10px 20px;font-size:12px;background:red}
可以看到cssnano在很用力的压缩整个代码。
以上是简单的插件使用,可以做到举一反三。
接着介绍稍微大点的插件,它们会有自己的一套语法系统。
cssnext可以把规范中已经提出来的或者已经定稿的但浏览器还没有实现的样式经过编译后浏览器能识别的样式,下图中是它的一些特性:
postcss.config.js文件:
const autoprefixer = require('autoprefixer');
const atImport = require('postcss-import');
const cssnano = require('cssnano');
const cssnext = require('postcss-cssnext');
module.exports = {
plugins: [
// atImport,
cssnext,
// autoprefixer({
// overrideBrowserslist:['last 2 versions']
// }),
// cssnano
]
}
03-cssnext.css文件:
:root{
--mainColor:red;
--danger-theme:{
color:white;
background-color:red;
}
}
a{
color: var(--mainColor);
}
.danger{
@apply --danger-theme;
}
运行./node_modules/.bin/postcss 03-cssnext.css -o build/03-cssnext.css
后生成build/03-cssnext.css文件:
a{
color: red;
}
.danger{
color:white;
background-color:red;
}
接着使用precss插件(使用前需要先安装npm install precss
),这个插件很像预处理器,所以叫precss。它有以下css功能:
- 变量
- 条件(if)
- 循环
- Mixin Extend
- import
- 属性值引用
修改postcss.config.js配置:
const autoprefixer = require('autoprefixer');
const atImport = require('postcss-import');
const cssnano = require('cssnano');
const cssnext = require('postcss-cssnext');
const precss = require('precss');
module.exports = {
plugins: [
// atImport,
precss,
// autoprefixer({
// overrideBrowserslist:['last 2 versions']
// }),
// cssnano
]
}
04-precss.css文件:
$blue:#056ef0;
$column:200px;
.menu{
width:calc(4 * $column);
}
.menu_link{
background: $blue;
width:$column;
}
.notice--clear{
@if 3< 5 {
background:green;
}
@else{
background:pink;
}
}
@for $i from 1 to 3 {
.b-$i{
width:$(i)px;
}
}
运行./node_modules/.bin/postcss 04-precss.css -o build/04-precss.css
后编译生成build/04-precss.css文件:
.menu{
width:calc(4 * 200px);
}
.menu_link{
background: #056ef0;
width:200px;
}
.notice--clear{
background:pink
}
.b-1{
width:1px;
}
.b-2{
width:2px;
}
.b-3{
width:3px;
}
使用起来和预处理器很像,项目中是使用预处理器还是precss都看个人选择。这样可以看出预处理器和postcss某些功能是重合的,它们都是css处理工具。
postcss作为css处理工具,它本身并不擅长构建工作,因此项目中会选择其它构建工具搭配使用,很多流行的构建工具流程中会使用postcss,除了前面介绍的postcss-cli,也就是postcss自带的cli命令行工具,它还可以和 webpack,Gulp,Grunt,Rollup等构建工具相结合,也就是不管使用什么样的构建工具,我们都可以很方便的把postcss集成进去。这些构建工具都有对应的postcss的插件:
gulp构建工具
gulp是一款非常流行的构建任务管理工具,它本身几乎没有构建功能,它的构建功能全靠插件。gulp本身提供的功能是 提供文件的输入和输出,然后使用流将各个插件串起来,主要是任务调度工具。
这里就没有再使用postcss命令行,也没有指定postcss配置文件,直接使用gulp进行构建。
首先需要安装gulp和gulp-postcss:npm install gulp gulp-postcss
,接着新建gulp的配置文件gulpfile.js:
const gulp = require('gulp');
const autoprefixer = require('autoprefixer');
const cssnano = require('cssnano');
const atImport = require('postcss-import');
gulp.task('postcss',function(){
var postcss = require('gulp-postcss');
return gulp.src('02-plugin-main.css')
.pipe(postcss([
atImport,
autoprefixer({
overrideBrowserslist:['last 2 versions']
}),
cssnano
]))
.pipe(gulp.dest('build/'));
});
02-plugin-main.css文件:
@import "./02-plugin-module.css";
.box{
box-shadow: 0 0 3px rgba(255,255,255,.3);
}
02-plugin-module.css文件:
*{
padding:0;
margin:0;
}
body{
margin:10px 20px 10px 20px;
font-size:12px;
}
body{
background: red;
}
运行./node_modules/.bin/gulp postcss
(这里加上了gulp局部安装的路径,全局安装的话直接运行gulp postcss
即可)编译后生成新的build/02-plugin-main.css文件:
*{padding:0;margin:0}body{margin:10px 20px;font-size:12px;background:red}.box{-webkit-box-shadow:0 0 3px hsla(0,0%,100%,.3);box-shadow:0 0 3px hsla(0,0%,100%,.3)}
webpack构建工具
接着来看看另外一个工程化利器webpack,webpack的核心思想是 JS是整个应用的核心入口。我们之前所有的web应用入口是html,html里面的js,css之间的关系,包括图片的应用,全部是在html里面完成的。但是webpack给了我们新的思路:JS是整个应用的核心入口,一切资源均由JS管理依赖,一切资源均由webpack打包。
举个小栗子:首先是webpack的基本应用,webpack被提到最多的是模块化打包工具,也就是 它首先解决的是js的模块化问题。es6现在支持模块化,但并没有被浏览器广泛的支持,所以需要一些工具来编译打包,webpack可以做到这些。
下面有三个文件相互引用:
05-webpack-main.js文件:
const module = require('05-webpack-module.js');
module.say();
05-webpack-module.js文件:
module.exports = {
say:function(){
console.log("hello,I'm a module");
}
}
05-webpack-main.html文件引用的js是webpack编译生成的js:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<script src="build/05-webpack-main.js"></script>
</head>
<body>
</body>
</html>
首先安装webpack:npm install webpack
,接着运行./node_modules/.bin/webpack 05-webpack-main.js -o build/05-webpack-main.js
。
会在build目录下生成一个05-webpack-main.js文件,接着在浏览器中浏览页面会打印出hello,I'm a module
。
在webpack中js是整个应用的核心入口,那css的引用自然也是通过js来完成。
06-webpack-main.js文件:
require('./06-webpack-css.css');
console.log('happychen');
06-webpack-css.css文件:
body{
background: pink;
}
运行./node_modules/.bin/webpack 06-webpack-main.js -o build/06-webpack-main.js
对06-webpack-main.js文件进行编译打包会报错:
通过require引用webpack会认为是一个js模块,但这里require引用的是css模块,是css语法所以会报错。
在webpack中引入css模块需要使用webpack的loader机制(即为指定的后缀或者指定的文件提供处理工具,像css这种文件需要先进行处理,处理完后再交给webpack,webpack是处理js的,所以需要先把css处理成js的工具,cssloader可以做到这点。)
所以需要先安装cssloader(npm install css-loader
),后面需要把样式注入到页面中,所以还需要安装style-loader(npm install style-loader
)。这两个都安装完后还需要配置文件(webpack.config.js)来指定我们需要这两个loader。
webpack.config.js文件:
module.exports = {
module:{
rules:[{
test:/\.css$/,
use:['style-loader','css-loader']
}]
}
}
再重新运行./node_modules/.bin/webpack 06-webpack-main.js -o build/06-webpack-main.js
打包成功生成build/06-webpack-main.js文件。
css-loader负责把css文件变成js文件,然后webpack进行引入处理;style-loader会把变成js后的css文件注入到页面head中。
下面举个相对完整的例子:
07-webpack-main.html代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<script src="build/07-webpack-main.js"></script>
</head>
<body>
<div id="component" class="component"></div>
</body>
</html>
div
容器里面是空的,它用来放我们自己的组件,它里面最终的内容由组件来决定。
07-webpack-main.js代码:
const component = require('./07-webpack-component');
document.addEventListener('DOMContentLoaded',function(){
component.init(document.querySelector('#component'));
})
这个组件引入了component组件,并且在页面初始化的时候去初始化这个组件,然后把div
容器传给component组件。
07-webpack-component.js代码:
require('./07-webpack-component.css');
exports.init = function($dom){
$dom.innerHTML = `
<p class="p">我是一个组件</p>
<p class="p red">我是红的</p>
<p class="p green">我是绿的</p>
`;
}
component组件代码就是向div
容器中添加代码,同时它还引入了css。这个组件中定义了结构和样式,在js中直接引用css。
07-webpack-component.css代码:
.p{
font-size:24px;
}
.red{
color: red;
}
.green{
color: green;
}
运行./node_modules/.bin/webpack 07-webpack-main.js -o build/07-webpack-main.js
构建上面的代码并在浏览器中打开html文件效果:
虽然组件定义了自己的样式,这个样式通过js注入到head区,这样性能不太好,需要等到js执行完后页面才有样式,而且还会有个问题是,这个组件的样式不是封闭式的,它可能会作用到别的组件。本来我们的样式时可以独立出来直接渲染的,接下来解决这两个问题。
webpack.config.js文件:
module.exports = {
module:{
rules:[{
test:/\.css$/,
use:['style-loader',{
loader:'css-loader',
options:{
modules:true
}
}]
}]
}
}
在webpack配置文件中修改css-loader,这样编译之后会把class名都改掉,运行./node_modules/.bin/webpack 07-webpack-main.js -o build/07-webpack-main.js
重新编译,查看html:
会发现样式都失效了,需要在组件中修改我们写的样式,css-loader会在引入css后返回一个样式表,修改07-webpack-component.js文件:
const styles = require('./07-webpack-component.css');
exports.init = function($dom){
$dom.innerHTML = `
<p class="${styles.p}">我是一个组件</p>
<p class="${styles.p} ${styles.red}">我是红的</p>
<p class="${styles.p} ${styles.green}">我是绿的</p>
`;
}
运行./node_modules/.bin/webpack 07-webpack-main.js -o build/07-webpack-main.js
重新编译,查看html:
这样就很好的解决了组件样式冲突问题。
修改07-webpack-component.js文件: 首先安装extract-text-webpack-plugin插件(npm i extract-text-webpack-plugin
),解决css通过js来渲染
const ExtractTextPlugin = require('extract-text-webpack-plugin');
module.exports = {
module:{
rules:[{
test:/\.css$/,
// use:['style-loader',{
// loader:'css-loader',
// options:{
// modules:true
// }
// }]
use:ExtractTextPlugin.extract({
fallback:'style-loader',
use:{
loader:'css-loader',
options:{
modules:true
}
}
})
}]
},
plugins:[
new ExtractTextPlugin('07-webpack-main.css')
]
}
这样就会从js中将css提取出来,提取出来的css并会产生新的文件07-webpack-main.css,运行./node_modules/.bin/webpack 07-webpack-main.js -o build/07-webpack-main.js
重新构建会输出以下文件:
会生成07-webpack-main.css文件,修改07-webpack-main.html文件引入这个css文件:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<link rel="stylesheet" href="build/07-webpack-main.css">
<script src="build/07-webpack-main.js"></script>
</head>
<body>
<div id="component" class="component"></div>
</body>
</html>
效果:
这样就不会影响加载性能。
webpack中的css总结:
- css-loader 将css变成 js
- style-loader 将js样式插入head
- ExtractTextPlugin 将css从js中提取出来
- css modules 解决css命名冲突问题
还有一些预处理器可以在webpack中使用,比如 less-loader,sass-loader