Upgrade to Pro — share decks privately, control downloads, hide ads and more …

進化したWeb技術でPWAをネイティブアプリに近づける / frontend-conf-2023

Yuhei FUJITA
November 18, 2023

進化したWeb技術でPWAをネイティブアプリに近づける / frontend-conf-2023

フロントエンドカンファレンス沖縄2023の登壇資料です。

https://frontend-conf.okinawa.jp/

PDF出力の関係で一部レイアウトが崩れてるので、アニメーションありは以下のリンクから表示できます。

https://yuheifujita.github.io/frontend-conf-2023/

Yuhei FUJITA

November 18, 2023
Tweet

More Decks by Yuhei FUJITA

Other Decks in Programming

Transcript

  1. 2 / 31 自己紹介 名前:Yuhei FUJITA X :@Yuhei_FUJITA コミュニティ運営 Vue

    Fes PWA Night VS Code Meetup 趣味 キャンプ フィルムカメラ
  2. 3 / 31 一昨日から沖縄満喫してた人です Yuhei FUJITA @Yuhei_FUJITA·Follow 鍾乳洞すごかった、湿度が #front_okinawa 8:27

    PM · Nov 16, 2023 2 Reply Copy link Read more on X Yuhei FUJITA @Yuhei_FUJITA·Follow 美ら海水族館行く #front_okinawa 12:29 PM · Nov 17, 2023 1 Reply Copy link Read more on X
  3. 5 / 31 Progressive Web Apps (PWA ) Reach Capabilities

    改めてPWA とは Using the latest web features to bring enhanced capabilities and reliability, Progressive Web Apps allow what you build to be installed by anyone, anywhere, on any device with a single codebase. 翻訳 プログレッシブウェブアプリは、最新のWeb 機能を 使用して機能と信頼性を強化し、構築したものを誰 でも、どこでも、どのデバイスでも、単一のコードベ ースでインストールできるようにします。 What are Progressive Web Apps? | Articles | web.dev
  4. 6 / 31 3 つの柱 Capable / 機能性 Web API

    Web Push geoLocation WebRTC Reliable / 信頼性 オフライン動作 Service Worker 高速な読み込み Pre Cache 安全な通信 HTTPS Installable / インストール可能 アプリ化 Standalone ホーム画面に追加 アプリアイコン What are Progressive Web Apps? | Articles | web.dev
  5. 7 / 31 なぜネイティブアプリにするのか? よりパワフルなアプリを作れる 処理速度・OS の提供するAPI モバイルSafari のサポート 使えないAPI

    ・わかりにくいインストール手順 ユーザーの認知度が高い アプリストア・インストールの導線
  6. 10 / 31 今回はPWA 化の話は割愛 去年の「PWA をインストールしやすく するための実装 by まぁし(知念)さ

    ん」がわかりやすいのでそちらを参照 してください。 PWA をインストールしやすくするための実装 by まぁし(知念)さん フロントエンドカンファレンス沖縄2022 - YouTube
  7. 11 / 31 maskable icon さまざまな形のアイコンに対応できる。 https://web.dev/articles/maskable-icon?hl=ja 1 // manifest.json

    2 { 3 "icons": [ 4 { 5 "src": "maskable_icon.png", 6 "sizes": "196x196", 7 "type": "image/png", 8 "purpose": "maskable" 9 }, 10 ], 11 }
  8. 11 / 31 maskable icon さまざまな形のアイコンに対応できる。 https://web.dev/articles/maskable-icon?hl=ja 8 "purpose": "maskable"

    1 // manifest.json 2 { 3 "icons": [ 4 { 5 "src": "maskable_icon.png", 6 "sizes": "196x196", 7 "type": "image/png", 9 }, 10 ], 11 }
  9. 11 / 31 maskable icon さまざまな形のアイコンに対応できる。 https://web.dev/articles/maskable-icon?hl=ja 1 // manifest.json

    2 { 3 "icons": [ 4 { 5 "src": "maskable_icon.png", 6 "sizes": "196x196", 7 "type": "image/png", 8 "purpose": "maskable" 9 }, 10 ], 11 }
  10. 13 / 31 PWA のチェックリスト Core Progressive Web App checklist

    Starts fast, stays fast (すばやく起動、常に高速で快適) Works in any browser (どのブラウザでも動作) Responsive to any screen size (あらゆる画面サイズに応答) Provides a custom offline page (カスタムのオフライン ページを用意) Is installable (インストール可能) より良いWeb 体験で重要なこと ネイティブアプリでも重要なこと What makes a good Progressive Web App? | Articles | web.dev
  11. 14 / 31 PWA のチェックリスト Optimal Progressive Web App checklist

    Provides an offline experience (オフライン機能を利用できる) Is fully accessible (完全にアクセス可能) Can be discovered through search (検索で見つけられる) Works with any input type (すべての入力タイプに対応) Provides context for permission requests (権限リクエストのコンテキストを提供する) Follows best practices for healthy code (正常なコードのためのベスト プラクティスにしたがっている) What makes a good Progressive Web App? | Articles | web.dev
  12. 18 / 31 Web Share API 共有機能を提供するAPI OS 標準の共有メニューを呼び出せる 統一されたUI

    を提供可能 さまざまなファイルを共有可能 pdf audio image text video Web Share API - Web APIs | MDN
  13. 19 / 31 Web Share API 1 type ShareData =

    { 2 title?: string, 3 text?: string, 4 url?: string, 5 files? :File[], 6 } 7 8 const shareContent = async ( 9 data: ShareData 10 ): Promise<void> => { 11 if(navigator.share) { 12 try { 13 await navigator.share(data); 14 console.log("success") 15 } catch (err) {
  14. 19 / 31 Web Share API 11 if(navigator.share) { 1

    type ShareData = { 2 title?: string, 3 text?: string, 4 url?: string, 5 files? :File[], 6 } 7 8 const shareContent = async ( 9 data: ShareData 10 ): Promise<void> => { 12 try { 13 await navigator.share(data); 14 console.log("success") 15 } catch (err) {
  15. 19 / 31 Web Share API 1 type ShareData =

    { 2 title?: string, 3 text?: string, 4 url?: string, 5 files? :File[], 6 } 7 8 const shareContent = async ( 9 data: ShareData 10 ): Promise<void> => { 11 if(navigator.share) { 12 try { 13 await navigator.share(data); 14 console.log("success") 15 } catch (err) {
  16. 19 / 31 Web Share API 12 try { 15

    } catch (err) { 1 type ShareData = { 2 title?: string, 3 text?: string, 4 url?: string, 5 files? :File[], 6 } 7 8 const shareContent = async ( 9 data: ShareData 10 ): Promise<void> => { 11 if(navigator.share) { 13 await navigator.share(data); 14 console.log("success")
  17. 19 / 31 Web Share API 13 await navigator.share(data); 14

    console.log("success") 1 type ShareData = { 2 title?: string, 3 text?: string, 4 url?: string, 5 files? :File[], 6 } 7 8 const shareContent = async ( 9 data: ShareData 10 ): Promise<void> => { 11 if(navigator.share) { 12 try { 15 } catch (err) {
  18. 19 / 31 Web Share API 1 type ShareData =

    { 2 title?: string, 3 text?: string, 4 url?: string, 5 files? :File[], 6 } 9 data: ShareData 13 await navigator.share(data); 7 8 const shareContent = async ( 10 ): Promise<void> => { 11 if(navigator.share) { 12 try { 14 console.log("success") 15 } catch (err) {
  19. 19 / 31 Web Share API 1 type ShareData =

    { 2 title?: string, 3 text?: string, 4 url?: string, 5 files? :File[], 6 } 7 8 const shareContent = async ( 9 data: ShareData 10 ): Promise<void> => { 11 if(navigator.share) { 12 try { 13 await navigator.share(data); 14 console.log("success") 15 } catch (err) {
  20. 19 / 31 Web Share API 1 type ShareData =

    { 2 title?: string, 3 text?: string, 4 url?: string, 5 files? :File[], 6 } 7 8 const shareContent = async ( 9 data: ShareData 10 ): Promise<void> => { 11 if(navigator.share) { 12 try { 13 await navigator.share(data); 14 console.log("success") 15 } catch (err) {
  21. 20 / 31 For Android Developers Intent | Android Developers

    これがないとAndroid アプリはPWA からの共有を受け取れない。 1 <!-- AndroidManifest.xml --> 2 <activity ...> 3 <intent-filter> 4 <action android:name="android.intent.action.VIEW" /> 5 <data android:scheme="http" android:host="www.example 6 <category 7 android:name="android.intent.category.DEFAULT" 8 /> 9 <category 10 android:name="android.intent.category.BROWSABLE" 11 /> 12 </intent-filter> 13 </activity>
  22. 20 / 31 For Android Developers Intent | Android Developers

    これがないとAndroid アプリはPWA からの共有を受け取れない。 9 <category 10 android:name="android.intent.category.BROWSABLE" 11 /> 1 <!-- AndroidManifest.xml --> 2 <activity ...> 3 <intent-filter> 4 <action android:name="android.intent.action.VIEW" /> 5 <data android:scheme="http" android:host="www.example 6 <category 7 android:name="android.intent.category.DEFAULT" 8 /> 12 </intent-filter> 13 </activity>
  23. 20 / 31 For Android Developers Intent | Android Developers

    これがないとAndroid アプリはPWA からの共有を受け取れない。 1 <!-- AndroidManifest.xml --> 2 <activity ...> 3 <intent-filter> 4 <action android:name="android.intent.action.VIEW" /> 5 <data android:scheme="http" android:host="www.example 6 <category 7 android:name="android.intent.category.DEFAULT" 8 /> 9 <category 10 android:name="android.intent.category.BROWSABLE" 11 /> 12 </intent-filter> 13 </activity>
  24. 22 / 31 Web Share Target API 他アプリからの共有を受け取るAPI Web Share

    API とは役割が逆 GET or POST リクエストで受け取る GET の場合は query で受け取る POST の場合は body で受け取る manifest.json で定義 受け取れる共有内容や受け取り方を記述 要インストール インストールしていない場合は利用不可 share_target - Web app manifests | MDN ` ` ` ` ` ` ` ` ` ` ` ` ` `
  25. 23 / 31 Web Share Target API GET の場合 `

    ` 1 // manifest.json 2 { 3 "share_target": { 4 "action": "/receiver/", 5 "method": "GET", 6 "params": { 7 "title": "name", 8 "text": "description", 9 "url": "link" 10 } 11 } 12 } 1 curl 'https://example.com/receiver/'\ 2 ?name=foo\ 3 &description=bar\ 4 &link=piyo
  26. 23 / 31 Web Share Target API GET の場合 `

    ` 2 { 10 } 1 // manifest.json 3 "share_target": { 4 "action": "/receiver/", 5 "method": "GET", 6 "params": { 7 "title": "name", 8 "text": "description", 9 "url": "link" 11 } 12 } 1 curl 'https://example.com/receiver/'\ 2 ?name=foo\ 3 &description=bar\ 4 &link=piyo
  27. 23 / 31 Web Share Target API GET の場合 `

    ` 3 "share_target": { 1 // manifest.json 2 { 4 "action": "/receiver/", 5 "method": "GET", 6 "params": { 7 "title": "name", 8 "text": "description", 9 "url": "link" 10 } 11 } 12 } 1 curl 'https://example.com/receiver/'\ 2 ?name=foo\ 3 &description=bar\ 4 &link=piyo
  28. 23 / 31 Web Share Target API GET の場合 `

    ` 4 "action": "/receiver/", 1 // manifest.json 2 { 3 "share_target": { 5 "method": "GET", 6 "params": { 7 "title": "name", 8 "text": "description", 9 "url": "link" 10 } 11 } 12 } 1 curl 'https://example.com/receiver/'\ 2 ?name=foo\ 3 &description=bar\ 4 &link=piyo
  29. 23 / 31 Web Share Target API GET の場合 `

    ` 5 "method": "GET", 6 "params": { 7 "title": "name", 8 "text": "description", 9 "url": "link" 1 // manifest.json 2 { 3 "share_target": { 4 "action": "/receiver/", 10 } 11 } 12 } 1 curl 'https://example.com/receiver/'\ 2 ?name=foo\ 3 &description=bar\ 4 &link=piyo
  30. 23 / 31 Web Share Target API GET の場合 `

    ` 1 // manifest.json 2 { 3 "share_target": { 4 "action": "/receiver/", 5 "method": "GET", 6 "params": { 7 "title": "name", 8 "text": "description", 9 "url": "link" 10 } 11 } 12 } 1 curl 'https://example.com/receiver/'\ 2 ?name=foo\ 3 &description=bar\ 4 &link=piyo
  31. 24 / 31 Web Share Target API POST の場合 `

    ` 1 // manifest.json 2 { 3 "share_target": { 4 "action": "/receiver/", 5 "method": "POST", 6 "enctype": "multipart/form-data", 7 "params": { 8 "title": "name", 9 "text": "description", 1 curl 'https://example.com/receiver/' \ 2 --form 'name="foo"' \ 3 --form 'description="bar"' \ 4 --form 'link="https://example.com"' \ 5 --form 'files=@"/path/to/file.csv"'
  32. 24 / 31 Web Share Target API POST の場合 `

    ` 4 "action": "/receiver/", 1 // manifest.json 2 { 3 "share_target": { 5 "method": "POST", 6 "enctype": "multipart/form-data", 7 "params": { 8 "title": "name", 9 "text": "description", 1 curl 'https://example.com/receiver/' \ 2 --form 'name="foo"' \ 3 --form 'description="bar"' \ 4 --form 'link="https://example.com"' \ 5 --form 'files=@"/path/to/file.csv"'
  33. 24 / 31 Web Share Target API POST の場合 `

    ` 5 "method": "POST", 1 // manifest.json 2 { 3 "share_target": { 4 "action": "/receiver/", 6 "enctype": "multipart/form-data", 7 "params": { 8 "title": "name", 9 "text": "description", 1 curl 'https://example.com/receiver/' \ 2 --form 'name="foo"' \ 3 --form 'description="bar"' \ 4 --form 'link="https://example.com"' \ 5 --form 'files=@"/path/to/file.csv"'
  34. 24 / 31 Web Share Target API POST の場合 `

    ` 6 "enctype": "multipart/form-data", 7 "params": { 8 "title": "name", 9 "text": "description", 1 // manifest.json 2 { 3 "share_target": { 4 "action": "/receiver/", 5 "method": "POST", 1 curl 'https://example.com/receiver/' \ 2 --form 'name="foo"' \ 3 --form 'description="bar"' \ 4 --form 'link="https://example.com"' \ 5 --form 'files=@"/path/to/file.csv"'
  35. 24 / 31 Web Share Target API POST の場合 `

    ` 1 // manifest.json 2 { 3 "share_target": { 4 "action": "/receiver/", 5 "method": "POST", 6 "enctype": "multipart/form-data", 7 "params": { 8 "title": "name", 9 "text": "description", 1 curl 'https://example.com/receiver/' \ 2 --form 'name="foo"' \ 3 --form 'description="bar"' \ 4 --form 'link="https://example.com"' \ 5 --form 'files=@"/path/to/file.csv"'
  36. 24 / 31 Web Share Target API POST の場合 `

    ` 1 // manifest.json 2 { 3 "share_target": { 4 "action": "/receiver/", 5 "method": "POST", 6 "enctype": "multipart/form-data", 7 "params": { 8 "title": "name", 9 "text": "description", 1 curl 'https://example.com/receiver/' \ 2 --form 'name="foo"' \ 3 --form 'description="bar"' \ 4 --form 'link="https://example.com"' \ 5 --form 'files=@"/path/to/file.csv"'
  37. 27 / 31 Shortcuts API 1 // manifest.json 2 {

    3 "shortcuts": [ 4 { 5 "name": "Open Play Later", 6 "short_name": "Play Later", 7 "description": "View the list of podcasts you saved 8 "url": "/play-later?utm_source=homescreen", 9 "icons": [ 10 { 11 "src": "/icons/play-later.png", 12 "sizes": "192x192" 13 } 14 ] 15 } 16 ]
  38. 27 / 31 Shortcuts API 3 "shortcuts": [ 16 ]

    1 // manifest.json 2 { 4 { 5 "name": "Open Play Later", 6 "short_name": "Play Later", 7 "description": "View the list of podcasts you saved 8 "url": "/play-later?utm_source=homescreen", 9 "icons": [ 10 { 11 "src": "/icons/play-later.png", 12 "sizes": "192x192" 13 } 14 ] 15 }
  39. 27 / 31 Shortcuts API 4 { 5 "name": "Open

    Play Later", 6 "short_name": "Play Later", 7 "description": "View the list of podcasts you saved 8 "url": "/play-later?utm_source=homescreen", 9 "icons": [ 10 { 11 "src": "/icons/play-later.png", 12 "sizes": "192x192" 13 } 14 ] 15 } 1 // manifest.json 2 { 3 "shortcuts": [ 16 ]
  40. 27 / 31 Shortcuts API 5 "name": "Open Play Later",

    1 // manifest.json 2 { 3 "shortcuts": [ 4 { 6 "short_name": "Play Later", 7 "description": "View the list of podcasts you saved 8 "url": "/play-later?utm_source=homescreen", 9 "icons": [ 10 { 11 "src": "/icons/play-later.png", 12 "sizes": "192x192" 13 } 14 ] 15 } 16 ]
  41. 27 / 31 Shortcuts API 6 "short_name": "Play Later", 1

    // manifest.json 2 { 3 "shortcuts": [ 4 { 5 "name": "Open Play Later", 7 "description": "View the list of podcasts you saved 8 "url": "/play-later?utm_source=homescreen", 9 "icons": [ 10 { 11 "src": "/icons/play-later.png", 12 "sizes": "192x192" 13 } 14 ] 15 } 16 ]
  42. 27 / 31 Shortcuts API 7 "description": "View the list

    of podcasts you saved 1 // manifest.json 2 { 3 "shortcuts": [ 4 { 5 "name": "Open Play Later", 6 "short_name": "Play Later", 8 "url": "/play-later?utm_source=homescreen", 9 "icons": [ 10 { 11 "src": "/icons/play-later.png", 12 "sizes": "192x192" 13 } 14 ] 15 } 16 ]
  43. 27 / 31 Shortcuts API 8 "url": "/play-later?utm_source=homescreen", 1 //

    manifest.json 2 { 3 "shortcuts": [ 4 { 5 "name": "Open Play Later", 6 "short_name": "Play Later", 7 "description": "View the list of podcasts you saved 9 "icons": [ 10 { 11 "src": "/icons/play-later.png", 12 "sizes": "192x192" 13 } 14 ] 15 } 16 ]
  44. 27 / 31 Shortcuts API 9 "icons": [ 10 {

    11 "src": "/icons/play-later.png", 12 "sizes": "192x192" 13 } 14 ] 1 // manifest.json 2 { 3 "shortcuts": [ 4 { 5 "name": "Open Play Later", 6 "short_name": "Play Later", 7 "description": "View the list of podcasts you saved 8 "url": "/play-later?utm_source=homescreen", 15 } 16 ]
  45. 27 / 31 Shortcuts API 1 // manifest.json 2 {

    3 "shortcuts": [ 4 { 5 "name": "Open Play Later", 6 "short_name": "Play Later", 7 "description": "View the list of podcasts you saved 8 "url": "/play-later?utm_source=homescreen", 9 "icons": [ 10 { 11 "src": "/icons/play-later.png", 12 "sizes": "192x192" 13 } 14 ] 15 } 16 ]
  46. 29 / 31 各ブラウザ対応状況 API Desktop Chrome Desktop Safari Mobile

    Chrome Mobile Safari Web Share API Yes Chrome OS Windows Yes Yes Yes Web Share Target API Yes No Yes No Shortcuts API Yes No Yes No
  47. 30 / 31 まとめ PWA の復習 PWA はWeb の最新技術を利用したWeb アプリ

    3 つの柱を中心に構築する PWA チェックリスト PWA をより良いものにするためのチェックリスト PWA に限らずWeb のチェックリスト Web API による機能拡張 Web API を駆使すればネイティブアプリに近づけられる とはいえすべてのブラウザで対応しているわけではない
  48. 31 / 31 参考 What are Progressive Web Apps? |

    Articles | web.dev Adaptive icon support in PWAs with maskable icons | Articles | web.dev What makes a good Progressive Web App? | Articles | web.dev Web Share API - Web APIs | MDN Integrate with the OS sharing UI with the Web Share API | Articles | web.dev Intent | Android Developers share_target - Web app manifests | MDN Receiving shared data with the Web Share Target API - Chrome for Developers shortcuts - Web app manifests | MDN Web Share API でPWA に共有機能を実装する