ECMAScript Modules

ECMAScript 모듈(ESM)은 웹에서 모듈을 사용하기 위한 사양입니다. 모든 최신 브라우저와 권장하는 웹 모듈 코드 작성법에서 지원됩니다.

Webpack은 ECMAScript 모듈을 최적화하기 위한 처리를 지원합니다.

Exporting

export 키워드를 사용하면 ESM 항목을 다른 모듈에 노출할 수 있습니다.

export const CONSTANT = 42;

export let variable = 42;
// 오직 읽는 값만 노출됩니다.
// 외부에서 변수를 수정할 수 없습니다.

export function fun() {
  console.log("fun");
}

export class C extends Super {
  method() {
    console.log("method");
  }
}

let a, b, other;
export { a, b, other as c };

export default 1 + 2 + 3 + more();

Importing

import 키워드를 사용하면 다른 모듈에 대한 참조를 ESM으로 가져올 수 있습니다.

// 다른 모듈에 export하기 위해 "bindings"를 가져옵니다.
// 바인딩은 활성상태입니다. 값은 복사되지 않습니다.
// 대신 "변수"에 접근하면 현재 값을 얻습니다.
// 가져온 모듈에서
import { CONSTANT, variable } from "./module.js";

// "기본" export를 가져오는 단축입니다.
import theDefaultValue from "./module.js";

// 모든 export를 가지는 "네임스페이스 객체"를 가져옵니다.
import * as module from "./module.js";

module.fun();

ECMAScript 모듈에서 namespace 객체를 가져오면 webpack은 ESM 규칙에 따라 namespace 객체의 Symbol.toStringTag를 "Module"로 설정합니다.

Flagging modules as ESM

기본적으로 webpack은 파일이 ESM인지 또는 다른 모듈 시스템인지 자동으로 감지합니다.

Node.js는 package.json의 속성을 사용하여 파일의 모듈 유형을 명시적으로 설정하는 방법을 확립했습니다. package.json에서 "type": "module"을 설정하면 package.json 아래의 모든 파일이 ECMAScript 모듈이 됩니다. "type": "commonjs"를 설정하면 CommonJS 모듈이 됩니다.

{
  "type": "module"
}

또한, 파일은 .mjs 또는 .cjs 확장자를 사용해서 모듈 유형을 설정할 수 있습니다. .mjs는 ESM이 되도록 강제하고, .cjs는 CommonJS가 되도록 강제합니다.

DataURIs에서 text/javascript 또는 application/javascript mime 유형을 사용하면 모듈 유형도 ESM으로 강제로 적용됩니다.

모듈 형식뿐 아니라 모듈 플래그를 ESM으로 지정하는 것은 로직 해석, interop 로직 및 모듈에서 사용 가능한 심볼에 영향을 줍니다.

import.meta in ESM

Webpack은 ESM에서 사용할 수 있는 여러 import.meta 속성을 제공합니다.

속성설명
import.meta.url현재 모듈 파일의 URL로, new Worker()나 new URL()에 사용할 수 있습니다.
import.meta.webpackwebpack 메이저 버전 번호입니다. (예: 5)
import.meta.webpackHotmodule.hot와 동일한 기능으로, ESM에서 HMR에 사용할 수 있습니다.
import.meta.webpackContextrequire.context의 ESM 대응 API

예시 - 에셋에 import.meta.url 사용하기:

// 현재 모듈을 기준으로 같은 디렉터리의 파일 경로를 해석합니다.
const iconUrl = new URL("./icon.png", import.meta.url);
const img = document.createElement("img");
img.src = iconUrl.href;

예시 - ESM에서 HMR 사용하기:

if (import.meta.webpackHot) {
  import.meta.webpackHot.accept("./module.js", () => {
    // 업데이트를 처리합니다.
  });
}

Top-Level Await

ESM에서는 모듈 최상위 레벨에서 await를 사용할 수 있습니다. Webpack은 해당 모듈을 자동으로 비동기 모듈로 처리합니다. 이 기능은 5.83.0부터 기본 활성화되었고, experiments.topLevelAwait 옵션 자체는 5.102.0에서 제거되었습니다. 즉, 별도 설정 없이 동작합니다.

// user.js (비동기 ESM 모듈)
const response = await fetch("/api/user");

export const user = await response.json();
// index.js - 비동기 모듈 import도 기대한 대로 동작합니다.
import { user } from "./user.js";

console.log(user.name);

Fully Specified Imports

ESM의 import는 더 엄격하게 해석됩니다. 파일이 ESM으로 지정된 경우, Node.js 규칙에 따라 상대 경로 요청에는 파일 확장자(예: *.js, *.mjs)를 포함해야 합니다.

// 실패합니다. 확장자가 없습니다.
import { helper as missingExt } from "./utils";

// ESM에서 올바른 방식입니다.
import { helper } from "./utils.js";

대규모 CJS 코드베이스를 마이그레이션할 때처럼 이 검사를 비활성화해야 한다면 fullySpecified=false를 사용할 수 있습니다.

