클라이언트 힌트로 리소스 선택 자동화

일리야 그리고릭

웹용으로 구축하면 비교할 수 없는 도달 범위를 제공할 수 있습니다. 웹 애플리케이션은 클릭 한 번으로 스마트폰, 태블릿, 노트북, 데스크톱, TV 등 브랜드나 플랫폼에 상관없이 연결된 거의 모든 기기에서 사용할 수 있습니다. 각 폼 팩터의 표현과 기능을 조정하는 반응형 사이트를 빌드하여 최상의 환경을 제공하기 위해 애플리케이션을 최대한 빠르게 로드할 수 있도록 성능 체크리스트를 점검하고 있습니다. 주요 렌더링 경로를 최적화하고 텍스트 리소스를 압축 및 캐시했으며 이제 대부분의 이미지 리소스(전송되는 바이트의 대부분)를 확인합니다. 문제는 이미지 최적화를 어렵다는 것입니다.

  • 적절한 형식 결정 (벡터와 래스터)
  • 최적의 인코딩 형식 (jpeg, webp 등) 결정
  • 적합한 압축 설정 결정 (손실 및 무손실)
  • 유지하거나 삭제해야 하는 메타데이터 결정
  • 각 디스플레이 + DPR 해상도에 대해 각각 여러 변형 만들기
  • ...
  • 사용자의 네트워크 유형, 속도, 환경설정 고려

이러한 문제들은 각각 잘 알려진 문제입니다. 결과적으로 개발자들은 종종 간과하거나 간과하는 큰 최적화 공간을 만듭니다. 특히 많은 단계가 관련되어 있을 때 인간은 동일한 검색 공간을 반복적으로 탐색하는 작업을 제대로 수행하지 못합니다. 반면 컴퓨터는 이러한 유형의 작업에 탁월합니다.

이미지 및 유사한 속성을 가진 기타 리소스에 대한 우수하고 지속 가능한 최적화 전략에 대한 답은 간단합니다. 바로 자동화입니다. 리소스를 수작업으로 조정한다면 실수입니다. 잊어버리거나 지연되거나 다른 사람이 실수를 할 수도 있습니다.

성능을 중시하는 개발자의 이야기

이미지 최적화 공간을 통한 검색은 빌드 시와 런타임이라는 별개의 두 단계로 나뉩니다.

  • 일부 최적화는 리소스 자체에 내재되어 있습니다. 적절한 형식 및 인코딩 유형 선택, 각 인코더의 압축 설정 미세 조정, 불필요한 메타데이터 제거 등입니다. 이러한 단계는 '빌드 시간'에 실행할 수 있습니다.
  • 다른 최적화는 이를 요청하는 클라이언트의 유형 및 속성에 따라 결정되며 '런타임'에 실행해야 합니다. 클라이언트의 DPR에 적합한 리소스와 의도한 디스플레이 너비 선택, 클라이언트의 네트워크 속도, 사용자 및 애플리케이션 환경설정 등을 감안하여 이러한 최적화를 수행해야 합니다.

빌드 시간 도구가 존재하지만 개선될 수 있습니다. 예를 들어 각 이미지와 각 이미지 형식의 '품질' 설정을 동적으로 조정하면 상당한 비용을 절감할 수 있지만, 연구 외부에서 실제로 이를 사용하는 사람은 아직 없습니다. 이것은 혁신이 발달한 영역이지만 이 게시물의 목적에 맞게 그만두겠습니다. 스토리의 런타임 부분에 초점을 맞춰 보겠습니다.

<img src="/image/thing" sizes="50vw"
        alt="image thing displayed at 50% of viewport width">

