모듈 페더레이션 타입 힌트와 로깅

타입 힌트로 더 안전하게

이번 새로운 모듈 페더레이션은 타입 힌트 제공이라는 편의를 제공한다.

원격 모듈을 사용하는 애플리케이션을 흔히 호스트(또는 소비자)라고 부른다. 만약 호스트를 타입스크립트로 개발한다면, 원격 모듈의 타입 정보를 제공받는 것은 큰 장점이 된다. 이번 모듈 페더레이션은 원격 모듈과 호스트 간에 타입 정보를 동기화하여, 더 안전하고 예측 가능한 개발 환경을 제공한다.

플러그인 구조

새로운 모듈 페더레이션의 또 다른 특징은 특정 번들러에 국한되지 않는 크로스 번들러 지원이다. 기존에는 웹팩에서만 사용할 수 있었지만, 이제는 Vite, Rspack 그리고 Nest.js 프레임워크의 빌드 시스템까지 지원한다.

웹팩의 경우, 이 모듈 페더레이션을 플러그인 형태로 제공하는데 구조를 살펴보았다. @module-federation/enhanced 패키지에서 재공하는 ModuleFederationPlugin이다.

class ModuleFederationPlugin {
  apply() {
    new RemoteEntryPLugin()
    new FederatioModulePlugin()
    new StartupChunkDependanciesPlugin()
    new DtsPlugin() // 타입 힌트 처리
    new PrefetchPlugin()
    new FederationRuntimePlugin()
  }

  afterPlugin() {
    new ContainerPlugin()
    new ContainerReferencepPlugin()
    new SharePlugin()
    new StatsPlugin()
  }
}

옵션에 따라 최대 10개의 하위 플러그인 조합으로 구성한다.

DtsPlugin과 타입 힌트 처리

플러그인을 사용할 때 PluginDtsOptions 형태의 dts 옵션을 전달할 수 있는데 이걸 처리하는게 DtsPlugin 이다. 타입스크립트 타입 정의 파일인 .d.ts 확장자에서 이름을 가져온 듯 하다.

class DtsPlugin {
  apply() {
    new DevPlugin() // 개발 환경 지원
    new TypesPlugin() // 타입 힌트 지원
  }
}

DtsPlugin은 다시 두 개의 플러그인을 조합하여 작동한다.

개발 환경을 위한 DevPlugin은 원격 모듈 타입이 갱신되면 이 플러그인이 자동으로 최신 타입으로 동기화 시켜 준다고 주석에 기록되어있다.

only consume once , if remotes update types , DevPlugin will auto sync the latest types

TypesPlugin이 타입스크립트 타입 힌트를 지원하는 플러그인이다.

class TypesPlugin {
  apply() {
    new GenerateTypesPlugin()
    new ConsumeTypesPlugin() // 타입 힌트 지원
  }
}

두 개의 하위 플러그인을 조합하여 작동한다.

  • GenerateTypesPlugin: 원격 모듈의 타입 파일을 생성.
  • ConsumeTypesPlugin: 생성된 타입 파일을 호스트에서 사용 가능하도록 처리.

호스트에서 원격 모듈의 타입을 제공하려면 원격 서버에서 타입 파일을 미리 준비해야한다. TypesPlugin의 GenerateTypesPlugin이 타입을 만드는 역할을 한다.

한편 ConsumeTypesPlugin은 원격에 만들어 둔 타입파일을 호스트에 다운로드한 뒤 개발 환경에 타입 힌트 기능을 지원하는 역할이다.

원격 타입 가져오기 과정

웹팩 플러그인 조합은 ConsumeTypesPlugin에서 끝난다. 이 플러그인은 실제로 원격의 타입을 다운로드하는 역할을 하는 전용 클래스 DtsManger로 인스턴스를 만들고 타입을 가져온다.

class ConsumeTypesPlugin {
  apply() {
    consumeTypes()
  }
}

function consumeTypes() {
  new DtsManager().consumeTypes()
}

이 후에는 DtsManger라는 전용 객체가 주도한다.

class DtsManager {
  consumeTypes() {
    // 1. manifest.json 요청
    // 2. 타입 파일 압축본 다운로드
  }
}
  • manifest.json 요청: 원격 서버에서 타입 메타 정보를 가져옴.
  • 타입 파일 다운로드: 압축본을 다운로드하여 타입 파일로 변환.

터미널에 로깅하기

DtsManger는 일련의 과정을 log4js 라이브러리를 이용해 .mf/typesGenerate.log 파일에 기록한다. 이 파일을 감시하면 로그를 터미널에 출력할 수 있겠다.

$ tail -f .mf/typesGenerate.log

logFile함수는 환경 변수 FEDERATION_DEBUG 에 따라 로그를 기록한다. 플러그인을 실행할 때 이 환경변수를 켜면 모듈 페더레이션의 동작을 더 자세히 관찰할 수 있다.

$ FEDERATION_DEBUG=true webpack

아예 커스텀 플러그인을 만들어도 되겠다.

class ModuleFederationConsoleLogPlugin {
  #logFile = path.join(process.cwd(), ".mf/typesGenerate.log")
  #name = "ModuleFederationConsoleLogPlugin"

  apply(compiler) {
    // 로그 파일 비우기
    compiler.hooks.watchRun.tap(this.#name, () => {
      fs.truncateSync(this.#logFile, 0)
    })

    // 로그 파일 검사, 출력
    compiler.hooks.done.tap(this.#name, () => {
      if (!fs.existsSync(this.#logFile)) {
        return
      }
      if (fs.statSync(this.#logFile).size === 0) {
        return
      }

      const log = fs.readFileSync(this.#logFile, "utf8")
      if (!log) {
        return
      }

      console.log(log)
    })
  }
}

결론

새로운 모듈 페더레이션은 타입스크립트 타입 힌트 기능을 지원해 더 안전한 개발 환경을 제공한다.

처음엔 원격 모듈을 가져오는데 성공했는지 실패했는지 알기 어려웠다. 특정 환경 변수에 따라 파일에 기록한다는 것을 알았다.

참고