Skip to content

Commit

Permalink
feat: caching of generated images
Browse files Browse the repository at this point in the history
  • Loading branch information
pzerelles committed Nov 29, 2023
1 parent 9b7a4f5 commit c4cd4e9
Show file tree
Hide file tree
Showing 8 changed files with 313 additions and 50 deletions.
5 changes: 5 additions & 0 deletions .changeset/quick-roses-fail.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'vite-imagetools': patch
---

feat: caching of generated images
1 change: 1 addition & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,4 @@ unclear!

- [Extend Imagetools](guide/extending.md)
- [Sharp's documentation](https://sharp.pixelplumbing.com)
- [Caching](guide/caching.md)
39 changes: 39 additions & 0 deletions docs/guide/caching.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# Caching

To speed up a build pipeline with many images, the generated images can be cached on disk.
If the source image changes, the cached images will be regenerated.

## How to enable caching

To enable caching, the cache directory has to be configured.

```
// vite.config.js, etc
...
plugins: [
react(),
imagetools({
cacheDir: './node_modules/.cache/imagetools'
})
]
...
```

## Cache retention to remove unused images

When an image is no longer there or the transformation parameters change, the previously
cached images will be removed after a configurable retention period.
The default retention is 84600 seconds. A value of 0 will disable this mechanism.

```
// vite.config.js, etc
...
plugins: [
react(),
imagetools({
cacheDir: './node_modules/.cache/imagetools',
cacheRetention: 169200
})
]
...
```
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
93 changes: 91 additions & 2 deletions packages/vite/src/__tests__/main.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { build, createLogger } from 'vite'
import { InlineConfig, build, createLogger } from 'vite'
import { imagetools } from '../index'
import { join } from 'path'
import { getFiles, testEntry } from './util'
Expand All @@ -7,7 +7,15 @@ import { OutputAsset, OutputChunk, RollupOutput } from 'rollup'
import { JSDOM } from 'jsdom'
import sharp from 'sharp'
import { afterEach, describe, test, expect, it, vi } from 'vitest'
import { createBasePath } from '../utils'
import { createBasePath, generateCacheID } from '../utils'
import { readFile, rm, utimes } from 'fs/promises'

const processPath = process.cwd()

const extractCreated = (path: string) =>
readFile(path, { encoding: 'utf8' })
.then((d) => JSON.parse(d).created as number)
.catch(() => undefined)

expect.extend({ toMatchImageSnapshot })

Expand Down Expand Up @@ -458,6 +466,65 @@ describe('vite-imagetools', () => {
expect(window.__IMAGE__).toHaveProperty('hasAlpha')
})
})
describe('cacheRetention', () => {
test('is used to clear cache with default 86400', async () => {
const cacheDir = './node_modules/.cache/imagetools_test_cacheRetention'
await rm(cacheDir, { recursive: true, force: true })
const root = join(__dirname, '__fixtures__')
const config: (width: number) => InlineConfig = (width) => ({
root,
logLevel: 'warn',
build: { write: false },
plugins: [
testEntry(`
import Image from "./pexels-allec-gomes-5195763.png?w=${width}"
export default Image
`),
imagetools({ cacheDir })
]
})
await build(config(300))

const relativeRoot = root.startsWith(processPath) ? root.slice(processPath.length + 1) : root
const cacheID = generateCacheID(`${relativeRoot}/pexels-allec-gomes-5195763.png?w=300`)
const indexPath = `${cacheDir}/${cacheID}/index.json`
const created = await extractCreated(indexPath)
expect(created).toBeTypeOf('number')

await build(config(200))
expect(await extractCreated(indexPath)).toBe(created)

const date = new Date(Date.now() - 86400000)
await utimes(indexPath, date, date)
await build(config(200))
expect(await extractCreated(indexPath)).not.toBe(created)
})
})
describe('cacheDir', () => {
test('is used', async () => {
const cacheDir = './node_modules/.cache/imagetools_test_cacheRetention'
await rm(cacheDir, { recursive: true, force: true })
const root = join(__dirname, '__fixtures__')
await build({
root,
logLevel: 'warn',
build: { write: false },
plugins: [
testEntry(`
import Image from "./pexels-allec-gomes-5195763.png?w=300"
export default Image
`),
imagetools({ cacheDir })
]
})

const relativeRoot = root.startsWith(processPath) ? root.slice(processPath.length + 1) : root
const cacheID = generateCacheID(`${relativeRoot}/pexels-allec-gomes-5195763.png?w=300`)
const indexPath = `${cacheDir}/${cacheID}/index.json`
const created = await extractCreated(indexPath)
expect(created).toBeTypeOf('number')
})
})
})

test('relative import', async () => {
Expand Down Expand Up @@ -516,6 +583,28 @@ describe('vite-imagetools', () => {
expect(files[0].source).toMatchImageSnapshot()
})

test('import with space in identifier and cache', async () => {
const cacheDir = './node_modules/.cache/imagetools_test_import_with_space'
await rm(cacheDir, { recursive: true, force: true })
const config: InlineConfig = {
root: join(__dirname, '__fixtures__'),
logLevel: 'warn',
build: { write: false },
plugins: [
testEntry(`
import Image from "./with space.png?w=300"
export default Image
`),
imagetools({ cacheDir })
]
}
await build(config)
const bundle = (await build(config)) as RollupOutput | RollupOutput[]

const files = getFiles(bundle, '**.png') as OutputAsset[]
expect(files[0].source).toMatchImageSnapshot()
})

test('non existent file', async () => {
const p = build({
root: join(__dirname, '__fixtures__'),
Expand Down
Loading

0 comments on commit c4cd4e9

Please sign in to comment.