팀프로젝트로 영화 사이트 api 가져와 영화소개 홈페이지 제작을 하다가 다른 동료들이 import, export 이런 것을 쓰면서 다른 파일의 함수, 변수등을 연결하는 걸 보고 신기했던 차에 Node.js 기초를 다룬 책을 읽다가 관련 내용이 나와서 열심히 읽어보았다.
모듈 사용 법
모듈을 만들 때는 모듈이 될 파일과 모듈을 불러와 사용할 파일이 필요
모듈 하나가 여러 개의 모듈에 사용될 수 있음.
여러 파일에 걸쳐 재사용되는 함수나 변수를 모듈로 만들어 놓으면 편리함.
모듈이 많아지고 모듈 간 관계가 얽히게 되면 구조 파악이 어렵다는 단점도 있음
1. CommonJS 모듈
표준 자바스크립트 모듈은 아니나 표준이 나오기 전부터 노드 생태계에서 가장 널리 쓰임.
// module.js (내보내는 부분, 모듈이 될 파일)
const odd = '홀수'
const even = '짝수'
module.exports = {
odd,
even
}
// 아래와 같이 두 줄로도 작성 가능
// exports.odd = '홀수';
// exports.even = '짝수';
위 두 종류의 코드가 동일하게 작동하는 이유 : module.exports와 exports가 같은 객체를 참조하기 때문. 다만 export.~~ = ~~ 의 형태는 객체만 사용할 수 있으므로 아래와 같이 함수를 대입한 경우에는 바꿀 수 없음.
한 모듈에 export.~~ = ~~ 형식과 module.exports를 동시에 사용하지 않는 것이 좋음.
// require.js (가져오는 부분, 모듈을 불러와 사용할 파일)
const {odd,even} = require('./module')
function checkOddOrEven(num) {
if(num % 2) {
return odd
}
return even
}
module.exports = checkOddOrEven
// index.js (모듈을 불러와 사용)
// 변수 선언 시 exports 할 당시의 변수명이나 함수명과 달라도 됨
// {odd,even} -> 구조분해할당
const { odd, even } = require('./module')
const checkNumber = require('./require')
function checkStringOddOrEven(str) {
if(str.length%2) {
return odd
}
return even
}
console.log(checkNumber(10)) // 짝수
console.log(checkStringOddOrEven('helloworld!')) // 홀수
! 순환참조 : 간단하게 말하면 두 모듈이 서로를 require 하는 경우. 예를들어 1.js 와 2.js 파일이 있고 이 둘이 서로를 참조함. 무한 루프와 비슷한 현상이 생길 걸 상상할 수 있는데 실제로는 순환참조되는 대상(먼저 실행되는 파일)을 빈 객체로 만듬. -> 예기치 못한 동작이 발생할 수 있으므로 순환참조가 발생하지 않도록 구조를 잘 잡는 것이 중요함.
2. ECMAScript 모듈 (= ES 모듈)
공식적인 자바스크립트 모듈 형식. 기존에는 CommonJS 모듈이 많이 사용되었으나 ES 모듈이 표준으로 정해지면서 점점 사용비율이 늘어나고 있음.
브라우저에서도 ES 모듈을 사용할 수 있어 브라우저와 노드 모두에 같은 모듈 형식을 사용할 수 있다는 것이 장점.
위의 코드를 ES 모듈 형식으로 바꿔보면 아래와 같다.
// module.mjs (내보내는 부분, 모듈이 될 파일)
export const odd = '홀수'
export const even = '짝수'
// require.mjs (가져오는 부분, 모듈을 불러와 사용할 파일)
import { odd, even } from './module.mjs'
function checkOddOrEven(num) {
if (num % 2) {
return odd
}
return even
}
export default checkOddOrEven
// index.mjs
import { odd, even } from './module.mjs'
import checkNumber from './require.mjs' // require.mjs에서 default로 정해놓은 함수가 불러와짐(이 때 import한 함수명이 다른 것은 상관 없음)
function checkStringOddOrEven(str) {
if(str.length % 2) {
return odd
}
return even
}
console.log(checkNumber(10)) // 짝수
console.log(checkStringOddOrEven('helloworld!')) // 홀수
ES 모듈의 import나 export default는 require나 module처럼 함수나 객체가 아닌 문법 그 자체이다.
파일도 js 가 아닌 mjs 확장자로 변경된다. js 확장자에서 import를 사용하려면 package.json에 type:"module" 속성을 넣으면 됨. (안넣으면 Syntex error ~~ 메세지 뜨면서 에러남)
간단하게 두 모듈을 비교하자면,
CommonJS 모듈은 확장자, index생략 가능하며 top lever await(스코프가 top level에 위치해 있을 때 async function ~~ 이런 형태 없이 await 사용하는 것) 불가하다.
다이내믹 임포트 가능
그리고 filename, dirname, require, module.exports, exports 사용 가능
ES 모듈은 확장자, index생략 불가능하며 top lever await 가능하다.
다이내믹 임포트 불가능
그리고 filename, dirname, require, module.exports, exports 사용 불가하며 __filename 대신 import.meta.url 사용한다.
서로간 호출은 가능하지만 잘 호환되지 않는 케이스가 많으므로 웬만하면 한 가지 형식만 사용하는게 좋다.