かもメモ

自分の落ちた落とし穴に何度も落ちる人のメモ帳

Svelte 子コンポーネントのスタイルを親コンポーネントで指定したい

LP作って〜って言われたので、ほな Svelte でやってみよっかな〜と思い Svelte に入門しました。

Svelte はデフォルトでは CSS in JS でスコープのあるスタイルが書けるのですが、共通して使いまわしているコンポーネントのスタイルを親コンポーネントで指定するのにちょいハマったのでメモ。

Svelte プロジェクトの作成

$ npx degit sveltejs/template my-project
$ cd my-project
$ npm install
# TypeScript 化 (設定勝手にやってくれる)
$ node scripts/setupTypeScript.js
# Sass 使えるようにする
$ npm i -D sass

Sass の利用は TypeScript 化をしていたからなのかは不明ですが sass をインストールするだけで特に設定をしなくても .svelte ファイル内で <style lang="scss"> とすれば使えるようになりました。

  • svelte: ^3.0.0
  • rollup: ^2.3.4
  • typescript: ^4.0.0
  • sass: 1.37.5

コンポーネントのスタイルを親コンポーネントで指定する

Logo コンポーネント

// src/Logo.svelte
<img class="logo" src="/images/logo.svg" alt="ロゴ" />

App コンポーネント

// src/App.svelte
<script lang="ts">
  import Logo from './Logo.svelte';
</script>

<main role="main">
  <Logo />
</main>

<style lang="scss">
  .logo { widh: 100px; }
</style>

App コンポーネントから Logo の width を指定したいけど、Svelte の <style> は書かれている DOM だけをスコープにするようで、上記では上手く動作しません

:global を使う方法

:global(selector) でスコープのないグローバルなクラスが作成できることを利用して、このコンポーネントのクラス :global(子コンポーネントのクラス) で該当コンポーネントの時だけ該当するスタイルになる

// src/App.svelte
// Logo => <img class="logo" src="/images/logo.svg" alt="ロゴ" />

<main class="main" role="main">
  <Logo />
</main>

<style lang="scss">
  .main :global(.logo) { widh: 100px; }
</style>

この方法は global な CSS としてスタイルが作成されるので、親コンポーネントから子コンポーネントのスタイルを作成することができます。
ただ、親コンポーネントが子コンポーネントのクラス名やタグ名などの内部実装を知っていなければならないのでチョット微妙です。

cf. CSS Modules やっていき - かもメモ :global とか :local についてのメモ

コンポーネントからスコープ付きのクラス名を渡す方法

必要なパッケージのインストール

$ npm i -D svelte-preprocess-cssmodules svelte-as-markup-preprocessor

ビルドの設定の変更

rollup.config.js

+ const cssModules = require('svelte-preprocess-cssmodules');
+ const { asMarkupPreprocessor } = require('svelte-as-markup-preprocessor');
// …

export default {
  // …
  plugins: [
    svelte({
-     preprocess: sveltePreprocess({ sourceMap: !production }),
+      preprocess: [
+       asMarkupPreprocessor([
+         sveltePreprocess({ sourceMap: !production }),
+       ]),
+       cssModules(),
+     ],
      compilerOptions: {
        // enable run-time checks when not in production
        dev: !production
      },
    }),
  ],

コンポーネントからクラス名を指定できるようにする

/src/Logo.svelte

<script lang="ts">
  const addClass = $$restProps.class || '';
  const className = [...new Set(['logo', addClass])].join(' ');
</script>

<img class={className} src="/images/logo.svg" alt="ロゴ" />

コンポーネントで子コンポーネントのスタイルを指定する

src/App.svelte

<script lang="ts">
  import Logo from './Logo.svelte';
</script>

<main role="main">
  <Logo class="main-logo" />
</main>

// module 属性を付ける
<style lang="scss" module>
  .main-logo { widh: 100px; }
</style>

ポイントは親コンポーネント<style>module 属性を付けることです! module 属性が無いと子コンポーネントに上手くスタイルが設定できませんでした。

cf. Support classes on nested components by nikku · Pull Request #2888 · sveltejs/svelte · GitHub

<style module> の時に気をつけること

<style module> の時 p {} のようなタグでスタイルを指定していると意図せず global なスタイルを作成してしまう事になるので注意が必要です

module でない時
<section>
  My Content
</section>

<style lang="scss">
  section { width: 100%; }
</style>

👇 コンパイルするとタグ指定のものは自動でクラス名が付けられる

<section class="svelte-1dsdi5i">
  My Content
</section>

<style>
section.svelte-1dsdi5i { width: 100%; }
</style>
module の時
<section>
  My Content
</section>

<style lang="scss" module>
  section { width: 100%; }
</style>

👇 コンパイルしてもタグ指定のままになる

<section>
  My Content
</section>

<style>
section { width: 100%; }
</style>

全ての <section> に適応されるクラスになってしまう!!!

Svelte でスタイルを <style module> にする時は必ずクラスを付けてスタイルを適応したほうが安全!

これで npm run dev 又は npm run build で確認すると .main-logo のクラス名がハッシュ化され Logo コンポーネントに渡され App コンポーネントで指定したスタイルが適応されるようになりました!
₍ ᐢ. ̫ .ᐢ ₎ A W E S O M E !

おわり

新しい事を学ぶのは楽しい!
納期が無ければもっと楽しい…!!
だが、納期がないと手を付けない…!!!!


[参考]