Lodash로 알아보는 CommonJS와 ESM( ECMAScript )

o_ov·2024년 3월 23일
0

js

목록 보기
2/2
post-thumbnail

빌드 개념을 공부하면서,
Lodash는 왜? 번들 사이즈가 큰 것인가! 에 대해 공부하게 되었다.

Lodash는 CommonJS 방식으로 모듈을 제공하기 때문에,
번들 사이즈가 크고 내가 쓰지 않는 lodash 내부 함수까지 다 불러온다는데

왜..? 그런 것인가..!를 보니 CommonJS 때문이라고 한다.
그렇담 CommonJS가 뭐길래 lodash는 이렇게 번들 사이즈가 큰 것이고
내가 사용하지도 않은 함수들까지 불러오는가를 한번 살펴보자

lodash로 알아보는 CommonJS

일단 lodash.js의 모듈 내부 파일을 보면

;(function() { ... }.call(this));

;(function(){}) : (); 익명함수로 선언과 동시에 실행된다.

익명함수

const MyFunc = function(){
	console.log('hello'); 
}
MyFunc();

우리도 이렇게 함수를 작성한 적이 있지 않은가..!
MyFunc에 할당 된 함수를 익명 함수라 한다고 한다.
이렇게 보면 처음 봤던 함수와 조금 닮아 있는 듯 싶다.

call()

call 함수는 함수 내부에서 this 가리키는 것을 설정한다.

헷갈리는 this
this => 현재 컨텍스트를 가리킴
전역 스코프에서 실행되면, this는 전역 객체를 가리키고
함수 안에서 호출되면 호출 될 때 사용된 객체를 가르킨다.


lodash 모듈 간소화 하여 표현

index.js

module.exports = require('./test'); 

test.js

(function() { 
    console.log('hello'); 
}.call(this));

이렇게 코드가 진행된다면 index.js 파일은 require('./test')를 통해 test.js 파일을 가져오고, 그것을 다시 모듈로 내보내는 형식이라고 한다.

index.js에서 require('./test')를 호출할 때마다 "hello"가 콘솔에 출력된다.이는

Node.js에서 모듈이 처음 로드될 때 코드가 실행되기 때문, 따라서 index.js를 import하면 test.js 안에 있는 코드가 전부 실행되는 구조가 된다.

해당 모듈을 사용하지 않으면, import 되지 않지만, import 하게 되면 test.js안에 있는 모든 코드가 실행되는 구조이다.

lodash.js를 보면 {} 안에 모든 코드가 작성되어 있기 때문에, lodash의 하나의 함수만 사용하려 해도 모든 코드가 실행되는 구조임을 알 수 있다.

이 일련의 과정은 CommonJS 구조라 보면 된다.


위 과정을 통해 본 CommonJS

CommonJS는 Node.js에서 사용되는 모듈 시스템의 표준이다.
이 시스템은 require() 함수를 사용하여 모듈을 가져오고,
module.exports 객체를 사용하여 모듈을 내보낸다.

이것은 JavaScript 파일들을 모듈화하여 코드를 구성하고 재사용할 수 있게 해준다.

module.exports = require('./test'); 와 같은 코드는 다른 파일에서 해당 모듈을 가져올 때, require('./index') 와 같이 사용된다. require('./test')를 호출하면 test.js 파일이 로드 되고 실행된다.

→ 모듈로써도 사용할 수 있고, 해당 파일을 import해서 사용할 수 있다는 것을 의미하는 것 같음.

이러한 개념은 CommonJS의 핵심 개념 중 하나 이고 해당 모듈이 필요한 경우에만 로드 되고 실행된다.


import 와 require의 차이가 뭘까

→ 결국 이 말은 CommonJS와 ESM의 차이를 말하는 것이다.

CommonJS

  • 동기적 로딩
    require은 동기적 로딩으로 선언 된 순서대로 실행되는 특징이 있다.
    Node.js에서는 문제 될 건 없지만, 브라우저 환경에서는 블로킹이 일어날 수 있는 위험이 있다.
  • 파일 경로 기반 모듈 시스템
    각 파일이 하나의 모듈을 나타내며, 상대 경로 및 절대 경로로 지정된다.
  • 동적 로딩
    모듈이 필요한 시점에 동적으로 가져와지며, 프로그램 실행 중에 모듈이 로드된다.

이러한 특징은 아래의 문제을 일으킬 수 있다.

파일 기반 모듈 시스템의 한계
CommonJS는 파일 단위로 모듈을 정의하고 가져오기 때문에, 해당 모듈을 가져올 때 해당 파일의 모든 코드가 실행된다. 이로 인해 필요하지 않은 코드가 로드되거나 실행 될 수 있다.

이러한 점 때문에 트리셰이킹이 되지 않는다.

