DEV Community

dala00
dala00

Posted on • Originally published at crieit.net

VueでWeb Componentsミニゲーム認証サンプル有り

Vue.jsでWeb Componentsが作れるようになっているようですね。VueがWeb Componentsっぽい、ということではなく、本物のWeb Componentsを生成できます。試しにちょっとしたミニゲームを含めたサンプルを作ってみましたので実際にどのように作成できるのかを解説してみます。

今回はvue-cli3で作成したプロジェクトで実装しています。

成果物

下記のようなものを作りました。これは何かというと、よくWebサイトのお問い合わせフォームなどに「私はロボットではありません」のようなチェックを入れないと送信できない機能がありますが、あれです。

web component.png

画像の下にあるのは単なるSubmitボタンで、上部画像はよくある一般的な15パズルです。15パズルを解かないとSubmitボタンが有効にならないようになっています。

引き続き解説していきますが、記事の最後に動かせるGitHubページへのリンクがあるので読むのが面倒な方がいれば、僕は全然気にしませんのでそちらを見てしまってください(肩を落としながら)

普通のVue.jsのコンポーネントと何が違うの?

普通のVue.jsのコンポーネントであればHTMLは下記のようになっていると思います。

<div id="app">
  <puzzle-button src="/img.png"></puzzle-button>
</div>
Enter fullscreen mode Exit fullscreen mode

id="app"のdivでVueコンポーネントを動作させる形ですね。

これと違って、Web Componentsは自分で独自タグを定義するものなので、このdivが不要になります。

<puzzle-button src="/img.png"></puzzle-button>
Enter fullscreen mode Exit fullscreen mode

どうやるの?

今はもうVueに@vue/web-component-wrapperというものが含まれているため、基本的にはそれを使うだけです。vue-cliを使う前提で作られているようですが、直接webpackでも可能なようです。

vuejs/vue-web-component-wrapper: Wrap a Vue component as a web component / custom element.

通常Vueを起動する際はVueのインスタンスを作成すると思いますがその作業は不要で、代わりに上記でWeb Componentsを生成してそれを登録するだけになります。

import Vue from 'vue'
import wrap from '@vue/web-component-wrapper'
import PuzzleButton from './components/PuzzleButton.vue?shadow'

window.customElements.define('puzzle-button', wrap(Vue, PuzzleButton));
Enter fullscreen mode Exit fullscreen mode

これだけでpuzzle-buttonタグが使えるようになります。

通常のVue.jsによる開発と違う点

いくつかちょっと通常のVue.jsによる開発方法とは違う点があるため、解説してきます。

プロパティの型

Vue.jsのコンポーネントの場合、プロパティに型が指定されている場合は下記のようにしてbindする必要があります。

<calc :value="100000"></calc>
Enter fullscreen mode Exit fullscreen mode

上記のような感じで、valueがNumberの場合はbindして直接数値を指定してあげる必要があります。ただ、Web CompoentsはVue.jsの構文とは関係がないため、コロンを付けてbindすることはできません。普通に属性を指定するだけになります。

<calc value="100000"></calc>
Enter fullscreen mode Exit fullscreen mode

前述した@vue/web-component-wrapperのREADMEにも書かれていますが、ちゃんとプロパティの定義に型が指定されていれば、コンポーネント内ではその型の値として初期化され使用することができるようになっています。

CSSの定義

Vue.jsではvueファイル内にてCSSを定義することができます。ところが、Web ComponentsにはShadow DOMという概念があり、これによってWeb ComponentsのDOM内を大元の呼び出し元のDOMと隔離することができるようになっています。これによりscopedの有無に関わらず、vueファイル内で定義したCSSが反映されなくなってしまいます。なぜかというとこのstyleタグが呼び出し元のHTMLのhead内に挿入されるためです。

そのため、下記のような設定により、styleタグがShadow DOMの内側に挿入されるように設定して上げる必要があります。

(vue.config.jsというファイルを作成)

module.exports = {
  chainWebpack: config => {
    config.module
      .rule('vue')
      .use('vue-loader')
        .loader('vue-loader')
        .tap(options => {
          options.shadowMode = true;
          return options;
        });

    config.module
      .rule('css')
      .oneOf('vue')
      .use('vue-style-loader')
        .tap(options => {
          options.shadowMode = true;
          return options;
        });
  }
}
Enter fullscreen mode Exit fullscreen mode

serveしてホットロードする時は問題ないのですが、productionビルドの時はちょっとCSSのビルド方法が変わってしまうためエラーになります。そのため、VUE_CLI_CSS_SHADOW_MODEという環境変数をtrueにしてビルドする必要があります。

僕はcross-envを使ったので、同じようにするのであればまずcross-envをインストールします。

yarn add cross-env
Enter fullscreen mode Exit fullscreen mode

あとはpackage.jsonのbuildのscriptsにcross-envの記述を追加します。

  "scripts": {
    "serve": "vue-cli-service serve",
    "build": "cross-env VUE_CLI_CSS_SHADOW_MODE=true vue-cli-service build",
    "lint": "vue-cli-service lint"
  },
Enter fullscreen mode Exit fullscreen mode

これでbuildも正常にできるようになります。

成果物

ソースはGitHubに公開してあります。

vue-web-components-sample - GitHub

動かせるサンプルもあります。polyfillは入れていませんので、対応しているブラウザで試す必要があります。僕はChromeとかAndroidとかでは確認できました。

動かせるサンプル

まとめ

Web Componentsは細かいパーツとか機能を使い回すのに非常に適していると思いました。ビルドして配布すれば、誰でも読み込んでタグを設定するだけでその機能を利用することができます。

よくWebサイトの制作でjQueryを読み込んで、jQueryのプラグインを読み込んで色々な機能を実装できますが、Web Componentsがどのブラウザでも標準的に動作するようになれば、こういったものはすべてWeb Componentsに置き換えられていく可能性もありそうです。設置も非常に楽ですし、プロパティで自然に設定できるのでわざわざscriptを書かなくてもよく直感的ですし。

ちらっと調べたところ他にも色々とWeb Components開発のフレームワーク的なものはあるようですが、Vueは元々使える人が多いと思うのでそういう意味でも選択肢にあがる可能性は高そうな気がします。

あとはクローラがどう解析してくれるのか、Vuexのstoreで複数のコンポーネントにて共通のstateを使ったりできるのか(Web Componentsとしてのメリットとは矛盾していそうですが)、色々まだ試してみると面白そうです。

Top comments (0)