JavaScript 를 사용하다보면 어떤 코드는 require 를 쓰고 있고 어떤 코드는 import 를 쓰고 있는 것을 확인할 수 있습니다.
언제 무엇을 써야 하고, 왜 그렇게 써야하는지 알기 위해서는 JavaScript의 변천사를 어느정도 이해하는 것이 필요합니다.
# 제발 안다고 하고 몰라서 require 랑 import 혼용해서 쓰지 말아주세요. 특히 Node.js 14에서...
우선 간단히 요약해서 설명하면,
- 정적 로드 : HTML <script>
- 동적 로드 (풀패키지) : Node.js 4~14 CommonJS(require)
- 동적 로드 (모듈) : Node.js 14~ ESModule(import)
의 순서로 이해하시면 좋습니다.
우선 1탄에서는 require를 사용하는 CommonJS, AMD, UMD 에 대해서 설명해보려합니다.
하아 이제부터 얘기할 거 틀릴까봐 겁납니다 ㅠㅠ
태초의 자바스크립트
HTML 내 <script> 태그 이용
우리가 JavaScript 를 맨 처음 배울 때, HTML 내 <script> 라는 태그를 쓰고, 직접 코드를 작성하거나 `src` attribute를 이용해서 특정 url 또는 상대경로에서 script 를 불러올 수 있었습니다. 이때까지만 하더라도 브라우저 내에서 <script> 태그 별로 하나의 스크립트를 실행할 수 있는 환경이었습니다.
CommonJS와 Node.js
require의 시작
프로그래밍 언어인 JavaScript 가 갖추어야할 기능은 많아지고 언어의 범용화를 위해 모듈화에 대한 요구사항이 늘어났습니다. 이 때 CommonJS(http://www.commonjs.org/) 라는 자발적으로 조직된 워킹그룹이 JavaScript 의 범용화를 주도하였습니다. 이 시기 언저리에 브라우저와 별개 JavaScript를 독립적으로 실행할 수 있는 환경인 Node.js 가 생겨났습니다. (Node.js 말고도 많았지만 살아남은건...)
Node.js는 브라우와 별개의 독립실행 환경에서 os 모듈, fs 모듈 등을 통해 시스템에 접근 가능하게 되었습니다. 이 때, os 와 fs 같은 외부 모듈에 접근해서 로드할 수 있는 메서드가 `require()` 입니다. 이와 같은 CommonJS 형식의 모듈을 제작할 때, `module.exports` 를 사용합니다. 이 때 모듈 단위는 파일 이름이거나 index.js를 포함하고 있는 폴더 이름입니다. (Python 에서의 모듈 개념과 비슷함. 파일 이름이거나, __init__.py를 포함하고 있는 폴더 이름)
* 파일이름에서 .js를 빼기도 합니다.
CommonJS 모듈화
CommonJS의 모듈화에 사용되는 개념은 아래와 같습니다.
- 스코프(Scope): 모든 모듈은 자신만의 독립적인 실행 영역이 있어야 한다.
- 정의(Definition): 모듈 정의는 exports 객체를 이용한다.
- 사용(Usage): 모듈 사용은 require 함수를 이용한다.
CommonJS(require) 사용 예시:
// Foo.js
module.exports = fooA // fooA 라는 객체, 메서드(함수), 변수를 모듈 자체로 사용함
// App.js
var fooB = require('Foo.js') // 위에서 만들어진 모듈이 현재 파일에서 fooB 라는 이름으로 사용됨
위 코드에서 fooA 는 Foo.js 에서 정의할 때 쓴 이름이고, fooB 는 App.js 사용하려고 쓴 이름입니다. 헷갈린다면 공부!!
// Foo.js
module.exports.foo = fooA // fooA 라는 객체, 메서드(함수), 변수를 모듈 자체로 사용함
// App.js 사용방법 1.
var fooB = require('Foo.js') // 일단 module.exports 가 require('Foo.js') 그 자체
console.log(fooB.foo);
// App.js 사용방법 2.
var { foo } = require('Foo.js') // 모듈은 {} 로 불렀고 내부에 foo property 가져옴
console.log(foo);
여기서 사용된 CommonJS 형태의 JS 코드는 몇가지 특징이 있습니다.
- 파일 자체가 모듈 형태임. 내부 객체를 빼낸다고 하더라도 객체 밖에 있는 코드는 전역적으로 한 번 동작함.
- 동기(Synchronous) 형태임. 호출과 동시에 실행됨.
- 비동기 기능을 수행하려면 Callback Function 형태를 이용해야 함. (콜백 지옥의 시작)
- 브라우저 내 환경은 파일 시스템을 기반으로 하지 않기에, 파일 단위의 전역 기능을 사용할 수 없음.
- 브라우저인지, 독립 환경(Node.js)인지에 따라서 JavaScript 모듈이 하는 역할(파일인가 데이터스트림인가)이 헷갈리는 이유
- CommonJS 는 브라우저 상에서 사용될 모듈 전송 포맷을 따로 정의
- 개인적으로 여기에서부터 CommonJS 를 잊어버려도 좋다고 생각
- 코드 중간에 동적 로드가 가능함.
비동기와 AMD
AMD의 약간 진화된 모듈화 (독립성↑)
CommonJS가 JavaScript를 브라우저에서 분리하려는 시도를 함과 달리, CommonJS 내에서 분리된 조직인 AMD는 브라우저 내에서의 JavaScript의 역할에 집중했습니다. AMD(Asynchronous Module Definition)는 네트워크 상에서 JavaScript 모듈을 호출하고 실행하기 위해서는 파일 단위가 아닌 실제 모듈의 정의와 호출 그리고 비동기 처리가 되어야 한다는 점에 집중했습니다.
`define`이라는 함수를 통해, 실제 사용자가 사용할 수 있는 모듈 부분을 파일 자체가 아닌 코드로 별도의 scope로 정의(의미론적으로 전역적으로 한 번 실행하는 부분을 없앤 모듈의 탄생)했습니다. '필요할 때 불러서 쓴다'라는 비동기 모듈 로드가 지원이 되었습니다. 이를 지원하는 AMD의 대표적인 모듈이 RequireJS 라는 모듈이 있습니다. (이거 때문에 또 헷갈림 +1)
CommonJS보다는 독립적인 모듈에 대한 정의를 명확히 했습니다. 하지만 이 또한 빌드 및 번들링 시, 전체 파일에 대한 패키징을 실행하기 때문에, 여전히 결과물의 파일 사이즈는 거대할 수 밖에 없었고 클라이언트 입장에서는 여전히 부담스러운 모듈이었습니다.
- 사실 의미론적인 성장... (옛다 받아라 파일 전체)
모듈 패턴의 진화 UMD
Commonjs, AMD 너네 그만 싸워
UMD(Universal Module Definition)는 CommonJS(서버사이드 우선)와 AMD(브라우저 우선)를 통합하기 위한 일종의 디자인 패턴으로 만들어졌습니다. Webpack 이나 Rollup.js 등 번들러를 통해 JavaScript 모듈을 UMD 형태로 빌드하면, 아래와 같은 즉시 실행 함수(IIFE)가 코드의 맨 처음에 위치하게 됩니다.
(function (root, factory) {
if (typeof define === 'function' && define.amd) {
// AMD
define(['exports', 'b'], factory);
} else if (typeof exports === 'object' && typeof exports.nodeName !== 'string') {
// CommonJS
factory(exports, require('b'));
} else {
// Browser globals
factory((root.commonJsStrict = {}), root.b);
}
}(this, function (exports, b) {
//use b in some fashion.
// attach properties to the exports object to define
// the exported module properties.
exports.action = function () {};
}));
위 코드는 CommonJS, AMD 그 외 기타 window 내 property로 적용되는 함수를 모두 돌릴 수 있는 형태로 제작되었습니다.
그러나 CommonJS가 주도했던 형태의 require를 사용하는 모든 JS 라이브러리(CommonJS, AMD, UMD)는 내부 패키지를 포함하여 빌드되었기에 그대로 배포하면 엄청 무거운지라 Webpack 과 같은 Bundler와 코드 크기를 줄여주는 Minifier 가 필수적으로 따라다녀야했죠.
- three.js 를 사용하였던 필자는 three.js 를 통해 UMD를 처음 접함 (three.module.js / 번들러 Rollup.js)
일단 여기서 1탄은 일단락 합니다. (Classic JavaScript)
여기까지는 require를 쓰는 JavaScript 였구요.
빼먹은 내용 중에서 JavaScript 특성에 v8 Engine, JIT Compile, 그리고 ECMA 의 ES3 표준을 따랐던 ActionScript(Flash) 등이 있겠지만 여기 내용도 길어서 어느 부분에 끼워 넣을까 고민중입니다.
JS 역사 타임라인은 별도로 작성해다가 올려볼께요.
# 수정이 많이 필요한 포스트
# 글은 나중에 정리 좀 해볼께요
다음 2탄에서는 Modern Javascript 에 대해 설명해보려합니다.
'JavaScript' 카테고리의 다른 글
번들링(Bundling) 이란? 이점과 기술 소개 (0) | 2023.05.23 |
---|---|
Node.js 버전 변경하는 방법 - MAC (2) | 2022.11.23 |
Node.js, NPM 제거하는 방법 - MAC (1) | 2022.11.21 |
NPM 사용하지 않는 모듈 일괄 제거하는 방법 (depcheck) (0) | 2022.09.08 |
NPM package.json 순서 정렬하는 방법 (sort-package-json) (0) | 2022.09.08 |