본문 바로가기

프로그래밍/JavaScript & jQuery

[JavaScript] 동적 로딩

Dynamic Script Loading

동적으로 자바스크립트를 로딩하는 방법 중 하나는 script 태그를 자바스크립트 코드에서 직접 생성하는 것인데요. 다음과 같이 script 태그를 생성하고 src에 로딩할 주소를 넣음으로서 로딩하게 됩니다. 
1
2
3
4
5
var head= document.getElementsByTagName('head')[0];
var script= document.createElement('script');
script.type= 'text/javascript';
script.src= 'helper.js';
head.appendChild(script)

위의 코드를 실행함으로서 스크립트 파일을 로딩을 할 수는 있지만, 스크립트가 로딩이 완료되어서 사용할 수 있는 지 여부를 알 수가 없습니다. 이 때 사용할 수 있는 방법은 두 가지가 있습니다. 
하나는, script 태그의 onload 속성을 사용하는 것과 
또 다른 하나는, 로딩하는 스크립트 파일안에 자신이 로딩되었음을 알려주는 코드를 넣는 방법입니다. 
1에서의 문제점은 IE에서는 script 태그에서의 onload를 지원하지 않는다는 점인데요. MSDN에는 attributes에 포함되어 있기는 합니다만 호출되지는 않습니다. 

대신에 IE에서 지원하는 onreadystatechange(비표준)를사용하면 script태그에서 로딩이 완료되었는지 여부를 알 수 가 있습니다. 
1
2
3
4
5
6
7
8
9
10
11
12
var head= document.getElementsByTagName('head')[0];
var script= document.createElement('script');
script.type= 'text/javascript';
 
script.onreadystatechange= function () {
   if (this.readyState == 'complete'
         || this.readyState == 'loaded') helper();
}
script.onload= helper;
 
script.src= 'helper.js';
head.appendChild(script)

※ IE에서 onreadystatechange를 사용하여 로딩을 체크하는 상태 값으로 'loaded' 또는'complete'가 호출되기 때문에 두 값중 하나를 체크해서 실행하도록 합니다. 
※※ 두 번 호출될 수도 있기 때문에 해당하는 부분에 대해서는 중복로딩이 되지 않도록 처리를 해야합니다.

2의 문제점은 동적으로 로딩하려는 코드의 뒤에 로딩되었음을 알려주는 동작이 추가되어야 하는 문제점이 있습니다. script 태그를 사용하지 않고 다른 방법으로도 로딩은 가능합니다.

가장 대표적인 방법으로 XMLHTTPRequest를 사용하는 방법인데요. 사용방법은 다 아실테니 생략하도록 하겠습니다. ^^;;; 파일을 요청한 후, 요청한 파일을 response로 받은 다음 이를 eval()를 사용하여서 로딩하게 되는데요. 파일을 로딩하는게 아니라 파일을 텍스트로 로딩한 후, 이를 실행하기 때문에 
일반적인 자바스크립트 파일이 브라우저에서 캐시가 되지만 텍스트에 대해서는 캐시할 수 없습니다.
에러가 발생하여도 몇 번째 줄에서 발생하였는 지에 대한 정확한 정보를 얻기 힘듭니다. 
이 외에도 iframe을 사용하여 로딩을하거나 document.write()를 쓸 수도 있겠죠.


Degrading Script Tags
Steve Souders의 Coupling asynchronous scripts에서는 조금 다른 방식을 취합니다. 먼저, 이 방식을 들여다보기 전에, 위대하신 레식 사마의 Degrading Script Tags에 대해서 살펴봐야 하는데요. 

아래와 같이 외부 파일 형태로 존재하는 자바스크립트 파일과 이를 사용하는 코드를 
1
2
3
4
5
<script src="some-lib.js"></script>
<script>
  var foo = use_some_lib();
  foo.do.stuff();
</script>

다음과 같이 하나의 script 태그로 묶습니다. 
1
2
3
4
<script src="some-lib.js">
  var foo = use_some_lib();
  foo.do.stuff();
</script>

그리고, some-lib.js의 파일 안에 some-lib.js를 로딩하는 script 태그 안의 자바스크립트 코드를 실행하는 코드를 추가합니다. 
1
2
var scripts = document.getElementsByTagName("script");
eval( scripts[ scripts.length - 1 ].innerHTML );

이렇게 묶게 되면 some-lib.js에 정의되어 있는 코드와 이를 사용하는 코드를 하나로 묶을 수 있기 때문에 some-lib.js를 로딩하는데 실패하게 되는 경우, 사용하는 코드도 함께 실행하지 않게 할 수 있는 장점(Degrading)이 있습니다. 

Degrading Script Tags는 script 태그의 다음 특성을 사용합니다. 
script 태그에 src 속성이 포함되어 있는 경우, script 태그 사이에 정의되어 있는 자바스크립트 코드는 실행되지 않습니다. 
그러나, script 태그도 HTMLElement이기 때문에 innerHTML로 script 태그안에 정의되어 있는 코드에 접근이 가능합니다. 


Coupling asynchronous scripts
Steve Souders는 Script 태그를 사용하여 동적으로 로딩하는 방식에 Degrading Script Tags를 함께 사용하여, 관련된 코드들을 하나로 묶어버립니다.  
1
2
3
4
5
6
var script = document.createElement('script');
script.src = "sorttable-async.js";
 
