Logo

GitHub Actions 자바스크립트 셋업

이번 포스팅에서는 깃허브의 CI 서비스인 GitHub Actions를 사용하여 자바스크립트 프로젝트의 지속 통합(Continuous Integration)을 위한 워크플로우를 구성하는 방법에 대해서 알아보겠습니다. 또한 자바스크립트 프로젝트에서 GitHub Actions 셋업이 용이하도록 깃허브에서 제공하는 Setup Node 액션에 대해서도 살펴보겠습니다.

실습 프로젝트와 코드 저장소 생성

실습을 위해서 Create React App을 통해 간단한 자바스크립트 프로젝트를 하나 생성하겠습니다.

$ npx create-react-app github-actions-setup-node

Creating a new React app in /Users/daleseo/temp/github-actions-setup-node.

Installing packages. This might take a couple of minutes.
Installing react, react-dom, and react-scripts with cra-template...


(... 생략 ...)

We suggest that you begin by typing:

  cd github-actions-setup-node
  npm start

Happy hacking!

그 다음 본인 깃허브 계정에 새로운 코드 저장소(repository)를 하나를 만들고 위에서 생성한 프로젝트의 코드를 올립니다.

$ cd github-actions-setup-node
$ git remote add origin https://github.com/DaleSchool/github-actions-setup-node.git
$ git branch -M main
$ git push -u origin main
Enumerating objects: 22, done.
Counting objects: 100% (22/22), done.
Delta compression using up to 8 threads
Compressing objects: 100% (22/22), done.
Writing objects: 100% (22/22), 285.94 KiB | 8.41 MiB/s, done.
Total 22 (delta 0), reused 0 (delta 0), pack-reused 0
To https://github.com/DaleSchool/github-actions-cache.git
 * [new branch]      main -> main
Branch 'main' set up to track remote branch 'main' from 'origin'.

저장소의 이름은 원하는대로 지으시면 되며 저는 프로젝트 명과 동일한 github-actions-setup-node이라는 이름으로 실습 코드 저장소를 만들었습니다.

npm 패키지 설치

자바스크립트 프로젝트에서 모든 CI 작업은 npm 패키지 설치에서 시작한다고 해도 과언이 아니겠죠? 그래서 먼저 CI 서버에 npm 패키지를 설치해야겠습니다.

프로젝트에 .github/workflows/라는 폴더를 만든 후, 그 안에 ci.yml이라는 이름의 YAML 파일을 하나 생성합니다.

코드 저장소에서 push 이벤트가 발생되면 워크플로우가 실행되도록 설정하고 build라는 간단한 작업(job)을 추가합니다. build 작업은 총 2단계로 이뤄지는데 첫번째 단계에서는 체크아웃 액션을 이용하여 코드 저장소에 올려둔 프로젝트의 코드를 CI 서버로 내려받고, 두번째 단계에서는 npm을 이용하여 package.json 파일에 명시되어 있는 패키지들을 CI 서버에 설치합니다.

GitHub에서 제공하는 체크아웃(Checkout) 액션에 대해서는 별도의 포스팅에서 자세히 다루었으니 참고 바랍니다.

.github/workflows/ci.yml
name: Our CI
on: push
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3      - run: npm ci

ci.yml 파일을 깃허브의 코드 저장소로 올린 후 Actions 탭에 들어가면 워크플로우가 실행될 것입니다.

Log
☑️ Set up Job
☑️ Run actions/checkout@v3
☑️ Run npm ci
▶ Run npm cinpm WARN deprecated source-map-resolve@0.6.0: See https://github.com/lydell/source-map-resolve#deprecatednpm WARN deprecated svgo@1.3.2: This SVGO version is no longer supported. Upgrade to v2.x.x.added 1415 packages, and audited 1416 packages in 27s179 packages are looking for funding  run `npm fund` for details6 moderate severity vulnerabilitiesTo address all issues (including breaking changes), run:  npm audit fix --forceRun `npm audit` for details.☑️ Post Run actions/checkout@v3
☑️ Complete Job

Run npm ci 단계를 보면 CI 서버에 총 1415개의 패키지가 설치된 것을 확인할 수 있습니다.

테스트 실행 및 애플리케이션 빌드

CI에서 테스트 실행과 애플리케이션 빌드는 거의 필수적으로 진행되죠? 테스트를 실행 후에 애플리케이션이 빌드되도록 워크플로우 파일을 한번 수정해보겠습니다.

.github/workflows/ci.yml
name: Our CI
on: push
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - run: npm ci
      - run: npm test      - run: npm run build

이제 ci.yml 파일의 변경 내용을 GitHub의 코드 저장소로 올리면 워크플로우가 실행될 것입니다.

