なぜCSSをJSに残して、古いCSSのプリプロセッサに戻ったのか


2021年5月30日CSS Varsに関する情報を追加.
私は以前JS(JSS)のCSSの大ファンでしたが、今は前処理されたCSSに戻ります.回帰?技術恐怖症?または正当な選択?説明しましょう.

問題点
第一に、JSSは単なる概念の証明ではない.文字通り「解決する」だけでなく、それらを緩和する手段を提供します.

1.1 .モジュールスコープのCSS
CSSは世界的にグローバルです.CSSファイルをモジュールにインポートすることはモジュールスコープのように見えるかもしれませんが、実際にはそうではありません.

CSS
.a {
  color: red;
}

A . JSX
import './A.css'
function A() {
  return <span class='b'>Hi</span>
}
あなたはその問題を見ますか.
アンサーA.jsx 用途b クラスで言及しないクラスA.css .
With JSS そして、エラーの種類が可能でさえないタイプスクリプト

東理
const useStyles = createUseStyles({
  a: {
    color: 'red';
  }
})

function A() {
  const styles = useStyles()
  return <span class={styles.b}>Hi</span>
}
A.tsx コンパイルしません.

1.2 .CSSとJS間の共有変数
つの可能な非JSSソリューションはcss-modules サポートcss-loader いくつかの設定が必要です.新しいブラウザにはCSS custom properties どちらとともgetComputedStyle .
JSSのものとして可能な限り簡単です:あなただけの通常のJSの変数を持っている-しかし、それを使用したい!
const itemHeight = 72
const useStyles = createUseStyles({
  item: {
    height: itemHeight,
  },
})

function Showcase({items, topItem}) {
  const styles = useStyles()

  return <div style={{translateY: -itemHeight * topItem}}>
    {
      items.map(item =>
        <div class={styles.item}>{item}</div>
      )
    }
  </div>
}

価格

2.1 .パフォーマンスペナルティ
バンドルオーバーヘッドstyled-components と61 KBreact-jss . また、あるruntime overhead , どちらis not argued libs著者によってさえ.

2.2 .devの経験は実際より悪い
エディタは、CSSを知っています.構文ハイライト、コード補完、その他の支援サービスを提供します.JSSでは、IDEがJSオブジェクト以上を見ているので、それらの多くを見逃してしまいます.
const styles = createUseStyles({
  btn: {
    border: '1px dark gray',
    boxSizing: 'border',
    padding: '4px 12px',
    whiteSpace: 'nowrap',
  },
});
ルックス退屈とエラーフレンドリー.ところで、あなたはスポットを見つけましたか?
アンサー
色はdarkgray , ないdark gray . IDEは助けません;しかし、CSSで、それは.
スタイルのコンポーネントの構文はまだ悪いIMOです.
const Btn = styled.button`
    border: 1px dark gray;
    boxSizing: border;
    padding: 0 12px 6px;
    whiteSpace: nowrap;
`

2.3 .LIBSはイライラするバグを含めることがあります
例えば、this one . この単純なクエリは動作しません.
const styles = createUseStyles({
  item: ({param}) => ({
    '@media (min-width: 320px)': {
      // ...
    },
  }),
})
問題は1歳ですそれは些細な使用ではなく、コーナーケース、まだそれはまだ開いているmaking devs suffer . なんと残念なことだろう.

3 . JSSの価値はありますか?
私は、技術を選ぶことはトレードオフの問題であると理解します誰かが短所を見つけるかもしれない.個人的には、JSSのパフォーマンスとdevの経験を疑う.
しかし、JSSなしで生きる方法?人気のあるオプションを見てみましょう.

3.1 .CSS modules
CSSモジュールもクラス名を生成するが、JSSと異なり、実行時オーバーヘッドを割り当てないコンパイル時に行う.仮定するconfigured everything correctly , 次のようになります.

ショーケースCSS
.showcase {
  display: flex;
}
.item {
  width: 33%;
}
.highlighted {
  background-color: lightgray;
}

ショーケースCSSd . td (生成)
export const showcase: string
export const item: string
export const highlighted: string

ショーケースTSX
import styles from './Showcase.css'

type Props = {items: string[], highlighted: number}

function Showcase({items, highlighted}: Props) {
  return <div className={styles.showcase}>{
    items.map((item, i) => {
      const c = `${styles.item} ${i===highlighted ? styles.highlighted : ''}`
      return <div className={c}>{item}</div>
    })
  }</div>
}
いいですね.これは、JSSの利点がランタイムペナルティが削除されます.しかし、あなたが見るようにtype definitions generated , なので、スムーズに開発するためには、適切なセットアップを行う必要があります.もちろん、devの経験を割引.

