Skip to main content

Command Palette

Search for a command to run...

2024년 5월 10일 작업일지

Updated
7 min read

터보팩 Tree shaking PR

어제 하던 작업에 이어서 로그를 추가한 뒤 돌려봤다.

        "./_/_interop_require_wildcard": Conditional(
            [
                (
                    "import",
                    Result(
                        "./esm/_interop_require_wildcard.js",
                    ),
                ),
                (
                    "default",
                    Result(
                        "./cjs/_interop_require_wildcard.cjs",
                    ),
                ),
            ],
        ),
[turbo/crates/turbopack-core/src/resolve/mod.rs:2262] handle_exports_imports_field(package_path, package_json_path, options,
            exports_field, &path, conditions, unspecified_conditions,
            query).await? = ResolveResult {
    primary: {
        RequestKey {
            request: Some(
                "./_/_interop_require_wildcard",
            ),
            conditions: {},
        }: Source(
            FileSource {
                path: FileSystemPath {
                    fs: DiskFileSystem {
                        name: "project",
                        root: "/Users/kdy1/projects/app-playground",
                    },
                    path: "node_modules/.pnpm/@swc+helpers@0.5.11/node_modules/@swc/helpers/cjs/_interop_require_wildcard.cjs",
                },
                query: "",
            },
        ),
    },
    affecting_sources: [
        FileSource {
            path: FileSystemPath {
                fs: DiskFileSystem {
                    name: "project",
                    root: "/Users/kdy1/projects/app-playground",
                },
                path: "node_modules/.pnpm/@swc+helpers@0.5.11/node_modules/@swc/helpers/package.json",
            },
            query: "",
        },
    ],
}

./_/_interop_require_wildcard가 포함된 로그 메시지들이다. 문제가 없어보여서 다른 부분을 먼저 보기로 했다.

const _interop_require_default = __turbopack_require__("[project]/node_modules/.pnpm/@swc+helpers@0.5.11/node_modules/@swc/helpers/cjs/_interop_require_default.cjs [ssr] (ecmascript) <facade>");
const _modernbrowserslisttarget = /*#__PURE__*/ _interop_require_default._(__turbopack_require__("[project]/node_modules/.pnpm/file+..+nextpack+tarballs+next.tar_react-dom@18.2.0_react@18.2.0/node_modules/next/dist/shared/lib/modern-browserslist-target.js [ssr] (ecmascript) <facade>"));

생성된 코드를 봤는데, 뭔가 문제가 있었다.

"[project]/node_modules/.pnpm/@swc+helpers@0.5.11/node_modules/@swc/helpers/cjs/_interop_require_default.cjs [ssr] (ecmascript) <facade>": (({ r: __turbopack_require__, f: __turbopack_module_context__, i: __turbopack_import__, s: __turbopack_esm__, v: __turbopack_export_value__, n: __turbopack_export_namespace__, c: __turbopack_cache__, M: __turbopack_modules__, l: __turbopack_load__, j: __turbopack_dynamic__, P: __turbopack_resolve_absolute_path__, U: __turbopack_relative_url__, R: __turbopack_resolve_module_id_path__, g: global, __dirname, x: __turbopack_external_require__, y: __turbopack_external_import__ }) => (() => {
"use strict";

__turbopack_esm__({});
var __TURBOPACK__imported__module__$5b$project$5d2f$node_modules$2f2e$pnpm$2f40$swc$2b$helpers$40$0$2e$5$2e$11$2f$node_modules$2f40$swc$2f$helpers$2f$cjs$2f$_interop_require_default$2e$cjs__$5b$ssr$5d$__$28$ecmascript$29$__$3c$module__evaluation$3e$__ = __turbopack_import__("[project]/node_modules/.pnpm/@swc+helpers@0.5.11/node_modules/@swc/helpers/cjs/_interop_require_default.cjs [ssr] (ecmascript) <module evaluation>");
"__TURBOPACK__ecmascript__hoisting__location__";
;
;

})()),