Log
☑️ Set up Job
☑️ Run actions/checkout@v3
☑️ Run npm ci
☑️ Run npm test▶ Run npm test  npm test  shell: /usr/bin/bash -e {0}> github-actions-setup-node@0.1.0 test> react-scripts testPASS src/App.test.js  ✓ renders learn react link (36 ms)Test Suites: 1 passed, 1 totalTests:       1 passed, 1 totalSnapshots:   0 totalTime:        1.468 sRan all test suites.☑️ Run npm run build
▶ Run npm run build  npm run build  shell: /usr/bin/bash -e {0}> github-actions-setup-node@0.1.0 build> react-scripts buildCreating an optimized production build...Compiled successfully.File sizes after gzip:  46.39 kB  build/static/js/main.f5998c51.js  1.79 kB   build/static/js/787.2063ca84.chunk.js  541 B     build/static/css/main.073c9b0a.cssThe project was built assuming it is hosted at /.You can control this with the homepage field in your package.json.The build folder is ready to be deployed.You may serve it with a static server:  npm install -g serve  serve -s buildFind out more about deployment here:  https://cra.link/deployment☑️ Post Run actions/checkout@v3
☑️ Complete Job

Actions 탭에 들어가 작업 실행 로그를 확인해보면 CI 서버에서 테스트가 통과하고 애플리케이션이 빌드되는 것을 볼 수 있습니다.

참고로 여기서 의도적으로 애플리케이션 빌드보다 테스트를 먼저 실행되도록 워크플로우를 설정하였는데요. 테스트가 실패한 코드를 굳이 빌드하여 CI 서버의 리소스를 낭비할 필요가 없기 때문입니다.

특정 노드 버전 설치

GitHub Action의 CI 서버에는 Node.js가 이미 설치가 되어 있어서 굳이 별도로 설치를 해 줄 필요는 없는데요. 그럼에도 불구하고 GitHub Actions의 CI 서버에 설치된 Node.js는 주기적으로 버전이 업데이트되기 때문에, 프로젝트에서 요구하는 정확한 버전의 Node.js를 설치하는 편이 더 안전할 것입니다.

이 때, 우분투 운영체제의 apt-get 명령어나 윈도우즈 운영체제의 choco 명령어를 사용해서 직접 특정 버전의 Node.js를 다운받아 설치할 수도 있지만, 깃허브에서 제공하는 Setup Node 액션을 사용하면 운영체제를 신경쓰지 않고 단순히 버전만 명시하여 Node.js를 설치할 수 있어서 매우 편리합니다. actions/setup-node@v3 액션의 node-version 입력 옵션을 16.14.2로 넘겨보겠습니다.

.github/workflows/ci.yml
name: Our CI
on: push
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-node@v3        with:          node-version: 16.14.2      - run: npm ci
      - run: npm test
      - run: npm run build

다시 ci.yml 파일의 변경 내용을 깃허브의 코드 저장소로 올리면 워크플로우가 실행될 것입니다.

Log
☑️ Set up Job
☑️ Run actions/checkout@v3
☑️ Run actions/setup-node@v3▶ Run actions/setup-node@v3Attempting to download 16.14.2...Acquiring 16.14.2 - x64 from https://github.com/actions/node-versions/releases/download/16.14.2-2007501289/node-16.14.2-linux-x64.tar.gzExtracting .../usr/bin/tar xz --strip 1 --warning=no-unknown-keyword -C /home/runner/work/_temp/b7b24842-f513-43a7-938d-2a409c6055c4 -f /home/runner/work/_temp/48e86587-038b-4eae-9b1d-dafbd445c172Adding to the cache ...Done☑️ Run npm ci
☑️ Run npm test
☑️ Run npm run build
☑️ Post Run actions/setup-node@v3
☑️ Post Run actions/checkout@v3
☑️ Complete Job

작업 로그를 통해 정확히 16.14.2 버전의 Node.js가 CI 서버에 설치되는 것을 확인할 수 있습니다.

.nvm 파일에 저장된 노드 버전 설치

만약에 프로젝트에서 nvm을 사용하고 있다면 특정 버전의 노드를 설치하기 보다는 .nvmrc 파일에 저장되어 있는 버전을 설치하는 게 좋을텐데요. 그러면 개발자가 사용하는 노드 버전과 GitHub Actions의 CI 서버에서 사용되는 노드 버전이 항상 일치시킬 수 있을테니까요.

우선 로컬 저장소에서 18.17.0이 들어있는 .nvmrc 파일을 생성한 후 원격 저장소로 올립니다.

$ echo "18.17.0" > .nvmrc
$ git add .
$ git commit -m "add .nvmrc"
$ git push

그 다음 워크플로우 파일을 수정해야하는데요. actions/setup-node@v3 액션에서 node-version-file 옵션으로 .nvmrc을 명시해주기면 하면 됩니다.

.github/workflows/ci.yml
name: Our CI
on: push
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-node@v3        with:          node-version-file: .nvmrc      - run: npm ci
      - run: npm test
      - run: npm run build

변경된 ci.yml을 커밋하면 바로 워크플로우가 실행될 것입니다.

Log
☑️ Set up Job
☑️ Run actions/checkout@v3
☑️ Run actions/setup-node@v3▶ Run actions/setup-node@v3Node version file is not JSON fileResolved .nvmrc as 18.17.0Attempting to download 18.17.0...Acquiring 18.17.0 - x64 from https://github.com/actions/node-versions/releases/download/18.17.0-5596145502/node-18.17.0-linux-x64.tar.gzExtracting .../usr/bin/tar xz --strip 1 --warning=no-unknown-keyword -C /home/runner/work/_temp/1a9db09f-2f53-417c-914c-fda24b457420 -f /home/runner/work/_temp/d7cc3bed-a8a0-41e8-9a41-b18c9878f4ddAdding to the cache ...Done☑️ Run npm ci
☑️ Run npm test
☑️ Run npm run build
☑️ Post Run actions/setup-node@v3
☑️ Post Run actions/checkout@v3
☑️ Complete Job

작업 로그를 통해 정확히 .nvmrc 파일에 저장되어 있는 18.17.0 버전의 Node.js가 CI 서버에 설치되는 것을 확인할 수 있습니다.

nvm(Node Version Manager)에 대한 자세한 내용은 관련 포스팅을 참고 바랍니다.

npm 패키지 캐싱

GitHub Actions에서 작업 로그를 확인해보면 npm 패키지를 설치하는 단계에서 시간이 가장 많이 소모되는 것을 눈치챌 수 있는데요. 개발자들은 계속 같은 컴퓨터를 사용하므로 npm 패키지를 최초에 한 번만 설치해주면 되지만, 항상 새롭게 셋업되는 CI 서버에서는 모든 npm 패키지를 매번 설치해야하는 비효율이 발생합니다.

특히 프로젝트의 규모가 커지고 더욱 많은 npm 패키지가 설치됨에 따라 이것은 프로젝트의 개발 생산성에도 부정적인 영향을 줄 수 있는데요. 다행히도 GitHub Actions에서는 캐싱(caching)을 지원하기 때문에 CI 서버에서 npm 패키지를 설치할 때 성능 향상을 꾀할 수 있습니다.

깃허브의 Setup Node 액선 사용하면 매우 간편하게 이러한 npm 패키지 캐싱도 설정할 수 있는데요. actions/setup-node@v3 액션의 cache 입력 옵션을 통해 npm, yarn, pnpm과 같은 해당 프로젝트에서 쓰고 있는 패키지 매니저를 넘길 수 있습니다. 실습 프로젝트는 npm을 사용하므로 package-lock.json 파일을 기준으로 캐싱을 하도록 cache 입력 옵션을 npm으로 넘기겠습니다.

.github/workflows/ci.yml
name: Our CI
on: push
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-node@v3
        with:
          node-version: 16.14.2
          cache: npm      - run: npm ci
      - run: npm test
      - run: npm run build

이제 ci.yml 파일의 변경 내용을 GitHub의 코드 저장소로 올리면 워크플로우가 실행될 것입니다.

Log
☑️ Set up Job
☑️ Run actions/checkout@v3
☑️ Run actions/setup-node@v3
▶ Run actions/setup-node@v3Attempting to download 16.14.2...Acquiring 16.14.2 - x64 from https://github.com/actions/node-versions/releases/download/16.14.2-2007501289/node-16.14.2-linux-x64.tar.gzExtracting .../usr/bin/tar xz --strip 1 --warning=no-unknown-keyword -C /home/runner/work/_temp/763ebb03-a499-446c-9a7e-ca4f5565cca1 -f /home/runner/work/_temp/eebaaa50-d30d-4b26-b2da-a687b624e6d2Adding to the cache ...Done/opt/hostedtoolcache/node/16.14.2/x64/bin/npm config get cache/home/runner/.npmnpm cache is not found☑️ Run npm ci
☑️ Run npm test
☑️ Run npm run build
☑️ Post Run actions/setup-node@v3
▶ Post job cleanup./opt/hostedtoolcache/node/16.14.2/x64/bin/npm config get cache/home/runner/.npm/usr/bin/tar --posix --use-compress-program zstd -T0 -cf cache.tzst -P -C /home/runner/work/github-actions-setup-node/github-actions-setup-node --files-from manifest.txtCache Size: ~37 MB (39269225 B)Cache saved successfullyCache saved with the key: node-cache-Linux-npm-0ea44dc6f6ce94ea47107f7661af88d55a05d065fdb8b953a40ff5c25151ffe7☑️ Post Run actions/checkout@v3
☑️ Complete Job

