$30 off During Our Annual Pro Sale. View Details »

進化した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. 進化したWeb
    技術でPWA

    ネイティブアプリに近づける
    Yuhei FUJITA
    2023-11-18@ZORKS
    沖縄

    View Slide

  2. 2 / 31
    自己紹介
    名前:Yuhei FUJITA
    X
    :@Yuhei_FUJITA
    コミュニティ運営
    Vue Fes
    PWA Night
    VS Code Meetup
    趣味
    キャンプ
    フィルムカメラ

    View Slide

  3. 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

    View Slide

  4. 4 / 31
    今日の話

    View Slide

  5. 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

    View Slide

  6. 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

    View Slide

  7. 7 / 31
    なぜネイティブアプリにするのか?
    よりパワフルなアプリを作れる
    処理速度・OS
    の提供するAPI
    モバイルSafari
    のサポート
    使えないAPI
    ・わかりにくいインストール手順
    ユーザーの認知度が高い
    アプリストア・インストールの導線

    View Slide

  8. 8 / 31
    Google Trends
    で見るPWA
    上が日本、下が世界

    View Slide

  9. 9 / 31
    より良いPWA
    にするには?

    View Slide

  10. 10 / 31
    今回はPWA
    化の話は割愛
    去年の「PWA
    をインストールしやすく
    するための実装 by
    まぁし(知念)さ
    ん」がわかりやすいのでそちらを参照
    してください。
    PWA
    をインストールしやすくするための実装 by
    まぁし(知念)さん
    フロントエンドカンファレンス沖縄2022 - YouTube

    View Slide

  11. 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 }

    View Slide

  12. 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 }

    View Slide

  13. 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 }

    View Slide

  14. 12 / 31
    より良いユーザー体験を
    提供するために

    View Slide

  15. 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

    View Slide

  16. 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

    View Slide

  17. 15 / 31
    PWA

    ネイティブアプリに
    近づける
    (タイトル回収)

    View Slide

  18. 16 / 31
    ネイティブアプリならある「あの機能」を
    PWA
    で提供する
    共有する機能を提供するWeb Share API
    共有される機能を提供するWeb Share Target API
    特定機能を瞬時に呼び出すShortcuts API

    View Slide

  19. 17 / 31
    Web Share API
    PWA
    から共有する

    View Slide

  20. 18 / 31
    Web Share API
    共有機能を提供するAPI
    OS
    標準の共有メニューを呼び出せる
    統一されたUI
    を提供可能
    さまざまなファイルを共有可能
    pdf
    audio
    image
    text
    video
    Web Share API - Web APIs | MDN

    View Slide

  21. 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 => {
    11 if(navigator.share) {
    12 try {
    13 await navigator.share(data);
    14 console.log("success")
    15 } catch (err) {

    View Slide

  22. 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 => {
    12 try {
    13 await navigator.share(data);
    14 console.log("success")
    15 } catch (err) {

    View Slide

  23. 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 => {
    11 if(navigator.share) {
    12 try {
    13 await navigator.share(data);
    14 console.log("success")
    15 } catch (err) {

    View Slide

  24. 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 => {
    11 if(navigator.share) {
    13 await navigator.share(data);
    14 console.log("success")

    View Slide

  25. 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 => {
    11 if(navigator.share) {
    12 try {
    15 } catch (err) {

    View Slide

  26. 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 => {
    11 if(navigator.share) {
    12 try {
    14 console.log("success")
    15 } catch (err) {

    View Slide

  27. 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 => {
    11 if(navigator.share) {
    12 try {
    13 await navigator.share(data);
    14 console.log("success")
    15 } catch (err) {

    View Slide

  28. 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 => {
    11 if(navigator.share) {
    12 try {
    13 await navigator.share(data);
    14 console.log("success")
    15 } catch (err) {

    View Slide

  29. 20 / 31
    For Android Developers
    Intent | Android Developers
    これがないとAndroid
    アプリはPWA
    からの共有を受け取れない。
    1
    2
    3
    4
    5 8 />
    9 10 android:name="android.intent.category.BROWSABLE"
    11 />
    12
    13

    View Slide

  30. 20 / 31
    For Android Developers
    Intent | Android Developers
    これがないとAndroid
    アプリはPWA
    からの共有を受け取れない。
    9 10 android:name="android.intent.category.BROWSABLE"
    11 />
    1
    2
    3
    4
    5 8 />
    12
    13

    View Slide

  31. 20 / 31
    For Android Developers
    Intent | Android Developers
    これがないとAndroid
    アプリはPWA
    からの共有を受け取れない。
    1
    2
    3
    4
    5 8 />
    9 10 android:name="android.intent.category.BROWSABLE"
    11 />
    12
    13

    View Slide

  32. 21 / 31
    Web Share Target API
    PWA
    に共有する

    View Slide

  33. 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
    ` ` ` `
    ` ` ` `
    ` ` ` `
    ` `

    View Slide

  34. 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

    View Slide

  35. 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

    View Slide

  36. 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

    View Slide

  37. 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

    View Slide

  38. 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

    View Slide

  39. 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

    View Slide

  40. 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"'

    View Slide

  41. 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"'

    View Slide

  42. 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"'

    View Slide

  43. 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"'

    View Slide

  44. 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"'

    View Slide

  45. 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"'

    View Slide

  46. 25 / 31
    Shortcuts API

    View Slide

  47. 26 / 31
    Shortcuts API
    ショートカットメニューを追加するAPI
    アプリアイコン長押し時に最大4
    つ表示可能
    任意の機能の呼び出し
    アプリを開くことなく直接機能を呼び出せる
    manifest.json
    で定義
    ショートカットの内容を記述
    shortcuts - Web app manifests | MDN
    ` `

    View Slide

  48. 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 ]

    View Slide

  49. 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 }

    View Slide

  50. 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 ]

    View Slide

  51. 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 ]

    View Slide

  52. 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 ]

    View Slide

  53. 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 ]

    View Slide

  54. 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 ]

    View Slide

  55. 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 ]

    View Slide

  56. 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 ]

    View Slide

  57. 28 / 31
    ネイティブアプリっぽくなったPWA
    Web Share API Web Share Target API Shortcuts API

    View Slide

  58. 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

    View Slide

  59. 30 / 31
    まとめ
    PWA
    の復習
    PWA
    はWeb
    の最新技術を利用したWeb
    アプリ
    3
    つの柱を中心に構築する
    PWA
    チェックリスト
    PWA
    をより良いものにするためのチェックリスト
    PWA
    に限らずWeb
    のチェックリスト
    Web API
    による機能拡張
    Web API
    を駆使すればネイティブアプリに近づけられる
    とはいえすべてのブラウザで対応しているわけではない

    View Slide

  60. 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
    に共有機能を実装する

    View Slide