애플리케이션 인텐트는 매우 간단합니다. 사용자 표시 영역의 50% 에서 이미지를 가져와 표시합니다. 대부분의 디자이너가 바를 위해 손과 머리를 감는 곳이 바로 이곳입니다. 한편 성능을 중시하는 팀의 개발자는 긴 밤을 보냅니다.

  1. 최상의 압축을 위해 각 클라이언트에 최적의 이미지 형식(Chrome용 WebP, Edge용 JPEG XR, 나머지용 JPEG)을 사용하려고 합니다.
  2. 최상의 시각적 품질을 얻으려면 각 이미지의 다양한 해상도(1x, 1.5x, 2x, 2.5x, 3x 및 그 사이 몇 배)로 여러 변형을 생성해야 합니다.
  3. 불필요한 픽셀을 전달하지 않으려면 '사용자의 표시 영역의 50% 가 실제로 의미하는 바'를 이해해야 합니다. 표시 영역 너비에는 여러 가지가 있습니다.
  4. 또한 느린 네트워크의 사용자가 더 낮은 해상도를 자동으로 가져올 수 있도록 복원력이 뛰어난 환경을 제공하는 것이 이상적입니다. 결국 중요한 것은 유리한 시점입니다.
  5. 또한 애플리케이션은 가져와야 하는 이미지 리소스에 영향을 주는 일부 사용자 컨트롤을 노출하므로 이 부분도 고려해야 합니다.

그리고 디자이너는 가독성을 최적화하기 위해 표시 영역 크기가 작은 경우 다른 이미지를 100% 너비로 표시해야 한다는 사실을 깨닫습니다. 즉, 이제 하나 이상의 애셋에 동일한 프로세스를 반복한 다음 표시 영역 크기에 따라 가져오기를 실행해야 합니다. 이게 어렵다고 말씀드렸던가요? 자, 그럼 시작해 볼까요. picture 요소를 사용하면 훨씬 더 많은 정보를 얻을 수 있습니다.

<picture>
    <!-- serve WebP to Chrome and Opera -->
    <source
    media="(min-width: 50em)"
    sizes="50vw"
    srcset="/image/thing-200.webp 200w, /image/thing-400.webp 400w,
        /image/thing-800.webp 800w, /image/thing-1200.webp 1200w,
        /image/thing-1600.webp 1600w, /image/thing-2000.webp 2000w"
    type="image/webp">
    <source
    sizes="(min-width: 30em) 100vw"
    srcset="/image/thing-crop-200.webp 200w, /image/thing-crop-400.webp 400w,
        /image/thing-crop-800.webp 800w, /image/thing-crop-1200.webp 1200w,
        /image/thing-crop-1600.webp 1600w, /image/thing-crop-2000.webp 2000w"
    type="image/webp">
    <!-- serve JPEGXR to Edge -->
    <source
    media="(min-width: 50em)"
    sizes="50vw"
    srcset="/image/thing-200.jpgxr 200w, /image/thing-400.jpgxr 400w,
        /image/thing-800.jpgxr 800w, /image/thing-1200.jpgxr 1200w,
        /image/thing-1600.jpgxr 1600w, /image/thing-2000.jpgxr 2000w"
    type="image/vnd.ms-photo">
    <source
    sizes="(min-width: 30em) 100vw"
    srcset="/image/thing-crop-200.jpgxr 200w, /image/thing-crop-400.jpgxr 400w,
        /image/thing-crop-800.jpgxr 800w, /image/thing-crop-1200.jpgxr 1200w,
        /image/thing-crop-1600.jpgxr 1600w, /image/thing-crop-2000.jpgxr 2000w"
    type="image/vnd.ms-photo">
    <!-- serve JPEG to others -->
    <source
    media="(min-width: 50em)"
    sizes="50vw"
    srcset="/image/thing-200.jpg 200w, /image/thing-400.jpg 400w,
        /image/thing-800.jpg 800w, /image/thing-1200.jpg 1200w,
        /image/thing-1600.jpg 1600w, /image/thing-2000.jpg 2000w">
    <source
    sizes="(min-width: 30em) 100vw"
    srcset="/image/thing-crop-200.jpg 200w, /image/thing-crop-400.jpg 400w,
        /image/thing-crop-800.jpg 800w, /image/thing-crop-1200.jpg 1200w,
        /image/thing-crop-1600.jpg 1600w, /image/thing-crop-2000.jpg 2000w">
    <!-- fallback for browsers that don't support picture -->
    <img src="/image/thing.jpg" width="50%">
</picture>

Google에서는 아트 디렉션과 형식 선택을 처리했으며 클라이언트 기기의 DPR 및 표시 영역 너비의 변동성을 고려하여 각 이미지의 6가지 변형을 제공했습니다. 굉장해요!

안타깝게도 picture 요소에서는 클라이언트의 연결 유형이나 속도에 따른 동작 방식에 관한 규칙을 정의할 수 없습니다. 하지만 처리 알고리즘을 통해 사용자 에이전트가 가져오는 리소스를 조정할 수 있습니다. 경우에 따라 5단계를 참고하세요. 사용자 에이전트가 충분히 똑똑하기를 바랄 뿐입니다. (참고: 현재 구현은 없습니다.) 마찬가지로 picture 요소에는 앱 또는 사용자 기본 설정을 고려하는 앱별 로직을 허용하는 후크가 없습니다. 이러한 마지막 두 비트를 가져오려면 위의 모든 로직을 JavaScript로 이동해야 하지만 picture에서 제공하는 미리 로드 스캐너 최적화는 사라집니다. 죄송합니다.

이러한 한계를 제외하면 작동합니다. 음, 적어도 이 애셋에서는요. 여기서 진짜이자 장기적인 문제는 디자이너나 개발자가 모든 애셋에 대해 이와 같은 코드를 수동으로 만들 것이라고 기대할 수 없다는 것입니다. 첫 시도에서는 재미있는 두뇌 퍼즐이지만, 그 직후에는 매력을 잃습니다. 자동화가 필요합니다 IDE 또는 다른 콘텐츠 변환 도구를 사용하면 위의 상용구를 자동으로 생성할 수도 있습니다.

클라이언트 힌트로 리소스 선택 자동화

심호흡을 하고 믿기지 않는 마음을 정지한 후 다음 예를 생각해 보세요.

<meta http-equiv="Accept-CH" content="DPR, Viewport-Width, Width">
...
<picture>
    <source media="(min-width: 50em)" sizes="50vw" srcset="/image/thing">
    <img sizes="100vw" src="/image/thing-crop">
</picture>

믿으실지 모르겠지만, 위의 예는 위의 훨씬 더 긴 사진 마크업과 동일한 모든 기능을 제공하는 데 충분하며, 앞에서 살펴본 것처럼 이미지 리소스를 가져오는 방법, 가져오기 방법 및 시기를 개발자가 완전히 제어할 수 있습니다. 클라이언트 힌트 보고를 사용 설정하는 첫 번째 줄에 있는 '매직'은 기기 픽셀 비율 (DPR), 레이아웃 표시 영역 너비 (Viewport-Width), 리소스의 의도한 디스플레이 너비 (Width)를 서버에 알리도록 브라우저에 지시합니다.

클라이언트 힌트를 사용 설정하면 결과로 도출되는 클라이언트 측 마크업이 프레젠테이션 요구사항만 유지합니다. 디자이너는 이미지 유형, 클라이언트 해상도, 전달된 바이트를 줄이기 위한 최적의 중단점 또는 기타 리소스 선택 기준에 대해 걱정할 필요가 없습니다. 사실을 직면해 봅시다. 그들은 한 번도 그런 적이 없고 할 필요가 없어야 합니다 더 좋은 점은 실제 리소스 선택이 클라이언트와 서버에 의해 협상되므로 개발자가 위의 마크업을 다시 작성하고 확장할 필요가 없다는 것입니다.

Chrome 46은 DPR, Width, Viewport-Width 힌트를 기본적으로 지원합니다. 힌트는 기본적으로 사용 중지되어 있으며 위의 <meta http-equiv="Accept-CH" content="...">는 지정된 헤더를 발신 요청에 추가하도록 Chrome에 지시하는 선택 신호 역할을 합니다. 이제 샘플 이미지 요청의 요청 및 응답 헤더를 검토해 보겠습니다.

클라이언트 힌트 협상 다이어그램

