이 Codelab에서는 다음 애플리케이션의 JavaScript 번들을 축소 및 압축하는 것이 앱의 요청 크기를 줄여 페이지 성능을 개선하는 방법을 살펴봅니다.
측정
최적화를 추가하기 전에 항상 먼저 애플리케이션의 현재 상태를 분석하는 것이 좋습니다.
- 사이트를 미리 보려면 앱 보기를 누른 다음 전체 화면을 누릅니다.
'사용하지 않는 코드 삭제' Codelab에서도 다룬 이 앱을 사용하면 좋아하는 고양이에게 투표할 수 있습니다. 🐈
이제 이 애플리케이션의 크기를 살펴보겠습니다.
- `Control+Shift+J`(Mac의 경우 `Command+Option+J`)를 눌러 DevTools를 엽니다.
- 네트워크 탭을 클릭합니다.
- 캐시 사용 중지 체크박스를 선택합니다.
- 앱을 새로고침합니다.
'사용하지 않는 코드 삭제' Codelab에서는 이 번들 크기를 줄이기 위해 많은 노력을 기울였지만 225KB는 여전히 상당히 큽니다.
축소
다음 코드 블록을 살펴보세요.
function soNice() {
let counter = 0;
while (counter < 100) {
console.log('nice');
counter++;
}
}
이 함수가 자체 파일에 저장된 경우 파일 크기는 약 112B (바이트)입니다.
공백을 모두 삭제하면 결과 코드는 다음과 같습니다.
function soNice(){let counter=0;while(counter<100){console.log("nice");counter++;}}
이제 파일 크기가 약 83바이트가 됩니다. 변수 이름의 길이를 줄이고 일부 표현식을 수정하여 더 손상되면 최종 코드는 다음과 같이 표시될 수 있습니다.
function soNice(){for(let i=0;i<100;)console.log("nice"),i++}
이제 파일 크기가 62바이트가 됩니다.
단계가 거듭될수록 코드를 읽기가 더 어려워집니다. 하지만 브라우저의 JavaScript 엔진은 이를 모두 동일한 방식으로 해석합니다. 이 방식으로 코드를 난독화하면 파일 크기를 줄이는 데 도움이 됩니다. 112바이트는 원래부터 그리 큰 크기가 아니었지만 크기가 50% 줄었습니다.
이 애플리케이션에서는 webpack 버전 4가 모듈 번들러로 사용됩니다. 구체적인 버전은 package.json
에서 확인할 수 있습니다.
"devDependencies": {
//...
"webpack": "^4.16.4",
//...
}
버전 4는 이미 프로덕션 모드에서 기본적으로 번들을 축소합니다. Terser용 플러그인인 TerserWebpackPlugin
를 사용합니다.
Terser는 JavaScript 코드를 압축하는 데 사용되는 인기 도구입니다.
축소된 코드가 어떻게 표시되는지 알아보려면 DevTools 네트워크 패널에 있는 동안 main.bundle.js
를 클릭합니다. 이제 Response 탭을 클릭합니다.
최소화되고 망가진 최종 형식의 코드가 응답 본문에 표시됩니다.
번들이 축소되지 않은 경우 번들의 크기를 확인하려면 webpack.config.js
를 열고 mode
구성을 업데이트합니다.
module.exports = {
mode: 'production',
mode: 'none',
//...
애플리케이션을 새로고침하고 DevTools Network 패널을 통해 번들 크기를 다시 확인합니다.
상당한 차이가 있습니다. 😅
계속하기 전에 여기에서 변경사항을 되돌리세요.
module.exports = {
mode: 'production',
mode: 'none',
//...
애플리케이션에 코드를 축소하는 프로세스를 포함하는 방법은 사용하는 도구에 따라 다릅니다.
- webpack v4 이상을 사용하는 경우 프로덕션 모드에서 기본적으로 코드가 축소되므로 추가 작업이 필요하지 않습니다. 👍
- 이전 버전의 webpack을 사용하는 경우
TerserWebpackPlugin
를 설치하고 webpack 빌드 프로세스에 포함합니다. 자세한 내용은 문서를 참고하세요. - BabelMinifyWebpackPlugin 및 ClosureCompilerPlugin과 같은 다른 축소 플러그인도 있으며 대신 사용할 수 있습니다.
- 모듈 번들러가 전혀 사용되지 않는 경우 Terser를 CLI 도구로 사용하거나 종속 항목으로 직접 포함합니다.
압축
'압축'이라는 용어는 축소 프로세스 중에 코드가 축소되는 방식을 설명하는 데 가끔 느슨하게 사용되지만 말 그대로 압축되지는 않습니다.
압축은 일반적으로 데이터 압축 알고리즘을 사용하여 수정된 코드를 나타냅니다. 완전히 유효한 코드를 제공하는 축소와 달리 압축된 코드는 사용하기 전에 압축을 풀어야 합니다.
브라우저와 웹 서버는 모든 HTTP 요청과 응답에서 헤더를 추가하여 가져오거나 수신되는 애셋에 대한 추가 정보를 포함할 수 있습니다. 이는 DevTools 네트워크 패널 내의 Headers
탭에서 확인할 수 있으며, 여기서는 세 가지 유형이 표시됩니다.
- 일반은 전체 요청-응답 상호작용과 관련된 일반 헤더를 나타냅니다.
- 응답 헤더는 서버의 실제 응답과 관련된 헤더 목록을 보여줍니다.
- 요청 헤더에는 클라이언트가 요청에 연결한 헤더 목록이 표시됩니다.
Request Headers
의 accept-encoding
헤더를 살펴보세요.
accept-encoding
는 브라우저에서 지원하는 콘텐츠 인코딩 형식 또는 압축 알고리즘을 지정하는 데 사용됩니다. 텍스트 압축 알고리즘은 많지만 HTTP 네트워크 요청의 압축(및 압축 해제)을 위해 여기에서 지원되는 알고리즘은 세 가지뿐입니다.
- Gzip (
gzip
): 서버와 클라이언트 상호작용에 가장 널리 사용되는 압축 형식입니다. Deflate 알고리즘을 기반으로 빌드되며 모든 최신 브라우저에서 지원됩니다. - Deflate (
deflate
): 일반적으로 사용되지 않습니다. - Brotli (
br
): 압축비를 더욱 개선하여 페이지 로드 속도를 높이는 것을 목표로 하는 최신 압축 알고리즘입니다. 대부분의 브라우저의 최신 버전에서 지원됩니다.
이 튜토리얼의 샘플 애플리케이션은 이제 Express가 서버 프레임워크로 사용된다는 점을 제외하면 '사용하지 않는 코드 삭제' Codelab에서 완료한 앱과 동일합니다. 다음 몇 섹션에서는 정적 압축과 동적 압축을 모두 살펴봅니다.
동적 압축
동적 압축은 브라우저에서 요청할 때 확장 소재를 즉시 압축하는 것을 말합니다.
장점
- 저장된 압축된 버전의 확장 소재를 만들고 업데이트할 필요는 없습니다.
- 즉석 압축은 동적으로 생성된 웹페이지에서 특히 효과적입니다.
단점
- 더 나은 압축 비율을 달성하기 위해 더 높은 수준에서 파일을 압축하면 시간이 더 오래 걸립니다. 이렇게 하면 서버에서 애셋을 전송하기 전에 사용자가 애셋이 압축될 때까지 기다리므로 성능이 저하될 수 있습니다.
Node/Express를 사용한 동적 압축
server.js
파일은 애플리케이션을 호스팅하는 노드 서버를 설정합니다.
const express = require('express');
const app = express();
app.use(express.static('public'));
const listener = app.listen(process.env.PORT, function() {
console.log('Your app is listening on port ' + listener.address().port);
});
현재 이 코드는 express
를 가져오고 express.static
미들웨어를 사용하여 public/
디렉터리의 모든 정적 HTML, JS, CSS 파일을 로드합니다. 이러한 파일은 빌드할 때마다 webpack에서 만듭니다.
모든 애셋이 요청될 때마다 압축되도록 하려면 압축 미들웨어 라이브러리를 사용할 수 있습니다. 먼저 package.json
에 devDependency
로 추가합니다.
"devDependencies": {
//...
"compression": "^1.7.3"
},
그런 다음 서버 파일 server.js
로 가져옵니다.
const express = require('express');
const compression = require('compression');
express.static
가 마운트되기 전에 미들웨어로 추가합니다.
//...
const app = express();
app.use(compression());
app.use(express.static('public'));
//...
이제 앱을 새로고침하고 네트워크 패널에서 번들 크기를 확인합니다.
225KB에서 61.6KB로 줄었습니다. 이제 Response Headers
에서 content-encoding
헤더는 서버가 gzip
로 인코딩된 이 파일을 전송하고 있음을 보여줍니다.
정적 압축
정적 압축의 기본 개념은 애셋을 미리 압축하여 저장하는 것입니다.
장점
- 높은 압축 수준으로 인한 지연 시간은 더 이상 문제가 되지 않습니다. 이제 파일을 직접 가져올 수 있으므로 파일을 압축하기 위해 실시간으로 아무것도 할 필요가 없습니다.
단점
- 애셋은 빌드할 때마다 압축해야 합니다. 높은 압축 수준을 사용하면 빌드 시간이 크게 증가할 수 있습니다.
Node/Express 및 Webpack을 사용한 정적 압축
정적 압축에는 파일을 미리 압축하는 작업이 포함되므로 빌드 단계의 일부로 애셋을 압축하도록 webpack 설정을 수정할 수 있습니다.
CompressionPlugin
를 사용할 수 있습니다.
먼저 package.json
에 devDependency
로 추가합니다.
"devDependencies": {
//...
"compression-webpack-plugin": "^1.1.11"
},
다른 webpack 플러그인과 마찬가지로 구성 파일 webpack.config.js:
에서 가져옵니다.
const path = require("path");
//...
const CompressionPlugin = require("compression-webpack-plugin");
plugins
배열 내에 포함합니다.
module.exports = {
//...
plugins: [
//...
new CompressionPlugin()
]
}
기본적으로 플러그인은 gzip
를 사용하여 빌드 파일을 압축합니다. 문서에서 다른 알고리즘을 사용하거나 특정 파일을 포함/제외하는 옵션을 추가하는 방법을 알아보세요.
앱이 다시 로드되고 다시 빌드되면 이제 기본 번들의 압축된 버전이 생성됩니다. Glitch Console을 열어 Node 서버에서 제공하는 최종 public/
디렉터리 내의 항목을 확인합니다.
- 도구 버튼을 클릭합니다.
- 콘솔 버튼을 클릭합니다.
- 콘솔에서 다음 명령어를 실행하여
public
디렉터리로 이동하고 모든 파일을 확인합니다.
cd public
ls
번들의 GZIP 버전인 main.bundle.js.gz
도 여기에 저장됩니다. CompressionPlugin
는 기본적으로 index.html
도 압축합니다.
다음으로 해야 할 일은 원래 JS 버전이 요청될 때마다 이러한 GZIP 파일을 전송하도록 서버에 지정하는 것입니다. 파일이 express.static
로 제공되기 전에 server.js
에서 새 경로를 정의하면 됩니다.
const express = require('express'); const app = express(); app.get('*.js', (req, res, next) => { req.url = req.url + '.gz'; res.set('Content-Encoding', 'gzip'); next(); }); app.use(express.static('public')); //...
app.get
는 서버에 특정 엔드포인트의 GET 요청에 응답하는 방법을 알려주는 데 사용됩니다. 그런 다음 콜백 함수가 이 요청을 처리하는 방법을 정의하는 데 사용됩니다. 경로는 다음과 같이 작동합니다.
'*.js'
를 첫 번째 인수로 지정하면 JS 파일을 가져오기 위해 실행되는 모든 엔드포인트에 작동합니다.- 콜백 내에서
.gz
는 요청의 URL에 연결되고Content-Encoding
응답 헤더는gzip
로 설정됩니다. - 마지막으로
next()
는 시퀀스가 다음에 있을 수 있는 콜백으로 계속 이어지도록 합니다.
앱이 새로고침되면 Network
패널을 다시 살펴봅니다.
이전과 마찬가지로 번들 크기가 크게 줄었습니다.
결론
이 Codelab에서는 소스 코드를 축소하고 압축하는 프로세스를 다뤘습니다. 이러한 두 가지 기법은 현재 사용 가능한 많은 도구에서 기본값이 되고 있으므로 도구 모음에서 이미 지원되는지 아니면 두 프로세스를 모두 직접 적용해야 하는지 확인하는 것이 중요합니다.