실서비스에서 CoffeeScript를 ES6로 변환하기

사내의 모바일웹 프로젝트의 프론트 환경이 CoffeeScript로 개발되어 있어 여러가지 불편을 겪었습니다. 이는 오랫동안 풀어야 할 숙원사업(?)이였는데 이를 해결하며 느꼈던 점, 절차, 노하우를 공유하고자 합니다. 제가 느꼈던 CoffeeScript 환경의 문제와 Webpack + ES6 도입 이유를 정리해 보았습니다.

CoffeeScript 개발환경 문제

사장 위기의 CoffeeScript

Webpack + ES6 도입 이유

기술 스펙

도입 절차

1. 기존 스크립트 구조와 역할 분석

각각의 스크립트들의 기능 파악

스크립트들의 구조 정리/공통 모듈은 따로 분리

스크립트들의 의존관계 정리

2. Webpack 도입하기

기존에는 grunt를 통해 jst(template), 기타 task를 처리해 주고 있었습니다. 원래 Webpack만으로 모든 task를 처리해 줄 수 있지만 jst 의존성을 React 도입 이전에 제거할 수가 없어 Webpack과 grunt를 병행하여 사용하였습니다. 아래는 사용했던 module과 plugin입니다.

//...

const common = {
	//...
    module: {
        loaders: [
            {
                test: /\.js$/,
                loader: 'babel',
                exclude: /(node_modules|bower_components)/,
                query: {
                    presets: ['es2015']
                }
            },
            {
                test: /\.(css|scss)$/,
                loader: ExtractTextPlugin.extract({
                    fallbackLoader: "style-loader",
                    loader: "css-loader!postcss-loader!sass-loader"
                })
            },
            {
                test: /\.(gif|png|jpg)$/,
                loader: 'file-loader?name=images/[name].[ext]&mimeType=image/[ext]&limit=100000'
            }
        ]
    },
    plugins: [
        new webpack.optimize.OccurenceOrderPlugin(),
        new webpack.NoErrorsPlugin(),
        new CommonsChunkPlugin({
            name: "common",
            filename: "common.js",
            minChunks: Infinity
        })
    ]
};

const prodConfig = {
    plugins: [
        new webpack.optimize.UglifyJsPlugin({
            compress: {warnings: false}
        }),
        new ExtractTextPlugin("[name].css")
    ]
};

let config;
if(target === 'watch') {
    console.log('watch build');
    config = common;
} else {
    console.log('real build');
    config = webpackMerge(common, prodConfig);
}

module.exports = config;

Webpack에 대한 더 자세한 내용은 개인 블로그에 정리해 두었습니다.

3. CoffeeScript 걷어내기

사내 동료 직원에게 좋은 OpenSource를 추천받았습니다. 이름부터 필이 팍 꽂히는 decaffeinate! 아래는 decaffeinate의 메인 멘트입니다.

decaffeinate를 사용하면 간편하게 CoffeeScript를 ES6로 변환해 줍니다. 기대했던 것보다 퀄러티 좋게 변환이 되어 놀랐습니다!

변환되기 전 샘플 코드(Coffee -> JS)

무난하게 변환된 샘플 코드(아직은 코드가 좀 더럽습니다..)

4. ES6 directory 구조잡기

여기서부터 가장 오랜 시간 공을 들였습니다. 제일 먼저 디렉토리 구조를 각각 의미에 맞는 구조로 정리하였습니다. 미흡하게 변환된 코드를 모듈화하고 한눈에 파악할 수 있는 구조가 되도록 노력하였습니다.

변환되기 이전/이후 디렉토리 구조

5. ES6 아름답게 구성하기(이슈 해결 방법)

문제는 mobile에서 ES6 기능들이 다 될것이라는 막연한 추측이였습니다. 하지만 대부분 지원되지 않는게 현실이었다는…이곳에서 확인 가능합니다.

1)ES6 함수를 decaffeinate로 사전에 차단하기
처음에 decaffeinate docs의 option을 제대로 보지 않았습니다. 저는 대충 훑고 넘어가서 삽질을…opensource 사용전 docs를 빠르게 스케닝하는 습관을 들여야 겠습니다.

2)저는 기왕하는거 ES6의 기능들을 사용하고 싶은데요?
저같이 ES6기능을 사용하고 싶은 분들을 위해 babel에서 Polyfill을 지원해 줍니다. 아래와 같이 설치 후 webpack의 entry에 전역으로 설정해 주면 모든 ES6함수를 IE8까지 지원해 줍니다.

npm install --save babel-polyfill

webpack entry에 Polyfill 적용

3)Polyfill 좋은데 size가 너무 큰데요?
Polyfill을 적용하여 기분좋게 사용하고 있는데 ES6 모든 기능을 require시키다 보니 size가 어마어마하였습니다.

Polyfill 적용 이후 bundle.js size(이전에는 반정도 였다는...)

제가 사용하는 기능들만 따로 require 시켜서 사용하고 싶었습니다. 좀 찾아보니 여러가지 방법이 있었습니다. webpack polyfills plugin도 있었지만 직접 사용해 보니 없는 기능들이 있었고 core-js가 가장 좋았습니다. 일단 문서화가 잘 되어 있으며 필요로 하는 ES6 기능을 대부분 지원하였습니다.

npm install --save core-js

이런식으로 entry에 적용(이보다는 더 깔끔하게 분리시키는게 좋겠죠?)

core-js 적용 이후 약 46KB 절약(다시 절반으로 뚝!)

소를 위해 대를 희생한다면 이보다 바보같은 짓은 없을겁니다. 쓸데없는 의존성을 죽입시다!(Kill Your Dependencies)

4)set 메서드는 정말 필요한 경우가 아니면 제외했습니다.

5)상수로 선언되는 값들은 Object.freeze를 사용하여 선언하였습니다.(const는 완벽한 상수선언이 아닌 리바인딩을 막는 선언자입니다)

Object.freeze 적용(점점 심플해져 가는 코드)

6)ES6 class 사용시 private 변수 선언이 힘듭니다.

6)이미 남발되어 있는 전역객체들이 너무 많았습니다. 충격적인 것은 window 객체에 지정하여 전역으로 사용되어 진다는 것이…코드자체가 문제였습니다.

7)스크립트 별 실행 여부를 결정짓는 url 체크 정규표현식들이 사용되어 코드가 지저분하였습니다. 해당 이슈는 다음 카테고리에서 해결됩니다.

5. Router 도입

각 스크립트에서 아래 코드처럼 상단에 정규식을 넣어 url에 따라 코드의 실행여부를 결정지었습니다. 때문에 Router의 도입은 필수적이였습니다.

스크립트의 존재여부를 결정짓는 정규식

React-Router같은 라이브러리를 쓰고 싶었지만…React까지 도입하기에는 여유가 없었기에 모듈화된 Router를 만들었습니다. 내부 로직을 몰라도 필요한 메서드만 호출하여 사용하게끔 구성하였습니다.

모듈화된 Router와 entry point에서 각각의 스크립트 실행여부를 결정짓는 코드

결과 화면

겉으로 바뀐건 아무것도 없습니다...

마무리

앞으로도 게을리 하지 않고 코드를 더 깔끔하게, 읽기 쉽게 작성하도록 노력하겠습니다. 여건이 된다면 React를 도입하여 router, template, test, flux 아키텍처의 장점을 활용하고 싶습니다.(여건이 되야 됩니다…) 긴 글 읽어주셔서 감사합니다!