본문 바로가기

Dev/[Javascript]

[Javascript] Module과 Module 시스템에 대해서(feat. CommonJS, ES6)

반응형

 

INTRO


 

개발 도중 다른 모듈을 사용하기 위해 아래와 같은 키워드들을 보거나 사용해 본 적이 있을것이다.

// ES6에서 모듈을 사용하는 방법
import { Express } from 'express';

// CommonJS에서 모듈을 사용하는 방법
const express = require('express');

이번 포스팅에서는 Javascript의 module에 대해 알아본다.


 

1. Module?

- 모듈(Module)은 다양한 곳에서 다양한 의미로 사용되며, 개발 분야로 범위를 제한해보자면 '프로그램의 다른 부분에서 재사용하고 가져올 있는 독립적인 단위로 구성된 코드' 라고 볼 수 있다.

 

- 그리고 이를 활용하여 아래와 같은 구조로 코드를 작성하는것을 '모듈 패턴을 사용한다' 라고 표현하며, 개발자들에 익숙한 패턴이다.

(혹은 모듈라 프로그래밍 이라고도 표현)

 

출처 : https://poiemaweb.com/es6-module

- 모듈은 아래와 같은 제약을 두고 파일 단위로 분리하는것이 일반적이며, 이렇게 개발되어야 개발 효율과 유지보수가 용이하다.

1. 모듈은 가능하면 다른 모듈에게서 독립적이어야 함

2. 특정 기능을 구현하기 위한(목적을 달성하기 위한) 코드들을 하나의 파일에 모아서 관리

3. 재사용성이 좋아야 함

 

- C에서는 #include, Java에서는 import와 같은 키워드를 통해 모듈을 가져와 사용한다.

- C의 #include는 언어의 개발 초기부터 지원되었고, Java또한 마찬가지로 초기 버전인 1.2부터 모듈을 import하는 기능을 지원했다.

- 이 말은 언어를 개발하는 단계부터 이러한 모듈 패턴을 고려하여 설계했다는 것.

- 반면에 JavaScript 1995년에 처음 도입되었을  모듈을 정의하거나 로드하는 표준화된 방법이 포함되어있지 않았다.

- 그래서 개발자들은 IIFE(Immediately Invoked Function Expression) 패턴이나, <script> 태그를 사용하여 다른 모듈을 정의/로드하는 방법을 사용하였다.

 

 

 

2. IIFE(Immediately Invoked Function Expression) 패턴과 <script> 태그 

- IIFE 패턴은 함수를 선언과 동시에 호출한다는 개념으로, 클로저가 활용된다.

var module = (function() {
  // private variable
  var privateVar = "This is private.";

  // private function
  function privateFunction() {
    console.log("This is private function.");
  }

  // public API
  return {
    publicVar: "This is public.",
    publicFunction: function() {
      console.log("This is public function.");
      console.log(privateVar); // private variable 접근
      privateFunction(); // private function 호출
    }
  };
})();

// 모듈 사용
console.log(module.publicVar);
module.publicFunction();

- 이렇게 사용하면 모듈화가 가능하지만, 가독성이 떨어지고 모듈이 늘어날수록 관리가 힘들어진다는 단점이 있다.

- 웹 브라우저에서 모듈을 로드할 때에는 <script> 태그가 주로 사용되었으며, 현대에도 이러한 방식은 자주 사용된다.

// exampleModule.js 모듈을 선언
const exampleModule = {
  exampleFunction: function() {
    console.log('This is an example function.');
  }
};
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>Example</title>
  </head>
  <body>
    <!-- 모듈 파일 로드 -->
    <script src="exampleModule.js"></script>
    <script>
      // exampleModule을 사용하여 코드를 작성.
      exampleModule.exampleFunction();
    </script>
  </body>
</html>

- 해당 방법도 단점이 존재하는데, 모듈을 로딩하는 순서에 따라 동작이 바뀔 수 있어 수동으로 관리가 필요하며, 캐싱이 불가하고 모듈이 증가할 수록 관리가 힘들어진다.

 

- 이를 이유로 Javascript 진영에서는 AMD(Asynchronous Module Definition), CommonJS와 같은 모듈 시스템이 제안되었다.

 

 

3. AMD(Asynchronous Module Definition) 

- 비동기 방식으로 모듈을 정의하고 로드하는 방식이다.

- 아래는 AMD모듈 시스템을 RequireJS 라이브러리를 통해 구현한 간략한 예제이다.

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>AMD Example</title>
  <script src="https://requirejs.org/docs/release/2.3.6/minified/require.js"></script>
