[Code Quality] Lighthouse CI

2023-10-17 hit count image

Google Chrome에서 제공하는 Lighthouse 기능을 로컬 또는 CI 환경에서 실행하는 방법에 대해서 알아보고, CI 환경에서 Lighthouse를 사용하여 웹 페이지의 성능을 자동으로 측정하도록 만드는 방법에 대해서 알아봅시다.

개요

GoogleChrome 브라우저는 Lighthouse라는 기능을 제공하며, 이를 통해 웹 앱 또는 웹 페이지의 성능을 측정할 수 있습니다.

Lighthouse CI - Google Chrome Lighthouse

Lighthouse는 웹 앱 또는 웹 페이지를 분석하여, 웹 사이트의 성능과 모범 사례(Best practices)를 확인할 수 있게 도와주는 툴입니다.

이번 블로그 포스트에서는 로컬 또는 CI 환경에서 Lighthouse를 실행하는 방법에 대해서 알아보겠습니다.

Lighthouse CI

Lighthouse CICI(Continuous Integration) 환경에서 Lighthouse의 실행과 결과를 확인할 수 있도록 도와주는 툴 입니다.

Lighthouse CI를 로컬 또는 CI 환경에서 실행시키기 위해서는 NodeJS16 버전 이상의 환경이 구성되어있어야 합니다.

Lighthouse CI 설치

NodeJS 환경 위에서 다음 명령어를 실행하여 Lighthouse CI를 설치합니다.

npm install --save-dev @lhci/cli

package.json 파일이 없는 경우, 다음 명령어를 실행하여 전역 환경에 Lighthouse CI를 설치합니다.

npm install -g @lhci/cli

Lighthouse CI 설정

Lighthouse CI를 사용하기 위해서는 Lighthouse를 설정할 필요가 있습니다. 프로젝트 루트 폴더에 .lighthouserc.js 파일을 만들고 다음과 같이 수정합니다.

module.exports = {
  ci: {
    assert: {
      preset: 'lighthouse:recommended',
    },
  },
};

이렇게 설정하면 Lighthouse가 제공하는 추천 설정을 그대로 사용할 수 있습니다.

대상 설정

이제 Lighthouse가 검사할 대상을 설정해야 합니다. 검사 대상은 정적 파일이나 URL을 설정할 수 있습니다.

정적 파일

정적 파일은 개발중인 웹 사이트나 웹 페이지의 빌드 결과를 검사하는 방법입니다. 정적 파일을 검사하기 위해서는 Lighthouse의 설정 파일인 .lighthouserc.js 파일을 열고 다음과 같이 수정합니다.

module.exports = {
  ci: {
    collect: {
      staticDistDir: '_site/',
    },
    assert: {
      preset: 'lighthouse:recommended',
    },
  },
};

저는 Jekyll을 사용하여 블로그를 관리하고 있습니다. Jekyllbundle exec jekyll build라는 명령어를 사용하여 Markdown으로 작성한 블로그 포스트들을 HTML 파일로 빌드할 수 있습니다. 이렇게 빌드된 파일들은 _site라는 폴더에 생성됩니다.

이렇게 생성된 정적 파일들의 폴더를 collectstaticDistDir 옵션에 설정함으로써, Lighthosue가 검사할 대상을 설정할 수 있습니다.

특정 URL 설정

정적 파일을 생성할 수 없는 경우, 검사 대상이 되는 페이지의 URL을 입력하여 Lighthouse를 실행할 수 있습니다. 특정 URL을 검사하기 위해서는 Lighthouse의 설정 파일인 .lighthouserc.js 파일을 열고 다음과 같이 수정합니다.

module.exports = {
  ci: {
    collect: {
      url: [
        // Korean
        'https://deku.posstree.com/ko/flutter/',
        'https://deku.posstree.com/ko/react/',
        'https://deku.posstree.com/ko/react-native/',
        // English
        'https://deku.posstree.com/en/flutter/',
        'https://deku.posstree.com/en/react/',
        'https://deku.posstree.com/en/react-native/',
        // Japanese
        'https://deku.posstree.com/flutter/',
        'https://deku.posstree.com/react/',
        'https://deku.posstree.com/react-native/'
      ]
    },
    assert: {
      preset: 'lighthouse:recommended',
    },
  },
};

이와같이 설정하면, Lighthouse는 해당 URL들을 검사하게 됩니다.

하위 폴더 문제

