@ledsun blog

Hのキーがhellで、Sのキーがslaveだ、と彼は思った。そしてYのキーがyouだ。

sprockets-css-purgerのAPIを考えるために、tailwindcssとPurgeCSSの境界を考える

sprockets-css-purgerの姿を想像する - @ledsun blogの続きです。

tailwindcssからPurgeCSSを呼び出している箇所

https://github.com/tailwindlabs/tailwindcss/blob/v2.2.15/src/lib/purgeUnusedStyles.js#L188-L220

      const purgeCSS = new PurgeCSS()
      purgeCSS.options = {
        ...defaultOptions,

        defaultExtractor: (content) => {
          const transformer = getTransformer(config)
          return defaultExtractor(transformer(content))
        },
        extractors: fileSpecificExtractors,
        ...purgeOptions,
        safelist: standardizeSafelist(purgeOptions.safelist),
      }

      if (purgeCSS.options.variables) {
        purgeCSS.variablesStructure.safelist = purgeCSS.options.safelist.variables || []
      }

      const fileFormatContents = content.filter((o) => typeof o === 'string')
      const rawFormatContents = content.filter((o) => typeof o === 'object')

      const cssFileSelectors = await purgeCSS.extractSelectorsFromFiles(
        fileFormatContents,
        purgeCSS.options.extractors
      )
      const cssRawSelectors = await purgeCSS.extractSelectorsFromString(
        rawFormatContents,
        purgeCSS.options.extractors
      )
      const cssSelectors = mergeExtractorSelectors(cssFileSelectors, cssRawSelectors)
      purgeCSS.walkThroughCSS(css, cssSelectors)
      if (purgeCSS.options.fontFace) purgeCSS.removeUnusedFontFaces()
      if (purgeCSS.options.keyframes) purgeCSS.removeUnusedKeyframes()
      if (purgeCSS.options.variables) purgeCSS.removeUnusedCSSVariables()

PurgeCSSを初期化します。

      const purgeCSS = new PurgeCSS()

オプションオブジェクトを作ります。

      purgeCSS.options = {
        ...defaultOptions,

        defaultExtractor: (content) => {
          const transformer = getTransformer(config)
          return defaultExtractor(transformer(content))
        },
        extractors: fileSpecificExtractors,
        ...purgeOptions,
        safelist: standardizeSafelist(purgeOptions.safelist),
      }
  • defaultOptions
  • defaultExtractor
  • fileSpecificExtractors
  • purgeOptions

あたりはソースコードの上の方を見ないとどんな値が入るかわかりません。 またpurgeOptionsあたりは、ユーザーが設定する値に依存すると予想できます。 PurgeCSSのオプションの意味を確認してみた方が良さそうです。

      if (purgeCSS.options.variables) {
        purgeCSS.variablesStructure.safelist = purgeCSS.options.safelist.variables || []
      }

オプションの値を詰め替えています。 なぜこの作業が必要なのかはpurgeCSSのAPIを見ないとわからなさそうです。

      const fileFormatContents = content.filter((o) => typeof o === 'string')
      const rawFormatContents = content.filter((o) => typeof o === 'object')

      const cssFileSelectors = await purgeCSS.extractSelectorsFromFiles(
        fileFormatContents,
        purgeCSS.options.extractors
      )
      const cssRawSelectors = await purgeCSS.extractSelectorsFromString(
        rawFormatContents,
        purgeCSS.options.extractors
      )

contentには文字列とオブジェクトが入るようです。 オブジェクトに入る形式がよくわかりません。

      const cssSelectors = mergeExtractorSelectors(cssFileSelectors, cssRawSelectors)
      purgeCSS.walkThroughCSS(css, cssSelectors)

これがpurgeCSSのメイン処理のようです。 cssとcssSelectorsにどのような値が期待されているのか、purgeCSSのAPIを確認すると良さそうです。

      if (purgeCSS.options.fontFace) purgeCSS.removeUnusedFontFaces()
      if (purgeCSS.options.keyframes) purgeCSS.removeUnusedKeyframes()
      if (purgeCSS.options.variables) purgeCSS.removeUnusedCSSVariables()

これはわかりやすいです。 オプションがあったらpurgeCSSのオプショナルな機能を実行します。

これを見るだけでも、tailwindcssは、予想していたよりも複雑な処理をしてからpugreCSSを呼び出しています。 これを踏まえて、tailwindcssとpugreCSSの境界をどこに設けるか考えてみましょう。

tailwindcssとpugreCSSの境界

雑に2パターンが考えられます。

  1. tailwindcssのオプション型のオブジェクトを受け取って、tailwindcssと同じ処理をしてpurgeCSSを呼び出す
  2. purgeCSSのオプション型のオブジェクトを受け取って、そのままpugreCSSを呼び出す

2の方が、汎用的なAPIになるはずです。 ですが、呼び出し側は複雑なロジックが必要です。 少なくとも、前述のようにさっと読み流せない程度には複雑です。 そんな使い方を要求できるのでしょうか?

1は1で、tailwindcssのロジックへの追従をどうするのか?という問題があります。 tailwindcssのバージョンアップ毎にロジックを修正していくのでしょうか?

第3のパターンとして、purgeCSSの機能をすべてを使えない、制限されたシンプルなAPIを新しく設計した方がいいのかもしれません。 WebPackerのイメージです。