쌓고 쌓다

[node] CommonJS 모듈, ECMASciprt 모듈 본문

프로그래밍/node.js

[node] CommonJS 모듈, ECMASciprt 모듈

승민아 2023. 1. 18. 19:33

REPL

자바스크립트를 컴파일하지 않고 콘솔을 통해 코드를 즉시 실행할 수 있었다.

노드도 비슷한 콘솔을 제공하는데 REPL(Read Eval Print Loop)이다.

 

윈도에서는 명령 프롬프트에서 node를 입력하고, VS Code에서는 터미널을 통해 실행 가능하다.

콘솔에서 node [자바스크립트 파일경로]로 실행한다.

Ctrl + c 를 두번 누르거나 .exit를 입력하여 종료함.

콘솔에서 helloWorld.js 실행

 

노드는 브라우저의 자바스크립트와 다르게 코드를 모듈로 만들 수 있다.

모듈? 특정한 기능을 하는 함수나 변수들의 집합.

노드에서는 CommonJS 모듈 또는 ECMAScript 모듈을 사용한다.

CommonJS 모듈

var.js

const odd = '홀수입니다.'
const even = '짝수입니다.'

module.exports = {
    odd,
    even
}

/*
module.exports = {
    odd: odd,
    even: even
}
*/

module.exports에 변수들을 담은 객체를 대입한다.

다른 파일에서 이 파일을 불러오면 module.exports에 대입된 값을 사용할 수 있다.

이 파일은 이제 모듈이 된것이다. func.js에서 var.js를 참조해 보자.

 

func.js

const { odd, even } = require('./var')
// const obj = require('./var') 로 객체 통째로 가져와 obj.odd로 사용도 가능하다.

function checkOddOrEven(num) {
    if(num%2){
        return odd
    } else {
        return even
    }
}

module.exports = checkOddOrEven

require 함수 안에 불러온 모듈의 경로를 적는다. js나 json 같은 확장자는 생략 가능하다.

func.js처럼 다른 모듈(var.js)을 사용하는 파일을 다시 모듈(func.js)로도 만들 수 있다.

module.exports에는 객체뿐만 아니라, 함수나 변수, 배열도 대입할 수 있다.

*module.exports는 파일에서 단 한 번만 사용해야 한다.

 

index.js

const {odd, even} = require('./var')
const checkNumber = require('./func.js')

function checkStringOddOrEven(str) {
    if(str.length%2){
        return odd
    } else {
        return even
    }
}

console.log(checkNumber(10))
console.log(checkStringOddOrEven('hello'))

var.js가 func.js와 index.js에 두번 쓰이는 것처럼 모듈 하나가 여러 개의 모듈에 사용될 수 있다.

 

실행 결과

 

모듈을 만들 때 modele.exports만 사용했지만, module 객체 말고 exports 객체로도 가능하다.

 

var.js - exports 객체

exports.odd = '홀수'
exports.even = '짝수'

module.exports와 exports가 같은 객체를 참조하기에 가능하다.

exports 객체에 add 함수를 넣으면 module.exports에도 add 함수가 들어간다.

 

 

모듈 여러 개 불렀는데 중복되는 모듈이 있다면? (require.cache)

위의 index.js에서 var.js를 참조하고, func.js도 참조하는데 이 모듈이 또 var.js를 참조한다.

var.js를 두 번 읽는 건가? 싶었는데 require('./var')를 실행하고 require.cache를 보면 알겠지만

캐시에 저장해 두어 두 번째 부를 땐 실제 파일을 읽는 것이 아니라 캐시에 저장된 것을 불러온다.

=> require.cache에 한번 require한 모듈에 대한 정보가 들어있다.

 

exports와 module.exports

exports => module.exports => { }

exports는 module.exports를 참조하고 module.exports는 빈 객체를 참조한다.

그래서 exports === module.exports는 true가 나오는 것이다.

 

주의 사항!

exports === module.exports === { } 라는것을 생각하고 아래의 코드를 보자.

 

var.js

