Skip to content

Commit

Permalink
chore: update code
Browse files Browse the repository at this point in the history
  • Loading branch information
ErKeLost committed Dec 15, 2024
1 parent a8a8b4c commit 742572e
Show file tree
Hide file tree
Showing 6 changed files with 210 additions and 80 deletions.
2 changes: 1 addition & 1 deletion playground/src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
<!-- <img src="/img/a.png" alt="" /> -->
<!-- <img src="/img/b.png" alt="" /> -->
<!-- <img src="/img/c.png" alt="" /> -->
<img style="width: 1200px" src="./assets/image/wallhaven-x61xdo.jpg" alt="" />
<!-- <img style="width: 1200px" src="./assets/image/wallhaven-x61xdo.jpg" alt="" /> -->
<!-- <img :src="Img" alt=""> -->
<!-- <div class="a"></div> -->
<!-- <div class="b"></div> -->
Expand Down
25 changes: 1 addition & 24 deletions playground/vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,28 +18,5 @@ export default defineConfig({
port: 8451,
},
// publicDir: 'base/public',
plugins: [
vue(),
imagemin({
compress: {
jpg: {
quality: 10,
},
jpeg: {
quality: 10,
},
png: {
quality: 10,
},
webp: {
quality: 0,
},
},
conversion: [
{ from: 'jpeg', to: 'webp' },
{ from: 'jpg', to: 'webp' },
{ from: 'png', to: 'webp' },
],
}),
],
plugins: [vue(), imagemin()],
});
56 changes: 14 additions & 42 deletions src/core/context.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
/* eslint-disable no-return-await */
/* eslint-disable no-await-in-loop */
import fs from 'node:fs/promises';
import { Buffer } from 'node:buffer';
import { cpus } from 'node:os';
import { performance } from 'node:perf_hooks';

import { basename, extname, isAbsolute, join, relative, resolve } from 'pathe';
import { basename, extname, join, resolve } from 'pathe';
import { createFilter } from '@rollup/pluginutils';
import { optimize } from 'svgo';
import chalk from 'chalk';
Expand All @@ -14,11 +15,13 @@ import { encodeMap, encodeMapBack } from './encodeMap';
import {
exists,
filterFile,
generateImageID,
getBundleImageSrc,
hasImageFiles,
isSvgFile,
isTurnImageType,
parseId,
transformFileName,
updateCssReferences,
} from './utils';
import { defaultOptions } from './compressOptions';
import devalue from './devalue';
Expand Down Expand Up @@ -156,17 +159,16 @@ export default class Context {
continue;
}

// 4. 创建处理任务
const task = async () => {
if (!isSvgFile(path)) {
return {
originFileName: asset.fileName,
result: await this.generateSquooshBundle(imagePool, path),
result: await this.processRasterImage(imagePool, path),
};
}
return {
originFileName: asset.fileName,
result: await this.generateSvgBundle(path),
result: await this.processSvg(path),
};
};

Expand Down Expand Up @@ -249,7 +251,7 @@ export default class Context {
}

