Google Chrome 에서 Grunt 사용하기

KStyleTrip 에서는 프론트엔드 영역에서 디자이너와 개발자가 협업하고 있다.

나는 사실 HTML CSS 를 어느 수준으로 다루기는 하지만 퍼블리셔는 아니어서 픽셀 단위의 디테일을 구현하는데는 어려움이 있다. 디자이너는 디자이너 대로 원하는 만큼의 디테일을 구현하기 어려워하는 내 모습을 보기 안타까워 했다. 그래서 현실적인 수준에서 협업을 시작했다.

개발자가 디자인을 보고 큰 덩이에 대해 마크업과 CSS 처리를 하면 디자이너가 원하는 수준의 디테일을 구현하는 방식이다.

그런데 역시 개발자와 비 개발자간의 협업은 환경구성에 어려움이 있기 마련이다. 예를들어 서버를 띄우거나 하는 일들 말이다. 물론 이것도 쉘 스크립트 등을 사용해 할 수 있는 한 단순하게 만들어 관리하고 있었지만 더 쉬운 방법을 찾게 되었다.

바로 크롬 개발자도구에서 Grunt 를 사용하는 것이다. 쉘스크립트로 관리되던 작업들을 Gruntfile 에 통합하고 grunt-devtools 를 사용해 여러 작업들을 GUI 로 작동할 수 있게 만들었다.

설치과정과 실행방법을 공유하고자 한다.

설치

크롬 익스텐션과 grunt-devtools npm 패키지를 설치해야 한다.

크롬 익스텐션 설치

크롬 익스텐션은 크롬 웹스토어에서 설치할 수 있다. 바로가기

익스텐션을 설치하고 크롬 개발자도구를 열어보면 Grunt 탭이 추가된 것을 알 수 있다.

abstract

grunt-devtools 명령어 설치

명령어를 실행할 것이므로 -g 옵션을 주어 패키지를 설치한다.

$ npm install -g grunt-devtools

사용방법

Gruntfile 이 있는 경로에서 grunt-devtools 를 실행하고 개발자 도구의 gruntjs 탭으로 이동하면 프로젝트가 추가된다.

$ grunt-devtools

여러 디렉토리를 옮겨가며 추가하면 상단 프로젝트 바에 여러 프로젝트들이 추가된다. 이 프로젝트들은 일시적인 것으로, grunt-devtools 와 크롬을 종료하면 사라진다. 따라서 크롬을 다시 시작하면 프로젝트마다 다시 실행해줘야 한다.

그리고 왼편에 위치한 태스크 리스트 아이템을 더블클릭하면 태스크가 실행된다.

태스크에는 Background Tasks, Alias Tasks, Tasks 가 있다.

Alias Tasks 나 Tasks 는 Grunt 의 기본기능이다. Background Tasks 가 생소한데, 간단히 말하면 콘솔을 점유하는 watch 같은 태스크를 Background 로 실행시킬 수 있는 기능이다. 콘솔을 점유하는 태스크들은 종료하지 않으면 다른 태스크를 사용할 수 없다. 이럴 때 Background Tasks 로 실행시켜두면 실행을 종료하지 않고 다른 태스크를 동시에 실행할 수 있다.

태스크를 일단 실행시키고 그 위에 B 아이콘을 클릭하면 Background Tasks 로 실행된다.

background-task

이제 디자이너는 grunt-devtools 라는 명령 하나만 외우면, 혹은 단축 아이콘을 더블클릭하는 것으로 서버를 띄우고 스프라이트 아이콘을 빌드하는 등의 작업을 할 수 있게 되었다. 더 쉽게 더 많은 권한을 드릴 수 있게 되어 기분이 매우 좋다 🙂

P.S. Gulp 에도 gulp-devtools 라는 프로그램이 있다.

페이스북에 Gruntjs 관심그룹을 운영 중입니다. 가입을 언제나 환영합니다 😀

하이브리드 프론트엔드 개발 5원칙

3년 정도 하이브리드 플랫폼들을 경험했다. 하이브리드 플랫폼은 정말 여러 곳에서 쓰이고 있다. 하지만 그 모든 플랫폼이 크롬, 파이어폭스만큼 우수한 디버깅 환경을 제공하지는 않는다. 에뮬레이터를 지원하지 않는 경우는 부지기수이고 한 줄이라도 수정하면 앱을 종료했다 다시 켜야 변경사항이 반영되는 때도 있다.

오늘도 하이브리드 개발 중에 API 서버가 죽었다. 일정도 꼬인 김에 하이브리드 개발 때 도움이 될만한 팁을 공유해본다.

독립된 API 서버를 갖춰라

