필요에 따라 PWA를 오프라인으로 전환하는 방법

마지막
작년에 우리가 프레젠테이션 원고를 위한 웹 소스 편집기DeckDeckGo를 내놓은 이후 가장 인기 있는 기능 중 하나는 오프라인 작업이다.
우리는 현재 이미 이 새로운 기능을 실현하고 출시했다. 이것이 바로 내가 여러분과 우리의 학습을 공유하고 싶은 이유이다. 우리는 어떻게 우리의 점진적인 인터넷 응용 프로그램을 위해 이런'콘텐츠 다운로드'기능의 넷플릭스나 Spotify를 개발했는가.

사용자 경험(UX)


오프라인 테마를 처리할 수 있는 방법은 매우 많다.내가 생각할 수 있는 방법 중 하나는 모든 응용 프로그램 (그 내용 포함) 을 항상 오프라인으로 사용할 수 있도록 하는 것이다.
다른 하나는 "온디맨드 오프라인 컨텐츠 솔루션"Spotify 또는 Netflix 솔루션입니다.이것은 당신이 익숙할 수 있는 방법입니다. 왜냐하면 이 플랫폼들은 이러한 방법을 제공했기 때문에 사용자는 요청에 따라 로컬에서 내용, 음악, 영화를 다운로드할 수 있습니다.
이것은 우리가 실시하는 방법이자 내가 여러분과 공유하는 방법이다.

소개


PWA 컨텐츠를 오프라인으로 사용할 수 있도록 다음 단계를 수행했습니다.
async goOffline() {
  await this.lazyLoad();
  await this.saveContent();
  await this.cacheAssets();
  await this.toggleOffline();
}

불활성 부하


우리의 프레젠테이션은 성능을 향상시키기 위해 로드 지연됩니다.슬라이드를 탐색할 때 현재, 이전 및 다음 슬라이드만 로드합니다.따라서 오프라인에 필요한 첫 번째 작업은 로컬에서 모든 자산(이미지, 도표 데이터, 코드 언어 등)을 다운로드하는 것이다.
당신의 응용 프로그램에서도 이런 상황이 나타날 수 있습니다.페이지 아래쪽이나 사용자가 아직 방문하지 않은 다른 위치에 로드가 지연된 이미지가 있다고 가정하십시오.하나의 해결 방안은 서비스 인원의 사전 처리 정책에 추가하는 것입니다. 그러나 구축할 때 동적이고 알 수 없는 경우, 이렇게 할 수 없습니다.
다행히도, 지연 로딩은 우리 해결 방안의 핵심이다. 우리의 모든 웹 구성 요소는 기본적으로 지연 로딩을 지원한다. 이것이 바로 이러한 과정을 시작하기 위해서 우리는 함수 하나만 호출해야 하는 이유이다.
private lazyLoad() {
  return new Promise(async (resolve, reject) => {
    try {
      const deck = document.querySelector('deckgo-deck');

      if (!deck) {
        reject('Deck not found');
        return;
      }

      await deck.lazyLoadAllContent();

      resolve();
    } catch (err) {
      reject(err);
    }
  });
}
이러한 절차는 모든 슬라이드와 구성 요소를 옮겨다니며 내용을 불러오는 것을 책임진다.단, 저희처럼 서비스 종사자를 사용하지 않으면, 자동으로 캐시할 수 없습니다.
우리는 다음과 같이 캐시 이미지와 같은 정책을 관리할 수 있습니다 Workbox.타사 공급업체의 COR 및 불투명 요청 문제를 피하기 위해 두 가지 다른 정책이 있습니다.
workbox.routing.registerRoute(
  /^(?!.*(?:unsplash|giphy|tenor|firebasestorage))(?=.*(?:png|jpg|jpeg|svg|webp|gif)).*/,
  new workbox.strategies.CacheFirst({
    cacheName: 'images',
    plugins: [
      new workbox.expiration.Plugin({
        maxAgeSeconds: 30 * 24 * 60 * 60,
        maxEntries: 60,
      }),
    ],
  })
);