<facade><exports>를 reexport 하지 않는 게 문제인 것 같았다.

                    // We can't use quote! as `with` is not standard yet
                    let chunk_prop = create_turbopack_part_id_assert(PartId::ModuleEvaluation);

                    module
                        .body
                        .push(ModuleItem::ModuleDecl(ModuleDecl::ExportAll(ExportAll {
                            span: DUMMY_SP,
                            src: Box::new(TURBOPACK_PART_IMPORT_SOURCE.into()),
                            type_only: false,
                            with: Some(Box::new(chunk_prop)),
                        })));

                    // We can't use quote! as `with` is not standard yet
                    let chunk_prop = create_turbopack_part_id_assert(PartId::Exports);

                    module
                        .body
                        .push(ModuleItem::ModuleDecl(ModuleDecl::ExportAll(ExportAll {
                            span: DUMMY_SP,
                            src: Box::new(TURBOPACK_PART_IMPORT_SOURCE.into()),
                            type_only: false,
                            with: Some(Box::new(chunk_prop)),
                        })));

그래서 관련된 코드를 봤더니 export * from "<exports>"는 정상적으로 생기고 있었다. 그래서 잠깐 고민하다가 CommonJS 모듈에 대해서는 module splitting을 하지 않게 바꾸기로 결정했다.


#[turbo_tasks::value(shared, serialization = "none", eq = "manual")]
pub(crate) enum SplitResult {
    Ok {
        asset_ident: Vc<AssetIdent>,

        /// `u32` is a index to `modules`.
        #[turbo_tasks(trace_ignore)]
        entrypoints: FxHashMap<Key, u32>,

        #[turbo_tasks(debug_ignore, trace_ignore)]
        modules: Vec<Vc<ParseResult>>,

        #[turbo_tasks(trace_ignore)]
        deps: FxHashMap<u32, Vec<u32>>,
    },
    Failed {
        parse_result: Vc<ParseResult>,
    },
}

이를 위해, SplitResult의 타입 정의를 위처럼 변경했다.

            // If the script file is a common js file, we cannot split the module
            if cjs_finder::contains_cjs(program) {
                return Ok(SplitResult::Failed {
                    parse_result: parsed,
                }
                .cell());
            }

그리고 common js 모듈을 탐지하는 로직을 넣었다. 그러고나서 로그를 보다보니까 SplitResult::Failed 인 경우 의존성이 로딩이 안 되는 것 같았다.




        let split_data = split_module(self.full_module).await?;

        let analyze = analyze(self.full_module, self.part).await?;

        let (deps, entrypoints) = match &*split_data {
            SplitResult::Ok {
                deps, entrypoints, ..
            } => (deps, entrypoints),
            SplitResult::Failed { .. } => return Ok(analyze.references),
        };

그래서 관련된 코드를 변경했다. 원래는 SplitResult::Failed { .. } 브랜치에서 Ok(Vc::cell(vec![]))를 반환했었다.

 ✓ Compiled /_error in 443ms
 ⨯ ReferenceError: RouteKind is not defined
    at /Users/kdy1/projects/app-playground/.next/server/chunks/ssr/node_modules__pnpm_0cbf07._.js:71:350
    at [project]/node_modules/.pnpm/file+..+nextpack+tarballs+next.tar_react-dom@18.2.0_react@18.2.0/node_modules/next/dist/esm/server/future/route-kind.js [ssr] (ecmascript) <internal part 3> (/Users/kdy1/projects/app-playground/.next/server/chunks/ssr/node_modules__pnpm_0cbf07._.js:73:3)

그랬더니 에러 메시지가 달라졌다. 저 파일을 보면

