Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
PWAの基本とTips
Search
Atsushi Nakatsugawa
PRO
October 18, 2018
Technology
0
95
PWAの基本とTips
Monaca UG OHMIYA #3 ~集まれMonaca ユーザー!~
https://monacaug.connpass.com/event/98537/
の発表資料です。
Atsushi Nakatsugawa
PRO
October 18, 2018
Tweet
Share
More Decks by Atsushi Nakatsugawa
See All by Atsushi Nakatsugawa
開発スピードとスキル向上を両立するAIコードレビューの活かし方
moongift
PRO
0
43
個人開発にAIレビューを導入しよう
moongift
PRO
0
32
AIによるコードレビューで開発体験を向上させよう!
moongift
PRO
0
610
AIによるコードレビューで開発体験を向上させよう!
moongift
PRO
0
1.2k
開発スピードとスキル向上を両立するAIコードレビューの活かし方
moongift
PRO
0
54
Let's speed up personal development with AI code reviews
moongift
PRO
0
22
DevRelに活かせるAIツールの紹介とレビュー
moongift
PRO
0
110
DevRelの基礎と戦略
moongift
PRO
0
58
DevRelの始め方
moongift
PRO
3
730
Other Decks in Technology
See All in Technology
Yamla: Rustでつくるリアルタイム性を追求した機械学習基盤 / Yamla: A Rust-Based Machine Learning Platform Pursuing Real-Time Capabilities
lycorptech_jp
PRO
3
120
登壇ネタの見つけ方 / How to find talk topics
pinkumohikan
5
430
How Community Opened Global Doors
hiroramos4
PRO
1
120
Postman AI エージェントビルダー最新情報
nagix
0
110
ハノーバーメッセ2025座談会.pdf
iotcomjpadmin
0
160
製造業からパッケージ製品まで、あらゆる領域をカバー!生成AIを利用したテストシナリオ生成 / 20250627 Suguru Ishii
shift_evolve
PRO
1
140
20250625 Snowflake Summit 2025活用事例 レポート / Nowcast Snowflake Summit 2025 Case Study Report
kkuv
1
310
Oracle Cloud Infrastructure:2025年6月度サービス・アップデート
oracle4engineer
PRO
2
240
生成AI時代 文字コードを学ぶ意義を見出せるか?
hrsued
1
290
PostgreSQL 18 cancel request key長の変更とRailsへの関連
yahonda
0
120
Amazon S3標準/ S3 Tables/S3 Express One Zoneを使ったログ分析
shigeruoda
4
480
Wasm元年
askua
0
140
Featured
See All Featured
Agile that works and the tools we love
rasmusluckow
329
21k
Fireside Chat
paigeccino
37
3.5k
Facilitating Awesome Meetings
lara
54
6.4k
Making Projects Easy
brettharned
116
6.3k
Imperfection Machines: The Place of Print at Facebook
scottboms
267
13k
Large-scale JavaScript Application Architecture
addyosmani
512
110k
Cheating the UX When There Is Nothing More to Optimize - PixelPioneers
stephaniewalter
281
13k
Navigating Team Friction
lara
187
15k
How to Create Impact in a Changing Tech Landscape [PerfNow 2023]
tammyeverts
53
2.8k
Writing Fast Ruby
sferik
628
61k
Exploring the Power of Turbo Streams & Action Cable | RailsConf2023
kevinliebholz
34
5.9k
Refactoring Trust on Your Teams (GOTO; Chicago 2020)
rmw
34
3k
Transcript
PAGE DAY 2017/11/01 # MOONGIFT X / 12 PWAの基本とTips
DAY 2018/09/10 # MOONGIFT 自己紹介 @goofmint fb.me/goofmint 中津川 篤司 株式会社MOONGIFT
代表取締役 www.moongift.jp エバンジェリスト コミュニティ
PAGE DAY 2017/11/01 # MOONGIFT X / 12 PWAとは? •
Progressive Web Appsの略 • Webアプリケーションをよりスマートフォンアプリに近づけ る仕組み • 特定の技術のことではない • Monacaの近いうちに対応するらしいよ
PAGE DAY 2017/11/01 # MOONGIFT X / 12
PAGE DAY 2017/11/01 # MOONGIFT X / 12
PAGE DAY 2017/11/01 # MOONGIFT X / 12 PWAの要素 1.
App Manifest 2. Service Worker 3. プッシュ通知 4. indexedDB 5. その他
PAGE DAY 2017/11/01 # MOONGIFT X / 12 ΞϓϦԽ
PAGE DAY 2017/11/01 # MOONGIFT X / 12 必要な技術 •
App Manifest • Service Worker • SSL/TLS • Android / Google Chromeは対応 • iOS Safariは微妙に対応
PAGE DAY 2017/11/01 # MOONGIFT X / 12 App Manifest
• アプリの仕様(マニフェスト)を記述したファイル • アプリ名 • アイコン • 最初に表示するURL • 背景色 • テーマ色
PAGE DAY 2017/11/01 # MOONGIFT X / 12
PAGE DAY 2017/11/01 # MOONGIFT X / 12 manifest.jsonの例 {
"short_name": "短いアプリ名", "name": "長いアプリ名", "icons": [ { "src": "アイコン.png", "sizes": "192x192", "type": "image/png" } ], "start_url": "./index.html", "display": "standalone", "theme_color": "#000000", "background_color": "#ffffff" } <link rel="manifest" href="/manifest.json">
PAGE DAY 2017/11/01 # MOONGIFT X / 12 Google Chromeで見る
PAGE DAY 2017/11/01 # MOONGIFT X / 12 Service Worker
• WebブラウザのJavaScriptとは別プロセスで動くJavaScript • 主にキャッシュ、プッシュ通知の受信で利用 • ネットワーク(fetch)/indexedDBが利用可能 • DOMは利用不可 • メッセージでWebブラウザのJavaScript通信可
PAGE DAY 2017/11/01 # MOONGIFT X / 12 Service Workerの登録
if ('serviceWorker' in navigator) { navigator.serviceWorker.register('/serviceworker.js') .then(registration => { // 登録成功 }).catch(err => { // 登録失敗 }); }
PAGE DAY 2017/11/01 # MOONGIFT X / 12 Service Workerでの処理
とりあえずキャッシュ処理 const CACHE_NAME = 'SALES_SYSTEM'; const urls = [ ‘/', ‘/styles/app.css', '/script/app.js' ]; self.addEventListener('install', event => { event.waitUntil( caches.open(CACHE_NAME) .then(cache => { return cache.addAll(urls); }) ); });
PAGE DAY 2017/11/01 # MOONGIFT X / 12 ネットワークアクセス self.addEventListener('fetch',
await (event) => { event.respondWith( const response = await caches.match(event.request) if (response) return response; return fetch(event.request); ); });
PAGE DAY 2017/11/01 # MOONGIFT X / 12 Tips 1
: キャッシュ • URL と レスポンスのKVS • キャッシュを普通に使うとGETリクエストのみ • 署名生成とかは非現実的なのでWebブラウザから送るのがポ イント
PAGE DAY 2017/11/01 # MOONGIFT X / 12 Tips2:キャッシュ •
キャッシュは二回目以降のアクセスで有効 • オンライン/オフライン関係なく利用される
PAGE DAY 2017/11/01 # MOONGIFT X / 12 ハマった例 •
NCMBでは署名処理 -> ヘッダーに追加してアクセス • キャッシュ(fetch)はヘッダーなしでアクセス -> 404 • 二回目以降のアクセスはオンラインでも404が返ってくる
PAGE DAY 2017/11/01 # MOONGIFT X / 12 動的キャッシュ(Web) const
channel = new MessageChannel(); navigator.serviceWorker.controller .postMessage({ action: 'cache', url: res.req.url, response: body }, [channel.port2]);
PAGE DAY 2017/11/01 # MOONGIFT X / 12 動的キャッシュ(Service Worker)
self.addEventListener('message', e => { if (e.data.action == 'cache') { caches.open(CACHE_NAME) .then((cache) => { const res = new Response(e.data.response); cache.put(e.data.url, res); }) } });
PAGE DAY 2017/11/01 # MOONGIFT X / 12 プッシュ通知
PAGE DAY 2017/11/01 # MOONGIFT X / 12 プッシュ通知の種類 •
ローカルプッシュ Notifications API • Chrome向けプッシュ Firebase必須 • macOS Safari向けプッシュ 証明書必須 • Push API/VAPID Chrome/Firefox/Edge
PAGE DAY 2017/11/01 # MOONGIFT X / 12 Notifications API
• サーバ不要 • ユーザの確認は必要 • 開いているタブに対してのみ • チャット、Webアプリの処理完了など
PAGE DAY 2017/11/01 # MOONGIFT X / 12 状態の確認 console.log(Notification.permission);
// -> default // -> granted // -> denied
PAGE DAY 2017/11/01 # MOONGIFT X / 12 許可を得る Notification.requestPermission(permission
=> { if (permission === "granted") { // 許可した場合 } // denied });
PAGE DAY 2017/11/01 # MOONGIFT X / 12 プッシュ通知を送信 const
options = { body: 'メッセージ本文', icon: 'img/icon.png' } const n = new Notification('メッセージタイトル', options);
PAGE DAY 2017/11/01 # MOONGIFT X / 12 プッシュ通知を自動で閉じる const
n = new Notification(‘title', options); setInterval(() => { n.close.bind(n) }, 3000);
PAGE DAY 2017/11/01 # MOONGIFT X / 12 プッシュ通知のイベント •
onclick 通知をクリックした時に呼ばれます • onerror エラーが出た時に呼ばれます • onclose 閉じた際に呼ばれます • onshow 表示した時に呼ばれます
PAGE DAY 2017/11/01 # MOONGIFT X / 12 クリックしたら消す const
options = { body: 'メッセージ本文', icon: 'img/icon.png' } const n = new Notification('メッセージタイトル', options); n.onclick = (event) => { // クリックしたら通知を消す n.close.bind(n); }
PAGE DAY 2017/11/01 # MOONGIFT X / 12 独自プッシュ Google
ChromeのFirebase、macOS Safariのプッシュ通知 今回はスルー
PAGE DAY 2017/11/01 # MOONGIFT X / 12 Push API/VAPID
• Firebase不要 • Android / Google Chrome / Firefox / Edge対応 • サーバ必要 • Service Worker必須
PAGE DAY 2017/11/01 # MOONGIFT X / 12 Push API/VAPIDの流れ
PAGE DAY 2017/11/01 # MOONGIFT X / 12 Push API/VAPIDの流れ
PAGE DAY 2017/11/01 # MOONGIFT X / 12 Push API/VAPIDの流れ
Firebase
PAGE DAY 2017/11/01 # MOONGIFT X / 12 Push API/VAPIDの流れ
Firebase 4FSWJDF 8PSLFS
PAGE DAY 2017/11/01 # MOONGIFT X / 12 Push API/VAPIDの流れ
Firebase 4FSWJDF 8PSLFS
PAGE DAY 2017/11/01 # MOONGIFT X / 12 受信状態を知る if
(!('serviceWorker' in navigator)) return; await navigator.serviceWorker.register('./serviceworker.js') if (!('showNotification' in ServiceWorkerRegistration.prototype)) return; if (Notification.permission === 'denied') return; if (!('PushManager' in window)) return; const serviceWorkerRegistration = await navigator.serviceWorker.ready; const subscription = await serviceWorkerRegistration.pushManager .getSubscription(); if (subscription) { // すでに購読中 } else { // 未購読 }
PAGE DAY 2017/11/01 # MOONGIFT X / 12 承認を得る const
serviceWorkerRegistration = await navigator.serviceWorker.ready const subscription = await serviceWorkerRegistration.pushManager.subscribe({ userVisibleOnly: true, applicationServerKey: convertedVapidKey }); console.log(subscription.toJSON());
PAGE DAY 2017/11/01 # MOONGIFT X / 12 JSONが得られる {
"endpoint":"https://fcm.googleapis.com/fcm/send/e7K...Hag", "expirationTime":null, "keys":{ "p256dh":"BK7...e5U", "auth":"DGt...D8g" } }
PAGE DAY 2017/11/01 # MOONGIFT X / 12 Service Workerの処理
self.addEventListener('install', () => { console.log('ServiceWorker がインストールされました'); }); self.addEventListener('push', ev => { // payloadの取得 const {title, msg, icon} = ev.data.json(); self.registration.showNotification(title, { icon: icon, body: msg }); });
PAGE DAY 2017/11/01 # MOONGIFT X / 12 URLを指定して実行 const
webpush = require("web-push"); const keys = require("./application-server-keys.json"); webpush.setVapidDetails( "mailto:
[email protected]
", keys.publicKey, keys.privateKey ); const subscribers = [/* さっきのJSON */]; const icon = `app.png`; const params = { title: "プッシュ通知です!", msg: `これはサーバから送っています. 今は ${new Date().toLocaleString()} です。 メッセージとアイコンも送っています `, icon: icon }; const res = await Promise.all(subscribers.map(subscription => { return webpush.sendNotification(subscription, JSON.stringify(params), {}); }));
PAGE DAY 2017/11/01 # MOONGIFT X / 12 受信(Service Worker)
self.addEventListener('install', () => { console.log('ServiceWorker がインストールされました'); }); self.addEventListener('push', ev => { // payloadの取得 const {title, msg, icon} = ev.data.json(); self.registration.showNotification(title, { icon: icon, body: msg }); });
PAGE DAY 2017/11/01 # MOONGIFT X / 12 プッシュ通知の表示
PAGE DAY 2017/11/01 # MOONGIFT X / 12 便利なライブラリ •
Node.js https://github.com/web-push-libs/web-push • Ruby https://github.com/zaru/webpush
PAGE DAY 2017/11/01 # MOONGIFT X / 12 データベース
PAGE DAY 2017/11/01 # MOONGIFT X / 12 データベースの種類 $PPLJF
MPDBM4UPSBHF JOEFYFE%# αΠζ ,# .# .# ͍ํ ಉظ ಉظ ඇಉظ 8FCϒϥβ 㾎 㾎 㾎 4FSWJDF8PSLFS º º 㾎 ܕ จࣈྻͷΈ จࣈྻͷΈ ˓
PAGE DAY 2017/11/01 # MOONGIFT X / 12 スキーマ let
db; let version = 1; const request = indexedDB.open("database", version); request.onerror = function(event) { alert("IndexedDB が使えません"); }; request.onupgradeneeded = function(event) { db = event.target.result; const objectStore = db.createObjectStore("items", { keyPath: "id" }); objectStore.createIndex("name", "name", { unique: false }); objectStore.createIndex("description", "description", { unique: true }); }; request.onsuccess = function(event) { db = event.target.result; }
PAGE DAY 2017/11/01 # MOONGIFT X / 12 データ追加 const
transaction = db.transaction(["items"], "readwrite"); transaction.oncomplete = (event) => { console.log("完了しました"); }; transaction.onerror = (event) => { console.log("エラーです", event); }; const objItem = transaction.objectStore("items"); for (let i = 1; i < 10; i += 1) { objItem.add( { id: i, name: `テスト商品 ${i}`, description: `テスト商品 ${i} の説明`, add: '追加' } ).onsuccess = (event) => { console.log(event.target.result); } }
PAGE DAY 2017/11/01 # MOONGIFT X / 12 データ検索 db.transaction(["items"])
.objectStore("items") .get(3) .onsuccess = (event) => { console.log("取得しました", event.target.result); }
PAGE DAY 2017/11/01 # MOONGIFT X / 12 データ更新 const
Items = db.transaction(["items"], "readwrite") .objectStore("items"); Items.get(3) .onsuccess = (event) => { const row = event.target.result; row.name = "商品3の名前を変更"; Items .put(row) .onsuccess = (event) => { console.log("更新しました"); } }
PAGE DAY 2017/11/01 # MOONGIFT X / 12 データ削除 db.transaction(["items"],
"readwrite") .objectStore("items") .delete(2) .onsuccess = (event) => { console.log("削除完了しました"); }
PAGE DAY 2017/11/01 # MOONGIFT X / 12 使うと便利なライブラリ •
Lovefield by Google https://github.com/google/lovefield • JS Store http://jsstore.net
PAGE DAY 2017/11/01 # MOONGIFT X / 12 その他のAPI
PAGE DAY 2017/11/01 # MOONGIFT X / 12 Credential Management
API 認証情報をブラウザに保存、呼び出すAPI const form = document.querySelector('#reg-form') const cred = new PasswordCredential(form); const credential = await navigator.credentials.store(cred);
PAGE DAY 2017/11/01 # MOONGIFT X / 12 認証情報取得 const
credential = await navigator.credentials.get({ password: true }) console.log(credential) /*{ iconURL: "", id: "
[email protected]
", name: "atsushi", password: "demo", type: "password" }*/
PAGE DAY 2017/11/01 # MOONGIFT X / 12 認証情報取得(外部サイト) const
credential = await navigator.credentials.get({ password: true, federated: { providers: ['https://example.com'] } }) console.log(credential) /*{ iconURL: "https://lh5.goog.com/-y0...6-c/photo.jpg", id: "
[email protected]
", name: "中津川篤司", protocol: "", provider: "https://accounts.google.com", type: "federated" }*/
PAGE DAY 2017/11/01 # MOONGIFT X / 12 Web Authentication
API ハードウェアや外部デバイスを用いた認証をサポート
PAGE DAY 2017/11/01 # MOONGIFT X / 12 Payment Request
API 決済情報をWebブラウザ内に保存、取得するAPI const request = new PaymentRequest(supportedInstruments, details); const result = await request.show(); await fetch('/pay', { method: 'POST', credentials: 'include', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(result.toJSON()) });
PAGE DAY 2017/11/01 # MOONGIFT X / 12 Web Share
API スマートフォンでネイティブアプリ風にコンテンツをシェア できるAPI navigator.share({ title: 'Web Fundamentals', text: 'Check out Web Fundamentals — it rocks!', url: 'https://developers.google.com/web', })
PAGE DAY 2017/11/01 # MOONGIFT X / 12 便利なTips
PAGE DAY 2017/11/01 # MOONGIFT X / 12 Lighthouse https://developers.google.com/web/tools/lighthouse/
PAGE DAY 2017/11/01 # MOONGIFT X / 12 自分でキャッシュを作る •
navigator.onLine を使う • オンラインの時に取得して indexedDB に入れておく • POST/PUT/DELETEの時に便利
PAGE DAY 2017/11/01 # MOONGIFT X / 12 Androidで確認する chrome://flags
で Bypass user engagement checks を有効にする
PAGE DAY 2017/11/01 # MOONGIFT X / 12 オフラインを再現する Chromeの開発者ツールでOfflineを有効にする
PAGE DAY 2017/11/01 # MOONGIFT X / 12 デスクトップでも使う
PAGE DAY 2017/11/01 # MOONGIFT X / 12 Thanks! !