美文网首页CSS
Postcss 简明教程 及 css module

Postcss 简明教程 及 css module

作者: li4065 | 来源:发表于2020-06-06 00:42 被阅读0次

    在前端开发中一直有个原则,叫做"关注点分离",意思就是各种技术只负责自己的领域,不要混合在一起,形成耦合,这种原则比较直观的体现就是不要写"行内样式"(inline style)和"行内脚本"(inline script),HTML、CSS、JavaScript各干各的事,避免混用(此处参考CSS in JS 简介

    现在前端在进行开发时,基本都被各种框架覆盖,新起一个项目,第一步都是技术选型。看看是用 React、还是用 Vue,然后再配套的去选择相关技术栈,以及相应的框架。这是技术的进度,让开发人员能比较关注业务逻辑的展开,但另一方面也可以认为这是一种枷锁。。。

    由于框架的使用,使我们之前的关注点分离变的策略,现在其实都是在 js 中进行。HTML 以 虚拟DOM 的形式存在其中,css 通常是以 import 的形式载入,最终通过 webpack 之类的工具,再导出成一个独立的样式文件。

    相比 HTML,框架对 CSS 都没进行什么特殊处理,也没有形成类似 JSX 的解决方案,不过这其中倒是有一个比较有意思的解决方案:css-in-js(不过在 js 中写 css,怎么都感觉有些别扭)。

    另外 css 本身编程能力薄弱,社区也形成各种方案来提升 css 编程能力。相比其他方案,我感觉 Postcss 更为优雅,所以这里特别介绍下

    目前是基于 postcss 7.0.31 版本

    基本概念

    • Postcss是一个用 js 插件转换成 css 的工具
    • Postcss 不是预处理器

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

    • Postcss 不是后处理器

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

    • Postcss 是作为一个平台的存在,利用 Postcss 提供的插件可以组合各种不同模块,来构建更为复杂的功能

    Demo

    此处没有使用vue或react的脚手架,为了演示完整功能,构建了一个基于Vue的开发环境,并演示加载样式和提取

    • 任意目录 npm init -y

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

    • 安装 相关依赖

    npm i --save-dev webpack webpack-cli webpack-dev-server html-webpack-plugin postcss postcss-loader mini-css-extract-plugin css-loader style-loader @babel/cli @babel/core @babel/preset-env vue-loader vue-template-compiler

    npm i --save vue

    * css-loader 是可以在页面中使用 import 引入 css 的能力
    * style-loader 是把 css 代码生成 style 标签,放到 head 标签中
    * mini-css-extract-plugin 提取css用的插件
    

    其他包不在本文讨论范围,故不介绍

    • 新建两个文件夹 srcdist,以及 babel.config.jswebpack.config.js 和其他相关文件
    /// babel.config.js
    const presets = [["@babel/env"]];
    
    module.exports = {
        presets
    }
    ...
    /// webpack.config.js
    const HtmlWebpackPlugin = require('html-webpack-plugin');
    const VueLoaderPlugin = require('vue-loader/lib/plugin');
    
    module.exports = {
        entry: './src/index.js',
        resolve: {
            extensions: ['.js', '.vue']
        },
        output: {
            filename: '[name].js',
            publicPath: '/'
        },
        mode: 'development',
        module: {
            rules: [
                {test: /\.js$/, use: 'babel-loader', exclude: /node_modules/},
                {test: /\.vue$/, use: 'vue-loader', exclude: /node_modules/},
                {test: /\.css/, use: ['style-loader', 'css-loader']}
            ]
        },
        devServer: {
            port: '8111'
        },
        plugins: [
            new HtmlWebpackPlugin({
                template: './src/tpl/index.html'
            }),
            new VueLoaderPlugin()
        ]
    }
    ...
    /// 假定有个 app.vue 按如上设置
    import '../style/app.css';
    

    这时就可以通过 import 方式把样式文件导入到页面中,因为此时用的是 style-loader ,样式会写入到 <head> 下的 <style> 标签中

    • 如不喜欢那种在把样式一股脑掉到 <head> 的方式,可使用 mini-css-extract-plugin ,这会把相关样式拆分成一个个独立的文件
    /// webpack.config.js
    const MiniCssExtractPlugin = require("mini-css-extract-plugin");
    ...
    {test: /\.css/, use: [MiniCssExtractPlugin.loader, 'css-loader']} // 替换 style-loader
    ...
    plugins: [
        ...
        new MiniCssExtractPlugin({
        filename: "[name].css",
        chunkFilename: "[id].css"
      })
    ]
    
    • import 多个文件,按目前的设置,会合并和一个样式文件导出,不过这些文件中的内容,只是简单的合并在一起,而且也没编程思想在其中,完全依赖样式编写人员控制每个样式文件的内容
    /// app.css
    .info {
        font-size: 24px;
    }
    ...
    /// color.css
    .info {
        color: red;
    }
    ...
    /// app.vue
    import '../style/app.css';
    import '../style/color.css';
    ...
    /// 合并出的样式文件如下
    .info {
        font-size: 24px;
    }
    .info {
        color: red;
    }
    

    编程化

    通过 .pcss 引入样式文件

    .pcss 是 Postcss 的专用格式文件

    • 安装 postcss-import,使在 .pcss 文件可以使用 @import 引入样式文件

    npm i --save-dev postcss-import

    需要先添加一个对 Postcss 的运行配置文件,可以叫 .postcssrc.jspostcss.config.js

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

    postcss.config.js 除了 plugins 还具有如下参数:

    • syntax: 提供语法分析器和字符串化器的对象
    • parser: 特殊的语法解析器(例如,SCSS)
    • stringifier: 特殊语法输出生成器(例如Midas)
    • map: 对map文件的设置
    • from: 输入文件名
    • to: 输出文件名

    不过常用的就是 plugins (定义使用的插件)

    /// 在webpack.config.js中做如下修改
    {test: /\.pcss$/, use: [
        MiniCssExtractPlugin.loader,
        "css-loader",
        "postcss-loader"
    ]},
    ...
    /// mian.pcss
    @import './app.css';
    @import './color.css';
    ...
    /// app.vue 引入 main.pcss
    import './main.pcss';
    

    PostCss 的编程能力,是通过各种插件实现的,可以自己编写,或者直接使用社区现有插件,插件在 Postcss 的配置文件中设置使用参数

    autoprefixer

    这应该是postcss中使用最为广泛的插件了,自动识别设定的浏览器兼容范围,添加浏览器样式前缀

    npm i --save-dev autoprefixer

    /// postcss.config.js
    let postcssConfig = {};
    postcssConfig.autoprefixer = {
        browsers: ['> 1%', 'ff 3']
    }
    
    module.exports = {
        plugins: postcssConfig
    }
    
    • 修改main.pcss内容如下
    b {
        border-radius:5px;
    }
    

    编译运行后结果为

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

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

    browsers 是利用 browserslist 功能来决定是否需要添加某些浏览器前缀,在browserslist的文档里我们可以找到详细设定,可以设定针对浏览器、国家、指定平台、年份做设置

    > 5%
    cover 99.5%
    > 5% in US
    node 10 and node 10.4
    since 2015
    ie 6-8
    not ie <= 8
    

    下面列举下在项目中最有可能碰到的浏览器

    • Android: Android webview浏览器
    • iOS: ios的Safari浏览器
    • Chrome: 谷歌浏览器
    • ChromeAndroid: 谷歌浏览器安卓版
    • Edge: 微软的Edge浏览器
    • ie: ie浏览器
    • Safari: Safari桌面浏览器
    • ff: firefox浏览器
    • and_ff: firefox安卓浏览器
    • and_qq: QQ浏览器安卓版
    • and_uc: UC浏览器安卓版

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

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

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

    前缀修饰符 not 表示不在某个范围中,还可以使用 cover extendssince 进行更细化的设置,包括道指定从哪年开始的什么版本。

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

    /// 如此设定后,生成的样式就不会有针对 火狐浏览器 的前缀
      browsers: ['> 1%', 'ff > 4']
    

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

    在 package.json 中设置 browserslist

    由于还会有其他插件(比如 babel )需要针对浏览器设置兼容情况,所以针对浏览器的范围设定,一般会建议加上 package.json 中。
    在其中,设置 "browserslist": ["> 1%", "ff > 4"](package.json 中设定的优先级低于插件的设置)

    postcss-preset-env

    这个插件允许开发人员在当前项目中使用 css 将来版本可能会加入的新特性,这个就非常类似于写 ES6 的代码,但是使用 babel 转成 ES5 的代码。

    这个插件中包含了autoprefixer

    另外一个类似的插件,postcss-cssnext 已经不再维护

    npm i --save-dev postcss-preset-env

    // 新增
    const postcssPresetEnv = require('postcss-preset-env');
    
    module.exports = {
      plugins: [
      postcssPresetEnv({ 
        stage: 2
        // browsers: 'last 2 versions' // 这个插件包含 autoprefixer 的功能,可以在配置中进行浏览器的相关设定
      })
      ]
    }
    

    插件参数说明

    下面根据官方的文档介绍相关设置参数:

    • stage: 根据现行web标准的进程(主要就是 w3c 组织认定的标准,所以这个插件中具备的功能都是未来很有可能直接加到 css 标准中的内容)来决定某些 css 功能需不需要通过垫片的方式添加,可以设置 0~4 的任意数字,如果没有设置这个值,这个值默认为 2

      • 0: 这个阶段处在 非官方草案或编辑草案阶段,很有可能会被删除
      • 1: 这个阶段处于 实验阶段,有可能会被设置为标准
      • 2: 这个阶段处于 待定阶段,这也是插件默认的数值,处于这个阶段的功能,基本可以认为会被加到未来的标准中
      • 3: 这个阶段处于 稳定阶段,基本上已经有浏览器厂商实现了,可以直接使用
      • 4: 这个阶段处于 标准阶段
    • features: 针对特定css功能进行单独设置,比如设置了 stage3,可以通过这个参数来特定使用还处于 stage2 的功能

    postcssPresetEnv({
      /* 使用stage为3的标准,同时允许嵌套规则(嵌套是stage 0的标准) */
      stage: 3,
      features: {
        'nesting-rules': true
      }
    })
    
    • browsers: 参加上面browserslist的介绍,就是设置浏览器的支持情况(不建议使用,直接在package.json 中设置)

    • insertBefore / insertAfter: 允许你在该插件运行之前或者之后运行某些插件,可以是一个或者多个

    import postcssSimpleVars from 'postcss-simple-vars';
    
    postcssPresetEnv({
      insertBefore: {
        'all-property': postcssSimpleVars
      }
    })
    
    • autoprefixer: 设为 false 可以禁用该功能

    • preserve: 决定所有插件是否应接收同一个preserve选项,该选项可以保留或删除以其他方式填充的 CSS

    • importFrom: 从外部导入相关变量信息(如自定义媒体、自定义属性、自定义选择器和环境变量),这个导入的数据源可以是 css、js 或者 json (还支持函数和直接对象传值)。这个功能完全可以根据运行时的 ENV 和某些自己设置的参数,来决定最基础的变量参数,来进行换肤或者针对PC,H5单独出套 ui,这样能减少 ui 中样式的大小(如果本身使用的就是 postcss.config.js 的方式进行配置,可以把切环境的功能放到这个配置文件中,然后动态的加载不同的css)

    postcssPresetEnv({
      importFrom: [
        /*
        @custom-media --small-viewport (max-width: 30em);
        @custom-selector :--heading h1, h2, h3;
        :root { --color: red; }
        */
        'path/to/file.css',
    
        /* module.exports = {
        customMedia: { '--small-viewport': '(max-width: 30em)' },
        customProperties: { '--color': 'red' },
        customSelectors: { ':--heading': 'h1, h2, h3' },
        environmentVariables: { '--branding-padding': '20px' }
        } */
        'and/then/this.js',
    
        /* {
        "custom-media": { "--small-viewport": "(max-width: 30em)" }
        "custom-properties": { "--color": "red" },
        "custom-selectors": { ":--heading": "h1, h2, h3" },
        "environment-variables": { "--branding-padding": "20px" }
        } */
        'and/then/that.json',
    
        {
          customMedia: { '--small-viewport': '(max-width: 30em)' },
          customProperties: { '--color': 'red' },
          customSelectors: { ':--heading': 'h1, h2, h3' },
          environmentVariables: { '--branding-padding': '20px' }
        },
        
        () => {
          const customMedia = { '--small-viewport': '(max-width: 30em)' };
          const customProperties = { '--color': 'red' };
          const customSelectors = { ':--heading': 'h1, h2, h3' };
          const environmentVariables = { '--branding-padding': '20px' };
    
          return { customMedia, customProperties, customSelectors, environmentVariables };
        }
      ]
    });
    
    • exportTo: 与importFrom功能相反,这个是导出相关变量(这里应该是鼓励我们把变量都放在一个公共的文件下,这样便于维护)

    stage 2及以上的css语法

    就是开启默认配置可以直接使用的css语法

    • all: 定义元素的所有属性的重置的属性 [stage 3 下面以数字简写]
    a  {
       all:initial ; 
    }
    
    • :any-link 用于匹配锚元素的伪类,不管是否被访问(一般情况下a标签被点击后a标签会使用:visited的设置信息,这个的属性的设置不受影响) [2]
    nav:any-link> span  {
       background-color:yellow ; 
    }
    
    • break属性:用于定义多列布局中中断行为的属性 [3]

      • break-inside: 描述了在多列布局页面下的内容盒子如何中断
      • break-after: 描述在生成的盒子之后的页面,列或区域中断行为
      • break-before: 描述列或区域在生成的盒子之前应如何处理中断
    • case-insensitive attributes:(看不明白,看意思是不区分大小写) [2]

    • custom properties:用于定义CSS属性接受的自定义值 [3]

    img {
      --some-length: 32px;
    
      height: var(--some-length);
      width: var(--some-length);
    }
    
    • :dir伪类 基于方向性匹配元素的伪类 [2]
    • double-position-gradients: 圆锥形渐变[2]
    .pie_chart {
      background-image: conic-gradient(yellowgreen 40%, gold 0deg 75%, #f06 0deg);
    }
    
    • :focus-visible: 焦点可见的伪类 [2]
    • :focus-within: 处于焦点状态的伪类 [2]
    • font-variant: 设置小型大写字母的字体显示文本,这意味着所有的小写字母均会被转换为大写,但是所有使用小型大写字体的字母与其余文本相比,其字体尺寸更小 [3]
    • gap属性 [3]

    该属性是用来设置网格行与列之间的间隙(gutters),是row-gap 和 column-gap的简写形式

    • gray(): 用于指定完全去饱和颜色的功能 [2]
    • alpha:十六进制颜色表示法 比一般的3/6表示法,多1/2个字符,可以指定透明度 [2]
    • hwb(): 用于通过色调指定颜色然后将白度和黑度指定为混合的功能 [2]
    • image-set(): 根据用户分辨率指定引用不同的图像源 [2]
    // resolve是postcss-assets的功能,下面有介绍
    .foo {
        background-image: image-set(resolve('logo1x.jpg') 1x,
                                    resolve('logo2x.jpg') 2x,
                                    resolve('logo-print.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'));
    }
    }
    
    • lab() 使用lab表示颜色 [2]
    • lch() 使用lch表示颜色 [2]
    • logical properties and values(流相对的属性和值) [2]
    span:first-child {
      float: inline-start;
      margin-inline-start: 10px;
    }
    
    • :matches 匹配伪类,可以一次设置多个属性值 [2]
    p:matches(:first-child, .special) {
      margin-top: 1em;
    }
    
    
    • :not 否定伪类,设置不在当前范围 [2]
    p:not(:first-child, .special) {
      margin-top: 1em;
    }
    
    
    • 媒体查询范围 使用普通比较符合定义范围 [3]
    @media (width < 480px) {}
    
    @media (480px <= width < 768px) {}
    
    @media (width >= 768px) {}
    
    • overflow 溢出属性设置 [2]
    • overflow-wrap 定义是否在单词中插入换行符来防止溢出属性 [2]
    • place 定义对齐属性 [2]
    • rebeccapurple 一个特殊的颜色值 [2]
    html  {
       color:rebeccapurple ; 
    }
    
    
    • system-ui 匹配通用字体 [2]
    body  {
       font-family:system-ui ; 
    }
    

    stage 1 的css语法

    因为设定的stage为2,如果要启用需要在features配置中开启,对应的id可以在插件api中查看,对应的id就是英文加中划线,有感觉对不上的可以去对应github的js文件中查看

    postcssPresetEnv({ 
        stage: 2,
        browsers: ['> 1%'],
        features: {
          'nesting-rules': true,
          'custom-media-queries': true,
          'custom-selectors': true
        }
    })
    
    • 自定义媒体查询
    @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;
    }
    

    stage 0的css语法

    • 嵌套(使用的场景很多,也一并开启使用)

    减少重复的选择器声明,通过两种方式进行嵌套:第一种方式要求嵌套的样式声明使用“&”作为前缀,“&”只能作为声明的起始位置;第二种方式的样式声明使用“@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;
    }
    

    postcss-assets

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

    安装

    npm i --save-dev postcss-assets

    // 设置loadPaths指定查找路径
    assets({
      loadPaths: ['src/images']
    }),
    

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

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

    这里会要求图片是目录下是存在的,如果不存在进行编译时会报错,loadPaths 是个数组,相当于是多个搜索的目录,只要图片在某一个目录下即可,不过从编译时性能角度来考虑,一定不可以设置过多的目录,否则一定会影响编译速度

    postcss-assets 还有其他几项设置:

    • basePath: 如果postcss.config.js不在项目的根目录可以使用这个参数就行修改,比如我这里,就是把配置文件在根目录下,这样我在设置loadPaths时,不用再去考虑其他路径的问题,可以从当前位置,直接设置图片目录为src/images,一般这种配置文件感觉放在根目录最好,这样可以很方便别人查看
    • baseUrl: 没特别弄明白这参数是什么意思,好像是在服务器运行时,设置url,不过理解不了,部署到生产环境的代码,肯定时编译后的代码,不能在线上再实时编译吧。。。
    • cachebuster: 是否设置缓存,默认是false
    // 比如如果设为true,图片后就会加一串hash值
    .logo {
     background-image: url('/src/images/logo.jpg?1637e45dd90');
    }
    
    • loadPaths: 设置查找特定目录
    • relative: 和URL解析有关,没试出来做什么用的。。。使用默认的false,禁用相对url解析
    • cache: 默认为false,如果引入文件没有发生变化,则有限使用缓存文件,感觉应该和cachebuster配套使用,能提升编译速度

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

    • inline: 是把图片转成base64文件,估计要慎用,如果图片很多的话,会影响编译速度
    • width: 是获取一张图片的宽度,height是获取一张图片的高度
    .logo {
     background-image: inline('logo.jpg');
     width: width('logo.jpg');
     height: height('logo.jpg');
    }
    ...
    // 运行后
    .logo {
     background-image: ...base64的图片
     width: 493px;
     height: 448px;
    }
    

    cssnano

    webpack4+ 的版本,已经集成了cssnano,mode 设为生产模式就会自动启用这个插件

    npm install --save-dev cssnano

    // 如果单独使用,可以这么配置
    module.exports = {
        plugins: [
            require('cssnano')({
                preset: 'default',
            }),
        ],
    };
    

    使用 postcss 实现一个 sass

    postcss 可自主定义相关插件的使用,组合出适合自己使用的功能,借助 postcss 的插件来实现一个类 sass

    相关插件介绍

    • postcss 基础功能包
    • postcss-advanced-variables 提供嵌套类似 sass 的变量、@if、@else、@for、@each、@mixin、@include、@content
    • postcss-scss 使 postcss 中可以正常使用 sass 中的 #{$var-name} 变量形式
    • postcss-apply
    • postcss-assets
    • postcss-import 使在.pcss文件可以使用@import引入样式文件
    • postcss-preset-env 一个对未来css语法规则,实现比较好的插件
    • postcss-cli 提供了终端运行的能力

    @import

    使用 postcss-import 插件

    const atImport = require("postcss-import");
    module.exports = {
      plugins: [
        atImport(),
    

    元素嵌套

    使用 postcss-preset-env 插件 stage:0 中的 nesting-rules

    require('postcss-preset-env')({
        stage: 2,
      browsers: ['> 1%'],
      features: {
        'nesting-rules': true
      }
    })
    

    具体写法上和sass嵌套的写法有些不一样,不过这种写法是未来css的标准,以这个为主要写法

    nav {
      & ul {
        margin: 0;
        padding: 0;
        list-style: none;
      }
    
      & li {
        display: inline-block;
      }
    
      & a {
        display: block;
        padding: 6px 12px;
        text-decoration: none;
        & span {
          color: $red;
        }
      }
    }
    

    变量、@if、@else、@for、@each、@mixin、@include、@content

    使用 postcss-advanced-variables 这个插件,这个插件能实现sass变量的大部分功能,不过一个特殊的语法#{$var-name}需要使用到 postcss-scss 这个插件,并且在postcss.config.js 中设置 parserpostcss-scss

    const advanced = require("postcss-advanced-variables");
    module.exports = {
      parser: "postcss-scss",
      plugins: [
        advanced(),
        ...
    
    // 变量
    $font-size: 1.25em;
    $font-stack: "Helvetica Neue", sans-serif;
    $primary-color: #333;
    
    body {
      font: $font-size $(font-stack);
      color: #{$primary-color};
    }
    ...
    // 转为
    body {
      font: 1.25em "Helvetica Neue", sans-serif;
      color: #333;
    }
    
    // @if、@else
    $type: monster;
     
    p {
      @if $type == ocean {
        color: blue;
      } @else {
        color: black;
      }
    }
    ...
    // 转为
    p {
        color: black
    }
    
    // @for
    @for $i from 1 through 5 by 2 {
      .width-#{$i} {
        width: #{$i}0em;
      }
    }
    ...
    // 转为
    .width-1 {
        width: 10em;
      }
    .width-3 {
        width: 30em;
      }
    .width-5 {
        width: 50em;
      }
    
    // @each
    @each $animal in (red, yellow, black, white) {
      .#{$animal}-icon {
        color: #{$animal};
      }
    }
    ...
    // 转为
    .red-icon {
        color: red;
      }
    .yellow-icon {
        color: yellow;
      }
    .black-icon {
        color: black;
      }
    .white-icon {
        color: white;
      }
    
    // @mixin @include
    @mixin heading-text {
      color: #242424;
      font-size: 4em;
    }
     
    h1, h2, h3 {
      @include heading-text;
    }
    ...
    // 转为
    h1,
    h2,
    h3 {
      color: #242424;
      font-size: 4em;
    }
    

    属性嵌套

    postcss-nested-props

    const nestedProps = require("postcss-nested-props");
    module.exports = {
      plugins: [
        nestedProps(),
    
    .funky {
      font: {
        family: fantasy;
        size: 30em;
        weight: bold;
      }
    }
    ...
    // 转为
    .funky {
        font-family: fantasy;
        font-size: 30em;
        font-weight: bold
    }
    

    Extend/Inheritance(扩展/继承)

    postcss-extend

    const extend = require("postcss-extend");
    module.exports = {
      plugins: [
        extend(),
    
    // 这里和sass有点不同,使用 @define-placeholder 而非 % 导出样式块
    @define-placeholder message-shared {
      border: 1px solid #ccc;
      padding: 10px;
      color: #333;
    }
    .message {
      @extend message-shared;
    }
    
    .error {
      border: 1px #f00;
      background-color: #fdd;
    }
    .seriousError {
      @extend .error;
      border-width: 3px;
    }
    ...
    // 转为
    .message {
      border: 1px solid #ccc;
      padding: 10px;
      color: #333;
    }
    .error, .seriousError {
      border: 1px #f00;
      background-color: #fdd;
    }
    .seriousError {
      border-width: 3px;
    }
    

    添加calc()计算

    postcss-calc

    const calc = require("postcss-calc");
    module.exports = {
      plugins: [
        calc(),
    
    @mixin columns_calc($count) {
      width: calc(100% / $count);
      @if $count > 1 {
        float: left;
      }
    }
    .column_calculated {
      @include columns_calc(2);
    }
    ...
    // 转为
    .column_calculated {
      width: 50%;
      float: left;
    }
    

    添加了这些插件,基本就可以完全可以满足正常开发需求

    完整配置如下:

    const postcssPresetEnv = require("postcss-preset-env");
    const atImport = require("postcss-import");
    const advanced = require("postcss-advanced-variables");
    const nestedProps = require("postcss-nested-props");
    const extend = require("postcss-extend");
    const calc = require("postcss-calc");
    module.exports = {
      parser: "postcss-scss",
      plugins: [
        atImport(),
        advanced(),
        nestedProps(),
        extend(),
        calc(),
        postcssPresetEnv({
          stage: 2,
          browsers: "> 3%",
          features: {
            "nesting-rules": true
          }
        })
      ]
    };
    

    独立使用postcss

    可以通过 postcss-cli 这个插件构建一个独立使用的 css 处理平台,可在 package.json 中添加如下命令

      "scripts": {
        "build:pc": "npx postcss src/main.pc.css -o dist/main.pc.css",
        "build:h5": "npx postcss src/main.h5.css -o dist/main.h5.css"
    
    // src/main.pc.css
    @import 'normalize.css';
    @import 'reset.css';
    @import 'variables.css';
    @import 'common.css';
    @import 'common.mixin.css';
    @import 'layout.mixin.css';
    @import 'layout.css';
    @import 'layout-flex.css';
    @import 'button.mixin.css';
    @import 'button.css';
    @import 'button-group.css';
    @import 'skeleton.mixin.css';
    @import 'skeleton.css';
    @import 'breadcrumb.css';
    @import 'dropdown.css';
    @import 'menu.css';
    @import 'pagination.mixin.css';
    @import 'pagination.css';
    @import 'step.css';
    @import 'checkbox.css';
    @import 'cascader.css';
    @import 'form.css';
    @import 'calendar.css';
    @import 'input-number.css';
    @import 'rate.css'
    

    这里由一个统一入口文件,控制针对PC,H5不同的样式表的输出,在入口文件中,可以设置引入不同的变量(比如上面例子中的 variables.css 正常开发中,可以细化成variables.pc.css或者variables.orange.css 这类的样式),让换肤类的功能也比较容易实现

    避免编译器对pcss文件报错

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

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

    • 安装postcss-sugar-language插件
    • 进入setting,搜索files.associations在其中添加如下内容
    "*.css": "postcss"
    

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

    css module

    (参考CSS in JS 简介
    css modulecss in js 都是社区针对 css 作用域提出的解决方案,从实际开发过程中,感觉 css module 的方式,更适合分工合作的要求。

    现在开发中,不管你使用什么框架,基本的流程大概都是:先做静态页面,再接动态数据。这里很容易就会形成两条工作线:一类专门进行重构,与设计师、交互对接完成视觉实现;另一类专门与产品经理、后端对接业务需求(体量大的公司会拆分成两个工种,小公司自然是全干了,但是工作场景多半如此)。

    css in js 的方式,更符合独立组件的封装型。从样式、逻辑到内容展现,在一个 js 中都实现,不需要再引入其他文件,如果是写 ui 组件这种方式是极好的。

    但是如果是正常的开发需求,你用这种方式,就表示样式问题的改动、修复,业务逻辑的实现,都是在一个文件中进行,不利于分工合作。

    个人感觉 css in js 方式会让 js 文件变得混乱,可读性下降(正常开发就算是状态管理,一般都会被拆分成一个个独立的文件)。而且没办法单独输出样式文件,没办法把样式文件丢在某个 cdn 服务器下,也影响组件的复用性。如果要复用某个组件(业务组件)就表示要接受这个组件自带的样式,稍微调整点字号颜色啥的,就要单独加个接收参数,或者新建个类似 list-red 的新组件(如果是引入样式的方式,完全可以通过再引入这个组件的外层加个类似 <div class='red-list'><list /></div>,在外层样式文件中直接控制,也很容易扩展多个不同样式、换肤啥的)。

    css in js 方式对重构人员也不友好,他们之前查看样式问题,直接修改样式文件部署后就能看出问题能不能解决,现在需要修改 js 文件等js文件部署后才能查看改动是否生效(如果再碰到分支不同,版本不同的情况,那更热闹了。。。)

    启用

    参考了 CSS Modules 用法教程

    配合 css-loader 使用,在 webpack 配置项中如下设置:

    {test: /\.pcss/, use: [MiniCssExtractPlugin.loader, 'css-loader?modules', 'postcss-loader']},
    

    这样就开启了 css module,它的使用会把对应的样式名变成一种有规律,但不可预期的名称,比如

    /// main.pcss
    /// 由于样式文件本身并没有要求 .classname 不能重复,所以要自己保证不要重复,否则针对同一类名会转出多个变量名,在使用时会有问题
    .box {
        border-radius:5px;
    }
    .info {
        color: red;
        font-size: 24px;
    }
    ...
    /// app.vue
    <template>
        <div :class="$style.info">{{ msg }}</div>
    </template>
    import style from '../style/main.pcss';
    ...
    computed: {
      $style () {
        return style;
      }
    }
    

    $style 的使用方式参考了 vue-loader 的 CSS Modules 方案,vue loader 会使用 $style 的计算属性,向组件注入 CSS Modules 局部对象

    style 会输出为(value 值是一个动态值)

    {box: "_2sI8WybUY_1NGPVWmXjdbV", info: "_hA0iOLbXZy9PpOuCjpkc"}
    

    组件内直接使用其中的定义的样式名,会自动替换成这些名称,样式文件也会自动转成这些名称,这样可以解决 css 的样式冲突(污染)的问题,全局污染的问题,也可以算是解决依赖的问题,组件只需要引入自己相关的样式,在这个相关样式文件中定义自己需要使用的样式,再通过 $style 的形式给相关组件使用。

    vue 的写法稍微麻烦点(如果用 JSX 会简单些),如果是在 react 中可以直接使用

    import style from '../style/main.css';
    
    class App extends Component {
        render () {
            return <div className={style.info}>app info</div>
        }
    }
    

    全局作用域

    在引入的样式文件中,默认会对所有样式进行转换,如果你的样式只是想通过普通方式使用,可以有两种方案:

    • 只针对 .pcss 开启 css module.css 不开启
    /// webpack.config.js
    {test: /\.css/, use: [MiniCssExtractPlugin.loader, 'css-loader']},
    {test: /\.pcss/, use: [MiniCssExtractPlugin.loader, 'css-loader?modules', 'postcss-loader']}
    ...
    /// global.css
    body {
        font-size: 18px;
        background: #cccccc;
    }
    ...
    /// app.vue
    import '../style/global.css';
    import style from '../style/main.pcss'; // 这里虽然是分开导入,但是最终会合并和一个样式文件
    ...
    /// main.css 导出的样式文件
    body {
        font-size: 18px;
        background: #cccccc;
    }
    ._2sI8WybUY_1NGPVWmXjdbV {
        border-radius:5px;
    }
    ._hA0iOLbXZy9PpOuCjpkc {
        color: red;
        font-size: 24px;
    }
    
    • 在待编译的样式文件使用 :global(.className) (也可以省略为 :global .className)的写法,这样这个对应的样式也不会编译

    :local 可以设置哪些需要转,因为默认就是转换,没必要再加一层说明其需要转换

    对于需要转换的,请一直使用 .classname,不要使用 id

    /// main.pcss
    ...
    :global(body)  {
        font-size: 18px;
        background: #cccccc;
    }
    

    Composing(组合) 及 Importing(导入)

    组合的意义并不是把样式代码进行混合,而是在于使用了组合的类名,在引用时会包含其组合的子类

    .box {
        border-radius:5px;
    }
    .info {
        color: red;
        font-size: 24px;
    }
    .foo {
        composes: box;
        composes: info;
        padding: 10px;
    }
    /// 经过转换后对应的json信息如下
    box: "src-style-main__box--2sI8W",
    info: "src-style-main__info--_hA0i",
    foo: "src-style-main__foo--LiN2X src-style-main__box--2sI8W src-style-main__info--_hA0i",
    /// 如果我们使用 $style.foo 还会包含其他两个子类的内容
    

    composes 还可以从其他样式模块中导入样式进行组合

    .foo {
        composes: main-title header-title from './color.css';
        padding: 10px;
    }
    

    @value

    css 变量的解决方案很多,这个是 css-loader 的方案

    官方有个命名建议:v- 定义value值, s- 选择器 m- 定义 media 规则

    @value v-primary: #BF4040;
    @value s-black: black-selector;
    @value m-large: (min-width: 960px);
    
    .header {
      color: v-primary;
      padding: 0 10px;
    }
    :global .s-black {
      color: black;
    }
    @media m-large {
      .header {
        padding: 0 20px;
      }
    }
    
    

    定制编译后的类名

    css-loader 默认的哈希算法是 [hash:base64],转出的就是这种 _2sI8WybUY_1NGPVWmXjdbV,这个转换后的名称是可以定制的

    // 参数比较长,再使用拼接的写法,看起来不美观
    loader: 'css-loader',
    options: {
      modules: {
        localIdentName: '[path][name]__[local]--[hash:base64:5]'
      } // 注意是在 modules 下设置 localIdentName
    }
    

    按官方建议

    • 开发环境使用 [path][name]__[local]--[hash:base64:5]
    • 生产环境使用 [hash:base64]

    这样转出的类名就类似 src-style-main__box--2sI8W,能看出路径和使用模块,方便定位样式问题所在的

    css loader 参数

    名称 类型 默认 描述
    url {Boolean|Function} true 启用/禁止 url() 功能
    import {Boolean|Function} true 启用/禁用 @import 处理
    modules {Boolean|String|Object} false 启用/禁用 CSS Modules 以及相关配置
    sourceMap {Boolean} false 启用/禁用 map 功能
    importLoaders {Number} 0 在 css-loader 之前使用多少个加载器(默认,别去动这个值,按 webpack 中的设置的 use 顺序去执行使用 loader)
    localsConvention {String} 'asIs' 导出的 JSON 对应的 Key 的规则
    onlyLocals {Boolean} false 影响打包时的 loader 顺序,一些 SSR 场景下可以需要设置这个值,一般情况下别用
    esModule {Boolean} false 是否启用 es module

    url

    /// 先安装 url-loader 处理图片,并添加相关配置信息
    {
      test: /\.(png|svg|jpg|jpeg|gif)$/,
      use: "url-loader"
    },
    ...
    /// main.pcss
    .foo {
      color: red;
      font-size: 14px;
      background: url("../image/timg.jpeg"); // 此处设定了背景图
    }
    

    按如上设置,转换出的 css 会对图片进行处理,但如果设置这个值为 false

    {
      loader: "css-loader",
      options: {
        modules: true,
        url: false // 这个值默认是 true
      }
    },
    

    转换出的 css 就不会对 url() 进行处理,原样输出

    /// 不如处理 background: url("../image/timg.jpeg");
    /// 这个 url 还可以设置为函数,在这里可以控制,只有指定图片名会转,进行差异化处理
    url: (url, resourcePath) => {
        // url ../image/timg.jpeg
      // resourcePath - css 的绝对路径
    
      // 不处理 `img.png` urls
      if (url.includes('img.png')) {
        return false;
      }
    
      return true;
    },
    

    import

    这个值和 url 功能类似,只不过这里是控制文件的导入导出,这个值也一样可以设置 true 或 false,如果这样直接设置,那这肯定有问题,这个参数要么不设置,要设置一定是要对url进行过滤

    /// @import "./color.css";
    import: (parsedImport, resourcePath) => {
        // parsedImport { url: './color.css', media: '' }
      // parsedImport.url - `@import` 文件时对应的路径 
      // parsedImport.media -  `@import` 媒体查询时对应的路径
      // resourcePath - css 文件的绝对路径
    
      // 包含 `style.css` 不进行合并处理
      if (parsedImport.url.includes('style.css')) {
        return false;
      }
    
      return true;
    },
    

    @import 是一个广泛被支持的 css 属性,大于 IE9 的浏览器就可以正常使用

    modules

    true 或 false 启用/禁止(还可以通过设置 modules 的值为 'local''global'启用/禁止) css modules

    前面提到的 :global :localComposing Importing@valuelocalIdentName 都是 modules 的配置信息,modules 可以接收一个对象进行其他配置

    modules: {
      mode: 'local', // 设置 local 启用 global 禁用 css 模块
      exportGlobals: true,
      localIdentName: '[path][name]__[local]--[hash:base64:5]', // 编译后的类名
      context: path.resolve(__dirname, 'src'),
      hashPrefix: 'my-custom-hash',
    },
    
    • mode 除了 local global 外,还有一个值是 pure,使用这个值就要求样式文件中必须是纯的选择器,不能使用 global local 进行包裹
    /// 在 mode 为 pure 时,会提示编译出错
     :local(.zoo)
    ...
    /// mode 还可以接受一个函数形式,对指定路径的样式文件进行特殊处理(什么文件开启转换,什么文件不开启),返回值只能是这三个值
    mode: (resourcePath) => {
      if (/pure.css$/i.test(resourcePath)) {
        return 'pure';
      }
    
      if (/global.css$/i.test(resourcePath)) {
        return 'global';
      }
    
      return 'local';
    }, 
    
    • exportGlobals 按描述应该是控制输出类名的名称,不过没没查出有什么特别的用处,设不设都不影响使用

    • context 生成hash值相同,参考那个 GitHub bug 说明,是在某些情况下,生成的hash值出现重复的情况,然后借助这个参数,解决此问题,不过我试了很多情况,没试出来生成 hash 相同的情况

    • hashPrefix 设定组自己的 hash 值规则,一般无须设置

    • getLocalIdent 设置编译后的规则名,同 localIdentName 只不过这是个函数,能设置的更细,一般用 localIdentName

    • localIdentRegExp 没试出干什么用

    localsConvention

    这个设定导出 JSON 时,key值和类名如何映射,默认值是 'asIs'

    loader: 'css-loader',
    options: {
      modules: {
        mode: 'local',
        localIdentName: '[path][name]__[local]--[hash:base64:5]'
      },
        localsConvention: 'asIs'
    }
    ...
    .infoNews {
        font-size: 35px;
    }
    .info-old {
        color: white;
    }
    .info_dashes {
        color: salmon;
    }
    .info {
        color: red;
    }
    ...
    /// 默认 asIs,类名是啥,导出的就是啥
    info: "src-components-page1-index__info--10GK2"
    info-old: "src-components-page1-index__info-old--k163w"
    infoNews: "src-components-page1-index__infoNews--1uma1"
    info_dashes: "src-components-page1-index__info_dashes--FVVUn"
    ...
    /// camelCase 驼峰,会把非驼峰的命名转为驼峰,并保留之前的类名
    info: "src-components-page1-index__info--10GK2"
    info-old: "src-components-page1-index__info-old--k163w"
    infoDashes: "src-components-page1-index__info_dashes--FVVUn" // 值同 info_dashes
    infoNews: "src-components-page1-index__infoNews--1uma1"
    infoOld: "src-components-page1-index__info-old--k163w" // 值同 info-old
    info_dashes: "src-components-page1-index__info_dashes--FVVUn"
    ...
    /// camelCaseOnly 与驼峰类似,只不过不会保留非驼峰的转换
    info: "src-components-page1-index__info--10GK2"
    infoDashes: "src-components-page1-index__info_dashes--FVVUn"
    infoNews: "src-components-page1-index__infoNews--1uma1"
    infoOld: "src-components-page1-index__info-old--k163w"
    ...
    /// dashes 一样也是转驼峰,只不过这里限制只转 ``-`` 号
    info: "src-components-page1-index__info--10GK2"
    info-old: "src-components-page1-index__info-old--k163w"
    infoNews: "src-components-page1-index__infoNews--1uma1"
    infoOld: "src-components-page1-index__info-old--k163w"
    info_dashes: "src-components-page1-index__info_dashes--FVVUn" // 不转换
    ...
    /// dashesOnly 不保留转换前的
    info: "src-components-page1-index__info--10GK2"
    infoNews: "src-components-page1-index__infoNews--1uma1"
    infoOld: "src-components-page1-index__info-old--k163w"
    info_dashes: "src-components-page1-index__info_dashes--FVVUn"
    

    之所以会有这么个参数,是因为我们在使用 css modules 时,最终可能会动态绑定到某个组件上,这是为了方便在 js 中使用

    相关文章

      网友评论

        本文标题:Postcss 简明教程 及 css module

        本文链接:https://www.haomeiwen.com/subject/cwbszhtx.html