cachebusting

package module
v1.0.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 22, 2021 License: MIT Imports: 16 Imported by: 1

README

Introduction:

This package provides tooling for creating and using cache busting copies of static files.

Details:

Cache busting is performed at runtime by prepending a partial hash to each of your source/original file's name, storing a copy of the file for use in responding to requests, and providing you a key-value map of the original file name to the cache busting file name (i.e.: script.min.js -> A1B2C3D4.script.min.js). You would take the key-value map and handle building your HTML files/templates with the correct filename.

Usage of this package is based upon a few assumptions:

  1. You know the non-cache busting file's name and use that name in your HTML files/templates. In other words, your source/original file's name doesn't change.
  2. You are okay with cache busting being performed by renaming of the source/original files.
  3. You have some manner of performing replacement of text in your HTML files/templates (i.e.: you use the html/templates package).

Further details:

  • Works with on-disk or embedded source template files.
  • Store configuration in-package (globally) or elsewhere (dependency injection).
  • Store the cache busting copies of your original files on disk or in memory (in memory only for embedded files).

Getting Started:

With your original static files in a directory structure similar to as follows:

/path/to/static/
├─ css/
│  ├─ styles.min.css
├─ js/
│  ├─ script.min.js
  1. Build the data structures for your static files by providing the local on-disk path and the path used on your website/app. Make note of the separators you use!
css := NewStaticFile(filepath.Join("/", "path", "to", "static", "css", "styles.min.css"), path.Join("/", "static", "css", "styles.min.css"))
  1. Initialize your configuration using NewConfig() or NewOnDiskConfig if you want to store your configuration elsewhere; or DefaultConfig or DefaultOnDiskConfig if you want to use the globally stored configuration.
err := NewOnDiskConfig(css)
if err := nil {
    log.Fatal(err)
    return
}
  1. Call Create() to validate your configuration and perform the creation of the cache busting files and data.

  2. Retrieve the key-value map of original to cache busting file names via GetFilenamePairs(). Pass this value to your HTML files/templates and handle replacement of the original files. Example code to handle replacement if you use the html/templates package is noted as follows:

<html>
  <head>
    {{$originalFile := "styles.min.css"}}
	{{$cacheBustFiles := .CacheBustFiles}} {{/* the key-value map retrieved via GetFilenamePairs */}}

	{{/*If the key "styles.min.css" exists in $cacheBustFiles, then the associated cache-busted filename will be returned as {{.}}. */}}
	{{with index $cacheBustFiles $originalFile}}
	  {{$cacheBustedFile := .}}
	  <link rel="stylesheet" href="/static/css/{{$cacheBustedFile}}">
    {{else}}
      <link rel="stylesheet" href="/static/css/{{$originalFile}}">
    {{end}}
  </head>
</html>

Using Embedded Files:

This package can work the files embedded via the embded package. You must have already "read" the embedded files using code similar to below prior to providing the embed.FS object to this package. Note that the paths must use a forward slash separator!

package main

//go:embed path/to/templates
var embeddedFiles embed.FS

func init() {
    css = NewStaticFile(path.Join("path", "to", "static", "css", "styles.min.css"), path.Join("/", "static", "css", "styles.min.css"))
    c = NewEmbeddedConfig(embeddedFiles, css)
    err := c.Build()
    if err != nil {
        log.Fatal(err)
        return
    }
}

Serving Files:

Based on the configuration of this package, the files you want to serve could be located in one of three locations:

  • On disk.
  • Memory.
  • Embedded.

Due to this, determining how to serve a request is a bit more difficult then just serving an on-disk directory using http.FileServer. There is an example HTTP handler provided that may work for you but is very strict on it's source directory layout (see below). This handler is designed to serve cache busting files, non-cache busting files, and locally stored third party libraries (i.e.: bootstrap) where certain files may be stored in memory and other are stored on disk or embedded.

Expected directory structure for the example HTTP handler:

/path/to/website/
├─ static/
│  ├─ css
│  │  ├─ styles.min.css
│  ├─ js
│  │  ├─ script.min.js

Copies of Source Files:

During the Create() process, copies of each source file are created and stored either on disk or in memory. A copy is created, versus just using a new filename to point to the original source file, so that there is a reduced chance of serving a file whose contents have changed since the time the hash has be calculated.

The copy of embedded files are always stored in memory since you cannot write to the embedded filesystem. You have the option of storing the copy of on disk files in memory for times when your app does not have write access to the system it is running or just personal choice.

Documentation

Overview

Package cachebusting handles creation of static files that will not be found in browser caches to prevent serving of old version of a file causing webpage/webapp performance or execution issues.

Cache busting is done at runtime by calculating a hash of each defined, original file (for example, script.min.js), creating a new name for the file by appending the hash to the beginning of the original file name, and saving a copy of the original file to disk or to the app's memory. This package then provides a key-value matching of the original file name to the cache busting file name that can be used to handle replacement of file names in <link> or <script> tags.

