본문 바로가기
JS

(191222) Common JS & ES6 Modules

by 양털의매력 2019. 12. 22.

Module (모듈)

프로그래밍에서 모듈이란 외부에 영향을 받지 않는 독립적이고 재사용 가능한 코드들의 묶음이다.

(OOP의 Encapsulation과 동일한 개념)

 

모듈화를 통해 변수나 함수들의 name space를 보장해주고, 모듈화를 통해 기능적인 코딩이 가능해진다.

 

즉, 쉽게 생각하면 모듈은 프로그램의 부품 하나하나라고 생각해 볼 수 있다. 그리고 모듈은 하나의 독립적인 소프트웨어라 볼 수 있다.

 

우리가 프로그램 등을 만들 때 하나의 파일에 모든 코드를 전부 작성하지않는다 

(할수는 있지만, 가독성이라던가 유지보수, 확장성 등의 문제가 있기 때문에 지양해야함!!)

 

그래서 보통은 역할과 기능에 맞게 여러가지 파일 (모듈)을 만든다.

이렇게 만든 모듈들을 불러와서 사용하는데 불러오는 방법에는 크게 다음과 같은 방법이 있다. (배운 것 위주로)

 

1. ES6 Modules -> import , export (default)

2. Common JS -> require, (module) exports

3. Browser -> <script src= "...">

 

기존의 자바스크립트 (ES5, 예전의 대부분의 브라우저에서 자바스크립트)

고전적인 웹 프로젝트에서 자바스크립트를 사용하는 방법을 보면 HTML 내부에 <script>> 태그를 이용하여 모듈을 불러오고 있다.

하지만 이렇게 삽입되면 모듈(파일)끼리 서로 모듈을 공유하는데 제약이 없어진다. 즉, script로 로드된 모듈들은 모두 window 객체의 속성이기 때문에 서로 다른파일에 위치해 있어도 모든 객체를 공유 할 수 있다.

따라서 각 파일이 독립적으로 기능하지 못하기 때문에 여러 문제들 (변수 이름이 겹치는 등)의 문제 때문에 즉시 실행함수 등을 사용하여 의존성을 관리해야하는 번거로움 등이 있었다.

 

이를 해결하기 위한 수단으로 ES6의 모듈이나 CommonJS가 등장하게 된다.

 

ES6 Modules (ESM)

ES6 이전까지는, 브라우저 환경에서 사용 가능한 표준 모듈 시스템이 없었다.

 

하지만 ES6에 와서 모듈 시스템이 정식으로 도입이 되었다. 

-> import 와 export를 통해 가능

 

(하위 호환성을 위하여 바벨과 같은 트랜스파일 모듈이 필요할 수도 있다...)

 

export

- named export

선언된 변수명 그대로 export한다

 
 // 1. 변수 선언 즉시 내보내는 방법
 export let temp1;
 export const temp2;
 export function temp3 () {};
 export class Temp {};
 
 // 2. 변수 먼저 정의하고, 모아서 내보내는 방법
 let temp1;
 const temp2;
 
 export { temp1, temp2 }
 
 // 3. 먼저 정의된 함수를 다른 이름으로 내보내는 방법
 let temp1;
 export { temp1 as temp2 } // 이 경우 import 할 때 temp2로 해야한다
 

- defalt export

모듈에서 단 하나만 존재해야한다!!!

 
 export defalut expression;
 export defalut function () {};
 export defalut function temp() {}
 export defalut class {};
 export defalut class Temp {};
 
 // named export 처럼 묶어서 내보내기 가능
 const temp1 = {};
 const temp2 = () => {};
 export { temp1 as default, temp2 } // as를 이용해서
 

 

import

다른 파일에서 모듈을 불러올 때 (named export나 default export를 불러올 수 있다)

 

temp라는 모듈이 있다고 가정

 
 // temp.js
 export function print(msg) {}; // console 찍는 함수로 가정
 export default {
   add(a,b) {}, // 더하기 함수 가정
   subtract(a,b) {}, // 빼기 함수 가정
 }
 

import 기본 사용법

 
 // app.js
 
 // default export를 가져와서 temp로 
 import temp from './temp'; 
 
 temp.add(1,2) // 3
 
 // named export를 가져온다
 import { print } from './temp';
 
 print("hi") // "hi"
 

as로 새로운 이름 부여

 
 // app.js
 
 import { print as logger } from './temp';
 
 logger("hi") // hi!
 

* 를 이용하여 모두 불러오기

 
 // app.js
 
 import * as temp from './temp';
 
 temp.print("hi") // hi
 
 // default export와 named export를 같이 하는 모듈을 *로 불러올 경우
 // default 라는 프로퍼티가 생긴다!!
 
 temp.default.add(1,2) // 3
 

default export와 named export 모두 불러오기

 
 // app.js
 
 import temp, { print } from './temp';
 
 temp.add(1,2) // 3
 print("hi") // hi
 

변수에 바인딩 없이 모듈의 사이드 이펙트만 가져오기

 
 // app.js
 
 import 'temp';
 // temp.js 가 실행된다. 변수에 담지 않았기 때문에 사용은 불가
 

 

CommonJS

Node.js에서 사용하는 모듈 시스템이다.

-> require, exports, module.exports를 통해 가능

 

참고로 CommonJS는 자바스크립트의 표준을 만들기 위한 CommonJS 그룹의 노력으로 시작된 것으로 CommonJS 그룹은 자바스크립트 생태계를 위한 단체이다.

(Node.js에서 fs나 path 등의 모듈이 내장 API로 제공되도록 한 것도 CommonJS 그룹의 역할이 있었음)

 

Node.js 에서는 파일하나하나가 모듈로 기능한다.

 

module.exports 와 exports