가능하다면 독립된 API를 갖는 게 좋다. 하이브리드 앱은 API와 통신이 많은 부분을 차지한다. 끊임없이 개발되고 있는 와중에 개발 중인 API가 안정적이기를 바라는 것은 무리다. 하지만 최소한 지금 프론트엔드 개발 중인 API는 안정적이어야 한다. 따라서 독립된 API 서버를 확보해서 현재 개발 중인 부분의 API 는 안정적으로 동작하도록 버전 관리 시스템의 리비전을 변경하면서 개발하자. “가능하다면” 이란 단서를 달았다. 만약 독립된 API 서버가 없다면 원활한 개발을 포기하면 된다.

ps. 단순한 프로젝트라면 API 서버를 대체할만한 목업 서버라도 만들어서 쓰기를 강력히 추천한다.

ps2. Vagrant나 Docker 사용도 추천할만 하다.

프락시를 활용하라

하이브리드 플랫폼에서는 hosts 파일을 변경하기 어렵다. 따라서 다른 방법으로 우회해야 하는데 그것이 바로 프락시다. 단말기의 네트워크 설정에서 프락시 서버를 사용하도록 하고 프락시 서버를 고쳐서 hosts 와 같은 효과를 낼 수 있다. 리버스 프락시와 PAC(Proxy Auto Config) 를 사용한다면 더 편리한 개발이 가능하다. 단말에 탑재된 소스 중 일부만 로컬서버에서 동작하게 할 수도 있고 문제가 있는 API 만 다른 API 서버로 우회하도록 동작시킬 수도 있다. 특히 소스를 곧바로 고칠 수 없는 환경에서 프락시는 활용도가 무척 높다. 가끔 프락시 조차 지원하지 않는 단말기가 있다. 이럴 때는 Wireless AP 를 만들어 해결하는 방법도 있다.

최대한 웹 브라우저에서 확인할 수 있도록 개발하라

하이브리드 애플리케이션은 단말 전용 API를 이용해 브라우저보다 더 많은 기능을 사용할 수 있다. 하지만 전용 API 를 사용한 특별한 부분을 제외하면 거의 모든 기능은 브라우저로도 동작할 수 있다. 어떤 하이브리드 플랫폼도 브라우저만큼 개발환경을 지원해주지 못한다. 따라서 기능 대부분을 브라우저에서 확인할 수 있도록 개발하는 것이 좋다.

가끔은 브라우저용 폴백을 직접 만들어야 할 때도 있다. 예를 들어 화면간에 파라미터를 전달하는 기능은 URL hash에 JSON 을 담아 전송하면 쉽게 파라미터를 전달할 수 있다. 하이브리드 플랫폼에서 데이터베이스를 사용할 수 있다면, Ajax로 데이터베이스에 접근하는 간단한 서버프로그램을 작성해 활용하면 브라우저에서도 쉽게 확인할 수 있다.

플랫폼에서 제공하는 디버깅 도구에 대해 공부하라

브라우저에서 디버깅하는 방법에 대해 잘 알고 있는 만큼 개발중인 디바이스 플랫폼에 대해서도 공부가 필요하다. 예를들어 안드로이드 플랫폼에서는 adb 를 사용할 수 있다. adb 를 통해 개발중인 소스를 기기로 복사할 수 있고 로그도 곧바로 볼 수 있다.

리모트 디버깅 도구를 활용하라

iOS6 와 OSX Lion 이상의 환경에서는 사용한다면 Safari 브라우저를 통해 웹뷰의 디버깅이 가능하고, 최근의 안드로이드 브라우저도 크롬을 통해 디버그할 수 있다. 위 플랫폼에서 벗어난 경우엔 Weinre 를 사용해보는 것을 추천한다.

한마디로 정리하면 – 가능한 모든 것을 추상화하라.

자바스크립트의 변수형을 알아내는 다른 방법

JavaScript 의 변수형을 알아내는 데는 일반적으로 typeof 를 사용한다. 하지만 toString 을 사용하면 어떤 객체의 인스턴스인지까지 한번에 알아낼 수 있다.

JavaScript 의 변수형을 알아내는 데는 일반적으로 typeof 를 사용한다.

이 방법이 가장 간단하고 일반적인 방법이다.

typeof 가 나타낼 수 있는 변수형은 아래와 같다. undefined, object, boolean, number, string, function, object

typeof 가 변수에 대한 기본적인 호기심을 해결해주긴 하지만 몇가지 모자란 점이 있다. 가장 큰 문제는 어떤 객체의 인스턴스인지 정확히 알려주지 않는다는 점이다.

typeof null 은 ‘object’ 이다.

typeof null === 'object'; // true

typeof [] 도 ‘object’ 이다.

