Some days ago, I played around with requirejs. Although I liked to group my client side code into AMD modules, using the (only) appropriate gem ‘requirejs-rails’ doesn’t felt that good. Main reasons for that
- last commit some months ago
- no Rails 4 support so far
- asset precompilation failed with my concrete project
To be fair, that asset:precompile issues with requirejs-rails were caused by some third-party Gems I used. But anyway, they compiled under stock Rails and they failed when using requirejs-rails. So it seems that sprockets integration isn’t that flawless with requirejs-rails.
So I thought about how to get some module-like-feeling, with just the tools, Rails delivers out of the box. Let’s approach.
Assumptions
Let’s assume the following class hierarchy
Component SeatPlan < Component SeatPlan.InputDataSanitizer (SeatPlan.Ui)
SeatPlan should be a subclass of Component. Also I wanted InputDataSanitzer to be a class on its own, but located “below” SeatPlan, because it only sanitizes SeatPlan input data. Think of some kind of namespacing. Same for Ui. The only difference between these two is, that SeatPlan should store a reference to DataInputSanitizer, where as for Ui it should only store a concrete instance.
AMD/requirejs
With AMD, I would write something like this
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# assets/javascript/Component.js.coffee | |
define -> | |
class | |
methodEverybodyShouldHave: -> | |
# … | |
# assets/javascript/SeatPlan/InputDataSanitizer.js.coffee | |
define -> | |
class | |
sanitize: (data) -> | |
# … | |
# assets/javascript/SeatPlan/Ui.js.coffee | |
define -> | |
class | |
constructor: (el) -> | |
# … | |
# assets/javascript/SeatPlan.js.coffee | |
define ['Component', 'SeatPlan/InputDataSanitizer', 'SeatPlan/Ui'], (Component, InputDataSanitizer, Ui) -> | |
class extends Component | |
constructor: (el) | |
@InputDataSanitizer = InputDataSanitizer | |
@ui = new Ui(el) |
Imitate AMD with sprockets
Without something like AMD, you have to put these classes somewhere in global scope in order to access them from within your client-side code. Let’s put them below window.app
window + app + Component + SeatPlan + InputDataSanitizer + Ui
Combining Rails asset-pipeline directives and some CoffeeScript we can imitate something like a module system. Let’s look at the code.
https://gist.github.com/msievers/6120667
Thanks to CoffeeScripts ‘do’, which allows us to explicitly create a closure, it feels almost like doing AMD. Just without the need for any additional Gem/module loader.