[Day40] Javascript - 모듈(Module)

Validator·2023년 8월 9일
0

Javascript - 모듈

개발하는 애플리케이션의 크기가 커지면 언젠간 파일을 여러 개로 분리해야 하는 시점이 오게 된다. 이때 분리된 파일 각각을 '모듈(module)'이라고 부르는데, 모듈은 대개 클래스 하나 혹은 특정한 목적을 가진 복수의 함수로 구성된 라이브러리 하나로 구성되게 된다.

자바스크립트가 만들어진 지 얼마 안 되었을 때는 자바스크립트로 만든 스크립트의 크기도 작고 기능도 단순했기 때문에 자바스크립트는 긴 세월 동안 모듈 관련 표준 문법 없이 성장할 수 있었다.

그런데 스크립트의 크기가 점차 커지고 기능도 복잡해지자 자바스크립트 커뮤니티는 특별한 라이브러리를 만들어 필요한 모듈을 언제든지 불러올 수 있게 해준다거나 코드를 모듈 단위로 구성해 주는 방법을 만드는 등 다양한 시도를 하게 된다.
모듈 시스템은 2015년에 표준으로 등재됐다. 이 이후로 관련 문법은 진화를 거듭해 이제는 대부분의 주요 브라우저와 Node.js가 모듈 시스템을 지원하고 있는 상태이다.

유지보수성 : 기능들이 모듈화가 잘 되어있다면, 의존성을 그만큼 줄일 수 있기 때문에 어떤 기능을 개선한다거나 수정할 때 훨씬 편하게 할 수 있다.
네임스페이스화 : 자바스크립트에서 전역변수는 전역공간을 가지기 때문에 코드의 양이 많아질수록 겹치는 네임스페이스가 많아질 수 있다. 그러나 모듈로 분리하면 모듈만의 네임스페이스를 갖기 때문에 그 문제가 해결된다.
재사용성 : 똑같은 코드를 반복하지 않고 모듈로 분리시켜서 필요할 때마다 사용할 수 있다.

모듈이란

모듈은 단지 파일 하나에 불과합니다. 스크립트 하나는 모듈 하나라고 볼 수 있다.
모듈에 특수한 지시자 export와 import를 적용하면 다른 모듈을 불러와 불러온 모듈에 있는 함수를 호출하는 것과 같은 기능 공유가 가능하게 된다.

export 지시자를 변수나 함수 앞에 붙이면 외부 모듈에서 해당 변수나 함수에 접근할 수 있다(모듈 내보내기).
import 지시자를 사용하면 외부 모듈의 기능을 가져올 수 있다(모듈 가져오기).

export 지시자를 사용해 파일 sayHi.js 내부의 함수 sayHi를 외부로 내보내보는 예시.

// 📁 sayHi.js
export function sayHi(user) {
  alert(`Hello, ${user}!`);
}

이제 import 지시자를 사용해 main.js에서 함수 sayHi를 사용할 수 있게 해봅시다.

// 📁 main.js
import {sayHi} from './sayHi.js';

alert(sayHi); // 함수
sayHi('John'); // Hello, John!

위 예시에서 import 지시자는 상대 경로(./sayHi.js) 기준으로 모듈을 가져오고 sayHi.js에서 내보낸 함수 sayHi를 상응하는 변수에 할당한다.

이제 브라우저에서 모듈이 어떻게 동작하는지 예시를 이용해 알아보자.
모듈은 특수한 키워드나 기능과 함께 사용되므로 script type="module" 같은 속성을 설정해 해당 스크립트가 모듈이란 걸 브라우저가 알 수 있게 해줘야 한다.
(잘 까먹고 놓치기 쉬운 부분인데, 반드시 기억하고 있자!)

<script type="module">
 import {sayHi} from './say.js';

 document.body.innerHTML = sayHi('John');
</script>

브라우저가 자동으로 모듈을 가져오고 평가한 다음, 이를 실행한 것을 확인할 수 있다.

모듈은 로컬 파일에서 동작하지 않고, HTTP 또는 HTTPS 프로토콜을 통해서만 동작한다. 로컬에서 file:// 프로토콜을 사용해 웹페이지를 열면 import, export 지시자가 동작하지 않는다. 따라서 이를 실행하려면 vsc의 live server를 이용하면 좋다.

모듈의 핵심 기능

엄격 모드로 실행됨

모듈은 항상 엄격 모드(use strict)로 실행된다. 선언되지 않은 변수에 값을 할당하는 등의 코드는 에러를 발생시킨다.