staticDistDir을 사용하여 폴더를 지정하였지만, 지정한 폴더의 하위 폴더에 있는 파일들을 검사하지 않는 문제가 발생하였습니다. 저는 이를 해결하기 위해 sitemap.xml을 활용하였습니다.

JavaScript에서 sitemap.xml 파일을 읽기 위해 xml-js 라이브러리를 사용하였습니다.

그럼 xml-js 라이브러리를 설치하기 위해 다음 명령어를 실행합니다.

npm install --save-dev xml-js

그리고 .lighthouserc.js 파일을 열고 다음과 같이 수정합니다.

const fs = require('fs');
const convert = require('xml-js');

const SITEMAP_PATH = './_site/sitemap.xml';
const SITE_URL = 'https://deku.posstree.com';

const data = fs.readFileSync(SITEMAP_PATH);
const result = convert.xml2json(data, { compact: true, spaces: 4 });
const json = JSON.parse(result);
const url = json.urlset.url.map((item) => item.loc._text.replace(SITE_URL, ''));

module.exports = {
  ci: {
    collect: {
      staticDistDir: './_site/',
      url: url,
    },
    assert: {
      preset: 'lighthouse:recommended',
    },
  },
};

SITEMAP_PATHSITE_URL을 자신에 프로젝트에 맞게 수정하여 사용하시기 바랍니다. 이렇게 파일을 수정하고 실행하면 sitemap.xml에 작성된 URL을 읽어와 SITE_URL을 사용하여 불필요한 URL을 제거한 후, 해당 URL을 Lighthouse의 옵션에 설정함으로써, Lighthouse가 검사할 수 있도록 만들었습니다.

이렇게 .lighthouserc.js 파일을 수정하고 Lighthouse를 실행하면, sitemap.xml에 기록된 모든 페이지가 잘 검사되는 것을 확인할 수 있습니다.

Lighthouse 실행

이제 Lighthouse를 사용하기 위한 모든 설정이 끝났습니다. 이제 Lighthouse를 실행하기 위해서 package.json 파일을 열고 다음과 같이 실행 스크립트를 추가합니다.

{
  "scripts": {
    "lighthouse": "lhci autorun"
  },
  "devDependencies": {
    "@lhci/cli": "^0.11.0"
  }
}

그런 다음, 다음 명령어를 실행하여 Lighthouse를 실행해 봅니다.

npm run lighthouse

package.json 파일이 없는 경우, 다음과 같이 Lighthouse 명령어를 직접 실행합니다.

lhci autorun
# npx lhci autorun

문제없이 실행되었다면 .lighthouseci 폴더가 생성되고 Lighthouse의 결과가 저장되는 것을 확인할 수 있습니다. 또한, 터미널에서 다음과 같이 Lighthouse의 결과가 표시되는 것을 확인할 수 있습니다.

...
  ✘  viewport failure for minScore assertion
       Does not have a `<meta name="viewport">` tag with `width` or `initial-scale`
       https://web.dev/viewport/

        expected: >=0.9
           found: 0
      all values: 0, 0, 0

이 터미널 결과를 보면서 문제를 수정하거나, .lighthouseci 폴더 안에 생성된 결과 파일(.html)을 브라우저에서 열어 문제를 확인하여 수정할 수 있습니다.

CI 환경에서 실행

이렇게 설정한 LighthosueCI 환경에서 실행하여 성능을 측정할 수 있습니다.

GitHub Actions

GitHub Actions에서 Lighthouse를 실행하는 방법에 대해서 알아봅시다. GitHub Actions를 사용하여 Lighthouse를 실행하기 위해서는 .github/workflows/ci.yml 파일을 만들고 다음과 같이 수정합니다.

name: CI
on: [push]
jobs:
  lighthouseci:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-node@v3
        with:
          node-version: 16
      - run: npm install
      - run: npm run lighthouse

package.json 파일이 없는 경우 npm install 부분과 npm run lighthouse 부분을 다음과 같이 수정합니다.

- run: npm install -g @lhci/cli
- run: lhci autorun

만약, 정적 파일들을 검사하도록 Lighthouse를 설정한 경우, npm run lighthouse 또는 lhci autorun 명령어를 실행하기 전에 반드시 정적 파일들을 생성하는 명령어를 추가하시기 바랍니다.

GitLab CI

GitLab CI에서 Lighthosue를 실행하기 위해서 .gitlab-ci.yml 파일을 만들고 다음과 같이 수정합니다.

lighthouse:
  image: node:16-slim
  script:
    - apt-get update
    - apt-get install -y libgtk-3.0 libgbm-dev libnss3 libatk-bridge2.0-0 libasound2
    - npm install --global npm@latest
    - npm install
    - npm run lighthouse
  only:
    refs:
      - merge_requests

package.json 파일이 없는 경우 npm install 부분과 npm run lighthouse 부분을 다음과 같이 수정합니다.

- npm install -g @lhci/cli
- lhci autorun

만약, 정적 파일들을 검사하도록 Lighthouse를 설정한 경우, npm run lighthouse 또는 lhci autorun 명령어를 실행하기 전에 반드시 정적 파일들을 생성하는 명령어를 추가하시기 바랍니다.

Basic authentication

Lighthouse를 사용하여 URL로 웹 페이지를 검사하도록 설정한 때, Basic authentication이 설정된 웹 페이지를 검사해야할 경우가 있습니다.

Lighthouse를 사용하여 Basic authentication이 설정된 웹 페이지를 검사하기 위해서는 몇가지 추가적인 설정을 할 필요가 있습니다. 우선, 다음 명령어를 사용하여 puppeteer을 설치합니다.

npm install --save-dev puppeteer

그런 다음, 프로젝트의 루트 폴더에 puppeteerScript.js 파일을 생성하고 다음과 같이 수정합니다.

const BASIC_AUTH_USER_NAME = process.env.BASIC_AUTH_USER_NAME;
const BASIC_AUTH_PASSWORD = process.env.BASIC_AUTH_PASSWORD;

async function main(browser, { url }) {
  const page = await browser.newPage();
  await page.authenticate({
    username: BASIC_AUTH_USER_NAME,
    password: BASIC_AUTH_PASSWORD,
  });
  await page.goto(url);
}

module.exports = main;

환경 변수 BASIC_AUTH_USER_NAME, BASIC_AUTH_PASSWORDBasic authentication의 아이디와 암호를 저장합니다. 그런 다음, .lighthouserc.js 파일을 열고 다음과 같이 puppeteer을 설정합니다.

module.exports = {
  ci: {
    collect: {
      url: [
        // Korean
        'https://deku.posstree.com/ko/flutter/',
        'https://deku.posstree.com/ko/react/',
        'https://deku.posstree.com/ko/react-native/',
        // English
        'https://deku.posstree.com/en/flutter/',
        'https://deku.posstree.com/en/react/',
        'https://deku.posstree.com/en/react-native/',
        // Japanese
        'https://deku.posstree.com/flutter/',
        'https://deku.posstree.com/react/',
        'https://deku.posstree.com/react-native/'
      ]
    },
    puppeteerScript: './puppeteerScript.js',
    puppeteerLaunchOptions: {
      defaultViewport: null,
      args: ['--no-sandbox'],
      headless: true,
    },
    assert: {
      preset: 'lighthouse:recommended',
    },
  },
};

이와같이 수정한 후, Lighthouse를 실행하면 문제없이 Basic authentication가 설정된 웹 페이지가 잘 검사되는 것을 확인할 수 있습니다.

완료

이것으로 Lighthouse CI를 사용하여 로컬 또는 CI 환경에서 Lighthouse를 실행하는 방법에 대해서 알아보았습니다. 빠른 웹 페이지 또는 웹 앱은 사용자 경험(UX)에 큰 영향을 줍니다. 또한, Lighthouse에서 측정하는 항목들은 SEO에도 큰 영향을 주므로, 여러분도 Lighthouse를 사용하여 관리중인 웹 페이지 또는 웹 앱의 성능을 개선해 보시기 바랍니다.

제 블로그가 도움이 되셨나요? 하단의 댓글을 달아주시면 저에게 큰 힘이 됩니다!

앱 홍보

책 홍보

블로그를 운영하면서 좋은 기회가 생겨 책을 출판하게 되었습니다.

아래 링크를 통해 제가 쓴 책을 구매하실 수 있습니다.
많은 분들에게 도움이 되면 좋겠네요.

스무디 한 잔 마시며 끝내는 React Native, 비제이퍼블릭
스무디 한 잔 마시며 끝내는 리액트 + TDD, 비제이퍼블릭
[심통]현장에서 바로 써먹는 리액트 with 타입스크립트 : 리액트와 스토리북으로 배우는 컴포넌트 주도 개발, 심통
Posts