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

Ilya Grigorik
Ilya Grigorik

웹용으로 빌드하면 독보적인 도달범위를 누릴 수 있습니다. 브랜드나 플랫폼에 상관없이 클릭 한 번으로 웹 애플리케이션을 사용할 수 있고 스마트폰, 태블릿, 노트북, 데스크톱, 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. 최상의 시각적 품질을 얻으려면 1배, 1.5배, 2배, 2.5배, 3배 등 여러 해상도로 각 이미지의 여러 변형을 생성해야 합니다.
  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 요소에는 앱 또는 사용자 환경설정을 설명하는 앱별 로직을 허용하는 후크가 없습니다. 마지막 두 비트를 가져오려면 위의 모든 로직을 자바스크립트로 이동해야 하지만 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>

위 예는 위의 훨씬 더 긴 사진 마크업과 동일한 기능을 제공하는 데 충분하며, 앞으로 보게 될 것처럼 이를 통해 개발자는 이미지 리소스를 가져오는 방법, 대상, 시기를 완전히 제어할 수 있습니다. 'magic'은 클라이언트 힌트 보고를 사용 설정하고 리소스의 기기 픽셀 비율 (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배), 레이아웃 표시 영역 너비(460px), 리소스의 의도된 디스플레이 너비 (230px)를 알리는 클라이언트 힌트 헤더입니다. 이는 사전 생성된 리소스의 가용성, 리소스 재인코딩 또는 크기 조절 비용, 리소스의 인기도, 현재 서버 로드 등 자체 정책 집합을 기반으로 최적의 이미지 변형을 선택하는 데 필요한 모든 정보를 서버에 제공합니다. 이 경우 서버는 DPRWidth 힌트를 사용하고 Content-Type, Content-DPR, Vary 헤더가 표시한 WebP 리소스를 반환합니다.

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

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

그리고 위에 있는 이 사람 기억나? 클라이언트 힌트를 통해 소박한 이미지 태그는 이제 추가 마크업 없이 DPR, 표시 영역, 너비를 인식합니다. 아트 디렉션을 추가해야 하는 경우 위에서 설명한 것처럼 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. 클라이언트 힌트가 수신 동의를 하는 이유는 무엇인가요? 클라이언트 힌트를 사용하지 않는 사이트의 오버헤드를 최소화하려고 합니다. 클라이언트 힌트를 사용 설정하려면 사이트에서 페이지 마크업에 Accept-CH 헤더 또는 이에 상응하는 <meta http-equiv> 지시어를 제공해야 합니다. 둘 중 하나가 있는 경우 사용자 에이전트는 모든 하위 리소스 요청에 적절한 힌트를 추가합니다. 향후 Google은 특정 출처에 이 환경설정을 유지하는 추가 메커니즘을 제공할 수 있으며, 이를 통해 탐색 요청에 동일한 힌트가 전달될 수 있습니다.

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

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

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

  6. <insert my favorite hint>는 무엇인가요? ServiceWorker를 사용하면 개발자가 모든 발신 요청을 가로채고 수정 (예: 새 헤더 추가)할 수 있습니다. 예를 들어 현재 연결 유형을 나타내는 NetInfo 기반 정보를 쉽게 추가할 수 있습니다. 'ServiceWorker를 사용한 기능 보고'를 참조하세요. 순수한 SW 기반 구현은 모든 이미지 요청을 지연시키기 때문에 Chrome에서 제공되는 '네이티브' 힌트 (DPR, Width, Resource-Width)는 브라우저에서 구현됩니다.

  7. 자세한 내용과 더 많은 데모는 어디에서 확인할 수 있나요? 설명 문서를 확인하고 의견이나 다른 질문이 있으면 GitHub에서 문제를 열어보세요.