</head>
<body>

  <script>
    // 모듈 정의
    define('math', [], function() {
      var add = function(x, y) {
        return x + y;
      };

      var sub = function(x, y) {
        return x - y;
      };

      return {
        add: add,
        sub: sub
      };
    });

    // 모듈 로딩 및 사용
    require(['math'], function(math) {
      console.log(math.add(2, 3)); // 5
      console.log(math.sub(2, 3)); // -1
    });
  </script>
</body>
</html>

- 네트워크를 통해 파일을 비동기적으로 내려받아야 하는 웹 브라우저 환경에서 주로 사용되며 define()함수를 사용하는 것이 특징이다.

- 서버 사이드 애플리케이션 진영에서도 비동기로 로드하는 방식으로 사용이 가능하나, 서버 사이드 애플리케이션의 경우 필요한 모듈들을 볼륨에 저장해두고 기동되는 환경이 일반적이기에 CommonJS의 동기 로드 방식이 주로 사용된다.

 

4. CommonJs의 require(), module.exports

- 기존 Javascript는 웹 브라우저에서만 사용되던 언어였는데, Node.js 를 통해 서버 사이드에서도 Javascript를 사용하게 되면서 모듈 시스템에 대한 필요성이 증가했다.

- CommonJS는 Node.js 에서 이 모듈 시스템을 '정의하고 구현한 하나의 프로젝트' 이다.

https://ko.wikipedia.org/wiki/CommonJS

 

CommonJS - 위키백과, 우리 모두의 백과사전

위키백과, 우리 모두의 백과사전.

ko.wikipedia.org

- 앞서 설명한 AMD 방식도 CommonJS의 모듈 시스템에서 분리되어 나온 방식이라고 한다.

AMD = 비동기 방식으로 모듈을 로드하기 위한 표준 정의,

CommonJS = 브라우저를 탈출한 자바스크립트에서 모듈을 정의/로드하기 위한 표준 정의

이렇게 두 방식의 목적이 다르므로, 분리될 수 밖에 없었을 것 같다.

- 여기서부터 프론트엔드 진영에서 익숙한 require(), module.export 키워드가 등장한다.

// math.js
function add(a, b) {
  return a + b;
}

function multiply(a, b) {
  return a * b;
}
module.exports = { add, multiply };


// main.js
const math = require('./math');
console.log(math.add(2, 3)); // Output: 5
console.log(math.multiply(2, 3)); // Output: 6

- 잘 알려진 문제점으로는 CommonJS는 가져온 모듈을 객체화하므로, 순환 참조의 위험이 있다.

- 이후 AMD, CommonJS 두 가지 규격을 통일하고자 UMD(Univasal Module Definition) 방식도 등장했고 현재까지도 사용중인 곳이 많다.

- 현대 프론트엔드 프레임워크(React, Vue 등)를 사용하여 개발하는 경우 일반적으로 ES6 이상의 문법을 사용하며,

- ES6(ES2015)부터 Javascript 수준에서 모듈 시스템을 도입하였다.

 

5. Javascript 수준에서 ES6부터 지원하는 import, export

- ES6부터는 아래처럼 CommonJS나 AMD보다 간략한 문법으로 모듈을 정의/로드 하는 시스템을 제공한다.

- import, export 키워드로 모듈을 가져오고 내보내며, 변수, 함수, 클래스 모두 export할 수 있다.

- CommonJS의 순환참조 문제를 런타임 이전에 발견 가능하므로 이를 방지할 수 있다.

- 이외에도 네트워크 로드에 최적화 된 몇 가지 장점들을 가지고 있다.

// message.js
export const message = "Hello World!";

// main.js
import { message } from './message.js';
console.log(message); // "Hello World!"

 

 

 

 

마무리

 require() 과 import 가 분명 목적이 같아 보임에도 왜 다르게 사용하는가? 에 대한 궁금증에서 시작하다보니 Javascript의 모듈에 대한 역사까지 공부하게 되었다.

현대의 프론트엔드 개발 트랜드는 ES6 의 import, export 를 주로 사용하고, Webpack, Babel등이 거의 필수적으로 사용되므로 이러한 내용에 대해서 깊게 고민할 필요가 많이 없어졌지만, 분명 이러한 내용들은 개발에 있어 도움이 되는 내용들이므로 미리 정리하고 넘어가는 것이 좋겠다.

 

참조 : https://d2.naver.com/helloworld/12864

https://d2.naver.com/helloworld/591319

https://poiemaweb.com/es6-module

 

 

 

-퍼가실 때는 출처를 꼭 같이 적어서 올려주세요!

 

반응형