Skip to main content

Optional Peer Dependencies in Shakapacker

Tip (Shakapacker 10.1+): This document describes the optional peer dependency model used by the core shakapacker package. If you want a tested, lockstep-pinned managed-build stack without listing each peer yourself, install shakapacker-webpack or shakapacker-rspack instead. See the v10.1 supplemental packages migration guide for adoption steps and docs/dependency-strategy.md for the design rationale.

Overview​

As of Shakapacker v9 (and continuing in v10), all peer dependencies are marked as optional via peerDependenciesMeta. This design provides maximum flexibility while maintaining clear version constraints.

Key Benefits​

  1. No Installation Warnings - Package managers (npm, yarn, pnpm) won't warn about missing peer dependencies
  2. Install Only What You Need - Users only install packages for their chosen configuration
  3. Clear Version Constraints - When packages are installed, version compatibility is still enforced
  4. Smaller Node Modules - Reduced disk usage by not installing unnecessary packages

Implementation Details​

Package.json Structure​

{
"dependencies": {
"js-yaml": "^4.1.0",
"path-complete-extname": "^1.0.0",
"webpack-merge": "^5.8.0" // Direct dependency - always available
},
"peerDependencies": {
"webpack": "^5.101.0",
"@rspack/core": "^2.0.0"
// ... all build tools
},
"peerDependenciesMeta": {
"webpack": { "optional": true },
"@rspack/core": { "optional": true }
// ... all marked as optional
}
}

TypeScript Type-Only Imports​

To prevent runtime errors when optional packages aren't installed, all webpack imports use type-only syntax:

// @ts-ignore: webpack is an optional peer dependency (using type-only import)
import type { Configuration } from "webpack"

Type-only imports are erased during compilation and don't trigger module resolution at runtime.

Configuration Examples​

Required Dependencies by Configuration​

Webpack + Babel (Traditional)​

{
"dependencies": {
"shakapacker": "^10.0.0",
"webpack": "^5.101.0",
"webpack-cli": "^6.0.0",
"babel-loader": "^8.2.4",
"@babel/core": "^7.17.9",
"@babel/preset-env": "^7.16.11"
}
}

Webpack + SWC (faster transpilation — see benchmarks)​

{
"dependencies": {
"shakapacker": "^10.0.0",
"webpack": "^5.101.0",
"webpack-cli": "^6.0.0",
"@swc/core": "^1.3.0",
"swc-loader": "^0.2.0"
}
}

Note: webpack-cli v7 is also supported but requires Node.js >= 20.9.0. If your project meets that requirement, you can use "webpack-cli": "^7.0.0" instead.

Rspack + SWC (largest end-to-end speedup — see benchmarks)​

{
"dependencies": {
"shakapacker": "^10.0.0",
"@rspack/core": "^2.0.0",
"@rspack/cli": "^2.0.0",
"@rspack/dev-server": "^2.0.0",
"rspack-manifest-plugin": "^5.2.2"
}
}

Migration Guide​

From v8 to v9+​

If upgrading from Shakapacker v8:

  1. No action required - Your existing dependencies will continue to work
  2. No more warnings - Peer dependency warnings will disappear after upgrading
  3. Option to optimize - You can now remove unused dependencies (e.g., remove Babel if using SWC)

New Installations​

The installer (bundle exec rake shakapacker:install) only adds packages needed for your configuration:

  • Detects your preferred bundler (webpack/rspack)
  • Installs appropriate JavaScript transpiler (babel/swc/esbuild)
  • Adds only required dependencies

Version Constraints​

Version ranges are carefully chosen for compatibility:

  • Broader ranges for peer deps - Allows flexibility (e.g., ^5.101.0 for webpack)
  • Specific versions in devDeps - Ensures testing against known versions
  • Forward compatibility - Ranges include future minor versions (e.g., ^5.0.0 || ^6.0.0)

Testing​

Installation Tests​

Test that no warnings appear during installation:

# Test script available at test/peer-dependencies.sh
./test/peer-dependencies.sh

Runtime Tests​

Verify Shakapacker loads without optional dependencies:

// This works even without webpack installed (when using rspack)
const shakapacker = require("shakapacker")

CI Integration​

The test suite includes:

  • spec/shakapacker/optional_dependencies_spec.rb - Package.json structure validation
  • spec/shakapacker/doctor_optional_peer_spec.rb - Doctor command validation
  • test/peer-dependencies.sh - Installation warning tests

Troubleshooting​

Still seeing peer dependency warnings?​

  1. Ensure you're using Shakapacker v9.0.0 or later
  2. Clear your package manager cache:
    • npm: npm cache clean --force
    • yarn: yarn cache clean
    • pnpm: pnpm store prune
  3. Reinstall dependencies

Module not found errors?​

  1. Check you've installed required dependencies for your configuration
  2. Refer to the configuration examples above
  3. Run bundle exec rake shakapacker:doctor for diagnostics

TypeScript errors?​

The @ts-ignore comments are intentional and necessary for optional dependencies. They prevent TypeScript errors when optional packages aren't installed.

Contributing​

When adding new dependencies:

  1. Add to peerDependencies with appropriate version range
  2. Mark as optional in peerDependenciesMeta
  3. Use type-only imports in TypeScript: import type { ... }
  4. Test with all package managers (npm, yarn, pnpm)
  5. Update this documentation if needed

Design Rationale​

This approach balances several concerns:

  1. User Experience - No confusing warnings during installation
  2. Flexibility - Support multiple configurations without forcing unnecessary installs
  3. Compatibility - Maintain version constraints for safety
  4. Performance - Reduce installation time and disk usage
  5. Type Safety - TypeScript support without runtime dependencies

Future Improvements​

Potential enhancements for future versions:

  1. Conditional exports - Use package.json exports field for better tree-shaking
  2. Dynamic imports - Load bundler-specific code only when needed
  3. Doctor updates - Enhance doctor command to better understand optional dependencies
  4. Automated testing - Add CI jobs testing each configuration combination

References​