一休.com Developers Blog

一休のエンジニア、デザイナー、ディレクターが情報を発信していきます

WebComponents でログインコンポーネントをつくってサービス横断で使えるようにした話

こんにちは。プロダクト開発部の渥美 id:atsumim です。
今回サービス横断で利用できるログインコンポーネントを WebComponents で実装したのでその紹介をします。

1. 背景

今年の2月に電話番号での会員登録及び認証機能をリリースしました。

これに伴って一休の会員基盤も刷新しました。
一休のサービスは主に、宿泊、レストラン、スパとあるのですが、 歴史的経緯により会員基盤が分散してしまっていたので、ひとつにまとめる狙いもありました。

f:id:atsumim:20210427113250j:plain
会員基盤 Before/After

その一環として、一休のサービスで横断して使えるログインコンポーネントを WebComponents で実装しました。 このコンポーネントにログインや会員登録の処理を集約し、新会員基盤へのインターフェースとするようにしました。
また、電話番号認証や2段階認証設定のモーダルも実装しました。下記が実際の画面です。

ログインモーダル 電話番号認証モーダル 2段階認証モーダル
f:id:atsumim:20210426161345p:plain
f:id:atsumim:20210426161308p:plain
f:id:atsumim:20210426161238p:plain

この記事ではログインモーダルに絞ってお話します。

2. 技術選定

技術選定するにあたって、条件は以下の通りでした。

  • ページ遷移を挟まずにログインができる
  • どのアプリケーションプラットフォームでも利用できる

1つ目の条件からモーダルコンポーネントを提供することはほぼ決まっていました。
予約入力をしている途中でログインページに遷移すると体験を損ねてしまうので、スムーズな予約を実現するためにはモーダルコンポーネントでの提供が必須でした。

2つ目の条件として、一休のサービスは主に Vue.js, Python テンプレート, ASP.NET 等のプラットフォームで 画面描画を行なっているのですが、どのプラットフォームでもログインができるようにする必要がありました。 そのためには Web 標準で使える WebComponents が適任でした。

WebComponents について詳しくはこちらの記事がよくまとまっています。

WebComponents の実装フレームワークには PolymerLitElement がありますが、 Vue CLI が標準で WebComponents をビルドできるのでこれを利用しました。 内部的には vue-web-component-wrapper が使われています。大変助かりました🙏

3. 実装

一部省略していますが、下記のインターフェースになるようにログインモーダルを実装しています。 実装したログインモーダルは <ikyu-login> という CustomElement で定義しました。 HTML に <ikyu-login> と書けば通常の HTML タグ同様に使えるようになります。

Attributes

Attribute Type Default Note
login-only Boolean false ログイン画面のみ表示するか
signup-only Boolean false 会員登録画面のみ表示するか
open Boolean false モーダルを表示するか

Events

Event Type Note
login Boolean ログイン及び会員登録成功
error Error ログイン及び会員登録失敗
close Boolean モーダルを閉じる

HTML への組み込み

実際に HTML への組み込みを見てみましょう。 CustomElement に属性を指定する場合は setAttribute 関数、イベントを取得する場合は addEventListener 関数を使います。

<html>
  <head>
    <meta charset="utf-8">
    <title>ログイン</title>
    <script src="https://unpkg.com/core-js-bundle@3.0.0-alpha.1"></script> // IE11 用
    <script src="https://unpkg.com/@webcomponents/webcomponentsjs/webcomponents-loader.js"></script> // IE11, Edge 用
    <script src="./ikyu-login.js"></script>
  </head>
  <body>
    <button onclick="openIkyuLogin()">open</button>
    <ikyu-login show-signup></ikyu-login>

    <script>
      const ikyuLogin = document.querySelector('ikyu-login');

      function openIkyuLogin() {
        ikyuLogin.setAttribute('open', true);
      }

      ikyuLogin.addEventListener('close', () => {
        ikyuLogin.setAttribute('open', false);
      });

      ikyuLogin.addEventListener('login', (status) => {
         // リダイレクトしたりする
      });
    </script>
  </body>
</html>

Vue への組み込み

Vue に組み込むときは通常の Vue コンポーネントと同様に propsevent のやりとりができます。 setAttributeprops, addEventListenerevent に置き換わるイメージです。

下記は実際の利用例です。

