ES6 기반 프론트엔드 개발 환경 알아보기

안녕하세요.  SK Planet 프론트엔드 개발자 윤지수 입니다.

ES6(ECMAScript 6 또는 ECMA 2015라고도 합니다.) 기반의 프론트엔드 개발 환경을 공유합니다.

ES 표준은 자바스크립트를 이루는 표준이라는 점에서 분명히 관심을 가져야 합니다. ES5와 ES6 에서 지원하는 syntax와 feature 중에는 쓸만한 것들이 많습니다. 아직 브라우저 지원 상황이 좋지는 않지만 몇 가지 도구를 활용하면 최신 브라우저에서는(대부분의 모바일웹 브라우저와 IE9이상 정도) ES6 기반 자바스크립트 개발이 가능합니다.

최근에 ES6 개발환경을 통해 UI 컴포넌트를 만들고 있습니다. 알고 보면 보통의 개발방식과 비슷하며 단지 몇 가지 도구가 필요하고 이를 편리하게 다룰 수 있는 환경이 필요할 뿐입니다.
이 글은 제 경험을 공유하는 글이며 ES6를 도입하실 때 도움이 됐으면 하는 마음입니다.

배경을 먼저 알려드려야 할 것 같네요.
최근 만들고 있는 컴포넌트는 다음과 같은 목표를 가지고 시작했습니다.

  • vanilla JS !!
  • ES6 코드 개발이 가능한 환경 갖추기.
  • 모듈 로딩 방식도 ES6 방식을 사용.
  • 컴포넌트는 이왕이면 재사용할 수 있는 구조로 만들기.

수 많은 라이브러리가 판을 치는 마당에 vanillaJS라는 건 무언가 반항하는 것 같으나, 전 오히려 필요한 시점이라 생각합니다.
우선 vanilla JS를 위해 jquery를 안 쓰기로 했습니다.
규모 있는 서비스 개발이라면 jquery와 같은 밑바탕 라이브러리가 필요할지도 모릅니다. 하지만 작은 UI 컴포넌트 개발이고 모바일웹만을 지원함으로 jquery를 많이 쓸 일이 없다고 생각했습니다. 이렇게 시작하니 vanilla JS기반의 코드를 유지하기 쉬워졌습니다.

그리고 ES6의 모든 것을 당장 다 쓰는 것이 목적은 아닙니다. 지금 필요한 것이 있다면 쓸 수 있지만 그것보다 시간이 흐른 뒤 ES6 의 매력적인 기능이 필요로 할 때 쉽게 적용할 수 있게 하기 위함입니다.

컴포넌트의 재사용은 ES6와는 큰 상관은 없습니다. 다만 ES6 의 클래스를 사용해서 모듈을 만들 겁니다.

이제 개발환경을 소주제들로 하나씩 알아보겠습니다.

.

.Babel & JSPM

.
babel을 활용해 ES6를 ES5로 변환하기로 했습니다.  비슷한 도구인 traceur 라는 것과 뚜렷한 차이는 없어보입니다.

그리고 JSPM이라는 패키지 매니저를 사용합니다.
이것으로 npm과 github에 있는 다양한 라이브러리를 쉽게 내려 받고, 갱신할 수 있습니다. 웹에서 대표적으로 사용되던 bower 와 비슷한 역할이라 외부 모듈을 관리하는 점에서 참 편리합니다.
뿐만 아니라 JSPM은 System.js라는 범용 모듈 로더를 품고 있어 jspm으로 다운로드한 모듈을 쉽게 추가할 수 있습니다.  또한 async loading 을 지원하며,  ES6 transpiler를 포함하고 있어 ES6와도 잘 어울린다 할 수 있습니다.

‘npm init’ 과 ‘jspm init’ 으로 뼈대를 만들고 gulp 설정까지 하면 아래와 같은 디렉토리를 볼 수 있습니다.

