Bundling Custom Assets with Sprockets
Tags: ruby, rails, javascript, mustache
Published: 2020-02-28 16:08:05

**This article explains how to process custom asset files with Rails Asset Pipeline by customizing Sprockets configuration.**

Let's consider an example case of Rails application that uses client-side [Mustache](https://mustache.github.io) templates rendering. What would be an efficient way to access the templates from JavaScript?

There are several approaches here. The basic one is to nest the templates directly into the HTML view code. Like this:

```html
<script type="text/template" id="post-template">
  <h2>{{ title }}</h2>
  <p>{{ body }}</p>
</script>
```

But this is far from ideal:

1. Most probably, there will be more templates, and you will need to share some of them between different views. Therefore it makes sense to keep them organized, instead of scattering templates all over the HTML.

2. Nesting a template is an unnecessary manual operation. It is better to have templates available from JavaScript right away, instead of relying on the fact that you won't forget to add one.

3. Sometimes nesting a template within `<script>` element breaks syntax highlighting, therefore keeping templates in individual `*.mustache` files usually works better. However, this depends on your text editor configuration.

To solve these issues, let's make Rails Asset Pipeline generate a JSON object within the JavaScript bundle, and populate this object from templates directory at `app/assets/templates`. For the consumer, it will look like this:

```javascript
window.Templates = {
  post: "<h2>{{ title }}</h2><p>{{ body }}</p>",
  comment: "..."
}
```

This way, the templates will be preloaded with the shared bundle, and remain available from every view that is using it.

## Step 1. Register new MIME type

In a typical Rails application, custom Sprockets configuration should be defined under `config.assets.configure` block within the `Application` class. So the examples below are based on the assumption that a block like this already exists in your `config/application.rb`:

```ruby
module MyApplication
  class Application < Rails::Application
    config.assets.configure do |env|

      // Custom Sprockets configuration should go here

    end
  end
end
```

MIME type registration is straightforward. Just define an association between a new type, and the list of file extensions you plan to process:

```ruby
env.register_mime_type('text/mustache', extensions: ['.mustache'])
```

## Step 2. Register new assets transformer

The most important part here is the callback. It is a callable object that converts asset file content from `source_type` to something of `target_type`. The callback should return a hash with processed content in `:data` key:

```ruby
source_type = 'text/mustache'
target_type = 'application/javascript'

callback = -> (input) { { data: '// HELLO ${input[:name]}' } }

env.register_transformer(source_type, target_type, callback)
```

Lambda expression could be unsuitable for real-life situations. A service class, defined somewhere outside Rails configuration, will be a better choice. But for now, let's finish the boilerplate first. A real transformer example will follow.

## Step 3. Enable custom file type processing

To achieve that, add a regular expression to the precompile array in the assets initializer (`config/initializers/assets.rb`):

```ruby
Rails.application.config.assets.precompile += [/\.(?:mustache)\z/]
```

## Step 4. Bundle transformed assets

After the new transformer is registered, both [require and require_tree](https://guides.rubyonrails.org/asset_pipeline.html#manifest-files-and-directives) directives will play well with the custom assets type.

If you keep Mustache templates under `app/assets/templates`, adding this line to `app/assets/javascripts/application.js` will inject transformer callback output for each `*.mustache` file:

```javascript
//= require_tree ../templates
```

Dummy lambda-transformer from the previous step will replace this line with a list of commented file names like this:

```javascript
// HELLO template_file_name
// HELLO another_template_file_name
// ...
```

To see it working, don't forget to restart Rails server and make sure to purge the assets cache.

And here is an actual transformer example, that generates JavaScript object from Mustache template files:

```ruby
class MustacheTransformer
  def self.call(input)
    key = input[:name]
    body = JSON.dump(input[:data])
    obj = 'window.Templates'
    { data: "#{obj} = #{obj} || {}; #{obj}['#{key}'] = #{body}" }
  end
end
```

Besides `:data`, the `input` object contains several other extension fields, that could be valuable during content transformation. Check out the official reference for the [full list](https://github.com/rails/sprockets/blob/master/guides/extending_sprockets.md#extension-keys).

The case of Mustache templates elaborated in this article is just a concrete example of extending Sprockets. Besides templates, it is possible to bundle any other assets with arbitrary transformation logic that suit your needs.

Peace ✌️

References:

- [Original discussion on GoRails forum](https://gorails.com/forum/extend-sprockets-to-bundle-mustache-templates)
- [Extending Sprockets, official guide](https://github.com/rails/sprockets/blob/master/guides/extending_sprockets.md)
- [Sprockets::Transformers API reference](https://www.rubydoc.info/github/rails/sprockets/Sprockets%2FTransformers%3Aregister_transformer)
- [Precompiling assets with Rails Assets Pipeline](https://guides.rubyonrails.org/asset_pipeline.html#precompiling-assets)
- [Complete list of MIME types](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Complete_list_of_MIME_types)
- [handlebars_assets gem](https://github.com/leshill/handlebars_assets): a ready-to-use replacement for particular case of embedding templates into Rails assets bundle.