Lightning Web コンポーネントでサードパーティ製 CSS を適用する

この記事は [Salesforce Platform Advent Calendar 2020 - Qiita](https://qiita.com/advent-calendar/2020/salesforce-platform) 第22日目の投稿です。

## サードパーティ製のスタイルシートを使いたい
Lightning プラットフォーム上での開発では通常、Lightning Experience にあわせて Salesforce の [Lightning Design System(SLDS)](https://www.lightningdesignsystem.com/)を利用して頂くのをお勧めします。これは Lightning Experience の既存ユーザーがカスタムコンポーネントを利用する際の学習コストを下げるためでもあります。

しかし、企業によってはこだわりのデザインを Lightning Experience でも使いたい場合やサードパーティの CSS を適用したい場合が発生します。この記事では、 Salesforce の Lightning Web Component の開発で既存の CSS を適用する方法をご紹介します。

## サードパーティ製 CSS は CSS モジュールを作成して読み込む
コンポーネントから SLDS 以外のカスタム CSS を読み込む方法は 2 つあります。

- [loadStyle() を使って静的リソースから非同期で読み込む](https://developer.salesforce.com/docs/component-library/documentation/en/lwc/js_third_party_library)
- [CSS モジュールを作成し、スタイルシートから import する](https://developer.salesforce.com/docs/component-library/documentation/en/lwc/js_third_party_library)

その中でも、自身で作成したものではなくサードパーティ製のスタイルシートを読み込む際には、後者の[CSS モジュールを作成し、スタイルシートから import する](https://developer.salesforce.com/docs/component-library/documentation/en/lwc/js_third_party_library)方法がおすすめです。


## loadStyle() による非同期読み込みは、コンポーネントのスコープが効かない
一旦 Salesforce の静的リソースにスタイルシートをアップロードし、JavaScript の `loadStyle()` で呼び出す方法は、**スタイルシートがページ全体に読み込まれるため、サードパーティ製のスタイルシートではクラス名の競合が発生しスタイルが干渉してしまう**事があります。
例として、予め静的リソースにアップロードしておいた [Bootstrap](https://getbootstrap.jp/) のスタイルシートを、JavaScript の `loadStyle()` で読み込んでみます。

```js:path/to/lwc/bootstrapExample/bootstrapExample.js
import { LightningElement } from 'lwc';
import bootstrap from '@salesforce/resourceUrl/Bootstrap';
import { loadStyle } from 'lightning/platformResourceLoader';

export default class BootstrapEdit extends LightningElement {
    renderedCallback() {
        loadStyle(this, bootstrap + '/css/bootstrap.min.css');
    }
}
```

そして、HTML テンプレートで Bootstrap のクラスを指定します。

```html:path/to/lwc/bootstrapExample/bootstrapExample.html
<template>
    <div class="card">
        <div class="card-body">
            <h5 class="card-title">Bootstrap - レコード編集</h5>
            <form>
                <div class="row">
                    <div class="col col-6">
                        <div class="form-group">
                            <label>姓</label>
                            <input type="email" class="form-control"
                                   placeholder="Enter email"/>
                        </div>
                    </div>
                    <div class="col col-6">
                        <div class="form-group">
                            <label>名</label>
                            <input type="email" class="form-control"
                                   placeholder="Enter email"/>
                        </div>
                    </div>
                    <div class="col col-6">
                        <div class="form-group">
                            <label>Eメール</label>
                            <input type="email" class="form-control"
                                   placeholder="Enter email"/>
                        </div>
                    </div>
                </div>
                <div class="mx-auto" style="width: 200px;">
                    <button type="submit" class="btn btn-primary">保存</button>
                    <button type="button" class="btn btn-danger ">キャンセル</button>
                </div>
            </form>
        </div>
    </div>
</template>
```


以下は、コンポーネントをページに配置した時のスクリーションです。少し分かりづらいですが、右上のアイコンが見切れてたり、ボタンの位置も上下に少しズレているのが分かります。他にもページヘッダやタブのフォントも変わってしまっています。
このように、**読み込んだスタイルシートがページ全体に影響しており、本来スタイルが適用されるべきでない場所にまで適用されている**のが分かります。

![Screen_Shot_2020-12-15_at_11_54_38.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/26378/40c81a0d-381a-25d2-039b-b30b203e4ee2.png)

このような干渉は、スタイルシート側でセレクタにプリフィックスなどを追加する事で解消できますが、巨大なスタイルシートのセレクタを一つ一つ修正していくのは現実的ではありません。そのため、コンポーネントのスコープがきちんと適用される方法でスタイルシート読み込む必要あります。

## スタイルシートから import することで、コンポーネントのスコープを適用する
Lightning Web コンポーネントではスタイルシートのみを持つコンポーネントを作成する事ができます。これはファイル構造上、コンポーネントの一種に見えますが、実際には単なる CSS モジュールで、他のコンポーネントのスタイルシートからインポートする事ができます。また **import されたスタイルはコンポーネント内の HTML にだけ適用されるので、コンポーネント外のスタイルと干渉する事もありません。**先程の例と同様に Bootstrap のスタイルシートを読み込んでみましょう。

まずは、 Bootstrap のスタイルシートを　CSS モジュール化します。 このスタイルシートはそのままだと、ファイルあたりの文字数制限を超えてしまうので、構文上問題の箇所で ２つのモジュールに分割し `bootstrapStyleA.css` と `bootstrapStyleB.css` として作成しました。また、サードパーティ製のスタイルシートは Web コンポーネントでの利用を想定してないことが多いので、 不要なセレクターなどは Salesforce が提供する VS Code の拡張のメッセージに従って削除、または変更しておきます。

```css:path/to/lwc/bootstrapStyleA/bootstrapStyleA.css
/* Bootstrap スタイルシートの前半部分を貼り付け */
html{--blue:#007bff;--indigo:#6610f2;--purple:#6f42....
```
```css:path/to/lwc/bootstrapStyleB/bootstrapStyleB.css
/* Bootstrap スタイルシートの後半部分を貼り付け */
@media (min-width:576px){.navbar-expand-sm{-ms-fl....
```

次に、コンポーネント側の CSS ファイルから、上で作成した２つの CSS モジュールをインポートします。

```css:path/to/lwc/bootstrapExample/bootstrapExample.css
@import 'c/bootstrapStyleA';
@import 'c/bootstrapStyleB';
```

上で作成したコンポーネントをそのまま編集している場合は JavaScript による静的リソースの非同期読み込みは動かないようにしておきましょう。

```js:path/to/lwc/bootstrapExample/bootstrapExample.js
import { LightningElement } from 'lwc';
//import bootstrap from '@salesforce/resourceUrl/Bootstrap';
//import { loadStyle } from 'lightning/platformResourceLoader';

export default class BootstrapEdit extends LightningElement {
    renderedCallback() {
        //loadStyle(this, bootstrap + '/css/bootstrap.min.css');
    }
}
```

これで、デプロイしてみると下のように、コンポーネントには Bootstrap のスタイルが適用されながらも、その他のスタイルに影響がないのが分かります。
![Screen Shot 2020-12-15 at 11.48.29.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/26378/d515aa1b-3109-7aa5-9ea0-9c0909a90827.png)

## まとめ
今回は、Lightning Web コンポーネントでサードパーティの CSS を適用方法について紹介しました。スタイルの干渉でお困りの方は一度試して見てはいかがでしょうか？