먼저 demo, dist, spec, src 는 모두 소스코드가 있는 곳입니다.
jspm_packages 디렉토리에는 npm 과 github 하위디렉토리가 있고 다운로드한 모듈이 모두 들어 있습니다. npm_packages 와 비슷합니다. jspm_packages에 모듈을 설치하는 것도 npm과 비슷하게  ‘jspm install OOO’ 입니다. babel 관련 모듈은 ‘jspm init’ 을 통해서 기본설치 됩니다.
이렇게 설치된 모듈은 코드에서 require 방식이나 ES6 import syntax 를 통해 가져올 수 있습니다.
config.js 는 ‘jspm init’ 이후 만들어진 설정 파일로 ES6 transpiler 설정 등을 포함하고 있습니다. 직접 수정할 일은 별로 없습니다.

이제 JSPM 으로 ES6 기반 javascript 개발을 할 수 있습니다.

class SimpleSwipe extends CommonComponent {

	constructor(elTarget, htOption) {
		super(htOption)
		this.elTarget = elTarget;
		this.init(htOption);
	}

	init(htOption) {
		this._registerEvents();
		this._setDefaultOption();
		this.option = {};
		super.execOption(htOption, this._htDefaultOption, this.option);
		this._setInitValue();
		this.htCacheData = {};
	}

