module

Park Taejoon·2021년 10월 6일
0

지식창고

목록 보기
1/4
post-thumbnail

조사하게 된 계기
모듈이란 단어가 생소한데 앞으로 모듈을 사용할 일이 많을 것이기 때문에 미리 친해지고 싶어서 정리하게 되었다.

모듈이란 무엇일까

개발하는 애플리케이션 크기가 커지다 보면 파일을 여러 개로 분리해야 하는 시점이 오는데 이 때 분리된 각 파일을 모듈이라고 흔히 부른다. 모듈은 클래스나 특정 목적을 가진 함수로 구성된다.

보통 기능별로 나누고 이렇게 각 파일로 분할하다보니 재사용성이 좋아 개발 효율성과 유지보수성을 높일 수 있다는 장점이 있다.

그럼 모듈을 사용하지 않고 그냥 파일을 나눠서 사용하면 되는 것 아닐까?
모듈을 사용하지 않고 그냥 js파일을 나눠서 사용하게 되면 각 js파일이 독자적인 스코프를 갖지 않는다는 문제가 있다. 예를 들어 2개의 js파일을 로드하면 하나의 전역을 공유하게 되면서 의도치 않게 중복이 되는 현상이 일어날 것이다.

자바스크립트에서 모듈

자바스크립트에서는 태생적 한계로 모듈 기능이 기본적으로 없다. 그렇기 때문에 script 태그로 자바스크립트 파일들을 불러왔을 때 하나의 전역객체를 공유한다. 이 상태로는 모듈화를 할 수 없었다.

그래서 자바스크립트 커뮤니티는 모듈화를 위한 다양한 시도를 하게 된다. 그러다가 몇 가지 모듈 시스템이 대표적으로 자리를 잡게 되는데 바로 AMD, CommonJS다.

사용자들은 모듈화를 구현하기 위해서 결국 모듈 라이브러리를 사용해야 됐다. 추후 Node.js에서는 CommonJS방식을 따르고 브라우저에서는 ES6의 모듈을 사용하면서 모듈화를 구현할 수 있게 되었다.

하지만 브라우저에서의 ES6 모듈은 IE같은 구형 브라우저 미지원, 일부 기능 미지원, ES6모듈을 사용하더라도 따로 또 트랜스 파일링을 위한 바벨이나 번들링을 위해 웹팩을 사용해야 하는 불편함 등이 있기 때문에 webpack 등 모듈 번들러를 사용하는 것이 일반적이라고 한다.

import와 export

모듈은 파일 하나다. 즉, 스크립트 하나는 모듈 하나다.

이 개별적인 모듈들에 있는 기능들을 필요에 따라 호출하기 위해서 exportimport 를 사용한다.

먼저 export 는 단어 뜻 그대로 내보내는 역할을 하는데 외부 모듈에서 사용할 수 있게 내보내는 것이다.

다음으로 import 는 가져온다는 뜻인데 export 로 내보낸 모듈을 가져와서 쓸 수 있게 한다.

기본적인 모듈

현업에서는 사용하지 않지만 간단하게 모듈이 어떤 식으로 돌아가는지 이해하고 친해지는 것이 이번 포스팅의 목적이기 때문에 ES6의 모듈을 사용한다. 그리고 다양한 예제들을 통해 친숙해지자.

variables.js

내보낼 변수들이 정의된 곳.

const foo = 'foo';
const bar = 'bar';

export {foo, bar};

main.js

필요에 따라 모듈을 사용 하기 위한 진입점을 만들어서 필요한 것만 사용할 수도 있다.

import {foo, bar} from './variables.js';

console.log(foo);
console.log(bar);

index.html

ESM 사용을 위해서 type="module"을 명시해서 브라우저에서 모듈 시스템을 사용할 수 있게 하고 모듈이라는 것을 명확히 하기 위해 .mjs 확장자 붙이는 것을 권장하고 있다.

<script type="module" src="main.mjs"></script>

export와 export default

export default 구문을 사용해서 모듈을 대표하는 하나를 지정하고 다른 모듈에서 불러서 편하게 사용할 수 있다.

todos.js

let todos = [
  { id: 3, content: 'Javascript', completed: false },
  { id: 2, content: 'CSS', completed: true },
  { id: 1, content: 'HTML', completed: false }
];

const add = newTodo => {
  todos = [newTodo, ...todos];
};

const render = () => {
  todos
    .map(
      ({ id, content, completed }) => `
        <li id="${id}">
          <label><input type="checkbox" ${completed ? 'checked' : ''}>${content}</label>
        </li>`
    )
    .join('');
};


const Todos = { add, render }; 
export default Todos;

위 예제에서 export default {add, render}로 사용할 수 없다. 하나만 export할 수 있기 때문이다.

