2024년 5월 8일 작업일지

SWC 이슈 1

Minifier 버그가 있었는데 버그가 발생한 이유가 살짝 골때렸다. #8778 에서 한 함수의 상단부에 확인 코드를 추가했는데, 이 이슈의 input에 대해선 확인 코드가 false가 되면서 하단부의 로직이 실행됐고, 하단부 로직엔 버그가 있었다.

문제는 백슬래시 1개 + escape 문자인 경우에만 replace를 해야하는데, 백슬래시 2개 + escape 문자인 경우에 replace가 되는 것이었다. 정확한 이슈는 어제 발견했는데, 고친 건 오늘이다.

SWC 이슈 2

.jsx 파일에 대한 임포트가 module.resolveFuly 와 같이 썼을 때 잘못된 결과를 뱉는 게 이슈였는데, 고치는 건 간단했다.

터보팩 Tree shaking PR

내 메인 작업이다. 작업일지를 쓰는 이유중 하나가 이건데 디버깅 난이도가 상당해서 적으면서 해야한다. 원래는 Linear에 적는데 영어로 적는 것보다 한국어로 적는 게 디버깅에 훨씬 나아서 그냥 여기다 적기로 했다.

모듈 쪼갠 뒤에 Export("Postpone")을 못 찾는다고 하길래 entrypoint 목록을 찍어봤다. TURBOPACK_reexport 가 생길 수 있는 경우는 딱 한가지뿐이다.

그래서 관련된 코드를 이렇게 바꿔줬다. 원래 코드에서는 exportedNone 일 수 있고 그런 경우에 저런 이상한 Export 엔트리가 생기는데 그 경우를 막아준 것이다.


그 다음에 눈에 띄는 건

PAGE, VAR_MODULE_APP 같은 파일들의 resolve 실패였다. next.js 는 템플릿 파일에서 몇몇 변수를 replace 해서 페이지를 실제 모듈로 바꾸는데, 페이지 템플릿까지 다 찢어져있는 관계로 replace를 못하는 것이라고 추측했다.

use std::io::Write;

use anyhow::{bail, Result};
use indexmap::indexmap;
use serde::Serialize;
use turbo_tasks::Vc;
use turbo_tasks_fs::FileSystemPath;
use turbopack_binding::{
    turbo::{
        tasks::Value,
        tasks_fs::{rope::RopeBuilder, File},
    },
    turbopack::{
        core::{
            asset::{Asset, AssetContent},
            context::AssetContext,
            file_source::FileSource,
            module::Module,
            reference_type::{EntryReferenceSubType, ReferenceType},
            source::Source,
            virtual_source::VirtualSource,
        },
        ecmascript::{chunk::EcmascriptChunkPlaceable, utils::StringifyJs},
    },
};

use crate::{
    next_config::NextConfig,
    next_edge::entry::wrap_edge_entry,
    pages_structure::{PagesStructure, PagesStructureItem},
    util::{file_content_rope, load_next_js_template, NextRuntime},
};

#[turbo_tasks::function]
pub async fn create_page_ssr_entry_module(
    pathname: Vc<String>,
    reference_type: Value<ReferenceType>,
    project_root: Vc<FileSystemPath>,
    ssr_module_context: Vc<Box<dyn AssetContext>>,
    source: Vc<Box<dyn Source>>,
    next_original_name: Vc<String>,
    pages_structure: Vc<PagesStructure>,
    runtime: NextRuntime,
    next_config: Vc<NextConfig>,
) -> Result<Vc<Box<dyn EcmascriptChunkPlaceable>>> {
    let definition_page = &*next_original_name.await?;
    let definition_pathname = &*pathname.await?;

    let ssr_module = ssr_module_context
        .process(source, reference_type.clone())
        .module();

    let reference_type = reference_type.into_value();

    let template_file = match (&reference_type, runtime) {
        (ReferenceType::Entry(EntryReferenceSubType::Page), _) => {
            // Load the Page entry file.
            "pages.js"
        }
        (ReferenceType::Entry(EntryReferenceSubType::PagesApi), NextRuntime::NodeJs) => {
            // Load the Pages API entry file.
            "pages-api.js"
        }
        (ReferenceType::Entry(EntryReferenceSubType::PagesApi), NextRuntime::Edge) => {
            // Load the Pages API entry file.
            "pages-edge-api.js"
        }
        _ => bail!("Invalid path type"),
    };

    const INNER: &str = "INNER_PAGE";

    const INNER_DOCUMENT: &str = "INNER_DOCUMENT";
    const INNER_APP: &str = "INNER_APP";

    let mut replacements = indexmap! {
        "VAR_DEFINITION_PAGE" => definition_page.clone(),
        "VAR_DEFINITION_PATHNAME" => definition_pathname.clone(),
        "VAR_USERLAND" => INNER.to_string(),
    };

    if reference_type == ReferenceType::Entry(EntryReferenceSubType::Page) {
        replacements.insert("VAR_MODULE_DOCUMENT", INNER_DOCUMENT.to_string());
        replacements.insert("VAR_MODULE_APP", INNER_APP.to_string());
    }

    // Load the file from the next.js codebase.
    let mut source = load_next_js_template(
        template_file,
        project_root,
        replacements,
        indexmap! {},
        indexmap! {},
    )
    .await?;

    // When we're building the instrumentation page (only when the
    // instrumentation file conflicts with a page also labeled
    // /instrumentation) hoist the `register` method.
    if reference_type == ReferenceType::Entry(EntryReferenceSubType::Page)
        && (*definition_page == "/instrumentation" || *definition_page == "/src/instrumentation")
    {
        let file = &*file_content_rope(source.content().file_content()).await?;

        let mut result = RopeBuilder::default();
        result += file;

        writeln!(
            result,
            r#"export const register = hoist(userland, "register")"#
        )?;

        let file = File::from(result.build());

        source = Vc::upcast(VirtualSource::new(
            source.ident().path(),
            AssetContent::file(file.into()),
        ));
    }

    let mut inner_assets = indexmap! {
        INNER.to_string() => ssr_module,
    };

    if reference_type == ReferenceType::Entry(EntryReferenceSubType::Page) {
        inner_assets.insert(
            INNER_DOCUMENT.to_string(),
            process_global_item(
                pages_structure.document(),
                Value::new(reference_type.clone()),
                ssr_module_context,
            ),
        );
        inner_assets.insert(
            INNER_APP.to_string(),
            process_global_item(
                pages_structure.app(),
                Value::new(reference_type.clone()),
                ssr_module_context,
            ),
        );
    }

    let mut ssr_module = ssr_module_context
        .process(
            source,
            Value::new(ReferenceType::Internal(Vc::cell(inner_assets))),
        )
        .module();

    if matches!(runtime, NextRuntime::Edge) {
        if reference_type == ReferenceType::Entry(EntryReferenceSubType::Page) {
            ssr_module = wrap_edge_page(
                ssr_module_context,
                project_root,
                ssr_module,
                definition_page.clone(),
                definition_pathname.clone(),
                Value::new(reference_type),
                pages_structure,
                next_config,
            );
        } else {
            ssr_module = wrap_edge_entry(
                ssr_module_context,
                project_root,
                ssr_module,
                definition_pathname.to_string(),
            );
        }
    }

    let Some(ssr_module) =
        Vc::try_resolve_downcast::<Box<dyn EcmascriptChunkPlaceable>>(ssr_module).await?
    else {
        bail!("expected an ECMAScript chunk placeable module");
    };

    Ok(ssr_module)
}

#[turbo_tasks::function]
async fn process_global_item(
    item: Vc<PagesStructureItem>,
    reference_type: Value<ReferenceType>,
    module_context: Vc<Box<dyn AssetContext>>,
) -> Result<Vc<Box<dyn Module>>> {
    let source = Vc::upcast(FileSource::new(item.project_path()));

    let module = module_context.process(source, reference_type).module();

    Ok(module)
}

#[turbo_tasks::function]
async fn wrap_edge_page(
    context: Vc<Box<dyn AssetContext>>,
    project_root: Vc<FileSystemPath>,
    entry: Vc<Box<dyn Module>>,
    page: String,
    pathname: String,
    reference_type: Value<ReferenceType>,
    pages_structure: Vc<PagesStructure>,
    next_config: Vc<NextConfig>,
) -> Result<Vc<Box<dyn Module>>> {
    const INNER: &str = "INNER_PAGE_ENTRY";

    const INNER_DOCUMENT: &str = "INNER_DOCUMENT";
    const INNER_APP: &str = "INNER_APP";
    const INNER_ERROR: &str = "INNER_ERROR";

    let next_config = &*next_config.await?;

    // TODO(WEB-1824): add build support
    let dev = true;

    let sri_enabled = !dev
        && next_config
            .experimental
            .sri
            .as_ref()
            .map(|sri| sri.algorithm.as_ref())
            .is_some();

    let source = load_next_js_template(
        "edge-ssr.js",
        project_root,
        indexmap! {
            "VAR_USERLAND" => INNER.to_string(),
            "VAR_PAGE" => pathname.clone(),
            "VAR_MODULE_DOCUMENT" => INNER_DOCUMENT.to_string(),
            "VAR_MODULE_APP" => INNER_APP.to_string(),
            "VAR_MODULE_GLOBAL_ERROR" => INNER_ERROR.to_string(),
        },
        indexmap! {
            "pagesType" => StringifyJs("pages").to_string(),
            "sriEnabled" => serde_json::Value::Bool(sri_enabled).to_string(),
            "nextConfig" => serde_json::to_string(next_config)?,
            "dev" => serde_json::Value::Bool(dev).to_string(),
            "pageRouteModuleOptions" => serde_json::to_string(&get_route_module_options(page.clone(), pathname.clone()))?,
            "errorRouteModuleOptions" => serde_json::to_string(&get_route_module_options("/_error".to_string(), "/_error".to_string()))?,
            "user500RouteModuleOptions" => serde_json::to_string(&get_route_module_options("/500".to_string(), "/500".to_string()))?,
        },
        indexmap! {
            // TODO
            "incrementalCacheHandler" => None,
            "userland500Page" => None,
        },
    )
    .await?;

    let inner_assets = indexmap! {
        INNER.to_string() => entry,
        INNER_DOCUMENT.to_string() => process_global_item(pages_structure.document(), reference_type.clone(), context),
        INNER_APP.to_string() => process_global_item(pages_structure.app(), reference_type.clone(), context),
        INNER_ERROR.to_string() => process_global_item(pages_structure.error(), reference_type.clone(), context),
    };

    let wrapped = context
        .process(
            Vc::upcast(source),
            Value::new(ReferenceType::Internal(Vc::cell(inner_assets))),
        )
        .module();

    Ok(wrap_edge_entry(
        context,
        project_root,
        wrapped,
        pathname.clone(),
    ))
}

#[derive(Serialize)]
struct PartialRouteModuleOptions {
    definition: RouteDefinition,
}

#[derive(Serialize)]
struct RouteDefinition {
    kind: String,
    bundle_path: String,
    filename: String,
    /// Describes the pathname including all internal modifiers such as
    /// intercepting routes, parallel routes and route/page suffixes that are
    /// not part of the pathname.
    page: String,

    /// The pathname (including dynamic placeholders) for a route to resolve.
    pathname: String,
}

fn get_route_module_options(page: String, pathname: String) -> PartialRouteModuleOptions {
    PartialRouteModuleOptions {
        definition: RouteDefinition {
            kind: "PAGES".to_string(),
            page,
            pathname,
            // The following aren't used in production.
            bundle_path: "".to_string(),
            filename: "".to_string(),
        },
    }
}

VAR_MODULE_APP으로 검색해서 관련된 코드를 찾았다. 근데 로그를 찍어보니까 문자열 치환이었다.

그래서 슬라이스 인덱스 이슈부터 보기로 했다.

로그 찍는 코드를 추가하고 다시 돌렸다.

해당 파일로 유닛 테스트 추가해보니 part_id 값이 정상이었다. 다른 모듈의 part_id 와 섞인 것 같아서

관련 코드에 로그 찍는 코드를 추가한 뒤 돌려봤다.

실행 결과를 보니까 섞인 게 맞는 것 같아서 로그를 더 추가했다.

export_part_id = 14는 없었고, dep = 14는 많았다. 참고로 app-route.js는 14번 Part가 없는 모듈이다.

[turbo/crates/turbopack-ecmascript/src/references/mod.rs:417] part = Evaluation
 ✓ Compiled /favicon.ico in 24ms
 ⨯ Error: failed to analyse ecmascript module '[project]/node_modules/.pnpm/file+..+nextpack+tarballs+next.tar_react-dom@18.2.0_react@18.2.0/node_modules/next/dist/esm/build/templates/app-route.js [app-rsc] (ecmascript)'

Caused by:
- part_id is out of range: 14 >= 14; entrypoints = {ModuleEvaluation: 0, Export("serverHooks"): 4, Export("requestAsyncStorage"): 2, Export("staticGenerationAsyncStorage"): 3, Export("patchFetch"): 6, Export("routeModule"): 1, Export("originalPathname"): 5}

Debug info:
- Execution of get_written_endpoint_with_issues failed
- Execution of <AppEndpoint as Endpoint>::write_to_disk failed
- Execution of Project::emit_all_output_assets failed
- Execution of VersionedContentMap::output_side_effects failed
- Execution of all_assets_from_entries failed
- Execution of AppEndpointOutput::output_assets failed
- Execution of AppEndpoint::output failed
- Execution of primary_referenced_modules failed
- Execution of <EcmascriptModulePartAsset as Module>::references failed
- Execution of analyse_ecmascript_module failed
- failed to analyse ecmascript module '[project]/node_modules/.pnpm/file+..+nextpack+tarballs+next.tar_react-dom@18.2.0_react@18.2.0/node_modules/next/dist/esm/build/templates/app-route.js [app-rsc] (ecmascript)'
- Execution of part_of_module failed
- part_id is out of range: 14 >= 14; entrypoints = {ModuleEvaluation: 0, Export("serverHooks"): 4, Export("requestAsyncStorage"): 2, Export("staticGenerationAsyncStorage"): 3, Export("patchFetch"): 6, Export("routeModule"): 1, Export("originalPathname"): 5}

결과 해석하는데에 좀 걸렸는데 섞인 것이 아닐수도 있겠다는 생각이 들었다.

그래서 bail에서 더 많은 값을 출력하도록 했다.

Caused by:
- part_id is out of range: 14 >= 14; uri = ./app-route.js; entrypoints = {ModuleEvaluation: 0, Export("serverHooks"): 4, Export("requestAsyncStorage"): 2, Export("staticGenerationAsyncStorage"): 3, Export("patchFetch"): 6, Export("routeModule"): 1, Export("originalPathname"): 5}: part_deps = {0: [7, 8, 9, 10, 13], 13: [7, 8, 9, 10], 10: [7, 8, 9], 4: [12, 13], 1: [11, 13], 8: [7], 5: [13], 2: [12, 13], 12: [11], 9: [7, 8], 6: [13, 12], 3: [12, 13]}

Debug info:
- Execution of get_written_endpoint_with_issues failed
- Execution of <AppEndpoint as Endpoint>::write_to_disk failed
- Execution of Project::emit_all_output_assets failed
- Execution of VersionedContentMap::output_side_effects failed
- Execution of all_assets_from_entries failed
- Execution of AppEndpointOutput::output_assets failed
- Execution of AppEndpoint::output failed
- Execution of primary_referenced_modules failed
- Execution of <EcmascriptModulePartAsset as Module>::references failed
- Execution of analyse_ecmascript_module failed
- failed to analyse ecmascript module '[project]/node_modules/.pnpm/file+..+nextpack+tarballs+next.tar_react-dom@18.2.0_react@18.2.0/node_modules/next/dist/esm/build/templates/app-route.js [app-rsc] (ecmascript)'
- Execution of part_of_module failed
- part_id is out of range: 14 >= 14; uri = ./app-route.js; entrypoints = {ModuleEvaluation: 0, Export("serverHooks"): 4, Export("requestAsyncStorage"): 2, Export("staticGenerationAsyncStorage"): 3, Export("patchFetch"): 6, Export("routeModule"): 1, Export("originalPathname"): 5}: part_deps = {0: [7, 8, 9, 10, 13], 13: [7, 8, 9, 10], 10: [7, 8, 9], 4: [12, 13], 1: [11, 13], 8: [7], 5: [13], 2: [12, 13], 12: [11], 9: [7, 8], 6: [13, 12], 3: [12, 13]}
    at withErrorCause (/Users/kdy1/projects/app-playground/node_modules/.pnpm/file+..+nextpack+tarballs+next.tar_react-dom@18.2.0_react@18.2.0/node_modules/next/dist/build/swc/index.js:415:19)
    at async EndpointImpl.writeToDisk (/Users/kdy1/projects/app-playground/node_modules/.pnpm/file+..+nextpack+tarballs+next.tar_react-dom@18.2.0_react@18.2.0/node_modules/next/dist/build/swc/index.js:598:20)
    at async handleRouteType (/Users/kdy1/projects/app-playground/node_modules/.pnpm/file+..+nextpack+tarballs+next.tar_react-dom@18.2.0_react@18.2.0/node_modules/next/dist/server/dev/turbopack-utils.js:414:41)
    at async Object.ensurePage (/Users/kdy1/projects/app-playground/node_modules/.pnpm/file+..+nextpack+tarballs+next.tar_react-dom@18.2.0_react@18.2.0/node_modules/next/dist/server/dev/hot-reloader-turbopack.js:644:17)
    at async DevBundlerService.ensurePage (/Users/kdy1/projects/app-playground/node_modules/.pnpm/file+..+nextpack+tarballs+next.tar_react-dom@18.2.0_react@18.2.0/node_modules/next/dist/server/lib/dev-bundler-service.js:18:20)
    at async DevServer.ensurePage (/Users/kdy1/projects/app-playground/node_modules/.pnpm/file+..+nextpack+tarballs+next.tar_react-dom@18.2.0_react@18.2.0/node_modules/next/dist/server/dev/next-dev-server.js:551:9)
    at async Object.ensure (/Users/kdy1/projects/app-playground/node_modules/.pnpm/file+..+nextpack+tarballs+next.tar_react-dom@18.2.0_react@18.2.0/node_modules/next/dist/server/dev/next-dev-server.js:169:17)
    at async DevRouteMatcherManager.matchAll (/Users/kdy1/projects/app-playground/node_modules/.pnpm/file+..+nextpack+tarballs+next.tar_react-dom@18.2.0_react@18.2.0/node_modules/next/dist/server/future/route-matcher-managers/dev-route-matcher-manager.js:96:13)
    at async DevRouteMatcherManager.match (/Users/kdy1/projects/app-playground/node_modules/.pnpm/file+..+nextpack+tarballs+next.tar_react-dom@18.2.0_react@18.2.0/node_modules/next/dist/server/future/route-matcher-managers/default-route-matcher-manager.js:155:26)
    at async NextNodeServer.handleCatchallRenderRequest (/Users/kdy1/projects/app-playground/node_modules/.pnpm/file+..+nextpack+tarballs+next.tar_react-dom@18.2.0_react@18.2.0/node_modules/next/dist/server/next-server.js:229:31)
    at async DevServer.handleRequestImpl (/Users/kdy1/projects/app-playground/node_modules/.pnpm/file+..+nextpack+tarballs+next.tar_react-dom@18.2.0_react@18.2.0/node_modules/next/dist/server/base-server.js:796:17)
    at async /Users/kdy1/projects/app-playground/node_modules/.pnpm/file+..+nextpack+tarballs+next.tar_react-dom@18.2.0_react@18.2.0/node_modules/next/dist/server/dev/next-dev-server.js:339:20
    at async Span.traceAsyncFn (/Users/kdy1/projects/app-playground/node_modules/.pnpm/file+..+nextpack+tarballs+next.tar_react-dom@18.2.0_react@18.2.0/node_modules/next/dist/trace/trace.js:154:20)
    at async DevServer.handleRequest (/Users/kdy1/projects/app-playground/node_modules/.pnpm/file+..+nextpack+tarballs+next.tar_react-dom@18.2.0_react@18.2.0/node_modules/next/dist/server/dev/next-dev-server.js:336:24)
    at async invokeRender (/Users/kdy1/projects/app-playground/node_modules/.pnpm/file+..+nextpack+tarballs+next.tar_react-dom@18.2.0_react@18.2.0/node_modules/next/dist/server/lib/router-server.js:174:21)
    at async handleRequest (/Users/kdy1/projects/app-playground/node_modules/.pnpm/file+..+nextpack+tarballs+next.tar_react-dom@18.2.0_react@18.2.0/node_modules/next/dist/server/lib/router-server.js:353:24)
    at async requestHandlerImpl (/Users/kdy1/projects/app-playground/node_modules/.pnpm/file+..+nextpack+tarballs+next.tar_react-dom@18.2.0_react@18.2.0/node_modules/next/dist/server/lib/router-server.js:377:13)
    at async Server.requestListener (/Users/kdy1/projects/app-playground/node_modules/.pnpm/file+..+nextpack+tarballs+next.tar_react-dom@18.2.0_react@18.2.0/node_modules/next/dist/server/lib/start-server.js:142:13) {
  [cause]: [Error: failed to analyse ecmascript module '[project]/node_modules/.pnpm/file+..+nextpack+tarballs+next.tar_react-dom@18.2.0_react@18.2.0/node_modules/next/dist/esm/build/templates/app-route.js [app-rsc] (ecmascript)'

  Caused by:
  - part_id is out of range: 14 >= 14; uri = ./app-route.js; entrypoints = {ModuleEvaluation: 0, Export("serverHooks"): 4, Export("requestAsyncStorage"): 2, Export("staticGenerationAsyncStorage"): 3, Export("patchFetch"): 6, Export("routeModule"): 1, Export("originalPathname"): 5}: part_deps = {0: [7, 8, 9, 10, 13], 13: [7, 8, 9, 10], 10: [7, 8, 9], 4: [12, 13], 1: [11, 13], 8: [7], 5: [13], 2: [12, 13], 12: [11], 9: [7, 8], 6: [13, 12], 3: [12, 13]}

  Debug info:
  - Execution of get_written_endpoint_with_issues failed
  - Execution of <AppEndpoint as Endpoint>::write_to_disk failed
  - Execution of Project::emit_all_output_assets failed
  - Execution of VersionedContentMap::output_side_effects failed
  - Execution of all_assets_from_entries failed
  - Execution of AppEndpointOutput::output_assets failed
  - Execution of AppEndpoint::output failed
  - Execution of primary_referenced_modules failed
  - Execution of <EcmascriptModulePartAsset as Module>::references failed
  - Execution of analyse_ecmascript_module failed
  - failed to analyse ecmascript module '[project]/node_modules/.pnpm/file+..+nextpack+tarballs+next.tar_react-dom@18.2.0_react@18.2.0/node_modules/next/dist/esm/build/templates/app-route.js [app-rsc] (ecmascript)'
  - Execution of part_of_module failed
  - part_id is out of range: 14 >= 14; uri = ./app-route.js; entrypoints = {ModuleEvaluation: 0, Export("serverHooks"): 4, Export("requestAsyncStorage"): 2, Export("staticGenerationAsyncStorage"): 3, Export("patchFetch"): 6, Export("routeModule"): 1, Export("originalPathname"): 5}: part_deps = {0: [7, 8, 9, 10, 13], 13: [7, 8, 9, 10], 10: [7, 8, 9], 4: [12, 13], 1: [11, 13], 8: [7], 5: [13], 2: [12, 13], 12: [11], 9: [7, 8], 6: [13, 12], 3: [12, 13]}] {
    code: 'GenericFailure'
  }
}

이것만 보면 14가 있는 게 이상하지만, 다른 로그까지 같이 보면 약간 다른 게 보인다.

[turbo/crates/turbopack-ecmascript/src/references/mod.rs:417] split_data = Ok {
    entrypoints: {
        ModuleEvaluation: 0,
        Export(
            "serverHooks",
        ): 4,
        Export(
            "requestAsyncStorage",
        ): 2,
        Export(
            "staticGenerationAsyncStorage",
        ): 3,
        Export(
            "patchFetch",
        ): 6,
        Export(
            "routeModule",
        ): 1,
        Export(
            "originalPathname",
        ): 5,
    },
    deps: {
        0: [
            7,
            8,
            9,
            10,
            14,
        ],
        13: [
            12,
        ],
        10: [
            7,
            8,
            9,
        ],
        4: [
            13,
            14,
        ],
        1: [
            12,
            14,
        ],
        14: [
            7,
            8,
            9,
            10,
        ],
        8: [
            7,
        ],
        5: [
            14,
        ],
        2: [
            13,
            14,
        ],
        12: [
            11,
        ],
        9: [
            7,
            8,
        ],
        6: [
            14,
            13,
        ],
        3: [
            13,
            14,
        ],
    },
    uri_of_module: "./app-route.js",
}

0: [7, 8, 9, 10, 13],
13: [7, 8, 9, 10],
10: [7, 8, 9],
4: [12, 13],
1: [11, 13],
8: [7],
5: [13],
2: [12, 13],
12: [11],
9: [7, 8],
6: [13, 12],
3: [12, 13]

