AngularJS 와 RequireJS 를 활용한 대규모 웹 어플리케이션 개발

AngularJS 기반 웹 어플리케이션, 어디서부터 시작해야 할까?

AngularJS를 기반으로 웹 어플리케이션을 개발할 때, AngularJS 사이트에서 제공해주는 문서들은 특정 API 활용 방법을 이해하거나 단순한 수준으로 시작하기에는 적합하지만 어떻게 수백에서 수천줄에 이르는 코드로 어플리케이션을 구성하고 발전시켜야하는지에 대해서는 명확한 가이드라인을 제공해주고 있지 않습니다. AngularJS를 기반으로 수차례의 크고 작은 개발을 진행하며 얻은 경험 지식과, 이를 기반으로 대규모 웹 어플리케이션 개발이 가능한 UI 프레임워크를 만드는 과정에서 고민하고 연구한 결과물을 정리해 보았습니다.

이 글은 대규모 웹 어플리케이션 개발에 있어 정답을 제시하는 글이 아니며, 대규모 웹 어플리케이션을 고려한 UI 프레임워크를 개발하는 과정에서 나온 산출물들 중 일부를 설명과 곁들여 공개하는 것입니다. 따라서 대규모 웹 어플리케이션 개발을 위한 인사이트를 얻는 방향으로 접근하시는 것이 좋습니다.

먼저는 파일과 디렉토리 구조를 어떻게 구성해야 하는지에서부터, 주요 자바스크립트 파일이 어떻게 구동되는지 하나하나 살펴보게 될 것입니다.

 

파일 및 디렉토리 구성

Brain Ford는 “Building Huuuuuge Apps with AngularJS” 라는 포스팅에서 대규모 어플리케이션을 개발할 때 중요한 것은 작고 집중되고 모듈화된 부분으로 개발해서 점진적으로 확장시켜나가는 것이 중요하다고 조언했습니다. 하지만 이렇게 대규모 어플리케이션을 어떠한 분류로 나누고 또 어떤 구조로 구성해야하는지에 대한 고민이 또다시 생기게 됩니다.

나누어진 부분들은 필연적으로 파일과 디렉토리로 분류되게 되는데, angular seed project 등에서도 권장하는 디렉토리 구조가 있고, 또 웹 상에서 검색해보면 다양한 구조로 파일과 디렉토리를 나누는 방법을 찾을 수 있습니다.

여기에서는 현재까지 여러 프로젝트에서 누적된 경험을 바탕으로, 모듈화된 AngularJS 프로젝트에 가장 적합한 파일과 디렉토리 구조를 소개하고자 합니다.파일 및 디렉토리 구조

  • 먼저 css와 img에는 각각 어플리케이션에서 공통적으로 사용되는 스타일시트와 이미지 데이터가 들어가게 됩니다.
  • lib 폴더에는 angular, jquery 등 어플리케이션에서 사용되는 라이브러리들이 위치합니다.
  • partials 폴더에는 ng-view에 뿌려줄 html template 파일들이 위치합니다.
  • js 폴더에는 모듈화된 스크립트 파일들이 위치하게 됩니다.

js 폴더와 partials 폴더 안의 각 파일들은 반드시 한 가지의 내용만 담고 있는 것을 권장합니다. 예를 들어, 자바스크립트 파일 하나에는 오직 하나의 컨트롤러 혹은 오직 하나의 다이렉티브만 담겨있어야 한다는 것입니다. 프로젝트의 규모가 커져서 파일이 많아질수록, 더 많은 서브 디렉토리를 두어서 구조를 더욱 체계화하여 관리할 필요가 있습니다.

이러한 파일과 디렉토리 구조는 앞서 이야기한 작고 집중되고 모듈화된 부분으로 개발하려는 방향성을 고민하여 설계된 것입니다. 이렇게 모듈화된 구조를 통해 필요한 부분만 선별적으로 로딩할 수 있게되고 초기 로딩 속도에 크게 영향을 받는 UX 개선에 도움을 얻을 수 있습니다. 모듈화와 동적 로딩은 requireJS를 기반으로 구현되었으며, 이에 관련해서는 이 글에 자세히 설명되어 있습니다.

 

웹 어플리케이션 구조

웹 어플리케이션 구조

전체적인 어플리케이션 구조는 위와 같습니다. document 전체에 대해 myApp 이라는 모듈이 부트스트래핑되고, html 태그에 CommonController 라는 컨트롤러가 자리합니다. CommonController에는 전체 메뉴와 같이 어플리케이션 전반에서 공통적으로 반복해서 사용되는 부분에 대한 컨트롤을 담당하게 됩니다.

