서비스 워커 및 애플리케이션 셸 모델

단일 페이지 웹 애플리케이션 (SPA)의 일반적인 아키텍처 기능은 애플리케이션의 전역 기능을 구동하는 데 필요한 최소한의 HTML, CSS, JavaScript 집합입니다. 실제로는 헤더, 탐색 및 모든 페이지에서 지속되는 기타 공통 사용자 인터페이스 요소인 경우가 많습니다. 서비스 워커가 이 최소 UI의 HTML 및 종속 애셋을 사전 캐시하는 경우 이를 애플리케이션 셸이라고 합니다.

애플리케이션 셸의 다이어그램 상단에 헤더, 하단에 콘텐츠 영역이 있는 웹페이지의 스크린샷입니다. 헤더에는 '애플리케이션 셸'이라는 라벨이 지정되어 있고 아래쪽에는 '콘텐츠'라는 라벨이 지정되어 있습니다.

애플리케이션 셸은 웹 애플리케이션의 인지 성능에서 중요한 역할을 합니다. 이 요소는 가장 먼저 로드되므로 사용자가 콘텐츠가 사용자 인터페이스에 채워질 때까지 기다리는 동안 가장 먼저 보게 되는 항목이기도 합니다.

애플리케이션 셸은 빠르게 로드할 수 있지만(네트워크를 사용할 수 있고 어느 정도 빠른 경우), 애플리케이션 셸과 관련 자산을 사전 캐시하는 서비스 워커는 애플리케이션 셸 모델에 다음과 같은 추가적인 이점을 제공합니다.

  • 재방문 시 신뢰할 수 있고 일관성 있는 실적 서비스 워커가 설치되지 않은 상태로 앱을 처음 방문할 때, 애플리케이션의 마크업과 관련 애셋을 네트워크에서 로드해야 서비스 워커가 이를 캐시에 저장할 수 있습니다. 그러나 반복 방문은 캐시에서 애플리케이션 셸을 가져오기 때문에 로드 및 렌더링이 즉각적으로 이루어집니다.
  • 오프라인 시나리오에서 기능에 안정적으로 액세스할 수 있습니다. 인터넷 접속이 불안정하거나 아예 없는 경우도 있고, "해당 웹사이트를 찾을 수 없습니다"라는 두려움이 있습니다. 애플리케이션 셸 모델은 캐시의 애플리케이션 셸 마크업을 사용하여 탐색 요청에 응답함으로써 이 문제를 해결합니다. 누군가 여러분의 웹 앱에서 이전에 방문한 적이 없는 URL을 방문하더라도 애플리케이션 셸이 캐시에서 제공되고 유용한 콘텐츠로 채워질 수 있습니다.

애플리케이션 셸 모델을 사용해야 하는 경우

애플리케이션 셸은 경로마다 변경되지 않지만 콘텐츠는 변경되지 않는 공통 사용자 인터페이스 요소가 있는 경우에 가장 적합합니다. 대부분의 SPA는 이미 효과적인 애플리케이션 셸 모델을 사용하고 있을 가능성이 높습니다.

이 내용이 프로젝트에 대해 설명되고 서비스 워커를 추가하여 안정성과 성능을 향상시키려는 경우 애플리케이션 셸은 다음과 같아야 합니다.

  • 빠르게 로드.
  • Cache 인스턴스의 정적 애셋을 사용합니다.
  • 헤더 및 사이드바와 같은 일반적인 인터페이스 요소를 페이지 콘텐츠와 별도로 포함합니다.
  • 페이지별 콘텐츠를 가져와 표시합니다.
  • 필요한 경우 오프라인 보기를 위해 동적 콘텐츠를 캐시합니다(선택사항).

애플리케이션 셸은 API 또는 JavaScript로 번들된 콘텐츠를 통해 페이지별 콘텐츠를 동적으로 로드합니다. 또한 애플리케이션 셸의 마크업이 변경되면 서비스 워커 업데이트가 새 애플리케이션 셸을 선택하고 자동으로 캐시해야 한다는 점에서 자동 업데이트되어야 합니다.

애플리케이션 셸 빌드

애플리케이션 셸은 콘텐츠와는 별개로 존재해야 하지만, 애플리케이션 셸에 콘텐츠가 채워지는 기반을 제공해야 합니다. 최대한 슬림한 디자인을 사용하는 것이 가장 좋지만 초기 다운로드에는 환경이 빠르게 로드되고 있음을 사용자가 이해할 수 있을 만큼 의미 있는 콘텐츠를 포함하는 것이 가장 좋습니다.

적절한 균형은 앱에 따라 다릅니다. Jake Archibald의 Trained To Thrill 앱용 애플리케이션 셸에는 Flickr에서 새 콘텐츠를 가져올 수 있는 새로고침 버튼이 있는 헤더가 있습니다.

두 가지 다른 상태의 Trained to Thrill 웹 앱의 스크린샷 왼쪽에는 캐시된 애플리케이션 셸만 표시되며 콘텐츠는 채워지지 않습니다. 오른쪽에서는 콘텐츠 (일부 열차의 사진)가 애플리케이션 셸의 콘텐츠 영역에 동적으로 로드됩니다.

애플리케이션 셸 마크업은 프로젝트마다 다르지만, 다음은 애플리케이션 상용구를 제공하는 index.html 파일의 한 가지 예입니다.