workbox.routing.registerRoute(
  /^(?=.*(?:unsplash|giphy|tenor|firebasestorage))(?=.*(?:png|jpg|jpeg|svg|webp|gif)).*/,
  new workbox.strategies.StaleWhileRevalidate({
    cacheName: 'cors-images',
    plugins: [
      new workbox.expiration.Plugin({
        maxAgeSeconds: 30 * 24 * 60 * 60,
        maxEntries: 60,
      }),
      new workbox.cacheableResponse.CacheableResponse({
        statuses: [0, 200],
      }),
    ],
  })
);
만약 우리가 개발한 모든 정책에 관심이 있다면, 우리의 소스 리포에서 우리의 sw.js 스크립트를 보십시오.

컨텐츠 저장


사용자는 인터넷에 더 이상 액세스할 수 없으므로 데이터베이스에 액세스하여 컨텐츠를 가져올 수 없습니다.이것이 바로 그것이 반드시 로컬에 저장해야 하는 이유이다.
비록 우리가 사용하고 있고 라이브러리는 오프라인 우선 순위 기능이나 지원을 제공했지만 우리는 자신의 맞춤형 해결 방안을 실현했다.
이것이 바로 우리가 IndexedDB의 도움으로 자신의 개념을 개발한 이유입니다.예를 들어 아래 코드에서 우리는 온라인 데이터베이스에서 데이터 그룹을 가져와 로컬에 저장합니다.주의해야 할 것은 우리가 요소의 유일한 식별자를 저장 키와handyCloud Firestore로 저장하는 것이다.
import {set} from 'idb-keyval';

private saveDeck(deckId: string): Promise<Deck> {
  return new Promise(async (resolve, reject) => {

    // 1. Retrieve data from online DB
    const deck = await this.deckOnlineService.get(deckId);

    if (!deck || !deck.data) {
      reject('Missing deck');
      return;
    }
    // 2. Save data in IndexedDB
    await set(`/decks/${deck.id}`, deck);

    resolve(deck);
  });
}
이 점에서 너는 자신에게 중점이 무엇인지 물어볼 수도 있다.내용을 로컬에 저장하는 것은 좋지만, 이것은 사용자가 오프라인으로 사용할 수 있다는 것을 의미하지 않습니까?그 밖에 이 데이터를 사용할 수 있도록 프로그램을 완전히 다시 써야 한다는 것을 걱정할 수도 있습니다. 그렇지 않습니까?
다행히도 우리의 응용 프로그램은 이미 서로 다른 층에서 분리되었다. 새로운 전역 상태의 도움으로 응용 프로그램이 offline인지 online인지를 알려준다. 우리는 우리의 단일 서비스를 확장하여 데이터베이스에서 모델에 따라 다르게 표현할 수 있다.
구체적으로 말하면 온라인이면 Firestore와 상호작용하고, 오프라인이면 IndexedDB와 상호작용한다.
export class DeckService {
  private static instance: DeckService;

  private constructor() {
    // Private constructor, singleton
  }

  static getInstance() {
    if (!DeckService.instance) {
      DeckService.instance = new DeckService();
    }
    return DeckService.instance;
  }

  async get(deckId: string): Promise<Deck> {
    const offline = await OfflineService.getInstance().status();

    if (offline !== undefined) {
      return DeckOfflineService.getInstance().get(deckId);
    } else {
      return DeckOnlineService.getInstance().get(deckId);
    }
  }
}
온라인 데이터베이스와의 상호작용은 변하지 않기 때문에 우리는 기능을 새로운 서비스로 옮기기만 하면 된다.
get(deckId: string): Promise<Deck> {
  return new Promise(async (resolve, reject) => {
    const firestore = firebase.firestore();

    try {
      const snapshot = await firestore
        .collection('decks')
        .doc(deckId)
        .get();

      if (!snapshot.exists) {
        reject('Deck not found');
        return;
      }

      const deck: DeckData = snapshot.data() as DeckData;

      resolve({
        id: snapshot.id,
        data: deck
      });
    } catch (err) {
      reject(err);
    }
  });
}
재구성 후, 우리는 반드시 오프라인 대응 항목을 만들어야 한다.
get(deckId: string): Promise<Deck> {
  return new Promise(async (resolve, reject) => {
    try {
      const deck: Deck = await get(`/decks/${deckId}`);

      resolve(deck);
    } catch (err) {
      reject(err);
    }
  });
}
보시다시피 저희는 유일한 식별자를 저장 키로 사용합니다. 이것은 전체 시스템을 매우 편리하게 합니다. 왜냐하면 우리는 로컬에서 데이터를 얻을 수 있기 때문입니다. 거의 우리가 온라인 데이터베이스를 사용하는 것과 같기 때문입니다.이렇게 하면 우리는 응용 프로그램의 다른 층을 수정할 필요가 없다. 모든 것이 오프라인으로 작업할 수 있고, 거의 상자를 열면 바로 사용할 수 있으며, 어떠한 진일보한 변경도 없다.

idb 키 캐시 자산


지금까지 IndexedDB를 사용하여 사용자의 데이터를 로컬에 저장하고 서비스 종사자가 캐시한 내용을 사용할 수 있기 때문에 모든 프레젠테이션을 오프라인으로 사용할 수 있지만 다른 내용이 부족합니까?
네, 확실합니다. 어떤 것들은 여전히 캐시되지 않았습니다. 응용 프로그램 자체의 자산입니다.
마찬가지로 이것은 캐시 정책을 통해 해결할 수 있지만, 우리도 해결할 수 없다면 대체 방안을 찾아야 한다.
우리는 다음과 같은 내용이다.우리는 아이콘과 글꼴을 포함하여 우리가 사용하고 있는 모든 자산을 열거한 새로운 을 만들었다.
{
  ...
  "navigation": [
     {"src": "/icons/ionicons/open.svg", "ariaLabel": "Open"},
     ...
}
그리고 사용자가 오프라인 모드를 요청할 때, 우리는 모든 항목을 교체하고, 응용 프로그램 상하문에서 서비스 종사자를 호출하여 캐시를 터치합니다.
async function cacheUrls(cacheName: string, urls: string[]) {
  const myCache = await window.caches.open(cacheName);
  await myCache.addAll(urls);
}
만약 당신이 이 특정 기능에 대한 더 많은 정보를 알고 싶다면, 나는 올해 초에 그것에 관한 또 다른 글을 발표했다.

JSON 파일 오프라인으로 전환


마지막으로, 모든 내용이 캐시되어 있기 때문에, 인터넷 접근은 현재 안전하게 닫힐 수 있으며, 우리는 응용 프로그램이 오프라인 모드에서 작동하는 것을 표시하기 위해 전역 상태를 저장할 수 있다.

온라인


당신은 위의 해결 방안이 가장 멋있는 곳이 무엇인지 아십니까?"단지"캐시와 아키텍처에 레이어를 추가하여 핵심 기능을 수정하거나 제한하지 않았기 때문에, 사용자는 오프라인으로 내용을 읽을 수 있을 뿐만 아니라 편집할 수 있다🔥.
이것은 사용자가 다시 온라인으로 연결될 때 로컬 내용을 원격 데이터베이스로 전송할 수 있어야 한다는 것을 의미한다.
이런 과정은 우리가 개발한 것과 같은 논리를 따른다.
async goOnline() {
  await this.uploadContent();

  await this.toggleOnline();
}
IndexedDB에서 모든 로컬 컨텐츠를 추출해야 하며, 사용자가 로컬에 추가한 모든 로컬 이미지 또는 기타 컨텐츠를 원격 스토리지로 전송해야 합니다.
private async uploadDeck(deck: Deck) {
  await this.uploadDeckLocalUserAssetsToStorage(deck);
  await this.uploadDeckDataToDb(deck);
}
만약 필요하다면, 이 과정을 더욱 발전시켜서 매우 기쁩니다. 저에게 질문해 주십시오👋.

총결산


나는 단지 이 글의 빙산의 일각을 폭로했을 뿐이지만, 나는 적어도 당신과 우리의 학습과 해결 방안의 전체적인 사고방식을 공유할 수 있기를 바랍니다.
물론, 만약 네가 다음 강연에서 우리의 편집을 시험해 볼 수 있다면, 나도 매우 기쁠 것이다👉 .
무한과 초월로!
데이비드
표지 사진은 deckdeckgo.com 에서 Kym Ellis

좋은 웹페이지 즐겨찾기