ng-view directive가 있는 부분이 동적으로 로딩되는 부분으로, partials 폴더 아래에 있는 partial view 템플릿들을 가져와서 뿌려주게 됩니다. 이렇게 로딩되는 view 마다 각 view에 해당하는 컨트롤러 역시 동적으로 로딩되어 할당됩니다.

이러한 구조에 대한 이해를 바탕으로, 실제 어떤 흐름으로 웹 어플리케이션이 구동되며 모듈들이 관리되는지를 이어서 살펴보겠습니다.

 

웹 어플리케이션 흐름

웹 어플리케이션 흐름

웹 어플리케이션의 전체적인 큰 그림은 위와 같습니다.

index.html 파일을 열면 여기에 require.js 파일을 script 태그로 가져오게 됩니다. RequireJS 는 로드된 후 data-main 속성에 따라 가장 먼저 main.js 파일을 동적으로 불러와서 실행하게 됩니다.

main.js 파일은 RequireJS 모듈 형태로 선언되어 있는데, 디펜던시 선언부 및 디펜던시 로드 뒤 실행부로 나뉘어져 있습니다. 실행부에는 myApp Angular 모듈을 부트스트래핑하도록 되어 있지만, 그 전에 디펜던시들이 먼저 로드 되어야 하므로, angularJS, jQuery 등의 라이브러리와 app.jsroute-config.js,routes.js 파일이 로드된 뒤에야 부트스트래핑이 진행되게 됩니다.

app.js 파일 역시 RequireJS 모듈 형태로 선언되어 있는데, 실행부에는 myApp 모듈 및 CommonController를 선언하는 내용으로 되어 있습니다. 이 내용 역시 바로 실행되지 않고 그 전에 디펜던시가 로드된 뒤 실행되게 됩니다. 따라서 디펜던시인 route-config.js 파일의 로드를 먼저 처리합니다.

route-config.js 파일 역시 RequireJS 모듈 형태로 동적으로 서비스, 다이렉티브, 필터 등을 로드할 때 필요한 스크립트들이 디펜던시로 등록되어 있습니다. 이 디펜던시들이 로드되면, AngularJS에서 경로(route) 설정 시 사용할 config 함수를 선언합니다.

route-config.js 파일의 처리가 끝나면 다시 역으로 app.js 파일의 실행부가 처리되고, 이어서 route-config.js 파일과 app.js 파일을 디펜던시로 갖고 있던 route.js 파일의 실행부가 처리됩니다. route.js 파일의 실행부에는 AngularJS의 경로 설정 로직이 있습니다.

여기까지 모든 디펜던시들의 처리가 끝나고 나면 다시 main.js 파일의 실행부가 처리되게 됩니다. 이 시점에서 myApp 모듈의 부트스트래핑이 작동되어 Angular Application 이 실행되게 됩니다.

이때, 입력된 route 에 따라서 해당 partial view template 파일과 controller 가 동적으로 로드됩니다.

 

다시 정리하면, RequireJS 를 통해 설정된 디펜던시들이 로드된 후 비로소 Angular Application이 시작되는 형태로, 또 각 route 마다 RequireJS가 template 과 controller 등을 동적으로 가져와서 추가해주는 방식으로 구현되어 있습니다.

그럼 이제, 각 스크립트 파일 안의 로직이 실제로 어떻게 동작하는지 구체적으로 살펴보겠습니다.

 

세부 흐름 분석

흐름의 시작은 index.html 파일입니다. require.js 에 대한 포스팅에서 이미 설명한 것과 크게 다르지 않은 구조로 설계되어 있습니다.

 

index.html

기본적으로 반드시 필요한 스타일시트는 link 태그로 먼저 입력하지만, 페이지마다 동적으로 필요한 스타일시트는 하단의 ng-repeat과 ng-href 를 활용해서 동적으로 로드될 수 있도록 설계되었습니다. IE8에서도 정상 동작하는 방식입니다.

하단의 require.js 파일을 로드해주는 부분에 data-main 속성을 설정해서 RequireJS가 로드된 후 바로 로드해서 실행해줄 JavaScript 파일을 지정해줄 수 있는데, 위의 예에서는 require.js 파일이 로드된 후에 바로 js 폴더 아래에 main.js 파일을 불러와서 실행하도록 되어 있습니다.

index.html 파일에서는 구체적으로 어떤 라이브러리들이 사용되는지 감추어짐으로써 전체적인 코드의 가독성도 높아지는 것을 볼 수 있습니다.

 