❓ 트리셰이킹
트리 셰이킹은 번들링 과정에서 사용되지 않는 코드를 제거하여 번들 파일의 크기를 줄이는 최적화 기법입니다. 주로 ES6의 모듈 시스템에서 지원되는 기능으로, import된 모듈 중에서 사용되지 않는 함수나 변수를 제거하여 번들 파일의 용량을 최적화합니다

import는 번들링 과정에서 필요한 모듈만 가져오게 되고 다른 것들은
가져오지 않는데, CommonJS는 그런 것 없이 다 가져오게된다.

❓ 파일 기반 모듈 시스템이기 때문에 번들링 과정에서도 트리 셰이킹이 안되는 건지

일반적으로 파일 기반 모듈 시스템에서는 번들링 과정에서 트리 셰이킹(tree shaking)이 제대로 이루어지지 않을 수 있다. 이는 주로 CommonJS나 AMD와 같은 모듈 시스템에서 발생하는 문제이다.

CommonJS의 require 함수는 동적으로 모듈을 가져오기 때문에 번들러가 정적으로 모듈 사용을 분석하기 어렵다. 따라서 번들링 과정에서 모든 모듈을 포함시키고, 사용되지 않는 코드를 제거하는 트리 셰이킹이 적용되지 않을 수 있다. 이는 번들 파일의 크기를 증가시키고 성능을 저하시킬 수 있다.

반면에 ES6의 모듈 시스템에서는 정적으로 모듈 사용을 분석하여 트리 셰이킹을 적용할 수 있다. import 구문을 통해 사용되지 않는 코드를 분석하여 번들 파일에 포함되지 않도록 처리할 수 있다. 따라서 ES6 모듈을 사용하는 경우 번들 파일의 크기를 최적화하는 데 더욱 효과적인 트리 셰이킹이 가능하다.


ESM

  • 정적 로딩
    import 구문을 사용할 때, 필요한 모듈만 가져와서 사용할 수 있다. 이건 모듈의 정적 로딩 특성에 의해 가능 한데, 모듈이 필요한 시점에만 가져와 진다. 프로그램이 실행될 때 필요한 모든 모듈이 미리 로드 되고 초기화되는 것을 의미

  • 동적 import를 제공
    CommonJS의 require은 동적 import 형식을 제공하지 않는다.
    이 말은 즉슨, 필요할 때 모듈을 가져오는 것이 불가능 하다는 것
    이러한 의문이 생길 수 있다.

    엇 근데 CommonJS는 동적 로딩의 특징을 가지고 있는데 왜 필요 시점에 가져오는 게 안되는 거지?

    require은 코드 실행 중에 모듈을 동적으로 로드하는 게 아니라, 프로그램의 시작시점에서 필요한 모듈을 모두 로드하고 준비하는 방식이다. require를 사용하여 로드한 모듈은 프로그램의 실행 도중에 변경되거나 추가되지 않는다.

  • 정적 분석 및 최적화

    ESM은 정적 분석이 가능하므로, 번들러가 불필요한 코드를 제거하거나 모듈을 최적화하는 등의 작업을 수행

  • 웹 브라우저 호환성

    SM은 웹 브라우저 환경에서도 사용할 수 있으며, 현대적인 웹 개발에 적합한 모듈 시스템을 제공

번들링 하게 되면 정적 로딩, 동적 로딩의 특징이 큰 의미가 없어진다. 어짜피 모든 코드를 미리 로드해놓고 준비해놓는 방식이기 때문에


가장 큰 차이점을 정리해보자면

  • CommonJS의 require 방식은 파일 경로 기반 이고, 파일 기반 모듈이기 때문에, 하나의 모듈이 한 파일에 작성된 모든 것이다. 그래서 파일 안에서 작성된 함수 하나만 가져오는 것이 불가능하다.

    내부 모듈이 require로 선언된 경우 내가 import로 가져와도 그냥 하나의 파일이 다 가져와 지는 것

  • ESM의 import 형식은 반대로 파일 기반이 아니기 때문에, 필요한 함수 같은 것만 가져오는 것이 가능하다.

  • 동적 import가 가능한 것
    import는 필요 시점에만 import 하는 것이 가능하다.

    리액트의 lazy 함수가 예로 설명할 수 있다.

    const abc = lazy(() => import('./Component/abc')); 

    CommonJS는 불가능하다.


마무리

lodash 모듈을 참고해서 ESM과 CommonJS를 이해해봤는데,
포스팅을 보는 것보단 코드를 뜯어보며 이해하니,
해당 개념에 대해 더 이해할 수 있었던 시간이였다. ☀️
다음엔 어떤 모듈을 뜯어볼까 🥺

profile
absolutely love this part

0개의 댓글

관련 채용 정보