2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

LIFULLその3Advent Calendar 2019

Day 20

単一コンポーネントライブラリ内のscoped cssの話

Last updated at Posted at 2019-12-20

私は本当にscoped cssが好きである。
cssのmodule設計は幾多あるが、だいたいがmoduleのネストで親子間の影響がネックになってmodule粒度が大きくなる傾向があった。
おそらくそういう文脈もあって、そこと向き合ったBEMはみんなに愛されているんだろうと思う。

ブラウザがネイティブでcssにscopeを与えようとした仕様は残念ながら消えてしまったがvue-loaderとかで変換される.vueファイルのようにcssにscopeを与えながら、単一コンポーネントをwebで実現する手段はいくつかある

vueファイルの例
<template>
  <div>
    <p class="heading">piyo</p>
  </div>
</template>

<style scoped>
  p {
    font-weight: bold;
  }
</style>

ここで定義されたpのスタイルは上述の.heading要素にしか当たらないようにコンパイルされる

compile後のコード

変換後のコードを見てみる

<style>
p[data-v-12345] {
  font-weight: bold;
}
</style>

<div data-v-12345>
  <p class="heading" data-v-12345>piyo</p>
</div>

するとhtml側にもstyle側にもそれぞれの要素・セレクタの末尾にdata属性が指定されている
セレクタの末尾にコンポーネントごとにユニークな[data-v-xxxx]を付与することでそれだけに効くスタイルができあがるというロジックらしい

簡易loader

踏まえてザルな簡易ローダーを作ってみるとこんな感じ

loader({
  template: `
    <div>
      <p class="heading">piyo</p>
    </div>
  `,
  style: `
    p {
      font-weight: bold;
    }
  `
});

function loader(component) {
  let hash = String(Date.now()); // 大雑把なuuid替わり

  return `
    <style>
      ${transformCSS(component.style, hash)}
    </style>
    ${transformHTML(component.template, hash)}
  `;
}
function transformHTML(html, hash) {
  let dataAttr = `data-${hash}`;
  return html.replace(/<([^/]+?)>/mg, (body, $1) => {
    return `<${$1} ${dataAttr}>`
  })
}
function transformCSS(style, hash) {
  let dataAttr = `[data-${hash}]`;

  return style.replace(/([^{])\s*\{\s*([^}]*?)\s*}/mg,(body, $1, $2) => {
    let selector = $1.split(',').map(v => {
      return `${v.trim()}${dataAttr}`;
    }).join(',');

    return `${selector} {
      ${$2}
    }`;
  })
}

出力

<style>      
p[data-1576831975938] {
  font-weight: bold;
}
</style>
    
<div data-1576831975938>
  <p class="heading" data-1576831975938>piyo</p>
</div>

selectorには@rule系とかあったり、擬似セレクタもあって末尾に必ずつけていいわけでもないのでcssのちゃんとしたparser使ってtransformした方がいいがおおよそ原理はこんな風になっているらしい。

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?