"[project]/node_modules/.pnpm/file+..+nextpack+tarballs+next.tar_react-dom@18.2.0_react@18.2.0/node_modules/next/dist/esm/server/future/route-kind.js [ssr] (ecmascript) <internal part 3>": (({ r: __turbopack_require__, f: __turbopack_module_context__, i: __turbopack_import__, s: __turbopack_esm__, v: __turbopack_export_value__, n: __turbopack_export_namespace__, c: __turbopack_cache__, M: __turbopack_modules__, l: __turbopack_load__, j: __turbopack_dynamic__, P: __turbopack_resolve_absolute_path__, U: __turbopack_relative_url__, R: __turbopack_resolve_module_id_path__, g: global, __dirname, x: __turbopack_external_require__, y: __turbopack_external_import__ }) => (() => {
"use strict";

__turbopack_esm__({});
var __TURBOPACK__imported__module__$5b$project$5d2f$node_modules$2f2e$pnpm$2f$file$2b2e2e2b$nextpack$2b$tarballs$2b$next$2e$tar_react$2d$dom$40$18$2e$2$2e$0_react$40$18$2e$2$2e$0$2f$node_modules$2f$next$2f$dist$2f$esm$2f$server$2f$future$2f$route$2d$kind$2e$js__$5b$ssr$5d$__$28$ecmascript$29$__$3c$module__evaluation$3e$__ = __turbopack_import__("[project]/node_modules/.pnpm/file+..+nextpack+tarballs+next.tar_react-dom@18.2.0_react@18.2.0/node_modules/next/dist/esm/server/future/route-kind.js [ssr] (ecmascript) <module evaluation>");
var __TURBOPACK__imported__module__$5b$project$5d2f$node_modules$2f2e$pnpm$2f$file$2b2e2e2b$nextpack$2b$tarballs$2b$next$2e$tar_react$2d$dom$40$18$2e$2$2e$0_react$40$18$2e$2$2e$0$2f$node_modules$2f$next$2f$dist$2f$esm$2f$server$2f$future$2f$route$2d$kind$2e$js__$5b$ssr$5d$__$28$ecmascript$29$__$3c$internal__part__2$3e$__ = __turbopack_import__("[project]/node_modules/.pnpm/file+..+nextpack+tarballs+next.tar_react-dom@18.2.0_react@18.2.0/node_modules/next/dist/esm/server/future/route-kind.js [ssr] (ecmascript) <internal part 2>");
"__TURBOPACK__ecmascript__hoisting__location__";
;
(function(RouteKind1) {
    /**
   * `PAGES` represents all the React pages that are under `pages/`.
   */ RouteKind1["PAGES"] = "PAGES";
    /**
   * `PAGES_API` represents all the API routes under `pages/api/`.
   */ RouteKind1["PAGES_API"] = "PAGES_API";
    /**
   * `APP_PAGE` represents all the React pages that are under `app/` with the
   * filename of `page.{j,t}s{,x}`.
   */ RouteKind1["APP_PAGE"] = "APP_PAGE";
    /**
   * `APP_ROUTE` represents all the API routes and metadata routes that are under `app/` with the
   * filename of `route.{j,t}s{,x}`.
   */ RouteKind1["APP_ROUTE"] = "APP_ROUTE";
})(__TURBOPACK__imported__module__$5b$project$5d2f$node_modules$2f2e$pnpm$2f$file$2b2e2e2b$nextpack$2b$tarballs$2b$next$2e$tar_react$2d$dom$40$18$2e$2$2e$0_react$40$18$2e$2$2e$0$2f$node_modules$2f$next$2f$dist$2f$esm$2f$server$2f$future$2f$route$2d$kind$2e$js__$5b$ssr$5d$__$28$ecmascript$29$__$3c$internal__part__2$3e$__["RouteKind"] || (RouteKind = {})); //# sourceMappingURL=route-kind.js.map

})()),
"[project]/node_modules/.pnpm/file+..+nextpack+tarballs+next.tar_react-dom@18.2.0_react@18.2.0/node_modules/next/dist/esm/server/future/route-kind.js [ssr] (ecmascript) <export RouteKind>": (({ r: __turbopack_require__, f: __turbopack_module_context__, i: __turbopack_import__, s: __turbopack_esm__, v: __turbopack_export_value__, n: __turbopack_export_namespace__, c: __turbopack_cache__, M: __turbopack_modules__, l: __turbopack_load__, j: __turbopack_dynamic__, P: __turbopack_resolve_absolute_path__, U: __turbopack_relative_url__, R: __turbopack_resolve_module_id_path__, g: global, __dirname, x: __turbopack_external_require__, y: __turbopack_external_import__ }) => (() => {
"use strict";

__turbopack_esm__({
    "RouteKind": ()=>__TURBOPACK__imported__module__$5b$project$5d2f$node_modules$2f2e$pnpm$2f$file$2b2e2e2b$nextpack$2b$tarballs$2b$next$2e$tar_react$2d$dom$40$18$2e$2$2e$0_react$40$18$2e$2$2e$0$2f$node_modules$2f$next$2f$dist$2f$esm$2f$server$2f$future$2f$route$2d$kind$2e$js__$5b$ssr$5d$__$28$ecmascript$29$__$3c$internal__part__3$3e$__["RouteKind"]
});
var __TURBOPACK__imported__module__$5b$project$5d2f$node_modules$2f2e$pnpm$2f$file$2b2e2e2b$nextpack$2b$tarballs$2b$next$2e$tar_react$2d$dom$40$18$2e$2$2e$0_react$40$18$2e$2$2e$0$2f$node_modules$2f$next$2f$dist$2f$esm$2f$server$2f$future$2f$route$2d$kind$2e$js__$5b$ssr$5d$__$28$ecmascript$29$__$3c$module__evaluation$3e$__ = __turbopack_import__("[project]/node_modules/.pnpm/file+..+nextpack+tarballs+next.tar_react-dom@18.2.0_react@18.2.0/node_modules/next/dist/esm/server/future/route-kind.js [ssr] (ecmascript) <module evaluation>");
var __TURBOPACK__imported__module__$5b$project$5d2f$node_modules$2f2e$pnpm$2f$file$2b2e2e2b$nextpack$2b$tarballs$2b$next$2e$tar_react$2d$dom$40$18$2e$2$2e$0_react$40$18$2e$2$2e$0$2f$node_modules$2f$next$2f$dist$2f$esm$2f$server$2f$future$2f$route$2d$kind$2e$js__$5b$ssr$5d$__$28$ecmascript$29$__$3c$internal__part__3$3e$__ = __turbopack_import__("[project]/node_modules/.pnpm/file+..+nextpack+tarballs+next.tar_react-dom@18.2.0_react@18.2.0/node_modules/next/dist/esm/server/future/route-kind.js [ssr] (ecmascript) <internal part 3>");
"__TURBOPACK__ecmascript__hoisting__location__";
;
;

})()),

