Skip to content

Commit

Permalink
Readme + Avif Support (#228)
Browse files Browse the repository at this point in the history
* Update deps

* Adding a better README and removing unused config

* Add avif to ordering

* Prefer avif over webp
  • Loading branch information
gauntface authored Jun 17, 2022
1 parent fcf2e2e commit 9785b18
Show file tree
Hide file tree
Showing 9 changed files with 223 additions and 89 deletions.
215 changes: 166 additions & 49 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,72 +1,189 @@
# go-html-asset-manager
![An image of a gopher moving a pile of boxes](default-social.png)

This is quite possibly the most terrible idea ever.
HTML asset manager provides a simple way to optimize CSS & JavaScripts assets in your statically generated site. You can think of it as an extra step in your build process.

`go-html-asset-manager` is library / set of CLI tools that look over a web project and perform
a set of operations on the source to optimize for web performance, this includes:
## What does it do?
The asset manager does the following:

- Inlining critical CSS
- Loading CSS asynchronously
- Generating muliple sizes of images
- Switching out `<img>` tags for `<picture>`
- Replaces Vimeo and YouTube videos with still images and JS to load videos.
Looks through all of your site's files for HTML, CSS, JS, and image files
The CSS and JS files are categorized into asset groups (group assignment is done via name convention discussed below):
Inline
Synchronous
Asynchronous
Preload
For each HTML file, the asset manager runs a set of "manipulators" listed below:
Removes any existing CSS and JS on the page
Async Src
Default Size for iframes
Add sizes to images
Generates picture element markup
Injects the required CSS and JS based on the HTML and classes used in the page
Add `lazyload` to images
Updates image for Open Graph to a suitable size
Wraps images and iframes with divs to apply appropriate ratios to the elements
Swaps out YouTube and Vimeo iframes with a static image

## Why do all of this?
Using go-html-asset-manager will improve the overall performance of a site without requiring a specific build process or site generator.

![Example score on Lighthouse](lighthouse-score.png)

## Installation
You will need to install golang to use this tool. Learn more at [go.dev](https://go.dev/doc/install).

```
Once go is installed, you can install the `htmlassets` tool with:

```bash
go install github.com/gauntface/go-html-asset-manager/v2/cmds/htmlassets@latest
```

If you'd like to generate image sizes, you can install the `genimgs` tool with:

```bash
go install github.com/gauntface/go-html-asset-manager/v2/cmds/genimgs@latest
```

## Usage
To use this tool, create an `asset-manager.json` file at the root of your project. This file will be used by both `htmlassets` and `genimgs`.

From your web project you can run it like so:

```shell
htmlassets
```
{
"html-dir": "public/",
"base-url": "https://www.gaunt.dev",
"assets": {
"static-dir": "public/",
"json-dir": "data/"
},
"gen-assets": {
"static-dir": "static/",
"output-dir": "static/generated/",
"output-bucket": "www.gaunt.dev",
"output-bucket-dir": "generated/",
"max-width": 700,
"max-density": 3
},
"img-to-picture": [
{
"id": "c-project-item__img",
"max-width": 620,
"source-sizes": [
"(min-width: 700px) 400px",
"100vw"
]
}
]
}
```

For configs living else where:
With this file, the next step is to name your CSS and JS files in the following structure:

```shell
htmlassets --config="${HOME}/project/asset-manager.json"
```
<HTML Element | CSS Classname>[-<inline | sync | async | preload>][.<media>].<css | js>
```
For example, your styles for `<h1>` elements might be:

`h1.css`: Primary CSS that you want to be inlined (**If no loading strategy is defined, inline is the default**)
`h1-async.css`: For optional styles.
`h1-sync.print.css`: Styles that are needed for print can be synchronously loaded by the browser when required.
`h1-async.js`: To load a script that adds anchor tags to the page.

If it's still unclear what is happening ![this image may help](explainer.png).

### Config

###### html-dir

This field is required and needs to be the path of the final HTML files. These files will be altered by `htmlassets`.

##### base-url

If you want to use `genimgs` to create a suitable image for social media posts, the base URL is needed to define a full URL to the generated image.

##### assets

`assets` tells `htmlassets` the various locations to look for images, CSS and JS.

##### assets > static-dir

`static-dir` is the path of CSS, JS and images served when deployed via "/".

For example, if the `static-dir` was `public/static/` and there was a JS file `public/static/script/example.js`, then `htmlassets` would assume this file could be referenced as `/script/example.js`.

##### assets > json-dir

Then add an assetmanager.json file.
You can define JSON files to describe assets you don't serve locally but should be injected into your HTML. A typical example is web fonts.

For example, `data/code.json` may define:

```json
{
"html-dir": "public/",
"base-url": "https://www.gauntface.com",
"assets": {
"static-dir": "public/",
"generated-dir": "public/generated/",
"json-dir": "themes/gauntface/data/hopin/"
},
"gen-assets": {
"static-dir": "static/",
"output-dir": "static/generated/",
"max-width": 800,
"max-density": 3
},
"img-to-picture": [
{
"id": "l-blog",
"max-width": 800,
"source-sizes": [
"(min-width: 800px) 800px",
"100vw"
]
},
{
"id": "c-blog-item__img",
"max-width": 200,
"source-sizes": [
"(min-width: 800px) 200px",
"20vw"
]
"css": {
"preload": [
"https://fonts.googleapis.com/css2?family=Fira+Code&display=swap"
]
}
],
"ratio-wrapper": ["l-blog"]
}
```

##### img-to-picture

If you want to convert `<img>` elements to `<picture>`, you can provide an array of queries with information on the appropriate sizes to apply.

```
"img-to-picture": [
{
"id": "c-project-item__img",
"max-width": 620,
"source-sizes": [
"(min-width: 700px) 400px",
"100vw"
]
},
{
"id": "c-blog-item__img",
"max-width": 620,
"source-sizes": [
"(min-width: 620px) 200px",
"100vw"
]
},
....
]
```

##### gen-assets

This config is used by `genimgs` to manage generated images stored locally and on AWS s3.

##### gen-assets > static-dir

The local path to find images used in your site.

##### gen-assets > output-dir

The local path to place any images generated. If you are using S3 to store your image files, you can add this directory to your `.gitignore` file as `genimgs` will check S3 for missing images.

##### gen-assets > output-bucket

The name of the S3 bucket to store these files.

##### gen-assets > output-bucket-dir

The directory in the bucket to store the generated files

##### gen-assets > max-width

The maximum width you would like your images to be. This value will be multiplied by the `max-density` to get the max-width for all desired screen densities.

##### gen-assets > max-density

The maximum screen density you'd like to account for when generating images.

## Future Work

There are some features/changes I'd like to make.

Split the config out for `genimgs` so there is a clear separation.
Ability to add styles if one or more elements/classnames are in the page. I.e. if you wanted to add styles for h1-h6, this approach requires 6 files, vs just one file with styles defined as `h1,h2,h3,h4,h5,h6 {...}`.

Thank you to [Ashley McNamara for the gopher image](https://github.com/ashleymcnamara/gophers).
Binary file added default-social.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added explainer.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ require (
github.com/mitchellh/go-homedir v1.1.0
github.com/otiai10/copy v1.7.0
github.com/schollz/progressbar/v3 v3.8.6
golang.org/x/net v0.0.0-20220615171555-694bf12d69de
golang.org/x/net v0.0.0-20220617184016-355a448f1bc9
)

require (
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,8 @@ golang.org/x/image v0.0.0-20220617043117-41969df76e82/go.mod h1:doUCurBvlfPMKfmI
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220615171555-694bf12d69de h1:ogOG2+P6LjO2j55AkRScrkB2BFpd+Z8TY2wcM0Z3MGo=
golang.org/x/net v0.0.0-20220615171555-694bf12d69de/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.0.0-20220617184016-355a448f1bc9 h1:Yqz/iviulwKwAREEeUd3nbBFn0XuyJqkoft2IlrvOhc=
golang.org/x/net v0.0.0-20220617184016-355a448f1bc9/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
Expand Down
Binary file added lighthouse-score.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
35 changes: 23 additions & 12 deletions manipulations/imgtopicture/imgtopicture.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ func shouldRun(conf *config.Config) bool {
return false
}

if conf.Assets == nil || conf.Assets.StaticDir == "" || conf.Assets.GeneratedDir == "" {
if conf.Assets == nil || conf.Assets.StaticDir == "" {
return false
}

Expand Down Expand Up @@ -234,9 +234,12 @@ func createSourceElement(imgtopic *config.ImgToPicConfig, imgs []genimgs.GenImg)
}

func orderedSourceSets(sourceSetByType map[string][]genimgs.GenImg) [][]genimgs.GenImg {
// Order of src-set is important and we prefer webp over other formats
// Order of src-set is important and we prefer avif, and then webp over other formats
desiredOrder := []string{
"image/avif",
"image/webp",
// Undefined is used for jpg and png
"",
}

sourceSets := [][]genimgs.GenImg{}
Expand All @@ -246,20 +249,28 @@ func orderedSourceSets(sourceSetByType map[string][]genimgs.GenImg) [][]genimgs.
continue
}
sourceSets = append(sourceSets, v)
delete(sourceSetByType, dt)
}

other := [][]genimgs.GenImg{}
for _, i := range sourceSetByType {
other = append(other, i)
otherTypes := []string{}
for t := range sourceSetByType {
knownType := false
for _, o := range desiredOrder {
if o == t {
knownType = true
break
}
}
if !knownType {
otherTypes = append(otherTypes, t)
}
}

// Sort the other values to ensure tests are reliable
sort.Slice(other, func(i, j int) bool {
return other[i][0].Type < other[j][0].Type
})

sourceSets = append(sourceSets, other...)
if len(otherTypes) > 0 {
fmt.Printf("⚠️ %v unexpected image format(s) in picture source set:\n", len(otherTypes))
for _, t := range otherTypes {
fmt.Printf(" - %v\n", t)
}
}

return sourceSets
}
Expand Down
Loading

0 comments on commit 9785b18

Please sign in to comment.