{
    0: [7, 8, 9, 10, 14],
    13: [12],
    10: [7, 8, 9],
    4: [13, 14],
    1: [12, 14],
    14: [7, 8, 9, 10],
    8: [7],
    5: [14],
    2: [13, 14],
    12: [11],
    9: [7, 8],
    6: [14, 13],
    3: [13, 14],
}

대체로 비슷해서 고민해봤는데 중간에 숫자가 빠졌다면 10 ~ 11번에서 문제가 있었던 것 같은데 관련된 코드를 보다가 SplitResult 타입이 한 모듈에 대해 2번 생성되고 있다는 걸 깨달았다.

[turbo/crates/turbopack-ecmascript/src/tree_shake/mod.rs:423] "Creating split result for " = "Creating split result for "
[turbo/crates/turbopack-ecmascript/src/tree_shake/mod.rs:423] &uri_of_module = "./app-route.js"
[turbo/crates/turbopack-ecmascript/src/tree_shake/mod.rs:423] &entrypoints = {
    ModuleEvaluation: 0,
    Export(
        "serverHooks",
    ): 4,
    Export(
        "requestAsyncStorage",
    ): 2,
    Export(
        "staticGenerationAsyncStorage",
    ): 3,
    Export(
        "patchFetch",
    ): 6,
    Export(
        "routeModule",
    ): 1,
    Export(
        "originalPathname",
    ): 5,
}
[turbo/crates/turbopack-ecmascript/src/tree_shake/mod.rs:423] &part_deps = {
    0: [
        7,
        8,
        9,
        10,
        14,
    ],
    13: [
        12,
    ],
    10: [
        7,
        8,
        9,
    ],
    4: [
        13,
        14,
    ],
    1: [
        12,
        14,
    ],
    14: [
        7,
        8,
        9,
        10,
    ],
    8: [
        7,
    ],
    5: [
        14,
    ],
    2: [
        13,
        14,
    ],
    12: [
        11,
    ],
    9: [
        7,
        8,
    ],
    6: [
        14,
        13,
    ],
    3: [
        13,
        14,
    ],
}
[turbo/crates/turbopack-ecmascript/src/tree_shake/mod.rs:423] "Creating split result for " = "Creating split result for "
[turbo/crates/turbopack-ecmascript/src/tree_shake/mod.rs:423] &uri_of_module = "./app-route.js"
[turbo/crates/turbopack-ecmascript/src/tree_shake/mod.rs:423] &entrypoints = {
    ModuleEvaluation: 0,
    Export(
        "serverHooks",
    ): 4,
    Export(
        "requestAsyncStorage",
    ): 2,
    Export(
        "staticGenerationAsyncStorage",
    ): 3,
    Export(
        "patchFetch",
    ): 6,
    Export(
        "routeModule",
    ): 1,
    Export(
        "originalPathname",
    ): 5,
}
[turbo/crates/turbopack-ecmascript/src/tree_shake/mod.rs:423] &part_deps = {
    0: [
        7,
        8,
        9,
        10,
        14,
    ],
    13: [
        12,
    ],
    10: [
        7,
        8,
        9,
    ],
    4: [
        13,
        14,
    ],
    1: [
        12,
        14,
    ],
    14: [
        7,
        8,
        9,
        10,
    ],
    8: [
        7,
    ],
    5: [
        14,
    ],
    2: [
        13,
        14,
    ],
    12: [
        11,
    ],
    9: [
        7,
        8,
    ],
    6: [
        14,
        13,
    ],
    3: [
        13,
        14,
    ],
}

이렇게 두개가 생성되고 있었다.

캐싱이 안 되고 있었던 것이므로 왜 캐싱이 안 됐는지 알아내기 위해 vdbg!에 인자를 추가했다.

[turbo/crates/turbopack-ecmascript/src/tree_shake/mod.rs:423] path = FileSystemPath {
    fs: DiskFileSystem {
        name: "project",
        root: "/Users/kdy1/projects/app-playground",
    },
    path: "node_modules/.pnpm/file+..+nextpack+tarballs+next.tar_react-dom@18.2.0_react@18.2.0/node_modules/next/dist/esm/build/templates/app-route.js",
}
[turbo/crates/turbopack-ecmascript/src/tree_shake/mod.rs:423] source = VirtualSource {
    ident: AssetIdent {
        path: FileSystemPath {
            fs: DiskFileSystem {
                name: "project",
                root: "/Users/kdy1/projects/app-playground",
            },
            path: "node_modules/.pnpm/file+..+nextpack+tarballs+next.tar_react-dom@18.2.0_react@18.2.0/node_modules/next/dist/esm/build/templates/app-route.js",
        },
        query: "",
        fragment: None,
        assets: [],
        modifiers: [],
        part: None,
        layer: None,
    },
    content: File(
        Content(
            File {
                meta: FileMeta {
                    permissions: Writable,
                    content_type: None,
                },
            },
        ),
    ),
}
[turbo/crates/turbopack-ecmascript/src/tree_shake/mod.rs:423] parsed = Ok
[turbo/crates/turbopack-ecmascript/src/tree_shake/mod.rs:423] path = FileSystemPath {
    fs: DiskFileSystem {
        name: "project",
        root: "/Users/kdy1/projects/app-playground",
    },
    path: "node_modules/.pnpm/file+..+nextpack+tarballs+next.tar_react-dom@18.2.0_react@18.2.0/node_modules/next/dist/esm/build/templates/app-route.js",
}
[turbo/crates/turbopack-ecmascript/src/tree_shake/mod.rs:423] source = FileSource {
    path: FileSystemPath {
        fs: DiskFileSystem {
            name: "project",
            root: "/Users/kdy1/projects/app-playground",
        },
        path: "node_modules/.pnpm/file+..+nextpack+tarballs+next.tar_react-dom@18.2.0_react@18.2.0/node_modules/next/dist/esm/build/templates/app-route.js",
    },
    query: "",
}
[turbo/crates/turbopack-ecmascript/src/tree_shake/mod.rs:423] parsed = Ok[turbo/crates/turbopack-ecmascript/src/tree_shake/mod.rs:423] parsed = Ok

원인은 확실해졌다. 한 터보팩 인스턴스에 next/dist/esm/build/templates/app-route.js 가 2개 존재하는데, 내용물이 서로 달라서 모듈 스플리팅 결과가 서로 다른 와중에 path는 서로 같아서 모듈 스플리팅 결과 타입이 섞이는 것이 문제인 것이다.

이 둘을 구분해야하는지 하나를 없애야하는지 모르겠어서 팀 채널에 물어봤고 path 는 같지만 ident가 다르기 때문에 구분해야한다는 답변을 받았다. 슬슬 피곤해서 관련된 질문만 하나 더 남기고 다른 이슈들 보기로 했다.

swc/plugins의 rust 툴체인 업데이트

집중 안 해도 할 수 있는 작업이라 골랐다. stdsimd 라는 rustc feature이 사라진 것 때문에 툴체인 업데이트만 하는 것이 불가능한가 했는데 ahash가 백포팅을 잘해주는 라이브러리라서 툴체인 + ahash 업데이트로 끊을 수 있었다.

swc/plugins의 의존성 업데이트

의존성만 업데이트했는데 소스맵에 관련된 테스트 결과물이 바뀌어서 온라인 base64 디코더로 돌려보니까 rangeMappings 관련된 변경사항이었다. sourcemap crate의 range mapping이나 관련된 최적화는 내가 구현해서 PR 보낸 것이라서 잘 이해하고 있는 것들이다. 그래서 바뀐 테스트 출력물들을 검증하는 데 어려움이 없었다.