생성된 코드를 보니까 타입스크립트 enum 초기화 코드중 일부가 Import 바인딩으로 바뀌어서 생성된 코드 같았다.


(function (RouteKind){})(RouteKind || RouteKind = {})

원본은 아마 위와 같을 것이다. Import binding을 치환하는 코드가 LHS에 바인딩이 있는 경우 (위 코드에서 RouteKind = {})를 처리 못하는 것 같아서 관련 코드를 찾아봤다.

imported__module 이 제일 특이한 코드니까 이것으로 검색했는데, 없길래 이것이 magic identifier 일 것이라고 판단하고 imported module 으로 검색해서 관련된 코드를 찾았다.

그리고 Expr::Ident를 처리하는 로직 옆에 SimpleAssignTarget::Ident 를 처리하는 로직을 넣었다.

근데 작동 안해서 여러가지를 시도했다. 그러다가 깨달은 게 있는데, ast_path 에선 BindingIdent를 건너뛴다던가 같은 건 하면 안 된다는 것...

그래서 코드가 살짝 더러워졌다. 한참 삽질하다가 어거지로 돌려보니까

 ○ Compiling /_error ...
 ✓ Compiled /_error in 511ms
 ⨯ TypeError: Cannot set property RouteKind of #<Object> which has only a getter
    at /Users/kdy1/projects/app-playground/.next/server/chunks/ssr/node_modules__pnpm_0cbf07._.js:71:672
    at [project]/node_modules/.pnpm/file+..+nextpack+tarballs+next.tar_react-dom@18.2.0_react@18.2.0/node_modules/next/dist/esm/server/future/route-kind.js [ssr] (ecmascript) <internal part 3> (/Users/kdy1/projects/app-playground/.next/server/chunks/ssr/node_modules__pnpm_0cbf07._.js:73:3)