const odd = '홀수'
const even = '짝수'
const word = 'hello'


exports.odd = odd
exports.even = even
module.exports = word

module.exports = word로 해버리면

exports === module.exports === {} 참조 관계가 깨져버린다.

exports !== module.exports가 되어버리는것이다.

module.exports는 'hello'만 들어가게 되는것이다.

 

func.js

const hello = require('./var')

console.log(hello)

var.js를 참조하는데 hello를 출력하면

{ odd, even, hello } 객체가 아닌 hello만 담겨 온다.

 

var.js가 아래와 같다면.

const odd = '홀수'
const even = '짝수'


exports.odd = odd
exports.even = even

객체로써 넘어온다.

그러므로 혼용하여 사용하는 것은 불가능하다.

 

 

순환 참조

dep1.js

const dep2 = require('./dep2')
console.log('require dep2', dep2)
module.exports = () => {
    console.log('dep2', dep2)
}

 

dep2.js

const dep1 = require('./dep1')
console.log('require dep1', dep1)
module.exports = () => {
    console.log('dep1', dep1)
}

 

dep-run.js

const dep1 = require('./dep1')
const dep2 = require('./dep2')

dep1()
dep2()

dep-run.js를 실행하면 

require('./dep1')이 실행되고, dept1.js에서는 require('./dep2')가 실행되는데 그럼 또 require('./dep1')이 실행되고

참조하고 참조하고 무한반복이 일어난다.

노드에서는 빈 객체로 만들어버려 실행한다. 그러니 주의하자.

 

ECMAScript 모듈(ES모듈)

var.mjs

export const odd = '홀수'
export const even = '짝수'

 

func.mjs

import {odd, even} from './var.mjs'

function checkOddOrEven(num) {
    if(num$%2) {
        return odd
    } else {
        return even
    }
}

export default checkOddOrEven

 

index.mjs

import {odd, even} from './var.mjs'
import checkNumber from './func.mjs'

function checkStringOddOrEven(str) {
    if(str.length%2) {
        return odd
    } else {
        return even
    }
}

console.log(checkNumber(10))
console.log(checkStringOddOrEven('hello'))

require, exports, module.exports는 각각 import, export, export default로 바뀌었다.

ES모듈의 import, export default는 require, module처럼 함수나 객체가 아니라 문법 그 자체이다.

import시에 js, mjs 같은 확장자는 생략 불가능하며 파일 확장자도 mjs여야 한다.

(js 확장자로 ES모듈을 사용하려면 package.json에 type: "module" 속성을 넣어야 함.)

 

CommonJS 모듈 ECMAScript 모듈
require('./a');
module.exports = A;
const A = require('./a');
exports.C = D;
const E = F; exports.E = E;
const { C, E } = require('./b');
import './a.mjs';
export default A;
import A from './a.mjs';
export const C = D;
const E = F; export { E };
import { C, E } from './b.mjs';

 

다이나믹 임포트

조건부로 모듈을 불러오는 방법을 다이나믹 임포트라고 한다.

CommonJS 모듈에서는 다이나믹 임포트가 가능하고, ES 모듈에서는 불가능하다. 하지만 다른 방법으로

ES 모듈에서 다이나믹 임포트를 한다.

 

dynamic.js

const a = false
if(a) {
    require(./func')
}
console.log('성공')

require('./func')는 실행되지 않는다.

 

dynamic.mjs

const a = false
if(a) {
    import './func.mjs'
}
console.log('성공')

import는 항상 탑 레벨에서, 몰아서 이루어져야 한다.

아래와 같이 dynamic.mjs를 수정하자.

 

const a = true
if(a) {
    const m1 = await import('./func.mjs')
    console.log(m1)
    const m2 = await import('./var.mjs')
    console.log(m2)
}

import라는 함수를 사용해서 모듈을 동적으로 불러올 수 있다.

import는 Promise를 반환하기에 await이나 then을 붙여야 한다.

ES모듈에서는 async 함수 없이도 최상위 스코프에서 await을 사용할 수 있다.

Comments