Optional Peer Dependencies in Shakapacker
Overview
As of Shakapacker v9, all peer dependencies are marked as optional via peerDependenciesMeta. This design provides maximum flexibility while maintaining clear version constraints.
Key Benefits
- No Installation Warnings - Package managers (npm, yarn, pnpm) won't warn about missing peer dependencies
- Install Only What You Need - Users only install packages for their chosen configuration
- Clear Version Constraints - When packages are installed, version compatibility is still enforced
- 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.76.0",
"@rspack/core": "^1.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
Webpack + Babel (Traditional)
{
"dependencies": {
"shakapacker": "^9.0.0",
"webpack": "^5.76.0",
"webpack-cli": "^5.0.0",
"babel-loader": "^8.2.4",
"@babel/core": "^7.17.9",
"@babel/preset-env": "^7.16.11"
}
}
Webpack + SWC (20x Faster)
{
"dependencies": {
"shakapacker": "^9.0.0",
"webpack": "^5.76.0",
"webpack-cli": "^5.0.0",
"@swc/core": "^1.3.0",
"swc-loader": "^0.2.0"
}
}
Rspack + SWC (10x Faster Bundling)
{
"dependencies": {
"shakapacker": "^9.0.0",
"@rspack/core": "^1.0.0",
"@rspack/cli": "^1.0.0",
"rspack-manifest-plugin": "^5.0.0"
}
}
Migration Guide
From v8 to v9
If upgrading from Shakapacker v8:
- No action required - Your existing dependencies will continue to work
- No more warnings - Peer dependency warnings will disappear after upgrading
- 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.76.0for 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 validationspec/shakapacker/doctor_optional_peer_spec.rb- Doctor command validationtest/peer-dependencies.sh- Installation warning tests
Troubleshooting
Still seeing peer dependency warnings?
- Ensure you're using Shakapacker v9.0.0 or later
- Clear your package manager cache:
- npm:
npm cache clean --force - yarn:
yarn cache clean - pnpm:
pnpm store prune
- npm:
- Reinstall dependencies
Module not found errors?
- Check you've installed required dependencies for your configuration
- Refer to the configuration examples above
- Run
bundle exec rake shakapacker:doctorfor 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:
- Add to
peerDependencieswith appropriate version range - Mark as optional in
peerDependenciesMeta - Use type-only imports in TypeScript:
import type { ... } - Test with all package managers (npm, yarn, pnpm)
- Update this documentation if needed
Design Rationale
This approach balances several concerns:
- User Experience - No confusing warnings during installation
- Flexibility - Support multiple configurations without forcing unnecessary installs
- Compatibility - Maintain version constraints for safety
- Performance - Reduce installation time and disk usage
- Type Safety - TypeScript support without runtime dependencies
Future Improvements
Potential enhancements for future versions:
- Conditional exports - Use package.json exports field for better tree-shaking
- Dynamic imports - Load bundler-specific code only when needed
- Doctor updates - Enhance doctor command to better understand optional dependencies
- Automated testing - Add CI jobs testing each configuration combination