같은 에러가 떴다. 모듈의 모든 프로퍼티가 readonly라서 발생하는 이슈인데, 이러면 import 결과물을 변수에 저장했다가 사용해야한다.

처음엔 const 변수를 만들려고 했는데 중복 identifier 오류가 떠서 변수를 유일하게 만드는 대신 그냥 var을 써버렸다. 냄새가 나는 코드긴 하지만 어쨌든 나한테 중요한 건 tree shaking 패스 버그 수정이니까 그냥 무시했다.

근데 변수명이 겹쳐서 자기 자신을 참조하는 이슈가 있어서 __binding 을 붙였다.

근데 알고보니 insert 위치 문제였어서 insert_hoisted_stmt을 사용했다.

그런데 여전히 SimpleAssignTarget 을 위한 코드가 실행되지 않아서 필살기를 사용했다.

Debug info:
- An error occurred while generating the chunk item [project]/node_modules/.pnpm/file+..+nextpack+tarballs+next.tar_react-dom@18.2.0_react@18.2.0/node_modules/next/dist/esm/server/future/route-kind.js [ssr] (ecmascript) <internal part 3>
- Execution of EcmascriptChunkItemContent::module_factory failed
- Execution of EcmascriptChunkItemContent::new failed
- A task panicked: explicit panic
    at [project]/node_modules/.pnpm/file+..+nextpack+tarballs+next.tar_react-dom@18.2.0_react@18.2.0/node_modules/next/dist/esm/server/future/route-kind.js [ssr] (ecmascript) <internal part 3> (/Users/kdy1/projects/app-playground/.next/server/chunks/ssr/node_modules__pnpm_0cbf07._.js:50:7)

하지만 통하지 않았다.

                        if let Some(binding) = &binding {
                            if binding.sym.contains("RouteKind") {
                                visitors.push(
                                    create_visitor!([], visit_mut_simple_assign_target(l: &mut SimpleAssignTarget) {
                                        dbg!(&l);
                                    }),
                                );
                            }
                        }

그래서 panic! 은 지우고 모듈 내의 모든 SimpleAssignTarget을 찍어보기로 했다.

[turbo/crates/turbopack-ecmascript/src/references/esm/binding.rs:196:41] &l = Member(
    MemberExpr {
        span: 591..613#0,
        obj: Ident(
            Ident {
                span: 591..600#3,
                sym: "RouteKind",
                optional: false,
            },
        ),
        prop: Computed(
            ComputedPropName {
                span: 600..613#0,
                expr: Lit(
                    Str(
                        Str {
                            span: 601..612#0,
                            value: "APP_ROUTE",
                            raw: Some(
                                "\"APP_ROUTE\"",
                            ),
                        },
                    ),
                ),
            },
        ),
    },
)
[turbo/crates/turbopack-ecmascript/src/references/esm/binding.rs:196:41] &l = Ident(
    BindingIdent {
        id: Ident {
            span: 646..655#2,
            sym: "RouteKind",
            optional: false,
        },
        type_ann: None,
    },
)

