블로그 이미지
윤영식
Full Stacker, Application Architecter, KnowHow Dispenser and Bike Rider

Publication

Category

Recent Post

2016. 4. 8. 18:54 Angular/Concept

Angular2 또는 최신 라이브러리를 읽기 위해서는 ES2015 (ES6)에 대한 기본적인 이해가 필요하다. 모질라의 Depth in ES6 번역글과 S65 페북모임에서 얻은 지식을 한 페이지로 정리해 본다. 






let 과 const


기본 var는 함수에 대한 scope(스코프)만을 갖는다. for, {} 블록에 대한 스코프가 없다. var의 대체제가 let 이다. 

  - let은 블럭을 기준으로 스코프를 결정한다. block-scoped

  - 글로벌 let은 없다. 즉 window.xxx 접근이 안된다. 어디간의 블럭안의 스코프에 속한다. 

  - for (let x...) 루프구문에서 x 변수를 새로 바인딩 한다. 

  - let 변수를 선언 전에 참조하는 것은 에러이다. 

  - let, var 충돌시에는 ES2015 모듈패턴을 사용해서 let으로 전환한다. 


const는 최초 값을 할당한 후 재 할당 할 수 없다. 단, 객체의 속성은 가능하다. 변수 중에 불변의 값을 갖는 것은 모두 const로 할당한다. 재 할당이 안된다는 것 외에는 let과 완전히 동일하므로 애플리케이션 상태 관리의 복잡도를 단순화 시켜줄 수 있다. 




class


이제는 OOP(Object Oriented Programming)를 위한 접근이 훨씬 쉬워졌다. Java의 OOP에 익숙한 개발자라면 import, class, super, extends, implements(TypeScript 지원)등을 통해 이전 Prototypal Inheritance의 이해 없이도 쉽게 JavaScript를 이용한 OOP가 가능해 진것이다

class Parent {

   contructor(params) { ... }

   static XXX() { ... }

   YYY() { ... }

   get ZZZ() { ... }

   set ZZZ(value) { ... }

}




Arrow Function


event 또는 XHR 콜백 펑션을 등록할 때 간혹 내부에 this를 사용하면 Global scope를 참조하게 된다. 이때 var that = this; 한 후 that을 넘겨 사용하기도 하는데 이제는 더 이상 그럴 필요가 없다. 자신 Execution Context를 만들지 않고 (function 보다 가볍다) 화살표 함수를 감싸는 외부 스코프의 this 값을 바로 쓸 수 있는 방법이 => 화살표 함수이다. 

$('.button').click(function (event) {

  ...

});


변경

$('.button').click(event => {

  ...

});


한 줄일 경우는 {}을 사용하지 않고 자동 return 구문이 된다. 만일 여러 줄일 경우는 {} 으로 감싼다. 단, 한 줄로 비어있는 객체를 return하고 싶다면 어떻게 해야할까?

arrs.map( item => ({}) ); // 성공

arrs.map( item => {} ); // bug




iterable 과 for~of 구문


배열을 순회(loop)하기 위한 방법으로 ES5의 myArray.forEach(function (value) { ....});를 사용한다. 기존 for ~ in 루프는 확장 속성들도 순회하기 때문에 성능상 이점이 없다. 배열에 만일 myArray.name = 'dowon'을 넣으면 name도 순회하고 prototype chain도 순회한다. 또는 가끔 무작위 순서로 순회하기도 한다. 쇗!! 이에 대한 해결로 for ~ of가 나왔다. 

for (var value of myArray) {

  console.log(value);  // 값이다. for ~ in 처럼 속성의 key가 아니다

}


Array, Map, Set은 순회를 위한 iterator 메소드를 제공한다. 사용자가 원하는 객체에 iterator 객체를 제공할 수 있다. 

  - 어떤 객체든 myObject[Symbol.iterator]() 메소드를 추가하면 자바스크립트에서 해당 객체를 순회할 수 있다. 

  - Duck Typing의 예로 iterator가 있으므로 해당 객체도 순회가 가능하다는 방식이다. 

  - myObject['iterator'] = function () {...} 라고 했고 만일 iterator() 가 이미 있다면 문제가 될 것이다. 이를 위해 제 7원소 Symbol을 사용해 어떤 코드의 key와도 충돌하지 않게 만든다. 

  - [Symbol.iterator]() 메소드를 제공하는 겍체를 이터러블 객체 (iterable object)라고 한다. 

Iterable is a simple representation of a series of elements that can be iterated over. current state만 있고, Iterator제공하는 메소드 하나를 가진다. 

Iterator is the object with iteration state. hasNext(), next()를 통해 다음 엘러먼트로 이동한다. 


iterator는 next를 제공해야 하므로 Symbol을 이용할 경우 다음과 같이 한다. value를 계속 0 만 주무로 무한루프이다. 

var myIterator = {

  [Symbol.iterator]: function() {

    return this;

  },

  next: function() {

    return { done: false, value: 0 } 

  }

}



형식

for ( VAR of ITERALBE ) {

  STATEMENT

}

동등한 코드 

var $iterator = ITERABLE[Symbol.iterator]();

var $result = $iterator.next();

while (!$result.done) {

  VAR = $result.value;

  STATEMENT

  $result = $iterator.next();

}




Collection 


