简体   繁体   中英

Rack::Static how to serve an asset file (css, js etc) file containing a fingerprint value in its name so as to cache bust it

In my Rack-based app I want to serve CSS and JS and so I use Rack::Static middleware as shown below:

config.ru

use Rack::Static, urls: ["/css" ], root: "public"

run MyApp

public folder structure:

public
  css
    application.min.css

As per Rack::Static implementation at https://github.com/rack/rack/blob/2.2.4/lib/rack/static.rb (link refers to code in the version of Rack I am using ie 2.2.4) by default Cache-Control header will not be set in Response.

But if I use following configuration

use Rack::Static, urls: ["/css" ], root: "public",
    :header_rules => [
      # Cache CSS/JS files, matching given regex in public caches (e.g. Rack::Cache) as well as in the browser. For e.g. myfile.1.2.1.css
      #
      [ /\.(?:[1-9]\.[0-9]\.[0-9])\.(?:css|js)\z/, {'cache-Control' => 'public, max-age=60'} ]
    ]

Then I can see following header Cache-Control: public, max-age=60 under Response Headers for eg in Network tab under Web Developer Tools in Firefox.

Now I want to cache bust that CSS file using fingerprint strategy as explained in following resources I found

https://css-tricks.com/strategies-for-cache-busting-css/#aa-changing-file-name

https://csswizardry.com/2019/03/cache-control-for-civilians/

So in my HTML pages I would have my stylesheet name include the fingerprint version for eg like following

<head>
  ...
  ...

  <link href="/css/application.min.<MY_ASSET_VERSION>.css" rel="stylesheet">
</head>

where say <MY_ASSET_VERSION> is set to 1.0.0 .

But I should not have any file by name application.min.1.0.0.css in my public folder. That naming is just done so as to trigger cache bust. So how can I make Rack::Static to serve the file css/application.min.css when it encounters path /css/application.min.1.0.0.css ?

Will I need to implement a middleware which should be put in application's middleware stack after Rack::Static ? If yes, can anybody please help me with an example because I have not implemented any middleware.

Or if there is any other standard way for addressing the need at hand, then please suggest that.

Thanks.

Posting below the solution which I implemented using a middleware and which is working for me.

middlewares/custom_middleware/util.rb

module CustomMiddleware
  module Util
    extend self

    EXTENSIONS_OF_ASSETS_TO_BE_FINGER_PRINTED = /css|js/
    ASSET_FINGER_PRINT_FORMAT_REGEX = /[1-9]\.[0-9]\.[0-9]/

    FINGER_PRINTED_ASSET_NAME_MATCHER_REGEX = /\.(?:#{ASSET_FINGER_PRINT_FORMAT_REGEX})\.(?:#{EXTENSIONS_OF_ASSETS_TO_BE_FINGER_PRINTED})\z/
    ORIGINAL_ASSET_NAME_DETERMINER_FROM_FINGER_PRINTED_NAME_REGEX = /(.+)\.(?:#{ASSET_FINGER_PRINT_FORMAT_REGEX})\.(#{EXTENSIONS_OF_ASSETS_TO_BE_FINGER_PRINTED})\z/

    def determine_original_asset_name(fingerprinted_asset_name:)
      md = fingerprinted_asset_name.match(ORIGINAL_ASSET_NAME_DETERMINER_FROM_FINGER_PRINTED_NAME_REGEX)

      return fingerprinted_asset_name if md.nil?

      arr = md.captures

      asset_file_name = arr[0]
      asset_file_extension = arr[1]

      asset_name = "#{asset_file_name}.#{asset_file_extension}"

      asset_name
    end
  end
end

middlewares/custom_middleware/fingerprinted_asset_name_modifier.rb

require_relative 'util'

module CustomMiddleware
  class FingeprintedAssetNameModifier
    def initialize(app)
      @app = app
    end

    def call(env)
      env_path_info_key = 'PATH_INFO'

      orig_path = env[env_path_info_key]

      modified_path = Util.determine_original_asset_name(fingerprinted_asset_name: orig_path)

      if modified_path != orig_path
        env.merge!(env_path_info_key => modified_path)
      end

      @app.call(env)
    end
  end
end

config.ru

require_relative "middlewares/custom_middleware/fingerprinted_asset_name_modifier"


use CustomMiddleware::FingeprintedAssetNameModifier

use Rack::Static, urls: ["/css", "/js" ], root: "public",
    :header_rules => [
      # Cache CSS/JS files in public caches (e.g. Rack::Cache) as well as in the browser. For e.g. myfile.css
      [ %w(css js), {'cache-control' => 'public, max-age=60'} ]
    ]


run MyApp

With above solution when following CSS file is included in my page

<head>
  ...
  ...

  <link href="/css/application.min.1.0.0.css" rel="stylesheet">
</head>

application.min.1.0.0.css file serves my file at public/css/application.min.css and in Response headers Cache-Control: public, max-age=60 is set implying the after 60 seconds if application.min.1.0.0.css is re-requested it will be served from my application and not from browser's cache.

Also within 60 seconds of 1st request to the asset if changing the asset fingerprint in page like following

<link href="/css/application.min.1.0.5.css" rel="stylesheet"> 

and reloading the page, the asset is served from my application and not from browser's cache.

Hoping that this turns out to be useful to people who may come up with a requirement like in question post.

Thanks.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM