들어가며
'시작은 미약했으나, 그 끝은 창대하리라' 이 말은 딱 자바스크립트를 위한 말이 아닐까 싶습니다. 초창기 자바스크립트가 생겨났을 때, 이렇게 대표적인 언어로 발전할 것이라고 예상을 했을까요? 자바스크립트는 탄생 이래로 정말 많은 변화와 발전과정을 거쳐왔습니다.
자바스크립트의 탄생부터 FE 세계에 있던 분이 아니라면, 저처럼 타입스크립트 시대부터 제데로 공부를 시작한 분이라면, JS의 모듈 시스템에 대해 정확히 이해하고 있기가 쉽지 않습니다. ts 파일을 직접 실행시킬 때 import 문제로 실행이 되지 않아 애먹은 적이 있었죠.
{
"compilerOptions": {
"target": "es5",
"module": "commonjs",
}
}
tsconfig 의 compilerOptions > module . 정확히 이해하고 사용하셨나요? (저는 아니었답니다.. 🥲)
그런 의미에서 오늘은 자바스크립트의 module 시스템의 역사, 번들링 에 대해 정리해보려고 합니다.
헌데, 제가 js 코어의 전문가라 특출난 내용을 쓸 수 있는 것도 아니고 이미 js 모듈에 대해서는 좋은 레퍼런스들이 많기 때문에, 다양한 레퍼런스를 살펴보고 목차별로 좋은 게시글을 공유드리고 기타 일반적으로 모르고 있거나 궁금할 만한 점들에 대한 내용을 기록하려고 해요.
읽어볼 문서 링크들 모음
JS의 큰 그림을 정말 재미있고 깔끔하게 요약해두신 분이 있네요! 이 사진은 꼭 공유드리고 싶었습니다.
전반적인 JS 모듈 내용에 대한 글 모음
*.mjs란 무엇일까
EcmaScript의 모듈용 js임을 명확히 하기 위해 사용합니다. CommonJS 모듈은 cjs 로 사용한다고 하네요.
<script type="module" src="lib.mjs"></script>
<script type="module" src="app.mjs"></script>
사실 우리는 주로 Webpack 번들러를 이용하거나, 번들링이 내장된 프레임워크의 단위로 코드를 짜기에 따로 설정할 일이 별로 없습니다.
nomodule 이란?
모듈 대체 스크립트를 의미합니다.
type 특성이 module을 지원하는 브라우저는 nomodule 특성을 가진 모든 <script>를 무시합니다. 그러므로 모듈 스크립트를 사용하면서도, 미지원 브라우저를 위한 대체 스크립트를 nomodule로 표시해 제공할 수 있습니다.
예시
<script type="module" src="main.mjs"></script>
<script nomodule src="fallback.js"></script>
tsconfig의 module 설정 실습
tsconfig 의 module 설정으로 실습을 해보았습니다.
구체적으로 문법을 외운다기 보다는, 각 모듈 방식의 핵심이 무엇인지, 어떤 문법을 가져가는지 정도만 이해하면 될 것 같아요.
테스트를 위한 기본 ts 파일입니다.
module.ts
export const a = 1
export default { 'module' : 1 }
core.ts
import { a } from './module'
이제 각각의 모듈 방식으로 컴파일링해볼게요.
$ tsc
"module" : "commonjs"
JS를 브라우저 뿐 아니라 다른 런타임에서도 실행가능하게끔 한다. nodeJS 의 방식.
{
"compilerOptions": {
"strict": true,
"target": "es2016",
/* Modules */
"module": "commonjs"
}
}
module.js
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.a = void 0;
exports.a = 1;
exports.default = { 'module': 1 };
core.js
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const module_1 = require("./module");
console.log({ a: module_1.a });
일반적인 require 형태로 임포트됩니다!
"module" : "es2022"
가장 최근에 통합된 방식, ESM, ES2020, ESNext 도 사실상 동일하다.
{
"compilerOptions": {
"strict": true,
"target": "es2016",
/* Modules */
"module": "es2022"
}
}
module.js
export const a = 1;
export default { 'module': 1 };
core.js
import { a } from './module';
console.log({ a });
es2022 는 타입스크립트 문법과 동일하게 그대로 컴파일링 됩니다!
es2022 는 최상위 await 구문도 사용 가능해요.
"module" : "amd"
비동기를 지원하는 방식
{
"compilerOptions": {
"strict": true,
"target": "es2016",
/* Modules */
"module": "amd"
}
}
module.js
define(["require", "exports"], function (require, exports) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.a = void 0;
exports.a = 1;
exports.default = { 'module': 1 };
});
core.js
define(["require", "exports", "./module"], function (require, exports, module_1) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
console.log({ a: module_1.a });
});
amd 의 임포트 방식은 일반적으로 사용하는 문법과는 사뭇 다르죠.
"module" : "umd"
AMD와 CommonJS가 서로 호환되지 않는 문제를 해결하기 위한 방식
{
"compilerOptions": {
"strict": true,
"target": "es2016",
/* Modules */
"module": "umd"
}
}
module.js
(function (factory) {
if (typeof module === "object" && typeof module.exports === "object") {
var v = factory(require, exports);
if (v !== undefined) module.exports = v;
}
else if (typeof define === "function" && define.amd) {
define(["require", "exports"], factory);
}
})(function (require, exports) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.a = void 0;
exports.a = 1;
exports.default = { 'module': 1 };
});
core.js
(function (factory) {
if (typeof module === "object" && typeof module.exports === "object") {
var v = factory(require, exports);
if (v !== undefined) module.exports = v;
}
else if (typeof define === "function" && define.amd) {
define(["require", "exports", "./module"], factory);
}
})(function (require, exports) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const module_1 = require("./module");
console.log({ a: module_1.a });
});
"module" : "system"
SystemJS 에서 사용하는 방식
{
"compilerOptions": {
"strict": true,
"target": "es2016",
/* Modules */
"module": "system"
}
}
module.js
System.register([], function (exports_1, context_1) {
"use strict";
var a;
var __moduleName = context_1 && context_1.id;
return {
setters: [],
execute: function () {
exports_1("a", a = 1);
exports_1("default", { 'module': 1 });
}
};
});
core.js
System.register(["./module"], function (exports_1, context_1) {
"use strict";
var module_1;
var __moduleName = context_1 && context_1.id;
return {
setters: [
function (module_1_1) {
module_1 = module_1_1;
}
],
execute: function () {
console.log({ a: module_1.a });
}
};
});
'Web > JavaScript' 카테고리의 다른 글
[JS] 디바운싱(Debouncing)과 쓰로틀링(Throttling) (1) | 2021.06.05 |
---|---|
[JS] event.preventDefault() 간단 설명 😊/ preventDefault란? (3) | 2020.01.19 |
[JS] Arrow Function / 화살표 함수란 ❓ (0) | 2020.01.08 |
[JS] 자바스크립트 함수 종류 / 화살표 함수 차이점 ❓ (0) | 2020.01.06 |
댓글