ウェブサイト検索

Vite と Vue を使用してオフラインで利用可能な Progressive Web App News Aggregator を構築する


導入

プログレッシブ Web アプリ (PWA) は、ネイティブのような機能と向上したユーザー エクスペリエンスを提供するモバイル フレンドリーな Web アプリケーションです。主な利点としては、任意のデバイスにアプリをインストールし、オフラインでアクセスできることが挙げられます。 PWA の必須コンポーネントは、Web アプリ マニフェストと Service Worker です。 IndexedDB API は、構造化データを永続化するために利用できます。

このチュートリアルでは、Vue と Vite を使用して構築されたサンプル Web サイトを、エンド ユーザーが任意のデバイスにインストールできるオフラインで利用可能な PWA に変換します。 Web アプリのプレゼンテーション層をキャッシュするための Web アプリのマニフェストと Service Worker を作成します。

また、IndexDB を使用してアプリに関する構造化データを保存し、オフラインでもデータを利用できるようにします。

前提条件

このチュートリアルを進めるには、次のものが必要です。

  • Node バージョン 18.15 以降および npm バージョン 9 以降を備えた Node.js 開発環境
  • 開発環境への git インストール
  • Vue および Vite を使用した開発経験があること。
  • News API アカウントと API キー。ここで登録して入手できます。

ステップ 1 - プロジェクトのセットアップ

このステップでは、サンプル プロジェクトのクローンを作成し、ローカル開発環境にセットアップします。 Vite と VueJS を使用して、より高速で無駄のない開発エクスペリエンスを実現し、スタイル設定には Tailwind CSS を使用します。

まず、このチュートリアル用に構築済みの Vue アプリを GitHub から複製します。 What's New アプリを使用します。これは、News API からデータを取得し、それらを見出し、一般的なニュース項目、およびパーソナライズされたカスタマイズ可能なニュース フィードとして分類して表示するニュース アグリゲーターです。

GitHub からプロジェクトのクローンを作成するには、まず、GitHub Web インターフェイスの右上隅にある [フォーク] ボタンをクリックして、新機能プロジェクトのコピーを作成する必要があります。

次に、コマンド ライン アプリを開き、次のコマンドを実行します。

git clone https://github.com/{your-github-username}/whats-new.git

上記のコマンドで強調表示されている URL の部分を実際の GitHub ユーザー名に置き換える必要があります。

コマンドラインで `whats-new` ディレクトリに移動します。

cd whats-new

スターター コード ブランチ do/starter-code をチェックアウトします。

git checkout do/starter-code

API キーの登録と取得をまだ行っていない場合は、今すぐ登録して取得する必要があります。

次に、お気に入りのコード エディターを使用して、.env.example ファイルのコピーを作成し、その名前を .env に変更します。

VITE_NEWS_API_KEY のプレースホルダー your-news-api-key の値を News API の API キーに置き換えて保存します。 .env ファイル。

プロジェクトの依存関係をインストールします。

npm install

アプリケーションを実行します。

npm run dev

ターミナルに次の出力が表示されるはずです。

> whats-new@0.0.0 dev
> vite

  VITE v5.0.10  ready in 3639 ms

  ➜  Local:   http://localhost:5173/
  ➜  Network: use --host to expose
  ➜  press h + enter to show help

ブラウザでアプリを起動すると、次の画面が表示されます。

ステップ 2 - Web アプリのマニフェスト構成の作成

Web アプリケーション マニフェストは、Web アプリケーションに関連付けられたメタデータを保存するための集中的な場所を提供する JSON ベースのファイルです。

プログレッシブ Web アプリ開発では、Web マニフェストを使用して、ブラウザーが Web アプリケーションをデバイスにインストールするために必要な情報 (アプリ名やアイコンなど) を提供します。

プログレッシブ Web アプリの Web アプリ マニフェストの中核には、それが有効であるためには、メンバーとも呼ばれる 4 つのトップレベル キーが含まれている必要があります。それらは、nameiconsstart_url、および display です。

Web アプリのマニフェスト ファイルの構成を手動で記述し、必要なアイコンを生成できます。ただし、このチュートリアルでは、この PWA マニフェスト ジェネレーター ツールを使用して構成とアイコンの両方を生成します。

ツールを起動し、以下に示すようにフォームに記入します。

アイコンフィールドには、whats-new プロジェクト フォルダーのルートにある app-icon-image.png ファイルをアップロードします。

