하나의 웹 페이지에서 여러 jQuery 버전을 사용해야 하는 일은 매우 드뭅니다. 그러나 레거시 코드와 신규 작성된 코드가 혼재된 경우, 서로 다른 버전의 jQuery를 기반으로 하는 컴포넌트를 활용하는 경우에는 이러한 일이 발생하기도 합니다. 이 까다로운 요구사항을 만족하기 위해서는 2가지 문제를 해결해야 합니다.
문제와 해결책
첫 번째 문제는 변수 덮어쓰기입니다. jQuery는 임베드하는 순간, 전역변수 $에 함수를 할당합니다. 따라서 두 번째 jQuery 버전을 임베드하면, 첫 번째로 임베드했던 jQuery의 $ 변수를 덮어쓰게 될 것입니다.
이 문제는 손쉬운 해결책이 있습니다. 바로 jQuery가 제공하는 noConflict()라는 함수입니다. 이 함수는 jQuery가 전역변수 $에 대한 제어권을 포기하고 할당을 해제하게 합니다. 따라서 한 버전의 jQuery를 이용하여 소스코드를 실행한 후, 다음 버전의 jQuery를 임베드하기 전에 noConflict() 함수를 실행하면, 또다른 버전의 jQuery로 전역변수 $가 할당되어 다음 소스코드를 실행할 수 있습니다.
두 번째 문제는 비동기 임베드입니다. jQuery를 임베드하면 이는 비동기적으로 실행되기 때문에, 해당 파일의 로딩이 끝나는 시점을 제어할 수 없습니다. 한 버전의 jQuery가 임베드 되어서 소스코드를 실행하고 있는데, 두 번째 버전의 jQuery의 임베드가 완료되면, noConflict() 함수가 사용되기도 전에 jQuery의 버전이 바뀔 수 있습니다. 따라서 비동기적으로 임베드 되는 jQuery를 순차적으로 임베드할 수 있는 방안이 필요합니다.
이를 위해서는 async와 await를 이용할 수 있습니다. aync 속성의 함수에서 await 지시자를 사용하면, 비동기 처리가 끝날 때까지 다음 행의 소스코드를 실행하지 않고 기다리게 합니다.
간단한 순차처리 구현
위 해결책들을 이용하여, 여러 jQuery 버전을 이용하도록 구현한 가장 간소한 형태의 소스코드는 아래와 같습니다.
(async () => {
await import('//code.jquery.com/jquery-3.6.0.min.js?1');
$('#dv1').text('First');
$.noConflict();
await import('//code.jquery.com/jquery-1.12.4.min.js?2');
$('#dv2').text('Second');
$.noConflict();
await import('//code.jquery.com/jquery-2.2.4.min.js?3');
$('#dv3').text('Third');
$.noConflict();
await import('//code.jquery.com/jquery-3.6.0.min.js?4');
$('#dv4').text('Fourth');
$.noConflict();
})();
이 코드는 매 블럭마다 3가지 과정을 수행합니다.
- import()로 jQuery를 임베드하고, await 지시자를 이용하여 해당 파일의 로딩이 끝날 때까지 기다립니다.
- jQuery를 사용하는 소스코드를 실행합니다.
- noConflict()로 Global Scope에 할당된 변수 $를 할당해제합니다.
이와 같이 구현하면 소스코드 실행을 위해 사용한 특정 버전의 jQuery 전역변수 $를, 다음 소스코드를 실행하기 전에 할당해제할 수 있습니다. 그러나 import() 할 때마다 jQuery 파일이 로딩되기 때문에, 동일한 버전의 jQuery를 사용하는 소스코드도 각각 jQuery를 임베드하는 점은 개선할 필요가 있습니다.
Javascript Class로의 구현
아래는 임베드의 중복을 막고자, Javascript Class로 이를 구현한 것입니다. 각 버전의 jQuery를 사용하는 소스코드를 콜백함수로 받아 배열에 담아 놓았다가, execute() 메서드를 실행하면 1번만 해당 버전의 jQuery를 임베드하고 담아놓은 콜백 함수를 하나씩 실행하는 과정으로 이루어집니다.
class jQueryExecutor {
static callbacks = {1:[], 2:[], 3:[]};
static add(jQueryVersion, callback) {
this.callbacks[jQueryVersion].push(callback);
}
static async executeVersion(key, version) {
if (this.callbacks[key].length > 0) {
await import('//code.jquery.com/jquery-' + version + '.min.js');
for(let fn of this.callbacks[key]) {
fn();
}
$.noConflict();
}
}
static async execute() {
await this.executeVersion(1, '1.12.4');
await this.executeVersion(2, '2.2.4');
await this.executeVersion(3, '3.6.0');
}
}
jQueryExecutor.add(3, () => {
$('#dv1').text('First');
});
jQueryExecutor.add(1, () => {
$('#dv2').text('Second');
});
jQueryExecutor.add(2, () => {
$('#dv3').text('Third');
});
jQueryExecutor.add(3, () => {
$('#dv4').text('Fourth');
});
jQueryExecutor.execute();