技術をかじる猫

適当に気になった技術や言語、思ったこと考えた事など。

LightningWebComponent(OSS)で Bootstrap を読み込む

LightningWebComponent をイジってみて、Web Component としてシンプルだなーOSS版も使ってみるかなーと思ったらいきなりドハマリしたのでメモ。

OSS版 LWC を始める

といってもやり方は恐ろしく単純で

$ npx create-lwc-app my-app

これだけでテンプレができる。
とりあえず Web アプリを指定した。

$ npm run watch

これで http://loclhost:3002 にサーバが立つので実行できる。

Bootstrap を読み込んだ(失敗版)

まず、Bootstrap を読み込んで失敗したケース。

index.html でこんなことをしてみたのだが

        <!-- styles -->
        <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css" integrity="sha384-9aIt2nRpC12Uk9gS9baDl411NQApFmC26EwAOH8WgZl5MYYxFfc+NcPb1dKGj7Sk" crossorigin="anonymous">
        <!-- js -->
        <script src="https://code.jquery.com/jquery-3.5.1.slim.min.js" integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj" crossorigin="anonymous"></script>
        <script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js" integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo" crossorigin="anonymous"></script>
        <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/js/bootstrap.min.js" integrity="sha384-OgVRvuATP1z7JjHLkuOU7Xw704+h835Lr+6QL9UvYjZE3Ipu6Tp75j7Bh/kR0JKI" crossorigin="anonymous"></script>

適用される範囲は LWC アプリケーションの外側のみという残念なことに。

何が起こったのかと思って挙動を調べて見ると、どうも shadow domコンポーネント毎に style が完全独立してて、相互の CSS の影響をうけないんだとかなんとか…
共通的なフォントサイズとかフォントファミリとかどないせーっちゅーねん(汗

と思って調べて見ると

developer.salesforce.com

Because native Shadow DOM is enabled out-of-the-box for Open Source, you can’t just use a global stylesheet that then allows to cascade styles across inheriting Lightning web components. Everything is truly encapsulated, which is one of the huge benefits. ネイティブのShadowDOMはオープンソースですぐに使用できるため、継承するLightningWebコンポーネント間でスタイルをカスケードできるグローバルスタイルシートを使用することはできません。すべてが本当にカプセル化されており、これは大きなメリットの1つです。 You will have to rethink your CSS strategy when it comes to building Lightning web components, or if you want to reuse components that you built and styled on Lightning Platform. Lightning Webコンポーネントの構築に関して、またはLightning Platformで構築およびスタイル設定したコンポーネントを再利用する場合は、CSS戦略を再考する必要があります。 On the other side, you can choose with LWC Open Source to use synthetic shadow as an easier way to interoperate with existing UI if needed. 一方、必要に応じて既存のUIと相互運用するためのより簡単な方法として、合成シャドウを使用するようにLWCオープンソースを選択できます。

Oh...

共通CSS取り込み戦略

ということで、共通CSSを取り込み戦略を調べて見ると

lwc.dev

実際にやってみると

f:id:white-azalea:20201023204303p:plain

CSS だけのモジュールを用意して

f:id:white-azalea:20201023204340p:plain

こんな感じで実行すると

f:id:white-azalea:20201023205646p:plain

てな感じ。
でも目標はあくまで Bootstrap を使うこと。

なので、一旦は cssCommon を削除する(次のトライの邪魔になる)。

Bootstrap を取り込む戦術

Bootstrap を npm 経由で取り込んで CSS で取り込む。

$ npm install bootstrap

としたら、lwc-services.config.js ファイルを開いて、resources をいじくります

module.exports = {
    resources: [
        { from: 'src/client/resources/', to: 'dist/resources/' },
        { from: 'node_modules/bootstrap/dist', to: 'src/bootstrap' },
        { from: 'node_modules/bootstrap/dist', to: 'dist/bootstrap' }
    ],
// 以下略

ここまで書いたら、

npm run build:development

実行するとこんな感じになる

f:id:white-azalea:20201023202536p:plain

ソースディレクトリに Bootstrap を強制配置。
ここまでやったら、全てのコンポーネントの先頭でこんな事すればまずは Bootstrap が使える。

<template>
  <link href="/styles/salesforce-lightning-design-system.css"
    rel="stylesheet" type="text/css" />
</template>

ただし、コレだとコンポーネントすべてで書かなきゃいけないので色々ダメ。
なので、もう少しアレな手段をとってみる。

さっきのように cssCommon コンポーネントを作るけど、今度は Javascript ファイルのみ用意する。
中身はコレ

import { LightningElement } from 'lwc';

export default class CssCommonElement extends LightningElement {

    _bootStrapCss() {
        let _bootstrap = '../bootstrap/css/bootstrap.min.css';
        const styles = document.createElement('link');
        styles.href = _bootstrap;
        styles.rel = 'stylesheet';
        return styles;
    }

    connectedCallback() {
        // テンプレートに無理やり Bootstrap の CSS 挿入
        this.template.appendChild(this._bootStrapCss());
    }
}

connectedCallback() はライフサイクルイベントの一種で、親コンポーネントの下に配置された際に実行されるコールバック。
つまり、初期化が終わって、コンポーネントが表示待機状態に入ったことを意味する。

ここで、スタイルシートを無理やり適用している。

あとは画面上に表示されるコンポーネント全てで LightningElement の代わりに CssCommonElement を継承する。

import CssCommonElement from 'todo/cssCommon';

export default class App extends CssCommonElement {}

この状態で html テンプレートに

<template>
    <button class="btn btn-primary">Bootstrap ぼたーん</button>
</template>

と記述すれば

f:id:white-azalea:20201023212427p:plain

ただし、欠点もある…というのも、CSS ではなくて Js (つまり JQuery を使ったDOM操作)を行う機能はうまく取り込めないらしい。
というのも shadow dom 使ってるせいで、id とかがうまく取れないことが原因のようだ…デスヨネ…

ちなみにこの対応で作ったものがコレ

github.com

参考にどうぞ