-
Notifications
You must be signed in to change notification settings - Fork 1
ESM 제대로 쓰기(추후 분리할 예정)
Typescript 파일을 실행하는 방법에는 여러가지가 있다. 그런데 같은 파일을 실행을 해도 그 방법에 따라 에러가 발생할 수도 있다.
다음과 같이 ts파일을 실행시킬 수 있다. (scripts에 명령어로도 가능)
-
npx ts-node index.ts
ornpx nodemon index.ts
: tsconfig.json을 따르고 ts-node의 flag들을 tsconfig.json의 ts-node 객체로 저장할 수 있다. -
npx tsc
&node/nodemon index.js
: tsconfig.json이 정의되어 있으면 파일들을 알아서 컴파일 해준다. npx vite
npx jest
문제 상황을 좀 더 구체적으로 가정해보자
// index.ts
import packages from './package.json'
-
tsconfig.json
의resolveJsonModule
값을 true로 했으니 컴파일은 문제가 없다.
그러나,
-
npx ts-node index.ts
ornpx nodemon index.ts
: -
npx tsc
&node/nodemon index.js
npx jest
npx vite
를 제외한 3가지 경우에 대해 실패하는 것을 알 수 있다. 그러면 어떻게 고쳐야할까??
문제점 1. ECMAScript 모듈 시스템(ESM, import
와 export
키워드)은 기본적으로 자바스크립트 코드만 가져올 수 있다.
- 오랫동안 JSON 가져오기는 commonjs 모듈 형식으로 지원되었다. 다음과 같이 작성할 경우 문제 없이 실행된다.
// index.cjs
const packages = require('./package.json');
console.log(packages);
-
JSON 모듈 제안의 일반
import
에서도 JSON을 import할 수 있도록 assert문을 추가한다.
// index.ts
import packages from './package.json' assert { type: "json" };
- 다음과 같이 작성할 경우 1, 2번의 경우를 해결할 수 있다.
- 그러나 여전히
npx jest
에서는 에러가 발생한다.
문제점 2. **npx jest
에서는 에러가 발생한다.**
import * as packages from './package.json';
console.log(packages);
- 다음과 같이 작성하면 해결된다.. 왜??
-
tsc
,ts-node
,nodemon
→import packages from './package.json' assert { type: "json" };
-
jest
→import * as packages from './package.json';
-
vite
→import packages from './package.json'
혹은@babel/plugin-syntax-import-assertions
의 설치?
자존심 강한 천재들의 대결…
왜 이런 슬픈 현상이 일어나는 것일까??
- Javascript 모듈을 런타임에 로드할 수 있게 만드는 구현체!
- 전역변수, 네임스페이스 관리가 어렵다
- 파일 덩치가 엄청나게 커진다.
// index.cjs
const packages = require('./package.json');
console.log(packages);
exports.add = function (a, b) {
return a + b;
}
// app.js
const { add } = require('./add.js');
- 파일 단위로 분리 가능!
- 라이브러리 함수 재사용
- npm의 등장
-
CommonJS는 언어 표준이 아니다!
-
정적 분석이 어렵고 함수를 재정의할 수 있다.
require
는 그냥 함수이기 때문에 조건에 따라 동적으로 인자를 넣을 수도 있고 다른 시그니쳐로도 사용이 가능하다. 따라서 코드가 무엇을 참조하는지 컴파일 타임에 분석하는 것이 어려워지고 실제로 사용하는 코드만 뽑아내는 tree-shaking 같은 작업도 어려워지게 된다.if (...) { add = require('./add.js'); } require( ... ? 'foo' : 'bar') const anotherRequire = global.require;
-
비동기 모듈 정의가 불가능하다. 따라서 연결을 위한 초기화 작업(e.g., flag 변수를 통해 연결이 되었는지를 확인)과 매번 연결을 확인하는 과정이 필요하다.
- 모든 의존성이 로컬 디스크에 존재해 필요한 모듈을 바로 사용할 수 있는 환경을 전제로한다.. → 동기적 호출
- 게다가 브라우저에서는 매번 js 파일을 불러와야 했기 때문에 동기적 모듈도 비동기적으로 로딩이 되기 때문에 사용하기 어려웠다. ⇒ 이 문제는 번들러로 해결했다!
import foo from 'foo'
export { foo };
export default foo;
- 동기/비동기 로드 지원
- 실제 객체/함수를 바인딩 → 수월한 순환 참조 관리
- 언어 표준 & 쉬운 문법 → 최상위 수준에서만 허용!
- 정적 분석 → 트리 쉐이킹
- 런타임에 모듈을 가져오기 위한 목적 → 모듈 로더
- 빌드 시 모듈을 묶고 런타임에서 추가적인 로드를 안 함 → 모듈 번들러
- 종속성 관계 및 순서 관리
- asset 로딩
- 브라우저의 모듈 시스템 지원 부족
- 프로덕트 개발 과정에서 필요한 일련의 과정까지 자동화(개발 → 빌드 → 최적화)
- 컨벤션 → eslint
- 컴파일, 빌드, 전처리 → Sass, Typescript
- 소스 코드를 축소 및 번들링(최적화)
-
Webpack : 번들 + 개발에 필요한 여러가지 도구들 제공
- asset을 javascript 코드로 변환, 분석 후 번들
- 개발 서버
- 라이브 리로딩
- 핫 모듈 교체: 앱을 종료하지 않고 갱신된 파일만을 교체하는 방식
- 변경된 모듈을 핫 모듈이라고 한다
- 단점
- JavaScript 모듈의 개수도 극적으로 증가 → build 및 위의 추가 기능까지 병목 현상 발생
- 소스 코드를 업데이트 하게 되면 번들링 과정을 다시 거쳐야함. 이를 우회하기 위한 핫 모듈 교체지만 선형적으로 갱신에 필요한 시간이 증가함
- 트리 쉐이킹을 위해 CommonJS 방식으로 모듈을 로드한 부분을 ES6로 교체해야함, 그러나 ES6 모듈 형태로 빌드 결과물을 출력할 수 없음
- 복잡함
- JavaScript 모듈의 개수도 극적으로 증가 → build 및 위의 추가 기능까지 병목 현상 발생
기존 번들러의 개발 서버의 단점 → Vite의 해결법
- 빌드 속도 자체가 느리다 →
esbuild
의 사용 - 빌드를 끝마치면 또 다시 번들을 해야한다 → 번들을 하지 말고 변경 사항이 있는 모듈만 빌드 후 업데이트
- 번들링을 하고 다시 웹 페이지에서 불러오기 → 핫 모듈 교체, 그러나 핫 모듈 역시 번들링을 한다 → ESM을 이용해, 브라우저가 요청을 할 때 교체된 모듈을 전달!
요약하자면, 모듈 별 빌드 + 브라우저 표준 모듈(ESM) 활용, 일종의 브라우저가 번들러의 역할을 수행을 통해 기존 번들러의 개발 과정에서의 단점을 해소했다!
그러나, 여전히 배포때에는 번들링을 수행한다!
- 프로덕션에서 번들 되지 않은 ESM을 가져오는 것은 중첩된 import로 인한 추가 네트워크 통신으로 인해 여전히 비효율적
- 번들링에 필수적으로 요구되는 기능인 코드 분할(Code-splitting) 및 CSS와 관련된 처리가 아직 미비
ESM은 CommonJS의 문제점들을 해결하는 언어 표준의 모듈 시스템이고 Vite는 ESBuild와 ESM을 이용해 Webpack 개발 서버의 문제점을 해결해 생산성을 높였다.
의문
- Vite는 ESM의 어떤 점을 이용해 번들링을 하지 않는 것일까?
- 그러면 제일 처음의 문제 상황은 어떤 원인때문에 발생한 것일까?
링크 에서 좋은 설명을 볼 수 있지만 생각보다 깊다..
- ESM 스펙은 일종의 모듈(파일)들을 파싱, 인스턴스화, 평가를 할 것인지를 정해놓은 것이라면, 스펙은 어떻게 모듈(파일)을 로딩할 것인가에 대한 이야기이다.
- Vite는 브라우저가 어떻게 로딩을 하는 것인가를 이용해 번들링을 하지 않는 것이라고 말할 수 있다.
Vite는 그저 브라우저의 판단 아래 특정 모듈에 대한 소스 코드를 요청하면 이를 전달할 뿐입니다. 따라서 조건부 동적 import 이후의 코드는 현재 화면에서 실제로 사용이 되어야만 처리가 됩니다.
- 로딩 과정에서 모듈맵을 작성한다!
- 더 복잡한 동작 원리는 추후에 공부해봐야겠다.
그래서 왜? import 하는 부분이 다 다를까???
- 동기(CommonJS) - 비동기(ESM)
- 아직도 너무 많은 CommonJS 모듈들이 존재
-
require
를 이용해 ESM 모듈을 import할 수 없다- 비동기에서 동기를 사용하기는 쉽지만 동기에서 비동기를 사용하기는 어려운 것처럼
-
package.json
에서type: module
명시하기-
package.json
에 아래에 있는 모든 파일은 ESM으로 동작한다. -
.js
파일은 가장 가까운package.json
설정을 따른다.
-
-
.cjs
,.mjs
로 명시하면 원하는 모듈 로딩 방식을 사용할 수 있다.
- 확장자를 명시해야한다.
-
require
는 명시하지 않아도 확장자를 붙여주면서 찾아준다 → Redundant하게 파일 시스템에 추가적으로 접근해야한다.import foo from './foo'
→import foo from './foo.js'
- TS 컴파일/Babel 을 통해 compile / transform 될 때에도 이를 반영한다.
-
- Typescript의 ESM 지원?
- 여전히
.js
로 import 해야한다?-
import foo from './foo.ts'
는 틀린 코드이다 -
import foo from './foo.js'
가 맞는 코드이다 - webpack이나 ts-node 같은 도구들과 궁합이 맞지 않는다.
- 따로 컴파일을 하지는 않으니까? 아직 이유는 불분명
- subpath import를 할 때에도 확장자를 명시해야한다…
-
-
.cjs
,.mjs
→.cts
,.mts
를 지원한다!
- 여전히
- Jest, ts-node
-
공통점 : 모두 require의 동작을 바꾼다!!!
- Jest: Mocking function때문에 require의 동작 방식을 고침
- ts-node:
require('./foo.ts')
, ts 파일을 require할 수 있도록 고침
-
-
ts-node
,tsc
,nodemon
은 타입스크립트 컴파일에 관한 것이고 컴파일 되었을 때 ESM을 잘 따르도록 규칙에 맞게 작성해야한다. ex) 확장자 명시, assert문 - Jest는 명확하지는 않지만 자체 import, require의 동작을 정의했다?
- Vite는 1번과 거의 동일하겠지만
@vitejs/plugin-react
때문에 babel이 관여한다. 따라서 어떻게 transform되는지 확인해봐야 한다.
- https://ui.toast.com/posts/ko_20211209
- Vite 이야기
- FECONF 2022 [B4] 내 import 문이 그렇게 이상했나요?
- https://www.typescriptlang.org/docs/handbook/2/modules.html
- https://typestrong.org/ts-node/docs/imports
- https://yozm.wishket.com/magazine/detail/1261/
- https://vitejs-kr.github.io/guide/why.html#the-problems
- https://yoiyoy.gitbooks.io/dev/content/hot-module-replacement.html
- https://developer.mozilla.org/ko/docs/Web/JavaScript/Guide/Modules
- https://ui.toast.com/weekly-pick/ko_20180402#es-modules-만화로-보는-심층-탐구
- OaO 환경설정 A to Z
- CRLF 너가 뭔데 날 힘들게 해?
- Github Issue 똑똑하게 사용하기
- OAO! CI CD 적용기 with release 자동화
- 매번 다른 import문
- 못생긴 상대경로에서 간zlzl존 절대경로로😎
- TodoList API 개발기
- 의존성 주입으로 DB를 바꿔보자
- 렌더링 최적화 서막: useNavigate를 추가한 순간 리렌더 범위가 확장된 건에 대하여
- 렌더링 최적화 1탄: 렌더링 범위에 대하여 (by 최적화무새)
- 렌더링 최적화 2탄: 잘못된 custom hook 사용,, 전체 리렌더링을 부르다,,
- 렌더링 최적화 3탄: Todo 상세 좀 봤다고 테이블 전체가 재렌더링 되는건을 고치기😌
- 렌더링 최적화 4탄: 다이어그램 편
- 🐁 마우스 상대위치 계산은 이상해
- React 컴포넌트에 애니메이션을 적용해보자 🏃🏻💨
- 컴포넌트 재사용성을 높여보자: Modal 분리기 🌹
- 선후관계를 자동완성으로 추가해보자 🔎