main.js

require.js 파일이 로드된 뒤 가장 처음 불러오는 파일인 main.js 스크립트는 크게 두 부분으로 나누어져 있습니다.

먼저는 RequireJS 의 환경을 설정하는 부분이고, 설정이 끝난 뒤에는 require 함수를 사용해서 디펜던시를 불러온 뒤 angular module을 부트스트래핑 하게 됩니다. 환경 설정에 대한 부분은 지난 require.js 에 대한 글에 충분하게 설명되어 있습니다.

앞서 흐름에 대해 살펴볼때 이야기했던 것처럼 angular module이 부트스트래핑 되는 것은 나열된 디펜던시들이 모두 로드된 시점이므로 모든 흐름의 끝에 실행됩니다.

나열된 디펜던시 중 ‘text’ 라는 것은 require.js 의 text plug-in 으로 스크립트 파일이 아닌 텍스트 형태의 파일을 동적으로 가져올 수 있도록 해주는 기능을 합니다.

 

app.js

AngularJS나 jQuery 와 같은 라이브러리 외에 main.js의 디펜던시로 걸린 파일 중 하나가 app.js 파일입니다. 이 디펜던시 설정으로 인해 app.js 파일이 로드되어 실행 된 후에야 main.js 파일의 실행된다는 것은 앞서 언급한 부분입니다.

app.js 파일에도 역시 디펜던시 설정이 걸려 있는데, AngularJS의 route 설정을 위한 route-config.js 파일입니다.  route-config.js 파일이 로드된 뒤에 app.js 에서는 myApp 모듈을 선언합니다. AngularJS는 모듈  선언시에만 접근할 수 있는 provider 들이 있는데 Lazy Loading을  위해서  이  provider 들을 별도로 저장해둡니다. 이 provider 들을  저장하는 부분이 route-config.js 에 구현되어 있기 때문에 route-config.js 파일이 디펜던시로 잡혀있는 것입니다.

또한 app.js 파일에는 앱 전체적으로 공통적으로 사용되는  CommonController가  선언되어  있습니다. 사실 각 partial view마다 또 컨트롤러가 존재하는데, 이 공통 컨트롤러에서는 partial view 외에 부분에서 사용되는 내용들이 위치하게 됩니다. 예를들어, 위의 소스에서는 동적으로 스타일 시트를  업데이트하는 로직이 들어 있는 것을  볼  수 있습니다. 이 외에도 앱 전체 메뉴를  설정하는  부분 등도 이 컨트롤러에 포함될 수 있습니다.

여기에서는 컨트롤러 하나만 myApp 모듈에 추가하고  있지만, 공통적으로 사용되는 directive가  존재한다면 이  app.js 파일에서 선언해서 추가해 주는 것도 가능합니다. 그  외에도 공통적으로 사용되고 동적으로 추가될 필요가 없는 value 값이나 service, filter 등도 여기에서 선언해두  추가해주는 것이 좋습니다.

 

 route-config.js

앞서 app.js 파일에 이 route-config.js 파일이 디펜던시로 잡혀있었기 때문에 route-config.js 파일이 먼저 실행되게 됩니다.

또 route-config.js 파일에는 각각 directive, service, filter 를 동적으로 등록시켜줄 때 사용되는 lazy-directives, lazy-services, lazy-filters 가 디펜던시로 잡혀있기 때문에 이 파일들이 로드된 뒤에 route-config.js 파일이 실행됩니다.

디펜던시가 로드된 뒤에는 route 설정과 관련된 함수들이 선언됩니다. 각각의 provider를 저장하는 함수들과 path에 따라 lazy-loading  을 구현하는 부분이 config 함수에 선언됩니다.

소스에 주석으로 설명이 되어 있지만 간단하게 전체 로직을 살펴보자면, 템플릿과 스크립트 파일 등을 파라메터로 받아서 차후에 호출이 들어올 경우 requireJS로 이들을 동적으로 가져와 등록하도록 예약하는 로직입니다.

 

route.js

route-config.js 파일과 app.js 파일을 디펜던시로 갖고 있던 route.js 파일의 실행부가 처리됩니다. route.js 파일의 실행부에는 AngularJS의 경로 설정 로직이 있습니다.

모듈의 config 메서드를 사용해서 각각의 경로를 설정해주게 되는데, 여기에서는 4개의 경로만 설정해주었습니다. 실제 프로젝트에서는 $http 서비스 등을 사용해서 메뉴  관련 데이터를 JSON으로 동적으로 받아와서  처리해주는  방법도 고민해볼  수 있습니다.

 