값이 찍혔고, 모듈 안에 존재하긴 한다는 걸 알게 됐다. ApplyVisitors 의 로직을 뜯어보기로 했는데, 로직이 복잡해보여서 무지성으로 로그찍기로 했다.


    #[inline(never)]
    fn visit_if_required<N>(&mut self, n: &mut N, ast_path: &mut AstKindPath<AstParentKind>)
    where
        N: for<'aa> VisitMutWith<dyn VisitMut + Send + Sync + 'aa>
            + for<'aa, 'bb> VisitMutWithPath<ApplyVisitors<'aa, 'bb>>,
    {
        let mut index = self.index;
        let mut current_visitors = self.visitors.as_ref();
        while index < ast_path.len() {
            dbg!(index, ast_path.len());

            let current = index == ast_path.len() - 1;
            dbg!(current);
            let kind = ast_path[index];
            dbg!(kind);
            if let Some(visitors) = find_range(current_visitors, &kind, index) {
                // visitors contains all items that match kind at index. Some of them terminate
                // here, some need furth visiting. The terminating items are at the start due to
                // sorting of the list.
                index += 1;

                // skip items that terminate here
                let nested_visitors_start =
                    visitors.partition_point(|(path, _)| path.len() == index);
                dbg!(nested_visitors_start, visitors.len());
                if current {
                    // Potentially skip visiting this sub tree
                    if nested_visitors_start < visitors.len() {
                        n.visit_mut_children_with_path(
                            &mut ApplyVisitors {
                                // We only select visitors starting from `nested_visitors_start`
                                // which maintains the invariant.
                                visitors: Cow::Borrowed(&visitors[nested_visitors_start..]),
                                index,
                            },
                            ast_path,
                        );
                    }
                    dbg!(visitors[..nested_visitors_start].len());
                    for (_, visitor) in visitors[..nested_visitors_start].iter() {
                        n.visit_mut_with(&mut visitor.create());
                    }
                    return;
                } else {
                    // `current_visitors` has the invariant that is must not be empty.
                    // When it becomes empty, we must early exit
                    current_visitors = &visitors[nested_visitors_start..];
                    dbg!(current_visitors.is_empty());
                    if current_visitors.is_empty() {
                        // Nothing to do in this subtree, skip it
                        return;
                    }
                }
            } else {
                // Skip visiting this sub tree
                return;
            }
        }
        // Ast path is unchanged, just keep visiting
        n.visit_mut_children_with_path(self, ast_path);
    }

로그를 읽기는 어렵겠지만 코드만 보고 디버깅하는 것보단 값 찍어보고 어느 코드가 실행되는지 보는 것이 훨씬 쉽다. 이 작업은 피로도가 꽤 높은 작업이라 오늘은 여기까지...

SWC 플러그인 테스트 정리

터보팩 PR 작업을 오래 했더니 피곤해서 쉬운 작업들을 하기로 했다. next#64890 을 고치려고 플러그인 레포를 vscode에서 열었는데 테스트 폴더가 camelCase인 것이 마음에 들지 않았다. 그래서 잠시 머리도 식힐 겸 테스트 정리부터 해서 PR로 만들었다.

SWC relay 플러그인 Config파싱 개선

next#64890 을 고치려고 봤더니 Wasm 플러그인이 serde_json::Value를 사용해서 설정을 파싱하길래 이를 #[derive(Deserialize)] 를 사용하게 변경했다.

next-swc : 큰 앱에서 터지는 버그 수정

swc_common::SourceMap 의 설계 미스로 인해 next-swc 가 터지는 버그인데, 해결은 간단하다. swc::Compiler가 들고있는 Arc<swc_common::SourceMap>를 재사용하지 않으면 되는 것이라서 swc::Compiler 인스턴스를 공유하지 않도록 했다.

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