π¦ Boxwerk
Boxwerk is an experimental Ruby package system with Box-powered constant isolation. It is used at runtime to organize code into packages with explicit dependencies and strict constant access using Ruby::Box. Inspired by Packwerk.
Features
-
Strict Isolation: Each package runs in its own
Ruby::Box, preventing constant leakage -
Explicit Dependencies: Dependencies declared in
package.yml, validated as a DAG - Controlled Exports: Only declared constants are accessible to importers
- Flexible Imports: Multiple strategies (namespaced, aliased, selective, renamed)
- Lazy Loading: Exports loaded on-demand when imported
Current Limitations
- No gem isolationβall gems are global across packages
- No constant reloading support
- Exported constants must follow Zeitwerk naming conventions
- Console runs in root box, not root package box (due to IRB loading issues)
-
Ruby::Boxitself is experimental in Ruby 4.0
Requirements
- Ruby 4.0+ with
RUBY_BOX=1environment variable set.
Quick Start
1. Create a Package Structure
my_app/
βββ Gemfile
βββ package.yml # Root package manifest
βββ app.rb # Your application entrypoint
βββ packages/
βββ finance/
βββ package.yml # Package manifest
βββ lib/
βββ invoice.rb # Defines Invoice
βββ tax_calculator.rb # Defines TaxCalculator
2. Define Your Gemfile
Gemfile:
source 'https://rubygems.org'
gem 'boxwerk'
gem 'money' # Example: gems are auto-required and globally accessible3. Define Packages
Root package.yml:
imports:
- packages/finance # Will define a `Finance` module to hold finance package exportspackages/finance/package.yml:
exports:
- Invoice
- TaxCalculator4. Use in Your Application
app.rb:
# No requires needed - imports are wired by Boxwerk
invoice = Finance::Invoice.new(10_000)
puts invoice.total # => #<Money fractional:10000 currency:USD>5. Run Your Application
RUBY_BOX=1 boxwerk run app.rbBoxwerk handles Bundler setup, gem loading, package wiring, and script execution automatically.
Example
See the example/ directory for a working multi-package application:
cd example
RUBY_BOX=1 boxwerk run app.rbCLI Usage
Run a script:
boxwerk run script.rb [args...]Interactive console (currently runs in root box, not root package):
boxwerk console [irb-args...]Help:
boxwerk helpPackage Configuration
A package.yml defines what a package exports and imports:
exports:
- PublicClass
- PublicModule
imports:
- packages/dependency1
- packages/dependency2: AliasExports
Constants that should be visible to packages that import this one. Exports are lazily loaded during boot; only those actually imported by dependent packages are loaded.
Imports
Package dependencies that are wired as new constants in the importing package's box. Default and aliased namespace imports create a module to hold the exports. Not transitive: if A imports B and B imports C, A cannot access C without explicitly importing it.
Import Strategies
Default namespace (all exports under package name):
imports:
- packages/finance
# Result: Finance::Invoice, Finance::TaxCalculatorAliased namespace (custom module name):
imports:
- packages/finance: Billing
# Result: Billing::Invoice, Billing::TaxCalculatorNote: Single exports import directly without namespace
Selective import (specific constants):
imports:
- packages/finance:
- Invoice
- TaxCalculator
# Result: Invoice, TaxCalculator (no namespace)Selective rename (custom names):
imports:
- packages/finance:
Invoice: Bill
TaxCalculator: Calculator
# Result: Bill, CalculatorGem Handling
All gems in your Gemfile are:
- Automatically loaded in the root box via Bundler
- Accessible globally in all packages (no gem isolation)
- No manual
requireorpackage.ymldeclaration needed
Known Issues
Related to Ruby::Box in Ruby 4.0+. See Ruby::Box documentation for details.
- Gem requiring: Crashes VM when requiring gems inside boxes after boot (workaround: gems pre-loaded in root box)
- Console context: Runs in root box instead of root package box due to IRB loading limitation
- IRB autocomplete: Disabled by since it currently crashes VMpe
Architecture
Boot process:
- Setup Bundler and require all gems in root box
- Find root
package.ymlby searching up from current directory - Build and validate dependency graph (DAG)
- Boot packages in topological order, creating isolated boxes
- Wire imports by lazily loading exports and injecting constants
- Execute command in root package context
Components:
- CLI: Parses commands, validates environment, delegates to Setup
- Setup: Finds root package, builds graph, creates registry, boots packages
- Graph: Builds DAG, validates no cycles, performs topological sort
-
Package: Parses
package.yml, tracks exports/imports/box - Loader: Creates boxes, loads exports lazily (Zeitwerk conventions), wires imports
- Registry: Tracks booted packages, ensures single boot per package
Development
After checking out the repo, run bin/setup to install dependencies. Then, run the tests:
RUBY_BOX=1 bundle exec rake testTo install this gem onto your local machine, run bundle exec rake install.
License
The gem is available as open source under the terms of the MIT License.
Contribution
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you shall be dual licensed as above, without any additional terms or conditions.