​​<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>
      Application Shell Example
    </title>
    <link rel="manifest" href="/manifest.json">
    <meta name="viewport" content="width=device-width,initial-scale=1.0">
    <link rel="stylesheet" type="text/css" href="styles/global.css">
  </head>
  <body>
    <header class="header">
      <!-- Application header -->
      <h1 class="header__title">Application Shell Example</h1>
    </header>

    <nav class="nav">
      <!-- Navigation items -->
    </nav>

    <main id="app">
      <!-- Where the application content populates -->
    </main>

    <div class="loader">
      <!-- Spinner/content placeholders -->
    </div>

    <!-- Critical application shell logic -->
    <script src="app.js"></script>

    <!-- Service worker registration script -->
    <script>
      if ('serviceWorker' in navigator) {
        // Register a service worker after the load event
        window.addEventListener('load', () => {
          navigator.serviceWorker.register('/sw.js');
        });
      }
    </script>
  </body>
</html>

프로젝트에 맞게 애플리케이션 셸을 구성하는 경우 다음과 같은 특성을 지녀야 합니다.

  • HTML에는 개별 사용자 인터페이스 요소를 위한 명확하게 격리된 영역이 있어야 합니다. 위 예에서는 애플리케이션의 헤더, 탐색, 기본 콘텐츠 영역, 로딩 '스피너'를 위한 공간이 포함되어 있습니다. 콘텐츠 로드 중에만 표시됩니다.
  • 애플리케이션 셸을 위해 로드된 초기 JavaScript 및 CSS는 최소한의 요소여야 하며, 콘텐츠가 아닌 애플리케이션 셸 자체의 기능과만 관련이 있어야 합니다. 이렇게 하면 애플리케이션이 셸을 최대한 빠르게 렌더링하고 콘텐츠가 표시될 때까지 기본 스레드 작업을 최소화합니다.
  • 서비스 워커를 등록하는 인라인 스크립트.

애플리케이션 셸이 빌드되면 서비스 워커를 빌드하여 애플리케이션과 그 자산을 모두 캐시할 수 있습니다.

애플리케이션 셸 캐싱

애플리케이션 셸과 필수 자산은 서비스 워커가 설치 시 즉시 사전 캐시해야 하는 항목입니다. 위의 예와 같은 애플리케이션 셸을 가정하고 workbox-build를 사용하여 기본 Workbox 예시에서 이를 실행하는 방법을 살펴보겠습니다.

// build-sw.js
import {generateSW} from 'workbox-build';

// Where the generated service worker will be written to:
const swDest = './dist/sw.js';

generateSW({
  swDest,
  globDirectory: './dist',
  globPatterns: [
    // The necessary CSS and JS for the app shell
    '**/*.js',
    '**/*.css',
    // The app shell itself
    'shell.html'
  ],
  // All navigations for URLs not precached will use this HTML
  navigateFallback: 'shell.html'
}).then(({count, size}) => {
  console.log(`Generated ${swDest}, which precaches ${count} assets totaling ${size} bytes.`);
});

build-sw.js에 저장된 이 구성은 shell.html에 포함된 애플리케이션 셸 마크업 파일을 포함하여 앱의 CSS 및 JavaScript를 가져옵니다. 스크립트는 다음과 같이 Node로 실행됩니다.

node build-sw.js

생성된 서비스 워커는 ./dist/sw.js에 기록되며, 완료되면 다음 메시지를 기록합니다.

Generated ./dist/sw.js, which precaches 5 assets totaling 44375 bytes.

페이지가 로드되면 서비스 워커가 애플리케이션 셸 마크업과 그 종속 항목을 사전 캐시합니다.

<ph type="x-smartling-placeholder">
</ph> 네트워크에서 다운로드한 애셋 목록을 보여주는 Chrome DevTools의 네트워크 패널 스크린샷. 서비스 워커가 미리 캐시한 애셋은 행의 왼쪽에 톱니바퀴가 표시된 다른 애셋과 구분됩니다. 여러 JavaScript 및 CSS 파일은 설치 시 서비스 워커에 의해 사전 캐시됩니다.
서비스 워커는 설치 시 애플리케이션 셸의 종속 항목을 사전 캐시합니다. 사전 캐싱 요청은 마지막 두 행이며, 요청 옆의 톱니바퀴 아이콘은 서비스 워커가 요청을 처리했음을 나타냅니다.

번들러를 사용하는 프로젝트를 비롯한 거의 모든 워크플로에서 애플리케이션 셸의 HTML, CSS 및 JavaScript를 사전 캐싱할 수 있습니다. 문서를 살펴보면서 Workbox를 직접 사용하여 도구 모음을 설정하여 SPA인지 여부에 관계없이 프로젝트에 가장 적합한 서비스 워커를 빌드하는 방법을 배우게 됩니다.

결론

애플리케이션 셸 모델을 서비스 워커와 결합하면 특히 마크업 또는 API 응답을 위해 사전 캐싱 기능을 네트워크 우선, 캐시 전략으로 대체하는 경우와 결합하는 경우 오프라인 캐싱에 매우 효과적입니다. 그 결과, 오프라인 상태에서도 반복 방문 시 애플리케이션 셸을 즉시 렌더링하고 안정적이고 빠른 환경을 구현할 수 있습니다.