2023년 6월 8일
이론
조회 : 400|8분 읽기
CJS - ESM
만화로 보는 es 모듈 번역과 첨언
변수 관리
모든 프로그래밍 언어는 변수를 관리하며 작업하는 것이 중요합니다. -> GC가 없다면 정리해야할 변수 관리, 있어도 전역 변수(static)나 힙 사이즈 관리
✅ JavaScript에는 Scope를 사용해 변수를 안전하게 관리할 수 있습니다.
❌ 하지만 Scope가 작동하는 방식 때문에 함수는 다른 함수에 정의된 변수에 액세스할 수 없습니다.
💡 이 부분을 해결하기 위해 전역 변수를 사용하게 됩니다.
⛔ 이제 함수에서 공용으로 사용해야하는 변수를 접근해 사용할 수 있지만 문제점들이 있습니다.
첫번쨰 만약 변수를 참조할때 그 변수가 아직 브라우저에 존재하지 않는다면 함수는 망가집니다.
※ 존재하지 않는다는 뜻은 스크립트 태그의 순서가 잘못되었다던가 오류로 인해 스크립트가 제대로 작동하지 않았을 때 입니다.
스크립트 간의 종속성은 암시적이기 때문에 모든 함수는 전역의 모든 것을 잡을 수 있으므로 어떤 함수가 어떤 스크립트에 의존하는지 알 수 없습니다.
두번쨰 전역 변수의 위험성
스코프를 이용해 변수를 안전하게 관리하며 사용했지만 전역 변수를 사용해 스코프간의 연결을 하게 됨으로서 스코프 오염이 됩니다.
모듈
모듈을 사용하면 변수와 함수를 그룹화할 수 있습니다.
함수와 변수가 모듈 스코프에 배치됩니다.
모듈 스코프는 모듈의 함수 간에 변수를 공유하는 데 사용할 수 있습니다.
😮 그러나 일반 스코프(함수)와 달리 모듈 스코프에는 다른 모듈에서도 변수를 사용할 수 있도록 하는 방법이 있습니다.
모듈의 변수, 클래스 또는 함수 중 어떤 것을 사용할 수 있는지 명시적으로 말할 수 있습니다.
다른 모듈에서 사용할 수 있는 항목이 있는 경우 이를 export라고 합니다.
export가 있으면 다른 모듈이 해당 변수, 클래스 또는 함수에 의존한다고 명시적으로 말할 수 있습니다.
⭐ 명시적 관계이기 때문에 다른 모듈을 제거할 경우 중단되는 모듈을 알 수 있습니다.
🧱 코드를 서로 독립적으로 작동할 수 있는 작은 덩어리로 훨씬 쉽게 나눌 수 있습니다.
후에 레고 블록과 같은 청크를 결합하여 동일한 모듈 세트에서 모든 종류의 응용 프로그램을 만들 수 있습니다.
ES 모듈 작동 방식
1. 종속성 그래프
🖊️ 모듈을 사용하여 개발할 때 종속성 그래프를 작성합니다.
서로 다른 종속성 간의 연결은 사용하는 모든 import 문에서 가져옵니다.
이러한 import 문은 브라우저 또는 노드가 로드해야 하는 코드를 정확히 아는 방법입니다.
🚪 그래프의 진입점으로 사용할 파일(main.js)을 제공합니다.
거기에서 import 문 중 하나를 따라 나머지 코드를 찾습니다.
이러한 모든 파일을 구문 분석하여 모듈 레코드라는 데이터 구조로 변환해야 합니다. 그렇게 하면 실제로 파일에서 무슨 일이 일어나고 있는지 알 수 있습니다.
※ 모듈 레코드
어떤 모듈들이 어떤 이름으로 import되는지, 필요한지 등 모듈이 파싱될 때 생성되는 내부 구조체
2. 인스턴스화
모듈 레코드를 인스턴스로 변환하는데 code, state를 결합합니다.
⌨ code는 기본적으로 일련의 명령입니다.
무언가를 만드는 방법에 대한 레시피와 같지만 이것만으로는 아무것도 할 수 없습니다. 명령과 함께 사용할 raw materials( ex) 통나무 )가 필요합니다.
🛢 state는 이러한 raw materials를 제공합니다.
state는 특정 시점의 변수들의 실제 값입니다.
그리고 모듈 인스턴스는 code(명령어 목록)와 state(모든 변수의 state 값)를 결합합니다.
우리는 이렇게 생긴 각 모듈에 대한 모듈 인스턴스가 필요합니다.
모듈 로딩 과정은 entry 파일에서 모듈 인스턴스의 전체 graph를 갖는 것으로 진행됩니다
3. 평가
code를 실행해 메모리 상자의 실제 값을 채웁니다.
ESM의 경우 이 작업이 세 단계로 이루어집니다.
🏗 구성 — 모든 파일을 찾아 다운로드하고 구문 분석하여 모듈 레코드로 만듭니다.
🔗 인스턴스화 - 메모리에서 내보낸 모든 값을 배치할 상자를 찾습니다(그러나 아직 값으로 채우지 않음).
그런 다음 내보내기와 가져오기가 모두 메모리의 해당 상자를 가리키도록 합니다. 이를 연결이라고 합니다.
🎉 평가 - 코드를 실행하여 상자의 실제 값을 채웁니다.
로더(loader)
사람들은 ES 모듈이 비동기식이라고 이야기합니다.
작업이 로드, 인스턴스화 및 평가의 세 가지 단계로 분할되고 이러한 단계를 별도로 수행할 수 있기 때문에 비동기로 생각할 수 있습니다.
이것은 사양이 CommonJS에 없었던 일종의 비동기를 도입한다는 것을 의미합니다.
나중에 더 자세히 설명하겠지만 CJS에서는 모듈과 그 아래의 종속성이 중간에 중단 없이 한 번에 로드, 인스턴스화 및 평가됩니다.
🔥 그러나 단계 자체가 반드시 비동기식일 필요는 없습니다.
동기식으로 수행 할 수 있습니다.
로딩을 수행하는 작업에 따라 다릅니다.
모든 것이 ES 모듈 사양에 의해 제어되는 것은 아니기 때문입니다. 실제로 작업의 두 반쪽이 있으며 서로 다른 사양으로 덮여 있습니다.
ES 모듈 사양은 파일을 모듈 레코드로 구문 분석하는 방법과 해당 모듈을 인스턴스화하고 평가하는 방법을 알려줍니다.
그러나 처음에 파일을 얻는 방법은 나와 있지 않습니다.
📁 파일을 가져오는 것은 로더입니다.
그리고 로더는 다른 사양으로 지정됩니다.
브라우저의 경우 해당 사양은 HTML 사양입니다.
그러나 사용 중인 플랫폼에 따라 다른 로더를 가질 수 있습니다.
로더는 또한 모듈이 로드되는 방식을 정확하게 제어합니다.
ES 모듈 메서드 '—' , ',' , '.' 를 호출합니다.
🧸 JS 엔진의 문자열을 제어하는 꼭두각시 인형과 같습니다.
ESM 작업 세 단계
1. 🏗 Construction 구성
각 모듈에 대해 세 가지 일이 발생합니다.
1. 모듈이 포함 된 파일을 다운로드 할 위치를 파악하십시오 (일명 모듈 해상도).
2. 파일 가져오기(URL에서 다운로드하거나 파일 시스템에서 로드)
로더는 파일을 찾고 다운로드하는 일을 처리합니다.
먼저 진입점 파일을 찾아야 합니다.
HTML에서는 스크립트 태그를 사용하여 로더를 찾을 수 있는 위치를 로더에 알려줍니다.
그리고 main.js가 직접적으로 의존하는 모듈들을 어떻게 찾을 수 있을까요?
💡 import 문을 사용해 모듈 지정자로 위치를 알려 다음 모듈에게 찾을 수 있도록 정보를 제공합니다.
브라우저는 URL만 모듈 지정자로 허용합니다.
해당 URL에서 모듈 파일을 로드합니다.
하지만 이것은 전체 그래프에서 동시에 발생하지 않습니다.
파일을 구문 분석 할 때까지 모듈에서 가져와야하는 종속성을 알 수 없습니다.
파일을 가져올 때까지 파일을 구문 분석 할 수 없습니다.
🌳 즉, 트리를 레이어별로 살펴보고 하나의 파일을 구문 분석한 다음 해당 종속성을 파악한 다음 해당 종속성을 찾아 로드해야 합니다.
만약 메인 스레드가 각 파일이 다운로드될 때까지 기다리면 다른 많은 작업이 대기열에 쌓이게 됩니다.
브라우저에서 작업할 때 다운로드 부분에 시간이 오래 걸리기 때문입니다.
CJS는 파일 시스템으로부터 파일을 로드하기 떄문에 인터넷으로 다운 받는 것보다 시간이 훨씬 적게 걸리기 때문에 다르게 작동한다.
이 말은 노드는 파일이 로드될때 까지 메인 스레드를 중단시킬 수 있습니다.
CommonJS 모듈 시스템에서 모듈 인스턴스를 생성하고 평가하는 것이 분리되어 있지 않기 때문에 파일이 이미 로드되어 있으면 모듈 인스턴스를 생성하고 평가하는 것이 합리적입니다.
⛸ 이는 모듈 인스턴스를 반환하기 전에 종속성을 로드하고 인스턴스화하고 평가하기 위해 전체 트리를 따라가며 종속성을 로드하고 인스턴스화하고 평가한다는 것을 의미합니다.
module map
로더는 모듈 맵(키 : URL, 값 : 상태)을 사용해 캐시를 관리합니다. 각 전역은 별도의 모듈맵에서 해당 모듈을 추적합니다.
3. 파일을 모듈 레코드로 구문 분석합니다
이제 이 파일을 가져왔으므로 모듈 레코드로 구문 분석해야 합니다. 이것은 브라우저가 모듈의 다른 부분이 무엇인지 이해하는 데 도움이 됩니다.
모듈 레코드가 생성되고 모듈 맵에 들어가 있다는 것은 언제든지 요청한다면 로더가 맵으로부터 꺼낼 수 있다는 뜻입니다.
모든 모듈은 최상위 코드에서 구문 분석됩니다. 그러나 다른 약간의 차이점이 있습니다. 예를 들어 키워드는 모듈의 최상위 코드에서 예약되며 값은 "use strict" 입니다.
그러나 이 문장에서는 구문 분석에 대한 사소한 세부 사항이 언급되었지만 실제로는 꽤 중요합니다. 이는 모든 모듈이 맨 위에서부터 구문 분석된다는 것을 의미합니다.
😵
이 다른 구문 분석 방법을 "구문 분석 목표"라고 합니다.
동일한 파일을 구문 분석하지만 다른 목표를 사용하면 다른 결과가 나옵니다.
따라서 구문 분석을 시작하기 전에 구문 분석하는 파일의 종류가 모듈인지 아닌지 알고 싶습니다.
브라우저에서는 매우 쉽습니다.
스크립트 태그를 붙이기만 하면 됩니다. type="module"
이렇게 하면 이 파일을 모듈로 구문 분석해야 한다는 것을 브라우저에 알릴 수 있습니다.
그리고 모듈만 가져올 수 있기 때문에 브라우저는 모든 가져오기도 모듈이라는 것을 알고 있습니다.
2. 🔨 Instantiation 인스턴스화
인스턴스는 코드와 상태를 결합합니다. 이 상태는 메모리에 있으므로 인스턴스화 단계는 메모리에 연결하는 것입니다.
먼저 JS 엔진은 모듈 스코프 레코드를 만듭니다.
이렇게 하면 모듈 레코드에 대한 변수가 관리됩니다.
그런 다음 메모리에서 모든 export에 대한 상자(address)를 찾습니다.
모듈 환경 레코드는 메모리의 어떤 상자가 각 내보내기와 연관되어 있는지 추적합니다.
메모리에 있는 이러한 상자는 아직 값을 가져오지 않습니다.
평가 후에야 실제 값이 채워집니다.
이때 주의할 점은 export한 모든 함수 선언은 이 단계에서 초기화됩니다. 이렇게 하면 평가가 더 쉬워집니다.
모듈 그래프를 인스턴스화하기 위해 엔진은 깊이 첫 번째 사후 순회라고 하는 작업을 수행합니다.
즉, 그래프의 맨 아래로, 즉 다른 것에 의존하지 않는 맨 아래의 종속성으로 내려가 내보내기를 설정합니다.
🧨 CJS와 다른 점은 CJS는 export 때 전체 export 객체가 복사됩니다. 즉 export하는 모든 값은 복사본입니다.
⭐ 반면 ESM은 라이브 바인딩이라는 것을 사용합니다. 두 모듈 모두 메모리의 동일한 위치를 가리킵니다.
⚡ 즉, export 모듈이 값을 변경하면 해당 변경 사항이 가져오기 모듈에 표시됩니다.
📍 값을 내보내는 모듈은 언제든지 해당 값을 변경할 수 있지만 가져오는 모듈은 가져오기 값을 변경할 수 없습니다.
즉, 모듈이 객체를 가져 오면 해당 객체에있는 속성 값을 변경할 수 있습니다.
이와 같은 라이브 바인딩을 사용하는 이유는 코드를 실행하지 않고도 모든 모듈을 연결할 수 있기 때문입니다.
이것은 아래에서 설명하겠지만 주기적 종속성이 있을 때 평가에 도움이 됩니다.
따라서 이 단계가 끝나면 export/import 변수의 모든 인스턴스와 메모리 위치가 연결됩니다.
이제 코드를 평가하고 해당 메모리 위치를 해당 값으로 채울 수 있습니다.
3. 🔫 Evaluation 평가
마지막 단계는 메모리에서 이러한 상자를 채우는 것입니다. JS 엔진은 최상위 코드(함수 외부에 있는 코드)를 실행하여 이를 수행합니다.
메모리에서 이러한 상자를 채우는 것 외에도 코드를 평가하면 부작용이 발생할 수 있습니다.
예를 들어, 모듈은 서버를 호출할 수 있습니다.
⛔ 평가 단계에서 CJS의 문제
출처 :