Error: ES Modules may not assign module.exports or exports.*, Use ESM export syntax 에러 이슈

MaxlChan·2025년 12월 8일

티스토리에 올렸던 2023년 07월 12일 글

최근 몇달 사이 패키지를 업데이트하거나 새로운 패키지를 설치하고 나서, 웹팩 빌드후 빌드된 asset를 실행시켰을 때

Error: ES Modules may not assign module.exports or exports.*, Use ESM export syntax

위와 같은 에러가 발생하는 경우가 있어서, 앞으로 같은 에러를 보고 당황하지 않고자 하는 마음으로 기록을 적으려 한다.

1. Sentry update

Sentry를 v6 -> v7로 버전을 업데이트 하는 과정에서 마이그레이션 가이드에 Moving To ES6 For CommonJS Files 라고 나와있었고, 요약하자면 v7부터는 패키지가 ES6 사용될 예정이니 old 브라우저 지원을 위해서 babel 같은 걸로 알아서 sentry를 ES5로 트랜스파일 하라는 내용이었다.

친절한 가이드에 따라서 webpack 빌드가 될때 babel-loader가 sentry 패키지에 대해서는 동작하도록 설정을 바꾸어주었다.

    module: {
      rules: [
        {
          test: /\.(j|t)sx?$/,
-         exclude: /node_modules/,
+         exclude: {
+           and: [/node_modules/],
+           not: [/@sentry/],
+         },
          loader: 'babel-loader',
          options: {
            plugins: babelPlugins,
          },
        },

설정은 webpack 공식 문서를 참고했다.
https://webpack.js.org/configuration/module/#condition

그리고 나서 잘 되겠지 하는 마음으로 다시 빌드 후에 실행을 시켰더니 이번에는 블로그 제목의 에러가 발생하였다.

sentry에서 es6이전 버전도 호환될 수 있도록 babel로 변환 시켜준 후에 발생된 에러

https://github.com/babel/babel/issues/12731#issuecomment-780153966

다행히도 바벨 maintainer가 바벨 configuration에 sourceType: "unambiguous" 넣어주면 해결해줄 것이라고 답변을 달아주었고 해당 옵션이 무엇인지에 대해서 공식문서에 정확히 내 상황과 딱 떨어지는 설명이 기재되어있었다. https://babeljs.io/docs/options#sourcetype

For instance, @babel/plugin-transform-runtime relies on the type of the current document to decide whether to insert an import declaration, or a require() call. @babel/preset-env also does the same for its "useBuiltIns" option. Since Babel defaults to treating files are ES modules, generally these plugins/presets will insert import statements. Setting the correct sourceType can be important because having the wrong type can lead to cases where Babel would insert import statements into files that are meant to be CommonJS files. This can be particularly important in projects where compilation of node_modules dependencies is being performed, because inserting an import statements can cause Webpack and other tooling to see a file as an ES module, breaking what would otherwise be a functional CommonJS file.

요약하자면, 바벨은 기본적으로 파일을 es moudle로 인식해서(Default: "module") 자동적으로 @babel/plugin-transform-runtime 와 같은 플러그인이 import 구문을 넣어준다고 한다. 그런데 sourceType을 잘 설정해주지 않으면 실제 CommonJS 파일에도 import구문이 잘못 들어갈 수 있기 때문에 정상적으로 작동하는 CommonJS 파일이 손상될 수 있으므로 node_modules 종속성 컴파일이 수행되는 프로젝트(딱 나의 상황..)에서 해당 옵션이 중요하다고 한다. "umabiguous" 옵션은 Babel이 CJS 또는 ESM 파일인지 자동으로 감지하여 CJS 종속성에 가져오기 문을 삽입하지 않도록 지시한다고 한다.

결론적으로 node_modules 패키지에 대해서는 전부 해당 옵션을 적용키로 결정하였고, 에러가 말끔히 해결 되었다. (fyi. root 파일의 바벨 configuration이 node_mouldes 영향을 주기위해서는 파일이름을 .babelrc -> babel.config.js(on) 으로 바꿔줘야하는 이슈도 있었다(이번 주제와는 거리가 멀기 때문에 pass..)
https://babeljs.io/docs/config-files, https://github.com/jestjs/jest/issues/6053#issuecomment-383632515)

{
  "presets": [
    [
      "@babel/preset-env",
      {
        "modules": false
      }
    ],
  ],
   "plugins": [
    [
      "@babel/plugin-transform-runtime",
    ],
   "env": {   

...

+ "overrides": [
+   {
+     "test": "./node_modules",
+     "sourceType": "unambiguous"
+   }
+ ]
}

2. Firebase/auth install

Firebase를 integration할 업무가 생겨서 firebase 패키지를 설치하고나서 firebase api가 잘 작동하는지 확인하기 위해 앱을 테스트해보았다. 에러가 왠지 어디서 본듯한..? 에러가 발생했더니 위에 있던 에러와 똑같은 에러메세지를 보여줬다. 결국 뭔가 빌드과정에서 ES Modules와 cjs 사이의 호환되지 않은 문제가 발생한 듯 보였다. 그런데 나는 이미 전에 node_modules에 대해서 unambiguous 설정을 해주었는데, 왜..? 발생하는 거지 의문이 들어서 해당 babel 쪽에 이리저리 수정해보았으나, 해결되지 않았다. 지금 생각해보면 그전과 비슷한 상황일 것이라고 가정하고 에러에 접근한 것이 잘못되었던 것 같다.

문뜩 다음날이 지나고(역시 휴식을 취해야..) 에러 메세지를 자세히 보다가 결국 저 에러가 firebase에 있는 node_modules에 whatwg-fetch(fetch 폴리필 라이브러리)에서 fetch에서 발생한다는 것을 발견했다. 내 package에 있는 whatwg-fetch 버전과 firebase가 dependency로 가지고 있는 whatwg-fetch 버전을 비교해보았더니 각각 v2, v3로 달랐다. 그리고 결국 내 node_modules에 있는 exports-loader가 에러를 발생시키는 것을 보고 수상하다고 여겨 webpack configuration쪽을 봤더니 역시나, 아래와 같이 ProvidePlugin으로 전역 fetch가 해당 모듈을 가르키게 설정되어있었다.

new webpack.ProvidePlugin({
   fetch: 'exports-loader?self.fetch!whatwg-fetch',
}),

exports-loader 특정 파일이 모듈화(cjs, esm)가 안되어있을 경우 모듈화를 시켜주는 라이브러리인데 실제 프로젝트의 whatwg-fetch v2는 소스코드를 열어보니 모듈화가 되어있지않았고, 정확히 v3부터 esm 모듈화를 지원한다고 나와있었다. https://github.com/cwage/fetch/releases/tag/v3.0.0

The whatwg-fetch package is now a module with exports

결론적으로 firebase에 whatwg-fetch는 이미 모듈화되어있는 버전인데, 내 패키지에서는 서드파티 라이브러리로 동일한 라이브러리를 임의로 모듈화를 시키는 과정에서 빌드 충돌이 일어난 것으로 판단했고 프로젝트의 whatwg-fetch의 버전을 v3으로 동일하게 올리고 해당 ProvidePlugin 설정을 웹팩에서 삭제시켜줌으로서 에러가 해결 되었다.

profile
한가지를 알아도 제대로 알자

0개의 댓글