1. What is the Action? ๐Ÿ‘ฉโ€

GitHub Actions๋Š” ๊นƒํ—ˆ๋ธŒ์—์„œ ์ œ๊ณตํ•˜๋Š” CI/CD ๋ฅผ ๋„์™€์ฃผ๋Š” ์„œ๋น„์Šค๋‹ค. ์œ ์‚ฌํ•œ ์„œ๋น„์Šค๋กœ๋Š” CircleCI, Travis CI, Jenkins, Fastlane ์™€ ๊ฐ™์€ ๊ฒƒ๋“ค์ด ์กด์žฌํ•œ๋‹ค. Actions ์˜ ์žฅ์ ์€ ๊นƒํ—ˆ๋ธŒ์—์„œ ์ œ๊ณตํ•˜๋Š” ๊ฒƒ์œผ๋กœ ์„ค์ •์ด๋‚˜ ์—ฐ๋™์ด ์‰ฝ๋‹ค๋Š” ๊ฒƒ์ด๋‹ค. ๊นƒํ—ˆ๋ธŒ ์•ก์…˜์—์„œ ๋ฐ˜๋“œ์‹œ ์•Œ์•„์•ผ ํ•  ๊ฐœ๋…์€ Events, Jobs, Actions, Runners, Secrets, Workflows์˜ 6๊ฐ€์ง€ ๊ฐœ๋…์ด๋‹ค.

1. Events

๊นƒํ—ˆ๋ธŒ ์•ก์…˜์„ ์‹คํ–‰ํ•  ์ด๋ฒคํŠธ ํŠธ๋ฆฌ๊ฑฐ ์กฐ๊ฑด์„ ์ง€์ •ํ•œ๋‹ค.

์ด๋ฒคํŠธ ์ค‘ ์ฃผ๋กœ ์‚ฌ์šฉ๋˜๋Š” ํŠธ๋ฆฌ๊ฑฐ ์œ ํ˜•์€ ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

  1. push: ์ฝ”๋“œ๋ฅผ ํ‘ธ์‹œํ•  ๋•Œ.
  2. pull_request: Pull Request ๋ฅผ ์ƒ์„ฑํ•˜๊ฑฐ๋‚˜ ์—…๋ฐ์ดํŠธํ•  ๋•Œ.
  3. issues: ์ด์Šˆ๊ฐ€ ์ƒ์„ฑ๋˜๊ฑฐ๋‚˜ ์—…๋ฐ์ดํŠธ๋  ๋•Œ.
  4. issue_comment: ์ด์Šˆ์— ์ƒˆ๋กœ์šด ๋Œ“๊ธ€์ด ์ž‘์„ฑ๋  ๋•Œ.
  5. pull_request_review: Pull Request ๋ฅผ ๋ฆฌ๋ทฐํ•  ๋•Œ.
  6. pull_request_review_comment: Pull Request ๋ฆฌ๋ทฐ์— ์ƒˆ๋กœ์šด ๋Œ“๊ธ€์ด ์ž‘์„ฑ๋  ๋•Œ.
  7. repository_dispatch: ์‚ฌ์šฉ์ž ์ •์˜ ์ด๋ฒคํŠธ๋ฅผ ์ˆ˜์‹ ํ•  ๋•Œ.
  8. schedule: ์ผ์ •์— ๋”ฐ๋ผ ์ฃผ๊ธฐ์ ์œผ๋กœ.
  9. workflow_dispatch: ์ˆ˜๋™์œผ๋กœ ์›Œํฌํ”Œ๋กœ์šฐ๋ฅผ ์‹คํ–‰ํ•  ๋•Œ.

2. Jobs

Workflows ์—์„œ ์‹คํ–‰๋˜๋Š” ๊ฐœ๋ณ„ ์ž‘์—… ๋‹จ์œ„๋กœ ์‹ค์ œ๋กœ ๋ฌด์—‡์„ ํ• ๊นŒ์— ๋Œ€ํ•œ ์ •์˜๋ฅผ ํฌํ•จํ•œ๋‹ค. ๋”ฐ๋ผ์„œ Workflows YAML ํŒŒ์ผ์˜ ๋Œ€๋ถ€๋ถ„์„ ์ฐจ์ง€ํ•˜๋ฉฐ, ๋„์ปค ์ปดํฌ์ฆˆ๊ฐ€ ์—ฌ๋Ÿฌ ์ปจํ…Œ์ด๋„ˆ์— ๋Œ€ํ•œ ์ž‘์—…์„ ์„ค์ •ํ•˜๋“ฏ์ด Workflows ๋Š” ์—ฌ๋Ÿฌ Jobs ๋ฅผ ์„ค์ •ํ•˜๊ฒŒ ๋œ๋‹ค.

๋”ฐ๋ผ์„œ ํ•„์š”์— ๋”ฐ๋ผ ํ•˜๋‚˜์˜ Workflows ์•ˆ์—๋Š” ํ•˜๋‚˜ ๋˜๋Š” ๊ทธ ์ด์ƒ์˜ Jobs ๋ฅผ ํฌํ•จํ•˜๊ฒŒ ๋œ๋‹ค.

3. Actions

Jobs ๋Š” ๋‚ด๊ฐ€ ์‚ฌ์šฉํ•  ์ž‘์—…์— ๋Œ€ํ•ด Workflows ์— ์ •์˜๋ฅผ ํ•˜๋Š” ๊ฒƒ์ด๋ผ๋ฉด, Actions ๋Š” ์žฌ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ๋‹จ์œ„๋กœ, ๋„์ปค์™€ ๋น„๊ตํ•˜๋ฉด Docker Hub ์˜ ๊ณต๊ฐœ๋œ ์ปจํ…Œ์ด๋„ˆ ๋˜๋Š” ์ง์ ‘ ๋งŒ๋“  ๋น„๊ณต๊ฐœ ์ปจํ…Œ์ด๋„ˆ๋ผ ๋ณผ ์ˆ˜ ์žˆ๋‹ค.

Actions ๋ฅผ ์ง์ ‘ ๋งŒ๋“ค๊ธฐ๋„ ํ•˜์ง€๋งŒ, Marketplace๋ฅผ ํ†ตํ•ด ์ปค๋ฎค๋‹ˆํ‹ฐ๋กœ ์šด์˜๋˜๋Š” ์•ก์…˜์„ ๊ฐ€์ ธ์™€ Jobs ์— ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

Jobs ๊ฐ€ ์‹ค์ œ๋กœ Workflows ์—์„œ ์ˆ˜ํ–‰๋  ์ž‘์—…์— ๋Œ€ํ•ด ์ •์˜ํ•œ๋‹ค๋ฉด, Actions ๋Š” ์žฌ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ๋‹จ์œ„๋กœ ํŒจํ‚ค์ง• ๋œ chunk ์— ํ•ด๋‹นํ•œ๋‹ค.

4. Runners

๊นƒํ—ˆ๋ธŒ์—์„œ ์ œ๊ณตํ•˜๋Š” ํ˜ธ์ŠคํŒ… ํ™˜๊ฒฝ์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์€ ๋ฌผ๋ก , Vercel, ์…€ํ”„ ํ˜ธ์ŠคํŒ… ๋“ฑ์˜ ์‹คํ–‰ ํ™˜๊ฒฝ์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

5. Secrets

.env์™€ ๊ฐ™์€ ํŒŒ์ผ์€ ์˜ฌ๋ ค์„œ๋Š” ์•ˆ ๋˜๊ณ , ์‹ค์ˆ˜๋กœ ์ธํ•œ ์œ ์ถœ์„ ๋ง‰๊ธฐ ์œ„ํ•ด ์˜ฌ๋ฆด ์ˆ˜ ์—†๋„๋ก ๋ง‰ํ˜€์žˆ๋‹ค. ํ•˜์ง€๋งŒ ์„œ๋น„์Šค ํ˜ธ์ŠคํŒ…์„ ์œ„ํ•ด์„œ๋Š” APIํ‚ค ๋˜๋Š” DB Connection ์ •๋ณด ๋“ฑ ๋‹ค์–‘ํ•œ ๋น„๋ฐ€ ์ •๋ณด๊ฐ€ ํ•„์š”ํ•˜๋‹ค. ๋”ฐ๋ผ์„œ ๊นƒํ—ˆ๋ธŒ๋Š” ์ด๋Ÿฐ ์ •๋ณด๋ฅผ repositories ์— ์˜ฌ๋ฆฌ๋Š” ๋Œ€์‹  Secrets๋ฅผ ์ œ๊ณตํ•˜๊ณ , ์—ฌ๊ธฐ ์ž‘์„ฑํ•œ ์ •๋ณด๋Š” ์•”ํ˜ธํ™” ๋˜์—ˆ๋‹ค Workflows ๊ฐ€ ์ˆ˜ํ–‰๋  ๋•Œ ๋ณตํ˜ธํ™”๋˜์–ด ๊นƒํ—ˆ๋ธŒ ์•ก์…˜ ์ƒ ํ™˜๊ฒฝ ๋ณ€์ˆ˜๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ์„ค์ •์„ ์ œ๊ณตํ•œ๋‹ค.

6. Workflows

๊นƒํ—ˆ๋ธŒ ์•ก์…˜์—์„œ CI/CD ๋ฅผ ์œ„ํ•œ ํ•ต์‹ฌ ๋‹จ์œ„๋กœ ์–ด๋–ค ์ž‘์—…์„ ์–ธ์ œ ์–ด๋–ป๊ฒŒ ํ• ๊นŒ์— ๋Œ€ํ•ด ์ •์˜ํ•œ ๋ฌธ์„œ๋กœ ๋„์ปค์™€ ๋น„๊ตํ•˜๋ฉด Docker-Compose ์— ํ•ด๋‹นํ•œ๋‹ค. ๊นƒํ—ˆ๋ธŒ Workflows ์—ญ์‹œ yamlํŒŒ์ผ๋กœ ์ž‘์„ฑํ•œ๋‹ค.

name: CI

on:
  push:
    branches:
      - main  # main ๋ธŒ๋žœ์น˜๋กœ ํ‘ธ์‹œํ•  ๋•Œ๋งŒ ์‹คํ–‰

jobs:
  build:
    runs-on: ubuntu-latest  # ์‹คํ–‰ ํ™˜๊ฒฝ์„ ์šฐ๋ถ„ํˆฌ ์ตœ์‹  ๋ฒ„์ „์œผ๋กœ ์ง€์ •

    steps:
    - name: Checkout code  # ์ฝ”๋“œ ์ฒดํฌ์•„์›ƒ ๋‹จ๊ณ„
      uses: actions/checkout@v3  # GitHub Actions์—์„œ ์ œ๊ณตํ•˜๋Š” checkout ์•ก์…˜ ์‚ฌ์šฉ

    - name: Set up Node.js  # Node.js ์„ค์ • ๋‹จ๊ณ„
      uses: actions/setup-node@v3  # Node.js ํ™˜๊ฒฝ์„ ์„ค์ •ํ•˜๋Š” ์•ก์…˜ ์‚ฌ์šฉ
      with:
        node-version: '20'  # Node.js ๋ฒ„์ „ ์ง€์ •

    - name: Install dependencies  # ์˜์กด์„ฑ ์„ค์น˜ ๋‹จ๊ณ„
      run: npm install  # npm์„ ์‚ฌ์šฉํ•˜์—ฌ ํ”„๋กœ์ ํŠธ์˜ ์˜์กด์„ฑ ์„ค์น˜

    - name: Run tests  # ํ…Œ์ŠคํŠธ ์‹คํ–‰ ๋‹จ๊ณ„
      run: npm test  # npm์„ ์‚ฌ์šฉํ•˜์—ฌ ํ”„๋กœ์ ํŠธ์˜ ํ…Œ์ŠคํŠธ ์‹คํ–‰

2. Make Actions ๐Ÿ‘ฉโ€

1. Failure Case

