| name | file-converter-development |
| description | Development guide for @rytass/file-converter base package (檔案轉換器開發指南). Use when creating new file converter adapters (新增檔案轉換 adapter), understanding converter interfaces, or building image processing pipelines. Covers Converter interface, ConverterManager, and Sharp integration patterns. Keywords: file converter, 檔案轉換, 圖片處理, image processing, Sharp, resize, watermark, transcode
|
File Converter Adapter Development Guide (檔案轉換 Adapter 開發指南)
Overview
本指南說明如何基於 @rytass/file-converter 基礎套件開發新的檔案轉換適配器。
Base Package Architecture
@rytass/file-converter (Base)
├── ConvertableFile # 輸入類型 (Readable | Buffer)
├── FileConverter<O> # 轉換器介面
└── ConverterManager # 管道式轉換管理器
Core Interfaces
FileConverter Interface
import { Readable } from 'stream';
type ConvertableFile = Readable | Buffer;
interface FileConverter<O = Record<string, unknown>> {
convert<Buffer>(file: ConvertableFile): Promise<Buffer>;
convert<Readable>(file: ConvertableFile): Promise<Readable>;
}
ConverterManager
class ConverterManager {
constructor(converters: FileConverter[]);
convert<ConvertableFileFormat extends ConvertableFile>(
file: ConvertableFile
): Promise<ConvertableFileFormat>;
}
注意: ConverterManager 不提供 pipe() 方法。所有轉換器必須在建構時透過陣列傳入。
Existing Adapters Reference
| Adapter | 功能 | 輸入 | 輸出 |
|---|
image-resizer | 圖片縮放 | Buffer / Readable | Buffer / Readable |
image-transcoder | 格式轉換 | Buffer / Readable | Buffer / Readable |
image-watermark | 浮水印疊加 | Buffer / Readable | Buffer / Readable |
Implementing a New Adapter
Step 1: Define Configuration
export interface MyConverterOptions {
someOption?: string;
concurrency?: number;
}
Step 2: Implement FileConverter
import { FileConverter, ConvertableFile } from '@rytass/file-converter';
import sharp from 'sharp';
import { Readable } from 'stream';
import { MyConverterOptions } from './typings';
sharp.cache(false);
export class MyConverter implements FileConverter<MyConverterOptions> {
private readonly options: MyConverterOptions;
constructor(options: MyConverterOptions) {
this.options = options;
sharp.concurrency(options.concurrency ?? 1);
}
async convert<Output extends ConvertableFile>(file: ConvertableFile): Promise<Output> {
let converter;
if (file instanceof Buffer) {
converter = sharp(file);
} else {
converter = sharp();
}
if (file instanceof Readable) {
file.pipe(converter);
}
if (file instanceof Buffer) {
return converter.toBuffer() as Promise<Output>;
}
return converter as Readable as Output;
}
}
Step 3: Export Package
export * from './typings';
export * from './my-converter';
Actual Adapter Implementations
ImageResizer (image-resizer)
實際選項介面:
export interface ImageResizerOptions {
maxWidth?: number;
maxHeight?: number;
keepAspectRatio?: boolean;
concurrency?: number;
}
使用範例:
import { ImageResizer } from '@rytass/file-converter-adapter-image-resizer';
const resizer = new ImageResizer({
maxWidth: 800,
maxHeight: 600,
keepAspectRatio: true,
concurrency: 1,
});
const result = await resizer.convert<Buffer>(inputBuffer);
const resultStream = await resizer.convert<Readable>(inputStream);
注意: 使用 withoutEnlargement: true,不會放大小於目標尺寸的圖片。
ImageTranscoder (image-transcoder)
實際選項介面(使用 Sharp 的格式特定選項):
import type { AvifOptions, GifOptions, HeifOptions, JpegOptions, PngOptions, TiffOptions, WebpOptions } from 'sharp';
type ImageTranscoderOptions =
| ({ targetFormat: 'avif' } & AvifOptions)
| ({ targetFormat: 'heif' } & HeifOptions)
| ({ targetFormat: 'gif' } & GifOptions)
| ({ targetFormat: 'tif' | 'tiff' } & TiffOptions)
| ({ targetFormat: 'png' } & PngOptions)
| ({ targetFormat: 'webp' } & WebpOptions)
| ({ targetFormat: 'jpg' | 'jpeg' } & JpegOptions);
type ImageTranscoderConstructorOptions = ImageTranscoderOptions & { concurrency?: number };
支援的來源格式:['jpg', 'png', 'webp', 'gif', 'avif', 'tif', 'svg']
使用範例:
import { ImageTranscoder } from '@rytass/file-converter-adapter-image-transcoder';
const transcoder = new ImageTranscoder({
targetFormat: 'webp',
quality: 80,
lossless: false,
concurrency: 1,
});
const jpegTranscoder = new ImageTranscoder({
targetFormat: 'jpeg',
quality: 85,
progressive: true,
});
const avifTranscoder = new ImageTranscoder({
targetFormat: 'avif',
quality: 50,
effort: 4,
});
const result = await transcoder.convert<Buffer>(inputBuffer);
注意: 不支援的來源格式會拋出 UnsupportedSource 錯誤。
ImageWatermark (image-watermark)
實際選項介面:
import type { Gravity } from 'sharp';
type FilePath = string;
interface Watermark {
image: FilePath | Buffer;
gravity?: Gravity;
}
export interface ImageWatermarkOptions {
watermarks: Watermark[];
concurrency?: number;
}
Sharp Gravity 值:
import { gravity } from 'sharp';
使用範例:
import { ImageWatermark } from '@rytass/file-converter-adapter-image-watermark';
import { gravity } from 'sharp';
const watermark = new ImageWatermark({
watermarks: [
{
image: watermarkBuffer,
gravity: gravity.southeast,
},
],
});
const multiWatermark = new ImageWatermark({
watermarks: [
{ image: logoBuffer, gravity: gravity.northwest },
{ image: copyrightBuffer, gravity: gravity.south },
],
concurrency: 2,
});
const result = await watermark.convert<Buffer>(inputBuffer);
Pipeline Usage
Using ConverterManager
ConverterManager 允許串接多個轉換器,按順序執行轉換。
import { ConverterManager } from '@rytass/file-converter';
import { ImageResizer } from '@rytass/file-converter-adapter-image-resizer';
import { ImageWatermark } from '@rytass/file-converter-adapter-image-watermark';
import { ImageTranscoder } from '@rytass/file-converter-adapter-image-transcoder';
import { gravity } from 'sharp';
const manager = new ConverterManager([
new ImageResizer({
maxWidth: 800,
maxHeight: 600,
keepAspectRatio: true,
}),
new ImageWatermark({
watermarks: [
{ image: watermarkBuffer, gravity: gravity.southeast },
],
}),
new ImageTranscoder({
targetFormat: 'webp',
quality: 85,
}),
]);
const result = await manager.convert<Buffer>(inputBuffer);
注意: 所有轉換器必須在建構 ConverterManager 時透過陣列傳入,不支援動態添加轉換器。
Error Handling
UnsupportedSource Error
ImageTranscoder 會在不支援的來源格式時拋出此錯誤:
import { ImageTranscoder } from '@rytass/file-converter-adapter-image-transcoder';
try {
const transcoder = new ImageTranscoder({ targetFormat: 'webp' });
await transcoder.convert(unsupportedFormatBuffer);
} catch (error) {
if (error instanceof Error && error.message === 'UnsupportedSource') {
console.error('不支援的圖片格式');
}
}
注意: UnsupportedSource 錯誤類別和 SupportSources 常數目前未從套件導出,僅供內部使用。
Testing Guidelines
import { MyConverter } from '../src';
import * as fs from 'fs';
import * as path from 'path';
describe('MyConverter', () => {
const testImage = fs.readFileSync(path.join(__dirname, 'fixtures/test.jpg'));
it('should convert image', async () => {
const converter = new MyConverter({ someOption: 'value' });
const result = await converter.convert<Buffer>(testImage);
expect(result).toBeInstanceOf(Buffer);
expect(result.length).toBeGreaterThan(0);
});
});
Package Structure
my-converter/
├── src/
│ ├── index.ts
│ ├── typings.ts
│ └── my-converter.ts
├── __tests__/
│ ├── fixtures/
│ │ └── test.jpg
│ └── my-converter.spec.ts
├── package.json
└── tsconfig.build.json
Publishing Checklist