2024년 5월 10일 작업일지
터보팩 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
인스턴스를 공유하지 않도록 했다.