feconf의 강의 영상을 보다가 CommonJS와 ESM 이야기가 나왔는데 분명 많이 들어봤는데 제대로 알지는 못한다는 생각이 들어 공부해봤다.
프로그램의 구성 요소를 관련 데이터와 함수를 하나로 묶은 단위를 뜻한다.
모듈들은 각자 독립적인 실행 영역을 가지고 있다.
모듈 사용의 장점
html가 <script> 로 자바스크립트 파일을 불러올때 파일들이 서로 의존적이어서 로드하는 순서도 중요해지는 문제점이 있었다.
아래의 코드를 보면 두개의 변수는 다른 script 태그 내에 있지만 같은 scope를 사용하기 때문에 script 태그가 달라도 조회할 수 있다.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<script>
var value1 = "value1";
const value2 = "value2";
</script>
<script>
console.log(value1); //value1
console.log(window.value1); //value1
console.log(value2); //valu2
console.log(window.variable_const); //undefined
</script>
</body>
</html>
var 키워드로 전역으로 정의한 경우 전역 객체의 프로퍼티로 등록된다.브라우저에서는 전역 객체인 window의 프로퍼티로 등록되어 window.value1으로 조회할 수 있게 된다.
let 키워드로 전역으로 정의한 경우 전역 객체의 프로퍼티로 등록되지는 않지만 보이지 않는 개념적 블록 내에 전역 변수로 존재한다. script가 같은 scope를 공유하기 때문에 두번째 스크립트에서 참조할 수 있게 된다.
참고 : javascript-deep-dive 14장, 15장
js를 브라우저에서만 아닌 서버사이드나 다른곳에서도 사용하고자 하는 수요가 늘어나면서 생겨난게 CommonJs이다.
//something.js
exports.consoleSomething = ()=> console.log("something");
//another.js
const something = require("./something.js");
something.consoleSomething();
Node.js는 하나의 파일을 하나의 모듈로 처리한다. CommonJS에서는 require 함수와 exports라는 특수한 객체를 통해서 모듈을 내보내거나 불러올 수 있다.
위의 코드에서는 consoleSomething이라는 함수는 exports 객체에 의해 모듈의 루트에 추가된다. 모듈 내의 변수들은 Node.js의 module wrapper를 통해 래핑되어 모듈만의 독립적인 스코프를 가질 수 있게 되어 파일 내(모듈 내)에서 전역 변수를 선언하더라도 외부에서 사용할 수 없게 되는 것이다.
//예약어가 아님
require = () => console.log("require은 예약어가 아님");
require(); //require은 예약어가 아님
//삼항연산자 적용 가능
const functions = require(true ? "./test1" : "./test3");
//if문 블럭에 넣어서 쓸 수도 있음
let functions = null;
if (true) {
functions = require(true ? "./test1" : "./test3");
}
console.log(functions == null); //false
require와 exports는 예약어가 아니라 함수와 객체라서 위의 코드처럼 사용해버릴 수도 있다는 문제점이 있다. require문과 exports를 소리소문없이 바꿔버릴 수 있기 때문에 정적 분석이 어렵다는 문제점도 생긴다.
Node.js는 CommonJS 모듈 시스템과 ECMAScript 모듈 시스템, 두 가지를 사용함.
Node.js가 시스템을 CommonJS 모듈로 처리하는 경우는 다음과 같다.
.cjs로 된 경우.js인 경우package.json의 type이 commonjs인 경우package.json에 type이 명시되지 않은 경우.mjs, .cjs, .json, .node, .js 가 아닌 경우에package.json 의 type이 module 이어도 require() 를 쓰는 경우가 있으면 CommonJs로 인식한다.참고 : https://nodejs.org/api/modules.html#the-mjs-extension
비동기적인 상황에서 javascript 모듈을 사용하기 위해 CommonJS와 합의하다가 독립한 그룹이다. AMD는 Asynchromous Module Definition의 약어이다. 비동기 모듈의 표준을 다룬다. 대표적인 AMD를 지원하는 모듈 로더는 Require.js이다.
CommonJS와 비슷한 부분도 있고 호환되는 부분도 있지만 AMD만의 대표적인 특징으로는 define() 함수를 사용한다는 것이다. javascript는 파일 스코프가 없기 때문에 define로 스코프 역할을 대신한다.
UMD는 스펙이 아닌 디자인 패턴에 가깝다. AMD와 CommonJS를 모두 지원하기 위해 만들어졌다.
ES6에 도입된 자바스크립트 모듈 시스템이다.
//test.js
export const add = (a, b)=> a+b;
import { add } from "./test.js"
export 키워드를 통해 모듈에서 내보낼 수 있다.
export default 로 내보내기는 모듈 내 한번만 사용할 수 있고 import 하는 곳에서 이름을 바꿔서 사용할 수 있다.
//test1.mjs
export const test = 1;
const defaultExport = "export default";
export default defaultExport;
//test2.mjs
import something, { test } from "./test1.mjs";
console.log(test); //1
console.log(something); //export default
console.log(window.test); //undefined
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<script src="./test2.mjs" type="module"></script>
</body>
</html>
script 태그에 type을 module로 지정해줘야 브라우저가 모듈로 인식할 수 있다.
모듈 시스템을 사용해 더이상 전역 객체 window 에 프로퍼티로 등록되지 않는것을 확인할 수 있다.
//module.mjs
console.log("module script load");
//noneModule.js
console.log("none module load");
//index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<script src="./module.mjs" type="module"></script>
<script src="./module.mjs" type="module"></script>
<script src="./noneModule.js"></script>
<script src="./noneModule.js"></script>
</body>
</html>

module script는 한 번만 실행되지만 일반 script는 여러번 실행한다.
모듈 시스템의 역사, 그리고 ESM
일반스크립트와 모듈스크립트
모듈(Module)와 모듈화란?
nodejs 공식 문서 - commonjs
avaScript 표준을 위한 움직임: CommonJS와 AMD
오 import export 와 require exports 사용법 정도만 알고 있었는데,
모듈 시스템이 생각보다 이렇게 심오한 개념인줄은 몰랐습니다
제대로 다시 공부해야할 필요성을 느겼습니다!
읽다가 '브라우저에서 script를 모듈로 사용하기' 예시코드에서
defaultExport 라는 이름으로 export default하고,
something 이라는 이름으로 import 되어있길래 실수로 잘못 적으신 코드인줄..
알고보니 export default로 내보낸 것은 다른 이름으로 import 할 수 있었네요 ㅡ.ㅡ
딥 다이브책 14장, 15장 읽어봐야겠네여 좋은 글 감사합니다!