이렇게 route.js 파일도 로드  및 실행이 완료되고 나면 다시 main.js 파일의 콜백 함수 부분으로 돌아가게 되고  비로소 myApp 모듈이 부트스트래핑되며  Angular Application 이 실행됩니다.

 

동적으로 로딩되는  컨트롤러 예 – grid.js

path 가 grid 일 경우 grid.js 파일이 컨트롤러로서 동적으로 로드됩니다.

RequireJS module 형태로 선언되어 있는데, 이 grid 메뉴의 화면에는 pqGrid를 사용하므로 디펜던시로 pqGrid 라이브러리를 넣고 있는 것을 볼 수 있습니다.

위와 같이 define으로 모듈을 선언한 뒤에 첫 파라메터로 배열 형태로 필요한 디펜던시를 선언합니다.

 

디펜던시 로드가 완료되면 아래 콜백 함수가 실행되는데, 콜백함수에서는 Angular Controller 형태로 함수를 선언해서 이 함수를 리턴해주는 것을 볼 수 있습니다.

또 동적으로 CSS를 설정해주기 위해 컨트롤러 내부에 $emit 으로 추가하고자하는 css 의 경로를 보내게 됩니다. 이러한 방식으로 CSS를 동적으로 추가/제거 해줌으로써 너무 많은 CSS 추가로 인해 스타일이 충돌하는 것을 예방할 수 있습니다.

 

 

실제 구현 미리보기

여기까지 설명한 내용을 바탕으로 동적으로 템플릿, 컨트롤러, 다이렉티브, 서비스, 필터 등을 로드하는 프로젝트 샘플을 제작해보았습니다. 이 프로젝트 샘플은 아래 프로젝트 샘플 다운로드 부분에서 다운 받을 수 있는 링크를 얻을 수 있습니다. 이 프로젝트 샘플을 좀더 개선하고 발전시켜 적용한 사례가 JCF UI 사이트입니다.

 

preview01

기본 경로인 view1 path로 들어간 모습입니다. CSS, 컨트롤러, 필터, 템플릿 모두 동적으로 로드되어 반영된 것을 볼 수 있습니다.

 

preview02

view2 메뉴를 누르면 역시 마찬가지로  CSS, 컨트롤러, 다이렉티브, 템플릿 모두 동적으로 로드되어 반영된 것을 볼 수 있습니다. 기존의 CSS는 제거되고 새로운 CSS만 반영된 것 역시 확인할 수 있습니다.

 

preview03

grid 메뉴를 누르면 디펜던시 설정된 pqGrid 라이브러리를 동적으로 로드하여 실행되는 것을 확인할 수 있습니다.

 

 

프로젝트 샘플 다운로드 – GitHub

지금까지 설명한 내용을 바탕으로 제작된 프로젝트 샘플을 GitHub에서 받으실 수 있습니다. 직접 받아서 수정하고 실행해본다면 전체적인 구조를 이해하고 활용하는데 도움이 될 것입니다.

 

githuboctacat

 

 

 

결론

지금까지 AngularJS를 기반으로 대규모 웹 어플리케이션을 개발할 때 고민하게 되는 모듈화, 리소스의 동적로딩 등에 대해 살펴보았습니다. 또 이를 바탕으로 간단한 샘플 프로젝트를 작성했습니다.

앞서 말씀드린 것처럼 이 글은 대규모 웹 어플리케이션 개발에 있어 정답을 제시하는 글이 아닙니다. route 를 동적으로 추가하는 부분이나, breadcrumb을 관리하는 부분 등 실제 대규모 웹 어플리케이션을 개발할 때 필요한 부분들이 많이 빠져있기 때문입니다. 하지만 부족하나마 대규모 웹 어플리케이션을 개발하는데 있어 기준점은 될 수 있지 않을까 생각해봅니다.

부족한 설명과 부실한 예제였지만, 대규모 웹 어플리케이션 개발을 위한 안목을 얻을 수 있는 시간이었기를 기대합니다. 다음 포스팅에서는 대규모 웹 개발을 위해 좀더 개선된 형태의 seed project를 살펴보도록 하겠습니다.


재하 안 | 2013년 5월 9일 |



출처 - http://jcf.daewoobrenic.co.kr/blog/?p=237

http://programmingsummaries.tistory.com/229


'Development > AngularJS' 카테고리의 다른 글

AngularJS  (0) 2013.10.13
angularjs - AngularJS tags  (0) 2013.10.13
Posted by linuxism
,