One of the multitudes of ways Rails simplifies and streamlines the creation of a web project is the way it bundles assets automatically. The Rails asset pipeline can pre-process CoffeeScript and Sass, and combine and minify JavaScript and CSS assets. Rails will even spit out a versioned file with a unique MD5 hash to make it easy to put the appropriate version of your static assets onto a CDN. If nothing changes, the hash doesn’t change, so the cached version your users have of that asset doesn’t need to be re-loaded.

A problem arises, however, when you have multiple Rails projects that all need to share a unified look. The benefits and “magic” of the asset pipeline go out the window if you’re trying to make one big hunk of CSS and JavaScript that can cover all of your projects. At Karma, we didn’t want to make one big monolithic Rails app, either. As we explained in our post about microservices, we really like a separation of concerns. Different parts of our system have radically different requirements for scale and functionality. Our marketing site is basically static, our user dashboards are totally dynamic. The blog you’re reading right now was built custom, and its styling, scripting, and logic needs are (almost) entirely different from those of our store.

The benefits and “magic” of the asset pipeline go out the window if you’re trying to make one big hunk of CSS and JavaScript that can cover all of your projects.

Our solution is a custom gem we call “Karmeleon.” It’s a Rails engine which carries with it all the shared assets, styles, partials, and helpers that should or could apply to any of our frontend projects. Whenever we start a new frontend project, we import Karmeleon and rely on it to provide a foundation and conventions for the project. Any custom styles, scripts, logic, or assets that are specific to one single frontend project stay in that project and don’t pollute the global scope. When a global style change is needed, we update Karmeleon. Any projects that rely on Karmeleon are then updated to the latest version of the gem. If a global change turns out to be a breaking change for one particular project, that particular project doesn’t need to update Karmeleon until the problem is resolved.

For instance, Karmeleon has global Sass stylesheets which include Bootstrap, common variables (like on-brand colors), and basic useful components. When those styles are compiled, the Rails asset pipeline calculates a filename for the resulting CSS, something like base-acbd18db4cc2f85c.css. Any project that relies on the same version of Karmeleon will end up with the same filename generated by the asset pipeline, so a visitor to any part of our site that relies on that version of Karmeleon will see an identical URL for the base CSS. If a project has an older version of Karmeleon, which happens occasionally but not often, it will have a unique filename (like base-edef654fccc4a4d8.css) so that there are no conflicts.

In addition to the base stylesheets, there are “common” styles which are applicable to most projects, but not all. Finally, each project has its own application-specific styles. The same separation of concerns applies to all our JavaScript.

In an ERB or Slim template we can simply use the appropriate Karmeleon helper to fill in our <head> tags:

<%= karmeleon_head_tags stylesheets: %w(base common marketing),

javascripts: %w(base common marketing) %>

The helper figures out all the appropriate URLs. Other Karmeleon helpers do everything from generating the meta description to generating URLs to other apps. Karmeleon also includes common components like the navigation bar and footer.

Although each project ends up with a “redundant” copy of everything inside Karmeleon, this is a non-issue for deployment: when we upload to the server it’s easy enough to check if a file named base-acbd18db4cc2f85c.css already exists, and if it does it doesn’t get uploaded.

For our upcoming product launch, we’ve been working on a redesign of the entire Karma web presence. To facilitate this, we created a branch of Karmeleon. We find Karmeleon simplifies even a major overhaul like this, because it allows us to focus on one specific section at a time without trying to hold a whole monolithic application in our heads. Even with breaking changes introduced by new global styles or a new version of Bootstrap, it’s easy when you’re looking at one small application at a time to figure out what changes apply to that specific app. And, of course, the versioning of Karmeleon means we don’t have to update everything all at once. If it was all one monolithic application, a global redesign would be, paradoxically, a much thornier problem.

It’s easy when you’re looking at one small application at a time

We think something like Karmeleon could be useful to anyone wrestling with a too-large Rails monolith, or someone looking to unify multiple projects. Unfortunately, it’s not something that would be easy to open source — Karmeleon is mostly convention, and very Karma specific. The good news is that all the “magic” is actually performed by common, well-known Rails features: the asset pipeline (for compiling assets and generating filenames), and the Rails engines (for importing the Karmeleon gem as a foundation for a project). The trick is figuring out which parts should be shared across projects and which parts shouldn’t.

If everything works as planned, the upshot is consistent styling across projects, reduced duplication of frontend code, and even a nice collaborative atmosphere for designers and other developers.

In addition to facilitating a consistent look and cached assets with Karmeleon, we stitch all the Rails projects together under a single hostname, and we also share user sessions across the apps. That’s outside the purview of this post, but we’re working on a post that will dig further into these technical details.

If you have any questions or comments, or if you know of someone with a similar architecture, we’d love to hear it!