먼저 moudle이라는 키워드가 있는데, module은 현재 모듈에 대한 정보를 담고 있는 객체(object)이다.

 

 
 // temp.js
 
 const PI = 3.14;
 module.exports.PI = PI;
 
 // app.js
 
 const temp = require('./temp');
 console.log(temp.PI); // 3.14;
 

이때 temp.js의 module 객체를 보면 다음과 같다.

 
 Module {
   ...
   exports : { PI: 3.14},
   ...
 }
 

exports 도 module.exports와 같은 동작을 수행한다.

 
 // temp.js
 
 exports.PI = 3.14;
 
 // app.js
 
 const temp = require('./temp');
 console.log(temp.PI) // 3.14
 

그럼 exports와 module.exports 의 차이점은 무엇일까??

 

exports는 module.exports 를 call by reference로 참조하고 있는 형태이다!!

(이를 굳이 코드로 표현하면 다음과 같다)

 
 let module = {
   exports: {}
 }
 
 let exports = module.exports;
 

따라서 exports를 사용할 때 exports에 값을 직접 할당하면 안된다!!

왜냐하면 참조관계가 깨져버리기 때문에 더 이상 module.exports를 참조하지 않아서 모듈로써 기능하지 않기 때문이다.

그래서 exports를 사용할 때에는 프로퍼티로 추가하는 방식으로 사용해야한다.

 

다음은 module.exports와 exports의 사용 예이다.

 
 // module.exports에 직접 할당하는 방식
 // temp.js
 
 function add(a,b) {
   return a+b;
 }
 
 module.exports = add; // module 객체안의 exports를 직접 할당
 
 // app.js
 const add = require('./temp.js');
 console.log(add(1,2)); // 3
 
 
 // module.exports에 여러 기능을 모듈화할때
 
 // temp.js
 
 function add(a,b) {} // 더하기
 function sub(a,b) {} // 빼기
 function mul(a,b) {} // 곱하기
 function div(a,b) {} // 나누기
 
 module.exports = {
   add : add,
   sub : sub,
   mul : mul,
   div : div
 };
 
 // app.js
 const add = require('./temp').add;
 const { mul } = require('./temp');
 
 console.log(add(1,2)) // 3
 console.log(mul(1,2)) // 2
 
 // or
 const temp = require('./temp');
 
 console.log(temp.add(1,2)) //3
 
 
 // exports 사용하기
 
 // temp.js
 
 exports.add = function(a,b) {};
 exports.sub = function(a,b) {};
 exports.mul = function(a,b) {};
 exports.div = function(a,b) {};
 
 // app.js
 
 const add = require('./temp').add;
 const { mul } = require('./temp');
 
 const temp = require('./temp');
 
 add(1,2) // 3
 mul(2,3) // 6
 
 temp.mul(2,3) // 6
 

 

require

require는 다른 모듈 파일을 불러온다.

공식문서에서 대표적으로 4가지 방법을 설명하고 있다.

 

1. File Modules

2. Folders as Modules

3. node_modules

4. Global Directory

 

- File Moduls

 (경로를 표시해서) 파일을 불러올 때 확장자가 .js, .json, .node 인 파일을 모듈로 불러온다. 이때 확장자는 생략 가능!!

 

- Folders as Modules

(경로를 표시해서) 폴더를 불러오면 다음과 같은 순서로 탐색한다.

 1. package.json 파일에 정의된 name 과 main 의 값을 활용 (???)

 2. 1번에서 탐색을 실패하면, 해당 폴더에 index.js 또는 index.node 파일이 있는지 확인 

      ( 즉, 자동으로 폴더에 index.js 파일을 먼저 탐색!!!, 왜 폴더에 index.js로 만드는지..!)

 3. 모두 실패하면 에러 throw

 

- node_modules

만약 파일의 경로에 대한 표시 없이 require를 호출하면 Node.js는 현재 모듈의 최상위 디렉토리에서 시작하여 /node_modules라는 경로를 앞에 붙이고 탐색한다.

 

만약 require('foo') 를 호출하면 아래와 같은 순서로 탐색한다.

 - /home/hyunsung/project/node_modules/foo.js

 - /home/hyunsung/node_modules/foo.js

 - /home/node_modules/foo.js

 - /node_modules/foo.js

 

(참고로 위의 탐색 경로는 module 객체의 paths 프로퍼티 값과 같다)

 

- Global_Directory

Node.js는 위 3가지 방법으로 찾지 못하면 OS의 글로벌 파일 경로를 탐색한다.

 

기본적으로 다음 GLOBAL_DIRECTORIES 를 탐색한다.

 - $HOME/.node_modules

 - $HOME/.node_libraries

 - $PREFIX/lib/node  (Node.js가 설정한 node_prefix의 경로이다)

 

npm 사용 시 위와 같은 곳까지 탐색 될 일은 없다고 한다..?????

 

 

AMD (Asynchronous Module Definition)

AMD API는 모듈과 종속성 파일들을 비동기적으로 로드할 수 있도록 모듈을 정의하는 메커니즘이다.

다른 모듈 시스템과 동적로딩에 있어서 차별점이 있다고 할 수있다.

 

이 부분은 다음에 좀 더 알아보기!!

 

 

참고자료

https://m.blog.naver.com/PostView.nhn?blogId=jdub7138&logNo=221022257248&proxyReferer=https%3A%2F%2Fwww.google.com%2F

https://velog.io/@doondoony/JavaScript-Module-System

https://www.zerocho.com/category/NodeJS/post/5835b500373b5b0018a81a10

'JS' 카테고리의 다른 글

(191128) Instantiation & Inheritance (2)  (0) 2019.11.28
(191127) Instantiation & Inheritance (1)  (0) 2019.11.27

댓글