Tree Shaking

Rspack supports tree shaking, a terminology widely used within the JavaScript ecosystem defined as the removal of unused code, commonly referred to as "dead code." Dead code arises when certain exports from a module are not used and they lack side effects, allowing such pieces to be safely deleted to reduce the final output size.

Upon setting the mode to production, Rspack by default enables a series of optimizations related to tree shaking, including:

  • usedExports: Checks whether module exports are utilized, allowing the removal of unused exports.
  • sideEffects: Assesses modules for side effects. Modules without side effects can be optimized further via re-exports.
  • providedExports: Analyzes all exports and their sources of re-exportation.
  • innerGraph: Tracks the transmission of variables, enhancing the accuracy of determining whether exports are indeed used.

Below are examples to illustrate how these configuration options function.

INFO

Note that Rspack does not directly remove dead code but labels unused exports as potential "dead code." These labels can then be recognized and processed by subsequent compression tools. As such, if compression features are turned off, no actual code removal will be observable. For enhanced readability, pseudocode might be used to demonstrate the effects of code removal.

Let's understand this mechanism better through an example, assuming src/main.js as the project's entry point:

src/main.js
import { foo } from './util.js';

console.log(foo);
// `bar` is not used
src/util.js
export const foo = 1;
export const bar = 2;

In this example, bar from util.js is unused. In production mode, Rspack defaults to enabling the usedExports optimization, detecting which exports are actively used. Unused exports, like bar, are safely removed. The final output would resemble:

dist/main.js
const foo = 1;

console.log(foo);

Side Effects Analysis

In production mode, Rspack also typically analyzes modules for the presence of side effects. If all exports from a module are unused and the module is devoid of side effects, then the entire module can be deleted. Let's modify the previous example a bit:

src/main.js
import { foo } from './util.js';

- console.log(foo);
// `bar` is not used

In this case, none of the exports from util.js are used, and it’s analyzed as having no side effects, permitting the entire deletion of util.js.

You may manually indicate whether a module retains side effects through package.json or module.rules. For information on how to do so, please consult sideEffects.

Re-export Analysis

Re-exports are common in development. However, a module might pull in numerous other modules while typically only a fraction of those are needed. Rspack optimizes this situation by ensuring that the referring party can access the actual exported modules directly. Consider this example involving re-exports:

src/main.js
import { value } from './re-exports.js';
console.log(value);
src/re-exports.js
export * from './value.js';
export * from './other.js'; // this can be removed if `other.js` does not have any side effects
src/value.js
export const value = 42;
export const foo = 42; // not used

Rspack defaults to enable providedExports, which can analyze all exports from a re-exporting module and identify their respective origins.

If src/re-exports.js contains no side effects, Rspack can convert the import in src/main.js from src/re-exports.js directly into imports from src/value.js, effectively:

src/main.js
- import { value } from './re-exports.js';
+ import { value } from './value.js';
console.log(value);

This approach benefits by entirely ignoring the src/re-exports.js module.

With an ability to analyze all re-exports in src/re-exports.js, it is determined that foo from src/value.js is not used and will be removed in the final output.

Variable Transmission

In some cases, even though exports are accessed, they might not actually be used. For example:

src/main.js
import { foo } from './value.js';

function log() {
  console.log(foo);
} // `log` is not used

const bar = foo; // `foo` is not used

In the scenario above, even though the log function and the variable bar depend on foo, since neither is used, foo can still be considered dead code and be deleted.

After enabling innerGraph optimization (enabled by default for production mode), for complex cross-module situations, Rspack maintains the ability to track variable usage, thereby achieving precise code optimization.

src/main.js
import { value } from './bar.js';
console.log(value);
src/bar.js
import { foo } from './foo.js';
const bar = foo;
export const value = bar;
src/foo.js
export const foo = 42;

In this context, because value is eventually used, the foo it depends on is retained.