์ฒ˜์Œ ๊นƒํ—ˆ๋ธŒ ์•ก์…˜์„ ์ ‘ํ•˜๊ณ  ์‹œ๋„ํ–ˆ์œผ๋‚˜ ์‹คํŒจํ–ˆ๋˜ ๋ฐฉ๋ฒ•์ด๋‹ค. ์•„๋งˆ๋„ ์•ก์…˜์„ ๊ฐ€์žฅ ๋จผ์ € ์ ‘ํ•  ๋•Œ ์ƒˆ ์•ก์…˜ ์ƒ์„ฑ์‹œ ๋‚˜์˜ค๋Š” Suggested for this repository์— ๋ณด์ด๋Š” ์ œ์•ˆ ์‚ฌํ•ญ์ด์ง€ ์•Š์„๊นŒ ์ƒ๊ฐ๋œ๋‹ค. ๋ณธ์ธ ์—ญ์‹œ ๊ทธ๋Ÿฌํ–ˆ๊ณ , ๊นƒํ—ˆ๋ธŒ ์•ก์…˜์—์„œ ์ œ๊ณตํ•˜๋Š” Webpack ์„ ์œ„ํ•œ ์•ก์…˜์ด ๋ณด์—ฌ ๋ฐ”๋กœ ์ด๊ฒƒ์„ ์‚ฌ์šฉํ–ˆ๋‹ค.

Repository Actions

๊ธฐ๋ณธ yaml ํŒŒ์ผ์€ ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

name: NodeJS with Webpack

on:
  push:
    branches: [ "main" ]
  pull_request:
    branches: [ "main" ]

jobs:
  build:
    runs-on: ubuntu-latest

    strategy:
      matrix:
        node-version: [14.x, 16.x, 18.x]

    steps:
    - uses: actions/checkout@v3

    - name: Use Node.js $
      uses: actions/setup-node@v3
      with:
        node-version: $

    - name: Build
      run: |
        npm install
        npx webpack

๋‹ค์–‘ํ•œ ๋…ธ๋“œ ๋ฒ„์ „์„ ํ…Œ์ŠคํŠธ ํ•  ํ•„์š”๋Š” ์—†์–ด์„œ strategy.matrix.node-version ๋ถ€๋ถ„์€ ์‚ญ์ œํ•˜๊ณ  ๋Œ๋ ค๋ณด์•˜๋‹ค. ๋‹น์—ฐํžˆ ๊นƒํ—ˆ๋ธŒ ์•ก์…˜์—์„œ ์ œ๊ณตํ•˜๋Š” ๊ฒƒ์ด๋‹ˆ workflow ๋Š” ์„ฑ๊ณต์ ์œผ๋กœ ์ž˜ ๋Œ์•„๊ฐ”๋‹ค.

ํ•˜์ง€๋งŒ ๋ฌธ์ œ๋Š” build๋ฅผ ์œ„ํ•œ Action ์ด๋ผ๋Š” ๊ฒƒ์ด๋‹ค. ์œ„ ์„ค์ •์— ๋‹จ์ˆœํžˆ packages.json ์— ์žˆ๋Š” ๋ช…๋ น์–ด๋ฅผ ์‚ฌ์šฉํ•˜๋„๋ก run ๋ช…๋ น์„ ์ถ”๊ฐ€ํ•ด ๋ณด์•˜์œผ๋‚˜ ์›ํ•˜๋Š”๋Œ€๋กœ ๋˜์ง„ ์•Š์•˜๋‹ค. ๋ฌผ๋ก  ์œ„ Workflow ํ…œํ”Œ๋ฆฟ์„ ์ž˜ ์ˆ˜์ •ํ•ด ์‚ฌ์šฉํ•˜๋ฉด ํ…Œ์ŠคํŠธ ์ž๋™ํ™” ๋ฐ Webpack build ํ›„ deploy ์ž๋™ํ™”๊นŒ์ง€ ํ•  ์ˆ˜ ์žˆ๊ฒ ์ง€๋งŒ ๊ทธ๋Ÿฌ๊ธฐ์—” ๋Ÿฌ๋‹์ปค๋ธŒ๊ฐ€ ์ƒ๋‹นํ•  ๊ฒƒ์œผ๋กœ ์˜ˆ์ƒ๋˜์—ˆ๋‹ค.

๋”ฐ๋ผ์„œ GitHub Pages ์— ํ˜ธ์ŠคํŒ…์„ ํ•ด์ฃผ๋Š” actions ์ด ์ด๋ฏธ ์กด์žฌํ•  ๊ฒƒ์ด๋ผ ์ƒ๊ฐํ–ˆ๊ณ , Chat-GPT ์— ๋ฌผ์–ด๋ณด๋‹ˆ peaceiris/actions-gh-pages๋ฅผ ์‚ฌ์šฉํ•˜๋ผ๊ณ  ํ–ˆ๋‹ค.

2. Try peaceiris/actions-gh-pages

peaceiris/actions-gh-pages ๋ฅผ ๋ฐฉ๋ฌธํ•ด๋ณด๋‹ˆ ๋‹ค์–‘ํ•œ ํ™˜๊ฒฝ์—์„œ GitHub Pages ํ˜ธ์ŠคํŒ…์„ ๊ฐ€๋Šฅํ•˜๋„๋ก Workflow ์ƒ˜ํ”Œ๋“ค์„ ์ œ๊ณตํ–ˆ๋‹ค. React ์—†์ด Node ํ™˜๊ฒฝ์—์„œ ๋„์šฐ๋Š” ๊ฒŒ ๋ชฉ์ ์ด์—ˆ๊ธฐ ๋•Œ๋ฌธ์— Static Site Generators with Node.js ํ…œํ”Œ๋ฆฟ์„ ์‚ฌ์šฉํ–ˆ๊ณ , ๊ธฐ๋ณธ yaml ํŒŒ์ผ์€ ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค(React and Next ๋˜๋Š” Vue and Nuxt ์„ ์‚ฌ์šฉํ•œ ํ™˜๊ฒฝ ํ…œํ”Œ๋ฆฟ๋„ ์กด์žฌํ•œ๋‹ค).

# peaceiris/actions-gh-pages ์—์„œ ์ œ๊ณตํ•˜๋Š” ํ…œํ”Œ๋ฆฟ์ด๋‹ค. ์‹ค์ œ ์ ์šฉํ•  yaml ํŒŒ์ผ์€ ์•„๋ž˜ ์ž‘์„ฑ๋œ yaml ์„ ํ™•์ธํ•  ๊ฒƒ.
name: GitHub Pages

on:
  push:
    branches:
      - main
  pull_request:

jobs:
  deploy:
    runs-on: ubuntu-22.04
    permissions:
      contents: write
    concurrency:
      group: ${{ github.workflow }}-${{ github.ref }}
    steps:
      - uses: actions/checkout@v3

      - name: Setup Node
        uses: actions/setup-node@v3
        with:
          node-version: '14'

      - name: Cache dependencies
        uses: actions/cache@v3
        with:
          path: ~/.npm
          key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
          restore-keys: |
            ${{ runner.os }}-node-

      - run: npm ci
      - run: npm run build

      - name: Deploy
        uses: peaceiris/actions-gh-pages@v3
        if: github.ref == 'refs/heads/main'
        with:
          github_token: ${{ secrets.GITHUB_TOKEN }}
          publish_dir: ./public

Cache dependencies ๋ถ€๋ถ„์„ ๋ณด๋ฉด ์ง€์†์ ์ธ CI/CD ๋ฅผ ๋น ๋ฅด๊ฒŒ ํ•˜๊ธฐ ์œ„ํ•ด ์‹คํ–‰ ์šด์˜์ฒด์ œ ์™€ package-lock.json์ด ๋ณ€๊ฒฝ๋˜์—ˆ๋Š”์ง€ ์—ฌ๋ถ€์— ๋”ฐ๋ผ ์บ์‹œ๋ฅผ ์‚ฌ์šฉํ•˜๋„๋ก ๋˜์–ด์žˆ๋‹ค.

npm ci๋Š” npm install๊ณผ ๋น„์Šทํ•˜์ง€๋งŒ package-lock.json์„ ์‚ฌ์šฉํ•ด ์ข€ ๋” ์—„๊ฒฉํ•˜๊ฒŒ ๋ช…์‹œ๋œ ์ข…์†์„ฑ์„ ๋”ฐ๋ฅด๋„๋ก ํ•˜๋ฉฐ, node_modules๋ฅผ ๋ชจ๋‘ ์ œ๊ฑฐํ•œ ํ›„ clean ํ•œ ์ƒํƒœ์—์„œ ์ƒˆ๋กญ๊ฒŒ ์„ค์น˜ํ•˜๋Š” CI ํ™˜๊ฒฝ์„ ์œ„ํ•œ ์„ค์น˜ ๋ช…๋ น์–ด๋‹ค. ์ด๊ฒƒ์€ Yarn ์„ ์‚ฌ์šฉํ•˜๋Š” ํ™˜๊ฒฝ์—์„œ yarn install --frozen-lockfile ๋ช…๋ น์œผ๋กœ yarn.lockํŒŒ์ผ์˜ ์˜์กด์„ฑ์„ ์—„๊ฒฉํ•˜๊ฒŒ ๋ช…์‹œ๋œ ์ข…์†์„ฑ์„ ๋”ฐ๋ฅด๋„๋ก ์„ค์น˜ํ•˜๋„๋ก ํ•˜๋Š” ๊ฒƒ๊ณผ ๋น„์Šทํ•˜๋‹ค.

๊ทธ ๋‹ค์Œ npm run build๋Š” package.json ์— ์žˆ๋Š” ๋ช…๋ น์–ด๋ฅผ ๋”ฐ๋ฅด๊ธฐ ๋•Œ๋ฌธ์— ๋ช…๋ น์–ด๊ฐ€ ๋‹ค๋ฅด๋‹ค๋ฉด yaml ํŒŒ์ผ์„ ์›ํ•˜๋Š” ๋ช…๋ น์„ ์ˆ˜ํ–‰ํ•˜๋„๋ก ์ˆ˜์ •ํ•ด์•ผํ•œ๋‹ค.

๋งˆ์ง€๋ง‰์œผ๋กœ ๊นƒ ์ปค๋ฐ‹์—์„œ ์–ด๋–ค ๊ฒƒ์„ ref๋กœ ํ• ์ง€ ์„ค์ •ํ•˜๋„๋ก ๋˜์–ด ์žˆ๋Š”๋ฐ main ์„ ์‚ฌ์šฉํ•œ๋‹ค๋ฉด ๊ฑด๋“œ๋ฆด ํ•„์š”๋Š” ์—†๊ณ , Actions ๋ฅผ ์‚ฌ์šฉํ•˜๋ ค๋Š” repository ์— secret ์— ๋“ค์–ด๊ฐ€ ๊นƒํ—ˆ๋ธŒ ํ† ํฐ์„ ์ €์žฅํ•ด์ฃผ๋ฉด ๋œ๋‹ค. ์ฐธ๊ณ ๋กœ GITHUB_TOKEN๋Š” ํ† ํฐ๋ช…์œผ๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์—†๊ธฐ ๋•Œ๋ฌธ์— ๋ฐ”๊ฟ”์„œ ์‚ฌ์šฉํ•ด์•ผํ•œ๋‹ค.


์šฐ์„  ์ด๊ฒƒ์„ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด์„œ ์ž์‹ ์˜ package.json๊ณผ webpack.config.js๋ฅผ ์ •ํ™•ํžˆ ์ดํ•ดํ•˜๊ณ  ์žˆ์–ด์•ผ ํ•œ๋‹ค. Webpack ๊ณผ ์ „์ฒ˜๋ฆฌ๊ธฐ ๊ด€๋ จ๋œ ๊ฒƒ๋“ค๋งŒ ์„ค์น˜๋˜์—ˆ๋‹ค๋Š” ๊ฐ€์ • ํ•˜์— package.json์€ ๋‹ค์Œ๊ณผ ๊ฐ™์„ ๊ฒƒ์ด๋‹ค.

