asset-pipeline

Asset pipeline

The asset pipeline fingerprints assets with a content hash, records the mapping in a manifest, and resolves the view helpers through it so a changed asset gets a new URL. It also builds import maps and serves digested assets with a long cache lifetime.

Fingerprinting and the manifest

digest-for($content) is the content hash; digested-name('app.css', $content) inserts it before the extension (app-<digest>.css). A Manifest maps each logical path to its digested name:

use MVC::Keayl::Assets;

my $manifest = MVC::Keayl::Assets::Manifest.build('app/assets'.IO, output => 'public/assets'.IO);
$manifest.lookup('css/app.css');   # 'css/app-<digest>.css'

build walks the source tree, digests each file, writes the digested copy to the output directory, and records the mapping. to-json/from-json persist the manifest.

Resolving in views

The asset helpers take a resolver. manifest-resolver($manifest) resolves a logical name to its digested /assets/... path, passing absolute and external URLs through unchanged:

my &resolver = manifest-resolver($manifest);

stylesheet-link-tag('application', :&resolver);   # href="/assets/application-<digest>.css"
image-tag('logo.png', :&resolver);                 # src="/assets/logo-<digest>.png"

set-asset-manifest($manifest) registers a manifest globally, and digested-resolver is a resolver that consults it, so helpers can resolve without threading the manifest through each call:

set-asset-manifest($manifest);
image-tag('logo.png', resolver => &digested-resolver);

A view defaults to digested-resolver, so once a manifest is registered the helpers emit fingerprinted URLs with no per-call configuration. When no manifest is registered the same resolver emits the plain /assets/<path> URL, which keeps development output unfingerprinted until you precompile.

Loading the manifest at boot

An application loads the precompiled manifest during boot. The assets initializer reads public/assets/manifest.json when it exists, registers it with set-asset-manifest, and points the asset root at public/assets. After a keayl assets-precompile run the view helpers fingerprint URLs in production with no extra configuration, and a deploy with new asset content serves new URLs that defeat the browser cache.

Both paths come from the assets config, defaulting to public/assets:

{
  "assets": {
    "public-root": "public/assets",
    "manifest": "public/assets/manifest.json"
  }
}

Serving

MVC::Keayl::Assets::Serving::AssetsController serves digested files from the configured root with Cache-Control: public, max-age=31536000, immutable (safe because the digest changes when the content does). It rejects path traversal and returns 404 for unknown assets:

set-asset-root('public/assets'.IO);

Import maps

An ImportMap pins module names to URLs. pin adds one (defaulting the URL to /assets/<name>.js); pin-all-from pins every .js file under a directory:

my $importmap = MVC::Keayl::Assets::ImportMap.new;
$importmap.pin('application', preload => True);
$importmap.pin('lodash', to => 'https://cdn/lodash.js');
$importmap.pin-all-from('app/javascript/controllers'.IO, under => 'controllers');

javascript-importmap-tags($importmap) emits the <script type="importmap"> with the import JSON and a <link rel="modulepreload"> for each preloaded pin. Pass a manifest to resolve the module URLs to their digested paths:

javascript-importmap-tags($importmap, :$manifest);

Precompiling

keayl assets-precompile builds the manifest and digested files, writing them and a manifest.json to public/assets:

keayl assets-precompile

It reads from app/assets and reports the count, or fails when there is no asset source directory.

MVC::Keayl v0.9.1

Model-View-Controller web framework

Authors

  • Greg Donald

License

Artistic-2.0

Dependencies

Cro::HTTPCrypt::RandomDigest::HMACDigest::SHA1::NativeOpenSSLMIME::Base64YAMLishORM::ActiveRecord:auth<zef:gdonald>Template::HAML:auth<zef:gdonald>

Provides

  • MVC::Keayl
  • MVC::Keayl::APIController
  • MVC::Keayl::ActionText
  • MVC::Keayl::ActionText::Repository
  • MVC::Keayl::ActionText::RichTextable
  • MVC::Keayl::Adapter
  • MVC::Keayl::Adapter::Cro
  • MVC::Keayl::Adapter::Test
  • MVC::Keayl::Application
  • MVC::Keayl::Assets
  • MVC::Keayl::Assets::Serving
  • MVC::Keayl::CLI
  • MVC::Keayl::CSRF
  • MVC::Keayl::Cable::Broadcasting
  • MVC::Keayl::Cable::Channel
  • MVC::Keayl::Cable::Connection
  • MVC::Keayl::Cable::PubSub
  • MVC::Keayl::Cable::PubSub::External
  • MVC::Keayl::Cable::PubSub::InMemory
  • MVC::Keayl::Cache
  • MVC::Keayl::Caching
  • MVC::Keayl::Config
  • MVC::Keayl::Controller
  • MVC::Keayl::Cookies
  • MVC::Keayl::Credentials
  • MVC::Keayl::Dispatcher
  • MVC::Keayl::Endpoint
  • MVC::Keayl::Engine
  • MVC::Keayl::ErrorReporter
  • MVC::Keayl::ErrorReporting
  • MVC::Keayl::Errors
  • MVC::Keayl::ExceptionPage
  • MVC::Keayl::Flash
  • MVC::Keayl::HealthController
  • MVC::Keayl::Helpers::Asset
  • MVC::Keayl::Helpers::DateTime
  • MVC::Keayl::Helpers::Form
  • MVC::Keayl::Helpers::Number
  • MVC::Keayl::Helpers::Options
  • MVC::Keayl::Helpers::Tag
  • MVC::Keayl::Helpers::Text
  • MVC::Keayl::Helpers::Url
  • MVC::Keayl::HttpAuthentication
  • MVC::Keayl::I18n
  • MVC::Keayl::I18n::Locale
  • MVC::Keayl::I18n::Pluralization
  • MVC::Keayl::Job
  • MVC::Keayl::Job::GlobalID
  • MVC::Keayl::Job::QueueAdapter
  • MVC::Keayl::Job::QueueAdapter::Async
  • MVC::Keayl::Job::QueueAdapter::Database
  • MVC::Keayl::Job::QueueAdapter::Inline
  • MVC::Keayl::Job::QueueAdapter::Test
  • MVC::Keayl::Live
  • MVC::Keayl::LogEvent
  • MVC::Keayl::Logger
  • MVC::Keayl::Mail
  • MVC::Keayl::Mailbox
  • MVC::Keayl::Mailbox::Ingress
  • MVC::Keayl::Mailbox::Router
  • MVC::Keayl::Mailer
  • MVC::Keayl::Mailer::Delivery
  • MVC::Keayl::Mailer::Delivery::File
  • MVC::Keayl::Mailer::Delivery::SMTP
  • MVC::Keayl::Mailer::Delivery::Test
  • MVC::Keayl::Mailer::DeliveryJob
  • MVC::Keayl::Mailer::Preview
  • MVC::Keayl::Middleware
  • MVC::Keayl::Middleware::Database
  • MVC::Keayl::Middleware::HostAuthorization
  • MVC::Keayl::Middleware::Logger
  • MVC::Keayl::Middleware::RequestId
  • MVC::Keayl::Middleware::SSL
  • MVC::Keayl::Middleware::SecureHeaders
  • MVC::Keayl::Middleware::Static
  • MVC::Keayl::MiddlewareStack
  • MVC::Keayl::Mime
  • MVC::Keayl::Notifications
  • MVC::Keayl::PWAController
  • MVC::Keayl::ParameterFilter
  • MVC::Keayl::Parameters
  • MVC::Keayl::Params
  • MVC::Keayl::Request
  • MVC::Keayl::Response
  • MVC::Keayl::Router
  • MVC::Keayl::Routing
  • MVC::Keayl::Routing::Mount
  • MVC::Keayl::Routing::PathPattern
  • MVC::Keayl::Routing::Redirect
  • MVC::Keayl::Routing::Resources
  • MVC::Keayl::Routing::Route
  • MVC::Keayl::Routing::RouteMatch
  • MVC::Keayl::Routing::UrlHelpers
  • MVC::Keayl::SafeString
  • MVC::Keayl::Secrets
  • MVC::Keayl::Session
  • MVC::Keayl::Storage
  • MVC::Keayl::Storage::Attached
  • MVC::Keayl::Storage::Repository
  • MVC::Keayl::Storage::Service
  • MVC::Keayl::Storage::Serving
  • MVC::Keayl::Storage::Variant
  • MVC::Keayl::TestSupport
  • MVC::Keayl::View
  • MVC::Keayl::View::Context
  • MVC::Keayl::View::Handler
  • MVC::Keayl::View::Handler::HAML

The Camelia image is copyright 2009 by Larry Wall. "Raku" is trademark of the Yet Another Society. All rights reserved.