목록 보기
Lighthouse CI를 알아보고 Github Actions에 적용하기
기타

Lighthouse CI를 알아보고 Github Actions에 적용하기

최근 진행한 프로젝트의 목표 중 하나가 성능 측정 및 최적화였습니다. 먼저, 성능을 최적화하기 위해 성능을 측정하는 도구를 찾다 보니 Lighthouse에 대해 알게 되었습니다. Lighthouse의 보고서는 성능 지표 및 해당 지표에 대한 측정 결과를 자세하게 제공해주었고, 우리는 이를 CI에 적용하여 이러한 정보를 자동으로 생성하고자 하였습니다. 이 글에서는 Lighthouse에 대해 간단히 살펴보고, Lighthouse CI와 Github Actions를 활용하여 성능 측정을 자동화하는 방법에 대해 살펴보겠습니다. Lighthouse Lighthouse의 홈페이지를 살펴보면 Lighthouse는 웹 앱 및 웹 페이지를 분석하여 최신 성능 지표와 개발자의 Best Practice 사례에 대한 인사이트를 제공합니다.라고 나와 있습니다. 어떤 지표를 제공하는지 실제로 Lighthouse를 실행하여 한번 살펴봅시다! create-react-app을 이용해서 리액트 앱을 만든 후, 크롬의 Lighthouse를 이용해서 성능 측정을 해보겠습니다. npx create-react-app lighthouse-cra cd lighthouse-cra npm run start 리액트 앱을 띄우고 크롬 개발자도구의 lighthouse 탭에서 Generate report 버튼을 클릭해 보고서를 생성해보겠습니다. 해당 보고서를 살펴보면, 크게 Performance, Accessibility, Best Practices, SEO, PWA 5개 카테고리의 측정값이 0~100점으로 나타나는 것을 볼 수 있습니다. 그리고 Performance의 상세 성능 지표로 First Contentful Paint, Time to Interactive, Speed Index, Total Blocking Time, LargestContentful Paint, Cumulative Layout Shift를 확인할 수 있습니다. 오른쪽 아래 빨간 박스를 보시면 해당 화면의 렌더링 후 스크린샷도 볼 수 있습니다! 각 카테고리 및 상세성능 지표에 대한 자세한 설명은 여기를 참고해주세요! 브라우저가 아닌 Node 환경에서 Lighthouse를 실행하고 싶다면 여기를 참고해주세요! Lighthouse CI Lighthouse를 간단하게 실행해보고 알아봤습니다. 이 Lighthouse를 CI 와 연동하기 위해서 크롬에서 제공하는 Lighthouse CI를 사용합니다. 또한, Lighthouse CI는 이름부터 알 수 있듯 다양한 CI 툴과 연동할 수 있습니다. 자세한건 여기에서 확인할 수 있습니다. 이번 예시에서는 Github Actions와 연동해보겠습니다. Local 먼저 로컬에서 Lighthouse CI를 실행하기 위해 설치를 진행합니다. npm i -E -g @lhci/cli 루트 디렉터리에 .lighthouserc.js파일을 생성합니다. 해당 파일은 Ligthouse CI의 설정 파일 입니다. 자세한 설정은 여기를 참고해주세요. 설정 파일의 기본 틀은 아래와 같습니다. // @lighthouserc.js module.exports = ( ci: ( collect: ( // collect options here ), assert: ( // assert options here ), upload: ( // upload options here ), server: ( // server options here ), wizard: ( // wizard options here ), ), ) 이번 예시에서 사용할 세팅은 아래와 같습니다. module.exports = ( ci: ( collect: ( staticDistDir: "./build", // startServerCommand: "npm run start", // 서버를 키는 명령어를 통해서도 실행 가능 url: ["http://localhost:3000"], numberOfRuns: 5, ), upload: ( target: "temporary-public-storage", ), ), ) 설정파일에 대해서 간단히 살펴봅시다. staticDistDir : 정적 파일의 경로를 작성합니다. Lighthouse CI는 해당 경로의 html 파일을 통해서 성능을 측정합니다. startServerCommand : 정적 사이트가 아닐 경우 서버를 켜는 명령어를 작성합니다. 이 명령어를 통해 Lighthouse CI는 서버를 켜고 성능 측정이 종료되면 종료시킵니다. url : 성능을 측정할 url을 배열형태로 작성합니다. 배열로 작성한다는 말인즉슨 여러 개의 url에 대하여 성능을 측정할 수 있다는 뜻입니다. numberOfRuns: Ligthouse가 실행되는 횟수입니다. Ligthouse의 성능 점수는 동일한 코드의 동일한 사이트라고 하더라도 네트워크 및 웹 기술 등의 변수에 의해서 결과가 다르게 나올 수 있습니다. 기본값으로는 3이며, 최대 5번을 추천하고 있습니다. 자세한건 여기에서 확인할 수 있습니다. upload.target : Lighthouse CI가 생성한 보고서를 저장할 위치 입니다. temporary-public-storage를 지정해두면 임시 스토리지에 자동으로 저장해줍니다. 이 결과는 7일 동안 유지됩니다. 물론 개인 서버에 저장할 수도 있습니다. 자세한건 여기에서 확인할 수 있습니다. 이제 설치, 설정이 끝났으니 실행해보겠습니다. 먼저 앱을 빌드하고, lhci를 실행해봅시다. 정상적으로 설치, 설정이 완료되었다면 아래와 같은 결과가 나타날 것입니다. npm run build lhci autorun

