Published on

ES6) module 시스템

Authors
  • avatar
    Name
    Hoehyeon Jung
    Twitter

Module 시스템

자바스크립트는 기존에 정적 페이지 요소를 수정하는 언어로 시작해서 발전을 거듭했다. 어느 프로젝트던 규모가 커짐에 따라 파일을 모듈화하여 분리하고 관리하려고 할 것이고 이는 자바스크립트에서도 예외가 아니다. 자바스크립트 V8 엔진의 런타임 환경인 NodeJS는 이러한 모듈 시스템을 이미 구축하고 있었고, 브라우저 환경에서도 이러한 모듈시스템의 도입 필요성이 생겼고 ES6에 이르러 모듈 시스템을 도입하였다.

모든 스크립트 상세 예제는 다음을 참조

Export & Import

모듈로 만들 js 파일을 결정했다면 우선적으로 모듈로 추출할 요소를 export 한다. export의 대상은 변수부터, 함수, class까지 모두 가능하다.

// basic-modules/modules/square.mjs
export const name = 'square';
       
function draw(ctx, length, x, y, color) {
 ctx.fillStyle = color;
 ctx.fillRect(x, y, length, length);

 return {
   length: length,
   x: x,
   y: y,
   color: color
 };
};
// ...

export { draw, };
export default randomSquare;

export는 기본적으로 하나의 요소마다 하나씩 할 수도 있으며 선언된 내용을 한번에 export로 묶어서 내보낼 수도 있다. 또한 default로 기본적으로 export할 요소를 정할 수도 있다. default는 하나의 모듈당 하나밖에 존재할 수 없으며 함수의 경우 익명함수로 선언이 가능하다.

//basic-modules/main.mjs
import { draw, } from './modules/square.mjs';
import randomSquare from './modules/square.mjs';

// ...
let square1 = draw(myCanvas.ctx, 50, 50, 100, 'blue');
let square2 = randomSquare(myCanvas.ctx);

import 시에는 선언 시 중괄호를 통해 export 했듯이 동일하게 처리가 가능하다. default로 선언된 요소는 중괄호 없이 호출이 가능하다.

Renaming

모듈 시스템을 통해 서로 다른 모듈들을 불러오는 경우, 메서드 명이 충돌하는 등의 문제가 발생할 수 있다. 이를 방지하기 위해 모듈 시스템에서는 renaming을 지원한다.

// modules/module.js
export {
  function1 as newFunctionName,
  function2 as anotherNewFunctionName
};

// main.js
import { newFunctionName, anotherNewFunctionName } from './modules/module.js';
// modules/module.js
export { function1, function2 };

// main.js
import { function1 as newFunctionName,
         function2 as anotherNewFunctionName } from './modules/module.js';

위 두 방법의 모두 __ as 'Name'과 같은 식으로 이름을 변경하여 충돌을 피할 수 있다. 다만 export 혹은 import 시에 renaming을 진행하는 방식의 차이이다. 하지만, import 시에 수정하는 것이 더 맞다고 생각하는데 이유는 export 되는 함수에 접근해서 수정하는 것이 불가능할 경우가 있기 때문이다.

module 객체 생성

import 할 요소들을 일일히 renaming함에 따라 코드가 점점 길어지게 된다면 import할 요소를 객체로 받는 방식도 있다.

// module-objects/main.mjs
import * as Square from './modules/square.mjs';
import * as Circle from './modules/circle.mjs';
import * as Triangle from './modules/triangle.mjs';

// draw a square
let square1 = Square.draw(myCanvas.ctx, 50, 50, 100, 'blue');
Square.reportArea(square1.length, reportList);
Square.reportPerimeter(square1.length, reportList);

module 객체를 생성하게 되면 사용할 모듈 요소들 앞에 as로 선언한 이름을 붙이게 되지만 import 부분을 좀 더 깔끔하게 처리할 수 있다.

Class

모듈이 ES6의 class 기반의 객체지향 코드로 작성되었을 경우에도 이러한 class를 export가 가능하다.

// classes/modules/circle.mjs
class Circle {
  constructor(ctx, listId, radius, x, y, color) {
    this.ctx = ctx;
    this.listId = listId;
    this.radius = radius;
    this.x = x;
    this.y = y;
    this.color = color;
    this.name = 'circle';
  }
}

export { Circle };
// classes/main.mjs
import { Square } from './modules/square.mjs';

// draw a square
let square1 = new Square(myCanvas.ctx, myCanvas.listId, 50, 50, 100, 'blue');
square1.draw();
square1.reportArea();
square1.reportPerimeter();

import 시에는 해당 class 클래스를 사용하면 된다.

aggregating modules

여러 개의 모듈들을 묶어서 하나의 모듈에서 export 하는 법은 다음과 같다.

// module-aggregation/modules/shapes.mjs
export { Square } from './shapes/square.mjs';
export { Triangle } from './shapes/triangle.mjs';
export { Circle } from './shapes/circle.mjs';
// module-aggregation/main.mjs
import { Square, Circle } from './modules/shapes.mjs';

let square1 = new Square(myCanvas.ctx, myCanvas.listId, 50, 50, 100, 'blue');

let circle1 = new Circle(myCanvas.ctx, myCanvas.listId, 75, 200, 100, 'green');

let triangle1 = new Triangle(myCanvas.ctx, myCanvas.listId, 100, 75, 190, 'yellow');

shapes에 Class로 정의된 각 도형에 대한 모듈들을 모아서 한번에 export 하면 import 시 aggregate된 모듈만 불러올 수 있어 코드를 좀 더 깔끔하게 짤 수 있다.

dynamic loading

모듈을 상단에서 전체를 불러오는 방법 이외에도 필요할 떄마다 모듈을 불러오는 방식도 있다.

// dynamic-module-imports/main.mjs
let squareBtn = document.querySelector('.square');

// create the canvas and reporting list
let myCanvas = new Canvas('myCanvas', document.body, 480, 320);
myCanvas.create();
myCanvas.createReportList();

// draw a square
squareBtn.addEventListener('click', () => {
  import('./modules/square.mjs').then((Module) => {
    let square1 = new Module.Square(myCanvas.ctx, myCanvas.listId, 50, 50, 100, 'blue');
    square1.draw();
    square1.reportArea();
    square1.reportPerimeter();
  })
});

위의 예제의 경우 버튼에 클릭 이벤트를 추가할 떄, import를 실행한다. 또한 Module 객체를 선언했으므로 해당 모듈 객체를 통해서 내부 요소에 접근이 가능하다.

마치며

module의 경우 프론트엔드의 경우 대체로 webpack 등의 번들러를 통해 호환을 해결했기 때문에 등한시 되었던 주제였는데 이번 기회를 통해 용법을 익힐 수 있는 좋은 주제였다.

참조