我爱编程

postcss学习

2018-05-25  本文已影响583人  li4065

安装

使用这种方式,项目的名词一定不要和某些库的名词冲突,比如叫webpack,postcss,如果叫这个名词安装相关库时就会报ENOSELF的错误

npm i --save-dev webpack webpack-cli

npm i --save-dev postcss

npm i --save-dev postcss-loader

npm i --save-dev babel-cli babel-preset-es2015 babel-loader

npm i --save-dev mini-css-extract-plugin

基本概念

预处理器是指对css能力增强的功能,添加一些一些本身不是css的功能(比如嵌套、变量),通过处理后能转成普通的CSS,

通过一些规则把已有的css进行完善,比如添加浏览器前缀

相关解析器

使用流程

src:源文件目录
dist:输出目录

const path = require('path');
const webpack = require('webpack');
const MiniCssExtractPlugin = require("mini-css-extract-plugin");

module.exports = {
    context: path.resolve(__dirname, 'src'),
    entry: {
        mjs: './main.js',
    },
    mode: 'development',
    output: {
        path: path.resolve(__dirname, 'dist'),
        filename: "[name].bundle.js"
    },
    module: {
        rules: [
            {test: /\.(js|jsx)$/, use: 'babel-loader'},
            {test: /\.css$/,
                use: [
                    MiniCssExtractPlugin.loader,
                    "css-loader"
                ]
            }
        ]
    },
    plugins: [
        new MiniCssExtractPlugin({
            // Options similar to the same options in webpackOptions.output
            // both options are optional
            filename: "[name].css",
            chunkFilename: "[id].css"
        })
    ]
};

mini-css-extract-plugin使用的例子

从js中分离css代码

// 在src文件夹下新建main.js,base.css,color.css内容如下
// main.js
import './base.css';
import './color.css';

const base = 'main.js';
...
// base.css
body {font-size: 12px}
...
// color.css
.red {color: red}

在package.json添加如下内容

 "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "build": "./node_modules/.bin/webpack --config webpack.config.js"
  },

运行npm run build,这时在dist目录下,就会看到mjs.bundle.js和mjs.css两个文件,打开mjs.css会发现里面内容就是base.css和color.css的内容

通过pcss中引入样式文件

参考上面的例子,这次我们从js把pcss的内容导出到css中

npm i --save-dev postcss-import

// 在webpack.config.js中做如下修改
{test: /\.pcss$/, use: [
    MiniCssExtractPlugin.loader,
    "css-loader",
    "postcss-loader"
]},
...
// 在src目录下新建mian.pcss
@import './base.css';
@import './color.css';
...
// main.js下做如下调整
// .pcss可以算是postcss专用格式
import './main.pcss';

const base = 'main.js';

这里修改添加一个对post的配置文件,要不运行时会报错,新建.postcssrc.js也可以叫postcss.config.js

// 暂时没有其他配置,可以把留空
module.exports = {
    plugins: []
}

运行后,发现mjs.css内容和之前打包后一致,通过这个例子我们在pcss可以import相关样式文件,下面我们安装一个常用的插件,来演示postcss的配置文件

npm i --save-dev autoprefixer

autoprefixer的参数,基本使用默认配置即可,这里只说browsers参数的设定,因为这关系到最终添加前缀的内容

// autoprefixer相关配置可以参考其官方文档
let postcssConfig = {};
postcssConfig.autoprefixer = {
    browsers: ['> 1%', 'ff 3']
}

module.exports = {
    plugins: postcssConfig
}
b {
    border-radius:5px;
}

编译运行后结果为

b {
    -webkit-border-radius:5px;
       -moz-border-radius:5px;
            border-radius:5px;
}

通过这个插件我们很便捷的添加了相关浏览器私有前缀,下面讲下如何设定添加前缀的范围。

autoprefixer的插件参数browsers是利用browserslist功能来决定是否需要添加某些浏览器前缀,在browserslist的文档里我们可以找到详细设定,可以设定针对某些浏览器,或者针对国家,或者针对指定平台,下面列举下在项目中最有可能碰到的浏览器

browsers接收的是一个数组,所以可以像例子中那样分开设置,下面的话的意思就是为了适配安卓2.3,ios3.2,Safari3.1,IE10浏览器要添加相关前缀

'> 0%','Android 2.3','iOS 3.2','Safari 3.1','IE 10'

至于 > 0%是指当你不想像上面设置那么繁琐的指定浏览器时,可以直接指定个大概,就是我要支持市面上多少多少比例的浏览器,这个数字前面可以添加普通的运算符 > >= < <=

