brotli로 네트워크 페이로드 축소 및 압축

Michael DiBlasio
Michael DiBlasio

이 Codelab은 네트워크 페이로드 축소 및 압축 Codelab의 확장 프로그램이며 사용자가 압축의 기본 개념을 잘 알고 있다고 가정합니다. 이 Codelab에서는 gzip와 같은 다른 압축 알고리즘과 비교하여 Brotli 압축(br)이 압축률과 앱의 전체 크기를 더욱 줄일 수 있는 방법을 살펴봅니다.

앱 스크린샷

측정

최적화를 추가하기 전에 항상 먼저 애플리케이션의 현재 상태를 분석하는 것이 좋습니다.

  1. 리믹스하여 수정을 클릭하여 프로젝트를 수정할 수 있도록 합니다.
  2. 사이트를 미리 보려면 앱 보기를 누른 다음 전체 화면전체 화면을 누릅니다.

이전 네트워크 페이로드 축소 및 압축 Codelab에서는 main.js의 크기를 225KB에서 61.6KB로 줄였습니다. 이 Codelab에서는 Brotli 압축을 사용하여 이 번들 크기를 더 줄이는 방법을 알아봅니다.

Brotli 압축

Brotligzip보다 더 나은 텍스트 압축 결과를 제공할 수 있는 최신 압축 알고리즘입니다. CertSimple에 따르면 Brotli 성능은 다음과 같습니다.

  • JavaScript의 gzip보다 14% 작음
  • HTML용 gzip보다 21% 작음
  • CSS의 gzip보다 17% 작음

Brotli를 사용하려면 서버에서 HTTPS를 지원해야 합니다. Brotli는 모든 최신 브라우저에서 지원됩니다. Brotli를 지원하는 브라우저에는 Accept-Encoding 헤더에 br가 포함됩니다.

Accept-Encoding: gzip, deflate, br

Chrome 개발자 도구 네트워크 탭(Command+Option+I 또는 Ctrl+Alt+I)의 Content-Encoding 필드를 사용하여 어떤 압축 알고리즘이 사용되는지 확인할 수 있습니다.

Network 패널 콘텐츠 인코딩 열에는 gzip 및 brotli (br)를 비롯하여 다양한 애셋에 사용된 인코딩이 표시됩니다.

Brotli 사용 설정 방법

Brotli 인코딩 리소스를 보내도록 웹 서버를 설정하는 방법은 인코딩 방법에 따라 다릅니다. 가능한 방법은 요청 시 Brotli를 사용하여 리소스를 동적으로 압축하거나 (동적) 미리 인코딩하여 사용자가 요청할 때 이미 압축되도록 (정적)하는 것입니다.

동적 압축

동적 압축에서는 브라우저에서 요청할 때 애셋을 즉석에서 압축합니다.

장점

  • 저장된 압축된 버전의 확장 소재를 만들고 업데이트할 필요는 없습니다.
  • 실시간 압축은 동적으로 생성되는 웹페이지에 특히 적합합니다.

단점

  • 더 나은 압축률을 얻기 위해 더 높은 수준에서 파일을 압축하는 작업은 더 오래 걸립니다. 이렇게 하면 서버에서 애셋을 전송하기 전에 사용자가 애셋이 압축될 때까지 기다리므로 성능이 저하될 수 있습니다.

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/directory에 있는 모든 정적 HTML, JS, CSS 파일을 로드합니다. 이러한 파일은 빌드할 때마다 webpack에서 만듭니다.

모든 애셋이 요청될 때마다 brotli를 사용하여 압축되도록 하려면 shrink-ray 모듈을 사용하면 됩니다. 먼저 package.jsondevDependency로 추가합니다.

"devDependencies": {
  // ...
  "shrink-ray": "^0.1.3"
},

서버 파일 server.js로 가져옵니다.

const express = require('express');
const shrinkRay = require('shrink-ray');

express.static가 마운트되기 전에 미들웨어로 추가합니다.

// ...
const app = express();

// Compress all requests
app.use(shrinkRay());
app.use(express.static('public'));

이제 앱을 새로고침하고 Network 패널에서 번들 크기를 확인합니다.

동적 Brotli 압축을 사용한 번들 크기입니다.

이제 Content-Encoding 헤더에서 bz에서 brotli가 적용된 것을 볼 수 있습니다. main.bundle.js225KB에서 53.1KB로 줄었습니다. 이는 gzip(61.6KB)에 비해 약 14% 작습니다.

정적 압축

정적 압축의 기본 아이디어는 애셋을 미리 압축하고 저장하는 것입니다.

장점

  • 높은 압축 수준으로 인한 지연 시간은 더 이상 문제가 되지 않습니다. 이제 파일을 직접 가져올 수 있으므로 파일을 압축하기 위해 실시간으로 실행할 필요가 없습니다.

단점

  • 애셋은 빌드할 때마다 압축해야 합니다. 높은 압축 수준을 사용하면 빌드 시간이 크게 늘어날 수 있습니다.

Webpack을 사용한 Node 및 Express의 정적 압축

정적 압축에는 파일을 미리 압축하는 작업이 포함되므로 빌드 단계의 일부로 애셋을 압축하도록 webpack 설정을 수정할 수 있습니다. 이를 위해 brotli-webpack-plugin를 사용할 수 있습니다.

먼저 package.jsondevDependency로 추가합니다.

"devDependencies": {
  // ...
 "brotli-webpack-plugin": "^1.1.0"
},

다른 webpack 플러그인과 마찬가지로 구성 파일 webpack.config.js에서 가져옵니다.

var path = require("path");

//...
var BrotliPlugin = require('brotli-webpack-plugin');

또한 플러그인 배열 내에 포함합니다.

module.exports = {
  // ...
  plugins: [
    // ...
    new BrotliPlugin({
      asset: '[file].br',
      test: /\.(js)$/
    })
  ]
},

플러그인 배열은 다음 인수를 사용합니다.

  • asset: 대상 애셋 이름입니다.
  • [file]은 원래 애셋 파일 이름으로 대체됩니다.
  • test: 이 RegExp와 일치하는 모든 애셋 (즉, .js로 끝나는 자바스크립트 애셋)이 처리됩니다.

예를 들어 main.jsmain.js.br로 바뀝습니다.

앱이 새로고침되고 다시 빌드되면 이제 압축된 버전의 기본 번들이 생성됩니다. Glitch Console을 열어 Node 서버에서 제공하는 최종 public/ 디렉터리 내의 항목을 확인합니다.

  1. 도구 버튼을 클릭합니다.
  2. 콘솔 버튼을 클릭합니다.
  3. 콘솔에서 다음 명령어를 실행하여 public 디렉터리로 변경하고 모든 파일을 확인합니다.
cd public
ls -lh
정적 Brotli 압축을 사용한 번들 크기

이제 번들의 brotli 압축 버전인 main.bundle.js.br도 여기에 저장되며 main.bundle.js보다 크기가 76% 작습니다(225KB 대 53KB).

그런 다음 원본 JS 버전이 요청될 때마다 이러한 brotli 압축 파일을 전송하도록 서버에 지시합니다. 파일이 express.static로 제공되기 전에 server.js에서 새 경로를 정의하면 됩니다.

const express = require('express');
const app = express();

app.get('*.js', (req, res, next) => {
  req.url = req.url + '.br';
  res.set('Content-Encoding', 'br');
  res.set('Content-Type', 'application/javascript; charset=UTF-8');
  next();
});

app.use(express.static('public'));

app.get는 특정 엔드포인트에 대한 GET 요청에 응답하는 방법을 서버에 알리는 데 사용됩니다. 그런 다음 콜백 함수가 이 요청을 처리하는 방법을 정의하는 데 사용됩니다. 경로는 다음과 같이 작동합니다.

  • '*.js'를 첫 번째 인수로 지정하면 JS 파일을 가져오기 위해 실행되는 모든 엔드포인트에 작동합니다.
  • 콜백 내에서 .br는 요청의 URL에 연결되고 Content-Encoding 응답 헤더는 br로 설정됩니다.
  • MIME 유형을 지정하기 위해 Content-Type 헤더가 application/javascript; charset=UTF-8로 설정됩니다.
  • 마지막으로 next()는 시퀀스가 다음에 있을 수 있는 콜백으로 계속 이어지도록 합니다.

일부 브라우저에서는 brotli 압축을 지원하지 않을 수 있으므로 brotli 압축된 파일을 반환하기 전에 Accept-Encoding 요청 헤더에 br가 포함되어 있는지 확인하여 brotli가 지원되는지 확인합니다.

const express = require('express');
const app = express();

app.get('*.js', (req, res, next) => {
  if (req.header('Accept-Encoding').includes('br')) {
    req.url = req.url + '.br';
    console.log(req.header('Accept-Encoding'));
    res.set('Content-Encoding', 'br');
    res.set('Content-Type', 'application/javascript; charset=UTF-8');
  }

  next();
});

app.use(express.static('public'));

앱이 새로고침되면 네트워크 패널을 다시 살펴봅니다.

번들 크기 53.1KB (이전 길이 225KB)

완료되었습니다. Brotli 압축을 사용하여 애셋을 추가로 압축했습니다.

결론

이 Codelab에서는 brotli를 통해 앱의 전체 크기를 더 줄이는 방법을 설명했습니다. 지원되는 경우 brotligzip보다 강력한 압축 알고리즘입니다.