You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Introduce support for cross-module function call optimization in Rspack. This allows function calls to be treated as side-effect-free (pure) based on external metadata or annotations, enabling more aggressive tree-shaking.
A function can be identified as "pure" via:
The /* @__NO_SIDE_EFFECTS__ */ annotation at the definition site.
User-defined configuration in rspack.config.js.
While this RFC focuses on function calls, the architecture is designed to extend to other cross-module analyses (e.g., member access or assignments) in the future.
Motivation
Rspack currently relies on SWC’s built-in side-effect analysis. This analysis is restricted to the scope of a single module and is intentionally conservative, leading to suboptimal tree-shaking in several scenarios:
Function Calls: Even if a function is logically pure, the call site is treated as a side effect unless locally annotated with /* @__PURE__ */.
Assignments: All assignments are treated as side effects.
Member Access: Property access (e.g., obj.prop) is treated as a side effect because the engine cannot guarantee the absence of a "getter" with side effects.
Current Side-Effect Strategy
Rspack currently categorizes side-effect signals into two levels:
User-defined (High Priority): Flagged via package.json ("sideEffects": false) or module.rules. If a module is marked side-effect-free, Rspack skips analysis for the module and its dependencies.
Automated Analysis: Rspack analyzes the AST using a conservative strategy.
The Chaining Problem:
If module A -> B -> C -> D exists, and only D contains a side effect, the entire chain A-B-C-D will be bundled even if B's exports are never used. This ensures that the side effects in D are executed, but it often leads to "bloated" bundles where developers wonder why unused modules are included.
Real-world Scenarios
CSS-in-JS (@emotion/css): The css function call is always treated as a side effect. In large component libraries, this prevents tree-shaking for nearly every component, even if unused.
Higher-Order Components (HOCs): Functions like connect from react-redux are used at the top level. Without cross-module purity info, the HOC call prevents the component from being removed.
Prior Arts
Compiler Notations Spec: A community effort led by [antfu](https://github.com/antfu) to standardize /* @__NO_SIDE_EFFECTS__ */.
Rollup: Supports manualPureFunctions in configuration and implemented the /* @__NO_SIDE_EFFECTS__ */ annotation.
esbuild: Also added support for the same community specification.
Detailed Design
We propose three methods to inform Rspack about side effects free functions:
1. /* @__NO_SIDE_EFFECTS__ */ Annotation
This is the safest method as the intent is declared at the source. It supports various declaration styles:
Function declarations (including async and generator).
Exported functions (default and named).
Arrow functions assigned to constants.
2. Configuration-based Marking
For third-party libraries (e.g., node_modules) where source modification is impossible, we provide a module.rules interface:
Note: This is a powerful but "dangerous" configuration. Rspack will attempt to validate if the identifier exists and is indeed a function to mitigate misconfiguration.
3. Optimization Pipeline
To support cross-module analysis, the side-effect detection logic must be decoupled from the initial parse phase, as cross-module context is not yet available.
Step A: Collection (Parse Phase)
During parsing, we record "potential side effects free statements" and establish a mapping between the statement and its ESMImportSpecifierDependency.
Step B: Analysis (FinishModules Phase)
Once all modules are processed, we iterate through the potential side effects free statements:
Identify the target module of the dependency.
Verify if the function is marked as sideEffectsFree in the target module (via annotation or config).
If all statements in a module are verified as side effects free, the module can be flagged as sideEffectFree.
Integration with Inner Graph
Rspack’s InnerGraph optimization tracks variable usage. We will refactor InnerGraph to leverage this cross-module side effects free information. If a variable is initialized by a side effects free function call and that variable is not used, InnerGraph can safely remove the statement.
Example of InnerGraph Transformation:
If heavyTask is verified as pure across modules:
// Sourceimport{heavyTask}from'lib';consta=heavyTask();// 'a' is unused// Internal Transformation for Minimizerimport{heavyTask}from'lib';consta=/*@__PURE__*/heavyTask();// Or logically: const a = null && heavyTask();
This allows the minimizer (e.g., Terser or SWC) to perform dead-code elimination (DCE) effectively.
Future Extensions
Automated Purity Analysis: Statically analyze function bodies to determine if it's side effects free without manual annotations.
Member Access: Support marking specific objects or classes as having side effects free property access.
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
Uh oh!
There was an error while loading. Please reload this page.
-
Implementation PR: Rspack #12559
Summary
Introduce support for cross-module function call optimization in Rspack. This allows function calls to be treated as side-effect-free (pure) based on external metadata or annotations, enabling more aggressive tree-shaking.
A function can be identified as "pure" via:
/* @__NO_SIDE_EFFECTS__ */annotation at the definition site.rspack.config.js.While this RFC focuses on function calls, the architecture is designed to extend to other cross-module analyses (e.g., member access or assignments) in the future.
Motivation
Rspack currently relies on SWC’s built-in side-effect analysis. This analysis is restricted to the scope of a single module and is intentionally conservative, leading to suboptimal tree-shaking in several scenarios:
/* @__PURE__ */.obj.prop) is treated as a side effect because the engine cannot guarantee the absence of a "getter" with side effects.Current Side-Effect Strategy
Rspack currently categorizes side-effect signals into two levels:
package.json("sideEffects": false) ormodule.rules. If a module is marked side-effect-free, Rspack skips analysis for the module and its dependencies.The Chaining Problem:
If module
A -> B -> C -> Dexists, and onlyDcontains a side effect, the entire chainA-B-C-Dwill be bundled even ifB's exports are never used. This ensures that the side effects inDare executed, but it often leads to "bloated" bundles where developers wonder why unused modules are included.Real-world Scenarios
cssfunction call is always treated as a side effect. In large component libraries, this prevents tree-shaking for nearly every component, even if unused.connectfromreact-reduxare used at the top level. Without cross-module purity info, the HOC call prevents the component from being removed.Prior Arts
/* @__NO_SIDE_EFFECTS__ */.manualPureFunctionsin configuration and implemented the/* @__NO_SIDE_EFFECTS__ */annotation.Detailed Design
We propose three methods to inform Rspack about side effects free functions:
1.
/* @__NO_SIDE_EFFECTS__ */AnnotationThis is the safest method as the intent is declared at the source. It supports various declaration styles:
asyncandgenerator).2. Configuration-based Marking
For third-party libraries (e.g.,
node_modules) where source modification is impossible, we provide amodule.rulesinterface:Note: This is a powerful but "dangerous" configuration. Rspack will attempt to validate if the identifier exists and is indeed a function to mitigate misconfiguration.
3. Optimization Pipeline
To support cross-module analysis, the side-effect detection logic must be decoupled from the initial
parsephase, as cross-module context is not yet available.Step A: Collection (Parse Phase)
During parsing, we record "potential side effects free statements" and establish a mapping between the statement and its
ESMImportSpecifierDependency.Step B: Analysis (FinishModules Phase)
Once all modules are processed, we iterate through the potential side effects free statements:
sideEffectFree.Integration with Inner Graph
Rspack’s InnerGraph optimization tracks variable usage. We will refactor InnerGraph to leverage this cross-module side effects free information. If a variable is initialized by a side effects free function call and that variable is not used, InnerGraph can safely remove the statement.
Example of InnerGraph Transformation:
If
heavyTaskis verified as pure across modules:This allows the minimizer (e.g., Terser or SWC) to perform dead-code elimination (DCE) effectively.
Future Extensions
Beta Was this translation helpful? Give feedback.
All reactions