코딩/Javascript

자바스크립트 Javascript ES6 NodeJS 모듈 Module exports export default require import 이해

드리프트 2021. 9. 27. 16:39
728x170

 

안녕하세요?

 

오늘은 자바스크립트나 NodeJS에서의 모듈(Module)에 대해 살펴보겠습니다.

 

먼저, 요즘 프런트엔드 쪽에서 많이 쓰는 React나 Vue 등은 NodeJS를 사용해서 웹페이지를 만드는데요.

 

그래서 먼저 NodeJS 쪽 모듈 import 및 exports에 대해 알아보겠습니다.

 

자바스크립트 표준 규격 체계인 ECMAScript 에는 모듈을 import export 하는 방법에 대한 규칙이 있는데요.

 

예전에 쓰인 방법이 CommonJS 방법입니다.

 

CommonJS는 require, module.exports 명령어를 써서 모듈 관리를 하는 거라고 이해하시면 쉽고요.

 

ES5나 ES6에서의 ES 모듈 처리 방식은 최근에 ECMAScript가 개정되면서 추가됐는데요.

 

ES 모듈 처리 방식이 더 직관적이라서 대세는 ES 모듈 처리방식입니다.

 

그러나, NodeJS는 기본적으로 CommonJS 방식을 쓰는데요.

 

먼저, 빈 NPM 프로젝트를 준비해 볼까요?

 

mkdir module-demo
cd module-demo
npm init -y

다 만들었으면 package.json 파일을 볼까요?

 

{
  "name": "module-demo",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC"
}

모듈에 대한 별다른 얘기는 없네요.

 

그러나 여기에 중요한 필드가 빠졌는데요.

 

바로 "type" 항목입니다.

 

NodeJS는 "type"항목이 없으면 즉, 생략됐으면 디폴트로 "type" : "commonjs"라고 처리합니다.

 

즉, package.json 파일은 기본적으로 다음과 같다고  볼 수 있습니다.

 

{
  "name": "module-demo",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "type": "commonjs"
}

NodeJS가 디폴트로 사용하는 모듈 처리 방식이 commonjs라는 겁니다.

 

그럼 NodeJS가 ES 방식의 모듈 처리 방식으로 작동되게 하려면 어떻게 해야 할까요?

 

바로 "type" 항목에 "module"라고 적으시면 됩니다.

 

"type": "module"

 

자 그럼, commonjs와 module의 차이에 대해 알아볼까요?

 

NodeJS는 파일 확장자를 기본적으로 3가지로 분류하는데요.

 

.cjs와 .js와 .mjs 세 개입니다.

 

.cjs는 commonjs를 명시적으로 의도한 확장자고요.

 

.mjs는 module 방식을 명시적으로 의도한 확장자입니다.

 

그러나 우리는 보통 .js 확장자를 많이 쓰죠.

 

그럼 commonjs일 때 또는 module 방식일 때 각 확장자 별 어떻게 NodeJS가 처리하는지 살펴볼까요?

 

"type" : "commonjs" 일 경우

 

1. .cjs 파일은 CommonJS 모듈로 처리

2. .js 파일은 CommonJS 모듈로 처리

3. .mjs 파일은 ES 모듈로 처리

 

"type" : "module" 일 경우

 

1. .cjs 파일은 CommonJS 모듈로 처리

2. .js 파일은 ES 모듈로 처리

3. .mjs 파일은 ES 모듈로 처리

 

 

자, 어떤가요?

 

.cjs 나 .mjs는 확장자 이름 처리 처리 방식도 명확합니다.

 

그러나 우리가 많이 쓰는  .js 확장자는 왔다 갔다 하는데요. 헷갈리시면 안 됩니다.

 

ES 모듈 방식일 때는 import 나 export 방식으로 모듈을 불러오거나 내보내야 하고요.

 

commonjs 방식일 때는 require 나 exports, module.exports로 내보내야 합니다.

 

 

 

모듈 내보내기, 불러오기 (CommonJS)

 

먼저 commonjs 일 경우 모듈 내보내기, 불러오기에 대해 알아보겠습니다.

 

commonjs일 경우 모듈 불러오기는 require라고 쉽습니다.

 

모듈 내보기가 헷갈리거든요. exports 와 module.exports 두 개가 있습니다.

 

module 이 붙고 안붙고 차이인데 왜 두 가지 경우로 만들었을까요?

 

그만한 이유가 있을 겁니다.

 

같이 알아보겠습니다.

 

먼저 module 이 뭘까요?

 

console.log(module) 해보겠습니다.

 

package.json 파일에 "type": "commonjs"가 있는지 확인 바랍니다.

 

"type": "module"이라고 되어 있으면 NodeJS는 module을 인식 못합니다.

 

실행 결과를 보면 Module은 실행된 파일의 정보를 담고 있는 거 같습니다.

 

경로도 보이고 Path도 보이고 하네요. 그런데 뭔가 이상하게 보입니다.

 

바로 exports 항목인데요. 빈 객체를 가지고 있네요.

 

index.js 파일에 다음과 같이 명령어를 추가해 볼까요?

 

exports.a = 'A';
exports.b = 'B';

console.log(module);

exports.a 에 "A"라고 지정하니까 module.exports 객체에 { a: "A" }라고 저장되네요.

 

여기서 exports는 module.exports에 대한 레퍼런스라는 걸 알 수 있습니다.

 

다음 코드를 실행해 보면 확실히 알 수 있습니다.

 

exports.a = 'A';
console.log(exports === module.exports);
console.log(module.exports);

exports === module.exports 가 true이네요. 즉, exports 가 module.exports 에 대한 레퍼런스라는 걸 알 수 있는 거죠.

 

즉, 최종적으로 module.exports 에 객체를 추가할 때 exports는 단축키 같은 역할을 한다고 볼 수 있습니다.

 

모듈을 내보내는 두 가지 방식을 살펴볼까요?

 

먼저, module.exports입니다.

 

module.exports = {
  greet: function (name) {
    console.log(`Hi ${name}!`);
  },

  farewell: function() {
    console.log('Bye!');
  }
}

두 번째는 exports 방식입니다.

 

exports.greet = function (name) {
  console.log(`Hi ${name}!`);
}

exports.farewell = function() {
  console.log('Bye!');
}

뭔가 exports 방식이 쉽다고 느껴지실 겁니다.

 

그래서 exports 방식을 많이 쓰는데요.

 

두 가지 경우를 같이 쓰면 어떻게 될까요?

 

다음 경우를 볼까요?

 

module.exports = {a: 'A'};
exports.b = 'B';
console.log(exports === module.exports);
console.log(module)

module.exports 와 exports를 혼용해서 썼습니다.

 

이럴 경우 exports 가 module.exports의 레퍼런스라는 역할이 사라집니다.

 

그래서 위의 스크린숏처럼 { b: "B" }가 exports 되지 않게 되는 거죠.

 

결론적으로 module.exports 와 exports를 혼용해서 쓰면 exports는 제 역할을 못한다고 이해하시면 됩니다.

 

좀 더 이해를 쉽게 하기 위해 CommonJS 방식의 module.exports 와 exports의 예제를 더 보여드리겠습니다.

 

// user.js
const getName = () => {
  return 'Jim';
};

exports.getName = getName;


------------------------------------

// index.js
const user = require('./user');
console.log(`User: ${user.getName()}`);

 

// user.js
const getName = () => {
  return 'Jim';
};

const getLocation = () => {
  return 'Munich';
};

const dateOfBirth = '12.01.1982';

exports.getName = getName;
exports.getLocation = getLocation;
exports.dob = dateOfBirth;


---------------------------------------

// index.js
const user = require('./user');
console.log(
  `${user.getName()} lives in ${user.getLocation()} and was born on ${user.dob}.`
);

 

// user.js
exports.getName = () => {
  return 'Jim';
};

exports.getLocation = () => {
  return 'Munich';
};

exports.dob = '12.01.1982';


---------------------------------------


// index.js
// destucturing assignment 이용
const { getName, dob } = require('./user');
console.log(
  `${getName()} was born on ${dob}.`
);
// user.js
class User {
  constructor(name, age, email) {
    this.name = name;
    this.age = age;
    this.email = email;
  }

  getUserStats() {
    return `
      Name: ${this.name}
      Age: ${this.age}
      Email: ${this.email}
    `;
  }
}

