Skip to content

Conversation

@JSerFeng
Copy link
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

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
Contributor

github-actions bot commented Dec 25, 2025

Rsdoctor Bundle Diff Analysis

Found 5 projects in monorepo, 0 projects with changes.

📊 Quick Summary
Project Total Size Change
react-10k 5.7 MB 0
react-1k 825.4 KB 0
react-5k 2.7 MB 0
rome 984.3 KB 0
ui-components 2.1 MB 0

Generated by Rsdoctor GitHub Action

@github-actions
Copy link
Contributor

github-actions bot commented Dec 25, 2025

📦 Binary Size-limit

Comparing a4929d0 to feat: add requireAlias option to control require variable renaming (#12686) by harpsealjs

❌ Size increased by 70.38KB from 47.88MB to 47.95MB (⬆️0.14%)

@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
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.

@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

codspeed-hq bot commented Dec 29, 2025

Merging this PR will degrade performance by 36.51%

Summary

❌ 1 regressed benchmark
✅ 15 untouched benchmarks
⏩ 1 skipped benchmark1

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

Performance Changes

Benchmark BASE HEAD Efficiency
js@Traverse module graph by dependencies 520.7 µs 820.2 µs -36.51%

Comparing feat/pure-fn (a4929d0) with main (b81dfa2)

Open in CodSpeed

Footnotes

  1. 1 benchmark was skipped, so the baseline result was used instead. If it was deleted from the codebase, click here and archive it to remove it 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 JSerFeng force-pushed the feat/pure-fn branch 3 times, most recently from 5d7e734 to 4f6ecd8 Compare January 8, 2026 09:29
@github-actions
Copy link
Contributor

github-actions bot commented Jan 8, 2026

📝 Benchmark detail: Open

Name Base (2026-01-09 29642b2) Current Change
10000_big_production-mode_disable-minimize + exec 23.3 s ± 550 ms 23.2 s ± 130 ms -0.36 %
10000_development-mode + exec 1.29 s ± 35 ms 1.3 s ± 20 ms +0.11 %
10000_development-mode_hmr + stats 241 ms ± 3.8 ms 239 ms ± 3.1 ms -0.66 %
10000_development-mode_noop-loader + exec 2.26 s ± 98 ms 2.24 s ± 48 ms -1.30 %
10000_production-mode + exec 1.4 s ± 36 ms 1.38 s ± 43 ms -1.15 %
10000_production-mode_persistent-cold + exec 1.55 s ± 26 ms 1.54 s ± 50 ms -1.01 %
10000_production-mode_persistent-hot + exec 1.07 s ± 30 ms 1.06 s ± 49 ms -1.14 %
arco-pro_development-mode + exec 1.59 s ± 123 ms 1.49 s ± 57 ms -6.63 %
arco-pro_development-mode_hmr + stats 39 ms ± 1.9 ms 40 ms ± 0.88 ms +0.69 %
arco-pro_production-mode + exec 3.02 s ± 66 ms 2.92 s ± 49 ms -3.44 %
arco-pro_production-mode_generate-package-json-webpack-plugin + exec 3.12 s ± 144 ms 2.98 s ± 87 ms -4.58 %
arco-pro_production-mode_persistent-cold + exec 3.1 s ± 69 ms 2.99 s ± 104 ms -3.67 %
arco-pro_production-mode_persistent-hot + exec 1.74 s ± 11 ms 1.73 s ± 62 ms -0.54 %
arco-pro_production-mode_traverse-chunk-modules + exec 3.04 s ± 89 ms 2.93 s ± 47 ms -3.43 %
large-dyn-imports_development-mode + exec 1.57 s ± 27 ms 1.58 s ± 34 ms +0.33 %
large-dyn-imports_production-mode + exec 1.72 s ± 38 ms 1.72 s ± 27 ms -0.21 %
threejs_development-mode_10x + exec 1.32 s ± 32 ms 1.26 s ± 17 ms -4.95 %
threejs_development-mode_10x_hmr + stats 208 ms ± 3.1 ms 205 ms ± 6 ms -1.54 %
threejs_production-mode_10x + exec 4.1 s ± 64 ms 4.02 s ± 36 ms -1.87 %
threejs_production-mode_10x_persistent-cold + exec 4.21 s ± 58 ms 4.14 s ± 82 ms -1.77 %
threejs_production-mode_10x_persistent-hot + exec 3.66 s ± 67 ms 3.52 s ± 29 ms -3.98 %
10000_big_production-mode_disable-minimize + rss memory 5802 MiB ± 170 MiB 5780 MiB ± 104 MiB -0.38 %
10000_development-mode + rss memory 621 MiB ± 24.9 MiB 624 MiB ± 26.1 MiB +0.56 %
10000_development-mode_hmr + rss memory 768 MiB ± 11.7 MiB 764 MiB ± 14.7 MiB -0.47 %
10000_development-mode_noop-loader + rss memory 918 MiB ± 15.3 MiB 917 MiB ± 54.4 MiB -0.02 %
10000_production-mode + rss memory 637 MiB ± 18 MiB 647 MiB ± 48.7 MiB +1.47 %
10000_production-mode_persistent-cold + rss memory 762 MiB ± 26 MiB 772 MiB ± 30.9 MiB +1.35 %
10000_production-mode_persistent-hot + rss memory 743 MiB ± 31.1 MiB 735 MiB ± 43.9 MiB -1.06 %
arco-pro_development-mode + rss memory 569 MiB ± 26.9 MiB 603 MiB ± 66.1 MiB +5.99 %
arco-pro_development-mode_hmr + rss memory 448 MiB ± 14.5 MiB 488 MiB ± 18.5 MiB +8.84 %
arco-pro_production-mode + rss memory 661 MiB ± 64.4 MiB 698 MiB ± 48.3 MiB +5.59 %
arco-pro_production-mode_generate-package-json-webpack-plugin + rss memory 680 MiB ± 77.4 MiB 703 MiB ± 59.7 MiB +3.39 %
arco-pro_production-mode_persistent-cold + rss memory 733 MiB ± 82 MiB 780 MiB ± 30.9 MiB +6.41 %
arco-pro_production-mode_persistent-hot + rss memory 562 MiB ± 88.6 MiB 569 MiB ± 115 MiB +1.33 %
arco-pro_production-mode_traverse-chunk-modules + rss memory 668 MiB ± 54.8 MiB 691 MiB ± 40.9 MiB +3.47 %
large-dyn-imports_development-mode + rss memory 653 MiB ± 6.05 MiB 654 MiB ± 7.15 MiB +0.24 %
large-dyn-imports_production-mode + rss memory 564 MiB ± 8.33 MiB 569 MiB ± 15.7 MiB +0.98 %
threejs_development-mode_10x + rss memory 601 MiB ± 31.3 MiB 606 MiB ± 27.3 MiB +0.97 %
threejs_development-mode_10x_hmr + rss memory 814 MiB ± 15 MiB 821 MiB ± 22.2 MiB +0.77 %
threejs_production-mode_10x + rss memory 703 MiB ± 150 MiB 721 MiB ± 140 MiB +2.62 %
threejs_production-mode_10x_persistent-cold + rss memory 825 MiB ± 71.7 MiB 836 MiB ± 31.8 MiB +1.34 %
threejs_production-mode_10x_persistent-hot + rss memory 655 MiB ± 48.4 MiB 664 MiB ± 60.3 MiB +1.38 %

/// @experimental
pub jsx: Option<bool>,
pub defer_import: Option<bool>,
pub side_effects_free: Option<Vec<String>>,
Copy link
Contributor

Choose a reason for hiding this comment

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

rename to no_side_effects? since the annotation is #NO_SIDE_EFFECTS#

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'm thinking that this can be used in more common way in the future, for example member access a.b, so I prefer this name more generic

Copy link
Contributor

@ahabhgk ahabhgk left a comment

Choose a reason for hiding this comment

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

Generally looks good.

Suggestion about naming: side_effects and side_effects_free is our core model, so I suggest for our core code using side_effects_free_xxx and side_effects_xxx for naming, and for user-level config, use pure/no_side_effects (more common at community) for naming

dependency::{ESMExportImportedSpecifierDependency, ESMImportSpecifierDependency},
};