typeof [] === 'object'; //true

따라서 어떤 객체인지 더 알아보려면 조건판단을 더 해주어야 한다. 예를들면 날짜 객체인지 알아보려면 아래처럼 구현하기도 한다.

var date = new Date();
if (typeof date === 'object' && date instanceof Date) {
    console.log 'date';
}

instanceof 는 같은 윈도우 환경에서만 작동하므로 위 방법은 프레임/아이프레임/팝업 같은 환경에서는 실패한다.

다른 방법

변수형을 알아내는 다른 방법도 있다. 바로 .toString() 을 사용하는 것이다.

Object.prototype.toString.call({}) === "[object Object]" //true

이 방법은 객체 타입까지 구체적으로 표현해주는 장점이 있다.

Object.prototype.toString.call(new Date) === "[object Date]"; //true
Object.prototype.toString.call(/s/) === "[object RegExp]"; //true
Object.prototype.toString.call(/null/) === "[object Null]"; //true
Object.prototype.toString.call(1) === "[object Number]"; //true

어떤 원리인지 자세히 알아보고 싶다면 참고링크의 Fixing the JavaScript typeof operator 항목을 참조하기 바란다.

underscore.js 에서는 아래와 같이 사용하고 있다.

  // Add some isType methods: isArguments, isFunction, isString, isNumber, isDate, isRegExp.
  each(['Arguments', 'Function', 'String', 'Number', 'Date', 'RegExp'], function(name) {
    _['is' + name] = function(obj) {
      return toString.call(obj) == '[object ' + name + ']';
    };
  });

성능문제

하지만 이 방법의 단점은 성능이다. 사실 프로토타입의 객체를 call로 부르는 구현법이 typeof 라는 연산자보다 성능이 좋을리가 만무하다. 참고링크에 JSPerf 항목에서 underscore의 이전 구현/현재 구현간의 성능차이를 확인할 수 있다.

성능문제에도 불구하고 객체의 타입까지 손쉽게 알아낼 수 있으므로 효율적인 개발을 가능하게 할 것이고, 이 방식의 매우 큰 장점이다.

참고링크

jQuery 요소 선택의 성능문제, 셀렉터와 탐색 중 어떤 것이 더 빠를까?

스택오버플로를 둘러보다 아래와 같은 질문을 찾았다.

Is jQuery traversal preferred over selectors?

jQuery 에서 요소 선택의 성능문제를 묻는 질문인데,

$("#vacations").find("li").last();

$("#vacations li:last");

이 두 가지중 어떤 것이 더 빠를까? 하는 질문이다.

질문자도 그렇고, 개인적인 생각으로도 처음엔 후자가 더 빠르지 않을까했는데, 베스트 답변은 생각과는 조금 달랐다.

결론부터 말하자면 jQuery 셀렉터가 아니라 표준 CSS 셀렉터를 사용하면 빠르다. 그렇지 않으면 탐색이 더 낫다

답변이 좀 길지만, 읽어볼 만한 가치가 있어보여 아래 번역한다.

There’s no cut and dry answer to this, but with respect to the :last selector you’re using, it’s a proprietary extension to the Selectors API standard.Because of this, it isn’t valid to use with the native .querySelectorAll method. What Sizzle does is basically try to use your selector with .querySelectorAll, and if it throws an Exception due to an invalid selector, it’ll default to a purely JavaScript based DOM selection/filtering. This means including selectors like :last will cause you to not get the speed boost of DOM selection with native code.

이 문제에 대한 명백한 해답은 없지만 질문에 나와있는 :last 셀렉터에 집중해 살펴보도록 하겠다. :last 셀렉터는 Selector API 표준을 jQuery 전용으로 확장한 것이다. 따라서 이 셀렉터는 네이티브 .querySelectorAll 메서드로는 사용할 수 없다.

Sizzle 은 기본적으로 요청받은 셀렉터를 .querySelectorAll 로 동작시켜본 후, 올바르지 않은 셀렉터로 인한 예외가 발생하면 순수 JavaScript 기반으로 DOM 선택, 필터링 한다.

즉 :last 등이 포함되면 네이티브 DOM 셀렉션의 빠른 속도를 얻을 수 없게 된다.

Furthermore, there are optimizations included so that when your selector is very simple, like just an ID or an element name, the native getElementById and getElementsByTagName will be used, which are extremely fast; usually even faster than querySelectorAll. And since the .last() method just grabs the last item in the collection instead of filtering all the items, which is what Sizzle filters normally do (at least they used to), that also will give a boost.

게다가 셀렉터가 ID나 태그이름만 사용된 경우처럼 아주 간단하다면 정말 빠른 네이티브 getElementById 와 getElementsByTagName 을 사용하도록 최적화 로직이 내장되어있다. . 이들은 일반적으로 querySelectorAll 보다 훨씬 빠르다.