<template>
 <div>
   <button @click="openIkyuSignupOnly()">モーダルを開く</button>
    <ikyu-login
      :open="ikyuLoggingin"
      :login-only="ikyuLoginOnly"
      :signup-only="ikyuSignupOnly"
      @close="ikyuLoggingin = false"
      @login="reload"
      @error="onError"
    >
 </div>
</template>
<script lang="ts">

export default Vue.extend({
  data() {
    return {
      ikyuLoggingin: false,
      ikyuLoginOnly: false,
      ikyuSignupOnly: false,
    }
  },
  methods: {
    openIkyuSignupOnly() {
      this.ikyuLoginOnly = false;
      this.ikyuSignupOnly = true;
      this.ikyuLoggingin = true;
    },
    reload(status) {
      window.location.reload()
    },
    onError(error: Error) {
      console.log(error);
    },
  }
});
</script>

注意点として、Vue 内で CustomElements を利用するときは Vue コンポーネントとして見なされてしまうため、明示的に Vue コンポーネントではないことを宣言する必要があります。

Vue.config.ignoredElements = ['ikyu-login'];

InternetExplorer の対応

案の定 InternetExplorer では WebComponents が動作しないので、憎しみと愛を持って対応します。
pollyfill の読み込み、スタイル崩れなどなどありましたが結果なんとかなってよかったです。IE 許すまじ。

まずは IE および Edge 用に core-jswebcomponents-loader を読み込みます。

<script src="https://unpkg.com/core-js-bundle@3.0.0-alpha.1"></script> // IE11 用
<script src="https://unpkg.com/@webcomponents/webcomponentsjs/webcomponents-loader.js"></script> // IE11, Edge 用

vue-web-component-wrapper 内ではES6で実装しているのでそのままでは IE で動きません。許さない。 これに対処するため babel で pollyfill してやる必要があります。また、ES6 をトランスパイルするための prebuild を記述します。

package.json は以下のようになりました。

{
  "scripts": {
    "prebuild": "npm-run-all babel node_modules/@vue/web-component-wrapper/dist/vue-wc-wrapper.js -o node_modules/@vue/web-component-wrapper/dist/vue-wc-wrapper.js",
    "build": "cross-env vue-cli-service build --no-clean --target wc --inline-vue --name ikyu-login 'src/components/IkyuLoginAndSignup.vue'",
  },
  "dependencies": {
    "babel-loader": "8.1.0",
    "babel-plugin-transform-es2015-arrow-functions": "6.22.0",
    "core-js": "3",
  },
  "devDependencies": {
    "@babel/cli": "7.11.6",
    "@babel/core": "7.11.6",
    "@babel/plugin-syntax-async-generators": "7.8.4",
    "@babel/plugin-transform-arrow-functions": "7.10.4",
    "@babel/plugin-transform-regenerator": "7.10.4",
    "@babel/preset-env": "7.11.5",
    "babel-plugin-transform-async-to-generator": "6.24.1",
    "babel-plugin-transform-custom-element-classes": "0.1.0",
    "babel-plugin-transform-es2015-shorthand-properties": "6.24.1",
    "babel-plugin-transform-es2015-template-literals": "6.22.0",
  }
}

これで InternetExplorer でも WebComponents が使えるようになりました🎉
IE 特有のデザイン崩れ等も対応しつつ、モダンブラウザと遜色なく動作するようになっています。


余談ですが IE に悪態をつきながら対応していたら同僚から実績解除の称号を得ました😇

f:id:atsumim:20210427123507p:plain
Legendary Hate Speech...

4. 所感

WebComponents を使ってみてよかった点と改善点を挙げます。

よかった点

コンポーネント指向であること

ログインモーダルの他にも認証モーダルなどを実装したのですが、共通コンポーネントを使い回せたので実装コストがかなり減りました。

Vue.js との親和性が高い

一休のアプリケーションプラットフォームは Vue.js が多いので、 Vue コンポーネントと同様のコンテキストスイッチで実装できたのはよかったです。

改善点

そのまま配信しようとすると重くなってしまった

当然ですが、実装を進めていくとどんどんファイルサイズが大きくなってしまいます。 そのまま配信するとコンポーネントを読み込んでいるページパフォーマンスが下がってしまう懸念があるので gzip での圧縮やブラウザキャッシュを付けて改善することが必要となってきます。


結果的に各サービスでのログイン実装が簡潔になり、ログイン処理が新会員基盤に集約できました。
Web 標準でお手軽に再利用できるコンポーネントが必要になった場合は、是非 WebComponents の選択肢を考えてみてください。