[マニフェストの生成] ボタンをクリックしてマニフェストを生成します。このツールは、マシンにダウンロードできる zip フォルダーを生成します。 zip フォルダーからファイルを抽出します。

manifest.webmanifest ファイルと、各アイコンの側面にプレフィックスが付いたいくつかの画像ファイルが表示されるはずです。画像ファイルをアプリケーションの /public フォルダーにコピーします。

任意のテキスト エディタで manifest.webmanifest ファイルを開きます。その内容は、以下の画像に示されているスニペットと同様である必要があります。

次のセクションのファイルで JSON オブジェクトを使用する必要があります。

ステップ 3 - Web アプリのマニフェストとサービス ワーカーの生成

このステップでは、上記の構成でアプリケーションの Web アプリケーション マニフェストを生成し、vite-plugin-pwa という Vite プラグインを使用して Service Worker を作成します。

このカスタマイズ可能なプラグインを使用すると、既存の Vite アプリケーションに PWA 機能を追加できます。

アプリケーションにプラグインを追加するには、npm 経由でインストールする必要があります。

npm install -D vite-plugin-pwa

次に、以下に示すように vite.config.js ファイルを編集して、アプリケーションのプラグインを構成します。

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import { VitePWA } from 'vite-plugin-pwa'

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [
    vue(),
    VitePWA({})
  ],
})

vite-plugin-pwa パッケージは、プラグインを構成するためのさまざまなオプションのセットを引数として含むオブジェクトを受け取る VitePWA という関数をエクスポートします。このプラグインに設定できるオプションの 1 つは、manifest オプションです。このオプションを設定すると、プラグインが Web アプリ マニフェストを生成するために使用するマニフェスト構成をプラグインに与えることになります。

VitePWA() 関数呼び出しのオブジェクトに manifest キーを追加し、生成された manifest.webmanifest ファイルから JSON オブジェクトに設定します。変更を保存します。最終的には、次のような Vite 構成オブジェクトが完成するはずです。

export default defineConfig({
  plugins: [
    vue(),
    VitePWA({
      manifest: {
        "theme_color": "#0F172A",
        "background_color": "#f5f8fa",
        "display": "standalone",
        "scope": "/",
        "start_url": "/",
        "name": "What's New - Vue News Aggregator Site",
        "short_name": "What's New",
        "description": "A news aggregator pulling news items from News API.",
        "icons": [
          {
            "src": "/icon-192x192.png",
            "sizes": "192x192",
            "type": "image/png"
          },
          {
            "src": "/icon-256x256.png",
            "sizes": "256x256",
            "type": "image/png"
          },
          {
            "src": "/icon-384x384.png",
            "sizes": "384x384",
            "type": "image/png"
          },
          {
            "src": "/icon-512x512.png",
            "sizes": "512x512",
            "type": "image/png"
          }
        ]
      }
    })
  ],
})

これで、インストール可能な PWA が動作するようになりました。アプリをビルドするたびに、プラグインはブラウザー用の Web アプリ マニフェストを生成し、それをアプリケーションのエントリ ポイント (/) で構成します。また、Service Worker も作成され、Service Worker をブラウザに登録するためのスクリプトが追加されます。アプリが PWA であることを確認するには、ブラウザーにこれらのアーティファクトが存在することをどのように確認すればよいでしょうか?

PWA の検証

ブラウザの開発者ツールを開き、[アプリケーション] タブに移動する必要があります。このチュートリアルでは、Chrome ブラウザを参照として使用します。 Chrome で開発者ツールを開くには、CTRL+SHIFT+I (Windows) キーまたは OPTION+CMD+I (MAC) キーを押します。サイドバーの [アプリケーション] タブの下に、[アプリケーション] というセクションが表示されます。このセクションにはマニフェストというサブリンクがあります。そのリンクをクリックすると、上記のプラグインに提供した内容に関連する情報が表示されるはずです。ただし、この段階では、以下の画面が表示されます。

さらに、Service Worker セクションに Service Worker が登録されていないことがわかります。

また、[ストレージ] セクションで [キャッシュ ストレージ] をクリックすると、キャッシュされた情報が表示されますが、これもマニフェスト* セクションと同様です。空であることがわかります。

ブラウザのツールバー セクションにも、下矢印アイコン ボタンのあるデスクトップが表示されます。

