AssetMapper

AssetMapper is a base level class who's job is simple. Given a JSON file matching its schema, reads the file into memory and then provides a logical mapping of the hashed file.

Warning

AssetMapper is still a work in progress. It is not yet released or stable. The below is essentially an RFC of how I plan for AssetMapper to work.

Why?

AssetMapper is built on the belief that your bundler of choice IE: Parcel, Webpack, Rollup, ESBuild, Vite, etc all know how to handle your files. AssetMapper acts as a bridge between frontend and backend.

How does AssetMapper work?

AssetMapper expects to be given a path to a JSON file(s) that is in a specific schema. To generate these schemas, generally a plugin needs to be made for your bundler of choice that complies with the AssetMapper schema. Then, for your framework of choice you create or find a rubygem that uses AssetMapper under the hood and can then compose view layer helpers for referencing asset files.

Why not Propshaft?

Propshaft is good. It's also Rails specific. It also does more than I would like it to. It does things like:

Roadmap

  • [ ] - Create a file hasher for bundle-less setups.
  • [ ] - Create a file watcher for development to auto-update the manifest.
  • [x] - Create a plugin for ESBuild.
  • [ ] - Create a plugin for Parcel 2.
  • [ ] - Create a plugin for Vite.
  • [ ] - Create a pluggable DevServer thats Rack compatible that can be injected as middleware.
  • [ ] - Create Rails view helpers.

For Developers

If you're interested in making a plugin for a bundler that is AssetMapper compatible here is the JSON schema expected:

Schema

{
  "<unhashed-path>": {
    "asset_path": "<shortened-hashed-path>"
    "file_path": "<full-hashed-path>"
  }
}

Programmatic Usage

# Create an AssetMapper::Configuration instance
asset_mapper = AssetMapper.new.configure do |config|
  # Where the manifest files can be found on the host machine
  config.manifest_files = ["public/builds/asset-mapper-manifest.json"]

  # The URL or path prefix for the files.
  config.asset_host = "/builds"

  # Do not cache the manifest in testing or in development.
  config.cache_manifest = !(Rails.env.development? || Rails.env.testing?)
end

manifest = asset_mapper.manifest
# =>
#   {
#     "files": {
#       "entrypoints/application.js" => {
#          "asset_path" => "entrypoints/application-[hash].js",
#          "file_path" => "public/assets/entrypoints/application-[hash].js",
#       }
#     }
#   }

manifest.find("entrypoints/application.js")
# => "/builds/entrypoints/application-[hash].js"

manifest.find("assets/icon.svg")
# => "/builds/assets/icon-[hash].svg"

Supported bundlers

Rails usage

Create an initializer to initialize AssetMapper at config/initializers/asset_mapper.rb.

# config/initializers/asset_mapper.rb

asset_mapper = AssetMapper.new.configure do |config|
  # Where the manifest files can be found on the host machine
  config.manifest_files = ["public/esbuild-builds/asset-mapper-manifest.json"]

  # prepends "/builds" to all URLs
  config.asset_host = "/builds"

  # Do not cache the manifest in testing or in development.
  config.cache_manifest = !(Rails.env.development? || Rails.env.testing?)
end

Rails.application.config.asset_mapper = asset_mapper

AssetMapper is now available under: Rails.application.config.asset_mapper. This is pretty verbose to use in your views you can create a helper for it.

Usage in Rails views

# app/helpers/application_helper.rb
module ApplicationHelper
  def asset_mapper
    Rails.application.config.asset_mapper
  end
end

Hanami setup

The first step is to create a "provider" for asset_mapper.

# config/providers/asset_mapper.rb

Hanami.app.register_provider(:asset_mapper) do
  prepare do
    require "asset_mapper"
  end

  start do
    files = Dry::Files.new
    asset_mapper = AssetMapper.new.configure do |config|
      # The URL or path prefix for the files.
      config.asset_host = "/assets"

      # Do not cache the manifest in testing or in development, only production.
      config.cache_manifest = Hanami.env?(:production)

      # Files to watch
      config.asset_files = ["app/assets/media/**/*.*"]

      # Where to dump the assets
      config.assets_output_path = files.join("public/assets")

      # Where to write the manifest to.
      config.manifest_output_path = files.join(config.assets_output_path, "asset-mapper-manifest.json")

      # top level directory of assets. Used for relative short paths.
      config.assets_root = files.join("app/assets")

      # Where the manifest files can be found on the host machine
      config.manifest_files = [
        "public/assets/esbuild-manifest.json",
        config.manifest_output_path
      ]
    end

    register "asset_mapper", asset_mapper
  end
end

Add a static rack server

Next, we need to add a setting to ./config.ru for when we write to the public/ folder.

# config.ru

require "hanami/boot"

run Hanami.app

#### Everything above here is provided by default

#### Add the below call
use Rack::Static, :urls => [""], :root => "public", cascade: true

Finally, to use in your views, do the following:

# app/context.rb
module Main # <- Replace this with your slice or application name
  class Context < Hanami::View::Context
    include Deps["asset_mapper"]

    def asset_path(asset_name)
      asset_mapper.find(asset_name)
    end
  end
end

# app/view.rb
module Main # <- Replace this with your slice or application name
  class View < Hanami::View
    config.default_context = Context.new
  end
end

```erb
# app/views/application.erb
<html>
  <head>
    <title>Bookshelf</title>
    <script src="<%= asset_path("javascript/entrypoints/application.js") %>" type="module">
  </head>
  <body>
    <%= yield %>
  </body>
</html>

Development

After checking out the repo, run bin/setup to install dependencies. Then, run rake test to run the tests. You can also run bin/console for an interactive prompt that will allow you to experiment.

To install this gem onto your local machine, run bundle exec rake install. To release a new version, update the version number in version.rb, and then run bundle exec rake release, which will create a git tag for the version, push git commits and the created tag, and push the .gem file to rubygems.org.

Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/paramagicdev/asset_mapper. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the code of conduct.

License

The gem is available as open source under the terms of the MIT License.

Code of Conduct

Everyone interacting in the AssetMapper project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the code of conduct.