Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Sign up for free
Menu
Search
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Pricing
Search
Sign in
Sign up for free
こえのブログでのPWA ~ PWA編 ~ / PWA Night Vol.4
Search
Sponsored
·
Your Podcast. Everywhere. Effortlessly.
Share. Educate. Inspire. Entertain. You do you. We'll handle the rest.
→
Kazunari Hara
May 15, 2019
Technology
5k
7
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
こえのブログでのPWA ~ PWA編 ~ / PWA Night Vol.4
PWA Night vol.4 ~PWAのミライや活用方法をみんなで考えよう~の資料です。
https://pwanight.connpass.com/event/128434/
Kazunari Hara
May 15, 2019
More Decks by Kazunari Hara
See All by Kazunari Hara
Amebaデザインシステム Spindleの開発 / The Development of Spindle
herablog
2
190
Google I/O Extended Japan 2023 - Web Performance at CyberAgent
herablog
0
460
2023年、知っておきたいWebのこと ~フレームワーク・Web UI~ / web-frameworks-and-web-ui-in-2023
herablog
1
1.9k
Enjoy the Web
herablog
5
1.7k
2022年、知っておきたいWebのこと ~パフォーマンス & セキュリティ~
herablog
3
660
Core Web Vitals in Practice
herablog
6
10k
Scalable PWA
herablog
7
14k
CDNフル活用でつくる、高速Webアプリ / Using CDN To Improve Web Performance
herablog
15
8.4k
Web App Checklist 〜高品質のWebアプリケーションをつくるために〜 / Web App Checklist 2019 at Inside Frontend
herablog
23
11k
Other Decks in Technology
See All in Technology
企画を Software Factory-ready にする——開発が高速になったとき、PdMの仕事はどう変わったか
yachibit
1
140
ソフトウェア ファクトリー - 再現性のあるソフトウェアの工場づくり / Software factory
iktakahiro
7
5.3k
LLM-as-a-Judgeは本当に信頼できるのか――評価器を評価する話
taka_aki
3
1k
初海外で注意すべきこと12選!!!安全快適なre:invent生活にするために
yuuuuuuu168
1
200
freeeにおけるSoftware Factory
freee
PRO
0
290
AWSセキュリティで活用するAI機能
cmusudakeisuke
1
200
オントロジーって 結局なにもの? AI エージェントに「意味」を渡す知識設計の話
greenmidori
0
260
OptunaHub: ブラックボックス最適化のためのプラットフォーム
pfn
PRO
0
150
Active Record Connection Adaptersにおける長年の課題に再び取り組む
yahonda
0
410
JAWS-UGグッズ職人仕事の流儀
kentapapa
0
130
LambdaWebFunctionsを試してみた / exploring-lambda-web-functions
koukihosaka
1
390
AWSDirectConnectの構築の進め方を整理してみた
masa_my
1
150
Featured
See All Featured
Chrome DevTools: State of the Union 2024 - Debugging React & Beyond
addyosmani
10
1.4k
Skip the Path - Find Your Career Trail
mkilby
1
260
Information Architects: The Missing Link in Design Systems
soysaucechin
1
1.2k
Building a Modern Day E-commerce SEO Strategy
aleyda
45
9.3k
sira's awesome portfolio website redesign presentation
elsirapls
0
460
Bioeconomy Workshop: Dr. Julius Ecuru, Opportunities for a Bioeconomy in West Africa
akademiya2063
PRO
1
420
Typedesign – Prime Four
hannesfritz
42
3.2k
Rails Girls Zürich Keynote
gr2m
96
14k
The MySQL Ecosystem @ GitHub 2015
samlambert
251
13k
The untapped power of vector embeddings
frankvandijk
2
1.9k
The Invisible Side of Design
smashingmag
301
52k
RailsConf & Balkan Ruby 2019: The Past, Present, and Future of Rails at GitHub
eileencodes
141
35k
Transcript
こえのブログでのPWA ~ PWA編 ~ 2019年5月15日 @株式会社ウフル Kazunari Hara
原 一成 Hara Kazunari Web Developer @herablog
None
None
喋るだけで ブログになる
本人の”声”でコンテンツ価値向上 https://voice.ameba.jp/emb ed/kobayashi-maya/rxxqHm 6s4iAqYRP4mjK5 https://voice.ameba.jp/e mbed/kose-sports/eaxzb 5mP6vMlw3FWpqX9 https://voice.ameba.jp/e mbed/toshl-official/9nzC7 iAFn6IDKHPerj6P
None
https://github.com/webmaxru/progressive-web-apps-logo
None
None
None
None
None
クロスプラットフォーム 小さくリリースできる ブラウザ機能の充実 ✖
Lighthouseでhttps://voice.ameba.jp/をMobile、Simulated Fast 3G、4x CPU Slowdown、ローカル環境で測定。
are user experiences.” “ https://developers.google.com/web/progressive-web-apps/
キャッシュ UIパーツ マルチメディア
サーバーサイド & クライアントサイド キャッシュ
Network GET / GET /voice-app.js GET /api/entry.json
Server DB Browser I/O 計算量 キャパシティ リダイレクト クエリ性能 ネットワーク状況 地理
Server DB Browser CDN
CDN利用: できる限りキャッシュ イベント駆動パージ エッジコンピューティング
できる限りキャッシュ: Time To Live (TTL) Surrogate Key
Method Path TTL Surrogate Key GET / max-age=2592000 web, web/release
GET /src/components/voice-app.js max-age=2592000 web, web/release GET /assets/audios/stadard/$USER _ID/$ENTRY_ID.mp3 max-age=2592000 api, entry/$ENTRY_ID, blogger/$USER_ID GET /api/entries/$USER_ID/$ENTR Y_ID/ max-age=2592000 api, entry/$ENTRY_ID, blogger/$USER_ID GET /api/playcounts/$USER_ID/$EN TRY_ID/ max-age=30, stale-while-revali date=120 api, entry/$ENTRY_ID, blogger/$USER_ID
イベント駆動パージ
# Surrogate Keyを操作 sub vcl_fetch { declare local var.SurrogateKey STRING;
If (req.http.x-url ~ "/audios/standard/([a-z0-9-]{3,24})/([a-zA-Z0-9]+)") { set var.SurrogateKey = var.SurrogateKey + " blogger/" + re.group.1 + " entry/" + re.group.2 + " audio/" + re.group.2; // e.g. "blogger/abcde", "entry/12345" } set beresp.http.Surrogate-Key = var.SurrogateKey; }
# ブラウザに配信するHTTPレスポンスヘッダーを追加 sub vcl_deliver { add resp.http.Server-Timing = fastly_info.state {",
fastly;desc="Edge time";dur="} time.elapsed.msec; set resp.http.Referrer-Policy = "origin-when-cross-origin"; set resp.http.X-Content-Type-Options = "nosniff"; add resp.http.Content-Security-Policy = "default-src 'self'; script-src 'self'..." }
CDN詳細は、 WEB+DB PRESS vol.109
クライアントキャッシュ: HTTP Headers Service Worker (Cache API) キャッシュ
HTTP Headerでの キャッシュ Cache-Control: maxage=3600
“25.5% of all logged requests were missing the cache.” https://code.fb.com/web/web-performance-cache-efficiency-exercise/
Service Worker (Cache API) で オリジン毎にキャッシュコ ントロール
プリキャッシュ: アプリの雛形となる ファイル(HTML, Image, JS) 全て Index.html (Entrypoint) Voice-app.js (App
shell) voice-home.js voice-editor.js lazy-resources.js PRPLパターン (Fragment)
プリキャッシュ: 各ファイルの変更毎に 入れ替え workbox.precaching.precacheAndRou te([{ url: "index.html", revision: "999s0cnacavav" },
…]);
index.html
onload Service Worker
Pre-cache assets
Reload Activate Service Worker
No Network Connection
Update Service Worker New Version App
変更があるファイル だけ更新
No Network Connection
ランタイムキャッシュ: オフラインや次回訪問に 備えてAPIデータや アセットをキャッシュ Cache First アートワーク画像 Network First 変更が多いAPIデータ
Stale While Revalidate 変更が少ないAPIデータ
None
UIパーツ
Web Components (LitElement) CSRのWebアプリ モバイルター ゲット コンポーネント Web標準 Web Components
<!-- use component --> <voice-mic recording> </voice-mic>
class VoiceMic extends LitElement { _handleMicClick() { this.dispatchEvent(new CustomEvent('mic-click')); }
render() { const { disabled, recording } = this; const buttonLabel = recording ? '停止' : '開始'; return html` <style></style> <button aria-label=${buttonLabel} aria-live="assertive" ?disabled=${disabled} type="button" @click=${this._handleMicClick} ></button> `; } } customElements.define('voice-mic', VoiceMic);
class VoiceMic extends LitElement { _handleMicClick() { this.dispatchEvent(new CustomEvent('mic-click')); }
render() { const { disabled, recording } = this; const buttonLabel = recording ? '停止' : '開始'; return html` <style></style> <button aria-label=${buttonLabel} aria-live="assertive" ?disabled=${disabled} type="button" @click=${this._handleMicClick} ></button> `; } } customElements.define('voice-mic', VoiceMic);
class VoiceMic extends LitElement { _handleMicClick() { this.dispatchEvent(new CustomEvent('mic-click')); }
render() { const { disabled, recording } = this; const buttonLabel = recording ? '停止' : '開始'; return html` <style></style> <button aria-label=${buttonLabel} aria-live="assertive" ?disabled=${disabled} type="button" @click=${this._handleMicClick} ></button> `; } } customElements.define('voice-mic', VoiceMic);
class VoiceMic extends LitElement { _handleMicClick() { this.dispatchEvent(new CustomEvent('mic-click')); }
render() { const { disabled, recording } = this; const buttonLabel = recording ? '停止' : '開始'; return html` <style></style> <button aria-label=${buttonLabel} aria-live="assertive" ?disabled=${disabled} type="button" @click=${this._handleMicClick} ></button> `; } } customElements.define('voice-mic', VoiceMic);
class VoiceMic extends LitElement { _handleMicClick() { this.dispatchEvent(new CustomEvent('mic-click')); }
render() { const { disabled, recording } = this; const buttonLabel = recording ? '停止' : '開始'; return html` <style></style> <button aria-label=${buttonLabel} aria-live="assertive" ?disabled=${disabled} type="button" @click=${this._handleMicClick} ></button> `; } } customElements.define('voice-mic', VoiceMic);
166 KB (gzip, style込み)
オフライン対応: IndexedDB Service Worker (Cache API) navigator.onLine
下書き保存 with IndexedDB
記事データが 更新されると Indexed DBに 保存
Offline Recording with Service Worker
Index.html (Entrypoint) Voice-app.js (App shell) voice-home.js voice-editor.js lazy-resources.js PRPLパターン (Fragment)
Offline Recording with Service Worker
Offline Notification with navigator.onLine
function watchOffline(callback) { window.addEventListener( ’online’, () => callback(false), ); window.addEventListener(
‘offline’, () => callback(true), ); callback( navigator.onLine === false ); }
watchOffline((offline) => { If (offline) { // Display snack bar
} });
Web App: Web App Manifest Media Queries Responsive Images Desktop
PWA
{ "short_name": "こえ", "name": "こえのブログ by Ameba", "description": "「こえのブログ」は、...", "lang":
"ja-JP", "icons": [], "background_color": "#fff", "theme_color": "#fff", "start_url": "/?source=homescreen", "scope": "/", "display": "standalone" } manifest.json
https://twitter.com/Nkzn/status/1110369084166692864
1025pxからDesktop版
@media screen and (min-width: 1025px) { :root { --app-header-height: 60px;
--app-page-background-color: var(--clr-whitesmoke); --app-drawer-width: 400px; ... } }
Responsive Images <img height="80" width="80" src="toshi.jpg?size=80" srcset=" toshi.jpg?size=160 2x, toshi.jpg?size=240
3x, " />
追加設定なしで Desktop PWA
ネットワーク状況 Network Information API
function watchNetwork (callback) { const connection = navigator.connection; if (connection)
{ callback(connection); connection.addEventListener( 'change', () => callback(connection) ); } } watchNetwork(({ effectiveType } => { if (effectiveType.includes('2g')) { // Display notification } });
Lazy-loading: Intersection Observer Native lazy-loading (onscroll)
Lazy-loading: Intersection Observer Native lazy-loading (Beta)
class LazyloadImage extends LitElement { firstUpdated() { If ('loading' in
HTMLImageElement.prototype) { this.shadowRoot.querySelector(‘img’).src = this.src; } else { // Use Intersection Observer } } render() { return html` <img alt=${alt} data-src=${src} loading="lazy" height=${height} srcset=${srcset} width=${width} /> `; } }
こえのブログをシェア with Web Share API
if (navigator.share) { navigator.share({ title: ‘こえのブログ by Ameba’, text: ‘こえのブログは...’,
url: ‘https://voice.ameba.jp/’, }); } else { // Open custom dialog }
こえのブログを貼り付け with Clipboard API
const text = ‘text to copy’; if (navigator.clipboard) { navigator.clipboard.writeText(
Text ); } else { // document.execCommand('copy'); }
Vibrate Notification with Vibration API
ブッ ブブ ブゥ 録音開始 残り10秒 録音終了
function notifyRecordingStart() { navigator.vibrate(30); } function notifyTimeToFinish() { navigator.vibrate([30, 100,
30]); } function notifyRecordingEnd() { navigator.vibrate(100); } ブブ
マルチメディア
Audio Recording
Mic Web Worker Browser Blob Stream Messaging Messaging
端末のマイクに アクセス navigator.mediaDevices .getUserMedia({ audio: { autoGainControl: false, channelCount: 1,
echoCancellation: true, noiseSuppression: true, }, }) .then((stream) => { // use the stream }) .catch((err) => { // NotAllowedError or // NotFoundError });
録音中の音声圧縮 WebAssembly & Web Worker WAV MP3
https://github.com/Kagami/vmsg Kagami/vmsg
録音した音声を操作 with Blob (Binary Large OBject) // Save to IndexedDB
const transaction = db.transaction( ['voice'], 'readwrite'); const objectStore = transaction.objectStore('voice'); const objectStoreRequest = objectStore.put({ audio: blob }); // Create URL to play audio URL.createObjectURL(blob); blob:https://voice.ameba.jp/76ee6fef-c126-4 f83-8a46-8fb00db57808
Read Photo
Camera/Photo Browser Server Blob ArrayBuffer File API Resize/Upload
<input type="file" accept="image/jpeg" /> function onFileChange(event) { const el =
event.target; if (el.files && el.files[0]) { const reader = new FileReader(); reader.onload = e => { const buffer = e.target.result; const type = el.files[0].type; const blob = new Blob([buffer], { type }); const fileName = el.files[0].name; }; } } 端末画像を読み込み with File API
window.loadImage( blob, canvas = > canvas.toBlob( resizedBlob => { //
Display resized image } ), { canvas: true, maxHeight: 1024px, maxWidth: 1024px, }, ); アップロード前に リサイズ
https://github.com/blueimp/JavaScript-Load-Image blueimp/ JavaScript-Load-Image (将来的に変更する可能性あり)
https://developers.cyberagent.co.jp/blog/archives/20506/ 詳細は・・・ CDN/PWA/Speech Recognition/WASM/Web Components/Service Worker/Performance Budget etc... https://speakerdeck.com/herablog/koe-no-blog-pwa こえのブログでのPWA
are user experiences.” “ https://developers.google.com/web/progressive-web-apps/