최근 자바스크립트의 사용량이 많아지면서 트래픽도 덩달아 증가하고 있다. CPU 연산 의존도가 높은 자바스크립트는 당장 필요치않은 라이브러리들까지 처리하려니 브라우저도 죽을 맛이다. 조금더 효율적인 처리방법은 없을까?
근간 호스팅 업체에서 제공하는 트래픽 분석 프로그램을 모니터링해 보니 좌측화면과 같이 상위권을 휩쓸고 있다. 브라우저가 파일캐시를 한다지만 신규방문자가 70-80% 이상이면 이것이 대안이라고 보기 힘들다. 자바스크립트 파일을 패키드(packed)하여 단순히 크기를 줄이는 방법도 있지만 브라우저에 부담을 주는 것은 마찬가지이다.

예를 들어 보자. 이미지를 멋지게 표현해주는 라이트박스(Lightbox JS)를 설치했으나 방분객은 이미지를 조회하지 않고 떠났다. 방문객은 필요치않은 자바스크립트 파일들을 다운로드 한 결과를 가져왔다. 물론 서버단에서 이미지가 있는 경우만을 체크하여 라이브러리를 로드했다 하더라도 작동을 시키지 않은것 또한 낭비인것은 사실이다. 페이지에 꼭 필요한 자바스크립만 로드하고 나머지는 필요한 때 동적으로 로드하도록 구현하여 쾌적한 브라우징 환경을 제공하면서 트래픽 손실도 줄이는 일석이조의 효과를 누려보자.

우선 자바스크립트를 동적으로 불러올 함수가 필요하다. 일전에 소개했던 "인클루드 (자바스크립트 || 스타일시트) 파일"을 변형해서 사용했으며 내용은 아래와 같다.(예제에 사용된 코드는 Prototype.js 1.5.1_rc2 기반으로 작성됨)
/**
* Include javascripts and stylesheets
* @aliasInclude
* @version 0.5
* @param{fileName} file name or file names string.
* @license MIT Style license
* @author firejune <to@firejune.com>
*
* @usage = new Include('fileName.js' || 'http://site.net/fileName1.css, /javascripts/fileName2.js ');
*
*/


var Include = Class.create();
Include.prototype = {
initialize: function(files, options, onComplete){
if(typeof options == 'function'){
onComplete = options;
options = '';
}
options = Object.extend({
JSPath : '/javascripts/', // javascripts default path
CSSPath : '/stylesheets/', // stylesheets default path
onComplete : typeof onComplete == 'function' ? onComplete : ''
}, options || {});

var element;
this.getArray(files).each(function(file){
file = this.getExpand(file);
if(!file.ext || !file.ext.match(/js|css/)){
debug(file.name, ' load failed, bad filename');
return;
}
try {
if(file.ext == 'js'){
file.path = !file.path? options.JSPath : file.path;
element = document.createElement('script');
element.type = 'text/javascript';
element.src = file.path + file.name;
} else if(file.ext == 'css'){
file.path = !file.path? options.CSSPath : file.path;
element = document.createElement('link');
element.rel = 'stylesheet';
element.type = 'text/css';
element.href = file.path + file.name ;
element.media = 'screen';
}
if(!this.isLoaded(element)){
document.getElementsByTagName('head')[0].appendChild(element);
debug(file.name, ' load accomplish');
} else {
debug(file.name, ' load failed, duplicate use in this page');
}
} catch(e){
debug(file.name, e.name + ': ' + e.message);
}
}.bind(this));

if(element.tagName == 'SCRIPT'
&& this.isLoaded(element)
&& typeof options.onComplete == 'function'){
// observe event for onComplete
if(Prototype.Browser.WebKit){
var timer = setInterval(function(){
if(/loaded|complete/.test(document.readyState)){
clearInterval(timer);
options.onComplete();
}
}, 10);
} else if(Prototype.Browser.IE){
element.onreadystatechange = function(){
if(this.readyState == "complete" || this.readyState == "loaded"){
options.onComplete();
}
};
} else {
element.onload = options.onComplete;
}
}
},
getExpand: function(src){
var fileName, filePath, fileExt;
if(src.match('/')){
fileName = src.split('/')[src.split('/').length - 1];
filePath = src.split(fileName)[0];
} else {
fileName = src;
}
fileExt = fileName.match(/\./)? fileName.split('.')[fileName.split('.').length - 1] : '';
return { name: fileName, ext: fileExt, path: filePath };
},
getArray: function(files){
var array = [];
files.split(',').each(function(file){
array.push(file.strip());
}.bind(this));
return array;
},
isLoaded: function(element){
var isLoaded = false;
var tags = element.tagName;
var src = tags == 'SCRIPT'? element.src : element.href;
$$(tags).each(function(tag){
tag = tags == 'SCRIPT'? tag.src : tag.href;
if(tag.match(src)){
isLoaded = true;
throw $break;
}
});
return isLoaded;
}
};
다운로드 : http://firejune.com/javascripts/include.js
자, 이제 준비는 끝났다. 예를 들어 Scriptaculous의 dragdrop.js(30.46kb)을 동적으로 호출하는 코드를 만들어보자.

var Draggable = function(element, options){
new Include('dragdrop.js', function(){
new Draggable(element, options);
});
};
'Draggable' 이라는 함수를 미리 선언했다. 이 함수는 dragdrop.js의 메인 클래스이름과 동일하다. 만약 어딘가에서 'Draggable' 클래스를 작동시키면 'dragdrop.js'파일이 로드되고 작동시킨 함수는 온로드(onload) 이벤트에 의해 실행되며 위에서 미리 선언한 함수는 'dragdrop.js' 원래의 클래스로 오버라이드(override)되는 로직이다. 위 코드는 이곳에서 실제 사용되는 코드로 키워드 뷰어나 댓글수정 등(Draggable 이 적용된 곳)에 적용되어 있으니 직접 사용해보자. FirefoxJSView 플러그-인을 설치하면 자바스크립트 및 스타일시트의 사용현황을 쉽게 확인 할 수 있다.

var FishEye = function(){ new Include('fisheye.js'); };
var CMotion = function(){ new Include('cmotion.js'); };
var Favicon = function(){ new Include('favicons.js'); };
위 코드는 어떤 역할을 할까? 그렇다. 해당 기능이 작동할 때에만 자바스크립트가 로드되고 오버라이드 된다. 'fisheye'는 어안메뉴이고, 'cmotion'은 블로그 상단의 썸네일 모션이며, 'favicons'는 댓글쓴이의 이름앞에 파비콘을 찍어주는 작은 단위의 라이브러리들이다. 이 함수들을 개별 파일로 분리하여 필요한 때에 필요한 파일을 동적으로 호출하는 것이다. 오버라이딩하는 기법을 사용하지 않고 아래처럼 단순히 로드해서 사용해도 무방하다.

if($$('pre code').length){
new Include('syntax.js, syntax.css');
}

끝으로, 이 작업을 거친후 페이지당 약 50-60KB로 용량을 절약할 수 있었으며, 하루평균 최소 100MB이상의 트래픽 손실 및 비용 절감 효과가 있다는 결론이 나왔다. 더불어 브라우저가 로딩-압박에서 조금은 벗어난 것을 체감할 수 있었다. 앞으로 30~50KB이상을 더 세부적, 체계적으로 분리할 예정이며, 마치 관례처럼 사용되고 있는 자바스크립트 사용법(All in header)에 조금씩 변화를 가져볼 생각이다.

알려진 문제점
- IE에서 onload 이벤트가 작동하지않아 setTimeout으로 처리함, 0.8초 후 오버라이드 되도록 처리 함. 때문에 통신상황이 좋지 않거나 누락된 경로인경우 오류가 발생 할 수 있음.(해결함)
- IE6, IE7 그리고 파이어폭스 1.5, 2.0에서 테스트 됨
- 오페라 9.20에서 아규먼트로 넘긴 이벤트가 작동하지 않음

2007-04-24 업데이트
- Include 함수를 클래스화 함
- 경로와 확장자를 근거로 파일의 확장자 및 경로가 구분되도록 함
- 더이상 boolean을 반환하지 않음
- 스크립트 로드(온로드) 이벤트를 브라우저별로 처리함
- options에 기본경로를 넘길 수 있음(파일과 옵션에 모두 경로가 있는 경우 파일명에 기입한 경로명를 우선시 함)
new Include('1.js, 2.css, /js/3.js, 4.css, 5.js', { JSPath : 'http://js.com/',CSSPath : '/css/' });
//=> 1.js = 'http://js.com/1.js'
//=> 2.css = '/css/2.css'
//=> 3.js = '/js/3.js/'
//=> 4.css = '/css/4.css/'
//=> 5.js = 'http://js.com/5.js'

2007-04-25 업데이트
- IE의 온로드 이벤트 버그 수정
- 아규먼트로 넘겨진 함수는 인클루드한 파일 타입이 자바스크립트이고 로드가 성공했을 때 에만 발생
- 복수개 파일을 호출한 때에는 마지막 자바스크립트 파일의 로드가 완료 되는 시점에 이밴트가 한번만 발생
Posted by 퓨전마법사
,