script.text = "sorttable.init()";
 
document.getElementsByTagName('head')[0].appendChild(script);

sorttable-async.js에 정의되어 있는 코드를 호출하는 코드를 script태그의 text에 넣어서 커플링을 최소화 시킨 후, sorttable-asnyc.js의 뒤에 script 코드를 실행하는 코드를 추가하여 외부 파일의 코드가 로딩된 후, 바로 실행되게 합니다. 
1
2
3
4
5
6
7
8
9
10
var scripts = document.getElementsByTagName("script");
var cntr = scripts.length;
while ( cntr ) {
    var curScript = scripts[cntr-1];
    if ( -1 != curScript.src.indexOf('sorttable-async.js') ) {
        eval( curScript.innerHTML );
        break;
    }
    cntr--;
}

Steve Souders는 위의 코드처럼 초기에 로딩될 필요가 없는 자바스크립트 코드의 로딩을 동적으로 로딩되게 한 후, 페이지가 전체 로딩된 다음에 실행될 코드들을 window의 onload() 시 호출되도록 조정하여 Stuart Langridge의 sorttable script의 로딩 속도를 487ms에서 417ms까지 단축시켰습니다. 

 

 

jQuery.getScript() 사용시:

$.getScript("somescript.js"
    function() {
        // 자바스크립트 로드 후 실행
    }          
);
cs


좀 더 상세하게는:

$.getScript("somescript.js"function ( data, textStatus, jqxhr ) {
    console.log(data); // 받은 data 
    console.log(textStatus); // success
    console.log(jqxhr.status); // 200
    console.log('자바스크립트 로드 완료');
});
cs


jQuery 미사용, pure JavaScript: head 태그 안쪽에 DOM 생성:

var headTag = document.getElementsByTagName("head")[0];         
var newScript = document.createElement('script');
newScript.type = 'text/javascript';
newScript.onload = function() { console.log('자바스크립트 로드 완료') };
newScript.src = 'somescript.js';
headTag.appendChild(newScript);
 

 

 

#JavaScript에서 외부 소스 코드 삽입하기

0. eval

eval 은 전역객체의 함수 프로퍼티이다.
eval 함수를 사용하면 자바스크립트 소스 코드를 동적으로 로드하여 실행할 수 있다.
하지만 eval 함수로는 보안상 위험이 존재하기 때문에 그리고 eval 함수 이외에도 동적으로 자바스크립트 소스 코드를 실행할 수 있는 방법이 많이 존재하기 때문에 권장되지 않는 방법이다. 자바스크립트 컴파일러가 미리 최적화시킬 수 없기 때문에 코드 최적화에 대한 이야기에 단골로 등장하는 함수가 eval 함수이다.
eval 함수의 파라미터로 넘어온 String 인자는 Javascript Parser에 의해 파싱되고 실행된다.
 

1. Ajax Loading
1
2
3
4
5
6
7
8
9
10
11
12
13
function require(script) {
    $.ajax({
        url: script,
        dataType: "script",
        async: false,           // <-- This is the key
        success: function () {
            // all good...
        },
        error: function () {
            throw new Error("Could not load script " + script);
        }
    });
}
cs



2. jQuery Loading
jQuery를 사용하면 한 줄로 외부 자바스크립트 소스를 가져올 수 있다.
1
2
3
$.getscript(‘myscript.js’, function(){
    console.log(‘myscript.js loading!!);
});
cs


3. JavaScript
dynamically generate a JavaScript tag and append it to HTML document from inside other JavaScript code
JavaScript 만으로는 createElement, appendChild 등 메소드를 이용하여
동적으로 html 태그를 추가하여 외부 소스코드를 삽입할 수 있다.
1
2
3
4
5
6
7
8
9
function includeJs(jsFilePath) {
    var js = document.createElement("script");
 
    js.type = "text/javascript";
    js.src = jsFilePath;
 
    document.body.appendChild(js);
}
includeJs("/path/to/some/file.js");
cs

4. Require JS
Require JS 라는 모듈을 사용하면 보다 간편한 삽입을 할 수 있다.


5. ECMA Script 6
ECMA 6에서는 import 라는 문법을 제공한다.
1
2
3
4
5
6
7
import name from "module-name";
import { member } from "module-name";
import { member as alias } from "module-name";
import { member1 , member2 } from "module-name";
import { member1 , member2 as alias2 , [...] } from "module-name";
import name , { member [ , [...] ] } from "module-name";
import "module-name" as name;
cs


6. 모듈 패턴을 이용한 window 객체의 프로퍼티로 추가
각각의 자바스크립트 파일을 모듈화 하여, 즉시 실행 함수로 실행하고,
실행하여 생성된 생성자 함수를 전역 객체인 window 객체의 프로퍼티로 추가하여 접근하는 방법이다.
export를 통해 프로퍼티를 추가할 수 있다.
1
2
3
(function(exports){
 
})(this);
cs

 

 


[출처]
Jan Wolter - JavaScript Madness: Dynamic Script Loading
John Resig - Degrading Script Tags
Steve Souders - Coupling asynchronous scripts
※ Steve Souders가 Coupling asynchronous scripts 다음으로 쓴 Loading Scripts Without Blocking 글도 함께 보시면 도움이 많이 될 것 같습니다.

 



[출처] https://hooni.net/2173