자바스크립트의 Object는 key-value 쌍의 컬렉션이다. 즉 속성을 추가하고 값을 할당할 수 있고 이런 속성의 집합을 객체라 한다. 일반 객체로 해결할 수 없는 자료구조에서 Map, Set을 도입한다. 

  - 속성 key를 객체로 사용하고 싶을 경우 (객체는 key는 문자열만 가능, 단 ES2015에는 Symbol이라는 새로운 타입이 존재한다)

  - 일반 객체는 iterable 하지 않기에 for~of 구문이나, ... 구문을 사용할 수 없다. 


Set은 중복을 제거할 때 사용한다. Value 를 추가하고 삭제하며 동일 Value는 포함 될 수 없기 때문이다. 

  - new Set, new Set(Iterable) : 새로운 set 만들기

  - size, has, add, delete, forEach, clear

  - 다양한 이터레이터를 리턴: keys, values, entries (Map과의 호환성을 제공)

  - set[Symbol.iterator]() 구문은 set안의 값들을 순회할 수 있는 새로운 이터레이터를 리턴한다. 

var uniqueWords = new Set(words);

for (var word of uniqueWords) {

  console.log(word);

}


Map은 key-value 쌍으로 이뤄진다. 순회시 분해(destructuring)을 사용할 수 있다. 

for (var [key, value] of addressMap) {

  console.log(key, ': ', value);


Set, Map의 대용이 없어지지않고 남아있으면 Garbage Collector가 메모리를 회수 할 수 없다. 이애 해결을 위해 WeakSet, WeakMap을 사용한다. 




Symbol


심볼은 ES2015의 7번째 타입이다. 프로그램에서 이름 충돌을 피하고자 객체 속성 키(symbol-keyed property)를 사용하고 싶을 경우 사용한다. 

  - Object.property 처럼 dot(.)로 접근할 수 없고 Object[] 형식으로 접근할 수 있다. 

  - if( MY_SYMBOL in element) 처럼 속성을 참조하거나, delete element[MY_SYMBOL] 처럼 속성을 제거할 수 있다. 

  - MY_SYMBOL은 실행 스코프안에서만 모든 행위가 가능해서 충돌을 걱정할 필요없이 캡슐화를 할 수 있다. 

var MY_SYMBOL = Symbol();

var myObj = {};

myObj[MY_SYMBOL] = "dowon";


사용방법

  - Symbol() 호출: 일반적인 사용 형태, Symbol('name') 과 같이 파라미터를 주는 것은 디버깅을 위한 용도이다. 

  - Symbol.for(string): 심볼을 공유하고 싶을 경우 사용

  - Symbol.iterator: 특정 객체에 대해 Duck Typing으로 iterator를 만들고 싶을 경우 사용 

  - core.js 폴리필을 첨부해야 사용이 가능하다


기본 6가지 타입

  - Undefined

  - Null

  - Boolean

  - Number

  - String

  - Object 




Generator, 2


자바스크립트의 co-routine 이다. 함수가 끝나기전에 자신의 스코프 밖으로 나올 수 없다. 그러나 제너레이터 함수(generator-function)를 만들면 가능해 진다.

  - 제너레이터 함수는 function* 키워드로 시작한다. 

  - 제너레이터 함수안에는 yield 구문이 존재한다. 함수의 return은 한번 실행되지만 yield는 여러번 수행 가능하다. yield는 제너레이터의 실행을 멈췄다가 다음에 다시 시작할 수 있게 만든다. 

function* mygen(name) {

  yield 'hi ' + name;

  yield 'Have a great day';

}


> var iter = myget('dowon');

 [object Generator]

> iter.next();

  'hi dowon'

> iter.next();

  'Have a great day'


제너레이터 함수를 호출하면 제너레이터 객체(generator object)를 전달 받는다. yield첫번째에  실행이 멈춘 상태의 객체이다. .next()를 호출하면 다음 yield까지만 수행한다. yield 구문이 실핼될 때, 제너레이터의 스택 프레임(stack frame: 로컬 변수, 인자, 임시 값, 제너레이터 코드의 실행 위치)은 스택에서 제거된다. 


제너레이터를 통해 이터레이터를 만들 수 있다. 

> Symbol을 통해 만들기: 실행

class RangeIterator {

  constructor(start, stop) {

    this.value = start;

    this.stop = stop;

  }


  [Symbol.iterator]() { return this; }


  next() {

    var value = this.value;

    if (value < this.stop) {

      this.value++;

      return {done: false, value: value};

    } else {

      return {done: true, value: undefined};

    }

  }

}


// 'start'에서 'stop'까지 더해나가는 새로운 이터레이터를 리턴합니다.

function range(start, stop) {

  return new RangeIterator(start, stop);

}


// 수행 

for ( let value of range(0, 5)) {

 console.log(value);

}


> Generator를 통해 만들기: 실행

 - Symbol과 동일한 역할을 하는 이유는 Generator는 .next() 코드와 [Symbol.iterator]() 코드를 내장하고 있기 때문이다. 그냥 루프 처리만 작성하면 된다. 

function* range(start, stop) {

  for (let i=start; i < stop; i++) {

    yield i;

  }

}


// 수행 

for ( let value of range(0, 5)) {

 console.log(value);

}


제너레이터는 객체를 이터러블하게 만들때, 엄청나게 큰 결과를 처리, 복잡한 루프 구문을 리팩토링, 이터러블을 다루는 도구등으로 사용한다. (이부분은 경험이 없어서 이해가 힘들다. 다음기회로..) 제너레이터의 동작은 동기적으로 싱글-쓰레드 환경에서 실행된다. yield에서 멈추고 .next()에서 수행되는 된다. 만일 for ~ of 구문의 경우 iterator 스팩에 따라 수행이 된다.  




Proxy


자바스크립트 스팩 레벨에서 특정 객체에 대한 hooking을 가능케 한다. 

  - 첫번째 인자는 후킹할 객체이다. 

  - 두번째 인자는 후킹을 위한 설정이다. 보통 스팩에서 정의한 14가지 내부 메소드(internal method)를 재정의한다. 

var obj = new Proxy(<후킹할 객체>, <핸들러>);


내부 메소드는 [[ ]] 로 감싼다. 내부 메소드는 가려져 있기때문에 개발자가 호출할 수 없다. 

  - 속성 값 가져오기: obj.[[Get]](key, receiver) 는 obj.prop 또는 obj[key] 사용시 호출된다. 여기서 receiver는 속성 조회를 처음 시작했던 대상 객체이다. 

  - 속성 값 할당: obj.[[Set]](key, value, receiver) 는 obj.prop = value 또는 obj[key] = value 사용시 호출된다.  

    예) obj.prop += 2 같은 할당문의 경우 [[Get]] 호출 후 [[Set]]을 호출한다. (++, -- 도 동일)

  - obj.[[HasProperty]](key): 속성이 존재하는지 테스트 

  - obj.[[GetPrototypeOf]](): obj의 프로토타입을 반환. obj.__proto__ 또는 Object.getPrototypeOf(obj) 사용시 호출된다. 

  - functionObj.[[Call]](thisValue, arguments): functionObj() 또는 x.method() 사용시 호출된다. 

  - constructorObj.[[Construct]](arguments, newTarget): 생성자를 실행. new Date(2017,1,1) 사용시 호출된다.  


