본문 바로가기

001Project

점진적으로 Vue 프레임워크 적용하기 (Vanilla Js -> Vue)

None Framework 프로젝트에 점진적으로  Framework 적용하기

0. 들어가기 앞서

None Framework 프로젝트를 프레임워크로 교체하기 위해서는 기존의 기능 및 디자인 등을 똑같이 다시 구현해야 하고, 이는 몇 개월 이상의 비용이 필요한 큰 작업입니다.

그런데 프레임워크가 적용되지 않은 프로젝트에 부분적으로 프레임워크를 적용할 순 없을까요?🤔

예를 들어 하나의서비스에서, 어느 페이지는 html로 만든 페이지이고 어떤 페이지는 vue 프로젝트를 build 하여 만들어낸 페이지인 것으로 말이죠.

이번 글에서는 점진적으로 프레임워크를 적용하기 위해 시도한 방법에 다뤄보려고 합니다.

기존 진행하던 프로젝트가 있다고 가정하여 간단한 예제를 만든 다음 일부 페이지를 Vue로 바꿔나가 보도록 하겠습니다.

(모든 코드는 github에 있습니다.)

1. legacy 코드 구성 (예제 구성)

먼저 우리가 프레임워크를 적용해야 할 대상인 legacy 프로젝트에 구조에 대해 알아보겠습니다.

기존 프로젝트의 환경의 기술 스택은 다음과 같습니다.

세세하게 어떤 기술을 사용했는지보다 프레임워크를 사용하지 않았다는 점만짚고 넘어가시면 됩니다.

백엔드 - Java / SpringBoot
프론트엔드 - Html / Css / JavaScript / ... (여튼 none framework)

그리고 현재 구현된 페이지와 기능은 다음과 같다고 가정하겠습니다.

1. index(홈) 페이지
2. 회원가입 페이지 - 회원 가입 가능
3. 로그인 - 로그인 가능

 

legacy 프로젝트 구조
index.html / join.html / login.html



WebController.java

@Controller
public class WebController {
    @GetMapping("/")
    public String index() {
        return "index";
    }

    @GetMapping("/login")
    public String login() {
        return "login";
    }

    @GetMapping("/join")
    public String join() {
        return "join";
    }

    @GetMapping("/info")
    public String info() {
        return "info";
    }
}

 

2. Vue 프로젝트 만들기

이번 글에서는 'index페이지와 로그인 페이지를 Vue 프로젝트로 바꾸기'로 목표를 설정하겠습니다.

그러기 위해서 첫 단계로 Vue 프로젝트를 생성하고, index 페이지와 login페이지를 Vue 프로젝트 안에 만들어 주겠습니다.

이때의 중요한 부분은 <router-link>로 라우팅을 해줄 때, Vue에 구현하지 않은 페이지에 대해서는 <router-link> 대신 <a> 태그를 사용한다는 점입니다. 그래야 legacy의 controller를 따라 legacy의 소스파일로 접근할 수 있습니다.

vue-project > src > App.vue

<template>
  <div id="app">
    <h1>점진적으로 프레임워크로 개발하기 - vue</h1>
    <div class="nav">
      <ul>
        <li><router-link class="nav-item" to="/">홈</router-link></li>
        <li><router-link class="nav-item" to="/login">로그인</router-link></li>
              //router-view가아닌 a태그사용
        <li><a class="nav-item" href="/join">회원가입</a></li>
      </ul>
    </div>
    <router-view />
  </div>
</template>

<style lang="stylus">
...
</style>

그리고 WebController.java 에서는 "/login"와 login.html을 맵핑 해주었던 메서드와 login.html을 제거해줍니다.

 

수정된 WebController.java

@Controller
public class WebController implements ErrorController {
	//주소로 vue router 새로고침시 에러 관련
    @GetMapping({"/", "/error"})
    public String index() {
        return "index";
    }

    @Override
    public String getErrorPath() {
        return "/error";
    }


	//login맵핑 관련 코드 삭제


    @GetMapping("/join")
    public String join() {
        return "join";
    }
  
}

지금까지 상황을 정리해보면 다음과 같습니다.

- index페이지와 로그인 페이지를 Vue로 구현했음
- 회원가입 페이지는 여전히 legacy프로젝트의 소스를 활용해야 함

 

즉, index페이지("/")와 로그인 페이지("/login")에 접근하면 Vue의 소스파일을 읽도록 하고, 가입 페이지("/join")에 접근하면 기존 legacy의 소스 파일("join.html")을 읽도록 해야 합니다.

다음 단계에서 이를 진행해보도록 하겠습니다.

3. Vue와 legacy Mix

아래 명령어를 이용하여 Vue 프로젝트를 build 해줍니다.

npm run build