現在、開発モードでアプリケーションを実行しているときにブラウザでアプリケーションを表示しているため、これらの画面は現在空白で、ボタンが表示されません。アプリケーションを開発モードで実行しており、プラグインはデフォルトでこれらのアーティファクトを生成し、実稼働モードで予期される動作を生成するように構成されているため、この動作は予期されています。次の 2 つのセクションでは、この問題に対処するために必要なオプションについて説明します。

アプリのビルドとプレビュー

npm run build コマンドと npm runreview コマンドを連続して実行して、PWA を確認し、ブラウザーで URL を起動することで、アプリをプレビューするオプションがあります。

ビルド プレビュー URL は、開発サーバーによって開発ビルド用に生成される URL とは異なります。

上記のプロセスを繰り返します。以下の画像に示すように、マニフェスト、キャッシュ ストレージ内のいくつかの項目、およびインストール ボタン (黄色でマーク) が表示されるはずです。

このオプションを使用すると、コードに変更を加えるたびにアプリを再構築する必要があり、非常に不便になる可能性があります。以下に示すように、次のオプションではこの不便さを解消します。ただし、次の主要なステップで説明する注意点がいくつかあります。ただし、チュートリアルのこの時点では、現在の目的のために、開発モードでプラグインを操作する方法を学びます。

Vite PWA プラグイン設定での devOptions オプションの使用

vite-plugin-pwa パッケージには、バージョン v0.11.13 から開発モードでアプリを PWA として表示する機能をサポートする devOptions オプションが用意されています。 >。以下のコード ブロックで強調表示されている行に示すように、enabled サブオプションを true に設定します。

export default defineConfig({
  plugins: [
    vue(),
    VitePWA({
      devOptions: {
        enabled: true
      },
      manifest: {...}
    })
  ],
})

変更を保存して開発サーバーを再起動すると、以下の強調表示された行に示すように、コンソール出力にいくつかの追加行が追加されていることがわかります。

> whats-new@0.0.0 dev
> vite

  VITE v5.0.10  ready in 1490 ms

  ➜  Local:   http://localhost:5173/
  ➜  Network: use --host to expose
  ➜  press h + enter to show help

PWA v0.17.4
mode      generateSW
precache  2 entries (0.12 KiB)
files generated
  dev-dist\sw.js
  dev-dist\workbox-9637eeee.js
12:28:22 p.m. [vite] vite.config.js changed, restarting server...
12:28:22 p.m. [vite] server restarted.

ログは、プラグインが使用するように構成されているモードを示します。 Service Worker を生成するように構成されているため、generateSW 値が表示されます。ログには、dev-dist フォルダーにいくつかのファイルが生成されていることも示されています。 sw.js ファイルは、プラグインによって作成される Service Worker スクリプトです。このスクリプトにはキャッシュ戦略が含まれています。このプラグインは workbox-build パッケージを利用するため、Service Worker のビルドに使用される ID に関連付けられたワークボックス ファイルも生成します。最後に、ログには表示されない registerSW.js というファイルも生成されます。 registerSW.js ファイルは、ブラウザが Service Worker をサポートしているかどうかを確認し、アプリケーションのロード時に Service Worker を登録するスクリプトです。

アプリケーションが起動してブラウザに読み込まれると、registerSW.js ファイルによって、sw.js で作成された Service Worker が登録され、ブラウザに加えられた更新を確認できます。上記の手順を再試行して、ブラウザのマニフェストキャッシュ ストレージのインターフェイスを確認します。

さらに、以下に示すように、[アプリケーション] タブの Service Worker セクションでアクティブな Service Worker を確認できます。

ステップ 4 - アプリケーション ファイルとアセットのキャッシュの処理

チュートリアルのこの時点では、アプリケーションは PWA ですが、まだオフラインに対応していません。これを確認したい場合は、ブラウザ開発ツールの [ネットワーク] タブに移動し、オフラインで実行するように設定します。空白の画面が表示されることがわかります。

オフライン動作をサポートするには、アプリケーション ファイルとアセットをキャッシュする必要があります。これを行うには、以下の強調表示された行に示すように、Vite PWA プラグインの設定をさらに変更する必要があります。

export default defineConfig({
  plugins: [
    vue(),
    VitePWA({
      devOptions: {...},
      includeAssets: [
        "**/*"
      ],
      manifest: {...},
      workbox: {
        globPatterns: ["**/*.{js,css,html,png}"]
      }
    })
  ],
})

includeAssets オプションは、Service Worker プリキャッシュの public フォルダーにある他の静的アセットを含めるようプラグインに指示します。このような静的アセットの例としては、public フォルダーに追加した Web サイトのファビコン、SVG ファイル、フォント ファイルなどがあります。

以前に public ディレクトリに追加されたアイコンは、プラグイン設定の manifest オプションで追加されたため、デフォルトで含まれています。

workboxglobPatterns オプションは、キャッシュするアプリケーション ファイルの種類を制限するために使用されます。この場合、.js.css.html、および .png ファイルをキャッシュする必要があります。それがメインのアプリケーション ファイルです。

これらの新しい変更により、アプリケーションはオフライン モードで実行できるようになります。実際の動作を確認するには、別のターミナルを開き、現在開発モードで実行している場合は、npm run build を使用してアプリケーションを再構築する必要があります。

「この状況ではなぜ違うのですか?」「開発モードでのプログレッシブ Web アプリケーションの作成に関連するすべての処理には、devOptions オプションで十分ではないでしょうか?」と疑問に思うかもしれません。すべては、開発サーバーの実行時に生成されたビルドと運用ビルドの違いを修正することに要約されます。

Vite の開発モードでアプリケーションを実行する場合、開発ビルドはメモリ内で管理され、ディスクには書き込まれません。つまり、運用アプリケーションのビルドで得られるような、dist フォルダー内のアプリケーション ファイルのビルド出力はありません。そのため、Service Worker がプリキャッシュに追加するものは何もありません。プリキャッシュに追加するものが何もない場合、ブラウザのキャッシュ ストレージには何もキャッシュされません。デフォルトでプリキャッシュに追加される項目は、index.html ファイル、つまりエントリ ポイントと registerSW.js ファイルだけです。

index.html ファイルには、アプリケーションに必要なものすべてをロードするために main.js ファイルが必要です。すでにご存知かもしれませんが、このファイルまたはインポートされる他のファイルはキャッシュされていないため、アプリケーションをオフライン アプリケーションとして開発モードで実行すると、常に空白の画面が表示されます。

キャッシュされていないことを確認するには、アプリケーションの実稼働ビルドを実行した後、dev-dist の両方の sw.js ファイルの内容を比較します。 dist ディレクトリ。 precacheAndRoute 関数の引数として提供される項目に注意する必要があります。

npm runreview を使用してアプリケーションの実稼働ビルドを実行し、ブラウザ上で起動します。開発ビルドを実行しているブラウザー タブと並べて配置できます。ブラウザ開発ツールの [ネットワーク] タブに移動し、オフラインで実行するように設定し、ページを更新します。アプリケーション画面はまだ表示されているはずです。

これは、アプリケーションがロードされたときに Service Worker が登録され、ビルド ファイルがキャッシュされたためです。ファイルを表示するには、[アプリケーション] タブの [キャッシュ ストレージ] セクションに移動し、workbox-precache-* というラベルのキャッシュされた項目をクリックします。アプリケーションの dist フォルダー内のファイルに対応するファイル名が表示されるはずです。

参考までにスクリーンショットを次に示します。

開発サーバーのブラウザ タブのキャッシュと比較することもできます。参考までに、別のスクリーンショットを次に示します。

注意すべき重要な点は、エントリ数 (青色でマーク)、ポート番号 (黄色でマーク)、およびキャッシュされたファイルのテーブル (赤色でマーク) の違いです。

ここまで進んだなら、おめでとうございます!これで、アプリケーションをオフラインで使用できるようにすることができました。次のステップは何ですか?アプリケーションは API から取得したデータを表示します。現時点では、アプリケーション ファイルのみがキャッシュされています。次のセクションでは、indexDB を追加してこのギャップを修正します。

ステップ 5 - IndexDB を使用したアプリケーション データのキャッシュ

IndexedDB は、クライアント側で構造化データや BLOB (バイナリ データ) を保存するためのブラウザ API です。これは、データを JavaScript オブジェクトのような構造にキーと値のペアとして保存するトランザクション データベース システムです。

このセクションでは、IndexedDB データベースをアプリケーションに追加し、それを使用してアプリケーション内のデータ キャッシュを管理します。

API からデータを取得するようにアプリを更新する

