Skip to main content

Command Palette

Search for a command to run...

작업 기록: Turbopack에 cjs 지원 개선

Updated
3 min read

PR: https://github.com/vercel/turbo/pull/3111

터보팩 구조를 잘 몰라서 얼마나 걸릴지 잘 모르는 상태였지만 뭐... 팀 프로젝트니까 해야지.


그래서 작업을 시작했다. 근데 난 Turbopack을 전혀 몰랐다. 내가 고쳐야할 코드가 어딨는지는 둘째치고 터보팩의 Value, ValueCell 같은 게 무슨 역할인지도 몰랐다. 문서야 있지만 내가 문서를 읽는 걸 좋아하지 않는 관계로 건너뛰고 대충 폴더 열어보면서 테스트를 찾아봤더. 테스트가 존재할만한 폴더들을 하나씩 열어보다가 turbopack-tests이라는 폴더를 찾았다. 내가 원하는 테스트 케이스 추가히고... cargo test 돌려보니까 내가 추가한 케이스가 실패하더라. 그 말은 제대로 추가한 게 맞는 얘기다. 근데 업데이트가 안 되길래 UPDATE=1 cargo test를 해봤다. swc에서 쓰는 패턴인데, 터보팩에서 쓰는지는 모르곘지만 swc에서 옛날부터 쓰던 패턴이라 당연히 가져갔겠지 싶어서 해봤다. 잘 업데이트 됐다. 여기까지가 테스트 시스템 파악이다.


그 다음에는 테스트 결과로 출력된 에러 메시지중에서 which has no exports를 뽑아와서 그걸로 검색해봤다.

EcmascriptExports::None => AnalyzeIssue {
    code: None,
    category: StringVc::cell("analyze".to_string()),
    message: StringVc::cell(format!(
        "export * used with module {} which has no exports\nTypescript only: Did you \
            want to import only types with `export type * from \"...\"`?",
        asset.path().to_string().await?
    )),
    path: asset.path(),
    severity: IssueSeverity::Warning.into(),
    source: None,
    title: StringVc::cell("unexpected export *".to_string()),
}
.cell()
.as_issue()
.emit(),

검색 결과에서 나온 코드EcmascriptExports::None인 경우에 실행되는 코드이므로, EcmascriptExports::None로 검색해봐서 해당 Variant를 만드는 코드를 찾았다.

let exports = if !esm_exports.is_empty() || !esm_star_exports.is_empty() {
    let esm_exports: EsmExportsVc = EsmExports {
        exports: esm_exports,
        star_exports: esm_star_exports,
    }
    .into();
    analysis.add_code_gen(esm_exports);
    EcmascriptExports::EsmExports(esm_exports)
} else if let Program::Module(_) = program {
    EcmascriptExports::None
} else {
    EcmascriptExports::CommonJs
};

EcmascriptExports::CommonJs가 사용되어야할 것 같은데, 탐지 코드가 없는 것 같더라. 그리고 위에 보니까 .add_code_gen이 있는데 그걸 사용하면 출력물에 무언가를 추가할 수 있는 것 같아서 EsmExports를 찾아봤다. 해당 타입의 코드를 보니까 내가 찍은 게 맞는 것 같더라.

처음엔 cjs 모듈에 무언가를 추가해야할 것 같아서 CjsExports 타입을 추가하고 EcmascriptExports::CommonJsEcmascriptExports::CommonJs(CjsExportsVc)로 변경했었다. 근데 하고나서 보니까 cjs 모듈에 코드를 추가하는 게 아니고 cjs 모듈을 불러온 ES 모듈에 코드를 추가해야하는 것이더라. 그래서 ESM 처리 코드를 봤다. 우선

let stmt = quote!("__turbopack_esm__($getters);" as Stmt,
    getters: Expr = getters.clone()
);

를 보니까 어떻게 돌아가는지 알 것 같더라. 출력물에 __turbopack_esm__가 존재했는데, 거기에 무언가를 추가하면 될 것 같았다. expand_star_exports를 보니까 출력물에 존재하는 Id들이나 문자열들이 어디서 오는지는 대략 알겠더라.


근데 그냥 추가하면 다른 테스트 파일들도 다 바뀔 것 같아서 간단하게 cjs 탐지코드를 넣었다.

fn has_cjs_export(p: &Program) -> bool {
    use swc_core::ecma::visit::{visit_obj_and_computed, Visit, VisitWith};

    struct Visitor {
        found: bool,
    }

    impl Visit for Visitor {
        visit_obj_and_computed!();

        fn visit_ident(&mut self, i: &Ident) {
            if &*i.sym == "module" {
                self.found = true;
            }
        }
    }

    let mut v = Visitor { found: false };
    p.visit_with(&mut v);
    v.found

이름 고민하기 귀찮아서 그냥 함수 안에다가 타입을 정의해놨다.


처음엔 Spread 원소들을 넣었는데, 그러니까 __turbopack__esm__이 import보다 먼저 출력되어서 런타임에 깨지는 문제가 있었다. 그래서 해도 되는지 물어본 뒤에 런타임에 __turbopack__cjs__를 runtime.js에 추가하고 그걸 호출하도록 변경했다. 삽입 위치는 임포트가 처리된 직후로 하라고해서 그렇게 구현했다. 스프레드를 쓸지 lazy를 쓸지도 물어봤는데 import 직후에 있으면 spread 써도 된다고 하더라. 그래서 작업하고 코드 정리한 뒤 PR 보냈다.

More from this blog

한국의 학벌에 대한 생각

내 블로그의 제목이 kdy1: The way I think 인만큼 앞으로는 내 생각을 더 자주 올리려고 한다. 한국 기준으로, 학벌은 사람을 볼 때 꽤나 유용한 지표이지만, 절대적이지는 않다. 경험적인 얘기일 뿐이지만, 성균관대학교 자퇴생으로서 느낀 것들이 몇 가지 있다. 대학까지 간 사람의 학벌은 학습 능력 x 성실함 에 대체로 비례한다. 그래서 의미가

Apr 3, 20261 min read

인간 지능에 대한 메모장

최종 업데이트: 2026/03/15 지능의 유전 현재 인류 기준으로, 고지능자는 고지능 유전자가 많이 겹친 사람이다. 지능의 유전엔 X 염색체가 매우 중요한 역할을 한다. 그리고 이게 남자와 여자의 지능 분포 차이를 만든다. 극상위권에 여자가 거의 없는 이유가 이것이다. 고지능 X 염색체가 여자한테서 발현되려면 2개가 있어야 한다. 이는 인간의 생

Mar 15, 20262 min read

Ai 코딩 팁 2 (한국어)

발표 자료: https://gamma.app/docs/AI--2a52e7tk3eb1ch1 AI 활용법 관련해서 간단하게 발표를 했다. 발표 자료 앞쪽은 전에 블로그에 올린 글이랑 같은 내용이다. 이 글에서는 기존 글에서 다루지 않은 내용들을 다루겠다. 에러 메시지 및 로깅 구체적 타입 및 스키마 활용 any 타입은 사람에게도 위험하지만, AI에게는 더 위험하다. 마찬가지로, JSON.parse처럼 아무 제약 없는 파싱 느슨한 인터페이스 ...

Jan 30, 20265 min read

kdy1: The way I think

233 posts