왜 이렇게 사용할까? 보통 모듈은 복수의 함수가 들어있는 라이브러리 형태가 아니고 개체 하나만 선언되어 있는 모듈을 사용하기 때문에 함수, 클래스, 변수 등 한 개의 기능을 하는 한 개의 모듈로 구현되어 있을 것이다. 그런데 이런 식으로 구현하면 파일이 많아질 것이다. 파일 이름을 잘 짓고 폴더를 나눔으로서 좀 더 명확하게 구분할 수 있게끔 하겠지만 export default를 사용하면서 해당 모듈 내부에는 한 개의 개체만 있다는 것을 명확히 전달할 수 있어서 사용하는 것 같다. 개인적으로 압축해서 보낸다는 느낌을 받았다.

하나의 모듈내부에 여러 함수나 변수가 있고 선택적으로 내보내고 싶을 때 export default를 사용하지 않고 export {변수, 함수, 함수} 형태로 default를 제외하고 사용할 수도 있다. 이렇게 내보내면 import 할 때 식별자 이름을 동일하게 불러와야 한다는 제약사항이 있다.

불러오고 나면 as 를 이용해서 다른 식별자 이름으로 바꿀 수도 있고 혹은 이렇게 내보낸 것을 한번에 사용하고 싶을 때는 *를 사용할 수도 있다.

한번에 다 불러오면서 이름 변경을 사용하면 이렇게 사용이 가능하다.

import * as todos from './todos.mjs'

궁금했던 부분 해결해보자

  • import, export 문은 블럭문안에서 작동하지 않는다.
if(true) {
  import {sayHi} from "./say.js" // error발생
}
  • 가독성을 생각해서 스크립트 맨 위에 import 문을 사용해서 가져온다는 것을 명시하고 export문은 제일 하단에 명시하는 것 같다. 내보낼 때는 모듈 내에서 기능을 다 만든다음에 export를 하고 가져올 때는 가져온 뒤에 사용해야 되기 때문에 제일 위에 import가 위치한다고 생각하자.

  • 같은 모듈을 여러번 import해도 한번만 평가된다.

  • async를 사용해서 외부 스크립트를 불러올 때 스크립트 로딩이 끝나면 HTML파싱이 완전히 끝나지 않았더라도 모듈이 실행되는데 이런 특징을 이용해서 어디에도 종속되지 않는 기능을 구현할 수 있다고 한다. (광고, 방문자 수 카운터)

  • 브라우저에서 import 문에는 반드시 상대나 절대 경로를 입력해줘야 한다. Node.js 환경이거나 번들링 툴을 사용할 때는 경로를 명시해주지 않아도 해당 모듈을 찾을 수 있는 방법을 알아서 명시해주지 않아도 된다.

  • 모듈엔 자동으로 strict mode로 동작한다.

  • 선언과 동시에 export하려면 export 키워드 식별자명 이렇게 해서 선언과 동시에 내보낼 수 있다.

모듈화를 하지 않고 그냥 사용하면 어떻게 될까

script태그를 여러개 사용해서 파일들을 그냥 불러오면 파일들을 나눠서 사용하는 것처럼 모듈화가 되는 것이 아닌가? 결론부터 말하면 아니다.

index.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <script src="./bar.js"></script>
    <script src="./foo.js"></script>
    <title>Document</title>
  </head>
  <body>
  </body>
</html>

foo.js

const x = 10;

console.log(x);

bar.js

const x = 10;

console.log(x);

index 페이지를 오픈해서 콘솔을 확인해보면 아래와 같은 결과가 나옵니다.
즉, js파일로 2개의 스코프를 나눈 것처럼 느꼈지만 실제로는 하나의 스코프로 전역변수를 공유하게 된 것입니다.

10문법에러 : x가 이미 선언되었다.

결과

module을 사용하면서 궁금했던 부분들을 해결할 수 있었고 가장 좋았던 점은 여러번 쳐보면서 실험을 해보니까 손에 조금 더 익는 것 같은 느낌이 든다. 혼자 공부할 때 module을 사용할 일이 없었어서 앞으로 많이 사용해보고 싶다. 그리고 다음에는 Node.js에서 CommonJS 방식의 module도 알아봐야겠다.


참고

https://ko.javascript.info/modules-intro

https://blaize.tistory.com/58

https://lily-im.tistory.com/21

https://enyobook.wordpress.com/2016/08/17/export-default%EC%97%90-%EB%8C%80%ED%95%B4/

https://medium.com/@_diana_lee/default-export%EC%99%80-named-export-%EC%B0%A8%EC%9D%B4%EC%A0%90-38fa5d7f57d4

0개의 댓글