// webpack.config.js
export default {
  module: {
    rules: [
      {
        test: /\.m?js/,
        resolve: {
          fullySpecified: false,
        },
      },
    ],
  },
};

CommonJS Interop

ESM에서는 require, module, exports, __filename, __dirname 같은 CommonJS 구문을 사용할 수 없습니다.

webpack이 ESM에서 import된 CommonJS 모듈을 번들할 때, default import(전체 module.exports 객체)와 named import(해당 속성들) 모두 동작합니다.

// esm-consumer.js (ESM)
import cjs from "./cjs-module.js";
import { foo } from "./cjs-module.js";

// cjs-module.js (CommonJS)
module.exports = { foo: 1, bar: 2 };

console.log(cjs.foo); // 1 - cjs is the whole exports object
console.log(foo); // 1 - named imports read properties of module.exports

이 상호 운용성은 webpack이 import된 모듈을 CommonJS로 처리할 때 적용됩니다. 해당 모듈 자체가 ESM export 구문을 사용하면 webpack이 이를 자동으로 ESM으로 감지하여 export를 직접 사용합니다. 이는 "type": "module"이 설정된 프로젝트에서 .js 파일을 혼용할 때 자주 나타납니다. 프로젝트 내부 일부 파일은 ESM으로 처리되는 반면, node_modules의 서드파티 패키지는 여전히 CommonJS인 경우가 많기 때문입니다.

Dependencies that ship modern syntax

webpack은 디펜던시 코드를 찾은 그대로 번들합니다. target과 browserslist 설정은 webpack이 생성하는 런타임 코드를 제한하며, 사용자나 패키지가 작성한 문법을 다운레벨하지 않습니다. 따라서 화살표 함수, optional chaining, 클래스 필드로 게시된 패키지는 게시된 그대로 브라우저에 도달하며, 구형 브라우저는 패키지가 아닌 번들을 가리키는 오류와 함께 파싱 시점에 실패합니다.

이는 exports 필드를 리졸브하는 webpack 5와 함께 더 일반적이 되었습니다. 레거시 빌드와 모던 빌드를 모두 제공하는 패키지는 이제 기본적으로 webpack에 모던 빌드를 전달합니다.

해결 방법은 두 가지이며, 상호 배타적이지 않습니다.

디펜던시를 트랜스파일합니다. 이 사이트의 빌드 성능 페이지를 포함한 가이드들은 node_modules에서 로더를 제외하도록 권장합니다. 이것은 코드의 대부분이고 변환하는 데 가장 느린 부분이므로 올바른 기본값입니다. 그 규칙을 제거하는 대신, 필요한 패키지만 예외로 추가하세요.

import path from "node:path";
import { fileURLToPath } from "node:url";

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

export default {
  module: {
    rules: [
      {
        test: /\.m?js$/,
        include: [
          path.resolve(__dirname, "src"),
          // plus the dependencies that are not ES5
          /node_modules[\\/](pkg-a|pkg-b)[\\/]/,
        ],
        loader: "babel-loader",
      },
    ],
  },
};

조건 문법은 이것을 반대 방향으로도 표현할 수 있습니다. exclude: { and: [/node_modules/], not: [/node_modules[\\/]pkg-a[\\/]/] }는 하나의 패키지를 제외하고 node_modules를 건너뜁니다.

또는 다른 빌드를 리졸브합니다. 패키지가 다른 조건이나 필드 아래 ES5 번들을 여전히 게시하고 있다면, 트랜스파일하는 대신 webpack이 그것을 가리키도록 하세요. resolve.conditionNames와 resolve.mainFields를 참조하세요. 두 가지 모두 webpack이 리졸브하는 모든 패키지에 적용되므로, 전역적으로 변경하는 대신 resolve.byDependency나 패키지별 Rule.resolve로 범위를 좁히세요.

Common Migration Errors

ReferenceError: require is not defined

파일이 ESM으로 처리되면 CommonJS 전역 객체인 require, module, exports, __filename, __dirname를 사용할 수 없습니다.

해결: require()를 import 구문으로 바꾸세요. 조건부 또는 동적 로딩이 필요하다면 import()를 사용하세요.


Must use import to load ES Module (Node.js) / SyntaxError: Cannot use import statement in a module (browser)

이 오류는 ESM import/export 구문을 사용하는 파일이 ESM으로 지정되지 않았을 때 발생합니다. package.json에 "type": "module"이 없거나, 파일 확장자가 .mjs가 아니라 .js인 경우가 대표적입니다.

해결: package.json에 "type": "module"을 추가하거나 파일 확장자를 .mjs로 변경하세요.


Module not found: Error: Can't resolve './utils' (missing extension)

ESM에서는 상대 import에 파일 확장자를 포함해야 합니다. Webpack도 여기서는 Node.js의 ESM 규칙을 따릅니다.

해결: import { helper } from './utils'를 import { helper } from './utils.js'로 바꾸거나, 마이그레이션하는 동안에는 webpack 설정에서 fullySpecified: false를 지정해 이 검사를 끌 수 있습니다.

Edit this page·
« Previous
Lazy Loading
Next »
Shimming

2 Contributors

sokraryzrr

Translators