그리고 .last() 메서드는 일반적인 Sizzle 의 필터처럼 모든 아이템 중에서 필터링하는 것이 아니라 그저 컬렉션에서 마지막 아이템을 가져오는 형태로 동작하므로 여기서도 이점을 찾을 수 있다.

IMO, keep away from the proprietary stuff. Now that .querySelectorAll is pretty much ubiquitous, there are real advantages to only using standards-compliant selectors. Do any further filtering post DOM selection. In the case of $(“#vacations”).find(“li”), don’t worry about the interim results. This will use getElementById followed by getElementsByTagName, and will be extremely fast. If you’re really super concerned about speed, reduce your usage of jQuery, and use the DOM directly.

개인적으로는 jQuery 전용 셀렉터를 배제하는 것이 좋겠다고 생각한다. .querySelectorAll 의 쓰임새가 훨씬 많은데, 표준 셀렉터를 사용해야만 그 이점을 누릴 수 있다. 그 이후의 필터링들은 DOM 셀렉팅 이후에 진행하도록 하자.

$(“#vacations”).find(“li”) 의 경우는 성능에 대해 크게 고민하지 않아도 된다. 이들은 getElementById 후에 getElementsByTagName 을 사용할 것이고, 이들은 정말 빠르기 때문이다.

진정으로 속도에 대해 고민한다면 jQuery 를 사용하지 말고 DOM 에 직접 접근하는 것이 좋다.

You’ll currently find notes in the docs for selectors like :last, that warn you about the performance loss:

Because :last is a jQuery extension and not part of the CSS specification, queries using :last cannot take advantage of the performance boost provided by the native DOM querySelectorAll() method. To achieve the best performance when using :last to select elements, first select the elements using a pure CSS selector, then use .filter(“:last”).

But I’d disagree that .filter(“:last”) would be a good substitute. Much better would be methods like .last() that will target the element directly instead of filtering the set. I have a feeling that they just want people to keep using their non-standards-compliant selectors. IMO, you’re better of just forgetting about them.

jQuery 문서에도 :last 와 같은 셀렉터들의 성능문제에 대한 내용이 추가되었다.

:last 는 CSS 명세에 포함되지 않은 jQuery 의 확장이므로, :last 쿼리를 사용하는 것은 네이티브 DOM querySelectorAll() 의 성능향상을 기대할 수 없다. :last 를 사용해 요소를 선택할 때 최고의 성능을 얻으려면 먼저 순수 CSS 셀렉터로 요소를 선택하고, .filter(“:last”) 를 사용하라.

하지만 .filter(“:last”) 가 좋은 대체물이라고 생각하지는 않는다. .last() 와 같은 메서드들이 컬렉션을 필터링하기보다는 곧바로 요소를 잡아낼 수 있어서 훨씬 낫다. 내 느낌엔 이들이 사람들이 계속 비표준 셀렉터를 사용하길 원하고 있는 것 같다. 그러니 이건 그냥 잊는게 좋다고 생각한다.

그러니 jQuery 셀렉터보다는 표준 CSS 셀렉터를 숙지해 사용하도록 하자 🙂

자바스크립트 파일이 세미콜론으로 시작하는 이유

어떤 코드를 살펴보면 자바스크립트 파일이 세미콜론으로 시작하는 때가 있다.

;(function () {})();

왜일까 궁금했었는데 스택 오버플로를 살펴보니 대략 아래와 같은 답변이다.

이 세미콜론은 자바스크립트 파일을 합칠 때(concatenating) 안정성에 도움이 된다. HTTP Request 개수를 줄이기 위한 목적으로 파일을 합쳐서 전송할 파일의 개수를 줄이는 경우가 있는데, 이때 순서상 해당 파일의 직전 파일이 세미콜론으로 끝나지 않으면 파일을 합쳤을 때 제대로 동작하지 않기 때문이다.

다시말해, a.js 와 b.js 를 합쳐 ab.js를 만들 때

a.js

(function () {alert('a');})()

b.js

(function () {alert('b');})();

ab.js

(function () {alert('a');})()(function () {alert('b');})();

이렇게 되면 문제가 될 수 있다는 것이다.

closure compiler 나 uglify 같은 프로그램을 통해 작업을 수행한다면 문제가 없겠지만, 수동으로 합치는 경우는 문제가 발생할 수도 있겠다.

물론 이런 문제를 인식하고 있는 개발자라면 자기가 개발한 파일의 끝에 세미콜론은 꼭 붙여주겠지.

Semicolon on the beginning of the JavaScript Files