[ JavaScript ] CJS, AMD, UMD, ESM

minidoo·2022년 6월 27일
1

자바스크립트 / NodeJS

목록 보기
25/28
post-thumbnail

초기 자바스크립트는 모듈을 불러오거나 내보내는 방법이 없어 하나의 파일에 모든 기능를 작성해야 했다. 이런 불편함을 해결하기 위해 CJS, AMD, UMD, ESM이 등장하였고 모듈로서 사용할 수 있게 되었다.

CJS (CommonJS)

자바스크립트를 브라우저에서 뿐만 아니라, 서버사이드 애플리케이션이나 데스크톱 애플리케이션에서도 사용하기 위한 방법이다.

fileA.js

var a = 1;
b = 2;
exports.sum = function(c, d) {
	return a + b + c + d;
}

fileB.js

var a = 3;
b = 4;
var moduleA = require("fileA");
moduleA.sum(a, b);  // 1 + 2 + 3 + 4

exports을 사용하여 다른 모듈로 추출할 수 있으며, require로 모듈을 변수에 담을 수 있다.

CommonJS는 모든 파일이 로컬 디스크에 있어 필요할 때 바로 불러올 수 있는 상황을 전제로 한다. 즉, 서버사이드 자바스크립트 환경을 전제로 한다. 브라우저에서 이런 방식은 필요한 모듈이 모두 다운될 때까지 아무것도 할 수 없는 상황이 발생한다. (동기적)

CJS 사용 라이브러리

  • 브라우저 : curl.js
  • 서버사이드 : Node.js

알아두면 좋은 내용

  • html 내에서 require 사용할 수 없는 이유 : require은 nodeJS 모듈 시스템으로 브라우저에서 지원되지 않는다. 관련 글 바로가기

AMD

AMD는 비동기 상황에서도 자바스크립트 모듈을 사용하기 위해 CommonJS와 독립한 그룹이다. CommonJS는 자바스크립트를 브라우저 밖으로 꺼내기 위해 탄생된 그룹이고, AMD는 브라우저에 중점을 둔 그룹이다.

fileA.js

define(["package/lib", function(lib) {
	function foo() {
		lib.log("hello, lib!");
	}
	return { foobar: foo };
});

fileB.js

require(["package/fileA"], function(fileA) {
	fileA.foobar();
})

브라우저 환경의 자바스크립트는 파일 스코프가 따로 존재하지 않기 때문에 define() 함수로 파일 스코프의 역할을 대신한다. 즉, 모듈에서 사용하는 변수와 전역변수를 분리한다.

정리하자면, 필요한 파일이 모두 로컬 디스크에 있어 바로 불러쓸 수 있는 서버사이드에서는 CommonJS, 필요한 파일을 네트워크를 통해 내려받아야 하는 브라우저 환경에서는 AMD가 더 유연한 방법을 제공한다.

AMD 사용 라이브러리

  • 브라우저 : RequireJS
  • 서버사이드 : RequireJS

UMD (Universal Module Definition)

AMD와 CommonJS 두 그룹으로 나눠지다 보니 서로 호환되지 않는 문제가 발생한다. 이를 해결하기 위해 나온 방법으로 AMD와 CommonJS, window에 추가하는 방식까지 모두 가능한 방식이다.

// root: 전역범위, factory: 모듈을 선언하는 함수
(function (root, factory) {
	// AMD 방식
  if (typeof define === 'function' && define.amd) {
    define(['exports', 'b'], factory);
	// CommonJS 방식
  } else if (typeof exports === 'object' && typeof exports.nodeName !== 'string') {
    factory(exports, require('b'));
	// Browser globals (window 객체에 모듈 내보냄)
	} else {
    factory((root.commonJsStrict = {}), root.b);
  }
}(this, function (exports, b) {  // 모듈을 생성하는 익명 함수
  exports.action = function () {};
}));

ESM (ECMAScript Module)

ECMAScript에서 지원하는 자바스크립트 공식 모듈로 import와 export를 지원하지 않는 브라우저가 많아 번들러와 함께 사용해야 한다.

import lib from "package/lib";

function foo() {
	lib.log("hello, lib!");
}

export { foobar: foo };

ESM은 자바스크립트 자체 모듈 시스템으로

<html>
<body>
  <script type="module" src="fileA.mjs"></script>
  <script type="module" src="fileB.mjs"></script>
</body>
</html>

fileA.mjs

var x = 'fileA';
console.log(x); // fileA

// 변수 x는 전역 변수가 아니며 window 객체의 프로퍼티도 아니다.
console.log(window.x); // undefined

fileB.mjs

// import 사용 가능
import test from './test.mjs';
console.log(test);  // hello word!

// 변수 x는 fileA.mjs에서 선언한 변수 x와 스코프가 다른 변수이다.
var x = 'fileB';
console.log(x); // fileB

test.mjs

const test = 'hello world!';
export default test;

type="module"을 사용하면 해당 파일에서 import와 export 사용이 가능하다. 파일마다 독립적인 스코프를 가져 각 파일의 window는 서로 공유되지 않는다. type="module"은 아직 많은 브라우저에서 모듈 시스템을 지원하지 않기 때문에 webpack 같은 번들러를 사용하는 것이 좋다.

상대경로 사용

pdf-lib.html

<html>
  <script type="module" src="pdf-lib.js"> </script>
</html>

pdf-lib.js

// path 에러
import { PDFDocument } from "pdf-lib";

// 변경
import { PDFDocument } from "/node_modules/pdf-lib/dist/pdf-lib.esm.min.js";
import fs from "fs";

// fs는 html의 브라우저 레벨에서 사용될 수 없다. FileReader() 방법을 사용한다.
// 만약 node 자체적으로 실행한다면 가능하다.

npm 패키지 중 pdf-lib을 import 하기 위해서는 절대경로를 사용할 수 없다.

파일이 실행되는 기준의 경로(상대경로)로 import 해야 path 에러가 발생하지 않는다. react와 같은 라이브러리에서 상대경로를 사용할 수 있는 이유는 webpack과 같은 번들러가 설치되어 있기 때문이다. 이때 fs와 같은 파일 시스템은 상대경로도 사용할 수 없기 때문에 브라우저에서는 FileReader() 객체를 사용한다.

참고 자료

[1][JavaScript] CJS, AMD, UMD, ESM
https://beomy.github.io/tech/javascript/cjs-amd-umd-esm/

[2] JavaScript 표준을 위한 움직임: CommonJS와 AMD
https://d2.naver.com/helloworld/12864

[3] 리액트 상대경로 → 절대경로 임포트
https://engineering.huiseoul.com/리액트-상대경로-절대경로-변경-1485babb5198

0개의 댓글