この記事はIonic Advent Calendar 2017の18日目です。
社会人3年目にしてようやく技術が楽しくなってきました@yosshitaku067です。
最近はIonicで作ったiOSアプリをAppiumでテストとか試しては挫折したり、
FullCalendarをIonicに埋め込んで遊んだりしています。
宜しくお願い致します。
Ionicを1年間使ってみて感じたメリットの1つは、
経験の浅いエンジニアでも顧客の目に止まりやすいそれっぽいプロトタイプアプリを手軽に作れることで、
デメリットはそのプロトタイプで突き進むと、事故りやすいんじゃないかなーというところです。
やったこと
Bakaus氏の個人ブログで紹介されているデモアプリをIonic版にしてみました。
要するに、よそのサイトにあるAMPページを引っ張ってIonicで作ったPWAに埋め込みました。
成果物(モバイル、またはブラウザのモバイルエミュレーションモードで表示してみてください)
GitHubリポジトリはこちら -> amp-in-pwa-by-ionic
やったことの詳細
やりたいことを色々試していたら、片付かなくなったのでAMPに関係のある部分だけ抜粋して紹介します。
AMPの公式サイトでは
AMPとPWAの組み合わせとして以下の3種類が紹介されています。
- PWA の機能を取り入れた AMP ページ(AMP as PWA)
- PWA へのエントリ ポイントとしての AMP(AMP to PWA)
- PWA のデータソースとしての AMP(AMP in PWA)
今回試してみたのは3つ目の「PWA のデータソースとしての AMP(AMP in PWA)」となります。
やったこととしては、AMP を埋め込んでデータソースとして使用するの内容をそのままなぞった形で、表示するコンテンツをBakaus氏のデモアプリと同じものにしました。
index.htmlで「Shadow AMP」をインクルード
index.htmlを以下(抜粋)のように編集して、Shadow AMP APIの読み込み、Service Workerの読み込み、
cordovaの読み込みを除外を行いました。
<!-- cordova.js required for cordova apps (remove if not needed) -->
<!-- <script src="cordova.js"></script> -->
<script async src="https://cdn.ampproject.org/shadow-v0.js"></script>
<script>
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('service-worker.js')
.then(() => console.log('service worker installed'))
.catch(err => console.error('Error', err));
}
</script>
表示したいAMPページを取得して埋め込む
表示したいAMPコンテンツを引っ張り、表示します。
今回は取得するAMPページにヘッダーやメニューの定義があるので、それを取り除くサニタイズも行っています。
ionViewDidLoad() {
// this.article.linkがURL
// AMPが使用可能かチェックする
(window.AMP = window.AMP || []).push((AMP: any) => {
const container = document.getElementById('container');
this.articleService.getArticlePage(this.article.link).then(doc => {
// データ整形
this.sanitize(doc);
// AMPページをレンダリングする
this.ampDoc = AMP.attachShadowDoc(container, doc, this.article.link);
});
});
}
sanitize(doc: Document) {
// ヘッダーを取り除く
let header = doc.getElementsByTagName('header');
if (header.length) {
header[0].remove();
}
// サイドメニューを取り除く
let sidebar = doc.getElementsByTagName('amp-sidebar');
if (sidebar.length) {
sidebar[0].remove();
}
// コンテンツのヘッダー部を取り除く
let contentHead = doc.querySelector('header.content__head');
if (contentHead) {
contentHead.remove();
}
// AMPページのTop画像を取り除く
let featuredImage = doc.querySelector('.media-primary amp-img');
if (featuredImage) {
featuredImage.remove();
}
}
<ion-header>
<ion-navbar color="primary">
<ion-title>
AMP in Ionic
</ion-title>
<ion-buttons end>
<button *ngIf="!navCtrl.canGoBack()" ion-button icon-only (tap)="onHome()">
<ion-icon name="home"></ion-icon>
</button>
</ion-buttons>
</ion-navbar>
</ion-header>
<ion-content no-padding class="card-background-page">
<parts-card [article]="article"></parts-card>
<div id="container">
<!-- ここにAMP記事がはめこまれる -->
</div>
</ion-content>
getArticlePage(url: string): Promise<Document> {
let ampUrl = this.backend.getAMPUrl(url);
var xhr = new XMLHttpRequest();
return new Promise((resolve, reject) => {
xhr.open('GET', 'https://amp-in-pwa-by-ionic.herokuapp.com/' + encodeURIComponent(ampUrl), true);
xhr.responseType = 'document';
xhr.setRequestHeader('Accept', 'text/html');
xhr.onload = () => {
var isAMP = xhr.responseXML.documentElement.hasAttribute('amp') || xhr.responseXML.documentElement.hasAttribute('⚡');
return isAMP ? resolve(xhr.responseXML) : reject('Article does not have an AMP version.');
}; // .responseXML contains a ready-to-use Document object
xhr.send();
});
}
クリーンアップ
ここの動きの詳細を確認していないのですが、クリーンアップを実行することでこのドキュメントをもう使用しないことが AMPに伝わり、メモリやCPUのオーバーヘッドが解放されるようです。
ionViewDidLoadで実行することとしました。
ionViewDidLeave() {
// クリーンアップ
this.ampDoc.close();
}
AMP記事を表示するのに本当に必要なのはこれだけ
意外と必要な準備は少なかったです。
もちろん、表示したい対象やバックエンドの構成次第でやらなければならないことは色々あります。
今回だと、
・CORS
・記事のサニタイズ
などなど
おわりに
作っていて楽しかったです。
私が、Webフロントエンドではなくネイティブよりのモバイル出身ということもあって、
PWAの大枠をIonicにすることで、画面遷移やライフサイクルの制御がやりやすくなったと感じました。
また、RSSフィードの制御にrxjsを採用してみました。
私には早かったかもしれません(笑)
ここの理解にかなり時間を取られてしまいました。
Readmeを用意したり、コードをリファクタリングしたりと、まだまだやるべきことはたくさんあるので、
粛々と進めていこうと思います。
ありがとうございました。
参考にしたサイト
Embed & use AMP as a data source
Building a PWAMP #0: Introducing the ShadowReader