첫번째 실행에는 워크플로우가 전혀 빨라지지 않을 거에요. 아직까지 캐시에 npm 패키지를 저장한 적이 없으니까요. 하지만 Post Run actions/setup-node@v3 단계를 보시면 npm 패키지를 압축하여 GitHub의 캐시에 올리는 것을 볼 수 있습니다.

이제 Actions 탭에서 실습 워크플로우를 수동으로 재실행해볼까요?

Log
☑️ Set up Job
☑️ Run actions/checkout@v3
☑️ Run actions/setup-node@v3
▶ Run actions/setup-node@v3Attempting to download 16.14.2...Acquiring 16.14.2 - x64 from https://github.com/actions/node-versions/releases/download/16.14.2-2007501289/node-16.14.2-linux-x64.tar.gzExtracting .../usr/bin/tar xz --strip 1 --warning=no-unknown-keyword -C /home/runner/work/_temp/85c6463c-9771-4922-9fa8-79825bd14a85 -f /home/runner/work/_temp/f80c4dd5-fcf0-411c-ad5e-5b7bfbb6e7e9Adding to the cache ...Done/opt/hostedtoolcache/node/16.14.2/x64/bin/npm config get cache/home/runner/.npmReceived 4194304 of 39269225 (10.7%), 4.0 MBs/secReceived 39269225 of 39269225 (100.0%), 27.1 MBs/secCache Size: ~37 MB (39269225 B)/usr/bin/tar --use-compress-program zstd -d -xf /home/runner/work/_temp/bc7adebc-41de-4291-845f-6915f7cd49a4/cache.tzst -P -C /home/runner/work/github-actions-setup-node/github-actions-setup-nodeCache restored successfullyCache restored from key: node-cache-Linux-npm-0ea44dc6f6ce94ea47107f7661af88d55a05d065fdb8b953a40ff5c25151ffe7☑️ Run npm ci
☑️ Run npm test
☑️ Run npm run build
☑️ Post Run actions/setup-node@v3
▶ Post job cleanup./opt/hostedtoolcache/node/16.14.2/x64/bin/npm config get cache/home/runner/.npmCache hit occurred on the primary key node-cache-Linux-npm-0ea44dc6f6ce94ea47107f7661af88d55a05d065fdb8b953a40ff5c25151ffe7, not saving cache.☑️ Post Run actions/checkout@v3
☑️ Complete Job

이 번에는 패키지를 설치하는 속도가 훨씬 빨라졌는데요. 기존에는 약 30초가 걸렸었는데 이번에는 약 10초가 걸리네요.

Run actions/setup-node@v3 단계를 보면 npm이 패키지 캐싱해두는 /home/runner/.npm 경로에 깃허브의 캐시에 저장되어 있던 약 37 MB의 파일의 압축을 풀고 있는 것을 볼 수 있습니다.

참고로 이것보다 좀 더 세밀한 캐싱 설정이 필요하다면 깃허브의 캐시(Cache) 액션을 사용할 수도 있는데요. 깃허브의 Cache 액션에 대해서는 별도의 포스팅에서 자세히 다루고 있으니 참고 바랍니다.

실습 코드

본 포스팅에서 작성한 YAML 파일과 워크플로우 실행 결과는 아래 코드 저장소에서 확인하실 수 있습니다.

https://github.com/DaleSchool/github-actions-setup-node

마치면서

지금까지 자바스크립트 프로젝트를 만들고 깃허브 저장소에 올린 후 GitHub Actions를 통해 기본적인 CI 구성을 해보았습니다. 실습 워크플로우에서는 일반적인 CI에서 기본적으로 필요한 패키지 설치, 테스트 실행, 애플리케이션 빌드까지만 설정을 해보았는데요.

실제 프로젝트에서는 여기에 코드 포맷팅(formatting)이나 린팅(linting)을 추가할 수도 있고, 타입스크립트를 사용한다면 당연히 컴파일 오류를 확인하는 단계도 필요할 것입니다. 뿐만 아니라 단순한 CI(Continuous Integration)에서 더 나아가 애플리케이션을 상용 환경에 반영하거나 라이브러리를 npm 저장소에 배포하는 CD(Continuous Deployment)까지 확장할 수 있겠죠?

본 포스팅에서 활용한 Setup Node 액션에 대한 좀 더 세부적인 내용은 GitHub 마켓플레이스를 참고 바라겠습니다.

GitHub Actions 관련 포스팅은 GitHub Actions 태그를 통해서 쉽게 만나보세요!