This supports embedded files (using the go embed package). To save a copy of the embedded file for serving under the cache busting file name, since the app cannot write to the embedded filesystem, the app saves the copy to memory. This can also be used when the original file is stored on disk in cases where your app cannot write to disk.

To use the cache busted version of each file, modify your html templates to replace usage of an original file with the cache busted version by matching up the original name of the minified file. For example: <html>

  <head>
    {{$originalFile := "styles.min.css"}}
	{{$cacheBustFiles := .CacheBustFiles}}

	{{/*If the key "styles.min.css" exists in $cacheBustFiles, then the associated cache-busted filename will be returned as {{.}}. *\/}}
	{{with index $cacheBustFiles $originalFile}}
	  {{$cacheBustedFile := .}}
	  <link rel="stylesheet" href="/static/css/{{$cacheBustedFile}}">
    {{else}}
      <link rel="stylesheet" href="/static/css/{{$originalFile}}">
    {{end}}
  </head>

</html>

The expected local directory format for your static files is as follows: website/ ├─ static/ │ ├─ css │ │ ├─ styles.min.css │ ├─ js │ │ ├─ script.min.js

The expected paths for each file as served from a browser is noted as follows: - example.com/static/css/{hash-prefix}.styles.min.css - example.com/static/js/{hash-prefix}.script.min.jss

Index

Constants

This section is empty.

Variables

View Source
var (
	//ErrNoFiles is returned when no static files were provided to cache bust.
	ErrNoFiles = errors.New("cachebusting: no files provided")

	//ErrEmptyPath is returned when a static file's local or url path is blank.
	ErrEmptyPath = errors.New("cachebusting: empty path provided is invalid")

	//ErrNoEmbeddedFilesProvided is returned when a user is using a config with embedded files
	//but no embedded files were provided.
	ErrNoEmbeddedFilesProvided = errors.New("cachebusting: no embedded files provided")

	//ErrNoCacheBustingInDevelopment is returned when CreateCacheBustingFiles() is called
	//but the config's Development field is set to True.
	ErrNoCacheBustingInDevelopment = errors.New("cachebusting: disabled because Development field is true")

	//ErrHashLengthToShort is returned when a too short hash length is provided to the config.
	ErrHashLengthToShort = errors.New("cachebusting: hash length too short, must be at least " + strconv.FormatUint(uint64(minHashLength), 10))

	//ErrFileNotStoredInMemory is returned when a user tries to look up a file's data but
	//that file's data is stored on disk, not in memory.
	ErrFileNotStoredInMemory = errors.New("cachebusting: file not stored in memory")

	//ErrNotFound is returned when a user tries to look up a file in the list of static files
	//but the file data cannot be found. This means the file was not cache-busted.
	ErrNotFound = errors.New("cachebusting: file not found")
)

errors

Functions

func Create

func Create() (err error)

Create handles creation of the cache busting files using the default package level config.

func Debug

func Debug(yes bool)

Debug sets the Debug field on the package level config.

func DefaultConfig

func DefaultConfig()

DefaultConfig initializes the package level config with some defaults set. This wraps NewConfig() and saves the config to the package.

func DefaultEmbeddedConfig

func DefaultEmbeddedConfig(e embed.FS, files ...StaticFile)

DefaultEmbeddedConfig initializes the package level config with the provided static files and some defaults.

func DefaultOnDiskConfig

func DefaultOnDiskConfig(files ...StaticFile)

DefaultOnDiskConfig initializes the package level config with the provided static files and some defaults.

func DefaultStaticFileHandler

func DefaultStaticFileHandler(cacheDays int, pathToStaticFiles string) http.Handler

DefaultStaticFileHandler is an example handler for serving static files using the package level saved config.

func Development

func Development(yes bool)

Development sets the Development field on the package level config.

func FindFileDataByCacheBustURLPath

func FindFileDataByCacheBustURLPath(path string) (b []byte, err error)

FindFileDataByCacheBustURLPath wraps FindFileDataByCacheBustURLPath for the package level config.

func GetFilenamePairs

func GetFilenamePairs() (pairs map[string]string)

GetFilenamePairs returns the file pairs for the package level config.

func HashLength

func HashLength(l uint)

HashLength sets the HashLength field on the package level config.

func PrintEmbeddedFileList

func PrintEmbeddedFileList(e embed.FS)

PrintEmbeddedFileList prints out the list of files embedded into the executable. This should be used for diagnostics purposes only to confirm which files are embedded with the //go:embed directives elsewhere in your app.

func UseMemory

func UseMemory(yes bool)

UseMemory sets the UseMemory field on the package level config.

Types

type Config

type Config struct {
	//Development is used to disable cache busting.
	Development bool

	//Debug enables printing out diagnostic information.
	Debug bool

	//HashLength defines the number of characters prepended to each original file's name
	//to create the cache busting file's name.
	HashLength uint

	//StaticFiles is the list of files to cache bust.
	StaticFiles []StaticFile

	//UseEmbedded means files built into the golang executable will be used rather than
	//files stored on-disk. You must have read the embedded files, with code such as
	//var embeddedFiles embed.FS, prior and you must provide the embed.FS to the EmbeddedFS.
	UseEmbedded bool

	//EmbeddedFiles is the filesystem embedded into this executable via the embed package.
	//You must have read the embedded files, with code such as var embeddedFiles embed.FS,
	//prior and you must set UseEmbedded to true to enable use of these files.
	EmbeddedFS embed.FS

	//UseMemory causes the cache busting copy of each file to be stored in the app's
	//memory versus on disk. This is only applicable when you are using original files
	//stored on disk since if you are using embedded files the copies will always be
	//stored in memory. This is useful for times when your app is running on a system
	//that cannot write to disk.
	UseMemory bool
}

Config is the set of configuration settings for cache busting.

func GetConfig

func GetConfig() *Config

GetConfig returns the current state of the package level config.

func NewConfig

func NewConfig() *Config

NewConfig returns a config for managing your cache bust files with some defaults set.

func NewEmbeddedConfig

func NewEmbeddedConfig(e embed.FS, files ...StaticFile) *Config

NewEmbeddedConfig returns a config for managing your cache busted files when the original files embedded in the app.

func NewOnDiskConfig

func NewOnDiskConfig(files ...StaticFile) *Config

NewOnDiskConfig returns a config for managing your cache busted files when the original files are stored on disk.

func (*Config) Create

func (c *Config) Create() (err error)

Create handles the creation of the cache busting files and associated data. This calculates a hash of each static file, creates a copy of the static file, and saves the copy referenced by a new name using the hash. The copy of the original static file is either saved to disk (for original files stored on disk) or in memory (for embedded files or if the config's UseMemory field is set to true). This also saves some info for use in serving each cache busting copy of the static original file.

func (*Config) FindFileDataByCacheBustURLPath

func (c *Config) FindFileDataByCacheBustURLPath(urlPath string) (b []byte, err error)

FindFileDataByCacheBustURLPath returns a StaticFile's file data for the given url. This url is the url path the browser is requesting and should be the cache busting URL, not the original static file url. This is used when serving files but only when files are stored in memory.

func (*Config) GetFilenamePairs

func (c *Config) GetFilenamePairs() (pairs map[string]string)

GetFilenamePairs returns the original to cache busting filename pairs.

func (*Config) StaticFileHandler

func (c *Config) StaticFileHandler(cacheDays int, pathToStaticFiles string) http.Handler

StaticFileHandler is an example func that can be used to serve static files whether you are using embedded or on-disk original files and in memory or on disk cache busting files. You would use this func in your http router. This is an example since it requires a strict local directory structure and strict url path to each static file. Notes: - See package level comment about expected directory structure. - Extra headers added for diagnosing where files are stored in browser dev tools. - Set cacheDays to 0 to prevent caching in the user's browser.

type StaticFile

type StaticFile struct {
	//Local path is the full, complete path to the original copy of the static file
	//you want to cache bust. This is the path to the file on disk or emebedded in
	//the app using the embed package.
	//
	//Caution of using the correct slash style. It is best to construct the path using
	//the 'filepath' package (or the 'path' package for embedded files).
	//
	//If you are using embedded files, each path must start at the root folder you are
	//embedding. I.e.: if you use //go: embed website/static/js/script.min.js then the
	//path here must be "/website/static/js/script.min.js". You must have also read the
	//embedded files, with code such as var embeddedFiles embed.FS, prior and you must
	//provide the embed.FS to the CacheBustConfig.
	//
	//Ex.: /path/to/static/js/script.min.js
	LocalPath string

	//URLPath is the path off your domain, not including the domain, at which you
	//would serve the original static file. This is used to construct the path to
	//the cache busting file on which the cache busting file will be served so that
	//you can match up requests for the cache busting file with the file's data. This
	//is only used when storing the cache busting file in memory since if the cache
	//busting file is saved to disk, it will be saved in the same directory as the
	//original file and thus you can simply serve the static directory using an http
	//route and os.DirFS and http.FileServer.
	//
	//Always use forward slashes! It is best to construct the path using the 'path'
	//package.
	//
	//Ex.: /static/js/script.min.js
	URLPath string
	// contains filtered or unexported fields
}

StaticFile contains the local path to the on disk or embedded original static file and the URL path on which the file is served. We use the local path to look up the file and create the cache busting version of the file. We use the URL the file is served on to reply with the correct file's contents if the file is stored in the app's memory (for embedded file or if UseMemory is true).

func NewStaticFile

func NewStaticFile(localPath, urlPath string) StaticFile

NewStaticFile returns an object for a static file with the paths defined. This is just a helper func around creating the StaticFile object.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL