-
[스압/데이터주의] 웹 최적화 방식 모음 - 1. 다운로드프로그래밍/Web 2020. 4. 4. 09:36
- [스압/데이터주의] 웹 최적화 방식 모음 - 0. 전반적 원칙과 원리
- [스압/데이터주의] 웹 최적화 방식 모음 - 1. 다운로드(현재)
- [스압/데이터주의] 웹 최적화 방식 모음 - 2. 파싱 및 렌더링 트리
- [스압/데이터주의] 웹 최적화 방식 모음 - 3. Layout 및 렌더링
- [스압/데이터주의] 웹 최적화 방식 모음 - 3.3 UX 트릭
- [스압/데이터주의] 웹 최적화 방식 모음 - 4. 로드 후
- [스압/데이터주의] 웹 최적화 방식 모음 - 5. 빌드
1. 다운로드
사용자(End-user) 응답시간의 80%는 초기 로딩부분에서 소요된다. 그 중 대부분의 시간은 모든 페이지 구성요소(images, syltesheets, scripts 등)의 다운로드에 소요된다.
따라서 다운로드 관련 시간을 아끼는 것은 중요하다.
크게 3~4가지의 기준으로 나누어볼 수 있다.
줄이기(용량, 요청), 합치기, 나누기
1.1 용량 줄이기
리소스의 용량을 줄이면 다운로드에 걸리는 시간이 줄고, 해석하는데 시간 또한 줄어들기 때문에 최적화에 이점이 있다.
필요한 데이터 양이 초기 혼잡 윈도우(일반적으로 14.6 KB 압축)를 초과하면 서버와 사용자 브라우저 간에 추가 왕복이 필요하기도 하다.
모바일 네트워크와 같이 지연 시간이 긴 네트워크를 사용하는 사용자의 경우 이로 인해 페이지 로드가 크게 지연될 수 있다.
1.1.1 디자인 단순화
개요
분류: Content
디자인을 단순하게 만들면 다운받아서 보여줄 요소들이 줄어들겠죠?
간단히 Dom 요소(element)를 줄이자고 생각해봅시다.
복잡한 페이지는 다운로드시 용량이 더 커지며 Javascript에서 DOM 엑세스 속도도 느려지게 만들 수 있다.
예를 들어 이벤트 핸들러를 추가할 때 500 VS 5000개의 DOM 요소를 반복해야 한다면 누가 더 빠를까?
보통 다음을 넘지 않는 것이 권장사항이다. [Uses An Excessive DOM Size]
- DOM 트리 노드: 1500
- 최대 깊이: 32
- 자식노드를 가지는 부모노드: 60
Browser Cache Usage - Exposed! 를 보면 40-60% 의 방문자가 브라우저 캐쉬의 혜택을 보지 못한다고 한다. 따라서 static 파일이라고 해도 그 수를 줄이는 것이 방문자에게 체감속도를 높이는 방법이다.
측정방법
DOM 요소의 수는 웹콘솔에 다음과 같이 입력하면 쉽게 알 수 있다.
document.getElementsByTagName('*').length
해결방법
레이아웃을 만들기 위해 DOM 요소를 남용했을 수도 있다.
CSS를 잘 활용하고, 중첩되는 것들을 최대한 줄여보자.
예를 들어 옛날에는 레이아웃을 위한 CSS가 없거나 제대로 적용되지 않는 브라우저가 많았기 때문에 <table>을 사용했었는데 중첩적으로 사용해야 했기 때문에 DOM 요소는 늘어날 수 밖에 없었다.
요즘은 Flexbox나 Grid 같이 매우 편리한 CSS Layout이 많다.
+. 이외의 <table> 태그의 문제점
<table> 태그는 전체가 하나의 객체이기 때문에 모든 셀(cell)이 로딩되어야만 보여준다.
<div> 태그는 각각이 하나의 객체라 점진적으로 렌더링이 가능한것과 대조적.
코드 수정, 컨텐츠 배치가 힘든 것도 단점.
++.
안드로이드나 IOS의 경우 깊이를 낮추기 위해 Constraint Layout이나 Auto Layout을 사용하는데 성능에 좋다.[Constraint Layout performance(번역), 안드로이드의 새로운 레이아웃 탐구서, 안드로이드 Layout별 성능 비교, High Performace Layout(요약1, 요약2), The Cassowary Linear Arithmetic Constraint Solving Algorithm]
Flexbox Layout vs Constraint Layout no Android com Beagle
제약(Constraint)을 이용하는게 Flexbox방식보다 일반적으로 낫다는 듯? [constraintlayout vs flexbox-layout]
몇몇 Constraint 관련 프로젝트들 소개.
- JS: autolayout.js, kiwi.js
- C++: kiwi
- Rust: Cassowary-rs, rust-cassowary
- Swift: Cassowary
Constraint Layout의 적응형을 찾아봤는데 ORCSolver의 알고리즘이 가장 나은 편인 듯 하다. [ORCSolver: An Efficient Solver for Adaptive GUI Layout with OR-Constraints(github)]
1.1.2 가벼운 라이브러리와 모듈
개요
분류: Content
기능에 비해 쓸데없이 비대한 라이브러리보다 가벼운 라이브러리를 사용하면 크기가 줄어든다.
또한 필요한 함수만 사용해야 한다.
실제로 의존성을 줄이는 것은 효과적인 최적화 방법이다.[Kill Your Dependencies]
측정방법
Github나 jsDelivr에서 min을 기준으로 확인해본다.
앞서 소개한 Webpack Bundle Analyzer로 쓸데없는 종속성을 탐색
해결방법
- 가벼운 라이브러리
셀렉터($)만 사용하는데 jQuery를 사용한다면 바닐라로 대체하거나 Cash(cash-dom, 타입스크립트도 잘 지원), DOM Helper /bala($ 셀렉터만 있는 라이브러리)을 이용할 수 있다. React라면 Preact 또는 Vue를 사용하는 식.
간단한 디자인의 사이트를 만드는 거라면 Bootstrap이나 Foundation을 사용하기 보다 Bulma, Spectre 더 가벼운 것을 원한다면 Pure CSS나 Skeleton이 있겠다.
유튜브 임베딩의 경우도 상당히 느린데 매개변수를 수정하거나, lite-youtube-embed, lite-youtube를 사용해 볼 수 있다.
- 필요한 함수만
import _ from 'lodash'; _.array(...); _.object(...);
보다는
import array from 'lodash/array'; import object from 'lodash/fp/object'; array(...); object(...);
처럼.
1.1.3 중복 스크립트 제거
개요
분류: Javascript
동일한 JavaScript 파일을 한 페이지에 두 번 포함하면 성능이 저하된다.
이 경우 중복 스크립트는 불필요한 HTTP 요청을 생성하고 JavaScript 실행을 낭비하여 성능을 저하시킨다.
불필요한 HTTP 요청은 구형 Internet Explorer에서는 발생하지만 최신 브라우저들에서는 발생하지 않는다.
구형 Internet Explorer에서 외부 스크립트가 두 번 포함되고 캐시 할 수없는 경우 페이지로드 중에 두 개의 HTTP 요청이 생성된다.
스크립트가 캐시 가능하더라도 사용자가 페이지를 다시로드하면 추가 HTTP 요청이 발생한다는 것.
불필요한 HTTP 요청을 생성하는 것 외에도 스크립트를 여러 번 평가하는 데 시간이 낭비된다.
이 중복 JavaScript 실행은 스크립트가 캐시 가능한지 여부에 관계없이 모두에서 발생한다.
중복된 스크립트의 가능성을 높이는 두 가지 주요요인은 팀 규모와 스크립트 수이다.
YDN의 글에 따르면 미국의 상위 10개 사이트 중 2개에서 발생하던 일이라 생각보다 드물지 않다는데 요즘은 빌드나 관리도구의 발전으로 덜할거라 예상한다. ㅎㅎ
하지만 예전 방식으로 만들어서 운영되는 사이트도 많을테니 아예 말이 안되는 이야기는 아니다.
측정방법
gtmetrix를 이용해보자.
const scripts = document.getElementsByTagName("script"); const scriptsObj = [...scripts].map(script => script.src) .filter(src => src !== "") .reduce((obj, value) => { if (obj.hasOwnProperty(value)) { obj[value] = obj[value] + 1; } else { obj[value] = 0; } return obj; }, {}); Object.keys(scriptsObj).forEach(key => { if (scriptsObj[key] > 0) delete scriptsObj[key]; });
를 웹콘솔에 입력해보아도 <script>의 중복된 갯수를 알 수 있다.
해결방법
중복 스크립트가 생기는 원인은 팀 규모와 스크립트 수라 하였다.
그렇다면 체계적인 시스템을 만들면 되겠죠?
- 이름 관리
실수로 동일한 스크립트를 두 번 포함하는 것을 피하는 한 가지 방법은 템플릿 시스템에 스크립트 관리 모듈을 구현하는 것입니다.
대표적인 예는 버전을 달아놓는 것.
<script type = "text / javascript"src = "menu_1.0.17.js"> </ script>
PHP의 대안은 insertScript이라는 함수를 만드는 것 이다.
<? php insertScript ( "menu.js")?>
동일한 스크립트가 여러 번 삽입되는 것을 방지 할뿐만 아니라 종속성 검사 및 버전 번호를 스크립트 파일 이름에 추가하여 향후 캐시 제어문 헤더를 지원하는 등 스크립트의 다른 문제를 처리 할 수 있다.
리소스 이름 관리는 캐시 제어에서 더 알아보도록 한다.
- 모듈 관리
자주 사용하는 함수들은 모듈로 묶어 관리하고, CSS는 SCSS를 사용하면 변수와 믹스인을 사용하여 역시 중복을 줄일 수 있다.
// foo.js function filter() { ... } function map() { ... } filter(); map(); // bar.js function filter() { ... } function find() { ... } filter(); find();
에서
// utils.js export function find() { ... } export function filter() { ... } export function map() { ... } // foo.js import {filter, map} from 'utils.js' filter(); map(); // bar.js import {filter, find} from 'utils.js' filter(); find();
가 되는 식이다.
1.1.4 JavaScript 및 CSS등 소스 축소
개요
분류: Javascript, CSS
축소(Minification)는 코드에서 불필요한 문자를 제거하여 크기를 줄이고 로드 시간을 개선하는 방법이다.
코드가 축소되면 불필요한 공백문자 (공백, 줄 바꿈 및 탭)뿐만 아니라 모든 주석이 제거된다.
JavaScript의 경우 다운로드 한 파일의 크기가 줄어들기 때문에 응답 시간 성능이 향상된다.
난독화(Obfuscation)는 소스 코드에 적용 할 수 있는 또 다른 최적화.
미국의 상위 10 개 웹 사이트를 대상으로 한 설문 조사에 따르면 축소처리(21%)에 비해 난독처리(25%)의 효율이 좋았다고 한다.
맹글링(Mangling) 같은 기법을 사용해 최적화를 하는데 최소화보다 복잡하므로 난독화 단계 자체로 인해 버그가 발생할 가능성도 있다는 점은 고려대상.
function_long_long_name을 fn1 처럼 만들어주어야 하니까.
외부 스크립트와 스타일을 축소하는 것 외에도 인라인 <script> 및 <style> 블록 을 축소 할 수 있다.
스크립트와 스타일을 압축해도 크기를 줄이면 크기가 5 % 이상 줄어든다.
JavaScript 및 CSS의 사용 및 크기가 증가함에 따라 코드를 축소하여 비용을 절약 할 수 있다.
해결방법
Uglify vs. Babel-minify vs. Terser: A mini battle royale라는 글에 따르면 [다음 레포도 참고, JS minification benchmarks]
terser의 성능이 상당히 좋았다.
CSS의 경우 CSS Minification Benchmark에 따르면 Crass나 CSS Nano가 좋아보인다.
또한 안쓰이는 스타일을 제거하는
를 추가적으로 사용할 수도 있다.
Webpack용 플러그인으로
가 유명하다.
기타 Node.js에서 작동하는 압축기를 찾아보면
- HTML: HTMLMinifier
- SVG: SVGO
- JSON: JSON Mangler, JSON-minify
정도가 좋은 것 같다.
사이트를 이용해서 살펴볼 생각이라면 다음 링크가 유용하다.
https://black7375.tumblr.com/post/180878742065/%EC%9B%B9%EC%86%8C%EC%8A%A4-%EC%95%95%EC%B6%95
JS 코드를 극도로 줄여야 하는 상황이라면 Roadroller같은 팩커를 찾아보아도 좋다.
1.1.5 웹폰트 압축 및 서브셋
개요
분류: Font
웹폰트 관련 확장기능에 잠시 손을 댄적이 있다보니 관련글도 많이 읽어보고, 실제로 적용도 해봤었다 ㅎㅎ[웹 폰트 사용과 최적화의 최근 동향, 웹폰트 최적화, 웹폰트 사용하기 (웹폰트 101)]
한글 폰트 기준, 대부분 용량이 1~2메가를 훌쩍 넘는일이 비일비재하다.
심지어 Noto Sans CJK는 44,683자를 포함하여 15.7MB에 달한다.
웹에서 1메가만 되도 엄청나게 커다란 용량이니 당연히, 줄이는 것이 필요하며 웹폰트는 CSSOM을 생성하는데 필요하므로 렌더링을 차단하는 현상이 일어난다.
- Font Face
@font-face를 통해 폰트 패밀리 명을 설정할 수 있다.
이때 성능과 관계된 것 위주로 알아보도록 하자.
@font-face { font-family: NanumSquareWeb; src: url(NanumSquareR.eot); /* IE 호환성 보기 */ src: local(NanumSquareR), local(NanumSquare), url(NanumSquareR.eot?#iefix) format('embedded-opentype'), /* IE 6 ~ 8 */ url(NanumSquareR.woff2) format('woff2'), url(NanumSquareR.woff) format('woff'), url(NanumSquareR.ttf) format('truetype'), url(NanumSquareR.svg#NanumSquareR) format('svg'); /* 구 모바일 브라우저 */ unicode-range: U+0-10FFFF; }
해결방안
- src
계속 겪을 일이지만 웹브라우저는 지원하는 형식 중 첫번째를 고른다는 것을 염두해두어야 한다.
- src에서 local을 사용하면 컴퓨터에 설치된 글꼴로 로드하므로 빠르다.
- url을 설정할 때 format을 사용하면 지원가능한 파일만 다운로드 받으므로 빠르다.
- 포맷
사용할 수 있는 웹폰트 포맷에는 eot(IE 전용), ttf, woff2, woff, svg가 있다.
이중 SVG는 거의 사용되지 않으며, eot와 ttf는 기본적으로 압축되지 않는다.
- eot와 ttf는 gzip과 같은 압축을 설정해야 한다.
- Zopfli 압축을 통해 eot, ttf, woff를 5%정도 더 압축 가능
- woff2의 압축률이 가장 높다(30% 이상)[WOFF 2.0 Evaluation Report]
- 특정 플랫폼에서 필요하지 않은 힌트나 커닝같은 메타데이터에 대해서 최적화 할 수도
- unicode-range 및 서브셋
unicode-range를 이용하면 범위 구분값 목록을 지정할 수 있다.
- 단일 코드 포인트: 단일 문자를 지정(예: U+416)
- 간격 범위: 범위의 시작과 끝 코드 포인트 지정(예: U+400-4ff)
- 와일드 카드 범위: ?문자를 이용해 임의의 16진수를 나타냄(예: U+4??)
중요한 점은 각 unicode-range마다 폰트파일을 따로 생성해줘야 한다는것.
/* NanumSquareR.woff 원본: 3MB */ @font-face { font-family: NanumSquareWeb; src: url(NanumSquareR-Arab.woff) format('woff'); /* 나눔스퀘어 아랍어 부분: 15KB */ unicode-range: U+06??; /* 아랍어 */ } @font-face { font-family: NanumSquareWeb; src: url(NanumSquareR-Cambodia.woff) format('woff'); /* 나눔스퀘어 아랍어 부분: 7KB */ unicode-range: U+1780-17FF; /* 캄보디아어 */ }
그럼 알파벳 26자보다 훨씬 크기가 큰 한글은 어떻게, 얼마나 나누는 것일까?
원본 나눔고딕은 한글 11,172자를 포함한다.
그러나 노란색 글씨처럼 자주 사용하지 않는 글자들이 있다.
KS X 1001은 "정보 교환용 부호계 (한글 및 한자)"라고 하여 자주 쓰이는 글자들을 모아놓았다.
이 중 한글은 2350자.
그런데 워낙 옛날에 제정된 것이기도 하고, 약간의 확장을 원한다면 노민지, 윤민구. KS 코드 완성형 한글의 추가 제안(224자)를 참고해보아도 좋다. [총2754자]
포맷과 subset 변환은 pyftsubset을 사용하면 된다. [fontsubset.sh 명령어]
나눔고딕 기준으로 2.2MB에서 267KB까지 줄일 수 있었다.
구글은 한발 더 나아간다. [Google Fonts + 한국어 소개, 구글, 한글 글꼴 공식 지원... 머신러닝으로 용량 문제 해결]
Google Fonts는 머신 러닝에 기반을 둔 최적화 기술을 통해 한글 폰트를 동적으로 분할 다운로드합니다.
웹상의 방대한 한국어 문서를 분석한 결과, Google은 주제에 따라 사용되는 글자의 패턴을 발견하고, 패턴에 따라 한글 폰트에 포함된 17,388개의 글리프를 100여 가지 그룹으로 나누었습니다. 여기에 Google Fonts는 사용자가 웹 페이지를 불러올 때, 폰트 전체를 다운로드 하는 대신 내용을 표시하는 데 꼭 필요한 몇 가지 그룹만을 선택적으로 다운로드 하는 방식으로 폰트를 제공합니다. 이 기술을 적용한 Google Font를 사용하면 보다 빠르게 폰트 전체를 다운로드한 것과 다름없는 페이지를 제공할 수 있습니다.
또한 Google Fonts API의 사이트 간 캐싱(cross-site caching)을 통해 해당 폰트가 여러 웹사이트에서 사용될수록 전체 다운로드 시간은 줄어들고, 한글 웹 폰트를 둘러싼 사용자 경험은 그만큼 개선될 것입니다.역시 나눔고딕을 예로 들면
/* [0] */ @font-face { font-family: 'Nanum Gothic'; font-style: normal; font-weight: 400; src: local('NanumGothic'), url(https://fonts.gstatic.com/s/nanumgothic/v17/PN_3Rfi-oW3hYwmKDpxS7F_z-7rJxHVIsPV5MbNO2rV2_va-Nv6p.0.woff2) format('woff2'); unicode-range: U+f9ca-fa0b, U+ff03-ff05, U+ff07, U+ff0a-ff0b, U+ff0d-ff19, U+ff1b, U+ff1d, U+ff20-ff5b, U+ff5d, U+ffe0-ffe3, U+ffe5-ffe6; } /* [1] */ @font-face { font-family: 'Nanum Gothic'; font-style: normal; font-weight: 400; src: local('NanumGothic'), url(https://fonts.gstatic.com/s/nanumgothic/v17/PN_3Rfi-oW3hYwmKDpxS7F_z-7rJxHVIsPV5MbNO2rV2_va-Nv6p.1.woff2) format('woff2'); unicode-range: U+f92f-f980, U+f982-f9c9; } /* [2] */ @font-face { font-family: 'Nanum Gothic'; font-style: normal; font-weight: 400; src: local('NanumGothic'), url(https://fonts.gstatic.com/s/nanumgothic/v17/PN_3Rfi-oW3hYwmKDpxS7F_z-7rJxHVIsPV5MbNO2rV2_va-Nv6p.2.woff2) format('woff2'); unicode-range: U+d723-d728, U+d72a-d733, U+d735-d748, U+d74a-d74f, U+d752-d753, U+d755-d757, U+d75a-d75f, U+d762-d764, U+d766-d768, U+d76a-d76b, U+d76d-d76f, U+d771-d787, U+d789-d78b, U+d78d-d78f, U+d791-d797, U+d79a, U+d79c, U+d79e-d7a3, U+f900-f909, U+f90b-f92e; } ... /* [117] */ @font-face { font-family: 'Nanum Gothic'; font-style: normal; font-weight: 400; src: local('NanumGothic'), url(https://fonts.gstatic.com/s/nanumgothic/v17/PN_3Rfi-oW3hYwmKDpxS7F_z-7rJxHVIsPV5MbNO2rV2_va-Nv6p.117.woff2) format('woff2'); unicode-range: U+d, U+48, U+7c, U+ac10, U+ac15, U+ac74, U+ac80, U+ac83, U+acc4, U+ad11, U+ad50, U+ad6d, U+adfc, U+ae00, U+ae08, U+ae4c, U+b0a8, U+b124, U+b144, U+b178, U+b274, U+b2a5, U+b2e8, U+b2f9, U+b354, U+b370, U+b418, U+b41c, U+b4f1, U+b514, U+b798, U+b808, U+b824-b825, U+b8cc, U+b978, U+b9d0, U+b9e4, U+baa9, U+bb3c, U+bc18, U+bc1c, U+bc30, U+bc84, U+bcf5, U+bcf8, U+bd84, U+be0c, U+be14, U+c0b0, U+c0c9, U+c0dd, U+c124, U+c2dd, U+c2e4, U+c2ec, U+c54c, U+c57c-c57d, U+c591, U+c5c5-c5c6, U+c5ed, U+c608, U+c640, U+c6b8, U+c6d4, U+c784, U+c7ac, U+c800-c801, U+c9c1, U+c9d1, U+cc28, U+cc98, U+cc9c, U+ccad, U+cd5c, U+cd94, U+cd9c, U+cde8, U+ce68, U+cf54, U+d0dc, U+d14c, U+d1a0, U+d1b5, U+d2f0, U+d30c, U+d310, U+d398, U+d45c, U+d50c, U+d53c, U+d560, U+d568, U+d589, U+d604, U+d6c4, U+d788; } /* [118] */ @font-face { font-family: 'Nanum Gothic'; font-style: normal; font-weight: 400; src: local('NanumGothic'), url(https://fonts.gstatic.com/s/nanumgothic/v17/PN_3Rfi-oW3hYwmKDpxS7F_z-7rJxHVIsPV5MbNO2rV2_va-Nv6p.118.woff2) format('woff2'); unicode-range: U+39, U+49, U+4d-4e, U+a0, U+ac04, U+ac1c, U+ac70, U+ac8c, U+acbd, U+acf5, U+acfc, U+ad00, U+ad6c, U+adf8, U+b098, U+b0b4, U+b294, U+b2c8, U+b300, U+b3c4, U+b3d9, U+b4dc, U+b4e4, U+b77c, U+b7ec, U+b85d, U+b97c, U+b9c8, U+b9cc, U+ba54, U+ba74, U+ba85, U+baa8, U+bb34, U+bb38, U+bbf8, U+bc14, U+bc29, U+bc88, U+bcf4, U+bd80, U+be44, U+c0c1, U+c11c, U+c120, U+c131, U+c138, U+c18c, U+c218, U+c2b5, U+c2e0, U+c544, U+c548, U+c5b4, U+c5d0, U+c5ec, U+c5f0, U+c601, U+c624, U+c694, U+c6a9, U+c6b0, U+c6b4, U+c6d0, U+c704, U+c720, U+c73c, U+c740, U+c744, U+c74c, U+c758, U+c77c, U+c785, U+c788, U+c790-c791, U+c7a5, U+c804, U+c815, U+c81c, U+c870, U+c8fc, U+c911, U+c9c4, U+ccb4, U+ce58, U+ce74, U+d06c, U+d0c0, U+d130, U+d2b8, U+d3ec, U+d504, U+d55c, U+d569, U+d574, U+d638, U+d654, U+d68c; } /* [119] */ @font-face { font-family: 'Nanum Gothic'; font-style: normal; font-weight: 400; src: local('NanumGothic'), url(https://fonts.gstatic.com/s/nanumgothic/v17/PN_3Rfi-oW3hYwmKDpxS7F_z-7rJxHVIsPV5MbNO2rV2_va-Nv6p.119.woff2) format('woff2'); unicode-range: U+20-22, U+27-2a, U+2c-38, U+3a-3b, U+3f, U+41-47, U+4a-4c, U+4f-5d, U+61-7b, U+7d, U+a1, U+ab, U+ae, U+b7, U+bb, U+bf, U+2013-2014, U+201c-201d, U+2122, U+ac00, U+ace0, U+ae30, U+b2e4, U+b85c, U+b9ac, U+c0ac, U+c2a4, U+c2dc, U+c774, U+c778, U+c9c0, U+d558; }
처럼 이루어져 있다.
CSS 제공또한 여러개를 하나로 묶어서 받을 수 있는 점은 요청수를 줄인다는 점에서 또 다른 강점.
<link href="https://fonts.googleapis.com/css2?family=Nanum+Gothic&family=Nanum+Gothic+Coding:wght@400;700&display=swap" rel="stylesheet">
위 코드는 나눔고딕 400, 나눔고딕 코딩 400과 700. 3개로 이루어져있다.
내가 만든 font-range를 사용하면 구글 서브셋이나 미리 정의해놓은 유니코드 레인지로 서브셋 할 수 있다. (예: Pretendard)
1.1.6 이미지 압축
개요
분류: Image
이미지가 웹에서 차지하는 용량은 매우 큰 편이다.
따라서 소스를 줄였던 것처럼 이미지도 압축해보는 것을 고려할 수 있다.
이미지 압축을 할때 가장 큰 원칙은 "괜찮게 보이는 가장 낮은 품질"로 압축하는 것이다.
- 모든 이미지에 대해 압축 품질을 가능한만큼 직접 튜닝하라.
- 나머지는 가장 높은 성능을 얻기 위해 최적화를 자동화하라.
해결방법
많이 쓰이는 포맷인 GIF, PNG, JPEG를 대상으로 아라보자.
- GIF
Gifsicle가 좋아보인다.
지금은 Gifsicle에 포함된 프로젝트인 Giflossy의 설명에 따르면 다음과 같이 명령어를 실행하면 최적화 할 수 있다.
gifsicle -O3 --lossy=80 -o RESULT.gif IMAGE.gif
- PNG
이미지 품질을 중요시 여기면 optipng, 용량을 중요시하면 pngquant가 좋다.[pngquant vs pngcrush vs optipng vs pngnq, Experimenting with PNG and JPG Optimization]
일단 YDN에서 소개한 pngcrush는 다음과 같이 실행할 수 있다.
pngcrush IMAGE.png -rem alla -reduce -brute RESULT.png
pngquant는 손실 압축이었고, 무손실 압축을 찾는다면 Zopfli(방법1, 방법2)나 PNGOUT을 쓸 수 있다.
Zopfili는 deflate(zip, gzip 등)와 호환성을 보장하는 압축 알고리즘이다.
단일 색상 PNG 파일 최적화를 위해서 PLTE(팔렛트) 및 IDAT(이미지 데이터 섹션)과 관련해 최적화를 할 수도 있다. [가장 작은 크기의 단색 256x256 PNG 파일, 어디서 봤을까?(요약)]
- JPEG
JPEG에서는 MozJPEG가 좋다.[Comparison of JPEG Lossless Compression Tools, mozjpeg vs libjpeg-turbo]
역시 YDN의 소개에 나온 jpegtran은 다음처럼 실행가능하다.
jpegtran -copy none -optimize -perfect IMAGE.jpg RESULT.jpg
회전(rotation)과 같이 손실 없는 JPEG 작업을 수행하고 최적화를 위한 사용이 가능하며, 이미지들로부터의 다른 쓸모 없는 정보(EXIF 정보와 같은)와 코멘트(comments)를 제거할 수 있다고 한다.
Guetzli라고 구글이 2017년에 발표한 인코더도 있는데 Google Guetzli vs MozJPEG: Why Google’s New JPEG Encoder Can’t Dethrone the Product Image King을 보면 품질은 좋으나 매우 느리다. 크기가 매우 크거나 작은 이미지가 아니라면 효율성이 낮기 때문에 쓰지 않는 것이 좋다. 단, 극도의 효율성을 위해서라면 크기가 큰 경우 MozJPEG을 사용한 후 같이 써도 좋은 모양. [Squeezing JPEG Images with Guetzli, Image Compression Benchmarks]
JPEG의 경우 Progressive를 활성화시키는 것도 좋다.
하지만 약간의 트레이드 오프가 있다는 것.
장점
- 더 빠르게 로딩된다고 느낌
- 더 높은 압축효율(2-10%)
단점
- 느린 디코딩(최대 3배)
- 매우 작은 이미지는 더 클 수도 있음
- 완전히 로드되었는지 알기 힘듦
- EXIF 제거
또한 Exif를 제거하는 것도 좋은 생각이다.[What Is EXIF Data?]
- NodeJS 바인딩
- GIF: gifsicle-bin
- PNG: imagemin-optipng, node-pngquant
- JPEG: mozjpeg-bin, guetzli-bin
1.1.7 이미지, 영상 포맷
개요
분류: Image, Video
- 이미지
사용자나 디자이너가 웹페이지에 대한 이미지 생성을 완료하면, 시도해 볼 수 있는 최적화 사항들이 몇 가지 더 있다.
특히 비효율적인 것으로 유명한 GIF는 최적화의 여지가 크다.
https://medium.com/vingle-tech-blog/stop-using-gif-as-animation-3c6d223fd35a
이외 이미지 최적화와 관련된 내용은 다음 글에서 잘 설명해주고 있다.
https://www.html5rocks.com/ko/tutorials/speed/img-compression/
https://developer.mozilla.org/en-US/docs/Web/Media/Formats/Image_types
- 영상
- 음성
https://developer.mozilla.org/en-US/docs/Web/Media/Formats/Audio_codecs
측정방법
GIF의 경우 이미지 색상들의 숫자에 상응하는 팔레트 크기를 사용하는지 ImageMagick을 사용하면 확인할 수 있다.
identify -verbose IMAGE.gif
팔레트에서 4가지 색상과 256 색상 슬롯(slot)을 사용하는 이미지를 보면 개선의 여지가 있다
해결방법
- GIF
정적인 GIF 파일은 PNG로 바꾸어보자.(ImageMagick 기준)
convert IMAGE.gif IMAGE.png
GIF 최적화를 위해 앞서 나온 Gifsicle같은 것을 이용할 수도 있다지만..
동적인 GIF 파일이라면 MP4처럼 영상파일로 바꾸는 것이 좋다.(ffmpeg 사용, 명령어 참고 )
ffmpeg -f gif -i ANIMATE_IMAGE.gif -pix_fmt yuv420p -c:v libx264 -movflags +faststart -filter:v crop='floor(in_w/2)*2:floor(in_h/2)*2' ANIMATE_IMAGE.mp4
GIF는 영상 재생을 위해 나온 포맷이 아니다보니 미디엄 포스트에서 나온 것처럼 10배 가까이 용량차이가 생기기도 한다.
실제로 Gfycat, Giphy처럼 GIF를 전문적으로 다루는 사이트나 짤로서 GIF가 많이 이용되는 사이트는 트래픽을 줄이고, 속도를 높이기 위해 영상으로 변환한 것을 자주 볼 수 있다.
영상으로 바꾸어도 HTML 마크업은 크게 복잡해지지 않는다.
단, 자동재생(autoplay), 반복(loop), 소리가 없음(muted)란 gif의 특징은 따라야할 필요가 있다.
<!-- OLD GIF Markup --> <img src="https://cdn.my-awesome-website.com/images/awesome-animation.gif" alt="My awesome animated image"> <!-- NEW Video Markup --> <video autoplay loop muted playsinline> <!-- Specify video sources --> <source src="https://cdn.my-awesome-website.com/images/awesome-and-efficient-animation.webm" type="video/webm"> <source src="https://cdn.my-awesome-website.com/images/awesome-and-efficient-animation.mp4" type="video/mp4"> <!-- FALLBACK for legacy browsers! --> <img src="https://cdn.my-awesome-website.com/images/awesome-animation.gif" alt="My awesome animated image"> </video>
MP4(H.264) 대신 WebM을 써도 좋다.
WebM은 효율적인 비디오(VP8, VP9, AV1)와 오디오(Vorbis, Opus) 코덱들을 지원한다.
Imgur의 경우 GIF와 사용경험은 똑같지만 내부는 WebM 또는 MP4로 된 GIFV 포맷을 개발하여 사용하며, Gfycat 또한 마찬가지이다. [INTRODUCING GIFV, About Gfycat]
- WebP
JPEG보다 평균 25–35%, PNG보다 26% 크기가 작을정도로 효율적인 이미지 포맷입니다.
많은 브라우저에서 지원하므로 충분히 시도해볼만 합니다.
WebP를 지원하지 않는 브라우저가(대표적으로 사파리) 존재하기 때문에 생기는 문제는 <picture>태그를 사용하면 된다.
source중 첫번째로 매칭되는 것(지원하는 것)을 가져오므로 최적화가 된 것을 앞에 두어야 한다.[Selecting an image source]
<picture> <source srcset="/path/to/image.webp" type="image/webp"> <img src="/path/to/image.jpg" alt=""> </picture> <picture> <source srcset="photo.jxr" type="image/vnd.ms-photo"> <source srcset="photo.jp2" type="image/jp2"> <source srcset="photo.webp" type="image/webp"> <img src="photo.jpg" alt="My beautiful face"> </picture>
- SVG나 CSS
간단한 이미지를 SVG나 CSS를 사용하면 텍스트로 만들어지기 때문에 큰 폭으로 이미지 크기를 줄일 수 있다.[A Beginner’s Guide to Pure CSS Images, 4 Ways for Using CSS Instead of Images for Design Effects]
특히 CSS는 메인 쓰레드가 아니라 컴포지터 쓰레드를 사용하므로 성능에 좋다.
다음은 SVG와 CSS를 비교한 어도비의 문서들이다.
- CSS vs. SVG: Graphical Text Effects
- CSS vs SVG: Styling Checkboxes and Radio Buttons
- CSS vs. SVG: Shapes and Arbitrarily-Shaped UI Components
- CSS vs SVG: The Final Round(up)
- 기타포멧
기타 효율적이지만 지원하지 않거나 앞으로 지원가능성이 있는 이미지 포맷들의 리스트다.
개인적으로 AVIF에 기대를 걸고 있다.
- FLIF: 압축률 기준으로 PNG, WebP, BPG등보다 좋다고 주장하는 무손실 포맷 [Poly-Fill]
- HEIF: 아래나올 HEVC를 이용한 이미지 포맷, 애플이 사용하는 것으로 유명
- AVIF: 역시 아래나올 AV1을 이용한 이미지 포맷
- 영상코덱
아직은 하드웨어 가속이 떨어지기 때문에 H.264를 이용하는 것이 낫겠지만 몇년후 지원기기가 많아지면 효율이 좋은 AV1을 사용해보는 것도 괜찮겠다. AV1 구현중 SVT-AV1을 ffmpeg(플러그인)에 붙여쓰는 형태가 되지 않을까 싶다. [AV1 vs HEVC: Should You Abandon HEVC Now?, AV1 is ready for prime time: SVT-AV1 beats x265 and libvpx in quality, bitrate and speed, Reddit Benchmark1, Reddit Benchmark2]
- NodeJS 바인딩
- ffmpeg: node-fluent-ffmpeg
- WebP: node-webp, cwebp-bin
이것저것을 찾다보니 imagemin이란 것을 알게 되었는데 여러 이미지 최적화를 한꺼번에 관리하기 편할 것 같다.
- GIF: imagemin-gifsicle, imagemin-gif2webp
- PNG: imagemin-optipng, imagemin-pngquant
- JPEG: imagemin-mozjpeg, imagemin-guetzli
- SVG: imagemin-svgo
- WebP: imagemin-webp
1.1.8 HTML에서 이미지 크기를 조정하지 않기
개요
분류: Image
- 리사이징
HTML에서 너비와 높이를 설정할 수 있다고 필요보다 큰 이미지를 사용하지 말자는 것.
<img width="100" height="100" src="IMAGE.jpg" alt="IMAGE" />
만약 위와 같은 구문을 원한다면 이미지 (IMAGE.jpg)는 축소된 500x500px 이미지가 아니라 100x100px이어야 한다.
커다란 이미지는
- 커다란 용량으로 다운로드 시간 낭비
- 대역폭을 낭비로 HTTP 연결갯수에 제한이 생길 가능성
- 메모리 크기
- 디코딩 시 비용
- 리스케일시 비용과 이미지 품질 미보장
처럼 많은 악영향을 끼친다.
- 반응형
크기에 꼭 맞게 하라는 말은 반응형 웹의 개념이 필요하다는 뜻이다.
즉, 각 해상도에 맞는 이미지를 제공해야 한다.[MDN 반응형 이미지, 구글 반응형 이미지]
이때 해상도마다 잘라내기나 확대가 다른 아트디렉션도 고려해야 한다. [Response Images Demo]
해결방법
- 변환
비트윈은 Skia를 이용해 리사이징을 했지만 Sharp란 패키지도 좋은 것 같다.
실제로 Skia의 성능은 매우 우수해 카이로를 제치고 웹브라우저에서 2D 그래픽용으로 사용되고 있다.
- 반응형
해상도
각 해상도에 맞는 이미지를 사용하려면 srcset 속성을 사용하면 된다.
<img srcset="EXAMPLE-320w.jpg, EXAMPLE-480w.jpg 1.5x, EXAMPLE-640w.jpg 2x" src="EXAMPLE-640w.jpg" alt="Example Image!!">
물론 앞서 나온 <picture>태그처럼 srcset을 지원하지 않으면 기본값인 src를 사용한다.
크기
sizes 속성으로 화면 크기에 따라 다른 크기의 이미지를 로드할 수도 있다.
<img srcset="EXAMPLE-320w.jpg 320w, EXAMPLE-480w.jpg 480w, EXAMPLE-800w.jpg 800w" sizes="(max-width: 320px) 280px, (max-width: 480px) 440px, 800px" src="EXAMPLE-800w.jpg" alt="Example Image!!">
아트디렉션
아트디렉션을 사용하기 위해서는 <picture> 태그가 필요하다.
source에서 media 속성을 사용해 어떠한 이미지를 보여줄지 결정한다.
미디어 속성은 추후에 등장할 미디어 쿼리에서 더 설명한다.
<picture> <source media="(max-width: 799px)" srcset="EXAMPLE-480w-close-portrait.jpg"> <source media="(min-width: 800px)" srcset="EXAMPLE-800w.jpg"> <img src="EXAMPLE-800w.jpg" alt="Example Image!!"> </picture>
1.1.9 파비콘을 작고 캐시 가능하게 만들기
개요
분류: Image
파비콘(favicon)은 주소창에 표시되는 아이콘으로, 주로 서버의 루트에 위치한다.(파비콘(Favicon)의 모든 것)
신경 쓰지 않아도 브라우저가 계속 요청하므로으로 404 Not Found가 아닌 것이 좋다.
또한 동일한 서버에 있기 때문에 쿠키는 요청 될 때마다 전송되며 다운로드 순서를 방해하기도 한다.
(예를 들어, 온로드에서 추가 구성 요소(component)를 요청하면 IE에서 이러한 추가 구성 요소보다 파비콘이 다운로드됩니다).
해결방법
파비콘의 단점을 보완하기 위해서는 다음을 지키는 것이 좋다.
- 1k 이하로 작게 만들기
- 변경설정 후에는 이름변경이 불가능하므로 원하는 시점의 Expires header를 설정.
아마 미래 시점(약 몇 달 뒤)의 Expires header를 안전하게 설정할 수 있을 것이다. 명확한 결정을 위해 현재 파비콘의 마지막 수정날짜 확인이 가능하다.
Favicon Generator를 이용해서 생성하면 다양한 기기에 대응할 수 있다.
1.1.10 HTTP 구성요소 압축
개요
분류: Server
HTTP 응답 구성요소를 압축하여 줄임으로써 응답 시간을 줄일 수 있다.[Content Negotiation]
웹 클라이언트는 HTTP 요청에서 Accept-Encoding 헤더를 사용하여 지원하는 압축을 나타낸다.
Accept-Encoding: br, gzip, deflate
웹 서버가 요청에서 이 헤더를 발견하면 클라이언트가 나열한 방법 중 하나를 사용하여 응답을 압축 할 수 있다.웹 서버는 응답의 Content-Encoding 헤더를 통해 웹 클라이언트에게 이를 알린다.
Content-Encoding: gzip
서버는 파일유형에 따라 압축대상을 선택할 수 있지만 일반적으로 이 결정에는 많은 제약이 따른다.
대부분의 웹사이트들은 자신들의 HTML 문서를 압축한다. 그 압축툴은 scripts와 stylesheets를 압축하는 데에도 매우 유용한 것들이지만 많은 웹사이트들은 이 기회를 놓치고 있다. 이미지와 PDF 파일은 이미 압축이 되어있기 때문에 압축할 필요가 없지만 XML이나 JSON을 포함한 텍스트 응답을 압축하는 데에는 유용하다. CPU를 낭비할 뿐 아니라 잠재적으로 파일크기를 증가시킬 수 있기 때문이다.
가능한 많은 파일 형식을 압축하는 것이 페이지 무게를 줄이고 사용자 경험(UX)을 가속화하는 쉬운 방법입니다.
해결방안
가장 좋은 것은 구글의 Brotli를 적용하는 것이다.[Introducing Support for Brotli Compression, Boosting Site Speed Using Brotli Compression]
최신 브라우저들은 브로틀리를 지원하니 안심하고 써도 된다.
- 아파치와 엔진엑스 설정법 [Apache: mode_brotli, NGINX: brotli]
- 웹팩(Webpack)에서 Brotli를 설정하고, Gzip을 Fallback으로 두는 방법
- 서버가 php 사용시 다음과 같이 압축된 것을 요구 가능.
<?php if (substr_count($_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip')) ob_start("ob_gzhandler"); else ob_start(); ?>
Gzip에는 앞서 언급한 Zopfli를 적용해볼 수 있다.(단, 시간이 약간 더 필요하다는 것은 고려대상)
lz4도 성능이 엄청 좋은데..(인코딩, 디코딩, 압축률 모두에서.. 개인 컴퓨터 커널 압축에 써먹는중)
왜 언급이 안되나 궁금.
1.1.11 클라이언트 힌트로 리소스 선택 자동화
개요
분류: Server
클라이언트 힌트를 사용하여 리소스 선택을 자동화 할 수 있다.[Adapting to Users with Client Hints, Automating Resource Selection with Client Hints, Client Hints]
앞선 최적화의 영향으로 우리는 최적화된 이미지 포맷(WebP, JPEG XR, JPEG), 해상도(1x, 1.5x, 2x, 2.5x, 3x), 뷰포트 너비에 따라 제어를 하고 싶어했고 그 결과 아래처럼 거대한 <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 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>
클라이언트 힌트는 서버에서 고르도록해 매우 간단하게 만들 수 있다.
- 용어 설명
클라이언트 힌트에서 나오는 용어들을 설명한다.[CSS 에서 항목 크기 조정]
- 고유 크기(Intrinsic Size): CSS등의 영향을 받기 전에 설정된 고유의 크기
- 외적인 크기(Extrinsic Size): CSS등을 사용해 설정한 특정 크기
- 밀도가 수정된 고유 크기(Density-corrected intrinsic size): 고유 크기를 장치 픽셀 비율로 나눈 값, 픽셀 밀도에 맞춰 수정된 미디어 리소스의 크기.
<img src="whats-up-1x.png" srcset="whats-up-2x.png 2x, whats-up-1x.png 1x" alt="I'm that image you wanted.">
고유 크기가 1x지만, 화면의 픽셀 비율이 2x인 장치를 사용하면 2x 이미지가 요청되는 식.
- Viewport: 브라우저의 페이지에서 가시영역. [Viewport meta 태그를 이용해 모바일 브라우저상에서 레이아웃 조종하는 법]
- 데스크톱 브라우저의 창크기, 모바일 브라우저의 줌크기에 따라 달라진다.
- viewport의 width 값에 따라 아트디렉션도 고를 수 있다.
- DPR(Device Pixel Ratio): 사용자 화면의 물리적 픽셀과 CSS 픽셀의 비율 [devicePixelRatio]
- Width: 이미지 리소스 요청에 대한 힌트가 나타남(예: DPR이 2, CSS의 너비가 320, 뷰포트 너비가 85라면 $(2 \times 320) \times 0.85$
- Content-DPR: 리소스 자체 픽셀과 CSS 픽셀의 비율, DPR과 Width 헤더를 모두 사용할 때 외적인 크기로 달라질때 유용
- Device-Memory: 메모리의 크기를 대략적(GB 기준)으로 표시. 비용이 비싼 Javascript를 조절할 때 쓰일 수 있다.
다음은 네트워크 정보들의 예.
- RTT: RTT 시간(ms)을 대략적(25ms 반올림)으로 제공
- Downlink: 대역폭의 크기(Mbps)를 대략적(2kb)으로 제공
- ECT(Effective Connection Types): RTT와 Downlink를 분석해 2g, 3g, 4g등으로 나눔
- Save-Data: 적은 양의 데이터를 보내야 한다는 환경설정
해결방법
- Accept 헤더
Accept헤더를 이용하면 브라우저가 컨텐츠 유형을 고를 수 있다.
Accept: image/webp,image/apng,image/*,*/*;q=0.8
PHP를 사용하면 이를 설정할 수 있다.
<?php // Check Accept for an "image/webp" substring. $webp = stristr($_SERVER["HTTP_ACCEPT"], "image/webp") !== false ? true : false; // Set the image URL based on the browser's WebP support status. $imageFile = $webp ? "whats-up.webp" : "whats-up.jpg"; ?> <img src="<?php echo($imageFile); ?>" alt="I'm an image!">
- Accept-CH
클라이언트 힌트는 DPR(Device Pixel Rate), Viewport-Width, Width를 서버에 알리도록 한다.
<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>
앞선 <picture> 태그보다 훨씬 간단하며 서버나 CDN을 통해 자동적으로 이미지를 다운받도록 만들 수 있다.
네트워크
네트워크의 경우 Save-Data가 on인지를 가장 먼저 확인하고, 없다면 네트워크 점수(0~1 사이)를 내서 사용할 수 있다.
웹폰트, 이미지 회전 메뉴와 아코디언 동작 등을 위한 Javascript, 컨텐츠 이미지등을 생략시키는 예.
다음과 같이 Save-Data가 활성화 되었는지 확인할 수 있다.
if ("connection" in navigator) { if (navigator.connection.saveData === true) { // Implement data saving operations here. } }
- 필수적이지 않은 웹폰트 제거
- 이미지를 낮은 해상도로
- 경량화된 코드
- 불필요한 코드 삭제
- 검색시 적은 수의 결과를 반환하거나 미디어가 많은 결과를 제한
기타 코드는 Delivering Fast and Light Applications with Save-Data를 참고바란다.
1.1.12 구성 요소를 25K 미만으로 유지
개요
분류: Mobile
이 제한은 iPhone이 25K보다 큰 구성 요소를 캐시하지 않는다는 연구가 있다. (단, 오래전이라 기준이 달라졌을 수도 있음)압축되지 않은 크기 기준이며, gzip이나 brotli등의 압축만으로는 충분하지 않을 수 있다는 뜻이다.
자세한 내용 은 Wayne Shea 및 Tenni Theurer의 " Performance Research, Part 5 : iPhone Cacheability-Making it Stick "을 확인하십시오.+.
TCP 혼잡 제어 때문에 웹사이트가 14kb 미만인게 좋다. [웹사이트 크기가 14kB 미만이어야 하는 이유]
1.1.13 기타 번들러 최적화
개요
분류: Content, Javascript, CSS
웹팩/바벨등의 최적화를 다룬다.[webpack for real tasks: decreasing front-end size and improving caching, Web Performance Optimization with Webpack, Awesome Webpack Perf]
웹팩
- mode: production을 고르면 디버깅용 코드 제거, 최적화 설정을 고를 수 있다.[webpack으로 development, product 환경 세팅하기, DefinePlugin, 소스맵]
- ContextReplacementPlugin: 디렉토리에서 매칭할 파일의 정규식을 사용
- CompressionWebpackPlugin: 압축하기
- Code Split: 자바스크립트를 청크로 나누기 [Reduce JavaScript Payloads with Code Splitting, SPA 초기 로딩 속도 개선하기, SplitChunksPlugin, MinChunkSizePlugin, LimitChunkCountPlugin, A Gentle Introduction to Code Splitting with React]
- Tree Shaking: 기법활용[Reduce JavaScript Payloads with Tree Shaking, Webpack 4의 Tree Shaking에 대한 이해, Webpack에서 Tree Shaking 적용하기, Webpack bundling 파일 사이즈 줄이기]
- 기타: awesome-webpack-perf
바벨
- babel-preset-env: 지원대상 브라우저를 설정해 최신 브라우저에서 제공하는 기능은 트랜스파일을 하지 않기. Differential serving과 관련된 글 또한 좋다. [babel-preset-env는 무엇이고 왜 필요한가?, 모던 브라우저에도 꼭 es5를 써야 할까(Differential Serving), Diffential Serving, Serve modern code to modern browsers for faster page loads, Differential serving vs polyfill service]
- 컴파일 타임 매크로 활용 [An Introduction to JavaScript Compile-Time Processing]
- 조건부 컴파일 [babel-plugin-conditional-compile]
최적화
- Closure Compiler: 데드코드 제거에 탁월한 걸로 유명하다.
Tsickle을 쓰면 타입스크립트를 이용해 Advanced 최적화를 할 수 있다. [Using Closure Compiler With Webpack + Typescript via Tsickle] - Prepack: 런타임 최적화에 집중한다.
조금 많이 괴랄한 걸로는 Js -> C++ -> LLVM -> Js로 바꾸는 방법도 있다? [A JavaScript optimizing compiler]
기타
이외 배송 최적화에 잘 써진 글. [Publish, ship, and install modern JavaScript for faster applications]
참고로 CommonJS 형태의 모듈은 ESM보다 번들링 최적화에서 손해일 수밖에 없다. [How CommonJS is making your bundles larger]
만약 각종 JS 모듈 시스템이 궁금하다면 JavaScript 번들러로 본 조선시대 붕당의 이해를 보자.
번들러 말고, 태스크 러너로 빌드하는 경우도 있을 수 있다. (타입스크립트나 자바스크립트를 안쓸 경우..?)
이때는 Grunt And Gulp Tasks For Performance Optimization 를 참고해보십셔.
1.2 요청 줄이기
각종 리소스는 서버에 요청을 하고 나서야 다운로드 할 수 있기 때문에 요청을 줄이거나 요청 후 응답시간을 줄이는 것은 다운로드 시간을 줄이는데 도움이 된다.
HTTP 1.1의 경우, 한 호스트에서 최대 6개의 리소스만 병렬적으로 가져올 수 있고, 그 이상은 대기한다.[Match Firefox's per-host connection limit of 15]
1.2.1 DNS 조회 감소
개요
분류: Content
DNS (Domain Name System)는 우리가 익히 알다시피 호스트 이름을 IP 주소에 매핑한다.
DNS 조회에는 시간(약 20~120ms)이 드는데 DNS 조회가 완료 될 때까지 브라우저는 이 호스트 이름에서 아무것도 다운로드 할 수 없다.
그래서 성능 향상을 위해 DNS 조회 정보가 캐싱된다.
사용자의 ISP와 LAN에 의해 유지 관리되는 캐싱 서버말고도 개별 컴퓨터의 운영체제와 브라우저에서도 일어난다.
클라이언트의 DNS 캐시가 비어있는 경우(브라우저와 운영 체제 모두) DNS 조회 수는 웹 페이지의 고유 호스트 이름 수와 같다.블로그 정보와 글: 티스토리, 이미지: imgur, react: jsDelivr, 부트스트랩: 클라우드 플레어, 폰트: 구글이라면 총 5개의 호스트가 되는 것이다.
즉, 페이지 URL에 사용 된 호스트 이름, 이미지, 스크립트 파일, 스타일 시트등이 포함되며 고유 한 호스트 이름 수를 줄이면 DNS 조회 수가 줄어 들 수 있다는 것이다.
하지만 고유 호스트 이름 수를 줄이면 병렬 다운로드가 줄어들 수 있다.(각 서버에서 병렬 다운로드에 제한을 걸 수 있음)따라서 DNS 조회가 줄었더라도 병렬 다운로드가 이루어지지 않아 응답 시간이 늘어날 수 있다.
해결방법
호스트 구성 요소를 2~4개로 적절히 분리통합하고 유지하는 것이 좋다.
1.2.2 CDN 사용
개요
분류: Server
웹 서버에 대한 사용자의 근접성은 응답 시간에 영향을 준다.
지리적으로 분산 된 여러 서버에 콘텐츠를 배포하면 사용자 관점에서 페이지를 더 빠르게 로드 할 수 있다.
해결방법
이미 잘 분산되어있는 CDN 서비스를 이용하는 것이 좋다.
무료에서는 클라우드 플레어의 CDNjs와 jsDelivr, 구글(주요 라이브러리만 무료)이 유명하고 쓸만한편.
1.2.3 404 피하기
개요
분류: Content
HTTP 요청은 비용이 많이 들기 때문에 HTTP 요청을 하고 쓸모없는 응답 (예 : 404 찾을 수 없음)을 얻는 것은 완전히 불필요하며 아무런 이점없이 사용자 경험을 느리게 합니다.
일부 사이트는 404때 "X를 의미 했습니까?"처럼 유용한 정보를 제공합니다. 이는 사용자 경험에는 좋지만 서버 리소스 (예 : 데이터베이스 등)를 낭비합니다.외부 JavaScript에 대한 링크가 잘못되고 결과가 404 인 경우 특히 나쁩니다.
먼저 이 다운로드는 병렬 다운로드를 차단하며 브라우저는 JavaScript 코드인 것처럼 404 응답 본문(body)을 구문 분석하여 사용할 수있는 것을 찾으려고 시도 할 수 있습니다.
측정방법
broken-link-checker같은 NodeJS 패키지나 LinkChecker, Xenu’s link Sleuth와 같은 프로그램을 쓸 수 있다.
사이트로는 Sitechecker, Dead Link Checker, W3C Linkchecker와 같은 것이 있다.
1.2.4 서명된 교환(SXGs, Signed Exchanges)
개요
분류: Content
SXG는 리소스가 전달된 방식과 관계없이 출처를 인증할 수 있는 전달 메커니즘이다. [Get started with signed exchanges on Google Search, Signed Exchanges (SXGs)]
특히 구글의 경우는 SXG 캐시를 사용해 콘텐츠를 미리 가져오므로 성능 개선을 할 수 있다.
최근 이야기가 나오는 Webpackage(Webbundle 포함)도 이를 적극사용 한다. [Webpackager]
다만 개인정보 관련하여 말이 있는 편. [Do Web Bundles threaten the Web?, WebBundles Harmful to Content Blocking, Security Tools, and the Open Web]
1.2.5 서버 응답 시간 개선
개요
분류: Server
서버 응답시간이 빠르면 좋으며 일반적으로 200 ms 아래면 좋다고 한다.
느린 애플리케이션 로직, 느린 데이터베이스 쿼리, 느린 라우팅, 프레임워크, 라이브러리, 리소스 CPU 부족 현상 또는 메모리 부족 현상등이 원인일 수 있다.
또한 서버의 응답시간의 변동폭은 적으므로 변동이 크다면 문제가 있다는 증거이기도 하다.
해결방법
크게 다음처럼 걸러낼 수 있다. [5 Ways to Reduce Server Response Times (TTFB)]
- 트래픽처리를 위한 충분한 리소스
- 아파치나 Nginx등 웹서버를 신중히 선택
- 웹서버 최적화
- WordPress나 Magento같은 CMS(Content Management System) 사용시 신중히 관리
- DB 최적화 [SQL Query Optimization: Understanding Key Principle, SQL Indexing and Tuning e-Book]
아키텍처 이야기만 해보자면 일반적으로 모놀리식(Monolithic)을 사용해도 상관이 없다. [Do Not Use MSA - 마이크로서비스 아키텍처가 꼭 필요한가요?, MSA 제대로 이해하기]
- 그러나 서비스가 복잡해지고 트래픽 규모가 커지면 MSA를 도입해볼 가치가 있다.
- 배민 MSA 여행기
- Microservice Architecture and its 10 Most Important Design Patterns
여긴 프론트엔드의 영역이 아니니 더 자세한 설명은 생략한다.
1.2.6 기타 네트워크 최적화
개요
분류: Server
High Performance Browser Networking이라는 사이트의 내용이 전반적으로 너무 좋다.
아래는 최적화 방법을 정리해 놓은 것.
아마 최신 리눅스 커널들을 사용한다면 대부분 활성화 되어있을 것이다.
+.
페이스북 기술 블로그에 쓰인 Building Zero protocol for fast, secure mobile connections
도 읽어볼만 하다.
- Transport & Session
OSI 7 Layer에서 Transport 계층에 속하는 TCP와 Session에 속하는 TLS에 대해 다루어본다.
최근 QUIC를 비롯해 제약이 덜한 UDP를 적극적으로 활용하는 경우가 많이 생기고 있다지만, 제약이 덜하다보니 튜닝 규칙또한 적기가 애매하여 제외하였다.
일반기업들은 TCP를 주로 사용하며, UDP에서는 QUIC를 제외하면 적용할 일이 거의 없을만 하기도 하고.
Session계층은 Transport보다 Application쪽과 가깝긴 한데..
Application에서 HTTP를 중점적으로 다룰 것이며, RTT 개선의 측면에서 Transport와 같이 설명하기 편하므로 묶어서 설명한다.
TCP
TCP 성능 튜닝에 대한 이야기. [Building Blocks of TCP, TCP Tuning for HTTP]
오랜만에 네트워크 책을 보는 느낌이 나는 듯 ㅎㅎ
재밌어서 그림들도 끼워넣으며 설명해본다.
먼저 혼잡제어에 대해 다루어보자. [사이 좋게 네트워크를 나눠 쓰는 방법, TCP의 혼잡 제어]
- 초기 혼잡 윈도우(Congestion Window, CWND) 크기 증가
초기 CWND가 크면(약 10 세그먼트) 첫번째 RTT에서 가져갈 수 있는 데이터양이 커지고, 혼잡제어 과정에서 윈도우의 크기를 빠르게 증가하는 것이 가능하다.
- Slow-Start 재시작 비활성화
일정 유휴시간(Idle)이 생기면 혼잡 윈도우 값을 초기화하여 Slow Start로 재시작 하는 상황이 생긴다.
Slow-Start 재시작을 비활성화 하면 오래 지속되는 TCP 연결의 성능이 향상된다.
- 윈도우 스케일링
TCP의 RWND(수신 윈도우) 크기를 알리기 위해 16비트가 할당되어 있다.
당연히 최대 크기는 $2^{16} = 65,536 \, byte$다.
최대 RWND의 크기를 키우기 위해 윈도우 스케일링(RFC 1323)이 제공되서 1기가까지 키울 수 있고, 대역폭을 최대한 이용할 수 있다.
원리는 기존 RWND값에 좌측으로 시프트할 갯수를 전달하는 것.
- TCP Fast Open
TCP Fast OPEN – Citrix NetScaler
SYN 패킷에 데이터 전송을 허가해서 대기시간을 제거할 수 있다.
구글에 따르면 HTTP 트랜젝션 네트워크 대기시간을 15%, 전체 페이지로드는 10~40%까지도 단축 시킬 수 있다고.
관심이 생긴다면 다음문서에 있는 링크들을 확인 바란다. [TCP Fast Open - 보다 빠르게 웹 컨텐츠를 전송하기 위한 기술]
2016년쯤에 엣지에 들어간다는 기사를 보고 알았던 것을 생각하면 비교적 최신 기술. [Enable TCP Fast Open in Microsoft Edge for quicker page load times]
UDP
관심이 있다면 다음을 읽어보자. [Building Blocks of UDP]
TLS
해당 링크에는 여러가지가 나왔지만 간단하게 3~4개만 다루려한다. [Transport Layer Security (TLS)]
링크에는 대칭키 암호화 사용(공개키 대신), TLS 레코드 크기 최적화, 인증서 체인 최적화, OCSP(Online Certifiate Status Protocol) 스태플링 설정, HSTS(HTTP Strict Transport Security) 사용 등등이 등장한다.
- 연결 재사용 / 구성 세션 캐싱
TCP + TLS 연결 설정을 위한 대기시간과 계산 시간 오버헤드를 막기위해 연결 시간 종료값을 설정한다.
SSL2.0에는 TLS 세션 캐싱이 도입되어 더욱 효율적으로 오버헤드를 제거할 수 있다.
- CDN 활용
CDN을 활용하면 물리적인 거리를 줄일 수 있기 때문에 유리하다.
- TLS 1.3 활성화
0-RTT로 오버헤드가 제거되었다. [알아두면 쓸데없는 신비한 TLS 1.3, Introducing Zero Round Trip Time Resumption (0-RTT)]
+.
현재 사용되지 않는 TLS False Start가 TLS 1.3의 아이디어와 살짝 비슷하다. [So long False Start, we hardly knew ye]
ClientKeyExchange를 보내면 이미 암호화키를 알고 있는 것과 다름 없으므로 어플리케이션 데이터를 전송해도 무방하다.
- Application
HTTP 프로토콜의 최적화를 다루어보고자 한다.
가장 좋은 것는 1.X보다 2를 3가 보급된다면 3를 사용하는 것이다.
HTTP 1.X
Keep-Alive, 파이프 라이닝을 다루었다. [HTTP/1.X]
도메인 샤딩, 이미지 스프라이트와 같은 방법은 프론트엔드에서 최적화 가능한 부분이라 따로 분리했다.
- Keep-Alive
매번 연결을 다시 하는 것보다 연결을 유지하여 동일한 TCP 연결을 하용하자는 아이디어.
- HTTP 파이프 라이닝
파이프라이닝을 활용하면 Keep-Alive를 사용해 응답을 받지 않고도 여러개의 요청을 하는 것이 가능하다. [HTTP/1.x의 커넥션 관리]
서버에서 FIFO로 각각이 아니라 병렬처리를 해서 합쳐보내줄 경우 더 단축시키는 것도 가능하다.
HTTP 2
구글의 SPDY에 기반해 만들어진 표준이다. [HTTP/2, HTTP/2 소개]
- 헤더 압축 / 바이너리 프레임
HPACK이라는 방식으로 헤더를 압축하여 전송한다.
(정적 허프만 코드로 인코딩, 헤더 필드를 인덱싱화하여 관리하면서 업데이트)
기존 텍스트방식에 비해 바이너리로 만들어져 파싱이 빠르고 요류도 줄어든다.
- 멀티플렉싱
HTTP 1.1의 파이프라이닝을 넘어서 다수의 응답과 요청을 동시적으로, 독립적으로 처리할 수 있다. [LoadMaster - HTTP/2]
- 스트림 우선순위
리소스 간 우선순위를 정하여 Critial Rendering Path의 개선에 도움이 된다.
스트림은 위와 같이 이루어져 있다.
- 서버푸시
클라이언트의 요청이 없더라도 서버에서 컨텐츠를 Push할 수 있다. [HTTP/2 Server Push Tutorial]
HTTP3(실험적, 개발중)
HTTP3는 앞서 소개한 TLS 1.3과 UDP기반 프로토콜인 QUIC로 구성되어 있다. [HTTP/3: 과거, 현재 그리고 미래, HTTP/3 explained, 5G 초연결시대에 웹 HTTP의 대안은 QUIC?]
- QUIC
TCP는 신뢰성을 보장하기 위해 패킷이 손실되면 패킷을 다시 전송하고 목적지를 찾는 동안 전체 TCP 연결이 중단된다.
하지만 QUIC는 UDP 기반이기 때문에 다른 스트림은 독립적으로 이루어지므로 모든 스트림의 전속을 막지 않는다.
TCP Fasct Open과 TLS에서 있던 문제점을 수정, TLS 1.3으로 모두 암호화 되어 있다는 점이 특이하다.
- 기타
이 가이드의 또 다른 재미는 와이파이와 모바일에 대해 다루었다는 점이다.
배터리등을 고려하는 것도 있지만 가장 눈에 띄는 것은 Adaptive HTTP Streaming이었다. [웹 기술로 구현하는 Adaptive HTTP Streaming]
간단히 설명하자면, 여러가지 해상도로 세그먼트를 쪼개 저장해두고 네트워크 상태에 따라 좋은 것과 나쁜것을 적응형으로 골라 스트리밍하도록 한다는 이야기.
넷플릭스와 같은 곳에서 적극적으로 사용하는 것으로 알고 있다.
+.
더 많은 해킹을 하고 싶다면?
카카오톡의 겁나빠른황소의 Loco 프로토콜처럼 직접 서비스 맞춤형 프로토콜을 만들 수 있고,
회사 측은 “패킷 사이즈 경량화와 푸시 시스템구조 최적화, 백엔드 시스템 성능 개선을 통해 3G 네트워크에서도 빠르고 효율적으로 메시지를 전달할 수 있게 됐다”고 설명했다. 카카오는 패킷 사이즈를 최적화한 메시지를 여러 경로로 나누어 처리, 일 6억건에 달하는 메시지를 지연 없이 전송하고 백엔드(후처리) 시스템을 확장해 향후 사용자가 수억 명에 이르더라도 카카오톡을 끊김 없이 사용할 수 있도록 했다.
- 카카오톡, 메시지 전송속도 최고 20배 빨라진다- [KakaoTalk+] LOCO 프로토콜 분석 (1)
- [KakaoTalk+] LOCO 프로토콜 분석 (2)
- [KakaoTalk+] LOCO 프로토콜 분석 (3)
- [KakaoTalk+] LOCO 프로토콜 분석 (4)
Matrix란 채팅 프로토콜의 CoAP+CBOR+Noise+Flate+UDP을 이용한 사례를 살펴보자. [Breaking the 100bps barrier with Matrix, meshsim & coap-proxy(PDF), Modern Cryptography Protocols]
- JSON => CBOR [CBOR, RION vs. Other Formats]
- HTTP => CoAP [Constrained Application Protocol, frame the Noise handshake packets as CoAP]
- TLS => Nosie Pipe [An Introduction to the Noise Protocol Framework, NoiseSocket instroduction]
- Flate를 통한 압축
Matrix에 대해 알아보다보니 IOT 프로토콜들도 나온다.
- 4 Major IoT Protocols — MQTT, CoAP, AMQP, DDS
- MQTT & IoT protocols comparison
- Which IoT protocol should I use for my system?
- CoAP vs HTTP | difference between CoAP and HTTP
JSON 대체 포맷들. 포맷마다 장단점과 구현된 언어들의 차이가 있다.
탈 중앙화와 관련된 네트워크 프로토콜인 IPFS도 재밌다.
1.2.7 API 포인트 최적화
개요
분류: Server, Javascript
방금 등장한 포맷 뿐만 아니라 API 구조도 고민할 수 있다. [GraphQL vs REST vs gRPC, An Architect's guide to APIs: SOAP, REST, GraphQL, and gRPC, APIs REST, GraphQL or gRPC – Who wins this game?, GraphQL: API 소비자에 대한 일관된 접근방식 수립]
크게 다음처럼 Rest, GraphQL, gRPC정도로 구분할 수 있다.
GraphQL은 쿼리를 보내 필요한 데이터만을 가져오기 때문에 과도한 데이터나 부족한 데이터 호출 응답을 방지한다.
dataloader나 batchloader의 경우를 보면 데이터 일괄처리와 캐싱을 통해 성능을 더욱 향상시킬 수 있다.
gRPC의 경우 바이너리를 사용하여 크기가 낮은 편이다.
아직 HTTP3를 지원하지 않는 모양인데, 활용하기 위한 이슈와 글이 있는 듯 하다.
해결방법
이상적인 방법은 GraphQL의 편리함과 데이터 로더 + gRPC처럼 가벼운 포맷이라 생각한다.
다음 글들은 GraphQL과 가벼운 포맷을 사용하려는 시도.
- A Proposal for GraphQL Response Optimization With CBOR, Protocol Buffer, Cap’n Proto or Another Serialization Format
- Microservices in Rust with GraphQL and Cap’n Proto
gRPC의 게이트웨이로 GraphQL을 제공하는 방법도 있는듯하다.
1.3 합치기
대부분 요청을 줄이는 것이 핵심이라 어떻게 보면 줄이기의 연장선상이다.
1.3.1 파일 결합하기
개요
분류: Content
script와 css파일을 합쳐서 요청횟수를 줄이는 것.
스크립트와 스타일 시트가 페이지마다 다를 경우 파일을 결합하는 것이 어렵겠지만 해결한다면 응답시간이 향상될 것이다.
해결방법
이를 가능하게 하는 대표적인 것이 패키지 번들러이다.
패키지 번들러에는 Webpack, Rollup, Parcel이 존재한다.[비교1, 비교2, 비교3]
TOAST UI에서 웹팩 가이드 문서를 쉽고 친절하게 써놓았다.
1.3.2 CSS Sprites
개요
분류: Image
CSS 스프라이트는 이미지 요청 수를 줄이기 위해 위해 나온 기법이다.
배경 이미지들을 단일 이미지로 결합하고, CSS의 background-image와 background-positon속성을 사용해 원하는 이미지 세그먼트를 표시한다.
스프라트를 만들때 몇가지 최적화 방법이 존재한다.
- 스프라이트의 이미지를 세로가 아닌 가로로 정렬하면 파일 크기가 줄어 듭니다.
- 스프라이트에 유사한 색상을 결합하면 PNG8에 맞도록 색상 수를 낮게 (256 색 미만) 유지할 수 있습니다.
- "모바일 친화적"으로 이미지 사이에 큰 간격을 두지 마십시오.
이것은 파일 크기에는 영향을 미치지 않지만 사용자 에이전트가 이미지를 픽셀 맵으로 압축 해제하기 위해 더 적은 메모리를 필요로합니다. 100x100 이미지는 1 만 픽셀이며 1000x1000은 1 백만 픽셀입니다.
NHN의 경우 CSM(CSS Sprites Matrix)라는 가이드라인을 통해 최적화를 한다.[Front-End 최적화의 끝판왕, CSM + Markup Complexity]
- Matrix 배열: 10x10 형태의 이미지 배치, 공간 활용도 향상, 유지보수
- 이미지 개수 제한: 페이지당 2개 이하
- 해상도 제한: 3메가픽셀(1024x1024)
- PNG 포맷
그런데 HTTP/2 에서는 스프라이트가 안티패턴이 될 수 있다는 말이 있어 관련 자료를 찾아보게 되었다. [모바일 환경에서 이미지 스프라이트가 필요할까?, HTTP/2 arrives but sprite sets ain’t no dead]
첫번째 링크에서 요약해놓은 논쟁점은 다음과 같다.
- 단일 이미지가 낫다!
- 스프라이트 이미지는 이용하는 페이지 외의 불필요한 이미지 리소스를 모두 불러온다.
- 스프라이트 이미지는 소수의 이미지 변경에도 모든 이미지의 브라우저 캐시가 무효화된다.
- iOS 웹브라우저의 경우 25kb 이상의 요소는 캐싱하지 않는다.
- Image Sprite를 써야한다!
- PNG 압축 알고리즘이 여러 개의 단일 이미지보다 한장의 스프라이트 이미지에서 더욱 효율적으로 동작한다.
- HTTP/2는 로드 시간이 현저히 감소하지만 HTTP 프로토콜 향상만으로는 front-end 최적화의 유용성을 충분히 대체하기 어렵다.
결론적으로 따지면 HTTP/2에서 로드 시간이 확연히 줄어들긴하나 효과는 있었다.
해결방법
위와 같이 스프라이트화된 이미지를 가지고 네개의 아이콘을 만들고 싶다면 다음과 같이 할 수 있다. [CSS 이미지 스프라이트]
.up, .down, .right, .left { background: url("/examples/images/img_image_sprites.png") no-repeat; } .up { width: 21px; height: 20px; background-position: 0 0; } .down { width: 21px; height: 20px; background-position: -21px 0; } .right { width: 22px; height: 20px; background-position: -42px 0; } .left { width: 22px; height: 20px; background-position: -65px 0; }
CSS Sprites: Image Slicing’s Kiss of Death, CSS로 이미지 스프라이트 구현하기, CSS Tricks
도 잘 설명해주고 있다.
사실 스프라이트의 가장 큰 단점은 스프라이트 이미지를 만들기 불편하고, 계산해서 쓰기 또한 힘들다는 것입니다.
spritesmith(웹팩)가 스프라이트 이미지를 쉽게 만들고 관리할 수 있는 것으로 알려져 있는데 css-sprite-loader(Webpack Plugin)를 사용하면 background-size, background-position을 알아서 계산해서 대입해주며 이미지 크기(@2x, @4x...)별로 대응도 가능하다.
예를들어
.foo { background: url('../assets/gift.png?sprite'); }
이미지 경로 끝에 ?sprite를 넣으면
.foo { background: url(/sprite.png?5d40e339682970eb14baf6110a83ddde) no-repeat; background-position: -100px -0px; }
스프라이트화가 되며 위와 같이 알아서 할당이 되는 구조.
네이버가 만든 플러그인도 찾았다. [image-sprite-webpack-plugin]
SVG의 경우 svg-sprite와 JetBrains의 svg-sprite-loader를 쓸 수 있다.
1.3.3 Image maps
개요
분류: Image
이미지 맵(Image maps)[w3.org]은 클릭 가능한 영역이 여러가지 있는 하나의 이미지이다.
좀 오래되어 보이지만.. 위와 같이 통으로 된 이미지에 각 페이지로 이동하는 링크를 달 때 사용할 수 있다.[Creating HTML Image Maps]
스프라이트처럼 여러 이미지를 단일 이미지로 결합하여 HTTP 요청 수를 줄인다는 아이디어.
그러나 이미지 맵은 페이지에서 메뉴바(Navigation Bar)처럼 이미지가 인접한 경우에만 작동하며 이미지 맵의 좌표를 정의하는 것은 지루하고 오류가 발생하기 쉽고 접근성이 떨어진다.
인접한 이미지들에만 쓰는 것이 좋다지만 메뉴바에 이미지 맵을 사용하는 것도 액세스 할 수 없으므로 권장되지 않는다..
그냥 이런 기법이 있었다는 것만 알고 넘어가는 것이 웬만하면 좋다.
해결방법
Image Map Creator나 다양한 생성기(Image Map Generator, Image map org, image maps com)이 있다.
하지만 화면 크기가 상이한 기기들을 대응하려면 반응형 생성기가 필요하다.(Responsive Image Map Generator1, Responsive Image Map Generator2, Responsive Image Map Creator)
기타 반응형 이미지 맵을 위해 나온 여러 도구들.
Image-Map, Image Map Resize, jQuery RWD Image Maps
1.3.4 인라인 인코딩
개요
분류: Content, Image
인라인 인코딩(Inline Encoding)은 data: URL scheme를 바이너리 표현(Base64)으로 하드코딩하는 것이다.
CSS파일의 크기가 커질 수 있지만 인라인 이미지를 (캐시된) 스타일 시트에 결합하면 HTTP 요청을 줄이고 페이지 크기를 늘리지 않을 수 있다.
단, 스프라이트 하기 힘든 작은 아이콘들에 적용하는 것이 좋다.
이미지는 랜더링을 막지 않지만 CSS의 해석시 렌더링을 막으며 캐시 가능성이 떨어진다. 파일의 크기가 증가하고 항상 이미지를 로드하며, 이미지 프리로드 등의 기술도 못쓴다는 점도 고려대상.[Base64 Encoding & Performance, Base64 인코딩과 성능]
베이스64 인코딩 하기 귀찮은 것도 문제. [Data URIs]
해결방법
NodeJS에서는 Base64 Encoding in Node.js, node-base64-image, base64-img등을 참고바란다.
웹팩의 경우 url-loader를 이용하면 쉽게 만들 수 있다.
다양한 이미지와 아이콘 사용 정리
이쯤되면 도대체 어떠한 상황에 이미지를 어떻게 처리하는지 혼란스러울것 이다.
- 단순한 이미지
아이콘처럼 단순한 이미지는 벡터 그래픽으로 표현한다.
@2x, @4x같이 해상도를 대응할 필요가 없고, 용량도 아낄 수 있다.
벡터 그래픽이라면 SVG와 아이콘 폰트, CSS라는 세가지 선택지가 존재한다.
우선, 가능하면 CSS로 작성하고, SVG와 아이콘 폰트에 대해 고민할 수 있는데..
최근에는 SVG를 사용하는 것을 선호하는 편이다. [Inline SVG vs Icon Fonts, Icon Fonts vs SVGs - Which One Should You Use in 2018?, It’s 2019! Let’s End The Debate On Icon Fonts vs SVG Icons]
React의 경우 SVG를 컴포넌트처럼 다룰 수 있기도 하고.
- 작은 이미지
10k 미만으로 작으면서 항상 불러와야 하는 이미지라면 Base64로 인코딩해서 사용하는 것도 괜찮다.
- 기타 정적 이미지
다양한 색상의 이미지들을 항상 불러와야 한다면 Image Sprite로 묶는다.
(컨텐츠 이미지는 제외)
- 압축과 포맷
가능하면 이미지를 압축하여 사용하도록 한다.
움직이는 GIF는 ffmpeg를 이용해 영상으로 변환하고 나머지 일반 이미지는 WebP를 사용하면 좋다.
1.3.5 다중문서에 구성요소들을 묶기
개요
분류: Mobile
이메일의 첨부파일과 같이 다중문서(Multipart Document)에 구성요소들을 묶는 것은 한 개의 HTTP 요청에 여러 개의 구성요소들을 불러오는데 도움이 된다.HTTP 요청들이 비용(시간&자원)이 발생하는 것을 막기 위해서다.
사용자가 이 기술을 사용할 때, 사용자 에이젼트(아이폰은 지원하지 않음)가 이러한 기능을 지원하는지 먼저 확인하는 것이 필요.
YDN에서 다중문서라 하길래 도대체 무엇인가 하니..
MHTML을 뜻한다구.
일반 웹문서가 HTML코드와 참조하는 별도 파일들(이미지, 음성, 영상)으로 구성된다면 MHTML은 MIME 포맷으로 인코딩해 합쳐진 것이 특징.
현재 의미있는 기술은 아니다.
1.4 나누기
크기가 크거나 모듈화나 캐싱이 가능한 것은 나누는 것이 좋다.
1.4.1 구성요소들을 도메인별로 분리
개요
분류: Content
DNS 요청을 줄이라는 원칙에서 언급했듯 요소들을 분할하는 것은 병렬 다운로드를 극대화할 수 있다.(HTTP 1.1 기준 한 도메인에서 6개가 최대)
DNS 조회의 불이익 때문에 2-4개 이상의 도메인을 사용하고 있지는 않은지 확인하는 것이 핵심.
예를 들어, www.example.org에서 HTML과 동적 콘텐츠를 호스트할 수 있고, static1.example.org와 static2.example.org 사이의 정적 구성 요소를 분할할 수 있다.
자세한 내용은 Tenni Theurer와 Patty Chi의 "Maximizing Parallel Downloads in the Carpool Lane"를 참조해라.해결방법
만약 1개의 도메인에서 모두 다 서비스하고 있다면 라이브러리는 CDN을 사용해보는 것이 어떨까?
물론 HTTP 1.1까지의 이야기.
1.4.2 JavaScript와 CSS를 외부로 두기
개요
분류: Javascript, CSS
이러한 성능 규칙 중 많은 부분이 외부 컴포넌트 관리 방법을 다룬다.그러나 이러한 고려 사항이 발생하기 전에 떠오르는 기본적인 질문이 있다.
JavaScript 및 CSS가 외부 파일에 포함되어야 할까? 아니면 페이지 자체에 인라인되는 것이 좋을까?
실제 환경에서 외부 파일을 사용하면 JavaScript 및 CSS 파일이 브라우저에 의해 캐시되므로 일반적으로 더 빠른 페이지를 생성한다.
HTML 문서에 인라인 된 JavaScript 및 CSS는 HTML 문서가 요청 될 때마다 다운로드된다.
이렇게하면 필요한 HTTP 요청 수가 줄어들지만 HTML 문서의 크기는 커진다.
반면, JavaScript 및 CSS가 브라우저에 의해 캐시 된 외부 파일에 있으면 HTTP 요청 수를 늘리지 않고 HTML 문서의 크기가 줄어든다.
인라인 인코딩등에서도 계속 고민해왔던 내용이라는 것을 알 수 있으며 접근방법도 동일하다.
따라서 핵심 요소는 요청 된 HTML 문서 수와 관련하여 외부 JavaScript 및 CSS 구성 요소가 캐시되는 빈도이다.
이 요소는 정량화하기는 어렵지만 다양한 메트릭을 사용하여 측정 할 수 있다.
사이트의 사용자가 세션 당 여러 페이지보기를 가지고 있고 많은 페이지가 동일한 스크립트 및 스타일 시트를 재사용하는 경우 캐시 된 외부 파일의 잠재적 이점이 더 크다.
많은 웹 사이트가 이러한 메트릭의 중간에 있다.
이러한 사이트의 경우 가장 좋은 해결책은 일반적으로 JavaScript 및 CSS를 외부 파일로 배포하는 것이다.
인라인을 선호하는 유일한 예외는 Yahoo! 의 첫 페이지 및 My Yahoo! 처럼 세션 당 페이지 뷰가 거의없는 (아마도 하나만) 홈 페이지는 JavaScript 및 CSS를 인라인하면 최종 사용자 응답 시간이 더 빨라질 수 있다.
일반적으로 많은 페이지뷰 중 첫 번째인 프론트 페이지에는 인라인이 제공하는 HTTP 요청 감소와 외부 파일을 사용하여 얻을 수있는 캐싱 이점을 활용하는 기술이 있다.
이러한 기술 중 하나는 프론트 페이지에서 JavaScript 및 CSS를 인라인하는 것이지만, 페이지 로드가 완료된 후 동적으로 외부 파일을 다운로드하는 것이다.
후속 페이지는 이미 브라우저 캐시에 있는 외부 파일을 참조한다.
해결방법
특수한 경우가 아니라면 외부에서 불러오도록 분리하자.
1.4.3 이미지 타일링
개요
분류: Image
지도처럼 불러와야 할 이미지가 매우 크다면 타일링을 할 수도 있다.
댓글