module.exports = User;


-------------------------------------------


// index.js
const User = require('./user');
const jim = new User('Jim', 37, 'jim@example.com');

console.log(jim.getUserStats());

 

 

ES6 모듈 내보내기(export) , 불러오기(import) 

 

ES2015(ES6)에서의 JavaScript는 모듈에 대한 빌트인 기본 지원을 받는데요.

 

ES6에서도 CommonJS와 마찬가지로 각 파일은 자체 모듈입니다.

 

객체, 함수, 클래스 또는 변수를 밖에서 사용할 수 있도록 하려면 내보내기 한 다음, 다른 파일에서 가져오기만 하면 됩니다.

 

 

내보내기(export)

 

ES6에서는 내보내기 할 때 내보낼 멤버 한 개 한개 따로따로 보낼 수 있고 내보내지 않은 멤버는 파일 외부에서는 사용할 수 없습니다.

 

export const myNumbers = [1, 2, 3, 4];
const animals = ['Panda', 'Bear', 'Eagle']; // Not available directly outside the module

export function myLogger() {
  console.log(myNumbers, animals);
}

export class Alligator {
   constructor() {
     // ...
   }
}

이 코드처럼 myNumbers, myLogger, Alligator을 각각 내보냈습니다.

 

그런데 animals는 내보내지 않았습니다.

 

이렇게 할 수 있고요. 한꺼번에 필요한 것만 내보낼 수 도 있습니다.

 

const myNumbers = [1, 2, 3, 4];
const animals = ['Panda', 'Bear', 'Eagle']; // Not available directly outside the module

function myLogger() {
  console.log(myNumbers, animals);
}

class Alligator {
   constructor() {
     // ...
   }
}

export { myNumbers, myLogger, Alligator };

마지막 코드를 보면 한꺼번에 내보냈네요.

 

내보낼 때는 별명으로 내보낼 수도 있습니다.

 

export { myNumbers, myLogger as Logger, Alligator }

myLogger를 Logger이란 이름으로 내보냈습니다.

 

 

Default export (디폴트 내보내기)

 

내보내기 할 때 특정 멤버를 디폴트로 내보낼 수 있습니다.

 

export const myNumbers = [1, 2, 3, 4];
const animals = ['Panda', 'Bear', 'Eagle'];

export default function myLogger() {
  console.log(myNumbers, pets);
}

export class Alligator {
  constructor() {
    // ...
  }
}

myLogger 함수를 export default로 내보냈습니다.

 

 

불러오기(import)

 

// app.js
export const myNumbers = [1, 2, 3, 4];
const animals = ['Panda', 'Bear', 'Eagle'];

export default function myLogger() {
  console.log(myNumbers, pets);
}

export class Alligator {
  constructor() {
    // ...
  }
}

-------------------------------------------------

// index.js
import { myNumbers, Alligator } from './app.js';

개별적으로 내보내기 한 myNumbers와 Alligator을 개별적으로 불러오기 했습니다.

 

개별적으로 불러올 때는 꼭, { } 형식을 써야 합니다.

 

import * as App from './app.js';

내보내기 한 모든 모듈을 App이란 이름으로 지정했습니다.

 

이렇게 되면 위에서 default로 내보낸 myLogger 이란 함수는 다음과 같이 인식됩니다.

 

import * as App from "./app.js";

console.log(App);
App.default();
App.myLogger();

 

export default myLogger라고 디폴트 지정이 되어 있으면 App.default로 이름이 되어 있어 주의가 필요합니다.

 

헷갈리시기 쉬울 텐데요. 주의가 필요합니다.

 

그럼 default로 내보내기 된 건 어떻게 불러올까요? 아래처럼 하시면 됩니다.

import myLogger from "./app.js"

 

그러면 다음 경우도 가능합니다.

import myLogger, { myNumbers, Alligator } from './app.js';

차이점은 default로 내보내기 된 거는 { }가 필요 없다는 겁니다.

 

지금까지 ES6 방식과 CommonJS 방식의 모듈 내보내기, 불러오기에 대해 알아보았는데요.

 

점점 추세는 ES6 방식이니 ES6 방식을 추천드립니다.

 

 

그리드형