こんにちは、編集長の白石です。
この記事は、9月24日に開催されるHTML5 Conference 2017に登壇するエキスパートに、予定しているセッションのトピックを中心に、様々な技術的なお話を伺おうというものです。セッションの内容をより深く理解する手助けになるだけでなく、本記事単体でも面白く読んでいただけることを目指しています。
今回お話を伺ったのは、株式会社サイバーエージェントにお勤めの泉水翔吾さんです。
▲株式会社サイバーエージェント 泉水翔吾さん
泉水さんのセッションは「The State of Web Components」(ルームA 16:20-17:00)です。 (現在HTML5 Conferenceは定員オーバーの状態ですが、無料イベントということもあってキャンセルも多めに出るらしいので、あきらめずにキャンセル待ちすることをお勧めします!)
Web Componentsについてまずは復習
白石: まずは、Web Componentsについて詳しくご存じない方のために、「Web Componentsとは何か」を簡単にお話していただけますでしょうか?
泉水: Web Componentsは、一言で言えばHTML/CSS/JavaScriptといったWeb技術上で、再利用できる部品作りを促進する仕組みです。
ReactやAngularなど、昨今のフレームワークを使えば、WebのUIをコンポーネントとして扱うのはすでに可能です。しかし、例えばCSSが単一のグローバルスコープしか持たなかったり、インポートする仕組みが弱かったりするという問題は、ブラウザがネイティブに対応することが望まれる問題なのです。
白石: 具体的な技術として見た場合、Web Componentsとはどのような技術なのでしょうか?
泉水: 大きく分けて4つの仕様からなっています。
1つ目はShadow DOM(仕様)。個人的には一番のポイントとなる技術だと思っています。
コンポーネントはそれぞれ、メインのDOMツリーから分離された、独自のDOMツリーを持つことができるようになり、それをShadow DOMといいます。Shadow DOMはそれぞれ異なるスコープを持つため、CSSやJavaScriptの名前空間が他と干渉しません。
例えば、一度作ったUI部品を、一年後にまた別の箇所で使おうと思った時、CSSのクラス名がかぶってしまってうまく扱えない…などの問題はありがちです。そういう問題を根本的に解決する手段として、Shadow DOMはとても有望な技術です。
2つ目はCustom Elementsです(仕様)。
これは一言で言うと「独自の要素を作れる」技術です。 customElements.define()
というAPIを用いて自由に要素を作成でき、作った要素は通常のHTML要素と同じくマークアップが可能です。Web Componentsによる「部品の再定義」とは、この「独自の要素を作って、使える」というCustom Elementsの機能を指すことが多いと思います。
3つ目はtemplate要素です(仕様)。
テンプレートとして使いたいHTMLを定義するための要素で、すでにほとんどのブラウザが対応しています。昔は、そういうテンプレートは不可視のdiv
要素だとか、独自のtype
を指定したscript
要素の中に書くのが通例でした。
標準の要素を用いてテンプレートを扱えるようになったことで、セマンティクス上わかりやすくなったり、テンプレートをDOMとして扱えるのでセキュリティ上の懸念があるinnerHTML
を利用する必要がなかったり、テンプレート内にimg
要素があったときに画像のリクエストが行われてしまう…といった問題を避けられるようになりました。
最後はHTML Importsと呼ばれている仕様でした(仕様)。この仕様はもともと、link
要素を用いて他のHTMLファイルを読み込むことができるというものです。
ただ、この仕様には反対するブラウザベンダーも多く、実装は進みませんでした。代わりにES Modules(※)の実装が進んできたので、まずはそれを使ってコンポーネントを宣言するJavaScriptを読み込む…というのが、当面はスタンダードな手段になりそうです。
※ES Modules…ECMAScriptで定められた、モジュールを扱うための標準仕様。import {XXX} as 'module';
のような形式で、他のモジュールを読み込むことができる。
Web Componentsが「ようやく使える」わけ
白石: 泉水さんがHTML5 Conferenceのセッション説明に書かれていたことによると、Web Componentsは「ようやくまともに使えるようになりそう」とのことですね。これまでなぜまともに使えなかったのか、そしてどのように状況が変わってきたのでしょうか?
泉水: ブラウザの対応が進まなかったというのが、これまでまともに使えなかったという理由です。
Web Componentsはもともと、2011年にGoogleが提案したものでしたが、ブラウザの反対にあったり実装されなかったり…という部分がとても多かった。 Shadow DOMやCustom Elementsの当初の仕様は「v0」(バージョン0)と呼ばれていて、ほぼ「なかったこと」になっています。
しかし、仕切り直された「v1」でようやくブラウザベンダーの合意形成がなされて、実装も進んできた。特にSafariの実装が進んできたのが大きいですね。やはり、iOSの存在感はとても大きいので、Safariで動かないとなると、「使えない」「使いにくい」と見なされるのが普通ですから。
先程申し上げた仕様で言うと、Shadow DOMはChromeとSafariでほぼ使える。 Custom Elementsも同様に、ChromeとSafariが対応しています。 template要素はIEを除くほぼすべてのブラウザで使えますし、ES ModulesもChrome、Safariが最新版で対応済みです。
白石: こうして見ると、むしろモバイルブラウザのほうがWeb Componentsを使いやすそうですね。最新版のブラウザであれば、ほぼ100%対応している。ようやく、時代がWeb Componentsに追いついてきたという感じですね。
Web Componentsの「作りかた」
白石: では、Web Componentsに準拠したコンポーネントはどのようにして作るのでしょうか?フレームワークやライブラリを使ったほうがいいですか?
泉水: 現時点だと、全てのブラウザがWeb Componentsに対応しているわけではないので、Polyfillの利用は考えたほうがいいかもしれませんね。 webcomponents.jsというライブラリがあるので、それを読み込んでおけば、あらゆるブラウザでWeb Componentsのコードが動作するようになります。
その上でまずは、素のJavaScriptで全然作っていけると思います。私の作ってみたサンプルがGitHubに上がっているので、そちらを例に取って説明しますね。
まずコンポーネントの作り方ですが、既存のHTML要素を継承したクラスを作ります。
// fancy-button.js // ↓のコードを記事用に改変 // https://github.com/1000ch/webcomponents-sandbox/blob/master/fancy-button.js export default class FancyButton extends HTMLElement { static get template() { return<style> button { display: inline-block; /* 中略 */ } </style> <button> <slot></slot> </button>
; }constructor() { super(); }
connectedCallback() { this.attachShadow({ mode: 'open' }).innerHTML = FancyButton.template; } };
コンポーネントを使う側は、そのコンポーネントをimport
で読みこんで、customElements.define()
を使ってカスタム要素を生成します。後は普通のHTML要素と同様に使っていけばいい。この例では、<fancy-button>
というタグが使えるようになりました。
// app.js // ↓のコードを記事用に改変 // https://github.com/1000ch/webcomponents-sandbox/blob/master/app.js import FancyButton from './fancy-button.js';customElements.define('fancy-button', FancyButton);
白石: この例でいくと、style
やその他の要素を文字列として定義しておくんですね。
泉水: そうですね。将来的にもこうした方法がスタンダードになるかはわかりませんが、現時点ではPolymer 3.0なども同様の方法を取っています。
Web Componentsに関連するライブラリやサービス
白石: Polymerとはなんですか?
泉水: Web Componentsの開発を促進するライブラリです。データバインディングなど、コンポーネントを作るのに便利な機能が数多く含まれています。現在は3.0が開発中で、コンポーネントのロード手段がHTML ImportsからES Modulesに変更されたり、APIが大幅に変わったりする予定です。
白石: その他、Web Componentsに関連するライブラリとか、サービスってありますか?
泉水: webcomponents.orgはWebComponentsのポータルサイトで、Web Componentsに関する情報や、公開されているコンポーネントが載っています。Polymerプロジェクトが作ったコンポーネントも多数公開されていて、‘iron’という接頭辞が付いたものはコアコンポーネント、‘paper’という接頭辞が付いたものはマテリアルデザインのコンポーネントです。
白石: Polymer以外にも、Web Componentsのライブラリってあるんでしょうか?
泉水: webcomponents.orgに一覧があります。例えば、SkateJSは軽量なライブラリで、Reactライクな仮想DOMを持ち、stateやpropsの管理も行ってくれるというものです。
YouTubeはWeb Componentsを使っている!
白石: Web Componentsがようやく使える段階に来ているということで、何か事例とかあったりするんでしょうか?
泉水: 事例で言うと、YouTubeがWeb Componentsを使っているというのは有名ですね。去年発表されて話題になりました。最近デザインもリニューアルされましたが、Polymerを使っているようです。
白石: YouTubeが!凄まじい利用者数もいるだろうに、そんなドラスティックな改変を行っているだなんて…。
泉水: YouTubeのチームは、すごく攻めるらしいんです(笑) 。ただYouTubeチームは、素のWeb Componentsを使っているわけではありません。
YouTubeのHTMLソースを見てみると、確かにたくさんのカスタム要素を使っているのですが、Shadow DOMは使っていないんです。
白石: 本当だ、yt-
って接頭辞の付いたタグをたくさん使ってるけど、それらの中身は通常のDOMですね。
泉水: 聞くところによると、Shady DOMやShady CSSというWeb ComponentsのPolyfillを使っているそうです。Shady DOMやShady CSSは、Web Componentsの動作を完全にエミュレーションするわけではないのですが、そのかわりに動作が軽量です。
白石: なるほど、それでWeb Componentsと互換性のあるコードを書いているというわけですね、きっと。
Web Componentsの気になることを何でも聞いてみる
白石: では、あとはWeb Componentsについて聞きたいことをいくつか用意してきたので、順に質問させてもらってもいいですか?
泉水: はい、お答えできることであれば何でも答えます。
白石: 例えばアクセシビリティはいかがでしょうか。Web Componentsが広まると、たくさんの独自要素が開発者の手によって作られていくことになると思いますが、それでもアクセシビリティは担保できるものなのでしょうか。
泉水: それは問題にならないと予想しています。
Web Componentsの中身は、結局のところ一般的なHTML要素から構成されますので、そうした要素が持つセマンティクスなどをブラウザが活用することは可能です。また、アクセシビリティ情報を付与したければ、自作のコンポーネントにWAI-ARIAの属性などを指定していくこともできるでしょう。
白石: ReactやAngularなど、既存のフレームワークとWeb Componentsの関係はどうなりますか?うまく組み合わせて使っていけるものなのでしょうか?
泉水: 私もあらゆるフレームワークを知っているわけではありませんが、Reactに限って言うなら、組み合わせて使うことは難しくありません(筆者注: Reactのドキュメントにも言及がある)。
Reactのコンポーネントを、Shadow DOMにするのも難しくはないと思います。仮想DOMから実際のDOMをレンダリングする部分で、通常のDOMの代わりにShadow DOMを使えばいい。
白石: そうすることによる利点には何があるでしょうか?
泉水: やはり、CSSがコンポーネントごとに分離できるのはうれしいですね。私はCSS Modulesとかがあまり好きではないので、標準的な仕組みでそれが行えるのであればすごく嬉しいです。
白石: CSSの分離という話でいうと、コンポーネントを使う側が、コンポーネント内のスタイルをカスタマイズしたいという要望は必ずあるかと思います。ですが、コンポーネントは基本的にスコープが閉じているので、外からスタイルを当てるのは工夫が必要ですよね。それについてはどう対応するのが正解なのでしょうか?
泉水: CSSカスタムプロパティを使うことですね。コンポーネント側では、使う変数名を決めておき、var(--変数名, デフォルト値)
と指定して使用します。こうすれば、コンポーネントのデフォルト値でレンダリングはされつつも、呼び出し元で変数を指定すれば、コンポーネントのスタイルを上書きすることができます。
(筆者注: Google Developersの文書に詳しく説明がある。Shadow DOM v1: 自己完結型ウェブ コンポーネント)
白石: ほか、最近Web Components周りで気になっている話題とかはありますか?
泉水: そうですね、コンポーネントの読み込みに関する部分はかなり気になるところです。
例えば、先程もコンポーネントをES Modulesで読み込むのが一般的になりそうだというお話をしましたが、一つ問題がありまして。type='module'
を指定したscript
要素(筆者注: JavaScriptモジュール)って、デフォルトでdefer
で読み込まれる(※)ので、コンポーネントを宣言するJavaScriptの実行がどうしても遅くなってしまうんです。
なのでそうなると、コンポーネントを宣言するJavaScriptがロードされるまで、カスタム要素の中身がそのままレンダリングされちゃうっていう事態が発生してしまうんですよね。
※script要素がdeferで読み込まれる…defer属性を指定されたscript要素は、レンダリングをブロックしないようにレンダリング処理と並列で読み込まれ、DOMContentLoaded終了後に実行される。
白石: おお、それは、先日ソフトバンク・テクノロジーの関口さんに伺ったWebフォントの話(「フォント素人のWebエンジニアが、「フォントおじさん」に聞いてみた!Webフォントの最近の事情とか」)にも近いものがあるかもしれません。Webフォントの場合も、フォントファイルの読み込みが終わる前にシステムフォントでレンダリングされてしまうという問題があって、それをFONTPLUSでは独自に、該当要素を非表示にすることでちらつきを抑えているそうです。
泉水: 同様の対処が必要かもしれませんね。
あともう一つ、コンポーネントの読み込み周りで気になるところでいうと、HTML ImportsならぬHTML Modulesっていう仕様が最近議論されていましたね。これは、import
にHTMLファイルを指定できるようにしようとするものです。
白石: それはなかなかアグレッシブなアイデアですね。ES Modulesで、コンポーネントのJavaScriptを読み込めるようにするというだけでは、やはりまだ満たせないものがあるということでしょうか。
泉水: やはり、ES Modulesだけだと、どうしてもJavaScriptが中心になってしまうんですよね。先程お見せしたコードでも、HTMLやCSSのコードを文字列としてコンポーネントが保持していました。でも、あれはそれほどいい方法とも思いません。マークアップやスタイリングを分業しやすいよう、JavaScriptファーストではない方法についても、検討の余地はまだまだあるということだと思います。
白石: なるほど、Web Compornentsの周りでも、まだまだ面白い展開が待ってそうですね。本日はいろんなことにお答えいただき、誠にありがとうございました!