CJS와 ESM
예전에 듣다가 중단했던 next 기초강의를 복기할 요량으로 듣고 있다.
( 원래 지난해에 들었어야 했지만.. 이런 저런 사정이 있었다 )
next를 이용한 프론트엔드와 express서버를 이용한 백엔드 구성에 대한 강의인데,
수월히 넘어갈것 같던 프엔쪽이 ..점입가경이어서 아직까지 서버쪽으로 못 넘어가고 있다.
강의 구매 후 2년여의 공백으로 적용되는 라이브러리의 버전이 당시와 많이 달라져,
매 강의마다 버전으로 인한 시행착오가 생겼다. ( react를 17버전을 사용하는 강의에
그대로 따라가긴 뭣해서 react나 node외에 라이브러리 버전을 좀 더 최신화했더니,
초보에게는 뭔가 알수없는 오류의 연속.. )
ESLint 오류를 넘었더니 이번엔 antd이다. ㅎㅎㅎ
⊙ 에러 내용
1 of 1 error Next.js (15.1.4) out of date (learn more) Server Error Error: Cannot find module '/Users/ .... (이부분은 디렉터리임으로 중간생략)/node_modules/rc-util/es/hooks/useMemo' imported from /Users/.... (이부분은 디렉터리임으로 중간생략)/node_modules/rc-util/es/ref.js This error happened while generating the page.
Any console logs will be displayed in the terminal window. finalizeResolution node:internal/modules/esm/resolve (275:11) moduleResolve node:internal/modules/esm/resolve (932:10) defaultResolve node:internal/modules/esm/resolve (1056:11) ModuleLoader.defaultResolve node:internal/modules/esm/loader (654:12) #cachedDefaultResolve node:internal/modules/esm/loader (603:25) ModuleLoader.getModuleJobForRequire node:internal/modules/esm/loader (353:53) new ModuleJobSync node:internal/modules/esm/module_job (341:34) ModuleLoader.importSyncForRequire node:internal/modules/esm/loader (326:11) loadESMFromCJS node:internal/modules/cjs/loader (1414:24) Module._compile node:internal/modules/cjs/loader (1547:5) Object..js node:internal/modules/cjs/loader (1708:10) Module.load node:internal/modules/cjs/loader (1318:32) Function._load node:internal/modules/cjs/loader (1128:12) TracingChannel.traceSync node:diagnostics_channel (322:14) wrapModuleLoad node:internal/modules/cjs/loader (219:24)
--> 최초로 antd를 불러서 적용하고 라이브서버를 열면 반영이 잘 되었다.
그러나 페이지를 새로고침 하거나, antd관련된 코드를 반영한 상태로 열면 저런 오류가 나타났다.
코드 문제는 아니고 어딘가 설정에 미흡함이 있는것이 분명한데, ref.js와 hooks내의
useMemo함수도 분명히 잘 있어서, 이런저런 설정을 바꾸어도 에러가 계속 발생했다.
⊙ 해결방법
next-transpile-modules에서 withTM을 next.config.mjs에 불러와서 antd사용에 관련된
모듈들에 적용해줌으로써 해결할수 있었다. ( next-transpile-module은 next.js에서 외부
모듈을 트랜스파일링 하면서 ESM모듈을 지원해야 할 경우에 사용한다고 한다. )
nextjs가 commonjs방식으로 구동을 하고 esm형식의 파일에 대해 트랜스파일링을 하는데,
node-modules내의 파일에 대해서는 이러한 번역(??) 작업을 적용하지 않도록 구성되어 있으므로
이에 예외를 적용해 주기 위해 사용하는 모듈이라 한다.
antd가 esm으로 구성되면서 생긴 문제라고 봐야 하는건가 ( 이 부분은 추가적인 배움이 있은후에
미래의 내가 코멘트 할 예정이다^^)
⊙ ESM과 CommonJS에 대한 내역 정리
ESModule(ESM)과 CommonJS(CJS) - code 공유, 재사용을 위한 모듈
- ESM은 브라우저 표준, CJS는 Node의 표준
- ESM에서의 CJS이용은 간편하나 (기존 모듈 대부분 CJS) 반대의 경우 불가능
CommonJS는 Node.js의 기본 모듈 시스템 (ESM 이전 방식)
- npmjs.com이나 yarnpkg.com에서 초기에 사용된 방법
- 각 파일이 별도의 모듈로 처리됨 ( CommonJS 구분으로 import/export)
const fs=require("fs")
model.exports = function() {return ... }
- 브라우저가 모듈 시스템이 없었음 (Node.js만 CommonJS로 모듈형태 구현)
ES Module은 브라우저에서 모듈작업을 수행 (ES6 by ECMAScript standardization)
- 모듈 처리시 import/ export 구문 활용
import fs from 'fs'
export default function() {return..}
- React나 Vue는 ESM구문 사용 (Babel등의 도구로 CommonJS구문 변환수행)
CJS에서의 ESM적용 ( 직접 적용불가, 우회 )
(1) Node.js에서 ESM 모듈채택
① '.mjs'형식의 모든 ESM파일에 확장자 적용
② package.json에 type:"module"으로 선언 (Node에 ESM문법이 적용되었음을 알려줌)
** 대부분의 경우 ESM와 CJS의 하이브리드 방식 지원
(2) 번들러 (Rollup 등)
- 빌드 타임에 (런타임 아님) 코드의 가져오기와 내보개기 해결 (CJS작성 후 ESModule출력가능)
---------------------------------------------------------------------------------------------------
chatGPT에게 묻고 버전도 바꾸고, 캐시 지우고, 설정을 바꾸고 의존성 다시 깔고 이런걸 30번은 한거 같다.
바벨을 깔아서 antd에 대한 옵션을 줘보거나(next는 기본 SWC를 사용한다고 한다 ),
nextConfig에서 experimental.esmExternals로 설정하거나,
next-with-less로 withLess설정,
next/dynamic을 가져와서 동적 로드설정 등 여러가지 삽질을 다양하게 했다.
사실 정확한 용어의 의미도 분명히 몰라서 확인하기 어려웠다.
필요한 경우에 좀 더 구체적으로 살펴보기 위해 간략히 내용을 정리하고 관련 블로그와 표준 문서의 위치에
대한 흔적을 남긴다.
참조한 문서들
https://nextjs.org/docs/app/api-reference/config/next-config-js/transpilePackages
https://blog.smilecat.dev/posts/next-transpile-modules
https://velog.io/@park2348190/AntD%EC%99%80-Next.js-12-14
https://konstantin.digital/blog/es-modules-all-you-need-to-know