// squoosh
async generateSquooshBundle(imagePool, item) {
async processRasterImage(imagePool, item) {
const start = performance.now();
const size = await fs.lstat(item);
const oldSize = size.size;
Expand Down Expand Up @@ -279,6 +281,8 @@ export default class Context {
[type!]: defaultSquooshOptions[type!],
};

console.log(currentType);

try {
await image.encode(currentType);
} catch (error) {
Expand Down Expand Up @@ -341,7 +345,8 @@ export default class Context {
spinner.stop();
}

async generateSvgBundle(item) {
async processSvg(item) {
const { assetsDir, outDir,base: configBase } = this.config;
const svgCode = await fs.readFile(item, 'utf8');

const result = optimize(svgCode, {
Expand All @@ -352,7 +357,6 @@ export default class Context {

const generateSrc = getBundleImageSrc(item, this.config.options);
const base = basename(item, extname(item));
const { assetsDir, outDir } = this.config;
const imageName = `${base}-${generateSrc}`;
const start = performance.now();
const size = await fs.lstat(item);
Expand All @@ -368,7 +372,7 @@ export default class Context {
};

compressSuccess(
join(this.config.base, outDir, svgResult.fileName),
join(configBase, outDir, svgResult.fileName),
newSize,
oldSize,
start,
Expand All @@ -377,18 +381,6 @@ export default class Context {
}
}

function getBundleImageSrc(filename: string, options: any) {
const currentType =
options.conversion.find(
(item) => item.from === extname(filename).slice(1),
) ?? extname(filename).slice(1);
const id = generateImageID(
filename,
currentType.to ?? extname(filename).slice(1),
);
return id;
}

export function resolveOptions(
options: any,
configOption: any,
Expand Down Expand Up @@ -448,24 +440,4 @@ export async function transformCode(
);
await fs.writeFile(finallyPath, item[sourceCode]);
});
}

export function isSvgFile(filename) {
return extname(filename) === '.svg';
}

function updateCssReferences(
cssContent: string,
fileNameMap: Map<string, string>,
): string {
try {
return Array.from(fileNameMap).reduce(
(updatedCssContent, [oldFileName, newFileName]) =>
updatedCssContent.replace(new RegExp(oldFileName, 'g'), newFileName),
cssContent,
);
} catch (error) {
console.error('[unplugin-imagemin] Error processing CSS:', error);
return cssContent;
}
}
}
2 changes: 2 additions & 0 deletions src/core/fileHandler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export default class FileHandler {
}
155 changes: 155 additions & 0 deletions src/core/imageProcess.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
// ImageProcessor.ts
import fs from 'node:fs/promises';
import { cpus } from 'node:os';
import { extname, basename, join } from 'pathe';
import { ImagePool } from 'squoosh';
import { optimize } from 'svgo';
import { performance } from 'node:perf_hooks';
import type { ResolvedOptions } from './types';
import { isSvgFile, getBundleImageSrc } from './Utils';

export default class ImageProcessor {
private imagePool: ImagePool;
private options: ResolvedOptions;
private cache: Cache;

constructor(options: ResolvedOptions, cache: Cache) {
this.options = options;
this.cache = cache;
this.imagePool = new ImagePool(cpus().length);
}

// /**
// * 处理图像文件,支持常规图像和 SVG
// * @param filePath 图像文件的路径
// * @returns 处理后的图像信息
// */
async processImage(filePath: string): Promise<{ fileName: string; source: Buffer | string }> {
if (isSvgFile(filePath)) {
return this.processSvg(filePath);
} else {
return this.processRasterImage(filePath);
}
}

/**
* 处理常规光栅图像(如 PNG、JPEG、WEBP 等)
* @param filePath 图像文件的路径
* @returns 处理后的图像信息
*/
private async processRasterImage(filePath: string): Promise<{ fileName: string; source: Buffer }> {
const startTime = performance.now();
const { ext } = this.getFileInfo(filePath);

const userConversion = this.options.conversion.find(
(item) => item.from === ext
);

const targetType = userConversion?.to ?? ext;
const imageId = getBundleImageSrc(filePath, this.options);
const imageName = `${basename(filePath, extname(filePath))}-${imageId}.${targetType}`;
const outputFilePath = join(this.options.assetsDir, imageName);

// const fileStat = await fs.stat(filePath);
// const cacheKey = `${filePath}-${targetType}`;
// const isCached = await this.cache.hasCachedAsset(cacheKey, fileStat.mtimeMs);

// if (isCached) {
// Logger.info(`Using cached image for ${filePath}`);
// const cachedData = await this.cache.getCachedAsset(cacheKey);
// return { fileName: outputFilePath, source: cachedData };
// }

const imageBuffer = await fs.readFile(filePath);
const image = this.imagePool.ingestImage(imageBuffer);

const encodeOptions = this.getEncodeOptions(targetType);
if (!encodeOptions) {
throw new Error(`Unsupported target image type: ${targetType}`);
}

try {
await image.encode({ [targetType]: encodeOptions });
} catch (error) {
Logger.error(`Error encoding image ${filePath}`, error as Error);
throw error;
}

const encodedImage = await image.encodedWith[targetType];
const newSize = encodedImage.size;
const oldSize = imageBuffer.length;

// 保存缓存
await this.cache.setCachedAsset(cacheKey, encodedImage.binary);

const timeSpent = (performance.now() - startTime).toFixed(2);
// Logger.success(
// `Compressed ${filePath} [${(oldSize / 1024).toFixed(2)} KB -> ${(newSize / 1024).toFixed(2)} KB] in ${timeSpent} ms`
// );

return {
fileName: outputFilePath,
source: encodedImage.binary,
};
}

private async processSvg(filePath: string): Promise<{ fileName: string; source: string }> {
const startTime = performance.now();

const svgContent = await fs.readFile(filePath, 'utf8');
const result = optimize(svgContent, {
multipass: true,
path: filePath,
});

if ('data' in result) {
const optimizedSvg = result.data;
const oldSize = Buffer.byteLength(svgContent, 'utf8');
const newSize = Buffer.byteLength(optimizedSvg, 'utf8');

// await this.cache.setCachedAsset(filePath, optimizedSvg);

const imageId = getBundleImageSrc(filePath, this.options);
const imageName = `${basename(filePath, '.svg')}-${imageId}.svg`;
const outputFilePath = join(this.options.assetsDir, imageName);

const timeSpent = (performance.now() - startTime).toFixed(2);
Logger.success(
`Optimized SVG ${filePath} [${(oldSize / 1024).toFixed(2)} KB -> ${(newSize / 1024).toFixed(2)} KB] in ${timeSpent} ms`
);

return {
fileName: outputFilePath,
source: optimizedSvg,
};
} else {
// log(`Error optimizing SVG ${filePath}`, result.error as Error);
throw new Error(`Failed to optimize SVG: ${result.error}`);
}
}

private getFileInfo(filePath: string): { base: string; ext: string } {
const base = basename(filePath);
const ext = extname(filePath).slice(1); // 去掉扩展名前的点
return { base, ext };
}

private getEncodeOptions(targetType: string): Record<string, any> | null {
switch (targetType) {
case 'avif':
return this.options.compress.avif;
case 'webp':
return this.options.compress.webp;
case 'mozjpeg':
return this.options.compress.mozjpeg;
case 'oxipng':
return this.options.compress.oxipng;
default:
return null;
}
}

async close() {
await this.imagePool.close();
}
}
Loading

0 comments on commit 742572e

Please sign in to comment.