{
  "name": "Project Name",
  "version": "1.0.0",
  "description": "Project Description",
  "main": "index.js",
  "scripts": {
    "serve": "webpack serve --mode=development --node-env=development --open",
    "watch": "webpack --watch --mode=development --node-env=development",
    "build": "webpack --mode=production --node-env=production"
  },
  "keywords": [],
  "author": "Project Author",
  "license": "Project License",
  "devDependencies": {
    "css-loader": "^6.10.0",
    "eslint": "^8.56.0",
    "eslint-config-prettier": "^9.1.0",
    "file-loader": "^6.2.0",
    "gh-pages": "^6.1.1",
    "html-loader": "^5.0.0",
    "html-webpack-plugin": "^5.6.0",
    "prettier": "^3.2.5",
    "sass": "^1.70.0",
    "sass-loader": "^14.1.0",
    "style-loader": "^3.3.4",
    "webpack": "^5.90.1",
    "webpack-cli": "^5.1.4",
    "webpack-dev-server": "^4.15.1"
  },
  "dependencies": {
    "reset-css": "^5.0.2"
  }
}
  • SCSS ์ „์ฒ˜๋ฆฌ๊ธฐ๋ฅผ ์œ„ํ•œ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ: sass, sass-loader, style-loader, css-loader
  • HTML ์ „์ฒ˜๋ฆฌ๊ธฐ๋ฅผ ์œ„ํ•œ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ: html-loader, html-webpack-plugin
  • Assets ์ „์ฒ˜๋ฆฌ๊ธฐ๋ฅผ ์œ„ํ•œ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ: file-loader (url-loader๋ฅผ ์‚ฌ์šฉํ•ด๋„ ์ž‘๋™ํ•œ๋‹ค. Module-script ๋ฅผ ์‚ฌ์šฉํ•  ๊ฒฝ์šฐ, webpack 5 ์—์„œ deprecated ๋˜์—ˆ์œผ๋ฏ€๋กœ ์„ค์น˜ํ•˜์ง€ ์•Š๋Š”๋‹ค.)
  • GitHub Pages ๋ฅผ ์œ„ํ•œ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ: gh-pages
npm i -D webpack webpack-cli webpack-dev-server \
sass sass-loader style-loader css-loader \
html-loader html-webpack-plugin \
file-loader url-loader
gh-pages

๋‹ค๋ฅธ ๋ฐฉ๋ฒ•์œผ๋กœ๋Š” npx webpack init ๋ช…๋ น์„ ์‚ฌ์šฉํ•ด webpack ํ”„๋กœ์ ํŠธ๋ฅผ ์ƒ์„ฑํ•œ๋‹ค.

npm i -D webpack webpack-cli @webpack-cli/generators
npx webpack init
const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");

module.exports = {
  entry: {
    index: "./src/html/main/index.js",
    signIn: "./src/html/sign-in/index.js",
  },
  output: {
    filename: "[name].[contenthash].bundle.js",
    path: path.resolve(__dirname, "dist"),
    publicPath: process.env.NODE_ENV === "development" ? "/" : "/project-base-url/",
    assetModuleFilename: "images/[hash][ext][query]",
  },
  module: {
    rules: [
      {
        test: /\.scss$/,
        use: ["style-loader", "css-loader", "sass-loader"],
      },
      {
        // file loader
        test: /\.(png|svg|jpg|jpeg|gif)$/i,
        use: {
          loader: "file-loader",
        },
        generator: {
          filename: "[path][hash][ext][query]", // ์ด๋ฏธ์ง€๊ฐ€ ๋ฒˆ๋“ค๋  ์œ„์น˜๋ฅผ ๋ณ€๊ฒฝ
        },
      },
      {
        // html loader
        test: /\.html$/,
        use: {
          loader: "html-loader",
        },
      },
    ],
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: "./src/html/main/index.html",
      filename: "index.html",
      chunks: ["index"], // entry ์˜ JavaScript ์ด๋ฆ„๊ณผ ๋™์ผํ•˜๊ฒŒ ์ง€์ •. ํ•ด๋‹น JavaScript ๋ฅผ ๋ถˆ๋Ÿฌ์˜จ๋‹ค.
    }),
    new HtmlWebpackPlugin({
      template: "./src/html/sign-in/index.html",
      filename: "sign-in/index.html", // directory ๊ฐ€ routing ๊ธฐ๋Šฅ์„ ํ•˜๋„๋ก "๋ผ์šฐํŒ… ๊ฒฝ๋กœ/index.html" ๋กœ ์„ค์ •ํ•œ๋‹ค.
      chunks: ["signIn"],
    }),
  ],
};

์ด๋ฏธ์ง€์™€ ๊ฐ™์€ ๋ฆฌ์†Œ์Šค๋ฅผ ์ •์ƒ์ ์œผ๋กœ ๋กœ๋“œํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” webpack ์ด ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•ด์•ผํ•œ๋‹ค. ์ฆ‰, ์ด๋ฏธ์ง€์˜ src ๊ฒฝ๋กœ๋ฅผ javascript ์—์„œ ๋‹จ์ˆœ ๋ฌธ์ž์—ด๋กœ ์‚ฌ์šฉํ•˜๋ฉด ์•ˆ ๋˜๊ณ , webpack ์ด ์ผ๊ณ  ๋ฒˆ๋“ค๋ง ํ•  ์ˆ˜ ์žˆ๋„๋ก html ๋ฌธ์„œ๋ฅผ ํ†ตํ•ด src ๋ฅผ ์ž‘์„ฑํ•˜๊ฑฐ๋‚˜, javascript ๋กœ ๊ฒฝ๋กœ๋ฅผ ์ž…๋ ฅํ•  ๋•Œ๋Š” import ๋ฅผ ์‚ฌ์šฉํ•ด ํŒŒ์ผ์„ ๋ถˆ๋Ÿฌ์™€ ์‚ฌ์šฉํ•ด์•ผํ•œ๋‹ค.


package.json type="module"์„ ์‚ฌ์šฉํ•  ๊ฒฝ์šฐ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ผ๋ถ€ ์ˆ˜์ •์ด ํ•„์š”ํ•˜๋‹ค (Asset Modules ์— ์˜ํ•˜๋ฉด webpack 5 ์—์„œ deprecated ๋œ raw-loader, url-loader, file-loader์˜ ์‚ฌ์šฉ์„ ๊ถŒ์žฅํ•˜์ง€ ์•Š๋Š”๋‹ค. module script ์‚ฌ์šฉํ•  ๊ฒฝ์šฐ ์ด ๋ถ€๋ถ„๋„ ํ•จ๊ป˜ ๋ฐ”๊ฟ”์ฃผ๋ฉฐ, ๋”์ด์ƒ ์œ„ loader ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋Š” ์„ค์น˜ํ•  ํ•„์š”๊ฐ€ ์—†๋‹ค).

import * as path from "path";
import HtmlWebpackPlugin from "html-webpack-plugin";

const __dirname = path.resolve();

