-
-
Notifications
You must be signed in to change notification settings - Fork 60
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
e45f30b
commit 4b82f5f
Showing
1 changed file
with
110 additions
and
75 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,105 +1,140 @@ | ||
import sharp from 'sharp' | ||
import { buildOptions, has, Options } from "./options" | ||
import { directives } from './directives' | ||
import { createHash } from 'crypto' | ||
import path from 'path' | ||
import { promises as fs } from 'fs' | ||
import { dataToEsm, createFilter } from "rollup-pluginutils" | ||
|
||
interface pluginOptions { | ||
include?: Array<string | RegExp> | string | RegExp | ||
exclude?: Array<string | RegExp> | string | RegExp | ||
import { createFilter, dataToEsm } from '@rollup/pluginutils' | ||
import { Plugin } from 'vite' | ||
import { get as cacheGet, put as cachePut } from 'cacache' | ||
import findCacheDir from 'find-cache-dir' | ||
import { basename, extname } from 'path' | ||
|
||
import * as directives from './directives' | ||
import * as outputFormats from './output' | ||
import { buildDirectiveOptions } from './util' | ||
import sharp, { Sharp } from 'sharp' | ||
import { DirectiveOptions, DirectiveContext, ImageTransformation, Directive } from './types' | ||
|
||
const options = { | ||
include: '**/*.{heic,heif,avif,jpeg,jpg,png,tiff,webp,gif}?*', | ||
exclude: 'public/**/*', | ||
cache: findCacheDir({ name: 'vite-imagetools' }) | ||
} | ||
|
||
const defaultOptions: pluginOptions = { | ||
include: ['**/*.jpeg', '**/*.jpg', '**/*.png', '**/*.webp', '**/*.webp', '**/*.avif', '**/*.gif', '**/*.heif'], | ||
exclude: ['public/**/*'] | ||
} | ||
export default function imagetools(): Plugin { | ||
const filter = createFilter('**/*.{heic,heif,avif,jpeg,jpg,png,tiff,webp,gif}?*') | ||
|
||
export default function (userOptions: pluginOptions = {}) { | ||
const pluginOptions = { ...defaultOptions, ...userOptions } | ||
return { | ||
name: 'imagetools', | ||
enforce: 'pre', | ||
async load(id) { | ||
const src = new URL(id, 'file://') | ||
|
||
const filter = createFilter(pluginOptions.include, pluginOptions.exclude) | ||
if (!filter(src.href)) return null | ||
|
||
const CACHE_DIR = './node_modules/.cache/vite-plugin-imageset' | ||
const pipelineConfigs = buildDirectiveOptions(src) | ||
|
||
let globalConfig | ||
const outputMetadatas = await Promise.all(pipelineConfigs.map(async config => { | ||
let { data, metadata } = await restoreFromCache(id, options.cache) | ||
|
||
return { | ||
name: 'imageset', | ||
enforce: 'pre' as 'pre', | ||
async buildStart() { | ||
await fs.mkdir(CACHE_DIR, { recursive: true }) | ||
}, | ||
configResolved(config) { | ||
globalConfig = config | ||
}, | ||
async load(assetPath: string) { | ||
const url = new URL(assetPath, 'file://') | ||
|
||
if (!filter(url.pathname)) { | ||
return null | ||
} else { | ||
const options = buildOptions(url, directives()) | ||
const id = generateId(assetPath) | ||
const ext = options.format ? `.${options.format}` : path.extname(url.pathname) | ||
const cachePath = path.join(CACHE_DIR, id + ext) | ||
|
||
if (globalConfig.command === 'serve') { | ||
if (!(await isCached(cachePath))) { | ||
const content = await transformImage(url, options) | ||
await fs.writeFile(cachePath, content) | ||
} | ||
if (!data) { | ||
// build transformation pipeline | ||
const { transforms, metadata: _metadata, parametersUsed } = buildTransforms(config,directives) | ||
|
||
metadata = { src: src.pathname, ..._metadata } | ||
|
||
console.log(transforms,metadata); | ||
|
||
if (!this.meta.watchMode) { | ||
// transform the image | ||
const image = transformImage(sharp(src.pathname), transforms) | ||
|
||
return dataToEsm('/' + path.relative(globalConfig.root, cachePath), globalConfig.build.rollupOptions) | ||
} else { | ||
let content: Uint8Array | ||
if (await isCached(cachePath)) { | ||
content = await fs.readFile(cachePath) | ||
} else { | ||
content = await transformImage(url, options) | ||
await fs.writeFile(cachePath, content) | ||
data = await image.toBuffer() | ||
|
||
// assign the metadata | ||
metadata = Object.assign({}, await image.metadata(), metadata) | ||
|
||
delete metadata.xmp | ||
// cache the result | ||
|
||
//await cachePut(options.cache,id,data,{ metadata }) | ||
} | ||
} | ||
|
||
const file = url.pathname | ||
if(!this.meta.watchMode) { | ||
// emit the file | ||
const file = src.pathname | ||
|
||
const fileHandle = this.emitFile({ | ||
name: `${path.basename(file, path.extname(file))}${ext}`, | ||
name: `${basename(file, extname(file))}.${metadata.format}`, | ||
type: 'asset', | ||
source: content | ||
source: data | ||
}) | ||
|
||
return dataToEsm(`__VITE_ASSET__${fileHandle}__`, globalConfig.build.rollupOptions) | ||
// adjust the src atrribute | ||
metadata.src = `__VITE_ASSET__${fileHandle}__` | ||
} | ||
|
||
} | ||
return metadata | ||
})) | ||
|
||
const output = Object.values(outputFormats) | ||
.map(f => f(src,outputMetadatas)) | ||
.find(res => !!res) | ||
|
||
return dataToEsm(output) | ||
} | ||
} | ||
} | ||
|
||
async function isCached(path: string) { | ||
function buildTransforms(config: DirectiveOptions, directives:Record<string,Directive>) { | ||
const parametersUsed = new Set() | ||
const metadata = {} | ||
|
||
const context: DirectiveContext = { | ||
useParam: (key) => parametersUsed.add(key), | ||
setMetadata: (key, value) => metadata[key] = value | ||
} | ||
|
||
const transforms = Object.values(directives) | ||
.map(dir => dir(config as any, context)) | ||
.filter(transform => typeof transform === 'function') | ||
|
||
return { metadata, transforms, parametersUsed } | ||
} | ||
|
||
async function restoreFromCache(id: string, cachePath: string) { | ||
let res | ||
try { | ||
const stat = await fs.stat(path) | ||
return stat.isFile() | ||
res = await cacheGet(options.cache, id) | ||
} catch { | ||
return false | ||
res = {} | ||
} | ||
return res | ||
} | ||
|
||
function transformImage(image: Sharp, transforms: ImageTransformation[]) { | ||
return transforms.reduce((image, transform) => transform(image), image) | ||
} | ||
|
||
function transformImage(url: URL, options: Options) { | ||
let pipeline = sharp(url.pathname) | ||
// try { | ||
// const { data, metadata } = await cacheGet(options.cache, id) | ||
// outputData = data | ||
// outputMetadata = metadata | ||
// } catch { | ||
// const { transforms, metadata, usedParameters } = buildTransforms(config) | ||
|
||
if (has(options, 'size', 'width', 'height')) { | ||
pipeline = pipeline.resize(options) | ||
} | ||
if (has(options, 'format')) { | ||
pipeline = pipeline.toFormat(options.format) | ||
} | ||
// if (!this.meta.watchMode) { | ||
// const image = sharp(src.pathname) | ||
|
||
return pipeline.toBuffer() | ||
} | ||
// outputMetadata = Object.assign({ src: src.pathname }, metadata, await image.metadata()) | ||
|
||
// outputMetadata.xmp = null | ||
|
||
// outputData = await (await transformImage(image, transforms)).toBuffer() | ||
|
||
function generateId(id: string) { | ||
return createHash('sha1').update(id).digest('hex').slice(0, 16) | ||
} | ||
// await cachePut( | ||
// options.cache, | ||
// id, | ||
// outputData, | ||
// { | ||
// metadata: outputMetadata | ||
// } | ||
// ) | ||
// } | ||
// } |