pub static SIDE_EFFECTS_FLAG_PLUGIN_STAGE: i32 = FLAG_DEPENDENCY_EXPORTS_STAGE + 1;
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
pub static SIDE_EFFECTS_FLAG_PLUGIN_STAGE: i32 = FLAG_DEPENDENCY_EXPORTS_STAGE + 1;
pub static SIDE_EFFECTS_FLAG_PLUGIN_STAGE: i32 = FLAG_DEPENDENCY_EXPORTS_STAGE + 10;

deferImport?: boolean;

/** Flag the function to have no side effects */
sideEffectsFree?: string[];
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
sideEffectsFree?: string[];
noSideEffects?: string[];

if parser.side_effects_item.is_none() {
for (callee, span) in callees {
if let Some(deferred) = try_extract_deferred_check(parser, callee, span) {
parser.build_info.deferred_pure_checks.insert(deferred);
Copy link
Contributor

Choose a reason for hiding this comment

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

deferred is confusing, maybe rename to maybe_side_effects_free?

Suggested change
parser.build_info.deferred_pure_checks.insert(deferred);
parser.build_info.maybe_side_effects_free.insert(deferred);


let logger = compilation.get_logger("rspack.SideEffectsFlagPlugin");

let modules_with_impurities = modules
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
let modules_with_impurities = modules
let maybe_side_effects_free_modules = modules

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.

3 participants