除此之外还可以加前缀修饰符not表示不在某个范围中,此外还可以使用cover extends或者since进行更细化的设置,包括道指定从哪年开始的什么版本。。。感觉把细化的有点恐怖了,就不去深究更细的设置了,基本使用上面的浏览器标识、大概覆盖范围、和特意排除某个浏览器就能完成基本的设置

如果想去掉关于firefox浏览器的设置,可以先修改支持范围,因为如果是> 0%其实就是全部浏览器支持就不存在筛选的问题

 browsers: ['> 1%']

这时在运行,其实发现加在border-radius的前缀,应该是现在市面99%浏览器都支持圆角的样式,为此可以进一步做些测试,验证这其他设置

  browsers: ['> 1%', 'ff 3']
  ...
  browsers: ['> 1%', 'ff < 4']

圆角功能是ff4才加的功能,我们指定适配某个浏览器版本,这时就会发现运行后的样式一样会有ff的私有前缀,所以一般我们结合浏览器覆盖范围,再加上对特定浏览器的排除就能完成相关设置

有一点需要注意的,设置范围时需要指定范围,不能直接设置ff或者not ff,这时编译会报错,你需要明确指明版本才可以

结合这个例子,其实也就能看出postcss其实像是一个平台,把可能功能集合到一起,通过配置文件的设置,就能指定编译器对pcss文件如何编译,下面介绍比较常用的插件,让我们可以像编写sass或者less那样编写样式

cssnext

这个插件允许开发人员在当前项目中使用css将来版本可能会加入的新特性,这个就非常类似于写ES6的代码,但是使用babel转成ES5的代码,这个插件包含的功能,一般就已经够用的了(这个插件中其实也包含了autoprefixer,使用了这个插件autoprefixer可以不安装)

npm i --save-dev postcss-cssnext

修改postcss的配置文件

// 新增
postcssConfig['postcss-cssnext'] = {};

下面介绍cssnext中主要使用的未来语法

允许在 CSS 中定义属性并在样式规则中作为变量来使用它们。自定义属性的名称以“–”开头。当声明了自定义属性之后,可以在样式规则中使用“var()”函数来引用

// 在main.pcss样式修改为
b {
    border-radius:5px;
}

:root {
 --text-color: black;
}
 
body {
 color: var(--text-color);
}

这时运行,cssnext会报个错误,提示你autoprefixer已经被集成到cssnext中,给你报个警报,所以我们可以这么修改设置

let postcssConfig = {};
// 屏蔽单独对autoprefixer的设置,直接在cssnext进行设置
// postcssConfig.autoprefixer = {
//     browsers: ['> 1%', 'ff 3']
// }
postcssConfig['postcss-cssnext'] = {
    browsers: ['> 1%', 'ff 3']
};

module.exports = {
    plugins: postcssConfig
}

再次运行,编译后的样式文件变为

b {
    -moz-border-radius:5px;
         border-radius:5px;
}
 
body {
 color: black;
}
:root {
  --main-font-size: 16px;
  --fontSize: 1rem;
}

body {
  font-size: var(--main-font-size);
}

h1 {
  font-size: calc(var(--main-font-size) * 2);
  height: calc(100px - 2em);
  margin-bottom: calc(
      var(--main-font-size)
      * 1.5
    )
}
h2 {
  font-size: calc(var(--fontSize) * 2);
}
...
// 转化为
body {
  font-size: 16px;
}

h1 {
  font-size: 32px;
  height: calc(100px - 2em);
  margin-bottom: 24px
}
h2 {
  font-size: 32px;
  font-size: 2rem;
}
@custom-media --small-viewport (max-width: 30em);

@media (--small-viewport) {
  h1 {font-size: 16px}
}
...
// 转码为
@media (max-width: 30em) {
  h1 {font-size: 16px}
}

最大最小宽度,可以使用>= <=代替

@custom-media --small-viewport (width >= 500px) and (width <= 1200px);

@media (--small-viewport) {
  h1 {font-size: 16px}
}
...
// 转为
@media (min-width: 500px) and (max-width: 1200px) {
  h1 {font-size: 16px}
}

CSS 扩展规范(CSS Extensions)中允许创建自定义选择器,可以使用@custom-selector”来定义自定义选择器

@custom-selector :--heading h1, h2, h3, h4, h5, h6;
 
:--heading {
 font-weight: bold;
}

运行后变为

h1,
h2,
h3,
h4,
h5,
h6 {
 font-weight: bold;
}

减少重复的选择器声明,通过两种方式进行嵌套:第一种方式要求嵌套的样式声明使用“&”作为前缀,“&”只能作为声明的起始位置;第二种方式的样式声明使用“@nest”作为前缀,并且“&”可以出现在任意位置

// 嵌套只能使用&开头,除非前缀有@nest
.message {
 font-weight: normal;
 & .header {
   font-weight: bold;
 }
  @nest .body & {
   color: black;
 }
}

运行后

.message {
 font-weight: normal
}
.message .header {
 font-weight: bold;
}
.body .message {
 color: black;
}
// resolve是postcss-assets的功能,下面有介绍,这里我就没准备那么多张图
.foo {
    background-image: image-set(resolve('logo.jpg') 1x,
                                resolve('logo.jpg') 2x,
                                resolve('logo.jpg') 600dpi);
}
...
// become
.foo {
    background-image: url(resolve('logo.jpg'));
}
@media (-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi) {.foo {
    background-image: url(resolve('logo.jpg'));
}
}
@media (-webkit-min-device-pixel-ratio: 6.25), (min-resolution: 600dpi) {.foo {
    background-image: url(resolve('logo.jpg'));
}
}

在工作时设计师应该都会给定相关的颜色色值,很少需要前端自己来算这个值,而且为了保证整体项目的ui统一,应该统一在一处定义好项目中可能用到的所有色值,在一个统一地方进行维护,尽量避免这种还需要自己计算的情况,会造成样式不好维护的情况

a {
    color: color(red alpha(-10%));
}

a:hover {
    color: color(red blackness(80%));
}
...
// become
a {
    color: rgba(255, 0, 0, 0.9);
}

a:hover {
    color: rgb(51, 0, 0);
}
body {
  color: hwb(90, 0%, 0%, 0.5);
}
...
body {
  color: rgba(128, 255, 0, .5);
}
.foo {
  color: gray(85);
}
...
// become
.foo {
  color: rgb(85, 85, 85);
}
body {
  background: #9d9c;
}
...
// become
body {
  background: rgba(153, 221, 153, 0.8);
}
color: rebeccapurple;
...
// become
color: #639;
h1 {
  font-size: 1.5rem;
}
...
// become
h1 {
  font-size: 24px;
  font-size: 1.5rem;
}
nav :any-link {
  background-color: yellow;
}
...
// become
nav :link,nav :visited {
  background-color: yellow;
}
p:matches(:first-child, .special) {
  color: red;
}
...
// become
p:first-child, p.special {
  color: red;
}
p:not(:first-child, .special) {
  color: red;
}
...
// become
p:not(:first-child):not(.special) {
  color: red;
}
.div1 {
  background-color: rgb(100 222.2 100.9 / 30%);
}
.div2 {
  color: hsl(90deg 90% 70%);
  background-color: hsl(300grad 25% 15% / 70%);
}
...
// become
.div1 {
  background-color: rgba(100, 222, 101, .3);
}
.div2 {
  color: hsl(90, 90%, 70%);
  background-color: hsla(270, 25%, 15%, .7);
}
body {
  font-family: system-ui;
}
...
// become
body {
  font-family: system-ui, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, Ubuntu, Cantarell, Droid Sans, Helvetica Neue;
}

暂时处在讨论中,不建议使用的特性

:root {
  --danger-theme: {
    color: white;
    background-color: red;
  };
}

.danger {
  @apply --danger-theme;
}
...
// 转码后
.danger {
  color: white;
  background-color: red;
}

下面介绍一些其他插件

postcss-assets

引用外部资源时,可以通过这个插件设置资源查找路径,简化在样式文件中插入图片的操作

安装

npm i --save-dev postcss-assets

在src目录下新建一个images目录,随意往其中存放一张图片(我使用的是一张logo.jpg的图片)

修改postcss配置文件,新增对postcss-assets路径的设置

// 设置loadPaths指定查找路径
postcssConfig['postcss-assets'] = {
    loadPaths: ['src/images']
}

这时如果在main.pcss的文件中,如果这么设置一个类

// 配置resolve,指定在查找路径下,搜索图片
.logo {
 background-image: resolve('logo.jpg');
}
...
// 编译后会变成,自动识别了
.logo {
 background-image: url('/src/images/logo.jpg');
}

postcss-assets还有其他几项设置,不过感觉不常用:

// 比如如果设为true,图片后就会加一串值,这个看情况添加吧
.logo {
 background-image: url('/src/images/logo.jpg?1637e45dd90');
}

另外这个插件,还支持通过图片进行相关设置

.logo {
 background-image: inline('logo.jpg');
 width: width('logo.jpg');
 height: height('logo.jpg');
}
...
// 运行后
.logo {
 background-image: ...base64的图片
 width: 493px;
 height: 448px;
}