build의 결과물인 dist파일을 legacy 코드의 resources경로로 이동시켜줍니다.
그런 다음 dist파일의 index.html 파일을 templates경로로 이동시켜줍니다.

 

이렇게 되면 "/"로 접근하였을 때, templates의 index.html 을 찾아가겠지만 index.html은 dist의 js, css파일들을 필요로 합니다.

그리고 join.html은 static안의 js, css, img파일들을 필요로 합니다.

즉 static 디렉토리와 dist 디렉토리, 두 개의 리소스 파일을 참고하도록 설정해주어야 합니다.
이를 위해 MvcConfiguration.java를 생성해줍니다.

@Configuration
public class MvcConfiguration implements WebMvcConfigurer {

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
    
    	// js, css, img파일들에 대해서 dist와 static 파일을 참조한다.
        registry.addResourceHandler(
                "/js/**",
                "/css/**",
                "/img/**"
        )
                .addResourceLocations(
                        "classpath:/dist/js/",
                        "classpath:/dist/css/",
                        "classpath:/dist/img/",
                        "classpath:/static/js/",
                        "classpath:/static/css/",
                        "classpath:/static/img/"
                )
                .setCacheControl(CacheControl.maxAge(10, TimeUnit.MINUTES));
    }
}

 

이상태로 애플리케이션을 실행해보면 index와 로그인 페이지는 vue프로젝트에서 작성한 대로, 회원가입 페이지는 기존 legacy코드에서 작성한 대로 나오는 것을 확인할 수 있습니다.

IndexPage.vue / Login.vue / join.html

 

다음 단계에서는 gradle build시에 Vue를 build시키고 생성된 dist와 index.html을 자동으로 이동시키는 방법에 대해 알아보겠습니다.

4. build파일 자동으로 resources로 이동시키기

첫째로, Vue build 후 dist파일을 resources 위치로 이동시키겠습니다.

아래 명령어를 통해 필요한 파일을 다운로드합니다.

npm install request

npm install path

Vue 프로젝트 경로에 vue.config.js 파일을 작성해줍니다.

const path = require("path");

module.exports = {
  outputDir: path.resolve(__dirname, "../src/main/resources/dist")
}

이러면 Vue build시 dist파일이 legacy 파일의 resources/dist에 위치하게 됩니다.

다음으로는 gradle build 시에 Vue가 build 되게 하고, 이어서 index.html을 templates경로로 복사되도록 합니다.

 

build.gradle

plugins {
	...
    id 'com.moowork.node' version '1.3.1'
}

...

node {
    workDir = file("./vue-project")
    npmWorkDir = file("./vue-project")
    nodeModulesDir = file("./vue-project")
}

task setUp(type: NpmTask) {
    description = "Install Node.js Package"
    args = ['install']
}

task buildFrontEnd(type: NpmTask, dependsOn: setUp) {
    description = "Build vue.js"
    args = ['run', 'build']
}

task copyIndex(type: Copy, dependsOn: buildFrontEnd) {
    description = "copy index.html"
    from "./src/main/resources/dist/index.html"
    into "./src/main/resources/templates"
}

processResources.dependsOn 'copyIndex'

 

./gradlew clean build를 해보면 resources경로에 dist파일이 생기고 templates 경로에 index.html이 위치한 것을 확인할 수 있습니다.

5. 정리

1. 프론트 프레임워크를 사용하지 않는 legacy가 존재한다.
2. 프레임워크로 수정하고 싶은 페이지를 프레임워크를 사용하여 구현한다.
3. 구현한 페이지에 대해, legacy에서 해당 페이지에 관한 Controller를 제거한다.
4. 프레임워크를 build한 후, 생성된 파일을 legacy의 resources에 위치시킨다.(이 때, index.html파일은 templates에 위치한다.)
5. config파일을 활용하여 리소스 경로를 추가해준다.

 

6. 마치며...

공유하고자 했던 내용은 모두 다룬 것 같습니다.

하지만 아직 실제로 서비스에 위 방법을 적용해본 것은 아니며 React를 사용해본 경험이 없는터라 React에서 동일하게 적용 가능할지 잘 모르겠습니다. (React를 공부해보고 React의 경우도 시도해보고 공유드리겠습니다.)


우선은 하나의 애플리케이션이 Vue 프로젝트의 build된 파일과 legacy 리소스 파일에 선택적으로 접근하게 함으로써, legacy를 점진적으로 프레임워크로 마이그레이션 하는 방법에 대해서 생각을 해보았습니다.

혹시 비슷한 고민을 했던 분들에게 조금이나마 도움이 되셨기를 바라는 마음입니다.

 

위 방법을 직접 서비스에 적용해보고 마이그레이션 해 볼 생각입니다.

가능하다면 '점진적으로 프론트 프레임워크 적용하기 2탄'을 작성해보도록 하겠습니다. 감사합니다.

 

 

 

 

반응형