Redirecting to static webpacker Content With Rails
With the release of Rails 6, webpacker will be the new default compilation pipeline for Javascript assets. This is a great thing once you wrap your head around how to use webpack and webpacker.
One of the benefits of webpacker, is like sprockets, the automatic compilation and fingerprinting of assets for production builds. Fingerprinting is very handy as it will automatically break any browser caches when new versions of that asset are compiled.
What if you have a Javascript snippet that you want others to use dynamically, such as a widget or html snippet? The fingerprinting of assets then makes it very difficult, as the filename will keep changing over and over again.
Fortunately with a combination of a Rails route and diving into the webpacker
gem, this can be accomplished fairly easily using redirects.
Rails Routing
Let’s say you have a Javascript widget that you want your customers to embed on their site. You want something easy to remember, such as https://www.example.com/external/widget.js
.
Let’s first setup our routes:
# config/routes.rb
get :widget, to: "widgets#show", path: "external/widget"
Our controller needs to then respond to the request, find the webpacker asset, and then respond with a redirect to the fingerprinted asset. Let’s look at the code:
class WidgetsController < ApplicationController
include ActionView::Helpers::AssetUrlHelper
include Webpacker::Helper
protect_from_forgery except: :show
def show
respond_to do |format|
format.js { redirect_to widget_javascript_source }
end
end
private
def widget_javascript_source
asset_pack_url("widget.js")
end
end
Most of this is fairly straight forward. The first thing we want to do is tell Rails not to worry about protecting from anti-forgery requests. Since this will be used from external applications, it’s not something we’re worried about.
The real magic comes in our widget_javascript_source
method, which refers to the asset_pack_url
that is included in the Webpacker::Helper
module.
What does webpacker actually do?
To figure out what asset_pack_url
does, it’s good to know what webpacker
actually does for us behind the scenes.
The webpacker
gem is a wrapper around webpack
, and hides away a lot of the complexity of webpack
. For our use case, the most important thing is compiling our assets. Instead of using sprockets
, we can now rely on webpack
to do all of our assets, and with that, we can hook into the broader webpack ecosystem. Part of that includes asset compilation if we’re using Typescript, asset transpiling using a tool such as babel, and minification and fingerprinting to optimize the assets we are serving.
One thing that webpacker
does for us when it compiles all of these assets for us is it creates a manifest.json
file with a list of entrypoints and a mapping of the compiled asset name. This is what the new javascript_pack_tag
ends up doing for us. We pass in the friendly name, and it uses that manifest file to find the fingerprinted asset name.
If we look at the manifest.json
file in the public/packs
folder, we’ll see an example
{
"application.js": "/packs/application-feff836d0a539cc07796.js",
"application.js.map": "/packs/application-feff836d0a539cc07796.js.map",
"widget.js": "/packs/widget-feff836d0a539cc07796.js",
"widget.js.map": "/packs/widget-feff836d0a539cc07796.js.map",
"entrypoints": {
"application": {
"js": [
"/packs/application-feff836d0a539cc07796.js"
],
"js.map": [
"/packs/application-feff836d0a539cc07796.js.map"
]
},
"widget": {
"js": [
"/packs/widget-feff836d0a539cc07796.js"
],
"js.map": [
"/packs/widget-feff836d0a539cc07796.js.map"
]
}
}
}
What webpacker is doing is reading the content of our manifest file, looking up the friendly name of our asset (widget.js), and then returns to us the properly compiled, minified and fingerprinted asset.
So, all we need to ask webpacker for us the name of our compiled widget.js
and then redirect from there!
After looking through some of the internals of webpacker gem, we can see just how easy it is to hook into the compiled assets from our Rails controllers. This allows us to continue to modify and enhance our external widget.js
file, and even work on it in a language such as Typescript, while still providing a single, easy to use snippet for others to embed in their own websites.