3.2 .BEM
BEMはおそらく最も有名なCSSクラス命名規則です.フルスペックは念入りに見えるかもしれませんが、本質はとても簡単です.
  • BEMは「ブロック、要素、修飾子」を表します
  • ブロックは、コンポーネントのトップレベルDOM要素です
  • ブロック名はプロジェクト内で一意でなければなりません
  • 要素はブロック内の何かです
  • 要素名block__element
  • 修飾子は、ブロックまたは要素を調整するクラスです
  • ブロック修飾子名block_modifier
  • 要素修飾子名block__element_modifier
  • CSSプリプロセッサとJSクラスprefixers 常に名前を繰り返す必要はありません.

    ショーケースSCSS
    .showcase {
      display: flex;
      &__item {
        width: 33%;
        &_highlighted {
          background-color: lightgray;
        }
      }
    }
    

    ショーケース日本学術振興会
    import './Showcase.scss';
    import {withNaming} from '@bem-react/classname';
    
    const cn = withNaming({e: '__', m: '_', v: '_' })
    
    const showcaseCn = cn('showcase');
    const itemCn = cn('showcase', 'item')
    
    function Showcase({items, highlighted}) {
      return <div className={showcaseCn()}>{
        items.map((item, i) => {
          const c = itemCn({highlighted: i===p.highlighted})
          return <div className={c}>{item}</div>
        })
      }</div>
    }
    
    
    

    BEMクラスは簡略化できるか
    私はbemに感謝します、しかし、接尾辞または長い名前を使用することは私に目立つようです.CSSコンバイナレーターと置き換えるとどうなるのでしょうか?試してみましょう.

    ショーケースSCSS
    .b-showcase {
      display: flex;
      >.item {
        width: 33%;
        &.highlighted {
          background-color: lightgray;
        }
      }
    }
    

    ショーケース日本学術振興会
    import './Showcase.scss';
    
    function Showcase({items, highlighted}) {
      return <div className='b-showcase'>{
        items.map((item, i) => {
          const c = `item ${i===p.highlighted ? 'highlighted' : ''}`
          return <div className={c}>{item}</div>
        })
      }</div>
    }
    
    より自然に見えるIMO.注意:
  • b- プレフィックスは非ブロック名で衝突を避けるために必要です

  • Descendant combinator 入れ子になったブロックから要素を予期せずに選択することがありますので使用しません
  • 要素の深さが不明な場合、BEM
  • 非常に大きなアプリケーションでは、子セレクタは、やや単純なBEMのクラスよりも遅く動作することがあります一方、プリフィックスを使用しないランタイムを保存します

  • どのようにブロッククラスは、大規模なアプリケーションで一意です確認するには?
    それはおそらくBEMの最も難しい部分です.しかし、助けを借りてscss-parser scssファイルを解析して検証するプログラム(またはWebpackプラグイン)を書くことは可能です.

    検証します.ts(簡略化)
    import {parse} from 'scss-parser'
    
    const clsToFile = new Map<string, string>()
    for await (const file of walkDir(__dirname)) {
      const cn = getTopLevelClass(String(await fs.promises.readFile(file)))
      if (!cn) {
        throw new Error(`No top level class: ${file}`)
      }
      if (clsToFile.has(cn)) {
        throw new Error(`Duplicate class '${cn}' in ${clsToFile.get(cn)} and ${file}` )
      }
      clsToFile.set(cn, file)
    }
    
    // Walks a dir recursively yielding SCSS files
    async function* walkDir(dir: string): AsyncGenerator<string> {
      // ...
    }
    
    // Returns top-level class if there is one
    function getTopLevelClass(scss: string) {
      const ast = parse(scss)
      // ...
    }
    
    完全妥当性検査.TS
    import {parse, Node} from 'scss-parser'
    import fs from 'fs'
    import path from 'path'
    
    main()
    
    main() {
      const clsToFile = new Map<string, string>()
      for await (const file of walkDir(__dirname)) {
        const cn = getTopLevelClass(String(await fs.promises.readFile(file)))
        if (!cn) {
          throw new Error(`No top level class: ${file}`)
        }
        if (clsToFile.has(cn)) {
          throw new Error(`Duplicate class '${cn}' in ${clsToFile.get(cn)} and ${file}` )
        }
        clsToFile.set(cn, file)
      }
    }
    
    async function* walkDir(dir: string): AsyncGenerator<string> {
      const entries = await fs.promises.readdir(dir, {withFileTypes: true})
      for (const e of entries) {
        const file = path.resolve(dir, e.name)
        if (e.isFile() && /\.scss$/.exec(e.name)) {
          yield file
        } else if (e.isDirectory()) {
          yield* walkDir(file)
        }
      }
    }
    
    function getTopLevelClass(scss: string) {
      const ast = parse(scss)
      if (Array.isArray(ast.value)) {
        const topLevelClasses = ast.value
          .filter(node => node.type === 'rule')
          .flatMap(ruleNode => ruleNode.value as Node[])
          .filter(node => node.type === 'selector')
          .flatMap(selectorNode => selectorNode.value as Node[])
          .filter(node => node.type === 'class')
          .flatMap(classNode => classNode.value as Node[])
          .filter(node => node.type === 'identifier')
          .map(identifierNode => identifierNode.value as string);
        if (topLevelClasses.length === 1) {
          return topLevelClasses[0];
        }
      }
    }
    

    変数を共有するには?
    これは簡単ではありませんが、オプションがあります.
  • With getComputedStyle あなたは、それを含む効果的に適用されたCSS値を得るかもしれませんcustom property (新しいブラウザのみ)
  • 要素のサイズとオフセットを取得するにはgetBoundingClientRect
  • 代わりに何かを使用することがありますアニメーションのタイミングに基づいてスケジューリングonanimationend and ontransitionend (新しいブラウザのみ)
  • これらがあなたのニーズに合わないならば、あなたは若干の命名規則を導入するかもしれません:

    ショーケースSCSS
    $shared-pad-size: 6px;
    
    .showcase {
      padding: $pad-size;
      // ..
    }
    

    ショーケース日本学術振興会
    const sharedPadSize = 6;
    
    export function Showcase() {
       // ...
    }
    

    3.3 .Tailwind CSS
    正直なところ、私はそれが好きではありませんが、2021年にCSSの話をすることはできません.そうです.devsだけでなく、それについて議論するだけでなく.それは楽しいですが、私は脇に滞在します😉

    3.4 .Web components
    それは全く別の世界です.それはまだ完全にすべての主要なブラウザーによってサポートされていない新しいです.おそらくそれは将来の主流である🙂

    最後に.何を選ぶか
    それは厳しい.銀弾丸はありません、妥協とトレードオフがあります.私は、連結またはちょうど境界なしで境界線を好みます.それで?