<script type="module">
  a = 5; // 에러
</script>

모듈 레벨 스코프

모듈은 자신만의 스코프가 있다. 따라서 모듈 내부에서 정의한 변수나 함수는 다른 스크립트에서 접근할 수 없다.

외부에 공개하려는 모듈은 export 해야 하고, 내보내진 모듈을 가져와 사용하려면 import 해줘야 합니다.

전역변수를 대신하여 hello.js에 user.js를 가져와 필요한 기능을 얻을 수 있습니다.

아래와 같이 코드를 수정하면 정상적으로 동작합니다.

import {user} from './user.js';

document.body.innerHTML = user; // John

브라우저 환경에서도 script type="module"을 사용해 모듈을 만들면 독립적인 스코프가 만들어집니다.

<script type="module">
  // user는 해당 모듈 안에서만 접근 가능합니다.
  let user = "John";
</script>

<script type="module">
  alert(user); // Error: user is not defined
</script>

참고로 브라우저 환경에서 부득이하게 window 레벨 전역 변수를 만들어야 한다면 window 객체에 변수를 명시적으로 할당하고 window.user와 같이 접근하는 방식을 취하시면 됩니다. 그런데 이 방법은 정말 필요한 경우에만 사용하길 권유합니다.

단 한 번만 평가됨

동일한 모듈이 여러 곳에서 사용되더라도 모듈은 최초 호출 시 단 한 번만 실행됩니다. 실행 후 결과는 이 모듈을 가져가려는 모든 모듈에 내보내 집니다.
이런 작동 방식은 중요한 결과를 초래합니다. 예시를 통해 이에 대해 알아봅시다.

alert 함수가 있는 모듈(alert.js)을 여러 모듈에서 가져오기로 해봅시다. 얼럿 창은 단 한 번만 나타납니다.

// 📁 alert.js
alert("모듈이 평가되었습니다!");
// 동일한 모듈을 여러 모듈에서 가져오기

// 📁 1.js
import `./alert.js`; // 얼럿창에 '모듈이 평가되었습니다!'가 출력됩니다.

// 📁 2.js
import `./alert.js`; // 아무 일도 발생하지 않습니

ES6(ES2015) 방식

import 와 export 구문을 사용하는 방식으로 나에게는 이 방식이 가장 익숙하다. 하지만 모든 브라우저가 지원하는 것이 아니기 때문에 Babel을 통해 변환시켜서 사용한다. 모듈 A,B가 있고 각각을 export 로 내보내는 방식과 그에 따라 어떻게 import 로 불러오는지 살펴보자.

moduleA.js

const A = () => {};
export default A;

moduleB.js

export const B = () => {};

index.js

import A from 'moduleA';
import { B } from 'moduleB';

export 와 export default 비교

먼저 export와 export default 를 비교해보자.

/* 모듈 내보내기 */
export function sayLikeIt(name) {
  console.log(`${name} 참 좋다.`);
}

export 지시자를 사용하여 sayLikeIt 함수를 만들어서 다른 파일에서 불러올 수 있도록 작성하였다.
export 지시자를 사용했을 때는 { } 해당 모듈의 이름을 넣어서 불러올 수 있다.

/* 모듈 불러오기 */
import { sayLikeIt } from "./test";

sayLikeIt("아이유");

여기서 참고할 부분은 export 로 보낸 모듈은 import 할 때 모듈로 만든 변수 또는 함수의 이름을 중괄호로 감싸지 않으면 에러가 발생한다.

export 키워드를 사용하면 여러개의 함수나 변수를 보내는 것도 가능하다.

/* export를 함수마다 사용 */
export function sayLikeIt(name) {
  console.log(`${name} 참 좋다.`);
}

export function sayRun(name) {
  console.log(`${name}아 뛰어야 돼 시간이 안 기다려 준대`);
}

하나의 파일에 두 개의 함수를 export 시키고 있다.
이 경우 각각의 함수 앞에 export를 붙이지 않더라도 한번에 export 시킬 수도 있다.

/* export를 한 번만 사용 */
function sayLikeIt(name) {
  console.log(`${name} 참 좋다.`);
}

function sayRun(name) {
  console.log(`${name}아 뛰어야 돼 시간이 안 기다려 준대`);
}

export { sayLikeIt, sayRun };

불러와서 사용할 때는 동일하다.

/* export한 함수 불러오기 */
import { sayLikeIt, sayRun } from "./test";

sayLikeIt("아이유");
sayRun("지은");

0개의 댓글