Chrome은 Accept 요청 헤더를 통해 WebP 형식 지원을 광고합니다. 새로운 Edge 브라우저도 마찬가지로 Accept 헤더를 통해 JPEG XR 지원을 알립니다.

다음 3개의 요청 헤더는 클라이언트 기기의 기기 픽셀 비율 (3x), 레이아웃 표시 영역 너비(460px), 리소스의 의도한 디스플레이 너비 (230px)를 알리는 클라이언트 힌트 헤더입니다. 이는 사전 생성된 리소스의 가용성, 리소스의 재인코딩 또는 크기 조절 비용, 리소스의 인기도, 현재 서버 로드와 같은 자체 정책 집합을 기반으로 최적의 이미지 변형을 선택하는 데 필요한 모든 정보를 서버에 제공합니다. 이 경우 서버는 Content-Type, Content-DPR, Vary 헤더에서 알 수 있듯이 DPRWidth 힌트를 사용하고 WebP 리소스를 반환합니다.

이때 마법은 없습니다. 리소스 선택을 HTML 마크업에서 클라이언트와 서버 간의 요청-응답 협상으로 이동했습니다. 따라서 HTML은 프레젠테이션 요구사항에만 관련이 있으며, Google은 모든 디자이너와 개발자가 작성할 것이라고 신뢰할 수 있는 반면, 이미지 최적화 공간을 통한 검색은 컴퓨터로 지연되고 이제는 규모에 맞게 쉽게 자동화됩니다. 성능을 중시하는 개발자를 기억하시나요? 이제 히로코의 업무는 제공된 힌트를 활용하고 적절한 응답을 반환하는 이미지 서비스를 작성하는 것입니다. 자신이 원하는 언어 또는 서버를 사용하거나 서드 파티 서비스 또는 CDN이 자신을 대신해 이 작업을 수행하도록 할 수 있습니다.

<img src="/image/thing" sizes="50vw"
        alt="image thing displayed at 50% of viewport width">

그리고 위의 이 사람을 기억하시나요? 클라이언트 힌트가 도입됨에 따라 이제 소박한 이미지 태그가 추가 마크업 없이도 DPR, 표시 영역, 너비를 인식합니다. Art-direction을 추가해야 하는 경우 위 그림과 같이 picture 태그를 사용하면 됩니다. 그렇지 않으면 기존의 모든 이미지 태그가 훨씬 더 스마트해집니다. 클라이언트 힌트는 기존 imgpicture 요소를 강화합니다.

서비스 워커로 리소스 선택 제어

ServiceWorker는 실제로 브라우저에서 실행되는 클라이언트 측 프록시입니다. 모든 발신 요청을 가로채고 응답을 검사, 재작성, 캐시하고 합성할 수도 있습니다. 이미지도 다르지 않습니다. 클라이언트 힌트를 사용 설정하면 활성 ServiceWorker가 이미지 요청을 식별하고 제공된 클라이언트 힌트를 검사하며 자체 처리 로직을 정의할 수 있습니다.

self.onfetch = function(event) {
    var req = event.request.clone();
    console.log("SW received request for: " + req.url)
    for (var entry of req.headers.entries()) {
    console.log("\t" + entry[0] +": " + entry[1])
    }
    ...
}
클라이언트는 serviceWorker에 힌트를 사용합니다.

ServiceWorker에서는 리소스 선택을 완전히 클라이언트 측에서 제어할 수 있습니다. 이는 매우 중요한 부분입니다. 가능성이 거의 무한하므로 이 부분부터 살펴보겠습니다.

  • 사용자 에이전트가 설정한 클라이언트 힌트 헤더 값을 다시 작성할 수 있습니다.
  • 새 클라이언트 힌트 헤더 값을 요청에 추가할 수 있습니다.
  • URL을 다시 작성하고 대체 서버(예: CDN)를 가리키는 이미지 요청을 가리킬 수 있습니다.
    • 힌트 값을 헤더에서 URL 자체로 옮길 수도 있습니다. 이렇게 하면 인프라에 더 쉽게 배포할 수 있습니다.
  • 응답을 캐시하고 제공되는 리소스에 대한 자체 로직을 정의할 수 있습니다.
  • 사용자의 연결 상태에 따라 응답을 조정할 수 있습니다.
  • 애플리케이션 및 사용자 환경설정 재정의를 고려할 수 있습니다.
  • 정말로... 심장이 원하는 모든 것을 할 수 있습니다.