CSS MQPacker在必要时会将你的媒体查询成一个规则

// 如果没有安装这个插件
.widget1 {
  width: 100%;

  @media (min-width: 30em) {
    width: 50%;
  }

  @media (min-width: 60em) {
    width: 25%;
  }
}
.widget2 {
  width: 100px;

  @media (min-width: 30em) {
    width: 200px;
  }
}
...
// become
// 很明显这里关于min-width: 30em的设定是重复的了,正常应该是要进行合并
.widget1 {
  width: 100%;
}
@media (min-width: 30em) {
  .widget1 {
    width: 50%;
  }
}
@media (min-width: 60em) {
  .widget1 {
    width: 25%;
  }
}
.widget2 {
  width: 100px;
}
@media (min-width: 30em) {
  .widget2 {
    width: 200px;
  }
}

npm install --save-dev css-mqpacker

修改相关配置项

// .postcssrc.js新增下面内容,启用该插件
postcssConfig['css-mqpacker'] = {};

再次运行编译后如下

.widget1 {
  width: 100%;
}
.widget2 {
  width: 100px;
}
@media (min-width: 30em) {
  .widget1 {
    width: 50%;
  }
  .widget2 {
    width: 200px;
  }
}
@media (min-width: 60em) {
  .widget1 {
    width: 25%;
  }
}

cssnano对编译后的代码进行压缩

因为我使用的是webpack4+以上的版本,这个版本已经集成了cssnano,如果使用时提示不存在,可以通过下面进行安装

npm install --save-dev cssnano

webpack4+以上的版本,基本不需要对cssnano进行配置,直接把模式改成生产模式,打出的样式就是压缩的,如果需要配置,可以按如下修改配置文件

// 整个配置文件
let postcssConfig = {};
postcssConfig['cssnano'] = {preset: 'default'};
postcssConfig['postcss-cssnext'] = {
    browsers: ['> 1%', 'ff 3']
};
postcssConfig['postcss-assets'] = {
    loadPaths: ['src/images'],
    cachebuster: true
};
postcssConfig['css-mqpacker'] = {};

module.exports = {
    plugins: postcssConfig
}

再次运行样式代码就进行压缩了,postcss的运行是讲究顺序的

关于Rucksack的使用,Rucksack是封装了不少好的相关插件,不过这个东西毕竟不是标准,别人来看代码的时候还要对Rucksack的写法要属性才能了解是做什么的。

// 比如下面的内容,如果你不了解Rucksack会很懵,完全不了解这个是做什么的
.foo {
  font-size: responsive;
}
.bar {
  position: relative 20% auto;
}

相比较而言,我个人很推荐使用cssnext,因为cssnext基本就是实现了w3c组织定义的css新规范,这些规范在未来很有可能直接被浏览器支持,而通过cssnext的插件也会把这些功能转成现在浏览器可识别的样式代码

不建议使用 CSS模块化

我对此有比较强烈的抵触感,使用这种方式就会把重构和逻辑开发混在一起,本来重构只需要管理好自己的样式代码,现在还要把留意改动个类名是否会影响到逻辑代码

// 比如下面这些代码
:global .title {
 font-size: 20px;
}
 
.content {
 font-weight: bold;
}
...
// 如果经过转换,可能会变成
.title {
 font-size: 20px;
}
 
._content_6xmce_5 {
 font-weight: bold;
}
...
// 再借由一些其他处理,我们在页面中很可能是这样使用代码
<div css-module="header.content">Hello world</div>
...
// 然后转意出来后代码变为
<div class="_content_6xmce_5">Hello world</div>

也就是说,现在重构在写样式时,不能再考虑修改content的类名了,因为一修改对应的header.content就找不到这个名词了,虽然在实际开发中这种场景其实并不多,重构定义好的类名也是不会随意修改的(而且也可以通过一些规范规则避免这些情况),但是这种为了解决一个问题,而可能引出另外一个问题的行为是否好呢(比如),如果按项目,页面,模块这几个维度设置好样式嵌套逻辑,是否可以很完美的解决样式组织的问题呢?

避免编译器对pcss文件报错

我使用的是我使用的是vscode,引用插件后写的样式,编译器基本是不认识的,会给你报错,所以这里说下如何避免对pcss的报错,其他编译器应该也有类似的方法

对vscode添加对.pcss文件的支持

"*.css": "postcss"

同时设置"postcss.validate": false,避免检查器对pcss进行检查

postcss要注意设置任务顺序,比如添加浏览器前缀,应该是要比较靠后执行的,避免有些无法前缀无法添加

上一篇下一篇

猜你喜欢

热点阅读