まず、アプリに表示されるデータの取得方法にいくつかの変更を加える必要があります。現在、表示されているデータはアプリ内変数として保存されています。

NewsItems.vue ファイルを開きます。 testNewsItemsData 変数と関連するコメントを削除します。

以下に示す <script; setup> タグ内の getCustomizedTabNewsItems という関数の部分を見つけて、コメント内に記載されている内容を実行します。

const getCustomizedTabNewsItems = () => {
  //...
  if (definedCustomizations) {
    //...
    /** TODO: Remove the the line below after setting up your API KEY and delete this comment */
    newsItems.value = [
      {
        source: {
          id: 'buzzfeed',
          name: 'Buzzfeed'
        },
        author: 'Melanie Aman',
        title: '37 Beauty Products Under $25 That Don\'t Skimp On Results',
        description: 'A heavy serving of getting more than you pay for ...don\'t mind if we do.View Entire Post ›',
        url: 'https://www.buzzfeed.com/melanie_aman/beauty-products-under-25-that-dont-skimp-on-results',
        urlToImage: 'https://img.buzzfeed.com/buzzfeed-static/static/2023-12/11/20/enhanced/46ff3dce9b4d/original-451-1702327901-3.jpg?crop=4205:2208;0,0%26downsize=1250:*',
        publishedAt: '2023-12-24T09:00:03Z',
        content: 'Psst! Bio-Oil contains retinol, which accelerates skin turnover but can make you more sensitive to the sun so don\'t forget your sunscreen!\r\nPromising review: \'I was skeptical of how amazing the revie… [+689 chars]'
      },
      // ...
    ]

    /** TODO: Uncomment after setting up your API KEY */
    // const { fetchedNewsItems, getNewsItems } = useNewsItems(requestUrl)
    // nextTick(async () => {
    //   await getNewsItems()
    //   newsItems.value = fetchedNewsItems.value
    // })
  }
}

ファイルの <script; setup> タグ内にさらに進み、以下で強調表示されているコードを見つけます。

if (props.tab.id === APPLICATION_TABS[2].id && props.retrieveCustomCuratedContent) {
  // ...
} else if (props.tab.id === APPLICATION_TABS[2].id && !props.retrieveCustomCuratedContent) {
  // ...
} else {
  // get news items for other tabs ...
  // TODO: Remove the the line below after setting up your API KEY and delete this comment
  newsItems.value = testNewsItemsData

  /** TODO: Uncomment after setting up your API KEY */
  // const { fetchedNewsItems, getNewsItems } = useNewsItems(requestUrl)
  // nextTick(async () => {
  //   await getNewsItems()
  //   newsItems.value = fetchedNewsItems.value
  // })
}

また、コメントに記載されていることを実行してください。

最後に、SourceToggleTokens.vue ファイルを開き、以下に示すコードを見つけます。

watch(sourcesRetrievalParameters, async (newParams) => {
  // ...

  /** TODO: Uncomment after setting up your API KEY */
  // const { newsSources: apiNewsSources, getNewsSources } = useNewsSources(queryString.value)
  // await getNewsSources()
  // newsSources.value = apiNewsSources.value
})

指示に従ってコードのコメントを解除し、変更を保存します。

最初に予想どおりに API キーを追加し、上で説明したようにファイルを更新した場合は、ホームページに別のニュース項目のセットが表示されるはずです。

さらに、上で青く強調表示されている設定アイコンをクリックすると、以下の画面に似たニュース ソースの長いリストが表示されます。

何を保管するかを決める

これまでは、アプリケーションのビジュアルだけに注目してきました。何を保存するかを理解するために、アプリケーションが News API にクエリを実行する方法について簡単に説明します。

アプリケーションには、見出し探索キュレーションの 3 つのタブがあります。 constants/general.js というファイルは、APPLICATION_TABS という変数にこれらの各タブの情報を保持します。各タブ オブジェクトには、apiCallURLapiCallParameters という 2 つのプロパティがあります。各 apiCallURL 値は、News API をクエリするための特定のエンドポイントで始まる文字列であり、そのエンドポイントに予期されるリクエスト パラメーターを表す中括弧内にいくつかのプレースホルダー値が含まれていることがわかります。これらのプレースホルダー値は、そのタブに対応する apiCallParameters 配列の要素と同じです。以下に示す [見出し] タブのオブジェクトを見てください。

export const APPLICATION_TABS = [
  {
    name: 'Headlines',
    id: 'headlines-tab',
    target: '#headlines',
    ariaControls: 'headlines',
    apiCallURL: 'https://newsapi.org/v2/top-headlines?country={country}&category={category}',
    apiCallParameters: ['category', 'country']
  },
  { // ... },
  { //... }
]

URL は、dynamic-string-parser.js ユーティリティの parse という関数を使用して完全なエンドポイントを生成するようにフォーマットされます。次に、それが引数として useNewsItems という関数に渡されます。 useNewsItems 関数は、2 つのものを返すコンポーザブルです。fetchedNewsItems - API からのデータを保持するリアクティブ配列、および getNewsItems - 関数実際のネットワークフェッチを実行し、fetchedNewsItems オブジェクトを更新します。

getNewsItems 関数を呼び出し、返された配列を newsItems に設定します。

以下のスニペットは、getNewsItems 関数を示しています。

async function getNewsItems() {
  try {
   const apiResponse = await fetch(url.value, {
    headers: {
     'X-Api-Key': import.meta.env.VITE_NEWS_API_KEY
    }
   })
   const data = await apiResponse.json()
   fetchedNewsItems.value = data.articles
   count.value = data.totalResults
  } catch (error) {

  }
 }

この関数を更新して、API からのニュース項目を次のセクションで作成する IndexedDB データベースに保存します。

アプリでの IndexedDB データベースのインストールとセットアップ

まず、npm から idb パッケージをインストールする必要があります。

npm install idb

idb パッケージは、このクライアント側ストレージと対話するために使用される IndexedDB API の軽量ラッパーです。 IndexedDB API は、そのままの形式で使用するには少し奇妙で、ユーザーフレンドリーではないという評判があります。このパッケージは、使いやすくする機能拡張を提供します。

次に、useIDB というコンポーザブル ファイルを作成し、次のコードをそれに追加する必要があります。このコンポーザブルには、News API からデータをフェッチするコンポーネントにプラグインできる再利用可能なコードが含まれています。

import { openDB } from 'idb'
import { ref } from 'vue'

const versionNumber = ref(1)

const useIDB = () => {
    const db = ref(null)

  return {
   db,
   versionNumber
  }
}

export default useIDB

このコンポーザブルは変数 db を返します。この変数は、idbopenDB 関数とグローバル versionNumber を使用してすぐにインスタンス化します。 versionNumber がファイルの先頭にある理由は、それが実行中のいつでもアプリに対してグローバルであることを保証するためであり、コードを呼び出すたびに新しいものを作成する必要はありません。構成可能。

以下のスニペットは上記に基づいて構築されており、openDB を使用してデータベースを開いた後、戻り値を db に割り当てます。

const useIDB = () => {
    const db = ref(null)
    const getDB = async (version, objectStoreName, keyPath) => {
        versionNumber.value += 1
        db.value = await openDB('whats-new', version, {
            upgrade(db, oldVersion) {
                if (version === 1 && oldVersion === 0) {
                    db.createObjectStore(objectStoreName, {
                        keyPath
                    })
                }

                if (version > 1) {
                    if (!db.objectStoreNames.contains(objectStoreName)) {
                        db.createObjectStore(objectStoreName, {
                            keyPath
                        })
                    }
                }
            }
        })
    }

    return {
        db,
        versionNumber,
        getDB
    }
}

getDB という新しい関数は、3 つの引数 versionobjectStoreName、および keyPath を取る非同期関数です。 IndexedDB API について話すときに使用される用語がいくつかあります。 1 つはデータベースのバージョンです。これは、データベースを何らかの方法で変更する必要がある場合に便利です。もう 1 つはオブジェクト ストアの概念です。

オブジェクト ストアは SQL データベースのテーブルに似ていると考えてください。

最後に、キーパスとして知られるものがあります。 keyPath は、オブジェクト ストアに追加するデータ内の固有のフィールドまたは列の名前です。

getDB 関数は、上記のコードに見られるようにデータベースを作成するために、使用側クライアント コンポーネントからこれら 3 つのパラメーターを必要とします。 versionNumber をインクリメントした後、db の値は、作成するデータベースの名前を指定して openDB を呼び出した結果に設定されます - 'whats-new' - およびバージョン番号。

openDB 関数には、3 番目の引数としてオブジェクトもあります。このオブジェクトには、データベースのセットアップに使用されるイベントが含まれています。

この使用例では、upgradeDb イベントのみが必要です。 upgradeDb イベントは、作成されたデータベースのバージョン番号が既存のデータベースのバージョン番号より大きい場合に実行されます。 oldVersion (ブラウザに存在するデータベースの現在のバージョン番号) が 0 で、作成するバージョンが 1 の場合、次のようにします。 objectStoreNamekeyPath を指定してオブジェクト ストアを作成します。

ただし、作成するバージョンが 1 より大きい場合は、指定された を持つオブジェクト ストアがない場合にのみ、指定された objectStoreNamekeyPath を使用してオブジェクト ストアを作成します。 >オブジェクトストア名

最後に、getDB 関数を返します。

このコンポーザブルが最後に行う必要があるのは、db が存在する場合、そこからデータを取得するためのインターフェイスを提供することです。これを実現する方法については、以下のコード スニペットを参照してください。

const useIDB = () => {
    const db = ref(null)
    // getDB

    const getDataFromObjectStore = async (objectStoreName) => {
        let data

        if (db.value) {
            data = await db.value.getAll(objectStoreName)
        } else {
            const db = await openDB('whats-new', undefined)
            data = await db.getAll(objectStoreName)
        }

        return data
    }

    return {
        //...
        getDataFromObjectStore
    }
}

db.valuenull でない場合は、オブジェクト ストアからすべてのデータを取得します。それ以外の場合は、ブラウザで現在の db を開き、すべてのデータを取得します。

コンポーザブルを作成したら、以下に示すように getNewsItems に必要な変更を加えることができます。

async function getNewsItems() {
  const { db, getDB, versionNumber, getDataFromObjectStore } = useIDB()

  try {
   // ... apiResponse
   const data = await apiResponse.json()
   // setting fetchedNewsItems

   await getDB(versionNumber.value, url.value, 'url')
   data.articles.forEach(async (article) => {
    await db.value.put(url.value, article)
   })
  } catch (error) {
   if (error instanceof TypeError && error.message.includes('Failed to fetch')) {
    const cachedItems = await getDataFromObjectStore(url.value)
    fetchedNewsItems.value = cachedItems
   }
  }
 }

API から記事を取得する試みが成功した場合は、getDB を呼び出し、indexedDB API の put 関数を使用して記事をデータベースに保存します。 put 関数は、最初のパラメータとして指定されたオブジェクト ストアに項目を追加します。

フェッチの試行が失敗した場合は、エラーがフェッチ エラーであることを確認し、getDataFromObjectStore を使用してデータベースからデータを取得します。

以下に示すように、useNewsSourcesgetNewsSources に対しても同様に行うことができます。

async function getNewsSources() {
  const { db, getDB, versionNumber, getDataFromObjectStore } = useIDB()
    try {
      // apiResponse
      const data = await apiResponse.json()
      // ...
      await getDB(versionNumber.value, baseUrl.value, 'id')
 data.sources.forEach(async (source) => {
  await db.value.put(baseUrl.value, source)
 })
    } catch (error) {
  if (error instanceof TypeError && error.message.includes('Failed to fetch')) {
   const cachedItems = await getDataFromObjectStore(baseUrl.value)
   newsSources.value = cachedItems
  }
    }
  }

以下のコマンドを使用して、変更を保存し、アプリを再構築し、プレビュー モードで実行します。

npm run build
npm run preview

ブラウザでアプリを起動します。ブラウザ開発ツールのアプリケーションタブのストレージセクションのIndexedDBサブセクションに新しいエントリが表示されます。

次に、「ネットワーク」タブに移動します。以下に示すように、ネットワーク スロットル オプションをオフラインに設定します。小さな黄色の警告アイコンが表示されるはずです。

アプリをリロードします。アプリケーションは以前と同じように読み込まれるはずです。

一時的に「インターネットなし」画面が表示されることがありますが、アプリはその後すぐに起動します。

結論

この記事では、Service Worker と IndexedDB API を使用して、Vue のシングルページ アプリケーションをプログレッシブ Web アプリに変換しました。次のステップとしては、ページの新しい更新についてユーザーに警告し、更新されたバージョンを確認するためにページをリロードするよう促すことが考えられます。 vite-plugin-pwa パッケージのドキュメントにある Vue セクションは、良い出発点となります。