핸들러 만들기

  - set시에 무조건 Error 발생시킴

  - get시에 Reflect를 통해 기본 동작을 수행하고 추가 작업을 수행할 수 있음

var target = {};

var handler = {

  set: function (target, key, value, receiver) {

    throw new Error('not settting value');

  }

  get: function (target, key, receiver) {

    // default action 

    var result = Reflect.get(target, key, receiver);

    // custom action 

  }

}

var proxy = new Proxy(target, handler); 


> proxy.name = 'hi';

  Error: not settting value


프락시를 이용하면 어떤 객체에 대한 접근을 관찰하거나 로그를 남길 때 유용하다. 




디스트럭쳐링(Destructuring)


배열 또는 객체의 속성값을 일괄처리로 변수에 할당 받을 수 있는 방법이다. 객체나 배열을 XHR을 통해 JSON형태의 포멧으로 받을 경우 해체를 통해 값을 간편하게 변수로 받아 사용할 수 있다. 이는 코드의 가독성과 간결성을 유지해 줄 수 있다. 해체시 Default Value를 할당 할 수 있다. 배열의 경우는 인덱스 순서에 따라 하나씩 변수에 값을 할당 받지 않고 한번에 변수에 값을 할당 받는다. 

var first = myArray[0];

var second = myArray[1];


이것은 하기와 동일하다


let [first, second] = myArray;


또는 다차원 배열의 해체도 가능하다 


let [foo, [[bar], baz]] = [1, [[2], 3]]; 


또는 선택해서 받을 수 있다. 


let [ , , third] = [1, 2, 3];


또는 Rest 패턴을 통해 배열의 요소를 다른 배열로 받을 수도 있다.


let [first, ...second] = [1, 2, 3, 4, 5];


또는 함수에서 배열 리턴값을 해체한다 


function myArrayFunc() {

  return [1, 2, 3, 4];

}

let [a, b, c, d] = myArrayFunc();


객체를 해체할 수 있다. 객체 해체시에는 반드시 let, const 또는 var를 써야한다. 객체의 경우 이름이 같으면 변수 alias를 사용하지 않아도 된다. 

let myObj = { name: 'dowon' };


// myObj.name을 받아 myName 이라는 변수에 할당한다.

let { name: myName } = myObj;


좀 더 복잡한 객체의 해체도 가능하다, second = 'kangnam'이라는 default value를 할당했다. 


let complicateObj = {

  arrProps: [

    'dowon',

    { address: 'seoul'}

  ]

}

let { arrProps: [first, { second = 'kangnam' }] } = complicateObj;


이터레이션 프로토콜과 함께 사용할 수 있다


for (let [key, value] of map) {

   console.log(key, value);

}


또는 함수에서 객체 리턴값을 해체한다


function myObjFunc() {

  return {

    name: 'dowon',

    address: 'seoul'

  }

}

let { name, address } = myObjFunc();


또는 continuation passing style 사용도 가능하다. k 라는 callback에 값 1, 2를 주면 foo = 1, bar = 2 각 해체되어 할당된다. 


function returnMultipleValue(k) {

  k(1, 2);

}

returnMultipleValue(( foo, bar ) => ..... ); 


또는 ES2015의 import 구문에서 사용한다


import { Component } from 'angular2/core'; 


함수의 파라미터가 객체일때 파라미터를 해체하는 구문을 사용할 수 있다. 기존에는 파라미터 객체를 받아서 함수 내부에서 다시 변수에 할당하는 방식이 있을 경우 파로 파라미터 객체 해체를 통해 코드 간결성을 유지할 수 있다.

function myFn({ a, b, c, d }




Rest Parameter 와 Default Parameter


함수 파라미터의 해체(Destructuring)외에 함수 문법의 표현을 간결하게 해주는 두가지가 Rest/Default Parameter 이다. 함수의 파라미터를 가져올 때 기존엔 parameters를 통해 접근을 했다면 ES2015에서는 Rest Parameter로 접근한다. Rest Parameter는 함수의 마지막에 정의한다. 

function myFunc(first, ...myParams) {

  for (let param of myParams) {

    console.log(param);

  }

}


myFunc('hi', 'dowon', 'great', 'day');


... 점(Dot) 3개이후 파라미터 명칭을 설정하면 나머지 파라미터가 배열 객체로 넘어온다. 


함수의 파라미터 값이 undefined인지 검증하는 코드를 함수안에 넣는데 undefined일 경우 기본 값을 할당할 수 있다. 

function myFunc( first = 'dowon', second = 'hi') {

  console.log(first, second);

}


myFunc('Yun');


arguments 객체를 코드를 읽기 어렵게 만들고 JavaScript VM의 성능 최적화하는 것도 어렵게 만든다고 하니 앞으로는 Rest Parameter를 사용하자.




템플릿 문자열 (Template String)


백틱(Backtick) ` ` 을 이용해 문자열을 만드는 방법이 새롭게 생겼다. Angular2에서 template 정의시 많이 사용한다. 이에 더불어 문자 맵핑 채워넣기 (string interpolation) 을 백틱안에서 ${ } 를 사용해 넣을 수 있다. 

  - cross-iste scripting에 대한 공격을 피하려면 기존 처럼 신뢰할 수 없는 데이터에 대한 처리를 직접해주어야 한다. 

  - 다국어 처리도 직접한다

  - 루프문이 없다. 

let name = 'dowon';

let myStr = `Hi ${name}. Have a great day!`;


태그된 템플릿 (Tagged Template)은 백틱앞에 붙이는 사용자 정의 함수라 할 수 있다. 즉, 이 함수를 통해 템플릿 문자열을 커스터 마이징 할 수 있다. 태그 함수의 인자에는 어떤 것이 와도 되고, 리턴 값도 마찬가지이다. 

function SaferHTML(templateData) {

  var s = templateData[0];

  for (var i = 1; i < arguments.length; i++) {

    var arg = String(arguments[i]);


    // 대입문의 특수 문자들을 이스케이프시켜 표현합니다.

    s += arg.replace(/&/g, "&amp;")

            .replace(/</g, "&lt;")

            .replace(/>/g, "&gt;");


    // 템플릿의 특수 문자들은 이스케이프시키지 않습니다.

    s += templateData[i];

  }

  return s;

}


SaferHTML`<p>${name} hi</p>` 처럼 말이다. 




참조


  - Depth in ES6 번역문서

  - iterator, iterable 차이



posted by 윤영식
2013. 11. 9. 07:08 My Services/PlayHub

Angular 기반 SPA를 위한 Push 환경은 Node 기반의 Socket.io를 사용한다. Angular.js의 장점중 하나가 DI(Dependency Injection)이고 이를 Node에서 구현한 프레임워크인 express-train (축약, train)을 사용한다. train은 특정 폴더에 .js 파일을 놓으면 자동으로 DI되어 사용을 할 수 있게 해준다. 일정한 규약만을 지키면 개발 코드를 모듈화 하여 관리할 수 있고 테스트 할 수 있다. train은 express에 DI 기능을 접목해 놓은 것으로 보면 된다





1. Express-Train 설치하기 

  - 설치 : npm install -g express-train 

  - 새로운 Node 환경 프로젝트 생성

// train이 사용하는 기본 템플릿을 기반으로 자동으로 palette.fronend.node 디렉토리에 기본 폴더와 파일을 생성해 준다 

$ train new palette.frontend.node 

// 수행 http://localhost:4000 호출 

$ train run 

  - 디렉토리 구조 

    + app : auto-injected 로 표현된 것 .js 파일을 해당 디렉토리에 놓으면 다른 곳에서 파일 명칭으로 불러와서 (function parameter DI 방식) 사용을 할 수 있다 

    + bin : 이것은 node_modules/express-train 밑에 존재한다 

    + test : Mocha를 이용한다

    + public : Angular.js 기반으로 작성한 모든 static 파일들이 이곳에 놓일 것이다. 엄밀히 따지면 Distributed된 Frontend파일이 놓인다

app

  /controllers      -- application controllers (**autoinjected**)

  /lib                 -- application specific modules (**autoinjected**)

  /middleware    -- application middleware (**autoinjected**)

  /models          -- application models (**autoinjected**)

  /public            -- static content (html, js, css, etc) : SPA모든 파일을 해당 폴더 밑에 복사할 것이다  

  /views            -- view templates (loaded into express' view engine) : 서버단의 파일 jade, ejs, hbs 같은 서버 template 파일

  index.js           -- index file exports the express train application : train run 시에 수행되는 파일 based on Node.js


bin                   -- executable scripts : 

doc                  -- documentation

config              -- environmental configuration files : train 환경 설정 파일 

test                 -- tests : 서버 테스트 파일

package.json    -- npm package.json (needs to have express-train as a dependency)

  - train의 환경파일 : development와 production 환경파일을 구분함 

{

  "cookie_secret": "boggle at the situation",

  "session":{

    "key": "boggle at the situation",

    "secret": "boggle at the situation"

  },

  "client_port": 3000,

  "request_timeout": 1000,

  // RESTful 호출 접두사 Restanuglar 참조 

  "restful_api_version": "/api/v1",

  "server_name": "first",

  // REDIS 연결 Client Library 환경 설정

  "data_broker": {

    "port" : 6379,

    "host" : "localhost",

    "auth" : ""

  },

  // MongoDB 환경 설정

  "mongodb":{

    "uri": "mongodb://localhost/playhub_palette"

  },

  "playhub_channel_file": "channels.json",

  // Winston 에러 로그가 쌓일 MongoDB 정보 

  "winston_mongo": {

    "level": "error",

    "db": "playhub_palette",

    "host": "localhost",

    "port": 27017,

    "collection": "sd_logs"

  },

  // Winston 로그 파일의 명칭 설정

  "winston_file": {

    "filename": "sd.log",

    "exception": "sd_exception.log",

    "maxSize": 20480,

    "maxFiles": 12,

    "colorize": true

  },

  "character_encoding":"utf-8"

}



2. Express-Train 파일 사용하기 

  - auto-injected 되는 폴더에 모듈로 운용하고 싶은 .js 파일을 놓는다. 주로 MVC 파일과 Utility 또는 Service 파일들이다 

  - auto-inject 환경내역은 node_modules/express-train/lib/app.js 파일안에 이미 설정되어 있다.

// app.js 내역중 일부 

// autoinject라는 옵션을 주면 해당 지정된 폴더의 .js파일을 메모리에 로딩하고 DI를 한다 

// 이때 주의할 것은 상호 참조가 되지 않도록 하기 위한 폴더 전략이 중요하다. 위에서 부터 아래순서로 로딩되어 DI 된다 

var LOCATIONS = createApplication.locations = {

    pkg:{

        path: '../package.json'

    },

    config:{

        path: '../config'

    },

    logs:{

        path: '../logs'

    },

    models: {

        path: 'models',

        autoinject: true,

        aggregateOn: 'models'

    },

    views: {

        path: 'views'

    },

    lib: {

        path: 'lib',

        autoinject: true

    },

    controllers: {

        path: 'controllers',

        autoinject: true

    },

    pub: {

        path: 'public'

    },

    middleware: {

        path: 'middleware',

        autoinject: true

    }, 

   // 만약 app밑에 services폴더를 만든다면! 

    services: {

       path: 'services',

       autoinject: true

    },

}; 


// app.js 에서 express를 생성하고 views와 public 폴더를 강제 설정하고 있다

// _.each에서 autoinject 폴더의 모든 파일을 읽고 DI를 수행해 준다. 이때 DI되는 .js 파일은 Singleton 패턴이다 

// tree.constant로 저장된 객체를 DI 받을 수 있다

function createApplication(dir, locs) {

    catchErrors();


    var app = express(),

        locations = _.defaults((locs || {}), LOCATIONS);

        tree = new nject.Tree();


    app.set('views', path.join(dir, locations.views.path));

    app.set('public', path.join(dir, locations.pub.path));


    var config = loadConfig(path.join(dir, locations.config.path));

    tree.constant('config', config);


    _.each(_.where(locations, {autoinject: true}), function(location) {

        traverseAndRegister(path.join(dir, location.path), tree, location.aggregateOn)

    });


    //allow override of app

    if(!tree.isRegistered('app')) {

        tree.constant('app', app);  

    }


    //log path 하나 추가함 

    var logPath = path.join(dir, locations.logs.path);

    tree.constant('logPath', logPath);


    return tree.resolve();

}


  - 폴더 전략 : app.js에 설정된 순서에-위에서 아래로- 따라 로딩됨, 참조가 많은 것은 주로 service가 되지 않을까 함

    + lib/1level : 가장 기본적인 파일 참조하는 것이 없는 것들 - util, logger, db connection, constant

    + lib/2level : express-train의 파일들  - route, views, middleware 설정

    + lib/3level : 1, 2level의 .js를 사용하는 파일들 

////////

// 형식

module.exports = function(app, config, logger, ...) {

  // app, config, logger 등 파일이름을 대소문자 구분하여 넣으면 자동 DI를 해준다. 객체는 train에서 Singleton으로 관리한다 

}


/////////////////////////

// 예) lib/1level/logger.js

// MongoDB에 winston logger를 이용하여 에러발생 내역을 저장하거나 파일로 떨굼 


var winston = require('winston'),

  path = require('path');


module.exports = function(app, config, logPath) {


  winston.info('[1level: log config] init');

  require('winston-mongodb').MongoDB;


  var dbOpts = {

    level: config.winston_mongo.level,

    db: config.winston_mongo.db,

    collection: config.winston_mongo.collection,

    host: config.winston_mongo.host,

    port: config.winston_mongo.port

  };

  winston.info('[1level: log config] DB option is ' + JSON.stringify(dbOpts) );


  var fileName = path.join(logPath, config.winston_file.filename);

  winston.info('[1level: log config] general log : ' + fileName);

  var exceptionFileName = path.join(logPath, config.winston_file.exception);

  winston.info('[1level: log config] exception log : ' + exceptionFileName);


  var fileOpts = {

    filename: fileName,

    maxsize: config.winston_file.maxSize,

    maxFiles: config.winston_file.maxFiles,

    colorize: config.winston_file.colorize

  }


  var exceptionOpts = {

    filename: exceptionFileName,

    maxsize: config.winston_file.maxSize,

    maxFiles: config.winston_file.maxFiles,

    colorize: config.winston_file.colorize

  }


  var logger = new winston.Logger({

    transports: [

      new winston.transports.Console(),

      new winston.transports.File(fileOpts),

      new winston.transports.MongoDB(dbOpts)

    ],

    exceptionHandlers: [

      new winston.transports.Console(),

      new winston.transports.File(exceptionOpts),

      new winston.transports.MongoDB(dbOpts)

    ]

  });


//  testing

// logger.log(config.winston_mongo.level, '[log config] Test logging module completely');

//  logger.info('==============>> hi info');

//  logger.warn('==============>> hi warn');

//  logger.error('==============>> hi error');  <- MongoDB에도 쌓인다 


  return logger;

}


////////////////////////////////////////////////

// 예) lib/2level/ 또는 lib/2level에서 logger사용하기 

// lib/2level/views.js 파일에서 

// logger.js 파일에서 파일명칭을 - 대소문자 구분 - Function Parameter DI 해준다 

module.exports = function (applogger) {

  logger.info('[2level: view engine - jade] init');

  app.set('view engine', 'jade');

};




방법-1. Angular 프로젝트 배포후 Express-Train 프로젝트로 통합 

  - 엄밀히 따지면 합치는 시점은 개발이 다 된후 수행한다. 즉, SPA와 Node 파트의 Git 저장소를 틀리게 가져감을 전제로 한다 

    + SPA 저장소 : palette.frontend

    + Node저장소 : palette.frontend.node 

  - 개발이 된후 SPA와 Node 통합은 다음 순서로 하자 

    + Yeoman을 통하여 생성된 Angular 스케폴딩 파일은 별도로 개발한다 -> grunt build 수행 -> dist 폴더에 배포파일이 생성된다 

    + palette.frontend.node/app/public/ 와 palette.frontend.node/app/views 폴더 밑의 모든 파일을 삭제한다

    + palette.frontend.node/app/public/ 밑으로 dist/* 모든 파일을 복사한다 

// 폴더구조 

playhub

  |_ palette.frontend         (별도 git repository)

  |_ palette.frontend.node (별도 git repository)


// Angular 기반 frontend 파일 

playhub$ cd palette.frontend

playhub$ grunt build 


// Express-train에서 사용하지 않을 파일 삭제  

playhub$ cd ../palette.frontend.node/app/public

playhub$ rm -rf *

playhub$ cd ../views

playhub$ rm -rf *


// Angular 기반 SPA frontend파일을 Express-train의 app/public/ 폴더 밑으로 복사 

playhub$ cp -rf ../../../palette.frontend/dist/* ../public/

  - 테스트 하기 

$ cd palette.frontend.node

train run 

// http://localhost:4000/ 으로 호출한다 



방법-2. Express-Train 프로젝트를 Angular 프로젝트로 통합

  - Angular 프로젝트(palette.frontend)의 grunt, bower, yo, karma 기능을 그대로 사용한다 

  - Express-Train 프로젝트(palette.frontend.node)는 express-train기반으로 이미 스케폴딩된 서버단 애플리케이션이다. 

    즉 palette.frontend.node의 디렉토리 구조를 변경시켜보자  

// express-train 프로젝트로 이동하기 

$ cd palette.frontend.node


// express-train의 bin 디렉토리를 palette.frontend.node로 이동하기  

$ cd node_modules/

$ cd express-train/

$ ls -arlt

total 80

drwxr-xr-x   3 nulpulum  staff    102  9 17 17:20 test

-rw-r--r--   1 nulpulum  staff     90  9 17 17:20 index.js

-rw-r--r--   1 nulpulum  staff     82  9 17 17:20 .npmignore

-rw-r--r--   1 nulpulum  staff  13393  9 26 19:13 ReadMe.md

drwxr-xr-x   4 nulpulum  staff    136 11  8 14:14 bin

drwxr-xr-x   5 nulpulum  staff    170 11  8 14:14 boilerplates

-rw-r--r--   1 nulpulum  staff  14976 11  8 14:14 package.json

drwxr-xr-x   4 nulpulum  staff    136 11  8 14:14 lib

drwxr-xr-x  12 nulpulum  staff    408 11  8 14:14 node_modules

drwxr-xr-x  11 nulpulum  staff    374 11  8 14:14 .

drwxr-xr-x  13 nulpulum  staff    442 11  8 14:14 ..

// bin을 palette.frontend.node 루트로 이동시킨다 

$ mv bin ../../


// bin으로 들어가서 train run 명령을 수행 - 내부적으로 commander 모듈을 사용한다 

$ train run

module.js:340

    throw err;

          ^

Error: Cannot find module 'commander'

    at Function.Module._resolveFilename (module.js:338:15)

    at Function.Module._load (module.js:280:25)

    at Module.require (module.js:364:17)

    at require (module.js:380:17)

    at Object.<anonymous> (/Users/nulpulum/development/playground/playhub/palette.frontend.node/bin/train:3:15)

    at Module._compile (module.js:456:26)

    at Object.Module._extensions..js (module.js:474:10)

    at Module.load (module.js:356:32)

    at Function.Module._load (module.js:312:12)

    at Function.Module.runMain (module.js:497:10)


// 에러가 발생하므로 commander, underscore, handlebars, boilerplate 모듈을 설치한다 

$ cd .. && npm install commander --save 

$ npm install underscore --save

$ npm install handlebars --save

$ npm install boilerplate --save-dev

$ cd bin && train run 

module.js:340

    throw err;

          ^

Error: Cannot find module '../../lib/app'

    at Function.Module._resolveFilename (module.js:338:15)

    at Function.Module._load (module.js:280:25)

    at Module.require (module.js:364:17)

    at require (module.js:380:17)

    at Object.<anonymous>(/Users/nulpulum/development/playground/playhub/palette.frontend.node/bin/commands/console.js:4:13)

    at Module._compile (module.js:456:26)

    at Object.Module._extensions..js (module.js:474:10)

    at Module.load (module.js:356:32)

    at Function.Module._load (module.js:312:12)

    at Module.require (module.js:364:17)


// 에러 다시 수정 하기 

$ cd ../node_modules/express-train/

$ ls -alrt

total 80

drwxr-xr-x   3 nulpulum  staff    102  9 17 17:20 test

-rw-r--r--   1 nulpulum  staff     90  9 17 17:20 index.js

-rw-r--r--   1 nulpulum  staff     82  9 17 17:20 .npmignore

-rw-r--r--   1 nulpulum  staff  13393  9 26 19:13 ReadMe.md

drwxr-xr-x   5 nulpulum  staff    170 11  8 14:14 boilerplates

-rw-r--r--   1 nulpulum  staff  14976 11  8 14:14 package.json

drwxr-xr-x   4 nulpulum  staff    136 11  8 14:14 lib

drwxr-xr-x  12 nulpulum  staff    408 11  8 14:14 node_modules

drwxr-xr-x  10 nulpulum  staff    340 11  9 15:19 .

drwxr-xr-x  16 nulpulum  staff    544 11  9 15:29 ..


// express-train의 lib밑의 모든파일을 bin 밑으로 이동시킨다 

$ mv lib/* ../../bin/

// index.js 파일을 이동한 bin 디렉토리에 같이 놓는다 

$ mv index.js ../../bin/


// express-train 은 삭제한다 

$ cd .. && rm -rf express-train

$ cd ../../bin/

// 최종 lib이 bin 밑으로 들어갔다 

$ ls -alrt

total 32

-rw-r--r--   1 nulpulum  staff    64  9 17 17:20 runner.js

-rw-r--r--   1 nulpulum  staff  3594  9 27 14:06 app.js

drwxr-xr-x  11 nulpulum  staff   374 11  9 15:19 ..

-rw-r--r--   1 nulpulum  staff    89 11  9 15:56 index.js

-rwxr-xr-x   1 nulpulum  staff   785 11  9 16:02 train

drwxr-xr-x   7 nulpulum  staff   238 11  9 16:02 .

drwxr-xr-x   3 nulpulum  staff   102 11  9 16:08 commands


// commands 폴더에서 runt.js를 제외한 사용하지 않는 나머지 파일은 삭제하거나 나중을 위해서 다른 위치로 백업한다 

$ cd commands && rm console.js cycle.js new.js boilerplate.js


// commands 폴더의 run.js 안에 내역을 수정 한다 

// 기존 내역 : var appPath = path.join(process.cwd(), '../app/index.js');

// 수정 내역 

var appPath = path.join(process.cwd(), './index.js');


// lib밑에 있는 index.js가 가르키는 파일 수정 

// 기존 내역 : app: require('./lib/app')

// 수정 내역

// module.exports = {

//   app: require('./app.js')

// };


var paletteApp = require('./app.js');

module.exports = paletteApp(__dirname);


// palette.frontend.node 루트의 app 디렉토리명칭을 api 라고 변경한다 

$ cd ../../ && mv app api 

  

// 다음으로 직접 app.js안의 인식하는 디렉토리 경로를 상대경로로 변경한다 

$ cd bin && vi app.js

//위치 변경하기 

var LOCATIONS = createApplication.locations = {

    pkg:{

        path: '../package.json'

    },

    config:{

        path: '../config'

    },

    logs:{

        path: '../logs'

    },

    models: {

        path: '../api/models',

        autoinject: true,

        aggregateOn: 'models'

    },

    views: {

        path: '../api/views'

    },

    lib: {

        path: '../api/lib',

        autoinject: true

    },

    controllers: {

        path: '../api/controllers',

        autoinject: true

    },

    // 향후 palette.frontend의 grunt build시에 dist 폴더 밑으로 되는 것을 public 폴더로 바꿀 것이다 

    pub: {

        path: '../public'

    },

    middleware: {

        path: '../api/middleware',

        autoinject: true

    }

};


// api 디렉토리 밑에 있는 public 디렉토리를 루트로 옮겨서 테스트 해본다 

$ mv app/public .


// bin 디렉토리 밑에 있는 train 쉘 실행하기 

$ train run 

[express train application listening on 4000]


  - Express-Train기반의 서버구조를 만들었고, 이제 이것을 palette.frontend 쪽으로 옮겨보자 

// palette.frontend.node 프로젝트의 package.json 명칭을 변경 - 나중에 palette.frontend쪽의 package.json과 합치기 위함

$ mv package.json packate.json.node


// public 폴더는 삭제한다 

$ rm -rf public 


// 옮기기 

$ mv * ../palette.frontend/

mv: rename node_modules to ../palette.frontend/node_modules: Directory not empty

mv: rename test to ../palette.frontend/test: Directory not empty

$ cd node_modules

$ mv * ../../palette.frontend/node_modules/


// palette.frontend 에서 dist를 public으로 명칭 변경하여 테스트 

$ cd ../../palette.frontend

$ mv dist public 

$ cd bin

$ train run

[express train application listening on 4000]


// 이제 grunt build를 수행할 때 dist 디렉토리를 만드는 것이 아니라 public 으로 만들어 보자 

$ cd .. && rm -rf public 


// grunt 환경파일의 내역 수정 

$ vi Gruntfile.js

// 변경내역 

 grunt.initConfig({

    yeoman: {

      // configurable paths

      app: require('./bower.json').appPath || 'app',

      dist: 'public'  // 'dist'를 'public'으로 변경한다 

    },


// 다시 grunt로 빌드를 하면 public 폴더가 생성된다 

$ grunt build 

$ cd bin && train run 

[express train application listening on 4000]


// 최종 palette.frontend의 합친 구조

  - api : Node 서버 

  - app : SPA Angular

  - public : app 배포 디렉토리이면서 bin/train run 수행시 인식되는 서비스 폴더


// palette.frontend git 저장소에 push하기전에 package.json.node의 내용을 package.json에 통합한다 

{

  "name": "palette",

  "version": "0.0.1",

  "dependencies": {

    "express": "3.2.x",

    "express-train": "1.0.x",

    "connect-timeout": "latest",

    "connect-mongodb": "latest",

    "mongoose": "latest",

    "async": "latest",

    "express-hbs": "latest",

    "passport": "latest",

    "winston": "latest",

    "commander": "~2.0.0",

    "underscore": "~1.5.2",

    "handlebars": "~1.1.2",

    "nject": "~0.1.6"

  },

  "devDependencies": {

    "grunt": "~0.4.1",

    "grunt-contrib-copy": "~0.4.1",

    "grunt-contrib-concat": "~0.3.0",

    "grunt-contrib-coffee": "~0.7.0",

    "grunt-contrib-uglify": "~0.2.0",

    "grunt-contrib-compass": "~0.5.0",

    "grunt-contrib-jshint": "~0.6.0",

    "grunt-contrib-cssmin": "~0.6.0",

    "grunt-contrib-connect": "~0.5.0",

    "grunt-contrib-clean": "~0.5.0",

    "grunt-contrib-htmlmin": "~0.1.3",

    "grunt-contrib-imagemin": "~0.2.0",

    "grunt-contrib-watch": "~0.5.2",

    "grunt-autoprefixer": "~0.2.0",

    "grunt-usemin": "~0.1.11",

    "grunt-svgmin": "~0.2.0",

    "grunt-rev": "~0.1.0",

    "grunt-concurrent": "~0.3.0",

    "load-grunt-tasks": "~0.1.0",

    "grunt-google-cdn": "~0.2.0",

    "grunt-ngmin": "~0.0.2",

    "time-grunt": "~0.1.0",

    "karma-ng-scenario": "~0.1.0",

    "grunt-karma": "~0.6.2",

    "karma-script-launcher": "~0.1.0",

    "karma-chrome-launcher": "~0.1.0",

    "karma-firefox-launcher": "~0.1.0",

    "karma-html2js-preprocessor": "~0.1.0",

    "karma-jasmine": "~0.1.3",

    "karma-requirejs": "~0.1.0",

    "karma-coffee-preprocessor": "~0.1.0",

    "karma-phantomjs-launcher": "~0.1.0",

    "karma": "~0.10.4",

    "karma-ng-html2js-preprocessor": "~0.1.0",

    "mocha": "latest"

  },

  "engines": {

    "node": ">=0.8.0"

  },

  "main": "./app/index.js",

  "scripts": {

    "test": "grunt test",

    "start": "cd bin && train run",

    "server-test": "mocha test"

  }

}


// npm 명령으로 scripts 설정 내역 호출하기 

$ npm start

[express train application listening on 4000]


  - 최종 완성본 저장소 : MyPlayHub GitHub Repository

// git clone 하여 train run 수행시 다음과 같이 오류가 발생할 경우 

$ train run

env: node\r: No such file or directory


// dos2unix 패키지를 설치하여 수정하여 준다 

$ brew install dos2unix

==> Installing dos2unix dependency: gettext

==> Downloading http://ftpmirror.gnu.org/gettext/gettext-0.18.3.tar.gz

.. 중략 ..


$ dos2unix train

dos2unix: converting file train to Unix format ...

$ train run 

[express train application listening on 4000]



방법-3. Yeoman Generator FullStack을 통한 통합

  - Express + Angular의 통합

  - Angular는 클라이언트의 파일 전송과 암호화를 위하여 Lint와 압축을 위해 Grunt를 사용함 

  - Express는 서버단이므로 압축이 필요없음 

// FullStack generator 설치 

$ sudo npm install -g generator-angular-fullstack

// FullStackTest라는 애플리케이션 명칭으로 스케폴딩 생성

$ yo angular-fullstack FullStackTest

// 빌드하여 클라이언트 파일 Lint 및 압축 

// public 폴더밑으로 배포본이 만들어짐 

$ grunt build 

... 중략 ...

Elapsed time

useminPrepare:html                   25ms

concurrent:dist                          5s

autoprefixer:dist                        113ms

concat:public/scripts/modules.js  23ms

copy:dist                                  640ms

cdnify:dist                                 21ms

ngmin:dist                                263ms

cssmin:public/styles/main.css     33ms

uglify:dist                                  28ms

uglify:public/scripts/plugins.js      373ms

uglify:public/scripts/modules.js   134ms

usemin:html                             482ms

usemin:css                              152ms

Total                                        8s



<참조> 

  - Angular-Express-Train-Seed

  - Yeoman : generator-fullstack with angular and express

  - dos2unix 설치하여 사용하기

posted by 윤영식
prev 1 next