PR #4: Split JS Pro Code to Separate Package - Implementation Plan
This comprehensive plan documents all architectural decisions and implementation steps for separating JavaScript Pro functionality from the core React-on-Rails package into a separate react-on-rails-pro package.
Core Architectural Decisions
1. Package Dependency Strategy
- Decision: Pro package uses core as a dependency (not peer dependency)
- Rationale: Follows React's model, eliminates user version management complexity, prevents "forgetting to import" issues
- Implementation: Pro package exports all core functionality + pro features
- User Experience:
- Core users:
import ReactOnRails from 'react-on-rails' - Pro users:
import ReactOnRails from 'react-on-rails-pro'(gets everything)
- Core users:
- Benefits: Single import decision per project, no multi-entry-point issues
2. Versioning Strategy
- Decision: Caret range strategy (
^16.1.0) - Rationale: Follows React-DOM pattern (
react-domuses^19.1.1for react) - Implementation: Major version alignment required, minor/patch independence allowed
- Pro package.json:
"dependencies": { "react-on-rails": "^16.1.0" }
3. Registry Architecture
- Decision: Dual registry system with direct imports based on package context
- Core Package: Simple Map-based registries (synchronous only, pre-force-load behavior)
- Pro Package: Advanced CallbackRegistry-based (async + synchronous, post-force-load behavior)
- Import Strategy:
- MIT files → Import core registries directly
- Pro files → Import pro registries directly
- Shared files → Use
globalThis.ReactOnRails.get()methods
4. Code Reuse Strategy (DRY Principle)
- Decision: Layer Pro features over Core functionality, reuse core rendering logic
- Implementation: Pro package imports and enhances core components where possible
- Example: Pro ClientSideRenderer uses core
createReactOutput()andreactHydrateOrRender() - Benefits: Maximizes DRY, reduces duplication, clear feature separation
5. Feature Split Strategy
Based on commit 4dee1ff3cff5998a38cfa758dec041ece9986623 analysis:
Core Package (MIT) - Pre-Force-Load Behavior:
- Simple synchronous registries
- Basic rendering without async waiting
- Methods:
register(),getComponent(),getStore(), etc.
Pro Package - Post-Force-Load Behavior:
- Advanced async registries with CallbackRegistry
- Immediate hydration, store dependency waiting
- Methods:
getOrWaitForComponent(),getOrWaitForStore(),reactOnRailsComponentLoaded(), etc.
Implementation Steps
Step 1: Create React-on-Rails-Pro Package Structure
Checkpoint 1.1: Create directory structure
- Create
packages/react-on-rails-pro/directory - Create
packages/react-on-rails-pro/src/directory - Create
packages/react-on-rails-pro/tests/directory - Verify directory structure matches target
Checkpoint 1.2: Create package.json
- Create
packages/react-on-rails-pro/package.jsonwith:"name": "react-on-rails-pro""license": "UNLICENSED""dependencies": { "react-on-rails": "^16.1.0" }- Pro-specific exports configuration matching current pro exports
- Independent build scripts (
build,test,type-check)
- Test that
yarn installworks in pro package directory - Verify dependency resolution works correctly
Checkpoint 1.3: Create TypeScript configuration
- Create
packages/react-on-rails-pro/tsconfig.json - Configure proper import resolution for core package types
- Set output directory to
lib/ - Verify TypeScript compilation setup works
Success Validation:
-
cd packages/react-on-rails-pro && yarn installsucceeds - TypeScript can resolve core package imports
- Directory structure is ready for code
Step 2: Create Simple MIT Registries for Core Package
Checkpoint 2.1: Create simple ComponentRegistry
- Create
packages/react-on-rails/src/ComponentRegistry.tswith:- Simple Map-based storage (
registeredComponents = new Map()) - Synchronous
register(components)method - Synchronous
get(name)method with error on missing component components()method returning Map- Error throwing stub for
getOrWaitForComponent()with message:'getOrWaitForComponent requires react-on-rails-pro package'
- Simple Map-based storage (
- Write unit tests in
packages/react-on-rails/tests/ComponentRegistry.test.js - Verify basic functionality with tests
Checkpoint 2.2: Create simple StoreRegistry
- Create
packages/react-on-rails/src/StoreRegistry.tswith:- Simple Map-based storage for generators and hydrated stores
- All existing synchronous methods:
register(),getStore(),getStoreGenerator(),setStore(),clearHydratedStores(),storeGenerators(),stores() - Error throwing stubs for async methods:
getOrWaitForStore(),getOrWaitForStoreGenerator()
- Write unit tests in
packages/react-on-rails/tests/StoreRegistry.test.js - Verify basic functionality with tests
Checkpoint 2.3: Create simple ClientRenderer
- Create
packages/react-on-rails/src/ClientRenderer.tswith:- Simple synchronous rendering based on pre-force-load
clientStartup.tsimplementation - Direct imports of core registries:
import { get as getComponent } from './ComponentRegistry' - Basic
renderComponent(domId: string)function - Export
reactOnRailsComponentLoadedfunction
- Simple synchronous rendering based on pre-force-load
- Write unit tests for basic rendering
- Test simple component rendering works
Success Validation:
- All unit tests pass
- Core registries work independently
- Simple rendering works without pro features
Step 3: Update Core Package to Use New Registries
Checkpoint 3.1: Update ReactOnRails.client.ts
- Replace pro registry imports with core registry imports:
import * as ComponentRegistry from './ComponentRegistry'import * as StoreRegistry from './StoreRegistry'
- Replace pro ClientSideRenderer import with core ClientRenderer import
- Update all registry method calls to use new core registries
- Ensure pro-only methods throw helpful errors
- Verify core package builds successfully
Checkpoint 3.2: Update other core files
- Update
serverRenderReactComponent.tsto useglobalThis.ReactOnRails.getComponent()instead of direct registry import - Update any other files that might import from pro directories
- Ensure no remaining imports from
./pro/in core files
Checkpoint 3.3: Test core package independence
- Run core package tests:
cd packages/react-on-rails && yarn test - Verify core functionality works without pro features
- Test that pro methods throw appropriate error messages
- Verify core package builds:
cd packages/react-on-rails && yarn build
Success Validation:
- Core package builds successfully
- Core tests pass (expected failures for pro-only features)
- No imports from pro directories remain
- Core functionality works independently
Step 4: Move Pro Files to Pro Package
Checkpoint 4.1: Move Pro JavaScript/TypeScript files
- Move all files from
packages/react-on-rails/src/pro/topackages/react-on-rails-pro/src/using git mv - Preserve directory structure:
CallbackRegistry.tsClientSideRenderer.tsComponentRegistry.tsStoreRegistry.tsReactOnRailsRSC.tsregisterServerComponent/directorywrapServerComponentRenderer/directory- All other pro files (22 files total)
- Git history preserved for all moved files
- Verify all pro files moved correctly (count and validate)
Checkpoint 4.2: Update import paths in moved files
- Update imports in pro files to reference correct paths
- Update imports from core package to use
react-on-railspackage imports (56 imports updated) - Fix relative imports within pro package
- Ensure no circular dependency issues
Checkpoint 4.3: Remove pro directory from core
- Delete empty
packages/react-on-rails/src/pro/directory - Verify no references to old pro paths remain in any files
- Update any remaining import statements that referenced pro paths
Success Validation:
- Pro files exist in correct new locations
- No pro directory remains in core package
- Import paths are correctly updated
- Git history preserved for all moved files
Step 5: Move and Update Pro Tests
Checkpoint 5.1: Identify pro-related tests
- Search for test files importing from pro directories:
streamServerRenderedReactComponent.test.jsxregisterServerComponent.client.test.jsxinjectRSCPayload.test.tsSuspenseHydration.test.tsx
- Identify tests that specifically test pro functionality
- Create list of all test files that need to be moved (4 test files identified)
Checkpoint 5.2: Move pro tests
- Move identified pro tests to
packages/react-on-rails-pro/tests/using git mv - Git history preserved for all moved test files
- Update test import paths to reflect new package structure
- Update Jest configuration if needed for pro package
- Ensure test utilities are available or create pro-specific ones
Checkpoint 5.3: Update remaining core tests
- Update core tests that may have been testing pro functionality to only test core features
- Updated serverRenderReactComponent.test.ts to use core ComponentRegistry
- Core ComponentRegistry and StoreRegistry tests already test core functionality with pro method stubs
- Verify all core tests pass
Success Validation:
- Core tests pass and only test core functionality
- Pro tests are properly moved and can run
- No test dependencies on moved pro files remain in core
Step 6: Create Pro Package Implementation
Checkpoint 6.1: Create pro package main entry point
- Create
packages/react-on-rails-pro/src/index.tsthat:- Imports all core functionality:
import ReactOnRailsCore from 'react-on-rails' - Imports pro registries:
import * as ProComponentRegistry from './ComponentRegistry' - Imports pro features:
import { renderOrHydrateComponent, hydrateStore } from './ClientSideRenderer' - Creates enhanced ReactOnRails object with all core methods plus pro methods
- Sets
globalThis.ReactOnRailsto pro version - Exports enhanced version as default
- Imports all core functionality:
- Ensure pro startup script runs and replaces core startup behavior
Checkpoint 6.2: Configure pro package exports
- Update
packages/react-on-rails-pro/package.jsonexports section - Include all current pro exports:
"."(main entry)"./RSCRoute""./RSCProvider""./registerServerComponent/client""./registerServerComponent/server""./wrapServerComponentRenderer/client""./wrapServerComponentRenderer/server""./ServerComponentFetchError"
- Ensure proper TypeScript declaration exports
Checkpoint 6.3: Test pro package build and functionality
- Verify pro package builds successfully:
cd packages/react-on-rails-pro && yarn build - Test that pro package includes all core functionality
- Test that pro-specific async methods work (
getOrWaitForComponent,getOrWaitForStore) - Verify pro package can be imported and used
Success Validation:
- Pro package builds without errors
- Pro package exports work correctly
- Pro functionality is available when imported
- All core functionality is preserved in pro package
Step 7: Update Workspace Configuration
Checkpoint 7.1: Update root workspace
- Update root
package.jsonworkspaces to include"packages/react-on-rails-pro" - Update workspace scripts:
"build"should build both packages"test"should run tests for both packages"type-check"should check both packages
- Configure build dependencies if pro package needs core built first
Checkpoint 7.2: Test workspace functionality
- Test
yarn buildbuilds both packages successfully - Test
yarn testruns tests for both packages - Test
yarn type-checkchecks both packages - Verify workspace dependency resolution works correctly
Success Validation:
- Workspace commands work for both packages
- Both packages build in correct order
- Workspace dependency resolution is working
Step 8: Update License Compliance
Checkpoint 8.1: Update LICENSE.md
-
Remove
packages/react-on-rails/src/pro/from Pro license section (no longer exists) -
Add
packages/react-on-rails-pro/to Pro license section -
Update license scope to accurately reflect new structure:
## MIT License applies to:
- `lib/react_on_rails/` (including specs)
- `packages/react-on-rails/` (including tests)
## React on Rails Pro License applies to:
- `packages/react-on-rails-pro/` (including tests) (NEW)
- `react_on_rails_pro/` (remaining files) -
Verify all pro directories are listed correctly
-
Ensure no pro code remains in MIT-licensed directories
Checkpoint 8.2: Verify license compliance
- Run automated license check if available
- Verify all pro files have correct license headers
- Manually verify no MIT-licensed directories contain pro code
- Check that
packages/react-on-rails-pro/package.jsonhas"license": "UNLICENSED"
Success Validation:
- LICENSE.md accurately reflects new structure
- All pro files are properly licensed
- No license violations exist
Step 9: Comprehensive Testing and Validation
Checkpoint 9.1: Core package testing
- Run full core package test suite:
cd packages/react-on-rails && yarn test - Test core functionality in dummy Rails app with only core package
- Verify pro methods throw appropriate error messages
- Test that core package works in complete isolation
- Verify core package build:
cd packages/react-on-rails && yarn build
Checkpoint 9.2: Pro package testing
- Run full pro package test suite:
cd packages/react-on-rails-pro && yarn test - Test in dummy Rails app with pro package (should include all core + pro features)
- Test pro-specific features:
- Async component waiting (
getOrWaitForComponent) - Async store waiting (
getOrWaitForStore) - Immediate hydration feature
- RSC functionality
- Async component waiting (
- Verify pro package works as complete replacement for core
Checkpoint 9.3: Integration testing
- Test workspace builds:
yarn buildfrom root - Test workspace tests:
yarn testfrom root - Verify no regressions in existing dummy app functionality
- Test that switching from core to pro package works seamlessly
- Verify all CI checks pass
Success Validation:
- All tests pass for both packages
- No functional regressions
- Pro package provides all core functionality plus enhancements
- Clean upgrade path from core to pro
Step 10: Documentation and Final Cleanup
Checkpoint 10.1: Update package documentation
- Update core package README if needed (mention pro package existence)
- Create
packages/react-on-rails-pro/README.mdwith installation and usage instructions - Update any relevant documentation about package structure
- Document upgrade path from core to pro
Checkpoint 10.2: Final cleanup and verification
- Remove any temporary files or configurations created during migration
- Clean up any commented-out code
- Verify all files are properly organized
- Run final linting:
yarn lintfrom root - Run final type checking:
yarn type-checkfrom root
Success Validation:
- Documentation is complete and accurate
- All temporary artifacts removed
- Final linting and type checking passes
- Packages are ready for production use
Success Criteria
Functional Requirements
- All existing functionality preserved in both packages
- No breaking changes for existing core users
- Pro users get all functionality (core + pro) from single package
- Clean separation between synchronous (core) and asynchronous (pro) features
Technical Requirements
- Both packages build independently without errors
- All CI checks pass for both packages
- TypeScript types work correctly for both packages
- Proper dependency resolution in workspace
- No circular dependencies
License Compliance
- Strict separation between MIT and Pro licensed code
- LICENSE.md accurately reflects all package locations
- All pro files have correct license headers
- No pro code in MIT-licensed directories
User Experience
- Core users: Simple import, basic functionality
- Pro users: Single import, all functionality
- Clear upgrade path from core to pro
- No migration required for existing code
Testing Strategy
After Each Major Step:
- Build Test: Verify affected packages build successfully
- Unit Tests: Run relevant unit test suites
- Integration Test: Test functionality in dummy Rails application
- Regression Check: Ensure no existing functionality broken
- License Validation: Check license compliance maintained
Validation Commands:
# Test workspace
yarn build
yarn test
yarn type-check
yarn lint
# Test individual packages
cd packages/react-on-rails && yarn build && yarn test
cd packages/react-on-rails-pro && yarn build && yarn test
# Test in dummy app
cd react_on_rails/spec/dummy && yarn install && yarn build
Rollback Strategy
Git Strategy:
- Each major step should be a separate commit with clear commit message
- Use descriptive commit messages:
"Step 4.1: Move pro files to pro package" - Tag successful major milestones
Rollback Process:
- Identify Issue: Determine which step introduced the problem
- Revert Commits: Use
git revertto undo problematic changes - Analyze Root Cause: Understand what went wrong
- Fix and Retry: Address the issue and re-attempt the step
- Validate: Ensure fix resolves the problem without introducing new issues
Key Implementation Principles
1. Direct Import Strategy
- MIT files import MIT registries directly (no indirection)
- Pro files import Pro registries directly (access to async methods)
- Shared files use
globalThis.ReactOnRailsfor flexibility
2. No Complex Dependency Injection
- Avoid complex registry injection patterns
- Keep architecture simple and understandable
- Use direct imports for clear dependencies
3. Maintain Backward Compatibility
- Core users should see no changes in behavior
- Pro users get enhanced functionality seamlessly
- No breaking changes to existing APIs
4. License Boundary Integrity
- Maintain strict separation between MIT and Pro code
- Update LICENSE.md immediately when moving files
- Never allow pro code in MIT-licensed directories
5. Independent Package Builds
- Each package builds independently
- Pro package manages its dependency on core package
- Clean separation of concerns
Post-Implementation Validation
Manual Testing Checklist:
- Fresh install of core package works
- Fresh install of pro package works
- Switching from core to pro package works
- All async pro features work correctly
- No console errors or warnings
- Performance is acceptable
- Memory leaks not introduced
Automated Testing:
- All unit tests pass
- All integration tests pass
- CI pipeline passes completely
- No new linting violations
- TypeScript compilation clean
This implementation plan ensures a methodical approach to separating the pro functionality while maintaining all existing capabilities and providing clear upgrade paths for users.