Skip to content

Turbopack: side effects optimization cleanups #82467

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 3 commits into
base: canary
Choose a base branch
from

Conversation

sokra
Copy link
Member

@sokra sokra commented Aug 8, 2025

pass ModulePart correctly for FreeVarReference::EcmaScriptModule

code simplification

use EsmExport in FindExportFromReexportsResult

Copy link
Member Author

sokra commented Aug 8, 2025

match part {
ModulePart::Evaluation => {
if *module.get_exports().split_locals_and_reexports().await? {
if *module.get_exports().split_locals_and_reexports().await? {
Copy link

@vercel vercel bot Aug 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The tree shaking logic restructure breaks reexport tree shaking for modules that don't have reexports, causing them to bypass tree shaking entirely when they should still be processed.

View Details

Analysis

The refactored TreeShakingMode::ReexportsOnly logic in lines 177-216 changes the conditional structure in a way that breaks tree shaking for certain modules.

Before the change: When part is ModulePart::Export(_), the code would:

  • If split_locals_and_reexports() returns true → apply tree shaking with facade module
  • If split_locals_and_reexports() returns false → still apply tree shaking directly on the module via apply_reexport_tree_shaking(Vc::upcast(*module), part, side_effect_free_packages)

After the change: The logic now checks split_locals_and_reexports() first:

  • If true → apply appropriate tree shaking based on part type
  • If false → always return Vc::upcast(*module) unchanged (line 215)

This means modules that don't have reexports (where split_locals_and_reexports() returns false) will completely bypass tree shaking even when explicitly requested for export parts in ReexportsOnly mode. This can result in larger bundles and dead code inclusion, as the tree shaking that should occur for these modules is never applied.

The split_locals_and_reexports() method returns true only when a module has star exports or imported bindings that are re-exported, so regular modules with only local exports will be affected by this bug.


Recommendation

Restore the original conditional structure where apply_reexport_tree_shaking is called for ModulePart::Export(_) regardless of whether the module splits locals and reexports. The logic should be:

if let Some(part) = part {
    match part {
        ModulePart::Evaluation => {
            if *module.get_exports().split_locals_and_reexports().await? {
                Vc::upcast(EcmascriptModuleLocalsModule::new(*module))
            } else {
                Vc::upcast(*module)
            }
        }
        ModulePart::Export(_) => {
            let side_effect_free_packages = module_asset_context
                .side_effect_free_packages()
                .resolve()
                .await?;

            if *module.get_exports().split_locals_and_reexports().await? {
                apply_reexport_tree_shaking(
                    Vc::upcast(EcmascriptModuleFacadeModule::new(
                        Vc::upcast(*module),
                        ModulePart::facade(),
                    ).resolve().await?),
                    part,
                    side_effect_free_packages,
                )
            } else {
                apply_reexport_tree_shaking(
                    Vc::upcast(*module),
                    part,
                    side_effect_free_packages,
                )
            }
        }
        _ => bail!("Invalid module part...")
    }
} else if *module.get_exports().split_locals_and_reexports().await? {
    Vc::upcast(EcmascriptModuleFacadeModule::new(
        Vc::upcast(*module),
        ModulePart::facade(),
    ))
} else {
    Vc::upcast(*module)
}

@ijjk
Copy link
Member

ijjk commented Aug 8, 2025

Failing test suites

Commit: 84bf1ec

pnpm test test/integration/read-only-source-hmr/test/index.test.js (turbopack)

  • Read-only source HMR > should detect a new page
Expand output

● Read-only source HMR › should detect a new page

TIMED OUT: /New page/

404
This page could not be found.

undefined

  736 |
  737 |   if (hardError) {
> 738 |     throw new Error('TIMED OUT: ' + regex + '\n\n' + content + '\n\n' + lastErr)
      |           ^
  739 |   }
  740 |   return false
  741 | }

  at check (lib/next-test-utils.ts:738:11)
  at Object.<anonymous> (integration/read-only-source-hmr/test/index.test.js:124:7)

Read more about building and testing Next.js in contributing.md.

Copy link

codspeed-hq bot commented Aug 8, 2025

CodSpeed Performance Report

Merging #82467 will improve performances by 3.92%

Comparing sokra/improve-side-effects-optimization (84bf1ec) with canary (428616e)

Summary

⚡ 3 improvements
✅ 6 untouched benchmarks

Benchmarks breakdown

Benchmark BASE HEAD Change
build[framer-motion-all] 3.2 s 3.1 s +3.92%
build[framer-motion-single] 2.3 s 2.2 s +3.28%
build[lucide-react-all] 9.2 s 8.8 s +3.77%

@sokra sokra changed the title pass ModulePart correctly for FreeVarReference::EcmaScriptModule Turbopack: side effects optimization cleanups Aug 8, 2025
@sokra sokra force-pushed the sokra/refactor-side-effects-optimization branch 2 times, most recently from 9e3cf69 to 2fb7903 Compare August 8, 2025 11:18
@sokra sokra force-pushed the sokra/improve-side-effects-optimization branch from e4b1cde to 2b7eb7d Compare August 8, 2025 11:18
_ => Ok(FollowExportsResult {
module,
export_name: Some(export_name),
ty: FoundExportType::Dynamic,
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code uses unreachable!() for an empty dynamic modules array, but this case could occur due to logic bugs or edge cases, causing a panic instead of graceful error handling.

View Details

Analysis

In the Dynamic match arm of follow_reexports, the code uses [] => unreachable!(), when handling dynamic_exporting_modules. While the current logic in find_export_from_reexports should prevent returning a Dynamic variant with an empty vector (it returns NotFound when dynamic_exporting_modules.is_empty() is true), using unreachable!() is risky in production code.

If there's any bug in the logic, race condition, or unexpected edge case that results in an empty vector being passed to the Dynamic case, the application will panic rather than handling the error gracefully. The previous implementation handled this case by returning a NotFound result, which was more defensive programming.


Recommendation

Replace the unreachable!() with graceful error handling that matches the old behavior. Instead of [] => unreachable!(),, use:

[] => Ok(FollowExportsResult {
    module,
    export_name: Some(export_name),
    ty: FoundExportType::NotFound,
}.cell()),

This maintains the same behavior as the previous implementation while avoiding potential panics.

@sokra sokra force-pushed the sokra/refactor-side-effects-optimization branch 2 times, most recently from 50c0d16 to c5ae955 Compare August 8, 2025 11:37
@sokra sokra force-pushed the sokra/improve-side-effects-optimization branch from 2b7eb7d to 27af5f2 Compare August 8, 2025 11:37
@sokra sokra force-pushed the sokra/refactor-side-effects-optimization branch from c5ae955 to ac71779 Compare August 8, 2025 13:47
@sokra sokra force-pushed the sokra/improve-side-effects-optimization branch from 27af5f2 to ef31503 Compare August 8, 2025 13:47
@sokra sokra changed the base branch from sokra/refactor-side-effects-optimization to graphite-base/82467 August 8, 2025 14:25
@sokra sokra force-pushed the sokra/improve-side-effects-optimization branch from ef31503 to 67e7a37 Compare August 8, 2025 14:26
@sokra sokra force-pushed the graphite-base/82467 branch from ac71779 to 428616e Compare August 8, 2025 14:26
@graphite-app graphite-app bot changed the base branch from graphite-base/82467 to canary August 8, 2025 14:26
@sokra sokra force-pushed the sokra/improve-side-effects-optimization branch from 67e7a37 to 84bf1ec Compare August 8, 2025 14:26
@sokra sokra force-pushed the sokra/improve-side-effects-optimization branch from 84bf1ec to 52d60b2 Compare August 11, 2025 05:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
created-by: Turbopack team PRs by the Turbopack team. Turbopack Related to Turbopack with Next.js.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants