起源看到 earlyBirdCamp 用到了 tailwindcss,然后顺势了解一番
tailwindcss 是什么
如果打开 tailwindcss 的 github仓库,大概会一脸蒙,怎么 css 框架一堆 js ,而且 css 文件也是奇怪的atrule?可以说,tailwindcss对 postcss 的使用真的上了一个层次,之前我也没想到可以这么玩!通过 js 配置,postcss-js 和 postcss-nested 去解析我们的配置,生成 node,然后最后再用 postcss 去生成 css,甚至还可以使用 puregecss 去裁剪生成的 css ,或者用比较熟悉的话术说,就是可以 tree shaking 我们的 css,听上去很高级的样子。这里不会细致,大概挑几个比较有特色的讲一下。
经典的 tailwind.css
首先看下面三个比较经典的 atrule ,atrule
是 postcss 里面 ast 的一个节点,代表 css中的@节点,其中后面跟随的是这个节点的 params ,具体可以去 astexplorer 以及结合 postcss 的api文档去理解
@tailwind base;
@tailwind components;
@tailwind utilities;
复制代码
在 tailwindcss 里面,这样经典的节点,会起两个作用,拿@tailwind base;
这个来说,postcss插件遇到这个 atrule 的时候,识别 params 是 base ,会把 pluginBase 中的节点内容生成 css ,同时会修复 source ,这样 sourcemap 就不会出错了,具体可以看下面,可以看到,现在 atRule 前面插入内容,移除这个 atRule
css.walkAtRules('tailwind', atRule => {
if (atRule.params === 'preflight') {
// prettier-ignore
throw atRule.error("`@tailwind preflight` is not a valid at-rule in Tailwind v1.0, use `@tailwind base` instead.", { word: 'preflight' })
}
if (atRule.params === 'base') {
atRule.before(updateSource(pluginBase, atRule.source))
atRule.remove()
}
if (atRule.params === 'components') {
atRule.before(postcss.comment({ text: 'tailwind start components' }))
atRule.before(updateSource(pluginComponents, atRule.source))
atRule.after(postcss.comment({ text: 'tailwind end components' }))
atRule.remove()
}
if (atRule.params === 'utilities') {
atRule.before(postcss.comment({ text: 'tailwind start utilities' }))
atRule.before(updateSource(pluginUtilities, atRule.source))
atRule.after(postcss.comment({ text: 'tailwind end utilities' }))
atRule.remove()
}
if (atRule.params === 'screens') {
includesScreensExplicitly = true
atRule.before(postcss.comment({ text: 'tailwind start screens' }))
atRule.after(postcss.comment({ text: 'tailwind end screens' }))
}
})
if (!includesScreensExplicitly) {
css.append([
postcss.comment({ text: 'tailwind start screens' }),
postcss.atRule({ name: 'tailwind', params: 'screens' }),
postcss.comment({ text: 'tailwind end screens' }),
])
}
复制代码
至于修复 sourcemap 指向,可以看下面这段函数,让新生成的所有节点都指向了原来 atRule 的source,完美~
function updateSource(nodes, source) {
return _.tap(Array.isArray(nodes) ? postcss.root({ nodes }) : nodes, tree => {
tree.walk(node => (node.source = source))
})
}
复制代码
再讲一个 screen atRule
Instead of writing a raw media query that duplicates that value like this:
@media (min-width: 640px) {
/* ... */
}
复制代码
you can use the @screen directive and reference the breakpoint by name:
@screen sm {
/* ... */
}
复制代码
上面这个英文,是从文档里面拷贝出来的, 就是常见的媒体查询,我们可以写死到 css,但是如果过段时间又要改呢?这里抽离出来到js配置里面,我们通过读取配置文件,默认是 tailwind.config.js,里面的 theme.screens 对应 atRule 中的 params 的值,生成我们具体的 css,简直不要太方便啊
import _ from 'lodash'
import buildMediaQuery from '../util/buildMediaQuery'
export default function({ theme }) {
return function(css) {
css.walkAtRules('screen', atRule => {
const screen = atRule.params
if (!_.has(theme.screens, screen)) {
throw atRule.error(`No \`${screen}\` screen found.`)
}
atRule.name = 'media'
atRule.params = buildMediaQuery(theme.screens[screen])
})
}
}
复制代码
关于 tailwindcss 插件的写法
tailwindcss 插件的写法,具体怎么写,这里不具体展开,这里讲讲原理。插件其实主要的作用就是把我们写的 js css 配置生成一个初步的 css,然后再根据配置文件,进行一个二次处理,最后生成实际的 css,拿一个内置的 zIndex 的插件当例子,主要是这个插件也写了测试用例,结合文档,介绍起来简直不要太方便
首先我们看看这个插件是怎么写的
import _ from 'lodash'
import prefixNegativeModifiers from '../util/prefixNegativeModifiers'
export default function() {
return function({ addUtilities, e, theme, variants }) {
const utilities = _.fromPairs(
_.map(theme('zIndex'), (value, modifier) => {
return [
`.${e(prefixNegativeModifiers('z', modifier))}`,
{
'z-index': value,
},
]
})
)
addUtilities(utilities, variants('zIndex'))
}
}
复制代码
其实我们要关心的就是 addUtilities(utilities, variants('zIndex'))
这块到底干嘛了,简单地说,就是 zIndex 塞到 pluginUtilities 里面,也就是说,最后是对应 @tailwind utilities;
,可以看看测试用例中生成的是什么,如下,就是这个样子,其中 utilities 还好理解,variants 是什么?variants 的使用可以看文档的时候,说白了,就是会有 @variants responsive
把我们utilities中生成的代码包住,这样就初步生成我们的 css,然后 substituteResponsiveAtRules 这个会二次处理这个 atRule,生成最终的 css
expect(addedUtilities).toEqual([
{
utilities: {
'.-z-20': { 'z-index': '-20' },
'.-z-10': { 'z-index': '-10' },
'.z-10': { 'z-index': '10' },
'.z-20': { 'z-index': '20' },
},
variants: ['responsive'],
},
])
复制代码
最后说一句
tailwindcss
还有很多玩法,具体可以去文档挖掘,这里只是简单说下它实现的原理,它给我带来的震撼是,没想到 postcss 或者说 css 还可以这么玩,脑洞太大了, ps: 为了了解这货,基本把主要依赖库的文档瞄了一遍,:逃)