Skip to content

Commit 6d8ecd3

Browse files
author
Adam
committed
feat(plugin): Add compression and resizing for image assets
1 parent c99c807 commit 6d8ecd3

File tree

2 files changed

+56
-8
lines changed

2 files changed

+56
-8
lines changed

docs/plugins/Assets.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,10 @@ Note that all static assets will then be accessible through its path on your gen
1111
> [!note]
1212
> For information on how to add, remove or configure plugins, see the [[configuration#Plugins|Configuration]] page.
1313
14-
This plugin has no configuration options.
14+
This plugin accepts the following configuration options:
15+
16+
- `resizeImages`: A subset of the [Sharp resizing options](https://sharp.pixelplumbing.com/api-resize). Defaults to `{ width: 1700, fit: 'inside' }`.
17+
- `compressImages`: If `true` (default), enable image compression. Disable to copy images as-is without modification.
1518

1619
## API
1720

quartz/plugins/emitters/assets.ts

Lines changed: 52 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,82 @@
1-
import { FilePath, joinSegments, slugifyFilePath } from "../../util/path"
1+
import { FilePath, joinSegments, slugifyFilePath, getFileExtension } from "../../util/path"
22
import { QuartzEmitterPlugin } from "../types"
33
import path from "path"
44
import fs from "fs"
5+
import sharp, { FitEnum } from "sharp"
6+
import { styleText } from "util"
57
import { glob } from "../../util/glob"
68
import { Argv } from "../../util/ctx"
79
import { QuartzConfig } from "../../cfg"
810

11+
interface Options {
12+
resizeImages?: { width?: number; height?: number; fit?: keyof FitEnum }
13+
compressImages?: boolean
14+
}
15+
16+
const defaultOptions: Options = {
17+
resizeImages: { width: 1700, fit: "inside" },
18+
compressImages: true,
19+
}
20+
21+
// Accepted Sharp formats https://sharp.pixelplumbing.com/#formats
22+
const imageExtensions = [".png", ".jpg", ".jpeg", ".webp", ".gif", ".avif", ".tif", ".tiff", ".svg"]
23+
924
const filesToCopy = async (argv: Argv, cfg: QuartzConfig) => {
1025
// glob all non MD files in content folder and copy it over
1126
return await glob("**", argv.directory, ["**/*.md", ...cfg.configuration.ignorePatterns])
1227
}
1328

14-
const copyFile = async (argv: Argv, fp: FilePath) => {
29+
const copyFile = async (argv: Argv, fp: FilePath, opts: Options) => {
1530
const src = joinSegments(argv.directory, fp) as FilePath
16-
1731
const name = slugifyFilePath(fp)
1832
const dest = joinSegments(argv.output, name) as FilePath
1933

2034
// ensure dir exists
2135
const dir = path.dirname(dest) as FilePath
2236
await fs.promises.mkdir(dir, { recursive: true })
2337

24-
await fs.promises.copyFile(src, dest)
38+
const ext = getFileExtension(fp)?.toLowerCase()
39+
if (ext && imageExtensions.includes(ext)) {
40+
await copyImage(src, dest, opts)
41+
} else {
42+
await copyBlob(src, dest)
43+
}
44+
2545
return dest
2646
}
2747

28-
export const Assets: QuartzEmitterPlugin = () => {
48+
const copyImage = async (src: FilePath, dest: FilePath, opts: Options) => {
49+
if (!opts.compressImages) {
50+
await copyBlob(src, dest)
51+
} else if (opts.resizeImages) {
52+
await sharp(src).resize(opts.resizeImages).toFile(dest)
53+
} else {
54+
await sharp(src).toFile(dest)
55+
}
56+
}
57+
58+
const copyBlob = async (src: FilePath, dest: FilePath) => {
59+
await fs.promises.copyFile(src, dest)
60+
}
61+
62+
export const Assets: QuartzEmitterPlugin<Partial<Options>> = (opts) => {
63+
opts = { ...defaultOptions, ...opts }
64+
65+
if ((opts.resizeImages?.width || opts.resizeImages?.height) && !opts.compressImages) {
66+
console.warn(
67+
styleText(
68+
"yellow",
69+
"Your asset resizing options are incompatible - enable compression to resize images",
70+
),
71+
)
72+
}
73+
2974
return {
3075
name: "Assets",
3176
async *emit({ argv, cfg }) {
3277
const fps = await filesToCopy(argv, cfg)
3378
for (const fp of fps) {
34-
yield copyFile(argv, fp)
79+
yield copyFile(argv, fp, opts)
3580
}
3681
},
3782
async *partialEmit(ctx, _content, _resources, changeEvents) {
@@ -40,7 +85,7 @@ export const Assets: QuartzEmitterPlugin = () => {
4085
if (ext === ".md") continue
4186

4287
if (changeEvent.type === "add" || changeEvent.type === "change") {
43-
yield copyFile(ctx.argv, changeEvent.path)
88+
yield copyFile(ctx.argv, changeEvent.path, opts)
4489
} else if (changeEvent.type === "delete") {
4590
const name = slugifyFilePath(changeEvent.path)
4691
const dest = joinSegments(ctx.argv.output, name) as FilePath

0 commit comments

Comments
 (0)