多年来我一直使用Sass.但是最近我想要使用PostCSS和它的cssnext插件来尝试处理样式.我爱死了现在就可以使用将来的CSS特性,相对于之前我用的工具,它们更顺手一些.我的个人站点就是尝试新特性的最好的测试地.
第一步是列出我Sass用法的清单.我需要知道我使用了哪些特性,并且确信新特性在postCSS中有替代品.以下是我正在这个项目中使用的特性:
部分引用(partial import)
变量(variables)
嵌套(nesting)
混合宏(mixins)
拓展(extend)
占位类(placeholder classes)
颜色函数(darken and rgba color functions)
压缩(compression)
在切换到新语法之后我需要做一些准备.现在项目的目录结构是Sass的典型用法.我用下划线( _
)来命名文件,文件的拓展名为 scss
.我使用两个文件夹来组织Sass文件. moudules
文件夹保存不直接产生CSS的Sass文件,像是变量、占位类和混合宏. partials
保存编译出CSS的Sass文件.
这是最初的文件结构:
css/ scss/ modules/ _module.scss ... partials/ _partial.scss ... tylergaw.scss
每个Sass组件会在 tylergaw.scss
中引入.
@import "modules/setup"; @import "modules/reset"; @import "modules/fonts";
我重新组织并且重命名了文件.我先把所有文件的后缀名从 scss
改为 css
.我使用了一个Bash脚本来完成这项工作,而不是一个一个修改.
for f in *.scss; do git mv -- "$f" "${f%.scss}.css"; done;`
前面的下划线是编写Sass的习惯所以我也去掉了它.我没办法使用Bash命令一次性完成,所以只能手动每个去修改.
最后一步就是将所有的CSS文件都移动至 modules
文件夹并且删除 partials
文件夹.我认为将所有CSS都当成modules来管理要比将他们按照moudules/partials拆分更容易理解.
我以 PostCSS CLI 为起始,在 package.json
里添加了一个临时的构建脚本命令:
"scripts": { "postcss": "postcss -o public/css/tylergaw.css src/css/tylergaw.css" }
在没有更改任何样式的情况下我编译了CSS:
npm run postcss`
正常工作.控制台没有报错,但是页面上没有任何CSS样式.
构建过程是可用的,现在的任务是把样式找回来.
在Chrome的控制台里我看到了很多404信息.这表示我们第一个丢失的特性就是内联 @import
. tylergaw.css
通过 @import
来引入CSS模块.浏览器看到这些,知道它要做什么.浏览器会通过HTTP请求来加载每个模块.我的构建过程只复制了一个独立的CSS文件,而不是每个模块.正因如此,浏览器找不到它们.
我可以改变构建过程来让默认的 @import
工作,但那样效率很低.我需要一个Sass样式内联 @import
的替代品.
postcss-import
插件可以代替Sass中的 @import
,在通过npm安装之后,我更新了构建脚本代码:
"scripts": { "postcss": "postcss -u postcss-import -o public/css/tylergaw.css src/css/tylergaw.css" }
再次运行 npm run postcss
,单个的CSS文件就包含了所有模块.现在的页面就展示出了部分样式.
在Sass中展现出内联方式的 @import
功能是非常强大的.它让我们能更好的组织样式.我不确定将来这个功能会不会原生支持.我们使用这种功能时总是需要一步编译,看起来也不坏.
我想 postcss-import
插件会成为我PostCSS的一个主要配置,对其他人来说应该也一样.下面引用了插件作者的看法:
This plugin should probably be used as the first plugin of your list. This way, other plugins will work on the AST as if there were only a single file to process, and will probably work as you can expect.
> [postcss-import](https://github.com/postcss/postcss-import#postcss-import) > >
cssnext 是PostCSS中一个插件,用于将未来CSS特性编译为现今支持的特性.特别需要指出,它和Sass或Less并非不同的语言.它提供正在进行中的CSS规范的特性.一些特性已经得到浏览器支持.另外一些还处于规范的初始阶段.
我使用cssnext来填补失去的Sass特性留下的鸿沟.
在构建这个网站之前我了解过 Autoprefixer .我用 自定义Sass混合宏 来解决添加所需要的前缀的问题.cssnext包含了Autoprefixer,所以我可以将这整个混合宏模块移除.
下一步我将Sass变量改为CSS自定义属性.比如在 _setup.scss 中,我这样写:
$grey: #1e1e1d; $yellow: #ffad15; $offwhite: #f8f8f8; $darkerwhite: darken($offwhite, 15);
这不是所有我使用的Sass变量,但是主要就这些.剩下都在独立的模块中.
注意:自定义属性和变量的区别.CSS自定义属性只在属性值有效,不能用于选择器,属性名或媒体查询.
新的 setup.css
:
:root { --white:#fff; --grey:#1e1e1d; --yellow:#ffad15; --offwhite:#f8f8f8; ... }
以下为使用示例:
a { color: var(--yellow); }
除了语法,CSS自定义属性和Sass变量工作方式是相同的.由于浏览器支持的限制,自定义属性值仍然需要编译.在上面的示例中,编译后的值为 color: #ffad15
.
在之前的例子中,我遗漏了一个变量: $darkerwhite: darken($offwhite, 15);
.这是另一个我需要寻找替代的Sass特性.这里有一个 规范草案 提供CSS颜色函数.cssnex现在包含这些函数,这非常酷.下面是 setup.css
,其中 darkerwhite
自定义属性是通过颜色函数和阴影调节器来实现的.
:root { ... --offwhite: #f8f8f8; --darkerwhite: color(var(--offwhite) shade(20%)); ... }
颜色函数提供了许多 调节器 .你可以在一个函数中使用多个调节器:
background-color: color(#d32c3f shade(40%) alpha(40%));`
编译结果为:
background-color: rgba(127, 26, 38, 0.4);`
再次重申,现在cssnext会将 color()
编译为16进制或rgba的色值.当颜色函数得到浏览器支持后,编译过程就没有必要了.颜色操作在运行时就可以发生.
嵌套是CSS预处理器不可或缺的特性.任何让人舒服的样式工具的必需品.Tab Atkins对CSS嵌套有一个正在进行中的规范,并且cssnext让它成为现实.
CSS的嵌套语法包含一个前置于内层的 &
,以下为sass片段:
.projects-list { ... li { & > div {...} } a { ... &:hover, &:focus {...} &::after {...} } @media (min-width: 640px) {...} }
对于CSS嵌套,我将它修改为以下形式:
.projects-list { ... & li { & > div {...} } & a { ... &:hover, &:focus {...} &::after {...} } @media (min-width: 640px) {...} }
基本的嵌套需要前置的 &
.伪类和选择器在Sass和CSS中是相同的.媒体查询不需要前置 &
.
另外值得注意的是 @nest
.正如 文档 中提到的,复杂的嵌套可能需要引入 @nest
来代替&.这个项目我还没有用到,或许将来用得到.
I was using Sass @extend
and placeholder classes for common styles. Here’s an example usage responsible for styling Futura headings:
%futura { font-family: 'futura-pt', helvetica, sans-serif; } %futura-heading { @extend %futura; font-weight: 700; line-height: 1.1; text-transform: uppercase; }
and an example usage:
.my-heading { @extend %futura-heading; }
We looked at CSS custom properties usage earlier. There’s a related in-progressspec for the @apply
rule. @apply
allows you to store a set of properties and reference them in selectors. I used @apply
in place of Sass’s extend
.
Back in setup.css
I added the updated Futura heading properties:
:root { ... --franklin: { font-family: 'futura-pt', helvetica, sans-serif; }; --franklin-heading: { @apply --franklin; font-weight: 700; line-height: 1.1; text-transform: uppercase; }; }
and an example usage:
.my-heading { @apply --franklin-heading; }
@apply
is not extend
. In the current form in cssnext, @apply
copies the properties and values to each rule. This is a small project so that’s OK. On larger projects the extra properties may cause too much bloat. At that time it would probably be best to use a common class name to get similar results.
At this point I had the site looking as it did before the changes. The Projects page was an exception. On it I used a different color for each project tile. Next I’ll describe how styling that correctly without Sass required more work and more typing.
The colorful tiles of the Projects page
To make writing the projects styles easier I used a Sass mixin. The mixin took a single argument, the color for the tile. Here’s the project-block
mixin:
@mixin project-block ($c) { background-color: $c; a { color: $c; &:hover { background-color: $c; color: $offwhite); } } }
and example usage:
.p-jribbble { @include project-block(#ff0066); }
At the time of this writing, I couldn’t find a way to mimic this functionality in CSS. Custom property sets with @apply
aren’t functions, so you can’t pass them arguments. In the future, it might be possible to use custom selectors for argument magic. The draft spec has a complex example that looks promising. Right now, I’ll admit, I don’t fully understand how it works.
That didn’t mean I was out of luck. The CSS I write is longer than the Sass, but not by much. I also made use of another in-progress CSS feature; the matches selector.
Here’s an example of the CSS replacement for the project-block
mixin:
.p-jribbble, .p-jribbble a:matches(:hover, :focus) { background-color: var(--color-jrb); & a { color: var(--color-jrb); } }
The color variables are in a :root
scope earlier in the file. cssnext compiles the above CSS to:
.p-jribbble, .p-jribbble a:hover, .p-jribbble a:focus { background-color: #ff0066 } .p-jribbble a, .p-jribbble a:hover a, .p-jribbble a:focus a { color: #ff0066; }
The last two selectors ...a a:hover
and ...a a:focus
won’t match any elements. They’re unecessary, but aside from a few more bytes they don’t hurt anything. I preferred nesting the a
selector for code readability.
With the styles back in proper order, I decided to take advantage of more PostCSS plugins. I used css mqpacker to combine media queries that share the same query. I also used cssnano for code optimization.
This is where I’m looking forward to using a PostCSS setup. With Sass I felt locked in to the features in the current version. Because PostCSS works as a collection of plugins, it’s extendible. If I have a specific need, I can write a plugin for it. The potential there is exciting.
After working with this setup for a few days, I’m all in. Making the switch from Sass syntax to new CSS syntax has been easy. And that’s after five or six years of using Sass on every project I worked on.
I enjoy the shift in thinking this brings. cssnext has a similar approach to CSS as Babel has to JavaScript. They both allow you to write the language as it is and as it will be in the future.
Thanks for reading