export default {
  entry: {
    index: "./src/html/main/index.js",
    signIn: "./src/html/sign-in/index.js",
  },
  output: {
    filename: "[name].[contenthash].bundle.js",
    path: path.resolve(__dirname, "dist"),
    publicPath: process.env.NODE_ENV === "development" ? "/" : "/project-base-url/",
    assetModuleFilename: "images/[hash][ext][query]",
  },
  module: {
    rules: [
      {
        test: /\.scss$/,
        use: ["style-loader", "css-loader", "sass-loader"],
      },
      {
        // file loader
        test: /\.(png|svg|jpg|jpeg|gif)$/i,
        type: "asset/resource",
      },
      {
        // html loader
        test: /\.html$/,
        use: {
          loader: "html-loader",
        },
      },
    ],
    parser: {
      javascript: {
        commonjsMagicComments: true,
        dynamicImportMode: "lazy",
        dynamicImportPreload: true,
        dynamicImportPrefetch: true,
      },
    },
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: "./src/html/main/index.html",
      filename: "index.html",
      chunks: ["index"], // entry ์˜ JavaScript ์ด๋ฆ„๊ณผ ๋™์ผํ•˜๊ฒŒ ์ง€์ •. ํ•ด๋‹น JavaScript ๋ฅผ ๋ถˆ๋Ÿฌ์˜จ๋‹ค.
    }),
    new HtmlWebpackPlugin({
      template: "./src/html/sign-in/index.html",
      filename: "sign-in/index.html", // directory ๊ฐ€ routing ๊ธฐ๋Šฅ์„ ํ•˜๋„๋ก "๋ผ์šฐํŒ… ๊ฒฝ๋กœ/index.html" ๋กœ ์„ค์ •ํ•œ๋‹ค.
      chunks: ["signIn"],
    }),
  ],
};
name: Deploy

on:
  push:
    branches:
      - main

jobs:
  deploy:
    runs-on: ubuntu-22.04
    permissions:
      contents: write
    steps:
      - uses: actions/checkout@v3

      - name: Setup Node
        uses: actions/setup-node@v3
        with:
          node-version: '20'

      - name: Cache dependencies
        uses: actions/cache@v3
        with:
          path: ~/.npm
          key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
          restore-keys: |
            ${{ runner.os }}-node-

      - run: npm ci
      - run: npm run build

      - name: Deploy
        uses: peaceiris/actions-gh-pages@v3
        if: github.ref == 'refs/heads/gh-pages'
        with:
          github_token: ${{ secrets.GH_ACTION_TOKEN }}
          publish_dir: ./dist

์ˆ˜์ •ํ•œ ๋ถ€๋ถ„์„ ๋ณด์ž.

  • node-version: ๊ธฐ๋ณธ ํ…œํ”Œ๋ฆฟ์—๋Š” node 14๋กœ ๋˜์–ด์žˆ๋Š”๋ฐ ํ•ด๋‹น ๋ฒ„์ „์„ ์‚ฌ์šฉํ•˜๋ฉด ์ตœ์‹ ์œผ๋กœ ์„ค์น˜๋œ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์ผ๋ถ€๋ฅผ ์ฐพ์ง€ ๋ชปํ•˜๋Š” ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค. ๋”ฐ๋ผ์„œ ๋ฒ„์ „์„ ๊ฐœ๋ฐœ ํ™˜๊ฒฝ๊ณผ ๋™์ผํ•œ ๋ฒ„์ „์œผ๋กœ ์˜ฌ๋ ค์ค˜์•ผํ•œ๋‹ค.
  • publish_dir: ์œ„์—์„œ๋„ ์„ค๋ช…ํ–ˆ๋“ฏ์ด, webpack.config.js ํŒŒ์ผ์˜ output.path์™€ ์ผ์น˜ํ•˜๋„๋ก ์ˆ˜์ •ํ•ด์ค˜์•ผํ•œ๋‹ค. ๋”ฐ๋ผ์„œ ./dist๋กœ ์ˆ˜์ •๋˜์—ˆ๋‹ค.

3. Configure GitHub Repository Setting

์šฐ์„  ๊ฐ€์žฅ ์ค‘์š”ํ•œ ๊ฒƒ์ด secrets์— ๊นƒํ—ˆ๋ธŒ ํ† ํฐ์„ ๋“ฑ๋กํ•˜๋Š” ๊ฒƒ์ด๋‹ค. ํ…œํ”Œ๋ฆฟ์˜ ํ† ๋ฏ„ ์ด๋ฆ„์€ ์‚ฌ์šฉํ•  ์ˆ˜ ์—†์œผ๋ฏ€๋กœ GH_ACTION_TOKEN๋กœ ์ˆ˜์ • ํ›„ ํ† ํฐ์„ ๋“ฑ๋กํ•ด์ฃผ์—ˆ๋‹ค.

๋ชจ๋“  ๊ฒƒ์ด ์ž˜ ๋˜์—ˆ๊ณ , ํ•ด๋‹น Workflow ๋Š” ์ •์ƒ์ ์œผ๋กœ ์ˆ˜ํ–‰๋˜์—ˆ๋‹ค. ํ•˜์ง€๋งŒ ํŽ˜์ด์ง€๋ฅผ ๋ฐฉ๋ฌธํ•˜๋ฉด ๋œจ๊ธด ๋œจ๋Š”๋ฐ HTML ๋งŒ ๋œจ๊ณ , ์‹ฌ์ง€์–ด ์ œ๋Œ€๋กœ ๋นŒ๋“œ๋˜์ง€๋„ ์•Š์€ ์ฑ„ ๋–ด๋‹ค. ๋ฌธ์ œ์˜ ์›์ธ์€ ๋ ˆํฌ์ง€ํ† ๋ฆฌ ์„ค์ • ์ค‘ GitHub Pages ๋ถ€๋ถ„์ด์—ˆ๋‹ค(๊ณ„์ •์˜ Pages ๊ฐ€ ์•„๋‹Œ ํ•ด๋‹น ๋ ˆํฌ์ง€ํ† ๋ฆฌ ์„ค์ •์—์„œ Pages ๋ฅผ ๋“ค์–ด๊ฐ€์•ผํ•œ๋‹ค).

Repository Settings Pages

