关于css:CSS工程化

7次阅读

共计 11194 个字符,预计需要花费 28 分钟才能阅读完成。

css 的问题

类名抵触的问题

当你写一个 css 类的时候,你是写全局的类呢?还是写多个层级抉择后的类呢?

你会发现,怎么都不好!

  • 过深的层级不利于编写、浏览、压缩、复用
  • 过浅的层级容易导致类名抵触

一旦款式多起来,这个问题就会变得越发重大,其实归根结底,就是类名抵触不好解决的问题。

反复款式

这种问题就更广泛了,一些反复的款式值总是一直的呈现在 css 代码中,保护起来极其艰难。

比方,一个网站的色彩个别就那么几种:

  • primary
  • info
  • warn
  • error
  • success

如果有更多的色彩,都是从这些色调中天然变动得来,能够设想,这些色彩会到处充斥到诸如背景、文字、边框中,一旦要做色彩调整,是一个十分大的工程。

css 文件细分问题

在大型项目中,css 也须要更细的拆分,这样有利于 css 代码的保护。

比方,有一个做轮播图的模块,它不仅须要依赖 js 性能,还须要依赖 css 款式,既然依赖的 js 性能仅关怀轮播图,那 css 款式也应该仅关怀轮播图,由此类推,不同的性能依赖不同的 css 款式、公共款式能够独自抽离,这样就造成了不同于过来的 css 文件构造:文件更多、拆分的更细

而同时,在实在的运行环境下,咱们却心愿文件越少越好,这种状况和 JS 遇到的状况是统一的,因而,对于 css,也须要工程化治理。

从另一个角度来说,css 的工程化会遇到更多的挑战,因为 css 不像 JS,它的语法自身通过这么多年并没有产生多少的变动(css3 也仅仅是多了一些属性而已),对于 css 语法自身的扭转也是一个工程化的课题

如何解决

这么多年来,官网始终没有提出计划来解决上述问题,一些第三方机构针对不同的问题,提出了本人的解决方案。

解决类名抵触

一些第三方机构提出了一些计划来解决该问题,常见的解决方案如下:

命名约定

就是提供一种命名的规范,来解决抵触,常见的规范有:

  • BEM
  • OOCSS
  • AMCSS
  • SMACSS
  • 其余

我次要以 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 语言自身简直无可救药了,罗唆间接用 js 对象来示意款式,而后把款式间接利用到元素的 style 中

这样一来,css 变成了一个一个的对象,就能够齐全利用到 js 语言的劣势,你能够:

  • 通过一个函数返回一个款式对象
  • 把公共的款式提取到公共模块中返回
  • 利用 js 的各种个性操作对象,比方:混合、提取、拆分
  • 更多的花色

这种计划在手机端的 React Native 中大行其道

css in js 的核心思想是:用一个 JS 对象来形容款式,而不是 css 样式表

例如上面的对象就是一个用于形容款式的对象:

const styles = {
    backgroundColor: "#f40",
    color: "#fff",
    width: "400px",
    height: "500px",
    margin: "0 auto"
}

因为这种形容款式的形式基本就不存在类名,天然不会有类名抵触

至于如何把款式利用到界面上,不是它所关怀的事件,你能够用任何技术、任何框架、任何形式将它利用到界面。

css in js 的特点

  • 绝无抵触的可能:因为它基本不存在类名,所以绝不可能呈现类名抵触
  • 更加灵便:能够充分利用 JS 语言灵便的特点,用各种招式来解决款式
  • 利用面更广:只有反对 js 语言,就能够反对 css in js,因而,在一些用 JS 语言开发挪动端利用的时候十分好用,因为挪动端利用很有可能并不反对 css
  • 书写不便:书写款式,特地是公共款式的时候,解决起来不是很不便
  • 在页面中减少了大量冗余内容:在页面中解决 css in js 时,往往是将款式退出到元素的 style 属性中,会大量减少元素的内联款式,并且可能会有大量反复,不易浏览最终的页面代码

css module

十分乏味和好用的 css 模块化计划,编写简略,相对不重名

通过命名标准来限度类名太过死板,而 css in js 尽管足够灵便,然而书写不便。
css module 开拓一种全新的思路来解决类名抵触的问题

思路:

css module 遵循以下思路解决类名抵触问题:

  1. css 的类名抵触往往产生在大型项目中
  2. 大型项目往往会应用构建工具(webpack 等)搭建工程
  3. 构建工具容许将 css 款式切分为更加精密的模块
  4. 同 JS 的变量一样,每个 css 模块文件中难以呈现抵触的类名,抵触的类名往往产生在不同的 css 模块文件中
  5. 只须要保障构建工具在合并款式代码后不会呈现类名抵触即可

实现原理

在 webpack 中,作为解决 css 的 css-loader,它实现了 css module 的思维,要启用 css module,须要将 css-loader 的配置 modules 设置为 true。

css-loader 的实现形式如下:

原理极其简略,开启了 css module 后,css-loader 会将款式中的类名进行转换,转换为一个惟一的 hash 值。

因为 hash 值是依据模块门路和类名生成的,因而,不同的 css 模块,哪怕具备雷同的类名,转换后的 hash 值也不一样。

如何利用款式:

css module 带来了一个新的问题:源代码的类名和最终生成的类名是不一样的,而开发者只晓得本人写的源代码中的类名,并不知道最终的类名是什么,那如何利用类名到元素上呢?

为了解决这个问题,css-loader 会导出原类名和最终类名的对应关系,该关系是通过一个对象形容的

这样一来,咱们就能够在 js 代码中获取到 css 模块导出的后果,从而利用类名了

style-loader 为了咱们更加不便的利用类名,会去除掉其余信息,仅裸露对应关系

其余操作

  • 全局类名

某些类名是全局的、动态的,不须要进行转换,仅须要在类名地位应用一个非凡的语法即可:

:global(.main){...}

应用了 global 的类名不会进行转换,相同的,没有应用 global 的类名,示意默认应用了 local

:local(.main){...}

应用了 local 的类名示意部分类名,是可能会造成抵触的类名,会被 css module 进行转换

  • 如何管制最终的类名

绝大部分状况下,咱们都不须要管制最终的类名,因为管制它没有任何意义

如果肯定要管制最终的类名,须要配置 css-loader 的 localIdentName

  • 其余注意事项
  1. css module 往往配合构建工具应用
  2. css module 仅解决顶级类名,尽量不要书写嵌套的类名,也没有这个必要
  3. css module 仅解决类名,不解决其余选择器
  4. css module 还会解决 id 选择器,不过任何时候都没有应用 id 选择器的理由
  5. 应用了 css module 后,只有能做到让类名气文知意即可,不须要恪守其余任何的命名标准

解决反复款式的问题

css in js

这种计划尽管能够利用 js 语言解决反复款式值的问题,但因为太过于激进,很多习惯写 css 的开发者编写起来并不是很适应

css 预编译器

有些第三方搞出一套 css 语言的进化版来解决这个问题,它反对变量、函数等高级语法,而后通过编译器将其编译成为失常的 css

这种计划特地像构建工具,不过它仅针对 css

常见的预编译器反对的语言有:

  • less
  • sass

基本原理

编写 css 时,受限于 css 语言自身,经常难以解决一些问题:

  • 反复的款式值:例如罕用色彩、罕用尺寸
  • 反复的代码段:例如相对定位居中、革除浮动
  • 反复的嵌套书写

因为官网迟迟不对 css 语言自身做出改良,一些第三方机构开始想方法来解决这些问题, 其中一种计划,便是预编译器。

预编译器的原理很简略,即应用一种更加优雅的形式来书写款式代码,通过一个编译器,将其转换为可被浏览器辨认的传统 css 代码。


目前,最风行的预编译器有 LESS 和 SASS,它们两个特地类似,这里次要说 less

  • 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 的装置和应用

从原理可知,要应用 LESS,必须要装置 LESS 编译器

LESS 编译器是基于 node 开发的,能够通过 npm 下载安装

npm i -D less

装置好了 less 之后,它提供了一个 CLI 工具 lessc,通过该工具即可实现编译

lessc less 代码文件 编译后的文件

光说不练假把式:

新建一个 index.less 文件,编写内容如下:

// less 代码
@red: #f40;

.redcolor {color: @red;}

运行命令:

lessc index.less index.css

能够看到编译之后的代码:

.redcolor {color: #f40;}

LESS 的根本应用

具体的应用见文档:https://less.bootcss.com/

  • 变量
  • 混合
  • 嵌套
  • 运算
  • 函数
  • 作用域
  • 正文
  • 导入

postcss

什么是 PostCss?

CSS 工程化面临着诸多问题,而解决这些问题的计划多种多样。如果把 CSS 独自拎进去看,光是款式自身,就有很多事件要解决。

既然有这么多事件要解决,何不把这些事件集中到一起对立解决呢?

PostCss 就是基于这样的理念呈现的。PostCss 相似于一个编译器,能够将款式源码编译成最终的 CSS 代码


看上去是不是和 LESS、SASS 一样呢?

但 PostCss 和 LESS、SASS 的思路不同,它其实只做一些代码剖析之类的事件,将剖析的后果交给插件,具体的代码转换操作是插件去实现的。

官网的一张图更能阐明 postcss 的解决流程:

这一点有点像 webpack,webpack 自身仅做依赖剖析、形象语法树剖析,其余的操作是靠插件和加载器实现的。

  • 官网地址:https://postcss.org/
  • github 地址:https://github.com/postcss/postcss

装置

PostCss 是基于 node 编写的,因而能够应用 npm 装置

npm i -D postcss

postcss 库提供了对应的 js api 用于转换代码,如果你想应用 postcss 的一些高级性能,或者想开发 postcss 插件,就要 api 应用 postcss,api 的文档地址是:http://api.postcss.org/

不过绝大部分时候,咱们都是使用者,并不心愿应用代码的形式来应用 PostCss

因而,咱们能够再装置一个 postcss-cli,通过命令行来实现编译

npm i -D postcss-cli

postcss-cli 提供一个命令,它调用 postcss 中的 api 来实现编译

命令的应用形式为:

postcss 源码文件 -o 输入文件

配置文件

和 webpack 相似,postcss 有本人的配置文件,该配置文件会影响 postcss 的某些编译行为。

配置文件的默认名称是:postcss.config.js

例如:

module.exports = {map: false, // 敞开 source-map}

插件

光应用 postcss 是没有多少意义的,要让它真正的发挥作用,须要插件

postcss 的插件市场:https://www.postcss.parts/

上面列举一些 postcss 的罕用插件:

  1. postcss-preset-env

过来应用 postcss 的时候,往往会应用大量的插件,它们各自解决一些问题, 这样导致的后果是装置插件、配置插件都特地的繁琐。

于是呈现了这么一个插件 postcss-preset-env,它称之为 postcss 预设环境,粗心就是它整合了很多的罕用插件到一起,并帮你实现了根本的配置,你只须要装置它一个插件,就相当于装置了很多插件了。

装置好该插件后,在 postcss 配置中退出上面的配置

module.exports = {
    plugins: {"postcss-preset-env": {} // {} 中能够填写插件的配置}
}

这个插件外面有很多性能,比如说:

  • 主动厂商前缀

能够实现某些新的 css 款式须要在旧版本浏览器中应用厂商前缀

例如:

::placeholder {color: red;}

这个性能在不同的旧版本浏览器中须要书写为

::-moz-placeholder{color: red;}
:-ms-input-placeholder{color: red;}
::placeholder{color: red;}

要实现这件事件,须要应用 autoprefixer 库。而postcss-preset-env 外部蕴含了该库,主动有了该性能。

如果须要调整兼容的浏览器范畴,能够通过上面的形式进行配置:

【形式 1】:增加 .browserslistrc 文件

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

last 2 version
> 1%

【形式 2】:在 package.json 的配置中退出browserslist

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

browserslist 是一个多行的(数组模式的)规范字符串。

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

个别状况下,大部分网站都应用上面的格局进行书写

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-env 的 stage 配置,告知 postcss-preset-env 须要对哪个阶段的 css 语法进行兼容解决,它的默认值为 2

"postcss-preset-env": {stage: 0}

一共有 5 个阶段可配置:

  1. Stage 0: Aspirational – 只是一个晚期草案,极其不稳固
  2. Stage 1: Experimental – 依然极其不稳固,然而提议已被 W3C 公认
  3. Stage 2: Allowable – 尽管还是不稳固,但曾经能够应用了
  4. Stage 3: Embraced – 比较稳定,可能未来会产生一些小的变动,它行将成为最终的规范
  5. Stage 4: Standardized – 所有支流浏览器都应该反对的 W3C 规范

理解了以上常识后,接下来理解一下将来的 css 语法,只管某些语法仍处于十分晚期的阶段,然而有该插件存在,编译后依然能够被浏览器辨认

① 变量

将来的 css 语法是人造反对变量的

:root{} 中定义罕用变量,应用 -- 前缀命名变量

:root{
    --lightColor: #ddd;
    --darkColor: #333;
}

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

编译后,依然能够看到原语法,因为某些新语法的存在并不会影响浏览器的渲染,只管浏览器可能不意识
如果不心愿在后果中看到新语法,能够配置 postcss-preset-envpreservefalse

② 自定义选择器

@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;}

编译后

a:focus,a:hover{color: #f40;}

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

h1.active,h2.active,h3.active,h4.active,h5.active,h6.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;}

2、postcss-apply

这个插件能够反对在 css 中书写属性集,相似于 LESS 中的混入,能够利用 CSS 的新语法定义一个 CSS 代码片段,而后在须要的时候利用它。

:root {
  --center: {
    position: absolute;
    left: 50%;
    top: 50%;
    transform: translate(-50%, -50%);
  };
}

.item{@apply --center;}

编译后

.item{
  position: absolute;
  left: 50%;
  top: 50%;
  -webkit-transform: translate(-50%, -50%);
  transform: translate(-50%, -50%);
}

☛实际上,该性能也属于 cssnext,不知为何 postcss-preset-env 没有反对

3、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 {
    /* 应用色彩 #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);
}

4、stylelint

官网:https://stylelint.io/

在理论的开发中,咱们可能会谬误的或不标准的书写一些 css 代码,stylelint 插件会实时的发现错误。

因为不同的公司可能应用不同的 CSS 书写标准,stylelint 为了放弃灵便,它自身并没有提供具体的规定验证。

你须要装置或自行编写规定验证计划

通常,咱们会装置 tylelint-config-standard 库 提供规范的 CSS 规定断定

装置好后,咱们须要通知 stylelint 应用该库来进行规定验证,通知的形式有多种,比拟常见的是应用文件.stylelintrc

//.styleintrc
{"extends": "stylelint-config-standard"}

此时,如果你的代码呈现不标准的中央,编译时将会报出谬误

body {background: #f4;}

产生了两处谬误:

  • 缩进应该只有两个空格
  • 十六进制的色彩值不正确

如果某些规定不是咱们所冀望的,能够在配置中进行设置:

{
    "extends": "stylelint-config-standard",
    "rules": {"indentation": null}
}

设置为 null 能够禁用该规定,或者设置为 4,示意一个缩进有 4 个空格。具体的设置须要参见 stylelint 文档:https://stylelint.io/

然而这种错误报告须要在编译时才会产生,如果我心愿在编写代码时就主动在编辑器里报错呢?如果想在编辑器里达到该性能,装置 vscode 的插件 stylelint 就能够了,它会读取你工程中的配置文件,依照配置进行实时报错。

解决 css 文件细分问题

这一部分,就要依附构建工具,例如 webpack 来解决了,利用一些 loader 或 plugin 来打包、合并、压缩 css 文件。

利用 webpack 拆分 css

要拆分 css,就必须把 css 当成像 js 那样的模块;要把 css 当成模块,就必须有一个构建工具(webpack),它具备合并代码的能力,而 webpack 自身只能读取 css 文件的内容、将其当作 JS 代码进行剖析,因而,会导致谬误。

于是,就必须有一个 loader,可能将 css 代码转换为 js 代码

css-loader

css-loader 的作用,就是将 css 代码转换为 js 代码,它的解决原理简略到令人发指:就是将 css 代码作为字符串导出。

例如:

.red{color:"#f40";}

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

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

下面的 js 代码是通过我简化后的,不代表实在的 css-loader 的转换后代码,css-loader 转换后的代码会有些简单,同时会导出更多的信息,但核心思想不变。

再例如:

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

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

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

这样一来,通过 webpack 的后续解决,会把依赖./bg.png 增加到模块列表,而后再将代码转换为

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

再例如:

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

会转换为:

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

总结,css-loader干了什么:

  1. 将 css 文件的内容作为字符串导出
  2. 将 css 中的其余依赖作为 require 导入,以便 webpack 剖析依赖

style-loader

因为 css-loader 仅提供了将 css 转换为字符串导出的能力,残余的事件要交给其余 loader 或 plugin 来解决。

style-loader 能够将 css-loader 转换后的代码进一步解决,将 css-loader 导出的字符串退出到页面的 style 元素中

例如:

.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 = {}

以上代码均为简化后的代码,并不代表实在的代码,style-loader 是有能力防止同一个款式的反复导入

抽离 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 后的款式对象

应用形式:

const MiniCssExtractPlugin = require("mini-css-extract-plugin")
module.exports = {
    module: {
        rules: [
            {test: /\.css$/, use: [MiniCssExtractPlugin.loader, "css-loader?modules"]
            }
        ]
    },
    plugins: [new MiniCssExtractPlugin() // 负责生成 css 文件
    ]
}

配置生成的文件名

output.filename 的含意一样,即依据 chunk 生成的款式文件名。

配置生成的文件名,例如[name].[contenthash:5].css

默认状况下,每个 chunk 对应一个 css 文件。

以上就是我明天的分享,心愿对大家有所帮忙,如果哪里有问题,心愿大家多多斧正,谢谢大家!

正文完
 0