Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/mavolin/hashets

#️⃣ Cache busting for Go by using file hashes. Hashing can be done both at run or compile time.
https://github.com/mavolin/hashets

cache-buster cache-busting caching go hashing

Last synced: 5 days ago
JSON representation

#️⃣ Cache busting for Go by using file hashes. Hashing can be done both at run or compile time.

Awesome Lists containing this project

README

        


hashets

[![Go Reference](https://pkg.go.dev/badge/github.com/mavolin/hashets.svg)](https://pkg.go.dev/github.com/mavolin/hashets)
[![Test](https://github.com/mavolin/hashets/actions/workflows/test.yml/badge.svg)](https://github.com/mavolin/hashets/actions)
[![Code Coverage](https://codecov.io/gh/mavolin/hashets/branch/develop/graph/badge.svg?token=ewFEQGgMES)](https://codecov.io/gh/mavolin/hashets)
[![Go Report Card](https://goreportcard.com/badge/github.com/mavolin/hashets)](https://goreportcard.com/report/github.com/mavolin/hashets)
[![License MIT](https://img.shields.io/github/license/mavolin/hashets)](./LICENSE)

---

## About

Hashets (a portmanteau of 'hash' and 'assets') is a utility for handling cache busting of static assets.
It works by adding the hash of the file's contents to the file name.

## Main Features

* ⚡ Three options:
1. Either generate files with hashed names before compiling,
2. use `hashets.HashToDir` or `hashets.HashToTempDir` at runtime,
3. or create a `hashets.FSWrapper` which translates requests for hashed file names to their original names.
* 🧒 Easy integration into templates by using a map of file names to hashed file names
* 📦 Support for `fs.FS`
* 🏖 Hassle-free versioning, that only causes refetching of files when their contents change (vs. `?v=1.2.3`)

## Examples

First impressions matter, so here are some examples of how to use hashets.

### Using `hashets.WrapFS`

> **This method is for you, if:**
>
> * 🧒 You want the easiest solution of all
> * 🤏 Have small assets, or you don't mind if your application takes a few milliseconds longer to start
> * 🕚 You know your assets at runtime
> * 🕵 You need cache busting during development and not just in production

`hashets.WrapFS` simply wraps an `fs.FS`, calculates the hashes of all its files,
and translates requests for hashed file names to their original names:

Add a `static.go` to your `static` directory:

```
static
├── file_to_hash.ext
└── static.go
```

```go
package static

import (
"embed"

"github.com/mavolin/hashets/hashets"
)

//go:embed file_to_hash.ext
var assets embed.FS

var (
FS *hashets.FSWrapper
FileNames hashets.Map
)

func init() {
var err error
FS, FileNames, err = hashets.WrapFS(assets, hashets.Options{})
if err != nil {
panic(err)
}
}
```

`FS` now translates requests for `FS.Open("file_to_hash_generateHash.ext")` to `assets.Open("file_to_hash.ext")`.
Additionally, `FileNames` maps all original file names to their hashed equivalents:

```go
var FileNames = hashets.Map{
"file_to_hash.ext": "file_to_hash_generateHash.ext",
}
```

Use this map in your templates to generate links to your assets:

```html

```

Then simply serve `FS` under `/static`:

```go
http.Handle("/static/", http.FileServer(http.FS(FS)))
```

Of course, instead of an `embed.FS`, you can also use any other `fs.FS` implementation, such as `os.DirFS`, etc.

### Using `go generate`

> **This method is for you, if:**
>
> * 📏 You have larger assets and need lightning fast startup times
> * 🕑 You know your assets at compile time
> * 🕵 You need cache busting during development and not just in production

Add a `static.go` to your `static` directory:

```
static
├── orig
│ └── file_to_hash.ext
└── static.go
```

```go
package static

import "static/hashed"

//go:generate hashets -o hashed orig
```

Now run `go generate`.
Your file structure should now look like this:

```
static
├── hashed
│ ├── file_to_hash_generateHash.ext
│ └── hashets_map.go
├── orig
│ └── file_to_hash.ext
└── static.go
```

Besides the hashed files, `hashets` also generated a `hashets_map.go` file,
that contains the `FileNames` `hashets.Map`, that maps the original file names
to their hashed equivalents:

```go
package hashed

import "github.com/mavolin/hashets/hashets"

var FileNames = hashets.Map{
"file_to_hash.ext": "file_to_hash_generateHash.ext",
}
```

### During CI

> **This method is for you, if:**
>
> * 📏 You have larger assets and need lightning fast startup times
> * 🕑 You know your assets at compile time
> * 🤷 You don't need cache busting during development

The `go generate` solution has one big drawback:
If you generate static assets in the same `go generate` run and `hashets` is
executed before the files are generated, the hashes will be wrong.

Luckily, there is another handy solution:

Add a `static.go` and a `hashets_map.go` to your `static` directory:

```
static
├── file_to_hash.ext
├── hashets_map.go
└── static.go
```

`static.go`
```go
package static

import "embed"

// It is important that you use wildcards for your files, as otherwise the
// hashed files generated by your CI won't be included in the embed.FS.
//go:embed file_to_hash*.txt
var FS embed.FS
```

`hashets_map.go`
```go
package static

import "github.com/mavolin/hashets"

// FileNames maps the original file names to their hashed equivalents.
// Unless you run hashets, this map will be nil, which causes [hashets.Map.Get]
// to behave specially:
// Instead of returning the hashed file name, it will return the path that it
// is given as-is.
//
// That means, unless you run hashets, you will simply use your unhashed assets.
var FileNames hashets.Map
```

Now, in your CI, run `hashets` before compiling:

```sh
hashets -replace -ignore static.go static
```

This will replace all of your assets with their hashed equivalents, i.e.
replace `file_to_hash.ext` with `file_to_hash_generateHash.ext`.
Additionally, it will overwrite `hashets_map.go` with a `FileNames` map that
contains the correct mappings.

### Using `hashets.HashToDir` and `hashets.HashToTempDir`

> **This method is for you, if:**
>
> * 🛠 You need maximum customizability
> * 🤏 You have smaller assets or don't mind if your application takes a few milliseconds longer to start
> * 🕚 You know your assets at compile or runtime
> * 🕵 You need cache busting during development and not just in production

If all of the above don't do the trick for you, you can also create hashes
using `hashets.HashToDir` and `hashets.HashToTempDir`, which will generate
hashed files and write them to an arbitrary or a temporary directory.

Head over to [pkg.go.dev](https://pkg.go.dev/github.com/mavolin/hashets) to read more.

## License

Built with ❤ by [Maximilian von Lindern](https://github.com/mavolin).
Available under the [MIT License](./LICENSE).