Skip to content

feat: support optimize side effects free function calls#12559

Merged
hardfist merged 44 commits intomainfrom
feat/pure-fn
Apr 7, 2026
Merged

feat: support optimize side effects free function calls#12559
hardfist merged 44 commits intomainfrom
feat/pure-fn

Conversation

@JSerFeng
Copy link
Copy Markdown
Contributor

@JSerFeng JSerFeng commented Dec 25, 2025

Summary

Support #__NO_SIDE_EFFECTS__ notation and user flag side effects free function calls.

What is it ?

it's a notation from https://github.com/javascript-compiler-hints/compiler-notations-spec/blob/main/no-side-effects-notation-spec.md, and it's already implemented by both esbuild and Rollup.

It can help Rspack analyze if one function is pure.

Integration

✅ Supports #__NO_SIDE_EFFECTS__ for following cases:

  • Pure functions in current scope
/* #__NO_SIDE_EFFECTS__ */ function getValue() {}

getValue()
  • Export pure functions

Rspack will analyze all modules to find if the function is pure in its declaration module.

/* #__NO_SIDE_EFFECTS__ */
export function getValue() {}

/* #__NO_SIDE_EFFECTS__ */
export default function getValue() {}
  • User can flag pure function if it's hard to add notation, for example code in node_modules
// rspack.config.mjs
export default {
  module: {
    rules: [
      {
        test: /node_modules\/test\/index\.js/,
        parser: {
          pureFunctions: [
            'foo',
            'bar',
          ]
        }
      }
    ]
  },
}

Used exports

Currently the pure function analyze is just work for optimization.sideEffect, not optimization.innerGraph, which means the call for pure function is still exist in output

Related links

Checklist

  • Tests updated (or not required).
  • Documentation updated (or not required).

@netlify
Copy link
Copy Markdown

netlify Bot commented Dec 25, 2025

Deploy Preview for rspack ready!

Name Link
🔨 Latest commit a4929d0
🔍 Latest deploy log https://app.netlify.com/projects/rspack/deploys/6960b3846dd9e700082ba619
😎 Deploy Preview https://deploy-preview-12559--rspack.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@github-actions github-actions Bot added release: feature release: feature related release(mr only) team The issue/pr is created by the member of Rspack. labels Dec 25, 2025
@JSerFeng JSerFeng changed the title feat: support pure function analyze feat: support NO_SIDE_EFFECTS notation Dec 25, 2025
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Dec 25, 2025

Rsdoctor Bundle Diff Analysis

Found 6 projects in monorepo, 0 projects with changes.

📊 Quick Summary
Project Total Size Change
popular-libs 1.7 MB 0
react-10k 5.7 MB 0
react-1k 826.9 KB 0
react-5k 2.7 MB 0
rome 984.1 KB 0
ui-components 5.0 MB 0

Generated by Rsdoctor GitHub Action

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Dec 25, 2025

📦 Binary Size-limit

