Class: Wurk::Engine::AssetMount

Inherits:
Object
  • Object
show all
Defined in:
lib/wurk/engine.rb

Overview

Rack::Files doesn't strip the URL prefix before file lookup, so /wurk-assets/assets/foo.js would resolve under vendor/assets/dashboard/ wurk-assets/assets/foo.js — a path that doesn't exist. AssetMount rewrites PATH_INFO to drop the /wurk-assets prefix before delegating to Rack::Files, then falls through to the next middleware on 404 so host-app routes outside the mount keep working.

Constant Summary collapse

PREFIX =
'/wurk-assets'
FINGERPRINTED_PREFIX =

Vite fingerprints everything it emits into assets/ (Dashboard-cGKycyd0.js), so the bytes behind one of those URLs can never change — cache them for a year and never revalidate. The rest of the bundle (index.html, the favicons, wurk-manifest.json, .vite/manifest.json) keeps a stable name across builds, so it must be revalidated or a client would pin a stale dashboard shell forever.

'/assets/'
IMMUTABLE_CACHE_CONTROL =
'public, max-age=31536000, immutable'
REVALIDATE_CACHE_CONTROL =
'public, no-cache'
CACHEABLE_METHODS =

Only a served body gets a cache directive. Rack::Files answers more than file reads: OPTIONS with 200 + Allow, 405 for any other verb (ALLOWED_VERBS = %w[GET HEAD OPTIONS]), and 416 for an unsatisfiable Range. 405 is cacheable by default (RFC 9110 §15.5.6), so stamping it immutable would let a shared proxy serve "method not allowed" for a year to everyone behind it.

%w[GET HEAD].freeze
CACHEABLE_STATUSES =
[200, 206, 304].freeze
ENCODINGS =

rake frontend:build writes a .br and a .gz next to each fingerprinted text asset (frontend/vite.config.ts, wurk-precompress). The mount sits ahead of any deflater the host runs, so without these the dashboard went out uncompressed. Preference order when the client's q-values tie.

{ 'br' => '.br', 'gzip' => '.gz' }.freeze
OFFERED_ENCODINGS =
[*ENCODINGS.keys, 'identity'].freeze
PRECOMPRESSED =
/\.(?:js|css|svg|json)\z/

Instance Method Summary collapse

Constructor Details

#initialize(app, root:) ⇒ AssetMount

Returns a new instance of AssetMount.



54
55
56
57
# File 'lib/wurk/engine.rb', line 54

def initialize(app, root:)
  @app   = app
  @files = ::Rack::Files.new(root)
end

Instance Method Details

#call(env) ⇒ Object



59
60
61
62
63
64
65
66
67
68
69
70
71
# File 'lib/wurk/engine.rb', line 59

def call(env)
  path = env[::Rack::PATH_INFO]
  return @app.call(env) unless path == PREFIX || path.start_with?("#{PREFIX}/")

  inner = env.dup
  stripped = path.delete_prefix(PREFIX)
  inner[::Rack::PATH_INFO] = stripped.empty? ? '/' : stripped
  response = precompressed(inner, env, stripped) || @files.call(inner)
  return @app.call(env) if response[0] == 404

  stamp_cache_headers(response, env, stripped)
  response
end