picture 요소는 HTML 마크업에 필요한 아트 디렉션 컨트롤을 제공합니다. 클라이언트 힌트는 리소스 선택 자동화를 사용 설정하는 결과 이미지 요청에 주석을 제공합니다. ServiceWorker는 클라이언트에서 요청 및 응답 관리 기능을 제공합니다. 이것이 실행 중인 확장 가능한 웹입니다.

클라이언트 힌트 FAQ

  1. 클라이언트 힌트는 어디에서 사용할 수 있나요? Chrome 46에서 제공됩니다. FirefoxEdge에서 고려 중입니다.

  2. 클라이언트 힌트를 선택하는 이유는 무엇인가요? Google에서는 클라이언트 힌트를 사용하지 않는 사이트의 오버헤드를 최소화하고자 합니다. 클라이언트 힌트를 사용 설정하려면 사이트에서 페이지 마크업에 Accept-CH 헤더 또는 이에 상응하는 <meta http-equiv> 지시어를 제공해야 합니다. 이 중 하나라도 있으면 사용자 에이전트가 모든 하위 리소스 요청에 적절한 힌트를 추가합니다. 향후 특정 출처에 이 환경설정을 유지하는 추가 메커니즘을 제공할 수 있으며, 이를 통해 탐색 요청 시 동일한 힌트가 전달될 수 있습니다.

  3. ServiceWorker가 있는 경우 클라이언트 힌트가 필요한 이유는 무엇인가요? ServiceWorker는 레이아웃, 리소스, 표시 영역 너비 정보에 액세스할 수 없습니다. 비용이 많이 드는 왕복을 도입하고 이미지 요청을 크게 지연시키는 경우가 아니라면 적어도 됩니다(예: 이미지 요청이 미리 로드 파서에 의해 시작되는 경우). 클라이언트 힌트는 브라우저와 통합되어 이 데이터를 요청의 일부로 사용할 수 있도록 합니다.

  4. 클라이언트 힌트는 이미지 리소스에만 적용되나요? DPR, 표시 영역 너비 및 너비 힌트의 핵심 사용 사례는 이미지 애셋에 대한 리소스 선택을 사용 설정하는 것입니다. 그러나 유형에 관계없이 모든 하위 리소스에 동일한 힌트가 제공됩니다. 예를 들어 CSS 및 JavaScript 요청도 동일한 정보를 얻으며 이러한 리소스를 최적화하는 데 사용할 수 있습니다.

  5. 일부 이미지 요청에서 너비를 보고하지 않는 이유는 무엇인가요? 사이트가 고유한 이미지의 크기를 사용하므로 브라우저가 의도한 디스플레이 너비를 모를 수 있습니다. 따라서 이러한 요청과 '디스플레이 너비'가 없는 요청(예: JavaScript 리소스)에서는 너비 힌트가 생략됩니다. 너비 힌트를 받으려면 이미지에 크기 값을 지정해야 합니다.

  6. <내가 좋아하는 힌트 삽입>은 어떻게 하나요? 개발자는 ServiceWorker를 사용하여 모든 발신 요청을 가로채고 수정 (예: 새 헤더 추가)할 수 있습니다. 예를 들어 NetInfo 기반 정보를 추가하여 현재 연결 유형을 쉽게 표시할 수 있습니다. 'ServiceWorker를 사용한 기능 보고'를 참조하세요. 순수 SW 기반 구현은 모든 이미지 요청을 지연시키므로 Chrome에서 제공되는 '네이티브' 힌트 (DPR, 너비, 리소스 너비)가 브라우저에 구현됩니다.

  7. 더 많은 데모를 볼 수 있는 곳은 어디인가요? 설명 문서를 확인하고 의견이나 다른 질문이 있으면 언제든지 GitHub에서 문제를 개설하세요.