# 2024년 5월 10일 작업일지

### 터보팩 Tree shaking PR

* [https://github.com/vercel/turbo/pull/7986](https://github.com/vercel/turbo/pull/7986)
    

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

```plaintext
        "./_/_interop_require_wildcard": Conditional(
            [
                (
                    "import",
                    Result(
                        "./esm/_interop_require_wildcard.js",
                    ),
                ),
                (
                    "default",
                    Result(
                        "./cjs/_interop_require_wildcard.cjs",
                    ),
                ),
            ],
        ),
```

```plaintext
[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`가 포함된 로그 메시지들이다. 문제가 없어보여서 다른 부분을 먼저 보기로 했다.

```javascript
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>"));
```

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

```javascript
"[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 하지 않는 게 문제인 것 같았다.

```rust
                    // 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을 하지 않게 바꾸기로 결정했다.

```rust

#[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`의 타입 정의를 위처럼 변경했다.

```rust
            // 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` 인 경우 의존성이 로딩이 안 되는 것 같았다.

```rust



        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![]))`를 반환했었다.

```plaintext
 ✓ 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)
```

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

```javascript
"[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 바인딩으로 바뀌어서 생성된 코드 같았다.

```javascript

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

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

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1715308949106/3d05d5b5-0d72-4b83-a8b7-1d8a170bfd31.png align="center")

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

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1715309906211/ca34f0a3-7cc5-44fb-833e-579718f36fb8.png align="center")

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

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1715310226699/5e269b90-bad9-4ede-bbce-de15437efaf0.png align="center")

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1715311062020/dbf99bb0-3f87-4326-ba4f-9d9da96d8dc4.png align="center")

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

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1715314344444/23f19c5b-0f6c-4407-aee7-fb5576fbefed.png align="center")

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

```javascript
 ○ 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 결과물을 변수에 저장했다가 사용해야한다.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1715327275636/092be0c7-7e8d-4e8c-b30d-ad004b9b0da8.png align="center")

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

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1715327536232/4346f5af-2d74-4a6e-96f5-be19dbae8f0a.png align="center")

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

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1715327864083/13b760e1-f64b-4486-974e-7942c1b1a61c.png align="center")

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

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1715328581552/199870c9-0fc4-4b0e-b246-06801b984b8f.png align="center")

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

```plaintext
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)
```

하지만 통하지 않았다.

```rust
                        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`을 찍어보기로 했다.

```plaintext
[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` 의 로직을 뜯어보기로 했는데, 로직이 복잡해보여서 무지성으로 로그찍기로 했다.

```rust

    #[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 플러그인 테스트 정리

* [https://github.com/swc-project/plugins/pull/298](https://github.com/swc-project/plugins/pull/298)
    

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

### SWC relay 플러그인 Config파싱 개선

* [https://github.com/swc-project/plugins/pull/299](https://github.com/swc-project/plugins/pull/299)
    

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

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

* [https://github.com/vercel/next.js/pull/65602](https://github.com/vercel/next.js/pull/65602)
    

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