	_setInitValue() {
		this.nSwipeWidth = super._getWidth(this.elTarget.firstElementChild);
		let nSwipeElementCount = this.elTarget.childElementCount;
		this.nMaxSwipeRange = 0;
		this.nMinSwipeRange = (nSwipeElementCount-1) * this.nSwipeWidth * -1;
		this.bAnimationing = false;
               .....
	}

......

.
제 경우 UI 컴포넌트를 만드는 것이라 클래스 형태로 모듈을 만들고,  다른 컴포넌트에서도 쓸 만한 공통 부분은 분리한 후 상속구조를 통해 사용했습니다.
Javascript에는 본래 클래스라는 것이 없으니 prototype기반으로 약간 복잡한 상속구조를 만들어야 했습니다. 물론 Typescript와 같은 것들이 ES6의 syntax를 이미 사용해오고 있긴합니다. 아무튼 ES6 class 키워드로 클래스를 만들었고 , extends 키워드로  다른 클래스를 쉽게 상속할 수 있습니다.

바로 위 코드를 살펴보면 ES6에서 지원하는 몇 가지 syntax를 살짝 볼 수 있습니다.
부모 클래스를 가리키는 super 키워드도 있고,  function 키워드 없이 함수를 정의한 것을 볼 수 있고 ‘let’으로 block scope 변수를 만들었고,  위에 없지만 ‘=>’ 로 callback 함수를 쉽게 만들 수도 있을 겁니다.

참고로 babel 컴파일 후의 코드를 살펴보면, class부분에는 prototype 키워드와 Object.create 메서드가 사용된 것을 볼 수 있습니다. 다시 말해 ES5 용 javascript syntax로 변환된 것입니다.

.ddddd

BUILD

.
빌드는 npm 과 shell script만으로도 가능하지만 놀랄만큼 많은 plugin을 가지고 있는 gulp를 사용합니다.
아래는 gulp 설정 파일내에 babel 컴파일 부분입니다.

gulp.task('compileSwipe', function() {
	        gulp.src('src/swipe_es6.js')
	        .pipe(sourcemaps.init())
	        .pipe(babel({ presets: ['es2015']}))
	        .pipe(concat('swipe_es5.js'))
	        .pipe(sourcemaps.write('.'))
	        .pipe(gulp.dest('dist'))
});

.
개발 중에 매번 위 빌드 태스크를 실행해야 하는 건 상당히 불편합니다.  소스코드를 모니터링 하는 것을 찾아보면 이를 쉽게 해결할 수 있습니다. 전 gulp의 watch기능으로 코드 변경시에 ES6빌드를 하도록하고, ‘live-server’ 라는 node 기반 프로그램으로 브라우저를 자동 갱신하게 했습니다.

gulp.task('watchSwipe', function() {
    	watch('src/swipe_es6.js' , batch(function(events, done) { 
    		gulp.start('compileSwipe', done);
	    }));
});

.
이제 코드를 수정하고 잠깐 기다리면 컴파일된 내용이 브라우저에 리로딩된 화면이 다시 보입니다.  뒷부분에 나오지만 테스트코드가 있다면 브라우저에 같이 띄워 놓으면 테스트 결과도 바로바로 볼 수 있습니다.

개발 완료 후 실제 서버에 배포 하기 위해서는 jspm을 활용할 수 있습니다.
jspm 의 bundleSFX 명령어를 통해 system.js, import로 불러온 모듈을 모두 하나로 패키징 할 수 있습니다.

gulp.task('bundleDemo', function() {
	return jspm.bundleSFX('demo/real_app.js',
			    'dist/real_app_bundle.js',
			     {sourceMaps:true}
			    )
});

이렇게 패키징 된 파일을 다른 소스와 합치고, 압축해서 실제 운영 서버에 배포하면 끝입니다.

SYSTEM.JS

.
빌드한 결과 파일은 System 모듈로 쉽게 불러 올 수 있습니다. promise 패턴과 비슷하게 then 메서드를 통해 반환되는 객체를 불러 사용할 수 있습니다. 참고로 아래 swipe_es5.js 에서는 단일 생성자 함수를 반환합니다. (Myswipe)

<script src="/jspm_packages/system.js"></script>
<script>
System.import("../dist/swipe_es5.js").then(function(Myswipe) {
     oMyswipe = new Myswipe.default(document.getElementById("swipeWrap"), {
        'nDuration' : 100,  //default 100
        'nBackWidth' : 60,  //default 60
        'nSideWidth' : 20,  //default 0
        'nDecisionSlope' : 0.8, //default 0.8
        'nForcedSwipeTime' : 100, //default 0
    });
});
....

.

DEBUGGING

.
ES6에서 ES5로 빌드한 결과는 디버깅이 어렵겠지만, 위에서 나왔던 gulp 빌드과정의 sourcemap 지원을 받으면 ES6  원래 코드를 크롬 개발자도구에서 쉽게 디버깅할 수 있습니다.

일반적인 디버깅 방법과 같이 breakpoint 를 찍고 그 시점 변수를 확인하고, callstack 으로 호출 관계를 따라가는 것 등이 모두 가능합니다.

.

UNIT TEST

.
ES6기반 코드 역시 ES5로 컴파일 된 코드를 테스트 하는 것임으로 테스트 코드를 구현하는 데에는 기존 방법과 차이점은 없습니다.
아래에서는 실제 서비스 개발에서 컴포넌트를 불러서 사용하는 것과 동일하게 System.js 로 모듈을 불러오고 이후에 필요한 테스트 코드를 구현합니다.
아래에서는 mochashould.js 를 사용한 테스트 코드 예 입니다.

<script src="../node_modules/mocha/should.js"></script>
<script src="/jspm_packages/system.js"></script>
<script>
         mocha.setup('bdd');mocha.reporter('html');
</script>

<script>
System.import("../dist/swipe_es5.js").then(function(Myswipe) {
	oMyswipe = new Myswipe.default(document.getElementById("swipeWrap"), {
                'nDuration' : 500,
                'nBackWidth' : 60,
                'nSideWidth' : 20,
                'nDecisionSlope' : 0.8,
            });
});

describe("swipe test ", function(){

	it('side moving limit', function () {

		oMyswipe.handlerTouchMove({
			"changedTouches" : [ { "pageX" : 100, "pageY" : 100} ],
			"preventDefault" : function(){},
		});

		var nResult = oMyswipe.getTranslate3dX(document.getElementById("swipeWrap"));
		nResult.should.be.equal(20);
	});
......
</script>

.

jQUERY FREE

.
마지막은 jquery 와의 이별입니다.

ES5, ES6, ES7 에는 jquery를 써야만 가능했던 javascript syntax를 포함하고 있습니다. 당장 그 syntax 를 모두 쓸 수는 없지만 위에서 말한대로 babel과 같은 라이브러리가 당분간 큰 도움이 됩니다.  더구나 mobile 개발환경이라면 호환성 이슈도 적으니 jquery를 벗어나는 건 어려운 일이 아닙니다.  (참고로 모바일 브라우저에서 발생하는 기기만의 괴상한 이슈는 jquery나 ES6와 상관없이 발생한다는 게 가슴 아픈 일이죠. 앞으로도 쭈욱 생길 그 이슈들 말입니다..)
또 한 가지 장점은 우리가 잊고 있던 javascript 문법을 자연스레 알게 된다는 것입니다.

jquery를 쓰지 않으면 불편한 점도 분명히 있습니다.  편리한 $를 못쓰고,  ajax 코드를 하나하나 구현해야 하고 DOM처리도 불편합니다.  개발 생산성은 어떨까요? 전 jquery를 안 쓴다고 개발 시간이 더 늘어난다고 생각하진 않습니다.  FE개발의 대부분의 시간은 설계와 수정 그리고 테스팅에 들어갑니다. $ 대신 querySelector를 쓴다고 개발 비용이 증가하지는 않습니다. 설사 그렇다고 하여도 그 정도는 미미한 정도입니다.  또 다른 예로 ajax 코드 역시 직접 구현한다고 코드가 엄청 증가하진 않습니다. 정말입니다.

그럼에도 마음이 불편하다면 작은 조각을 쓰는 것도 좋습니다.  예를 들어 날짜 처리를 돕는 라이브러리를 선택하거나 promise 패턴이 적용된 ajax 처리 라이브러리를 사용할 수는 있습니다. 살펴보면 polyfill 형태로 사용하는 작은 조각코드나 라이브러리가 상당히 많아졌습니다. polyfill은  표준문법을 사용할 수 있으니 나중에 걷어내기가 좋습니다. 더 이상 필요없는 라이브러리를 제거 할 때 서비스코드를 수정하지 않는다는 건 분명 장점입니다.

polyfill은 구글링을 통해 많은 것을 찾을 수 있습니다. 물론 직접 구현하면 더 좋겠네요.
몇 가지 예를 들어보면,  MDN 사이트에서 Function bind와 같은 polyfill 코드나 gist에 있는 requestAnimationframe(rAf.js) polyfill 등이 있겠습니다.

저도 실제로 android4.x 초기 버전지원을 위해 애니메이션 조작에 필요한 rAf.js 를 사용하고, css 스타일을 변경하는 등의 좀 지저분하고 자주 일어나는 작업은 별도 유틸 메서드로 분리해서 개발합니다. 범용성 있게 만들어서 잘 쌓아두면 다른데 유용하게 쓰이는 우리만의 유틸리티 클래스를 만들 수 있겠죠.

(아래처럼 css 작업을 jquery 없이 하는 건 해 볼 만 합니다)

 

getTranslate3dX(ele) {

    let sTF = this.getCSSTransformName();
    let sPreCss = ele.style[sTF];
    let nPreX = +sPreCss.replace(/translate3d\((-*\d+(?:\.\d+)*)(px)*\,.+\)/g , "$1");
    return nPreX;

}

getCSSTransformName() {

    let sTF = this.htCacheData['sTF'];
    if(typeof sTF === "undefined") {
         sTF = (typeof document.body.style.webkitTransform !== 'undefined') ? "webkitTransform" : 'transform';
         this.htCacheData['sTF'] = sTF;
    }
    return sTF;

}
....

.
.

끝으로,

.
지금까지 ES6기반 FE 개발 환경과 방법을 살펴봤습니다.
UI 컴포넌트라는 작은 부분의 예를 들었지만 서비스개발에서도 이와 동일한 환경과 방법을 사용할 수 있습니다. 이렇게 만들어진 코드는 지속 가능하고 누구나 거리낌 없이 수정할 수 있는 코드입니다.

ES6기반의 프론트엔드 개발 방법은 여기 나온 것들 말고도 상당히 많습니다. 어렵긴 하지만 각자의 환경에 어울리는 것을 잘 선택하는 것은 반드시 필요합니다.
이 글이 개발과정에서 ES6를 함께 하는데 도움이 되면 좋겠습니다. 🙂

윤지수

WEB-UI 를 사랑하는 개발자입니다.

공유하기