IE で WebComponent (lit-element) 使うのつらい
- カテゴリ:
- JavaScript
- IE
- コメント数:
- Comments: 2
◆ Chrome だと簡単
◆ ビルドするのも最小限のモジュールのパスを変換するくらい
◆ CDN に頼れば HTML ファイルを書くだけでビルドもインストールも不要
◆ IE11 だと polyfill 入れたりオプション設定したりですごく大変
◆ うまく動かないときの調査も大変
◆ ビルドするのも最小限のモジュールのパスを変換するくらい
◆ CDN に頼れば HTML ファイルを書くだけでビルドもインストールも不要
◆ IE11 だと polyfill 入れたりオプション設定したりですごく大変
◆ うまく動かないときの調査も大変
lit-element のドキュメントを見てみると IE も対応と書いてたので IE でも WebComponent がいい感じに動くのかなーと気になったので試してみました
結構苦戦したのでやっぱり IE 対応はしなくていいやというのが結論です
ほとんどは IE 対応と書いてる割に対応するための方法がドキュメントになく readme には webcomponentsjs が必要くらいしか書いてないのが原因なので一度わかれば次からはそこまで困らないのですが それでもやりたいとは思えない手間の多さです
とりあえずインストールします
parcel や webpack でも良さそうですが polymer プロジェクトなので polymer-cli を使ってビルドします
公式チュートリアルもこれですから
でインストールしたら
でビルドします
エントリポイントの HTML は le-test.html にしています
モジュールのロードは file プロトコルじゃうごかないので build/default フォルダを適当な簡易サーバで公開します
あたりで
ソースは単純なコンポーネントひとつだけにしてます
le-test.html
index.js
test-elem.js
これで localhost:8000 の /le-test.html を開くと毎秒現在時刻が更新されるページが表示されます
polymer-cli だと開発用にサーバ機能と自動で更新される機能もあります
ビルドしたものは
のように自動でパスが変更されます
それくらいでバンドルされるわけでもなくそれほど変わりません
すごくシンプルな例だとこれで十分です
これを HTML ファイルに貼り付けて開くだけでビルドもサーバもいらずです
追加で @webcomponents/webcomponentsjs をインストールします
HTML にこれをロードする script を追加します
le-test.html
bundle のほうは全部入りで loader の方は必要なものだけロードするというものです
Chrome などモダンブラウザを重くしたくないので loader の方を選びました
これを忘れると 「'Promise' は定義されていません」とエラーが出ます
の場合はブラウザによって自動で必要な処理をしてくれるので ここまでの変更だけで IE でも Chrome のように時刻表示できるようになります
build の場合はこれだけだと動きません
ES5 じゃないと動かないので
--js-compile
と
--js-transform-modules-to-amd
が必要です
--js-compile では ES5 に構文を変換します
--js-transform-modules-to-amd では es modules を amd 形式に変換します
こっちもやらないと import などで構文エラーになります
さらに --extra-dependencies に
node_modules/@webcomponents/webcomponentsjs/**
が必要です
loader からロードする部分は静的解析されないので自分で設定してビルド時に含めるファイルに追加しないといけません
これがないと polyfill が追加されないので「'Promise' は定義されていません」とエラーが出ます
ここまでやって やっと IE11 で動きます
ES5 変換とかあるので Chrome でもレガシーコードが動くのでパフォーマンスを考えるなら別々のビルドを用意して polymer serve のようにクライアントに応じて出し分けるのがベストのようです
いらないものがごちゃごちゃあると Chrome でのデバッグも大変になりますからね
webcomponents 用の polyfill のみなので Promise など一部の機能と構文変換だけです
構文変換があるので {...foo} や () => {} や `tpl str` などは動くのですが Object.entries や includes メソッドみたいな polyfill ライブラリは含まれません
includes などの JavaScript 自身の機能の polyfill
remove や closest みたいな DOM 系の polyfill
fetch などのその他 web api の polyfill
は別途必要です
要素の選択で画面上を選択したり 要素を右クリックして要素の検査をクリックしても表示されません
test-element の shadowRoot プロパティを経由したらアクセスはできました
IE の機能だと devtools の機能で調査は難しいのでソースを探したのですが規模が大きくて探すのが大変だったので Chrome で動かしてみました
Chrome でも
を最初に定義しておけば強制的に ShadyDOM による ShadowDOM が作られるので調査しやすいです
Chrome でみてみると
普通に test-element の内側にありました
ただ test-element の children は空ですし childElementCount は 0 です
test-element の内側にある p の parentElement も null でした
実際には test-element の中にあるのでレンダリングされているものの children や parentElement を書き換えて取得できなくしてるようです
IE は実際の構造ではなく DOM のプロパティを元に devtools の表示も行っているので表示されないということでしょう
CSS のスコープは再現するために CSS の中身や HTML のクラスが書き換えられてます
CSS はただの span だったのに span.test-element になっていて p には test-element というクラスが追加されていました
ランダムな文字列とかではないので 重複しないように自作要素のタグ名をクラスにつけない方が良さそうです
HTML チェックしてたとしても文字列から動的につけられるクラス名は無理がありますしね
結構苦戦したのでやっぱり IE 対応はしなくていいやというのが結論です
ほとんどは IE 対応と書いてる割に対応するための方法がドキュメントになく readme には webcomponentsjs が必要くらいしか書いてないのが原因なので一度わかれば次からはそこまで困らないのですが それでもやりたいとは思えない手間の多さです
Chrome の場合
まずは Chrome でやってみますとりあえずインストールします
yarn add lit-element
parcel や webpack でも良さそうですが polymer プロジェクトなので polymer-cli を使ってビルドします
公式チュートリアルもこれですから
yarn global add polymer-cli
でインストールしたら
polymer build --entrypoint le-test.html
でビルドします
エントリポイントの HTML は le-test.html にしています
モジュールのロードは file プロトコルじゃうごかないので build/default フォルダを適当な簡易サーバで公開します
php -S localhost:8000
py -m http.server
あたりで
ソースは単純なコンポーネントひとつだけにしてます
le-test.html
<!doctype html>
<script type="module" src="index.js"></script>
<test-element></test-element>
index.js
import "./test-element.js"
test-elem.js
import { LitElement, html } from "lit-element"
// 日時を表示して自動で更新するコンポーネント
customElements.define(
"test-element",
class extends LitElement {
static get properties() {
return {
time: { type: Date },
}
}
constructor() {
super()
this.time = new Date()
this.tid = null
}
connectedCallback() {
super.connectedCallback()
this.tid = setInterval(() => (this.time = new Date()), 1000)
}
disconnectedCallback() {
super.disconnectedCallback()
clearInterval(this.tid)
}
render() {
return html`
<p>Time: <span>${this.time.toLocaleString()}</span></p>
<style>
span {
font-size: 1.2;
color: red;
}
</style>
`
}
}
)
これで localhost:8000 の /le-test.html を開くと毎秒現在時刻が更新されるページが表示されます
polymer-cli だと開発用にサーバ機能と自動で更新される機能もあります
polymer serve
ビルドしたものは
import { LitElement, html } from "./node_modules/lit-element/lit-element.js";
のように自動でパスが変更されます
それくらいでバンドルされるわけでもなくそれほど変わりません
CDN
一応ビルドしましたが Chrome なら CDN からロードするだけで済ませられるので polymer-cli や npm (yarn) がなくても使えますすごくシンプルな例だとこれで十分です
これを HTML ファイルに貼り付けて開くだけでビルドもサーバもいらずです
<!doctype html>
<script type="module">
import { LitElement, html } from "https://unpkg.com/lit-element/lit-element.js?module"
customElements.define(
"test-element",
class extends LitElement {
render() {
return html`
<h1>test</h1>
`
}
}
)
</script>
<test-element></test-element>
IE11 の場合
serve まで
IE11 だと WebComponent に対応していないので polyfill が必要です追加で @webcomponents/webcomponentsjs をインストールします
yarn add @webcomponents/webcomponentsjs
HTML にこれをロードする script を追加します
le-test.html
<!doctype html>
<script src="node_modules/@webcomponents/webcomponentsjs/webcomponents-loader.js"></script>
<script type="module" src="index.js"></script>
<test-element></test-element>
bundle のほうは全部入りで loader の方は必要なものだけロードするというものです
Chrome などモダンブラウザを重くしたくないので loader の方を選びました
これを忘れると 「'Promise' は定義されていません」とエラーが出ます
polymer serve
の場合はブラウザによって自動で必要な処理をしてくれるので ここまでの変更だけで IE でも Chrome のように時刻表示できるようになります
build も
polymer の簡易サーバ以外で動かすには build しないといけないですがbuild の場合はこれだけだと動きません
ES5 じゃないと動かないので
--js-compile
と
--js-transform-modules-to-amd
が必要です
--js-compile では ES5 に構文を変換します
--js-transform-modules-to-amd では es modules を amd 形式に変換します
こっちもやらないと import などで構文エラーになります
さらに --extra-dependencies に
node_modules/@webcomponents/webcomponentsjs/**
が必要です
loader からロードする部分は静的解析されないので自分で設定してビルド時に含めるファイルに追加しないといけません
これがないと polyfill が追加されないので「'Promise' は定義されていません」とエラーが出ます
polymer build --js-compile --js-transform-modules-to-amd --extra-dependencies node_modules/@webcomponents/webcomponentsjs/** --entrypoint le-test.html
ここまでやって やっと IE11 で動きます
ES5 変換とかあるので Chrome でもレガシーコードが動くのでパフォーマンスを考えるなら別々のビルドを用意して polymer serve のようにクライアントに応じて出し分けるのがベストのようです
いらないものがごちゃごちゃあると Chrome でのデバッグも大変になりますからね
polyfill
これで動きはしましたが 完璧ではありませんwebcomponents 用の polyfill のみなので Promise など一部の機能と構文変換だけです
構文変換があるので {...foo} や () => {} や `tpl str` などは動くのですが Object.entries や includes メソッドみたいな polyfill ライブラリは含まれません
includes などの JavaScript 自身の機能の polyfill
remove や closest みたいな DOM 系の polyfill
fetch などのその他 web api の polyfill
は別途必要です
Shady DOM / Shady CSS
動いたのは良いのですが ShadowDOM ってどう実装されてるんだろう?と IE で devtools を開いてみたら ShadowDOM の部分はどこにも表示されていません要素の選択で画面上を選択したり 要素を右クリックして要素の検査をクリックしても表示されません
test-element の shadowRoot プロパティを経由したらアクセスはできました
IE の機能だと devtools の機能で調査は難しいのでソースを探したのですが規模が大きくて探すのが大変だったので Chrome で動かしてみました
Chrome でも
ShadyDOM = {force: true}
を最初に定義しておけば強制的に ShadyDOM による ShadowDOM が作られるので調査しやすいです
Chrome でみてみると
普通に test-element の内側にありました
ただ test-element の children は空ですし childElementCount は 0 です
test-element の内側にある p の parentElement も null でした
実際には test-element の中にあるのでレンダリングされているものの children や parentElement を書き換えて取得できなくしてるようです
IE は実際の構造ではなく DOM のプロパティを元に devtools の表示も行っているので表示されないということでしょう
CSS のスコープは再現するために CSS の中身や HTML のクラスが書き換えられてます
CSS はただの span だったのに span.test-element になっていて p には test-element というクラスが追加されていました
ランダムな文字列とかではないので 重複しないように自作要素のタグ名をクラスにつけない方が良さそうです
HTML チェックしてたとしても文字列から動的につけられるクラス名は無理がありますしね