✅ .lighthouseci/ directory writable ✅ Configuration file found ✅ Chrome installation found Healthcheck passed!

Started a web server on port 58273... Running Lighthouse 5 time(s) on http://localhost:58273/ Run #1...done. Run #2...done. Run #3...done. Run #4...done. Run #5...done. Done running Lighthouse!

Uploading median LHR of http://localhost:58273/...success! Open the report at https://storage.googleapis.com/lighthouse-infrastructure.appspot.com/reports/1653542738398-38777.report.html No GitHub repository slug found, skipping URL map upload. No GitHub token set, skipping GitHub status check.

Done running autorun. Lighthouse CI가 정상적으로 작동했고, 보고서가 업로드되었습니다. 아래의 생성된 링크를 클릭하면 아래 이미지와 같은 보고서를 확인할 수 있습니다. 빌드를 실패하게 하기 (assert) 연동하기에 앞서 미리 정의된 기준을 충족하지 못할 경우 빌드를 실패하게 만드는 작업을 먼저 진행해봅시다. 이 작업은 lighthouserc.js설정 파일에서 assert를 이용해서 기준을 정의할 수 있습니다. 아래는 Lighthouse의 카테고리 중 하나인 performance와 accessibility를 확인하고 performance가 90점 이하이면 warning,accessibility가 100점 미만이면 error를 발생시키는 간단한 예시입니다. ( "ci": ( "assert": ( "assertions": ( // performance 카테고리 점수가 90점 미만이면 warning "categories:performance": ["warn", ( "minScore": 0.9 )], // accessibility 가 100점 미만이면 error "categories:accessibility": ["error", ( "minScore": 1 )] ) ) ) ) 이렇게 직접 카테고리를 설정할 수도 있고, Lighthouse CI에서 제공하는 프리셋을 이용할 수도 있습니다. 이제 깃헙 액션과 연동하여 결과를 살펴보겠습니다. Github Actions 연동하기 이제 본격적으로 Lighthouse CI와 Github Actions를 연동해보겠습니다. 루트 디렉토리에 .github/workflows를 생성하고, lighthouse.yaml파일을 만들어줍니다. 파일명은 자유롭게 작성해도 됩니다. 그리고 여기를 방문해서 Lighthouse CI Github App을 본인의 레포지토리에 설치 후, secrets에 LHCI_GITHUB_APP_TOKEN으로 본인의 키를 저장해둡니다. 이제 yaml파일을 작성해봅시다. #@ lighthouse.yaml

name: Run lighthouse CI When Push on: [push] jobs: lhci: name: Lighthouse CI runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v2

  - name: Use Node.js 17.3.1
    uses: actions/setup-node@v1
    with:
      node-version: 17.3.1

  - name: Install packages
    run: |
      npm ci

  - name: Build
    run: |
      npm run build

  - name: Run Lighthouse CI
    env:
      LHCI_GITHUB_APP_TOKEN: $(( secrets.LHCI_GITHUB_APP_TOKEN ))
    run: |
      npm install -g @lhci/cli
      lhci autorun || echo "Fail to Run Lighthouse CI!" Checkout : 소스코드를 내려받습니다. Use Node.js 17.3.1 : Node.js를 설치합니다. (본인의 프로젝트 Node 버전에 맞게 작성해주세요) Install packages & Build : 필요한 패키지들을 설치하고 앱을 빌드 합니다. Run Lighthouse CI : lhci 설치 및 실행합니다. 이제 레포지토리에 코드를 푸시한 후 PR을 생성해봅시다! Github Actions가 돌고 아래와 같은 결과를 볼 수 있습니다. 빌드에 실패했군요! Actions 탭에서 자세한 로그를 살펴봅시다. 5 result(s) for http://localhost:42329/ :

✘ csp-xss failure for minScore assertion Ensure CSP is effective against XSS attacks https://web.dev/csp-xss/

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

( ... )

⚠️ render-blocking-resources warning for maxLength assertion Eliminate render-blocking resources https://web.dev/render-blocking-resources/

    expected: <=0
       found: 1
  all values: 1, 1, 1, 1, 1

Assertion failed. Exiting with status code 1. 위와 같은 이유로 CI에 실패했습니다. 이제 이 조건들을 통과하도록 코드를 수정 후 다시 Github Actions를 실행해보겠습니다. 이제 Merge를 할 수 있습니다! Lighthouse의 5개의 카테고리 점수가 표시되는 것을 확인할 수 있고, 오른쪽의 Details를 통해 보고서를 볼 수 있습니다. PR에 댓글로 나타내기 위에서 알아본 Lighthouse CI Github App은 충분히 훌륭합니다. 하지만 저는 대표적인 6가지 카테고리 이외에 First Contentful Paint(FCP), Largest Contentful Paint(LCP)등 다양한 지표도 알아보고 싶습니다. 먼저 lighthouserc.js파일에서 결과물을 파일로 저장합니다. //@ lighthouserc.js module.exports = ( ci: ( ... ), upload: ( target: 'filesystem', outputDir: './lhci_reports', reportFilenamePattern: '%%PATHNAME%%-%%DATETIME%%-report.%%EXTENSION%%', ), ), 그리고 로컬에서 Lighthouse CI를 실행해봅시다. 그러면 위처럼 보고서들이 파일로 저장이 되는데 이때 manifest.json파일을 살펴보면 [ ( "url": "http://localhost:49262/", "isRepresentativeRun": false, "htmlPath": "./-2022_05_26_06_59_40-report.html", "jsonPath": "./-2022_05_26_06_59_40-report.json", "summary": ( "performance": 1, ( ... ) ) ), ( ... ) ] 위와 같이 페이지별로 summary를 확인할 수 있습니다. 그리고 jsonPath에 적혀있는 json파일을 살펴보면 우리가 알고자 하는 FCP, LCP등의 값을 확인할 수 있습니다. 이제 Github Actions에서 manifest.json파일을 읽고 comment에 추가할 정보들을 파싱하는 스크립트를 작성해봅시다. # @.github/workflows/lighthouse.yaml ( ... ) - name: Format lighthouse score id: format_lighthouse_score uses: actions/github-script@v3 with: github-token: $((secrets.GITHUB_TOKEN)) script: |

        const fs = require('fs')
        # 본인의 환경에 맞게 설정해주세요
        const results = JSON.parse(fs.readFileSync("/(Github Actions runner directory)/lhci_reports/manifest.json"))
        # comment를 담을 변수 입니다.
        let comments = ""

        results.forEach((result) => (
          const ( summary, jsonPath ) = result
          const ( audits ) = details

          const details = JSON.parse(fs.readFileSync(jsonPath))
          const formatResult = (res) => Math.round(res * 100)

          Object.keys(summary).forEach(
            (key) => (summary[key] = formatResult(summary[key]))
          )

          # 점수가 색상으로 구분되는 방식 (https://web.dev/performance-scoring/#color-coding)
          const score = (res) => (res >= 90 ? "" : res >= 50 ? "" : "")

          const comment = [
            `⚡️ Lighthouse report!`,
            `| Category | Score |`,
            `| --- | --- |`,
            `| $(score(summary.performance)) Performance | $(summary.performance) |`,
            ( ... )
          ].join("\n")

          const detail = [
            `| Category | Score |`,
            `| --- | --- |`,
            `| $(score(
              audits["first-contentful-paint"].score * 100
            )) First Contentful Paint | $(
              audits["first-contentful-paint"].displayValue
            ) |`,
            ( ... )
          ].join("\n")
          comments += comment + "\n" + detail + "\n"
        ))
        # comments 변수의 값을 다음 job으로 넘겨줍니다.
        core.setOutput('comments', comments)

  - name: comment PR
    uses: unsplash/comment-on-pr@v1.3.0
    env:
      GITHUB_TOKEN: $(( secrets.GITHUB_TOKEN ))
    with:
      msg: $(( steps.format_lighthouse_score.outputs.comments)) 위와같이 파싱하는 스크립트를 작성 후 Github에 push 해봅시다! 그러면 짜잔! 우리가 원하는 FCP, LCP등의 상세 정보들을 확인할 수 있습니다! 이제 푸시할 때마다 Lighthouse CI가 돌면서 성능 리포트를 생성하고 해당 리포트를 파싱해서 PR의 댓글에서 상세 정보를 간단하게 확인할 수 있습니다. 결론 지금까지 웹 성능 측정 도구인 Lighthouse를 살펴보고, Lighthouse CI와 Github Actions를 연동하여 성능 측정 및 Lighthouse 보고서 생성을 자동화하는 과정을 살펴보았습니다. 이제 CI에 Lighthouse를 도입함으로써 Pull Request 마다 성능 저하를 미리 알 수 있기 때문에 사용자가 성능 저하를 경험하기 전에 해결할 수 있게 되었습니다. 참고 해당 글에서는 다루지 않았지만, 로그인이 필요한 사이트를 테스트하는 경우 Puppeteer를 사용하여 로그인 후 Lighthouse를 실행할 수 있습니다. 자세한건 여기를 참고해주세요. 또한, Lighthouse CI Server를 사용한다면 과거의 기준 커밋과 현재의 커밋의 차이를 손쉽게 비교할 수 있습니다.

댓글 0

댓글을 작성하려면 로그인이 필요합니다.

댓글을 불러오는 중...