WebComponents だけでアプリの骨格を作ってみる
はじめに
WebComponents、何がいいかというとブラウザでネイティブに対応してるとこですね。
そろそろ本家、3rdパーティーともライブラリが充実してきて、Vue とか React とかのフレームワークを使わずに WebComponents だけでいけそうな感じになってきたので、とりあえず下のようなアプリの骨格を作って検証してみます。
オフィシャルの material.io の GitHub にはメジャーな
- material-components-android
- material-components-ios
- material-components-web
- material-components-flutter
の他に
ってのがあります(名前が非常にまぎらわしい><)。GUI コンポーネントとして今回これを使わせてもらいます。
また router として、
を使わせてもらいます。
早速作ってみましょう。
準備
node, npm をダウンロードします
https://nodejs.org
この記事は node v12.4.0, npm 6.12.0 で検証しています
ディレクトリを掘って cd して node のプロジェクトを作成します。
$ mkdir wc
$ cd wc
$ yarn init
es-dev-server をインストールして動かしておきます。
$ npm install -g es-dev-server
$ es-dev-server --node-resolve --watch
8000 番のポートが使われてなければ
es-dev-server started on http://localhost:8000
と表示されます。ブラウザでここにアクセスしてください。
--node-resolve オプションが何故必要かについて知りたい場合は以下を参照してください。
作成
使うコンポーネントを登録
$ yarn add @material/mwc-drawer
$ yarn add @material/mwc-icon-button
$ yarn add @material/mwc-top-app-bar
$ yarn add @vaadin/router
メイン画面に表示する WebComponent を作成
Main.js
class
Main extends HTMLElement {
constructor() {
super();
this.attachShadow( { mode: 'open' } ).innerHTML = '<div>MAIN</div>'
}
}
customElements.define( 'jp-main', Main )
About.js
class
About extends HTMLElement {
constructor() {
super();
this.attachShadow( { mode: 'open' } ).innerHTML = '<div>ABOUT</div>'
}
}
customElements.define( 'jp-about', About )
まとめあげ
index.html
<!doctype html>
<html lang=zxx>
<title>drawer demo</title>
<link href="https://fonts.googleapis.com/css?family=Roboto:300,400,500" rel="stylesheet">
<link href="https://fonts.googleapis.com/css?family=Material+Icons&display=block" rel="stylesheet">
<body>
<script type="module">
import '@material/mwc-drawer'
import '@material/mwc-top-app-bar'
import '@material/mwc-icon-button'
import './Main'
import './About'
import { Router } from '@vaadin/router'
class
App extends HTMLElement {
constructor() {
super();
this.attachShadow( { mode: 'open' } ).innerHTML = `
<mwc-drawer hasHeader type="dismissible">
<span slot="title">Drawer Title</span>
<span slot="subtitle">subtitle</span>
<div>
<p>Drawer content</p>
<a href="/">Main</a><br />
<a href="/About">About</a><br />
</div>
<div slot="appContent">
<mwc-top-app-bar>
<mwc-icon-button slot="navigationIcon" icon="menu"></mwc-icon-button>
<div slot="title">Title</div>
</mwc-top-app-bar>
<div id="outlet" />
</div>
</mwc-drawer>
`
const router = new Router( this.shadowRoot.getElementById( 'outlet' ) )
router.setRoutes(
[ { path: '/' , component: 'jp-main' }
, { path: '/about' , component: 'jp-about' }
]
)
const drawer = this.shadowRoot.querySelector( 'mwc-drawer' )
drawer.addEventListener(
'MDCTopAppBar:nav'
, _ => drawer.open = !drawer.open
)
}
}
customElements.define( 'jp-app', App )
</script>
<jp-app />
</body>
勘所
navigationIcon
$ yarn add @material/mwc-drawer
$ yarn add @material/mwc-icon-button
$ yarn add @material/mwc-top-app-bar
$ yarn add @vaadin/router
class
Main extends HTMLElement {
constructor() {
super();
this.attachShadow( { mode: 'open' } ).innerHTML = '<div>MAIN</div>'
}
}
customElements.define( 'jp-main', Main )
class
About extends HTMLElement {
constructor() {
super();
this.attachShadow( { mode: 'open' } ).innerHTML = '<div>ABOUT</div>'
}
}
customElements.define( 'jp-about', About )
<!doctype html>
<html lang=zxx>
<title>drawer demo</title>
<link href="https://fonts.googleapis.com/css?family=Roboto:300,400,500" rel="stylesheet">
<link href="https://fonts.googleapis.com/css?family=Material+Icons&display=block" rel="stylesheet">
<body>
<script type="module">
import '@material/mwc-drawer'
import '@material/mwc-top-app-bar'
import '@material/mwc-icon-button'
import './Main'
import './About'
import { Router } from '@vaadin/router'
class
App extends HTMLElement {
constructor() {
super();
this.attachShadow( { mode: 'open' } ).innerHTML = `
<mwc-drawer hasHeader type="dismissible">
<span slot="title">Drawer Title</span>
<span slot="subtitle">subtitle</span>
<div>
<p>Drawer content</p>
<a href="/">Main</a><br />
<a href="/About">About</a><br />
</div>
<div slot="appContent">
<mwc-top-app-bar>
<mwc-icon-button slot="navigationIcon" icon="menu"></mwc-icon-button>
<div slot="title">Title</div>
</mwc-top-app-bar>
<div id="outlet" />
</div>
</mwc-drawer>
`
const router = new Router( this.shadowRoot.getElementById( 'outlet' ) )
router.setRoutes(
[ { path: '/' , component: 'jp-main' }
, { path: '/about' , component: 'jp-about' }
]
)
const drawer = this.shadowRoot.querySelector( 'mwc-drawer' )
drawer.addEventListener(
'MDCTopAppBar:nav'
, _ => drawer.open = !drawer.open
)
}
}
customElements.define( 'jp-app', App )
</script>
<jp-app />
</body>
navigationIcon
mwc-top-app-bar の navigationIcon スロットに入れられたボタンが押されると、mwc-drawer に対して MDCTopAppBar:nav イベントが送られます。
shadowRoot
WebComponent は constructor の中で attachShadow ってやると、shadowRoot が使えるようになります。プライベートな DOM みたいなものです。これを実際の DOM に connect して表示するようになります。
constructor の中ではまだ実際の DOM にコネクトされていないので、shadowRoot に対して getElement... とか querySelector とか発行して、エレメントをとります。
shadowRoot に対して、今回の記事では innerHTML をスタティックテキストでセットしているだけですが、実際にはダイナミックに生成したい場合が多いと思われます。そんなときは polymer の lit-element を使うという選択肢があります。lit-html という HTML テンプレートライブラリをラップしたものです。
古いブラウザのサポート
Edge とか IE11 とかをサポートするにはポリフィルが必要です。詳しくは以下を参照してください。
最後に
lit-element を使うと、reactive になります。また別記事で紹介させてもらいたいと思います。
Author And Source
この問題について(WebComponents だけでアプリの骨格を作ってみる), 我々は、より多くの情報をここで見つけました https://qiita.com/Satachito/items/4ce25efc6a630a66ba17著者帰属:元の著者の情報は、元のURLに含まれています。著作権は原作者に属する。
Content is automatically searched and collected through network algorithms . If there is a violation . Please contact us . We will adjust (correct author information ,or delete content ) as soon as possible .