Roadie ====== [![Build history and status](https://travis-ci.org/Mange/roadie.svg?branch=master)](http://travis-ci.org/#!/Mange/roadie) [![Code Climate](https://codeclimate.com/github/Mange/roadie.png)](https://codeclimate.com/github/Mange/roadie) [![Code coverage status](https://codecov.io/github/Mange/roadie/coverage.svg?branch=master)](https://codecov.io/github/Mange/roadie?branch=master) [![Gem](https://img.shields.io/gem/v/roadie.svg)](https://rubygems.org/gems/roadie) [![Passive maintenance](https://img.shields.io/badge/maintenance-Passive-yellow.svg)][passive] ||| |---|---| | :warning: | This gem is now in [passive maintenance mode][passive]. [(more)][passive] | > Making HTML emails comfortable for the Ruby rockstars Roadie tries to make sending HTML emails a little less painful by inlining stylesheets and rewriting relative URLs for you inside your emails. How does it work? ----------------- Email clients have bad support for stylesheets, and some of them blocks stylesheets from downloading. The easiest way to handle this is to work with inline styles (`style="..."`), but that is error prone and hard to work with as you cannot use classes and/or reuse styling over your HTML. This gem makes this easier by automatically inlining stylesheets into the document. You give Roadie your CSS, or let it find it by itself from the `` and ` ``` Roadie will use the given asset providers to look for the actual CSS that is referenced. If you don't change the default, it will use the `Roadie::FilesystemProvider` which looks for stylesheets on the filesystem, relative to the current working directory. Example: ```ruby # /home/user/foo/stylesheets/primary.css body { color: green; } # /home/user/foo/script.rb html = <<-HTML
HTML Dir.pwd # => "/home/user/foo" document = Roadie::Document.new html document.transform # => # # # # # ``` If a referenced stylesheet cannot be found, the `#transform` method will raise an `Roadie::CssNotFound` error. If you instead want to ignore missing stylesheets, you can use the `NullProvider`. ### Configuring providers ### You can write your own providers if you need very specific behavior for your app, or you can use the built-in providers. Providers come in two groups: normal and external. Normal providers handle paths without host information (`/style/foo.css`) while external providers handle URLs with host information (`//example.com/foo.css`, `localhost:3001/bar.css`, and so on). The default configuration is to not have any external providers configured, which will cause those referenced stylesheets to be ignored. Adding one or more providers for external assets causes all of them to be searched and inlined, so if you only want this to happen to specific stylesheets you need to add ignore markers to every other styleshheet (see above). Included providers: * `FilesystemProvider` – Looks for files on the filesystem, relative to the given directory unless otherwise specified. * `ProviderList` – Wraps a list of other providers and searches them in order. The `asset_providers` setting is an instance of this. It behaves a lot like an array, so you can push, pop, shift and unshift to it. * `NullProvider` – Does not actually provide anything, it always finds empty stylesheets. Use this in tests or if you want to ignore stylesheets that cannot be found by your other providers (or if you want to force the other providers to never run). * `NetHttpProvider` – Downloads stylesheets using `Net::HTTP`. Can be given a whitelist of hosts to download from. * `CachedProvider` – Wraps another provider (or `ProviderList`) and caches responses inside the provided cache store. * `PathRewriterProvider` – Rewrites the passed path and then passes it on to another provider (or `ProviderList`). If you want to search several locations on the filesystem, you can declare that: ```ruby document.asset_providers = [ Roadie::FilesystemProvider.new(App.root.join("resources", "stylesheets")), Roadie::FilesystemProvider.new(App.root.join("system", "uploads", "stylesheets")), ] ``` #### `NullProvider` #### If you want to ignore stylesheets that cannot be found instead of crashing, push the `NullProvider` to the end: ```ruby # Don't crash on missing assets document.asset_providers << Roadie::NullProvider.new # Don't download assets in tests document.external_asset_providers.unshift Roadie::NullProvider.new ``` **Note:** This will cause the referenced stylesheet to be removed from the source code, so email client will never see it either. #### `NetHttpProvider` #### The `NetHttpProvider` will download the URLs that is is given using Ruby's standard `Net::HTTP` library. You can give it a whitelist of hosts that downloads are allowed from: ```ruby document.external_asset_providers << Roadie::NetHttpProvider.new( whitelist: ["myapp.com", "assets.myapp.com", "cdn.cdnnetwork.co.jp"], ) document.external_asset_providers << Roadie::NetHttpProvider.new # Allows every host ``` #### `CachedProvider` #### You might want to cache providers from working several times. If you are sending several emails quickly from the same process, this might also save a lot of time on parsing the stylesheets if you use in-memory storage such as a hash. You can wrap any other kind of providers with it, even a `ProviderList`: ```ruby document.external_asset_providers = Roadie::CachedProvider.new(document.external_asset_providers, my_cache) ``` If you don't pass a cache backend, it will use a normal `Hash`. The cache store must follow this protocol: ```ruby my_cache["key"] = some_stylesheet_instance # => #Hello world
``` When hovering over this ``, the color will not change as the `color: green` rule takes precedence. You can get it to work by adding `!important` to the `:hover` rule. It would be foolish to try to automatically inject `!important` on every rule automatically, so this is a manual process. #### Turning it off #### If you'd rather skip this and have the styles not possible to inline disappear, you can turn off this feature by setting the `keep_uninlinable_css` option to false. ```ruby document.keep_uninlinable_css = false ``` ### Callbacks ### Callbacks allow you to do custom work on documents before they are transformed. The Nokogiri document tree is passed to the callable along with the `Roadie::Document` instance: ```ruby class TrackNewsletterLinks def call(dom, document) dom.css("a").each { |link| fix_link(link) } end def fix_link(link) divider = (link['href'] =~ /?/ ? '&' : '?') link['href'] = link['href'] + divider + 'source=newsletter' end end document.before_transformation = ->(dom, document) { logger.debug "Inlining document with title #{dom.at_css('head > title').try(:text)}" } document.after_transformation = TrackNewsletterLinks.new ``` ### XHTML vs HTML ### You can configure the underlying HTML/XML engine to output XHTML or HTML (which is the default). One usecase for this is that `{` tokens usually gets escaped to `{`, which would be a problem if you then pass the resulting HTML on to some other templating engine that uses those tokens (like Handlebars or Mustache). ```ruby document.mode = :xhtml ``` This will also affect the emitted `` if transforming a full document. Partial documents does not have a ``. Build Status ------------ Tested with [Travis CI](http://travis-ci.org) using: * MRI 2.1 * MRI 2.2 * MRI 2.3 * MRI 2.4 * JRuby (latest) * Rubinius (failures on Rubinius will not fail the build due to a long history of instability in `rbx`) [(Build status)](http://travis-ci.org/#!/Mange/roadie) Let me know if you want any other VM supported officially. ### Versioning ### This project follows [Semantic Versioning](http://semver.org/) and has been since version 1.0.0. FAQ --- ### Why is my markup changed in subtle ways? Roadie uses Nokogiri to parse and regenerate the HTML of your email, which means that some unintentional changes might show up. One example would be that Nokogiri might remove your ` `s in some cases. Another example is Nokogiri's lack of HTML5 support, so certain new element might have spaces removed. I recommend you don't use HTML5 in emails anyway because of bad email client support (that includes web mail!). ### I'm getting segmentation faults (or other C-like problems)! What should I do? ### Roadie uses Nokogiri to parse the HTML of your email, so any C-like problems like segfaults are likely in that end. The best way to fix this is to first upgrade libxml2 on your system and then reinstall Nokogiri. Instructions on how to do this on most platforms, see [Nokogiri's official install guide](http://nokogiri.org/tutorials/installing_nokogiri.html). ### What happened to my `@keyframes`? The CSS Parser used in Roadie does not handle keyframes. I don't think any email clients do either, but if you want to keep on trying you can add them manually to a `