淺談 tailwindcss 的原理

起源看到 earlyBirdCamp 用到了 tailwindcss,然后順勢(shì)了解一番

tailwindcss 是什么

如果打開 tailwindcss 的 github倉庫,大概會(huì)一臉蒙,怎么 css 框架一堆 js ,而且 css 文件也是奇怪的atrule?可以說,tailwindcss對(duì) postcss 的使用真的上了一個(gè)層次,之前我也沒想到可以這么玩!通過 js 配置,postcss-js 和 postcss-nested 去解析我們的配置,生成 node,然后最后再用 postcss 去生成 css,甚至還可以使用 puregecss 去裁剪生成的 css ,或者用比較熟悉的話術(shù)說,就是可以 tree shaking 我們的 css,聽上去很高級(jí)的樣子。這里不會(huì)細(xì)致,大概挑幾個(gè)比較有特色的講一下。

經(jīng)典的 tailwind.css

首先看下面三個(gè)比較經(jīng)典的 atrule ,atrule是 postcss 里面 ast 的一個(gè)節(jié)點(diǎn),代表 css中的@節(jié)點(diǎn),其中后面跟隨的是這個(gè)節(jié)點(diǎn)的 params ,具體可以去 astexplorer 以及結(jié)合 postcss 的api文檔去理解

@tailwind base;

@tailwind components;

@tailwind utilities;

復(fù)制代碼

在 tailwindcss 里面,這樣經(jīng)典的節(jié)點(diǎn),會(huì)起兩個(gè)作用,拿@tailwind base;這個(gè)來說,postcss插件遇到這個(gè) atrule 的時(shí)候,識(shí)別 params 是 base ,會(huì)把 pluginBase 中的節(jié)點(diǎn)內(nèi)容生成 css ,同時(shí)會(huì)修復(fù) source ,這樣 sourcemap 就不會(huì)出錯(cuò)了,具體可以看下面,可以看到,現(xiàn)在 atRule 前面插入內(nèi)容,移除這個(gè) 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' }),
      ])
    }
復(fù)制代碼

至于修復(fù) sourcemap 指向,可以看下面這段函數(shù),讓新生成的所有節(jié)點(diǎn)都指向了原來 atRule 的source,完美~

function updateSource(nodes, source) {
  return _.tap(Array.isArray(nodes) ? postcss.root({ nodes }) : nodes, tree => {
    tree.walk(node => (node.source = source))
  })
}
復(fù)制代碼

再講一個(gè) screen atRule

Instead of writing a raw media query that duplicates that value like this:

@media (min-width: 640px) {
  /* ... */
}
復(fù)制代碼

you can use the @screen directive and reference the breakpoint by name:

@screen sm {
  /* ... */
}
復(fù)制代碼

上面這個(gè)英文,是從文檔里面拷貝出來的, 就是常見的媒體查詢,我們可以寫死到 css,但是如果過段時(shí)間又要改呢?這里抽離出來到j(luò)s配置里面,我們通過讀取配置文件,默認(rèn)是 tailwind.config.js,里面的 theme.screens 對(duì)應(yīng) 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])
    })
  }
}

復(fù)制代碼

關(guān)于 tailwindcss 插件的寫法

tailwindcss 插件的寫法,具體怎么寫,這里不具體展開,這里講講原理。插件其實(shí)主要的作用就是把我們寫的 js css 配置生成一個(gè)初步的 css,然后再根據(jù)配置文件,進(jìn)行一個(gè)二次處理,最后生成實(shí)際的 css,拿一個(gè)內(nèi)置的 zIndex 的插件當(dāng)例子,主要是這個(gè)插件也寫了測(cè)試用例,結(jié)合文檔,介紹起來簡直不要太方便

首先我們看看這個(gè)插件是怎么寫的

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'))
  }
}

復(fù)制代碼

其實(shí)我們要關(guān)心的就是 addUtilities(utilities, variants('zIndex')) 這塊到底干嘛了,簡單地說,就是 zIndex 塞到 pluginUtilities 里面,也就是說,最后是對(duì)應(yīng) @tailwind utilities; ,可以看看測(cè)試用例中生成的是什么,如下,就是這個(gè)樣子,其中 utilities 還好理解,variants 是什么?variants 的使用可以看文檔的時(shí)候,說白了,就是會(huì)有 @variants responsive 把我們utilities中生成的代碼包住,這樣就初步生成我們的 css,然后 substituteResponsiveAtRules 這個(gè)會(huì)二次處理這個(gè) 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'],
    },
  ])
復(fù)制代碼

最后說一句

tailwindcss 還有很多玩法,具體可以去文檔挖掘,這里只是簡單說下它實(shí)現(xiàn)的原理,它給我?guī)淼恼鸷呈牵瑳]想到 postcss 或者說 css 還可以這么玩,腦洞太大了, ps: 為了了解這貨,基本把主要依賴庫的文檔瞄了一遍,:逃)

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容