[JS][Module] Module
자바스크립트에서 모듈(module)은 자바스크립트 파일 그 자체를 말한다. 파일 하나가 모듈이 되는 것이다.
자바스크립트를 이용하여 무언가를 구현할 때 그 규모가 커지면 코드를 단 하나의 파일 안에 담기에는 코드량이 너무 많아지고 복잡해지기에 여러 개의 모듈로 파편화하여 나눈 후, 이들을 연결시키는 방식을 사용할 수 밖에 없다.
모듈 가져오기, 내보내기
자바스크립트에서 외부 모듈을 가져오거나 반대로 현재 모듈을 내보내 외부 모듈에서 이를 사용하게끔 할 수 있는 키워드로 각각 import, export가 있다.
export는 외부 모듈에서의 접근을 허락하고자 하는 변수 또는 함수 앞에 붙이면 된다. 그러면 해당 모듈을 가져오려는 모듈에서는 해당 모듈 파일을 import 키워드를 이용하여 가져오면 해당 기능들을 사용할 수 있는 구조이다.
다음은 함수와 변수를 export하고, 이를 다른 모듈에서 가져와 사용하는 예제이다. 다음에 보일 두 자바스크립트 모듈들은 모두 같은 디렉토리 내에 있다고 가정한다.
/**
* 주어진 정수 범위 내에서 무작위로 정수 하나를 추출하여 반환하는 함수.
minInt, maxInt도 범위에 포함된다.
* @param {number} minInt
* @param {number} maxInt
* @returns {number}
*/
export function getRandomInt(minInt, maxInt) {
let range = maxInt - minInt + 1;
return Math.floor(Math.random() * range) + minInt;
}
export let hello = 'hello, world!';
my-random.js
import {getRandomInt, hello} from './my-random.js';
console.log(getRandomInt(1, 6));
console.log(hello);
use-module.js
3
hello, world!
use-module.js 실행결과
import를 사용하는 법에는 다음의 형태들이 있다.
import {someFunc} from './myModule.js';
import './myModule.js';
중괄호 안에는 들여오는 모듈 내에서 사용하고자 하는 함수 또는 변수명을 적으면 된다.
또한, 모듈은 그 파일의 상대 또는 절대경로로 지정해야한다. 또한 뒤에 .js 확장자도 붙여야 제대로 import가 된다.
node.js 환경에서 import, export 사용 시 제대로 사용했음에도 SyntaxError가 발생한다면 다음의 방법을 고려해보는 것이 좋겠다. 해당 폴더 내에 package.json (없으면 이를 만든다) 파일을 연 후, 다음의 프로퍼티를 추가하면 된다.
{ "type": "module" }
그 후, import, export가 사용된 자바스크립트를 다시 실행해보면 잘 작동할 것이다. (package.json 파일은 루트 폴더에 단 하나만 있어도 된다)
인라인 모듈 스크립트
HTML 내부에서 자바스크립트 코드를 사용 가능하게 해주는 <script>
태그를 <script type=”module”>
과 같이 사용하면 해당 스크립트는 모듈임을 알려준다.
다음은 HTML 내 모듈 스크립트를 사용하며, 해당 모듈 내부에서 같은 디렉토리에 있는 다른 모듈의 함수를 불러와 사용하는 예제이다.
<style>
#display > p {
border: 1px solid black;
width: 6.1em;
text-align: center;
}
</style>
<div id="display">
<p>주사위</p>
<button>주사위 던지기</button>
</div>
<script type="module">
import {getRandomInt} from './my-random.js';
const displayBox = document.getElementById('display');
const displayButton = displayBox.querySelector('button');
const displayP = displayBox.querySelector('p');
displayButton.addEventListener('click', () => {
displayP.innerHTML = getRandomInt(1, 6);
});
</script>
예제 2-1
예제 2-1 초기 화면
예제 2-1 실행결과. “주사기 던지기” 버튼을 누르면 1부터 6까지 중 랜덤한 숫자가 하나 등장한다.
모듈의 특성
Module-level scope
모듈은 그 모듈 크기의 scope를 가진다. 즉, 모듈 내부에 정의된 변수, 함수는 export, import 키워드를 사용하지 않는 한 외부에서 접근할 수 없다.
let someVar = '자바스';
var.js
console.log(someVar);
print-var.js
print-var.js를 실행하면 다음의 에러 메시지를 얻는다.
ReferenceError: someVar is not defined
print-var.js 실행결과
이러한 모듈 레벨 스코프는 HTML 내부에서의 <script type=”module”>
에 대해서도 똑같이 적용된다.
단 한 번의 실행
A라는 모듈이 여러 외부 모듈에서 가져와 사용될 때, 최초로 호출될 때 딱 한 번만 A 모듈 내부의 코드가 실행된다. 그 이후 다른 외부 모듈에서 이 모듈을 사용하려고 하면 해당 모듈을 재실행하지 않고 그 실행 결과를 가져가게 되는 것이다.
다음의 예제를 보겠다.
console.log("print-msg 모듈에서 인사드립니다.");
print-msg.js
import './print-msg.js';
receiver1.js
import './print-msg.js';
receiver2.js
import './receiver1.js';
import './receiver2.js';
top-receiver.js
top-receiver.js를 실행하면 다음의 결과를 얻는다.
print-msg 모듈에서 인사드립니다.
top-receiver.js 실행결과
위 실행결과에서는 단 한 번의 메시지만 출력되었다. top-receiver.js 코드를 보면 최초로 print-msg.js를 가져오고 있으므로 이 때 해당 모듈 내 코드가 실행된다. 그 후에는 다른 모듈에서 가져오더라도 재실행이 되지 않기에 위 실행결과에서처럼 단 한 번만 메시지가 출력된 것이다.
외부 모듈들이 모듈을 공통으로 가져와 사용한다면, export하는 해당 모듈의 자원을 공통으로 사용하게 되는 것이다. 그래서 외부 모듈들 중 하나가 해당 모듈의 자원을 변경하면 그 정보가 업데이트되어 다른 외부 모듈들에서도 변경된 정보가 보여진다. 다음은 이를 보여주는 예제이다.
export let user = {
name: "자바스",
age: 20,
job: '웹 개발자',
printUserInfo() {
console.log("===== 사용자 정보 =====");
console.log(`이름: ${this.name}`);
console.log(`나이: ${this.age}`);
console.log(`직업: ${this.job}`);
}
};
user-info.js
import {user} from './user-info.js';
// 객체 리터럴 정보 변경.
user.name = '파이썬';
user.age = 30;
user.job = '회사원';
console.log('from change-info.js');
user.printUserInfo();
change-info.js
import {user} from './user-info.js';
console.log('from print-info.js');
user.printUserInfo();
print-info.js
import './change-info.js';
import './print-info.js';
top-script.js
top-script.js를 실행하면 다음의 결과를 얻는다.
from change-info.js
===== 사용자 정보 =====
이름: 파이썬
나이: 30
직업: 회사원
from print-info.js
===== 사용자 정보 =====
이름: 파이썬
나이: 30
직업: 회사원
top-script.js 실행결과
이 실행결과는 분명 user-info.js 파일 내 데이터와는 전혀 다른 데이터임을 알 수 있다. 해당 실행결과의 데이터들은 change-info.js에서 온 데이터들이다. 즉, change-info.js에서 user-info.js의 객체 리터럴 내 프로퍼티 값들을 변경하였다. 그러면 이 변경된 정보가 user-info.js에 반영되어 이를 import하는 다른 외부 모듈 print-info.js에서도 변경된 정보의 user 객체 리터럴을 받게 되는 것이다.
정리하자면, 특정 모듈을 여러 개의 모듈들이 가져다 사용할 때,
- export하는 모듈은 단 한 번만 실행된다. 그 이후로 다른 외부 모듈들이 똑같이 해당 모듈을 가져와 사용해도 재실행은 되지 않는다.
- export하는 모듈 내 자원들은 이를 import하는 여러 외부 모듈들이 공통으로 공유하는 자원이 된다. 그래서 하나의 외부 모듈에서 해당 모듈 내 자원을 변경하면 다른 외부 모듈에서도 이 변경된 사실이 반영되어 나타난다.
모듈과 this
모듈 내부에서의 this는 undefined가 된다. 이는 일반 스크립트에서의 this가 전역 객체인 것과는 다른 점이다.
<script>
console.log(this);
</script>
<script type="module">
console.log(this);
</script>
예제 3-1
window {window: Window, self: Window, document: document, name: '', location: Location, …}
undefined
예제 3-1 출력결과
브라우저에서의 모듈의 특징
지연 실행
문서를 브라우저가 렌더링할 때, 모듈 스크립트에 대해 다음의 특징들을 보인다.
- 외부 모듈 스크립트를 불러오는 과정과 HTML 문서 렌더링이 병렬적으로 진행된다. 즉, 둘 중 하나가 먼저 로딩된 후에 그 후에야 다른 하나가 로딩되는 구조가 아니라는 것이다. 즉, 로딩 과정은 병렬적이다.
- 모듈 스크립트는 HTML 문서 로딩보다 더 빨리 로딩된 경우, 모듈 내 코드들을 실행시키지 않고, HTML 문서가 모두 로딩된 후에야 실행된다. 즉, 실행 과정은 HTML → 모듈로 직렬적이다. ⇒ 지연 실행
- 여러 모듈들이 있을 경우 문서상 위쪽의 모듈부터 순서대로 실행된다.
이로 인해, 모듈 스크립트는 항상 HTML 문서 전체를 탐색, 접근할 수 있다. 그래서 만약 어떤 모듈이 HTML 내 요소를 가져와 사용해야 할 때, 모듈 스크립트가 요소보다 위에 있어도 문제없이 작동한다. 다음은 그 예시이다.
<script>
const labelDiv = document.getElementById('label');
try {
console.log(`${labelDiv.innerText} from normal script`);
} catch (e) {
console.log(labelDiv);
}
</script>
<script type="module">
const labelDiv2 = document.getElementById('label');
console.log(`${labelDiv2.innerText} from module script`);
</script>
<div id="label">글자</div>
예제 4-1
null
글자 from module scrip
예제 4-1 콘솔창 결과
위 예제에서, 모듈 스크립트인 <script type=”module”>
은 사용하고자 하는 요소가 뒤에 있더라도 HTML 문서 렌더링 완료 후에 실행되기에 문제없이 가져와서 사용할 수 있다. 반면 일반 스크립트의 경우 해당 스크립트가 먼저 렌더링된 뒤에야 div 요소가 렌더링 되므로 해당 요소를 가져와 이를 사용할 수가 없다.
이렇게 모듈을 사용할 경우, HTML 문서가 보여진 뒤에 모듈이 실행되므로, 사용자 입장에서는 아직 모듈은 실행되지 않은 상태의 HTML 문서가 보일 것이다. 이 상태에서 사용자가 해당 사이트의 버튼과 같은 요소를 클릭하면 원하는 결과를 얻지 못할 수 있다. 따라서 이런 경우, 모듈까지 로딩될 동안 사용자가 HTML 문서와 상호작용하지 못하도록 로딩창 같은 것을 띄우는 등의 조치가 필요하다.
인라인 스크립트의 비동기 처리
인라인 모듈 스크립트에 async 속성을 붙이면 이를 비동기적으로 실행한다. 즉, 다른 스크립트나 HTML 렌더링 작업 등을 기다리지 않고 바로 실행한다.
<script async type="module">
import ...
// 작업할 코드
</script>
References
[1] 모듈 소개
[2] node.js에서 모듈 import, export 사용하는 법
Node.js에서 ES 모듈(import/export) 사용하기
This content is licensed under
CC BY-NC 4.0
댓글남기기