์œ„ Workflow ๋ฅผ ์ˆ˜ํ–‰ํ•˜๋ฉด ์ž๋™์œผ๋กœ gh-pages ๋ธŒ๋žœ์น˜์— ๋นŒ๋“œ๋œ ๊ฒฐ๊ณผ๋ฌผ์„ ์ปค๋ฐ‹์„ ํ•œ๋‹ค. ๋”ฐ๋ผ์„œ deployment ๋Œ€์ƒ ๋ธŒ๋žœ์น˜๋ฅผ main์—์„œ gh-pages๋กœ ๋ณ€๊ฒฝํ•ด์ค˜์•ผํ•œ๋‹ค.

Jekyll ์—์„œ main์„ deploy ํ•˜๋„๋ก ๋˜์–ด ์žˆ์–ด์„œ ์‹ ๊ฒฝ ์“ฐ์ง€ ์•Š์•˜๋Š”๋ฐ ์ด ๋ถ€๋ถ„์„ ๋†“์ณ ๋ช‡ ์‹œ๊ฐ„์„ ๊ณ ๋ฏผํ–ˆ์—ˆ๋‹ค. ๊ผญ ๋Œ€์ƒ ๋ธŒ๋žœ์น˜๋ฅผ ํ™•์ธํ•ด์•ผํ•œ๋‹ค!!

์ •์ƒ์ ์œผ๋กœ ๊นƒํ—ˆ๋ธŒ ํŽ˜์ด์ง€ ํ˜ธ์ŠคํŒ…์ด ๋˜์—ˆ๋‹ค!!


3. Netlify ๐Ÿ‘ฉโ€

Netlify ์— ์ง์ ‘ ํ˜ธ์ŠคํŒ… ํ•˜๋Š” ๊ฒƒ์ด ์•„๋‹Œ, GitHub ์— push ๋ฅผ ํ•˜๋ฉด ํŠน์ • ๋ธŒ๋žœ์น˜๋กœ๋ถ€ํ„ฐ ์ž๋™์œผ๋กœ ๋ฐฐํฌ๋˜๋„๋ก ์—ฐ๋™ํ•˜๋Š” ๊ฒƒ์— ๋Œ€ํ•ด ์„ค๋ช…ํ•œ๋‹ค. Netlify ๋กœ ๋ฐฐํฌํ•  ๊ฒฝ์šฐ ์œ„ GitHub Actions ๋Š” ์„ค์ •ํ•˜์ง€ ์•Š์•„๋„ ๋œ๋‹ค.

GitHub Pages ๋กœ ๋ฐฐํฌํ•  ๊ฒฝ์šฐ ๊ธฐ๋ณธ ๊ฒฝ๋กœ ์„ค์ •์„ ํ•˜์ง€ ์•Š์œผ๋ฉด root ๊ฐ€ repository ์ด๋ฆ„์„ ์ œ์™ธํ•œ ๊ฒฝ๋กœ๋กœ ์„ค์ •๋˜์„œ publicPath๋ฅผ ์„ค์ •ํ•ด์ฃผ์—ˆ๋‹ค. Netlify ๋ฅผ ์‚ฌ์šฉํ•  ๊ฒฝ์šฐ ๊ฐ๊ฐ ๋ฐฐํฌ๊ฐ€ ๊ฐ€๋Šฅํ•˜๋ฏ€๋กœ webpack.config.json์—์„œ output.publicPath: process.env.NODE_ENV === "development" ? "/" : "/project-base-url/"๋ฅผ ์‚ญ์ œํ•œ๋‹ค.

GitHub with Netlify 1

  • Base directory: ํ•ด๋‹น ๋ ˆํฌ์ง€ํ† ๋ฆฌ์—์„œ ๋ฐฐํฌํ•˜๋ ค๋Š” ์•ฑ์˜ ๊ธฐ๋ณธ ๊ฒฝ๋กœ๋‹ค. ํ•˜๋‚˜์˜ ๋ ˆํฌ์ง€ํ† ๋ฆฌ์— ๋””๋ ‰ํ† ๋ฆฌ๋ณ„๋กœ ์—ฌ๋Ÿฌ ๊ฐœ์˜ ์•ฑ์„ ๋ฐฐํฌํ•  ๊ฒฝ์šฐ, ํ•ด๋‹นํ•˜๋Š” ๊ฒฝ๋กœ๋ฅผ ์ž…๋ ฅํ•˜๋ฉด ๋˜๊ณ , ๋ ˆํฌ์ง€ํ† ๋ฆฌ ํ•˜๋‚˜์— ํ•˜๋‚˜์˜ ์•ฑ์ผ ๊ฒฝ์šฐ root ๊ฒฝ๋กœ์ด๋ฏ€๋กœ ๋น„์›Œ๋‘”๋‹ค (๊ฒฝ๋กœ๊ฐ€ ์ž…๋ ฅ๋  ๊ฒฝ์šฐ, โ€˜Package directoryโ€™, โ€˜Publish directoryโ€™, โ€˜Functions directoryโ€™ ์•ž์— base dir/์€ ์ž๋™์œผ๋กœ ์ž…๋ ฅ๋œ๋‹ค).
  • Build command: npm ci๋Š” Netlify ๊ฐ€ ์•Œ์•„์„œ ์ง„ํ–‰ํ•˜๋‹ˆ npm run build๋งŒ ์ž‘์„ฑํ•˜๋ฉด ๋œ๋‹ค.
  • Publish directory: webpack ์˜ output.path๋ฅผ ์ž…๋ ฅํ•˜๋ฉด ๋œ๋‹ค.

GitHub with Netlify 2

GitHub with Netlify 3

GitHub with Netlify 4


๋งŒ์•ฝ, ์–‘์ชฝ์— ๋ชจ๋‘ ๋ฐฐํฌ๋ฅผ ์›ํ•  ๊ฒฝ์šฐ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์„ค์ •์„ ๋‚˜๋ˆŒ ์ˆ˜ ์žˆ๋‹ค.

1 ) script ๋ช…๋ น์— ๋ณ€์ˆ˜๋ฅผ ์‚ฌ์šฉํ•ด ๋ถ„๋ฆฌํ•˜๋Š” ๋ฐฉ๋ฒ•

{
  "scripts": {
    "serve": "webpack serve --mode=development --node-env=development --open",
    "watch": "webpack --watch --mode=development --node-env=development",
    "build-github": "webpack --mode=production --node-env=production --base-url=project-base-url",
    "build-netlify": "webpack --mode=production --node-env=production"
  }
}
export default {
  output: {
    filename: "[name].[contenthash].bundle.js",
    path: path.resolve(__dirname, "dist"),
    publicPath: process.env.BASE_URL === undefined ? "/" : process.env.BASE_URL,
    assetModuleFilename: "images/[hash][ext][query]",
  },
};

