Webtoon 토이프로젝트를 진행하다보니 프론트엔드 서버(react)와 백엔드 서버(를 따로 빌드하고, 실행하는 것이 약간 불편하게 느껴지기 시작했다..
그래서 'Spring 프로젝트에 프론트엔드 파일들을 넣어두고 Gradle을 통해서 build 한번에 프론트와 백엔드 모두 Build 할 수 있지않을까?' 라는 생각이 들어 서칭을 시작해보았다.
아니나다를까 이미 그런 생각을 하신 분들이 많았고, 좋은 예제들이 있었다. 그래서 찾았던 예제를 바탕으로 내가 직접 적용해본 과정을 기록해보기로 했다.

해당 프로젝트의 전체 코드는 이 곳에서 볼 수 있습니다.

1. Frontend 파일 Spring Boot Project로 옮기기

## spring projcet root

|- frontend
|    - webtoon
|        - ...
|- src
|    - main
|        - java
|            - com.essri.webtoon
|                - ...
  • 내가 옮길 프론트엔드 프로젝트 디렉토리의 이름은 webtoon, 위의 구조처럼 우선 스프링 프로젝트의 최상단에 frontend 디렉토리를 생성하고, frontend 폴더 하위로 옮겨주었다.

2. frontend 프로젝트의 package.json 수정하기

{
  "name": "webtoon-react",
  "version": "1.0.0",
  "description": "",
  "main": "App.js",
  "dependencies": {
    "acorn": "^7.1.0",
    "bootstrap": "^4.3.1",
    "react": "^16.8.6",
    "react-dom": "^16.8.6",
    "react-router": "^5.0.1",
    "react-router-dom": "^5.0.1",
    "react-scripts": "3.0.1",
    "reactstrap": "^8.1.1",
    "stringquery": "^1.0.8",
    "webpack": "4.29.6"
  },
  "devDependencies": {},
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject"
  },
  "author": "Mark",
  "license": "ISC",
  "browserslist": {
    "production": [
      ">0.2%",
      "not dead",
      "not op_mini all"
    ],
    "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
    ]
  }
}
  • 내가 진행했던 package.json 파일은 위와 같다. 사실 크게 수정할 내용은 없으며 기본적으로 create-react-app을 했을때 적용되어지는 라이브러리들만 포함하고 있으면 문제없다.

3. Gradle 수정 및 Task 추가하기

3.1 nodejs 의존 추가

classpath("com.moowork.gradle:gradle-node-plugin")
  • gradle build를 통해서 react project도 빌드되려면 기본적으로 nodejs가 필요하다. 그래서 위와 같이 buildscript에 의존을 추가해주어서 nodejs를 설치할 수 있게한다.

3.2 Task 추가

def webappDir = "$projectDir/frontend/webtoon"

// node 버전 및 npm, node 설치파일 저장 디렉토리 명시
node {
    version = '12.6.0'
    download=true
    workDir = file("${project.buildDir}/nodejs")
    npmWorkDir=file("${project.buildDir}/npm")
}

// npm install 과정
task appNpmInstall(type: NpmTask) {
    workingDir = file("${project.projectDir}/frontend/webtoon")
    args = ["install"]
}

// yarn build
task yarnBuild(type: YarnTask) {
    workingDir = file("${project.projectDir}/frontend/webtoon")
    args = ['build']
}

// 빌드된 결과 resources로 이동
task copyWebApp(type: Copy) {
    from "frontend/webtoon/build"
    into 'build/resources/main/static/.'
}

yarnBuild.dependsOn appNpmInstall
copyWebApp.dependsOn yarnBuild
compileJava.dependsOn copyWebApp
  • react 프로젝트를 빌드하기위해서 npm installreact build를 진행하고, 그 결과를 백엔드에서 사용할 수 있게 resources 폴더로 옮겨주는 과정을 Gradle에 작성해주었다.

4. Build 하기 !!

  • 프로젝트 최상단에서 gradle clean build를 해주게 되면 빌드가 진행된다. (test코드를 제외하고싶다면 -x test를 붙여주면 된다.)

5. 발생했었던 예외상황들..

5.1 travis build error

  • 이 프로젝트는 travis CI를 통해 빌드하고, AWS의 CodeDeploy를 통해 배포하도록 되어있었다. 여기에서 프로젝트 파일들을 압축할 때 node_modules 폴더를 포함시키는 바람에 빌드로그가 너무 많이 나오게 되어서 빌드를 실패했다. => 압축할 때 -x 옵션으로 불필요한 파일들을 제외시켜주었다.

5.2 Route 문제

  • 처음에 전부 파일들을 병합하고 빌드하는데까지 무리없었지만 ViewResolver를 분명히 서버코드에 작성하지 않았었는데 어떻게 메인페이지가 출력된다는걸까? 하고 의심했었다.
  • 내 리액트 프로젝트는 페이지가 하나 뿐이라서 route를 설정을 하지않아도 될 줄 알았다.. 설정 전에는 역시 리액트 프로젝트의 메인페이지에 제대로 접근하지못했고 계속 삽질만 하다가.. 깨닫게 되었다..

5.3 CodeDeploy 에러

deploy-error.png

  • 처음에는 빌드 후 배포가 잘되더니 계속 프로젝트파일을 압축 후 CodeDeploy를 통해 배포하는 부분에서 뻗어버렸다.. 아무래도 프론트엔드 프로젝트를 합쳐서 빌드한 후로 부터 에러가 나기 시작했기 때문에 배포하는 용량적인 측면이 아닐까하고 생각하고, 서칭을 시작했다.
  • 결국 찾은 해결방법은 공식문서 였다.

6. 후기..

  • 아무리 봐도 문제점이 없어보일 때는 잠깐 쉬고, 보면 보일 때가 더 많은 것 같다.. 정말 간단한 문제들이였지만 이번에는 삽질을 너무 많이 했다..