LitElementでLight DOMのWeb Componentを作る方法

はいさい！ちゅらデータのオースティンやいびーん！

# 概要
Web Componentsの作成を助けてくれるLitでShadow DOMではなく、Light DOMにレンダーする部品を作る方法を紹介します！

## 背景
LitElementで部品を作ると、デフォルトで<a href="https://developer.mozilla.org/ja/docs/Web/Web_Components/Using_shadow_DOM">Shadow DOM</a>を追加し、そのShadow DOMにHTMLエレメント、CSSのスタイルなどをレンダーしていきます。

しかし、Shadow DOMではなく、Light DOMにレンダーしたい場合があります。
1. グローバルCSSを適応したい時
1. ログインフォームでブラウザのキーマネージャーと相性良く付き合いたい時（Chrome、SafariはShadow DOMに対してパスワードの自動入力・保存ができないのです）
1. Light DOMの\<form>に入る入力部品を作りたい時

こういう時に以下の方法でLight DOMにレンダーしていただけます！

# コード
LitElementは、customElement.defineでブラウザに登録された時に、内蔵化されているLitElement.createRenderRootを実行して、部品のHTMLをどこに入れるかを定めます。

この関数を以下のように上書きすると、this.shadowRootではなく、thisをRenderRootに指定することができます。
```typescript:src/lit-element.ts
import { LitElement, html } from "lit";

export default class LitComponent extends LitElement {
  protected createRenderRoot() {
    return this; // Light DOMを使うように
    // thisがLight DOMの<lit-component>のエレメントに当たります
  }

  #handleSubmit: EventListener = (event) => {
    event.preventDefault();
  };

  render() {
    return html`
      <h1>Lit Form</h1>
      <form @submit=${this.#handleSubmit}>
        <label for="name">名前</label>
        <input type="text" id="name" name="name" />
      </form>
    `;
  }
}
```
これで、この部品はLight DOMにレンダーされるようになります！

## 実際にブラウザで見てみよう。
Webpackなどのバンドラーのエントリーポイントで上記のLitComponentを読み込んで、customElements.defineでブラウザに登録します。
```typescript:src/index.ts
import LitComponent from "./lit-component"

customElements.define("lit-component", LitComponent);
```
また、HTMLのテンプレートに\<lit-element>を入れます。また、Light DOMの\<style>タグでh1の文字の色を赤にしています。

\<lit-element>内の\<h1>が赤くなっていれば、Light DOMにレンダーできていることがわかります。
```html
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>
  <style>
    h1 {
      color: red;
    }
  </style>
  <lit-component></lit-component>
</body>

</html>
```
これをビルドして、ブラウザで開いてみると、![スクリーンショット 2022-07-08 11.53.36.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2344956/e2ac8567-e2f3-9cb5-81c4-da0b9a84b71e.png)
出た！赤いね。

そして、InspectorでHTMLを見てみると、
![スクリーンショット 2022-07-08 11.54.33.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2344956/620cf78f-ffb7-3efd-4840-c175181eea39.png)
Shadow DOMはなく、Light DOMにレンダーされていることが確認できます。

## 注意点： LitElementのstylesが使えなくなる！
このように、Light DOMにレンダーすると、通常のLitElementのCSSを指定するstatic styles = css``が使えなくなります。

通常は、以下のように書きます。
```typescript
import { LitElement, html, css } from "lit";

export default class LitComponent extends LitElement {
  #handleSubmit: EventListener = (event) => {
    event.preventDefault();
  };

  static styles = css`
    input {
      height: 40px;
    }
  `;

  render() {
    return html`
      <h1>Lit Form</h1>
      <form @submit=${this.#handleSubmit}>
        <label for="name">名前</label>
        <input type="text" id="name" name="name" />
      </form>
    `;
  }
}
```
しかし、上記のLight DOMをRenderRootに指定する方法だと、static stylesが反映されません。

なぜなら、実は、LitElement.createRenderRootでは、デフォルトで以下のようなコードを実行しているからです。
```typescript
import { LitElement, html, css, adoptStyles } from "lit";

...

protected createRenderRoot() {
    this.attachShadow({ mode: "open" });
    adoptStyles(this.shadowRoot!, [LitComponent.styles])
    return this.shadowRoot; 
}
```
このcreateRenderRootを上書きすると、lit.adoptStylesがなくなってしまいます。もっと残念な知らせですが、このadoptStylesにthisだけを渡すことができないようです。

これで、以下のような工夫が必要になります。
```typescript
import { LitElement, html, css } from "lit";

export default class LitComponent extends LitElement {
  protected createRenderRoot() {
    return this; // Light DOMを使うように
    // thisがLight DOMの<lit-component>のエレメントに当たります
  }

  #handleSubmit: EventListener = (event) => {
    event.preventDefault();
  };

  render() {
    const styles = css`
      input {
        height: 40px;
      }
    `;
    return html`
      <style>${styles}</style>
      <h1>Lit Form</h1>
      <form @submit=${this.#handleSubmit}>
        <label for="name">名前</label>
        <input type="text" id="name" name="name" />
      </form>
    `;
  }
}
```

# まとめ
これで、LitElementをShadow DOMからLight DOMにレンダーするように指定する方法を紹介してきましたが、いかがでしょうか？

次の記事では、Shadow DOMとLight DOMの両方にレンダーする方法を紹介します！