project-base-url ์€ /github-name/repository-name/ ํ˜•ํƒœ๋กœ ์ž‘์„ฑํ•œ๋‹ค.

2 ) webpack configuration ํŒŒ์ผ๋กœ ๋ถ„๋ฆฌํ•˜๋Š” ๋ฐฉ๋ฒ•

{
  "scripts": {
    "serve": "webpack serve --mode=development --config webpack.development.config.js --node-env=development --open",
    "watch": "webpack --watch --mode=development --config webpack.development.config.js --node-env=development",
    "build-github": "webpack --mode=production --config webpack.prod-github.config.js --node-env=production",
    "build-netlify": "webpack --mode=production --config webpack.prod-netlify.config.js --node-env=production"
  }
}
// webpack.development.config.js
export default {
  output: {
    filename: "[name].[contenthash].bundle.js",
    path: path.resolve(__dirname, "dist"),
    assetModuleFilename: "images/[hash][ext][query]",
  },
};

// webpack.prod-github.config.js
export default {
  output: {
    filename: "[name].[contenthash].bundle.js",
    path: path.resolve(__dirname, "dist"),
    publicPath: "project-base-url",
    assetModuleFilename: "images/[hash][ext][query]",
  },
};

// webpack.prod-netlify.config.js
export default {
  output: {
    filename: "[name].[contenthash].bundle.js",
    path: path.resolve(__dirname, "dist"),
    assetModuleFilename: "images/[hash][ext][query]",
  },
};

project-base-url ์€ /github-name/repository-name/ ํ˜•ํƒœ๋กœ ์ž‘์„ฑํ•œ๋‹ค.

3 ) ํ˜ธ์ŠคํŒ… ์„œ๋ฒ„์˜ ํ™˜๊ฒฝ๋ณ€์ˆ˜๋กœ ๋ถ„๋ฆฌํ•˜๋Š” ๋ฐฉ๋ฒ•

BASE_URL์„ GitHub Pages ๋‚˜ Netlify ์— ํ™˜๊ฒฝ๋ณ€์ˆ˜๋ฅผ ์„ค์ •ํ•˜๊ณ , ์ด๋ฅผ ๊ฐ€์ ธ๋‹ค ์‚ฌ์šฉํ•˜๋„๋ก ํ•  ์ˆ˜ ์žˆ๋‹ค.

{
  "scripts": {
    "serve": "webpack serve --mode=development --node-env=development --open",
    "watch": "webpack --watch --mode=development --node-env=development",
    "build": "webpack --mode=production --node-env=production"
  }
}
export default {
  output: {
    publicPath: process.env.NODE_ENV === "development" ? "/" : "/project-base-url/",
  },
};

GitHub Pages ์˜ project-base-url ์€ /github-name/repository-name/ ํ˜•ํƒœ๋กœ ์ž‘์„ฑํ•˜๊ณ , Netlify ๋Š” /๋กœ ์ž‘์„ฑํ•œ๋‹ค.

์œ„ ์„ธ ๊ฐ€์ง€ ์ค‘ ์–ด๋–ค ๋ฐฉ๋ฒ•์„ ์‚ฌ์šฉํ•˜๋“ , output.publicPath๋Š” HTML, JavaScript, Images ์™€ ๊ฐ™์€ Network ์š”์ฒญ์„ ๋ณด๋‚ผ ๋•Œ ๊ธฐ๋ณธ URL ์„ ์ถ”๊ฐ€ํ•ด์ค€๋‹ค. ํ•˜์ง€๋งŒ <a href="/sign-in">๊ณผ ๊ฐ™์€ ๊ฒฝ๋กœ๋Š” ์ˆ˜์ •๋˜์ง€ ์•Š๋Š”๋‹ค. ์ฆ‰, ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•  ๋•Œ BASE_URL ์„ ํ™˜๊ฒฝ๋ณ€์ˆ˜๋กœ๋ถ€ํ„ฐ ๊ฐ€์ ธ์˜ค๋„๋ก ์ฝ”๋”ฉ์„ ํ•ด์•ผํ•œ๋‹ค. ๋”ฐ๋ผ์„œ 2ํŽ˜์ด์ง€ ์ด์ƒ์ผ ๊ฒฝ์šฐ, Github Pages ๋กœ ๋ฐฐํฌํ•˜๋Š” ๊ฒƒ์€ ๊ทธ๋ƒฅ Webpack ์—†์ด HTML, JavaScript ๋ฅผ ์ง์ ‘ ๋ฐฐํฌํ•˜๊ฑฐ๋‚˜, React ๋กœ ๋ฐฐํฌํ•ด์•ผํ•œ๋‹ค. Vanilla JS ํ”„๋กœ์ ํŠธ๊ฐ€ 2ํŽ˜์ด์ง€ ์ด์ƒ์ผ ๊ฒฝ์šฐ ๋ฐฐํฌ๋Š” Netlify ๋กœ ํ•˜๋Š” ๊ฒƒ์ด ๋” ์ข‹๋‹ค.




Reference

  1. peaceiris, โ€œactions-gh-pages.โ€ GitHub. Mar. 31, 2023, [https://github.com/realm/SwiftLint].
  2. โ€œ์ œ๋ฐœ ๊นƒํ—ˆ๋ธŒ ์•ก์…˜๐Ÿ”ฅ ๋ชจ๋ฅด๋Š” ๊ฐœ๋ฐœ์ž ์—†๊ฒŒํ•ด ์ฃผ์„ธ์š” ๐Ÿ™.โ€ Youtube. Jun. 28, 2022, ์ œ๋ฐœ ๊นƒํ—ˆ๋ธŒ ์•ก์…˜๐Ÿ”ฅ ๋ชจ๋ฅด๋Š” ๊ฐœ๋ฐœ์ž ์—†๊ฒŒํ•ด ์ฃผ์„ธ์š” ๐Ÿ™.
  3. โ€œAsset Modules.โ€ Webpack Docs. Feb. 27, 2024, https://webpack.js.org/guides/asset-modules/.