Comparing 5843920 to perf: modules should use IdentifierHasher (#13601) by Cong-Cong Pan

❌ Size increased by 74.66KB from 49.25MB to 49.32MB (⬆️0.15%)

@JSerFeng JSerFeng marked this pull request as ready for review December 26, 2025 06:13
Copilot AI review requested due to automatic review settings December 26, 2025 06:13
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR adds support for the #__NO_SIDE_EFFECTS__ notation from the JavaScript Compiler Hints specification, enabling better tree-shaking optimization. The feature allows developers to mark function definitions as pure (having no side effects), which Rspack can use to safely eliminate unused function calls.

Key changes:

  • Implements #__NO_SIDE_EFFECTS__ comment annotation parsing for various function types (declarations, expressions, arrows, exports)
  • Adds pureFunctions configuration option in module rules to manually flag functions as pure for third-party code
  • Introduces deferred purity checking system that validates imported functions across module boundaries

Reviewed changes

Copilot reviewed 29 out of 30 changed files in this pull request and generated 13 comments.

Show a summary per file
File Description
website/docs/en/guide/optimization/tree-shaking.mdx Adds English documentation for NO_SIDE_EFFECTS notation and pureFunctions config
website/docs/zh/guide/optimization/tree-shaking.mdx Adds Chinese documentation for the new feature
tests/rspack-test/configCases/tree-shaking/no-side-effects-notation/* Test cases for NO_SIDE_EFFECTS annotation on various function types
tests/rspack-test/configCases/tree-shaking/user-mark-pure-functions/* Test cases for pureFunctions config and warning validation
packages/rspack/src/config/types.ts Adds pureFunctions array type to JavascriptParserOptions
packages/rspack/src/config/normalization.ts Adds noSideEffectsNotation to ExperimentsNormalized
packages/rspack/src/config/defaults.ts Sets default value (false) for noSideEffectsNotation experiment
packages/rspack/src/config/adapter.ts Maps pureFunctions config to raw options
packages/rspack/etc/core.api.md Updates API documentation with new config options
crates/rspack_plugin_javascript/src/parser_plugin/side_effects_parser_plugin.rs Implements annotation parsing, deferred checking, and validation warnings
crates/rspack_plugin_javascript/src/plugin/side_effects_flag_plugin.rs Adds finish_modules hook to validate deferred pure checks and unused ParsedPureFunction struct
crates/rspack_plugin_javascript/src/plugin/flag_dependency_exports_plugin.rs Exports stage constant for plugin ordering
crates/rspack_plugin_javascript/src/parser_plugin/inner_graph/plugin.rs Threads analyze_pure_annotation parameter through to purity checking functions
crates/rspack_plugin_javascript/src/visitors/dependency/parser/mod.rs Changes definitions field visibility to pub(crate) for cross-module access
crates/rspack_core/src/module.rs Adds pure_functions and deferred_pure_checks to BuildInfo
crates/rspack_core/src/options/module.rs Adds pureFunctions option to JavascriptParserOptions
crates/rspack_core/src/options/experiments/mod.rs Adds no_side_effects_notation flag to Experiments
crates/rspack_binding_api/src/raw_options/raw_module/mod.rs Binds pureFunctions from JavaScript to Rust
crates/rspack_binding_api/src/raw_options/raw_experiments/mod.rs Binds noSideEffectsNotation from JavaScript to Rust
crates/rspack/src/builder/mod.rs Adds builder support for noSideEffectsNotation experiment
crates/rspack/src/builder/builder_context.rs Minor whitespace formatting
crates/node_binding/napi-binding.d.ts Updates TypeScript type definitions
Comments suppressed due to low confidence (1)

crates/rspack_plugin_javascript/src/parser_plugin/side_effects_parser_plugin.rs:843

  • The analyze_pure_notation parameter is added to numerous functions (is_pure_call_expr, is_pure_expression, is_pure_class, is_pure_decl, is_pure_var_decl, is_pure_class_member, is_pure_pat, is_pure_function) but is never actually used to control any conditional logic within these functions. This suggests the parameter is unnecessary code that should either be removed or should actually gate some behavior related to analyzing pure notations.
fn is_pure_call_expr(
  parser: &mut JavascriptParser,
  analyze_pure_notation: bool,
  expr: &Expr,
  unresolved_ctxt: SyntaxContext,
  comments: Option<&dyn Comments>,
) -> bool {
  let Expr::Call(call_expr) = expr else {
    unreachable!();
  };

  let callee = &call_expr.callee;
  let pure_flag = comments
    .map(|comments| comments.has_flag(callee.span().lo, "PURE"))
    .unwrap_or(false);

  if pure_flag {
    call_expr.args.iter().all(|arg| {
      if arg.spread.is_some() {
        false
      } else {
        is_pure_expression(
          parser,
          analyze_pure_notation,
          &arg.expr,
          unresolved_ctxt,
          comments,
        )
      }
    })
  } else {
    if let Some(Expr::Ident(ident)) = callee.as_expr().map(|expr| expr.as_ref())
      && parser
        .build_info
        .pure_functions
        .as_ref()
        .map(|pure_functions| pure_functions.contains(&ident.sym))
        .unwrap_or(false)
    {
      // this is a locally pure function
      return true;
    }

    if let Some(deferred_check) = try_extract_deferred_check(parser, callee) {
      parser.build_info.deferred_pure_checks.push(deferred_check);
      return call_expr.args.iter().all(|arg| {
        if arg.spread.is_some() {
          false
        } else {
          is_pure_expression(
            parser,
            analyze_pure_notation,
            &arg.expr,
            unresolved_ctxt,
            comments,
          )
        }
      });
    }

    !expr.may_have_side_effects(ExprCtx {
      unresolved_ctxt,
      in_strict: false,
      is_unresolved_ref_safe: false,
      remaining_depth: 4,
    })
  }
}

fn try_extract_deferred_check(
  parser: &mut JavascriptParser,
  callee: &Callee,
) -> Option<DeferredPureCheck> {
  let Callee::Expr(expr) = callee else {
    return None;
  };

  let info = match &**expr {
    Expr::Ident(ident) => parser.get_variable_info(&ident.sym)?,
    _ => return None,
  };

  let tag_info_id = info.tag_info?;
  let tag_info = parser.definitions_db.expect_get_tag_info(tag_info_id);

  if tag_info.tag != ESM_SPECIFIER_TAG {
    return None;
  }

  let data = ESMSpecifierData::downcast(tag_info.data.clone()?);

  Some(DeferredPureCheck {
    import_request: data.source.to_string(),
    atom: data.name.clone(),
    start: callee.span().lo.0,
    end: callee.span().hi.0,
  })
}

impl SideEffectsParserPlugin {
  fn analyze_stmt_side_effects(&self, stmt: &Statement, parser: &mut JavascriptParser) {
    if parser.side_effects_item.is_some() {
      return;
    }
    match stmt {
      Statement::If(if_stmt) => {
        if !is_pure_expression(
          parser,
          self.analyze_pure_notation,
          &if_stmt.test,
          self.unresolve_ctxt,
          parser.comments,
        ) {
          parser.side_effects_item = Some(SideEffectsBailoutItemWithSpan::new(
            if_stmt.span(),
            String::from("Statement"),
          ));
        }
      }
      Statement::While(while_stmt) => {
        if !is_pure_expression(
          parser,
          self.analyze_pure_notation,
          &while_stmt.test,
          self.unresolve_ctxt,
          parser.comments,
        ) {
          parser.side_effects_item = Some(SideEffectsBailoutItemWithSpan::new(
            while_stmt.span(),
            String::from("Statement"),
          ));
        }
      }
      Statement::DoWhile(do_while_stmt) => {
        if !is_pure_expression(
          parser,
          self.analyze_pure_notation,
          &do_while_stmt.test,
          self.unresolve_ctxt,
          parser.comments,
        ) {
          parser.side_effects_item = Some(SideEffectsBailoutItemWithSpan::new(
            do_while_stmt.span(),
            String::from("Statement"),
          ));
        }
      }
      Statement::For(for_stmt) => {
        let pure_init = match for_stmt.init {
          Some(ref init) => match init {
            VarDeclOrExpr::VarDecl(decl) => is_pure_var_decl(
              parser,
              self.analyze_pure_notation,
              decl,
              self.unresolve_ctxt,
              parser.comments,
            ),
            VarDeclOrExpr::Expr(expr) => is_pure_expression(
              parser,
              self.analyze_pure_notation,
              expr,
              self.unresolve_ctxt,
              parser.comments,
            ),
          },
          None => true,
        };

        if !pure_init {
          parser.side_effects_item = Some(SideEffectsBailoutItemWithSpan::new(
            for_stmt.span(),
            String::from("Statement"),
          ));
          return;
        }

        let pure_test = match &for_stmt.test {
          Some(test) => is_pure_expression(
            parser,
            self.analyze_pure_notation,
            test,
            self.unresolve_ctxt,
            parser.comments,
          ),
          None => true,
        };

        if !pure_test {
          parser.side_effects_item = Some(SideEffectsBailoutItemWithSpan::new(
            for_stmt.span(),
            String::from("Statement"),
          ));
          return;
        }

        let pure_update = match for_stmt.update {
          Some(ref expr) => is_pure_expression(
            parser,
            self.analyze_pure_notation,
            expr,
            self.unresolve_ctxt,
            parser.comments,
          ),
          None => true,
        };

        if !pure_update {
          parser.side_effects_item = Some(SideEffectsBailoutItemWithSpan::new(
            for_stmt.span(),
            String::from("Statement"),
          ));
        }
      }
      Statement::Expr(expr_stmt) => {
        if !is_pure_expression(
          parser,
          self.analyze_pure_notation,
          &expr_stmt.expr,
          self.unresolve_ctxt,
          parser.comments,
        ) {
          parser.side_effects_item = Some(SideEffectsBailoutItemWithSpan::new(
            expr_stmt.span(),
            String::from("Statement"),
          ));
        }
      }
      Statement::Switch(switch_stmt) => {
        if !is_pure_expression(
          parser,
          self.analyze_pure_notation,
          &switch_stmt.discriminant,
          self.unresolve_ctxt,
          parser.comments,
        ) {
          parser.side_effects_item = Some(SideEffectsBailoutItemWithSpan::new(
            switch_stmt.span(),
            String::from("Statement"),
          ));
        }
      }
      Statement::Class(class_stmt) => {
        if !is_pure_class(
          parser,
          self.analyze_pure_notation,
          class_stmt.class(),
          self.unresolve_ctxt,
          parser.comments,
        ) {
          parser.side_effects_item = Some(SideEffectsBailoutItemWithSpan::new(
            class_stmt.span(),
            String::from("Statement"),
          ));
        }
      }
      Statement::Var(var_stmt) => match var_stmt {
        VariableDeclaration::VarDecl(var_decl) => {
          if !is_pure_var_decl(
            parser,
            self.analyze_pure_notation,
            var_decl,
            self.unresolve_ctxt,
            parser.comments,
          ) {
            parser.side_effects_item = Some(SideEffectsBailoutItemWithSpan::new(
              var_stmt.span(),
              String::from("Statement"),
            ));
          }
        }
        VariableDeclaration::UsingDecl(_) => {
          parser.side_effects_item = Some(SideEffectsBailoutItemWithSpan::new(
            var_stmt.span(),
            String::from("Statement"),
          ));
        }
      },
      Statement::Empty(_) => {}
      Statement::Labeled(_) => {}
      Statement::Block(_) => {}
      Statement::Fn(_) => {}
      _ => {
        parser.side_effects_item = Some(SideEffectsBailoutItemWithSpan::new(
          stmt.span(),
          String::from("Statement"),
        ))
      }
    };
  }
}

pub fn is_pure_pat<'a>(
  parser: &mut JavascriptParser,
  analyze_pure_notation: bool,
  pat: &'a Pat,
  unresolved_ctxt: SyntaxContext,
  comments: Option<&'a dyn Comments>,
) -> bool {
  match pat {
    Pat::Ident(_) => true,
    Pat::Array(array_pat) => array_pat.elems.iter().all(|ele| {
      if let Some(pat) = ele {
        is_pure_pat(
          parser,
          analyze_pure_notation,
          pat,
          unresolved_ctxt,
          comments,
        )
      } else {
        true
      }
    }),
    Pat::Rest(_) => true,
    Pat::Invalid(_) | Pat::Assign(_) | Pat::Object(_) => false,
    Pat::Expr(expr) => is_pure_expression(
      parser,
      analyze_pure_notation,
      expr,
      unresolved_ctxt,
      comments,
    ),
  }
}

pub fn is_pure_function<'a>(
  parser: &mut JavascriptParser,
  analyze_pure_notation: bool,
  function: &'a Function,
  unresolved_ctxt: SyntaxContext,
  comments: Option<&'a dyn Comments>,
) -> bool {
  if !function.params.iter().all(|param| {
    is_pure_pat(
      parser,
      analyze_pure_notation,
      &param.pat,
      unresolved_ctxt,
      comments,
    )
  }) {
    return false;
  }

  true
}

pub fn is_pure_expression<'a>(
  parser: &mut JavascriptParser,
  analyze_pure_notation: bool,
  expr: &'a Expr,
  unresolved_ctxt: SyntaxContext,
  comments: Option<&'a dyn Comments>,
) -> bool {
  pub fn _is_pure_expression<'a>(
    parser: &mut JavascriptParser,
    analyze_pure_notation: bool,
    expr: &'a Expr,
    unresolved_ctxt: SyntaxContext,
    comments: Option<&'a dyn Comments>,
  ) -> bool {
    let drive = parser.plugin_drive.clone();
    if let Some(res) = drive.is_pure(parser, expr) {
      return res;
    }

    match expr {
      Expr::Call(_) => is_pure_call_expr(
        parser,
        analyze_pure_notation,
        expr,
        unresolved_ctxt,
        comments,
      ),
      Expr::Paren(_) => unreachable!(),
      Expr::Seq(seq_expr) => seq_expr.exprs.iter().all(|expr| {
        is_pure_expression(
          parser,
          analyze_pure_notation,
          expr,
          unresolved_ctxt,
          comments,
        )
      }),
      _ => !expr.may_have_side_effects(ExprCtx {
        unresolved_ctxt,
        is_unresolved_ref_safe: true,
        in_strict: false,
        remaining_depth: 4,
      }),
    }
  }
  _is_pure_expression(
    parser,
    analyze_pure_notation,
    expr,
    unresolved_ctxt,
    comments,
  )
}

pub fn is_pure_class_member<'a>(
  parser: &mut JavascriptParser,
  analyze_pure_notation: bool,
  member: &'a ClassMember,
  unresolved_ctxt: SyntaxContext,
  comments: Option<&'a dyn Comments>,
) -> bool {
  let is_key_pure = match member.class_key() {
    Some(PropName::Ident(_ident)) => true,
    Some(PropName::Str(_)) => true,
    Some(PropName::Num(_)) => true,
    Some(PropName::Computed(computed)) => is_pure_expression(
      parser,
      analyze_pure_notation,
      &computed.expr,
      unresolved_ctxt,
      comments,
    ),
    Some(PropName::BigInt(_)) => true,
    None => true,
  };
  if !is_key_pure {
    return false;
  }
  let is_static = member.is_static();
  let is_value_pure = match member {
    ClassMember::Constructor(_) => true,
    ClassMember::Method(_) => true,
    ClassMember::PrivateMethod(_) => true,
    ClassMember::ClassProp(prop) => {
      if let Some(ref value) = prop.value {
        is_pure_expression(
          parser,
          analyze_pure_notation,
          value,
          unresolved_ctxt,
          comments,
        )
      } else {
        true
      }
    }
    ClassMember::PrivateProp(prop) => {
      if let Some(ref value) = prop.value {
        is_pure_expression(
          parser,
          analyze_pure_notation,
          value,
          unresolved_ctxt,
          comments,
        )
      } else {
        true
      }
    }
    ClassMember::TsIndexSignature(_) => unreachable!(),
    ClassMember::Empty(_) => true,
    ClassMember::StaticBlock(_) => false,
    ClassMember::AutoAccessor(_) => false,
  };
  if is_static && !is_value_pure {
    return false;
  }
  true
}

pub fn is_pure_decl(
  parser: &mut JavascriptParser,
  analyze_pure_notation: bool,
  stmt: &Decl,
  unresolved_ctxt: SyntaxContext,
  comments: Option<&dyn Comments>,
) -> bool {
  match stmt {
    Decl::Class(class) => is_pure_class(
      parser,
      analyze_pure_notation,
      &class.class,
      unresolved_ctxt,
      comments,
    ),
    Decl::Fn(_) => true,
    Decl::Var(var) => is_pure_var_decl(
      parser,
      analyze_pure_notation,
      var,
      unresolved_ctxt,
      comments,
    ),
    Decl::Using(_) => false,
    Decl::TsInterface(_) => unreachable!(),
    Decl::TsTypeAlias(_) => unreachable!(),

    Decl::TsEnum(_) => unreachable!(),
    Decl::TsModule(_) => unreachable!(),
  }
}

pub fn is_pure_class(
  parser: &mut JavascriptParser,
  analyze_pure_notation: bool,
  class: &Class,
  unresolved_ctxt: SyntaxContext,
  comments: Option<&dyn Comments>,
) -> bool {
  if let Some(ref super_class) = class.super_class
    && !is_pure_expression(
      parser,
      analyze_pure_notation,
      super_class,
      unresolved_ctxt,
      comments,
    )
  {
    return false;
  }
  let is_pure_key = |parser: &mut JavascriptParser, key: &PropName| -> bool {
    match key {
      PropName::BigInt(_) | PropName::Ident(_) | PropName::Str(_) | PropName::Num(_) => true,
      PropName::Computed(computed) => is_pure_expression(
        parser,
        analyze_pure_notation,
        &computed.expr,
        unresolved_ctxt,
        comments,
      ),
    }
  };

  class.body.iter().all(|item| -> bool {
    match item {
      ClassMember::Constructor(_) => class.super_class.is_none(),
      ClassMember::Method(method) => is_pure_key(parser, &method.key),
      ClassMember::PrivateMethod(method) => is_pure_expression(
        parser,
        analyze_pure_notation,
        &Expr::PrivateName(method.key.clone()),
        unresolved_ctxt,
        comments,
      ),
      ClassMember::ClassProp(prop) => {
        is_pure_key(parser, &prop.key)
          && (!prop.is_static
            || if let Some(ref value) = prop.value {
              is_pure_expression(
                parser,
                analyze_pure_notation,
                value,
                unresolved_ctxt,
                comments,
              )
            } else {
              true
            })
      }
      ClassMember::PrivateProp(prop) => {
        is_pure_expression(
          parser,
          analyze_pure_notation,
          &Expr::PrivateName(prop.key.clone()),
          unresolved_ctxt,
          comments,
        ) && (!prop.is_static
          || if let Some(ref value) = prop.value {
            is_pure_expression(
              parser,
              analyze_pure_notation,
              value,
              unresolved_ctxt,
              comments,
            )
          } else {
            true
          })
      }
      ClassMember::TsIndexSignature(_) => unreachable!(),
      ClassMember::Empty(_) => true,
      ClassMember::StaticBlock(_) => false, // TODO: support is pure analyze for statements
      ClassMember::AutoAccessor(_) => false,
    }
  })
}

fn is_pure_var_decl<'a>(
  parser: &mut JavascriptParser,
  analyze_pure_notation: bool,
  var: &'a VarDecl,
  unresolved_ctxt: SyntaxContext,
  comments: Option<&'a dyn Comments>,
) -> bool {
  var.decls.iter().all(|decl| {
    if let Some(ref init) = decl.init {
      is_pure_expression(
        parser,
        analyze_pure_notation,
        init,
        unresolved_ctxt,
        comments,
      )
    } else {
      true
    }
  })

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread website/docs/zh/guide/optimization/tree-shaking.mdx Outdated
Comment thread crates/rspack_plugin_javascript/src/plugin/side_effects_flag_plugin.rs Outdated
Comment thread crates/rspack_plugin_javascript/src/parser_plugin/side_effects_parser_plugin.rs Outdated
Comment thread crates/rspack_plugin_javascript/src/plugin/side_effects_flag_plugin.rs Outdated
Comment thread website/docs/en/guide/optimization/tree-shaking.mdx Outdated
Comment thread crates/rspack_plugin_javascript/src/plugin/side_effects_flag_plugin.rs Outdated
Comment thread crates/rspack_plugin_javascript/src/parser_plugin/side_effects_parser_plugin.rs Outdated
Comment thread crates/rspack_plugin_javascript/src/parser_plugin/side_effects_parser_plugin.rs Outdated
Comment thread website/docs/zh/guide/optimization/tree-shaking.mdx Outdated
Comment thread tests/rspack-test/configCases/tree-shaking/user-mark-pure-functions/index.js Outdated
@JSerFeng JSerFeng marked this pull request as draft December 26, 2025 06:25
@JSerFeng JSerFeng changed the title feat: support NO_SIDE_EFFECTS notation feat: support NO_SIDE_EFFECTS notation PART 1 Dec 26, 2025
@JSerFeng JSerFeng force-pushed the feat/pure-fn branch 2 times, most recently from 54392cb to 0d2f8a6 Compare December 29, 2025 07:22
@codspeed-hq
Copy link
Copy Markdown

codspeed-hq Bot commented Dec 29, 2025

Merging this PR will degrade performance by 36.56%

🎉 Hooray! codspeed-node just leveled up to 5.2.0!

A heads-up, this is a breaking change and it might affect your current performance baseline a bit. But here's the exciting part - it's packed with new, cool features and promises improved result stability 🥳!
Curious about what's new? Visit our releases page to delve into all the awesome details about this new version.

❌ 2 (👁 1) regressed benchmarks
✅ 7 untouched benchmarks
⏩ 28 skipped benchmarks1

⚠️ Please fix the performance issues or acknowledge them on CodSpeed.

Performance Changes

Mode Benchmark BASE HEAD Efficiency
👁 Simulation js@Traverse module graph by dependencies 530.2 µs 835.7 µs -36.56%
Simulation js@collect imported identifiers 210 µs 212.7 µs -1.28%

Comparing feat/pure-fn (5843920) with main (640f999)

Open in CodSpeed

Footnotes

  1. 28 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

@JSerFeng JSerFeng force-pushed the feat/pure-fn branch 5 times, most recently from 2af18ae to ac6765f Compare December 30, 2025 07:06
@JSerFeng JSerFeng marked this pull request as ready for review December 30, 2025 07:26
@JSerFeng JSerFeng force-pushed the feat/pure-fn branch 2 times, most recently from 4eb6cfe to 9bc3ebc Compare January 7, 2026 07:27
@JSerFeng JSerFeng changed the title feat: support NO_SIDE_EFFECTS notation PART 1 feat: support optimize side effects free function calls Jan 7, 2026
@JSerFeng JSerFeng force-pushed the feat/pure-fn branch 3 times, most recently from 5898d5c to d48437b Compare January 7, 2026 11:08
@JSerFeng
Copy link
Copy Markdown
Contributor Author

JSerFeng commented Apr 7, 2026

@codex review

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 58439205d0

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@JSerFeng JSerFeng enabled auto-merge (squash) April 7, 2026 05:10
@hardfist hardfist disabled auto-merge April 7, 2026 05:57
@hardfist hardfist merged commit 7165b23 into main Apr 7, 2026
36 of 38 checks passed
@hardfist hardfist deleted the feat/pure-fn branch April 7, 2026 05:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

release: feature release: feature related release(mr only) team The issue/pr is created by the member of Rspack.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants