Skip to content

Conversation

@justin808
Copy link
Member

Summary

  • Add bundler utility functions that make it easier to write bundler-agnostic configuration code
  • Users no longer need to write conditional logic to handle differences between Webpack and Rspack

New Exports

Export Description
isRspack Boolean indicating if current bundler is Rspack
isWebpack Boolean indicating if current bundler is Webpack
getBundler() Returns the bundler module (webpack or @rspack/core)
getCssExtractPlugin() Returns MiniCssExtractPlugin or CssExtractRspackPlugin
getCssExtractPluginLoader() Returns the CSS extraction loader string
getDefinePlugin() Returns the DefinePlugin for the current bundler
getEnvironmentPlugin() Returns the EnvironmentPlugin for the current bundler
getProvidePlugin() Returns the ProvidePlugin for the current bundler

Usage Examples

Before (manual conditional logic):

const { config } = require('shakapacker');
const isRspack = config.assets_bundler === 'rspack';
const CssPlugin = isRspack 
  ? require('@rspack/core').CssExtractRspackPlugin 
  : require('mini-css-extract-plugin');

After (using new utilities):

const { getCssExtractPlugin, isRspack } = require('shakapacker');
const CssPlugin = getCssExtractPlugin();

Test Plan

  • Added 16 new unit tests for all bundler utility functions
  • All 361 tests pass
  • ESLint passes
  • TypeScript compiles without errors

Closes #875

🤖 Generated with Claude Code

@coderabbitai
Copy link

coderabbitai bot commented Feb 4, 2026

Warning

Rate limit exceeded

@justin808 has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 4 minutes and 16 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch jg/fix-875

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@greptile-apps
Copy link

greptile-apps bot commented Feb 4, 2026

Greptile Overview

Greptile Summary

Introduced bundler utility functions that simplify writing configuration code compatible with both Webpack and Rspack, eliminating the need for manual conditional logic.

Key Changes

  • Added package/utils/bundlerUtils.ts with 8 utility exports (isRspack, isWebpack, getBundler, getCssExtractPlugin, getCssExtractPluginLoader, getDefinePlugin, getEnvironmentPlugin, getProvidePlugin)
  • Exported all utilities from package/index.ts and package/rspack/index.ts for user access
  • Comprehensive test suite with 16 unit tests covering all utility functions
  • Proper error handling using existing requireOrError utility for missing dependencies
  • Well-documented JSDoc comments with usage examples

CHANGELOG Note

According to CLAUDE.md guidelines, this PR adds user-visible features (new utility exports) and should have a CHANGELOG entry. However, no CHANGELOG.md changes were included in this PR.

Confidence Score: 5/5

  • This PR is safe to merge with no significant risks
  • The implementation is clean, well-tested, and follows existing patterns. All 8 utility functions have proper error handling via requireOrError, comprehensive JSDoc documentation, and thorough test coverage (16 tests). The changes are purely additive exports with no modifications to existing functionality. The only missing piece is a CHANGELOG entry, which should be added per project guidelines.
  • No files require special attention

Important Files Changed

Filename Overview
package/utils/bundlerUtils.ts Added comprehensive bundler utility functions with proper error handling and documentation
test/package/bundlerUtils.test.js Added thorough test coverage for all bundler utility functions
package/index.ts Exported new bundler utilities with proper documentation

Sequence Diagram

sequenceDiagram
    participant User as User Code
    participant Index as package/index.ts
    participant BundlerUtils as bundlerUtils.ts
    participant Config as config
    participant RequireOrError as requireOrError
    participant Webpack as webpack
    participant Rspack as @rspack/core

    User->>Index: require('shakapacker')
    Index->>BundlerUtils: import bundler utilities
    
    User->>BundlerUtils: getBundler()
    BundlerUtils->>Config: read assets_bundler
    Config-->>BundlerUtils: 'webpack' or 'rspack'
    
    alt isWebpack
        BundlerUtils->>RequireOrError: require('webpack')
        RequireOrError->>Webpack: load module
        Webpack-->>RequireOrError: webpack module
        RequireOrError-->>BundlerUtils: webpack module
        BundlerUtils-->>User: webpack module
    else isRspack
        BundlerUtils->>RequireOrError: require('@rspack/core')
        RequireOrError->>Rspack: load module
        Rspack-->>RequireOrError: @rspack/core module
        RequireOrError-->>BundlerUtils: @rspack/core module
        BundlerUtils-->>User: @rspack/core module
    end
    
    User->>BundlerUtils: getCssExtractPlugin()
    BundlerUtils->>Config: read assets_bundler
    
    alt isWebpack
        BundlerUtils->>RequireOrError: require('mini-css-extract-plugin')
        RequireOrError-->>BundlerUtils: MiniCssExtractPlugin
        BundlerUtils-->>User: MiniCssExtractPlugin
    else isRspack
        BundlerUtils->>RequireOrError: require('@rspack/core')
        RequireOrError-->>BundlerUtils: @rspack/core
        BundlerUtils->>BundlerUtils: extract CssExtractRspackPlugin
        BundlerUtils-->>User: CssExtractRspackPlugin
    end
    
    User->>BundlerUtils: getDefinePlugin()
    BundlerUtils->>BundlerUtils: call getBundler()
    BundlerUtils->>BundlerUtils: extract DefinePlugin
    BundlerUtils-->>User: DefinePlugin (webpack or rspack)
Loading

@claude
Copy link

claude bot commented Feb 4, 2026

Code Review for PR #880

Great work on this PR! The bundler utility functions are a nice addition that will improve the developer experience for users switching between Webpack and Rspack. Here's my detailed review:

✅ Strengths

  1. Clear API Design: The exported functions have clear, intuitive names and excellent JSDoc documentation with examples
  2. Comprehensive Test Coverage: 16 new tests covering all exported functions with good assertions
  3. Good Error Handling: Uses requireOrError to provide clear error messages when dependencies are missing
  4. Consistent Patterns: Follows existing codebase patterns (e.g., using requireOrError, TypeScript conventions)
  5. Backward Compatibility: Purely additive changes - no breaking changes to existing APIs
  6. Documentation: PR description clearly explains the motivation and usage

🔍 Code Quality Observations

TypeScript Type Safety

The use of unknown as return types is intentional (matching the project's pattern for dynamic requires), but consider these observations:

  1. package/utils/bundlerUtils.ts:60 - getBundler() returns unknown, which is then cast to specific types in other functions. This is consistent with the project's approach for dynamic requires (as seen in the eslint config exemption).

  2. Type assertions pattern: Functions like getDefinePlugin() cast the bundler to { DefinePlugin: unknown }. This is safe given the controlled context, but users won't get type safety when using these functions.

Consideration: While the current approach is consistent with the project, you might want to add a TypeScript declaration file (e.g., package/utils/bundlerUtils.d.ts) in the future to provide better type hints for users, even if the implementation uses unknown.

Test Coverage

The tests are thorough but focus on the default (Webpack) case. Consider adding:

  1. Rspack-specific tests: Test behavior when config.assets_bundler === 'rspack' (would require mocking the config)
  2. Error cases: Test that helpful errors are thrown when required packages are missing
  3. Integration test: Verify that the functions work correctly when used together (e.g., that the plugin from getDefinePlugin() is actually usable)

Example test to add:

describe("when bundler is rspack", () => {
  beforeAll(() => {
    // Mock config to return 'rspack'
    jest.mock("../../package/config", () => ({ assets_bundler: "rspack" }))
  })
  
  test("isRspack is true and isWebpack is false", () => {
    expect(index.isRspack).toBe(true)
    expect(index.isWebpack).toBe(false)
  })
})

Potential Issues

1. Missing Trailing Newline ✓

Checked all files - they properly end with newlines. Good!

2. Export Consistency

Both package/index.ts and package/rspack/index.ts export the new utilities, which is correct. However:

  • package/index.ts uses ES6 imports
  • package/rspack/index.ts uses CommonJS requires

This is intentional based on the existing pattern, but worth noting for consistency.

🎯 Suggestions (Optional Improvements)

1. Consider Memoization for Performance

The getter functions call requireOrError on every invocation. For frequently used plugins, consider memoizing:

let _bundlerCache: unknown | null = null
const getBundler = (): unknown => {
  if (\!_bundlerCache) {
    _bundlerCache = isRspack ? requireOrError("@rspack/core") : requireOrError("webpack")
  }
  return _bundlerCache
}

This is a micro-optimization and not critical since require itself is cached by Node.js.

2. Add Runtime Type Guards

For better safety, you could add runtime checks:

const getDefinePlugin = (): unknown => {
  const bundler = getBundler() as { DefinePlugin: unknown }
  if (\!bundler.DefinePlugin) {
    throw new Error('[SHAKAPACKER]: DefinePlugin not found in bundler module')
  }
  return bundler.DefinePlugin
}

This would catch unexpected API changes in webpack/rspack.

3. Update CHANGELOG.md

Per the project's CLAUDE.md guidelines, this user-visible feature should be documented in CHANGELOG.md. You can use the /update-changelog command for guided updates.

🔒 Security & Performance

  • No security concerns: The code doesn't introduce any vulnerabilities
  • Performance: Minimal overhead from dynamic requires (already cached by Node.js)
  • No breaking changes: All changes are additive

📋 Final Checklist

  • ✅ Code follows project conventions
  • ✅ TypeScript compiles (per PR description)
  • ✅ ESLint configuration updated appropriately
  • ✅ Good JSDoc documentation with examples
  • ✅ Test coverage for happy paths
  • ⚠️ CHANGELOG.md needs update (user-visible feature)
  • ⚠️ Consider adding Rspack-specific tests
  • ⚠️ Consider adding error case tests

Verdict

Approve with minor suggestions. This is solid work that will make Shakapacker easier to use. The suggestions above are enhancements, not blockers. The only required change per project guidelines is updating CHANGELOG.md.

Great job! 🚀

@claude
Copy link

claude bot commented Feb 4, 2026

Code Review - PR #880: Export bundler utility functions

Summary

This PR adds excellent bundler-agnostic utility functions that simplify the developer experience when working with both Webpack and Rspack. The implementation is clean, well-documented, and thoroughly tested.


✅ Strengths

  1. Excellent Documentation: JSDoc comments are comprehensive with clear examples for every function
  2. Strong Test Coverage: 16 tests covering all exported functions with good assertions
  3. Clean API Design: The utility functions provide a simple, intuitive interface
  4. Type Safety: Uses TypeScript with appropriate type annotations
  5. Error Handling: Leverages requireOrError for clear error messages when dependencies are missing
  6. Backward Compatible: Adds new exports without breaking existing functionality
  7. Follows Project Conventions: Adheres to CLAUDE.md guidelines and existing code patterns

🔍 Code Quality Observations

package/utils/bundlerUtils.ts:33-44

The isWebpack implementation assumes any non-rspack bundler is webpack. This is correct given the current codebase, but worth noting:

const isWebpack: boolean = config.assets_bundler !== "rspack"

This works because assets_bundler is optional (assets_bundler?: string in types.ts:37) and defaults to webpack. The logic is sound.

package/utils/bundlerUtils.ts:84-90

getCssExtractPlugin() calls requireOrError() twice for rspack (once in getBundler, once here). This is fine for clarity but could be micro-optimized:

const getCssExtractPlugin = (): unknown => {
  if (isRspack) {
    const rspack = requireOrError("@rspack/core")  // First call
    return rspack.CssExtractRspackPlugin
  }
  return requireOrError("mini-css-extract-plugin")
}

The same pattern appears in getCssExtractPluginLoader() at lines 112-119. This is acceptable since require() is cached by Node.js, but the plugin getter functions could potentially reuse getBundler() result. However, the current approach is more explicit and easier to understand.

package/utils/bundlerUtils.ts:139-186

The plugin getter functions (getDefinePlugin, getEnvironmentPlugin, getProvidePlugin) all follow the same pattern of calling getBundler() and accessing properties. This is clean and consistent.


🧪 Test Coverage

test/package/bundlerUtils.test.js

The tests are well-structured:

  • Tests verify function types and return values
  • Checks mutual exclusivity of isRspack/isWebpack
  • Validates default behavior (webpack mode)
  • Tests plugin properties are defined

Potential Enhancement: Consider adding tests for the rspack mode by temporarily modifying the config, though this may require test infrastructure changes. Current tests adequately cover the default webpack scenario.


⚡ Performance Considerations

  1. Lazy Loading: Functions call requireOrError() on each invocation rather than caching. This is fine since:

    • Node.js caches require() calls internally
    • These functions are typically called during config initialization (not in hot paths)
    • The current approach avoids premature module loading
  2. Multiple Calls: getCssExtractPlugin() and getCssExtractPluginLoader() both call requireOrError("@rspack/core") separately. Node's require cache makes this negligible.


🔒 Security Considerations

No security concerns identified:

  • Uses existing requireOrError utility with proper error handling
  • No user input processing
  • No dynamic code execution beyond standard module loading
  • Appropriate error messages that don't leak sensitive information

📋 Suggestions (Optional Enhancements)

  1. Type Safety Enhancement: Consider adding proper return types instead of unknown:

    // Instead of:
    const getDefinePlugin = (): unknown => { ... }
    
    // Could use:
    type DefinePluginConstructor = typeof import('webpack').DefinePlugin
    const getDefinePlugin = (): DefinePluginConstructor => { ... }

    However, this would require complex conditional types and the current approach is pragmatic given the dual-bundler support.

  2. Test Enhancement: Add a test that validates the functions throw appropriate errors when dependencies are missing (though this might be challenging without mocking).

  3. Performance: Consider memoizing the bundler module if these functions will be called multiple times in tight loops, though this seems unlikely given typical usage patterns.


✅ Final Verdict

Recommendation: Approve and Merge

This is a high-quality PR that:

The code is production-ready. The minor optimizations mentioned above are truly optional and the current implementation prioritizes code clarity and maintainability, which is the right choice.

Great work! 🎉

@claude
Copy link

claude bot commented Feb 4, 2026

Code Review - PR #880

Summary

This PR adds bundler utility functions to simplify writing bundler-agnostic configuration. The implementation is clean, well-documented, and follows the project's patterns.

✅ Strengths

  1. Excellent Documentation: The new bundlerUtils.ts file has comprehensive JSDoc comments with clear examples for each function. This will make it easy for users to adopt these utilities.

  2. Good Abstraction: The utilities abstract away the conditional logic needed when supporting both webpack and rspack, which aligns perfectly with the project's goal of supporting both bundlers seamlessly.

  3. Comprehensive Test Coverage: 16 new tests covering all 8 exported utilities with good assertions. The tests verify:

    • Correct types for each export
    • Mutually exclusive isRspack/isWebpack flags
    • Default behavior (webpack)
    • Return values have expected properties
  4. Type Safety: Good use of TypeScript with proper type imports and explicit boolean types for isRspack/isWebpack.

  5. Error Handling: Leverages existing requireOrError utility which provides helpful error messages when dependencies are missing.

  6. Consistent Export Pattern: The utilities are properly exported from both package/index.ts and package/rspack/index.ts, maintaining consistency with the project's dual entry point structure.

💡 Suggestions

1. TypeScript Return Type Specificity

The functions currently return unknown, which forces users to cast types:

const getBundler = (): unknown =>
  isRspack ? requireOrError("@rspack/core") : requireOrError("webpack")

Consider: While I understand this is intentional (added to ESLint's type safety improvement list), for better DX you could add type overloads or conditional types in a future PR:

type BundlerModule = typeof import('webpack') | typeof import('@rspack/core')
const getBundler = (): BundlerModule => ...

This would enable autocompletion for users. However, this is not a blocker and can be addressed in the type safety improvement pass.

2. Memoization Opportunity

The getBundler() function is called multiple times internally (by getDefinePlugin, getEnvironmentPlugin, getProvidePlugin). While the overhead is minimal, you could consider memoizing the bundler module:

let cachedBundler: unknown
const getBundler = (): unknown => {
  if (\!cachedBundler) {
    cachedBundler = isRspack ? requireOrError("@rspack/core") : requireOrError("webpack")
  }
  return cachedBundler
}

Note: This is a micro-optimization and not necessary for correctness. The current implementation is fine.

3. Test Enhancement - Rspack Code Path

The tests only verify the webpack code path (default behavior). Consider adding tests for the rspack path in a follow-up or documenting that rspack-specific testing requires a different test environment setup.

// Could add in a separate test suite with mocked config
describe('when bundler is rspack', () => {
  beforeEach(() => {
    // Mock config.assets_bundler = 'rspack'
  })
  // Test rspack-specific return values
})

However, this might require test infrastructure changes, so it's okay to defer.

🔒 Security

No security concerns identified. The code uses existing requireOrError utility which properly handles missing dependencies.

⚡ Performance

No performance concerns. The utilities are simple getter functions with minimal overhead.

🧪 Testing

Test coverage is good for the default webpack case. All tests pass according to the PR description.

📋 Nitpicks

  1. Line 138 in package/rspack/index.ts: Missing trailing newline (though this might be auto-fixed by prettier/eslint).

  2. Consistency: The order of exports in comments vs actual exports is consistent - nice attention to detail!

✅ Project Guidelines Compliance

  • ✅ Trailing newlines present in all files
  • ✅ No unnecessary comments (all JSDoc is useful)
  • ✅ Follows existing patterns (similar to other utility modules)
  • ✅ Test coverage included
  • ✅ TypeScript properly configured

🎯 Recommendation

APPROVE - This is a well-implemented feature that provides clear value to users. The code quality is high, documentation is excellent, and tests are comprehensive. The minor suggestions above are optional enhancements that don't block merging.

Great work! This will make it much easier for users to write bundler-agnostic configurations. 🚀

@claude
Copy link

claude bot commented Feb 4, 2026

Code Review - PR #880: Export bundler utility functions

Summary

This PR adds excellent bundler-agnostic utility functions that significantly improve the developer experience when working with both Webpack and Rspack. The implementation is clean, well-documented, and thoroughly tested.


✅ Strengths

1. Code Quality & Design

  • Excellent abstraction: The utilities elegantly hide the complexity of bundler-specific differences
  • Consistent API: All getter functions follow a clear naming pattern (get<PluginName>)
  • Type safety: Proper TypeScript types with unknown return types (appropriate given dynamic nature)
  • JSDoc documentation: Comprehensive documentation with clear examples for every export
  • DRY principle: Properly reuses getBundler() in plugin getter functions (lines 140, 161, 185 in bundlerUtils.ts)

2. Error Handling

  • Robust error handling: Uses requireOrError helper consistently, providing clear error messages when packages are missing
  • User-friendly messages: Error messages direct users to documentation for peer dependencies

3. Test Coverage

  • Comprehensive tests: 16 tests covering all 8 exported utilities
  • Good test structure: Well-organized with logical groupings using describe blocks
  • Appropriate assertions: Tests verify both type correctness and actual functionality
  • Default behavior tested: Confirms webpack is the default bundler

4. Documentation

  • CHANGELOG: Properly formatted entry with clear before/after examples
  • Inline examples: Every function has practical usage examples in JSDoc
  • PR description: Excellent documentation of the feature with usage table

🔍 Minor Observations

1. Performance Consideration

In getCssExtractPlugin() and getCssExtractPluginLoader(), the bundler is loaded twice:

const getCssExtractPlugin = (): unknown => {
  if (isRspack) {
    const rspack = requireOrError("@rspack/core")  // First load
    return rspack.CssExtractRspackPlugin
  }
  return requireOrError("mini-css-extract-plugin")
}

const getCssExtractPluginLoader = (): string => {
  if (isRspack) {
    const rspack = requireOrError("@rspack/core")  // Second load
    return rspack.CssExtractRspackPlugin.loader
  }
  // ...
}

Impact: Node.js module caching makes this a non-issue - subsequent require() calls return the cached module. No action needed, but worth noting for understanding.

2. Type Safety Trade-off

The use of unknown return types is appropriate here since the actual types depend on runtime bundler selection. However, this means users won't get IDE autocomplete for plugin options.

Why this is acceptable:

  • The bundler plugins have different type signatures, so a union type wouldn't work well
  • Users typically already know the plugin API they're targeting
  • The alternative would require complex conditional types that add minimal value

3. ESLint Configuration

The file is added to the list requiring relaxed type safety rules (eslint.config.js:236). This is reasonable given the dynamic require() statements, but consider if stricter typing could be achieved in future refactoring.


🔒 Security

No concerns identified

  • No external input processing
  • No security-sensitive operations
  • Uses existing requireOrError helper which has been vetted

🎯 Best Practices Alignment

Follows CLAUDE.md guidelines:

  • All files end with trailing newline
  • Focused, minimal changes
  • Existing patterns maintained
  • CHANGELOG properly updated with PR link format
  • Tests added for new functionality

Code conventions:

  • Matches existing codebase style
  • Uses established utilities (requireOrError, config patterns)
  • Consistent with rspack/index.ts export pattern

📝 Suggestions (Optional)

Consider caching for repeated calls

If users call these functions repeatedly in their config (unlikely but possible), consider memoization:

let cachedBundler: unknown
const getBundler = (): unknown => {
  if (!cachedBundler) {
    cachedBundler = isRspack ? requireOrError("@rspack/core") : requireOrError("webpack")
  }
  return cachedBundler
}

Note: This is likely premature optimization since webpack configs are loaded once. Current implementation is cleaner.

Future enhancement: Type guards

Consider adding type guards for advanced users:

export const isWebpackBundler = (bundler: unknown): bundler is typeof webpack => isWebpack

Note: Low priority - the boolean flags are sufficient for most use cases.


✨ Overall Assessment

Recommendation: ✅ APPROVE

This is a high-quality PR that:

The implementation is production-ready. Great work! 🎉


Test Results: Per PR description, all 361 tests pass, ESLint passes, and TypeScript compiles without errors.

@claude
Copy link

claude bot commented Feb 4, 2026

Code Review Summary

Excellent work on this PR! The bundler utility functions are a well-designed addition that significantly improves the developer experience. Here's my detailed review:


✅ Strengths

Code Quality

  • Well-structured and maintainable: The bundler utilities are cleanly separated in package/utils/bundlerUtils.ts with clear separation of concerns
  • Excellent documentation: Comprehensive JSDoc comments with usage examples for every function
  • Strong TypeScript typing: Well-defined interfaces (PluginConstructor, CssExtractPluginConstructor, BundlerModule) provide good type safety
  • Consistent error handling: Uses the existing requireOrError utility which provides helpful error messages with documentation links
  • Good export structure: Functions are properly exported from both main entry points (package/index.ts and package/rspack/index.ts)

API Design

  • Intuitive naming: Function names like getBundler(), getCssExtractPlugin() are self-explanatory
  • Backwards compatible: Adds new exports without breaking existing API
  • Solves real pain points: Eliminates the need for users to write repetitive conditional logic

Testing

  • 16 new unit tests covering all exported functions
  • Tests verify function existence, return types, and basic functionality
  • All tests passing (361 total)

Documentation

  • Excellent CHANGELOG entry following project conventions with proper PR link format
  • Clear examples in both PR description and inline JSDoc comments
  • Before/after comparisons help users understand the value

💡 Suggestions for Improvement

1. Test Coverage for Rspack Mode

The tests currently only verify webpack mode (the default). Consider adding tests that verify rspack mode behavior:

describe('when bundler is rspack', () => {
  // Would need to mock config.assets_bundler = 'rspack'
  test('isRspack returns true', () => {
    // Test rspack-specific behavior
  })
})

Note: This may require refactoring to allow dependency injection or config mocking. Not a blocker, but would increase confidence.

2. Type Safety in bundlerUtils.ts

The file is added to the ESLint ignore list for type safety issues. While pragmatic for now, consider:

  • The unknown return types on some interfaces could be more specific
  • requireOrError returns unknown which propagates through the codebase

Recommendation: File a technical debt issue to improve type safety in a future PR.

3. Documentation Enhancement

Consider adding these utility functions to the documentation:

  • docs/api-reference.md or create a new docs/bundler-utilities.md
  • docs/rspack.md could mention these utilities for migration

This would help users discover these helpful functions.

4. Minor: Plugin Getter Consistency

Functions like getDefinePlugin() call getBundler() each time. This is fine for current usage, but if performance ever becomes a concern, consider caching the bundler module:

let cachedBundler: BundlerModule | null = null
const getBundler = (): BundlerModule => {
  if (!cachedBundler) {
    cachedBundler = (isRspack ? requireOrError('@rspack/core') : requireOrError('webpack')) as BundlerModule
  }
  return cachedBundler
}

Note: Only optimize if needed - current approach is simpler and clearer.


🔒 Security & Performance

  • ✅ No security concerns identified
  • ✅ Dynamic requires are properly handled with error checking
  • ✅ No performance issues - utilities are lightweight wrappers

📋 Checklist


Verdict

✅ Approve with minor suggestions

This PR is ready to merge. The suggestions above are nice-to-haves that could be addressed in follow-up PRs. The core implementation is solid, well-tested, and provides clear value to users.

The bundler-agnostic API is a significant UX improvement that makes Shakapacker configurations cleaner and easier to maintain. Great work! 🎉


Files Reviewed
  • package/utils/bundlerUtils.ts (239 lines added) - Core implementation ✅
  • test/package/bundlerUtils.test.js (97 lines added) - Test coverage ✅
  • package/index.ts (26 lines modified) - Main exports ✅
  • package/rspack/index.ts (20 lines modified) - Rspack exports ✅
  • package/types/index.ts (8 lines modified) - Type exports ✅
  • CHANGELOG.md (17 lines added) - Documentation ✅
  • eslint.config.js (1 line added) - Config adjustment ✅

🤖 Review generated with Claude Code

@justin808 justin808 added the p2 Medium: enhancements, docs, quality improvements label Feb 9, 2026
justin808 and others added 3 commits February 8, 2026 21:53
Add bundler utility functions that make it easier to write bundler-agnostic
configuration code. Users no longer need to write conditional logic to handle
differences between Webpack and Rspack.

New exports from shakapacker:
- isRspack: Boolean indicating if current bundler is Rspack
- isWebpack: Boolean indicating if current bundler is Webpack
- getBundler(): Returns the bundler module (webpack or @rspack/core)
- getCssExtractPlugin(): Returns MiniCssExtractPlugin or CssExtractRspackPlugin
- getCssExtractPluginLoader(): Returns the CSS extraction loader string
- getDefinePlugin(): Returns the DefinePlugin for the current bundler
- getEnvironmentPlugin(): Returns the EnvironmentPlugin for the current bundler
- getProvidePlugin(): Returns the ProvidePlugin for the current bundler

Usage example:
  const { getCssExtractPlugin, isRspack } = require('shakapacker')
  const CssPlugin = getCssExtractPlugin()
  new CssPlugin({ filename: 'styles.css' })

Closes #875

Co-Authored-By: Claude Opus 4.5 <[email protected]>
Improves developer experience by providing proper TypeScript types:
- BundlerModule: Interface describing the bundler module
- PluginConstructor: Common interface for plugin constructors
- CssExtractPluginConstructor: Typed CSS extraction plugin
- CssExtractPluginOptions: Options for CSS extraction plugins

Users now get autocompletion when using these utilities.

Co-Authored-By: Claude Opus 4.5 <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

p2 Medium: enhancements, docs, quality improvements

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Enhancement: Export bundler utility functions for Webpack/Rspack compatibility

1 participant