UNPKG

@codefast/image-loader

Version:

Flexible image loader for Next.js supporting multiple CDN providers

111 lines (110 loc) 3.93 kB
class ImageLoaderFactory { loaders = []; config; loaderCache = {}; transformCache = {}; maxCacheSize = 1000; constructor(config = {}){ this.config = { defaultQuality: 75, ...config }; } registerLoader(loader) { if (this.loaders.some((l)=>l.getName() === loader.getName())) throw new Error(`Loader with name "${loader.getName()}" is already registered`); this.loaders.push(loader); this.clearLoaderCache(); } registerLoaders(loaders) { for (const loader of loaders)this.registerLoader(loader); } unregisterLoader(name) { const index = this.loaders.findIndex((loader)=>loader.getName() === name); if (-1 !== index) { this.loaders.splice(index, 1); this.clearLoaderCache(); return true; } return false; } getLoaders() { return [ ...this.loaders ]; } findLoader(source) { const domain = this.extractDomain(source); if (domain in this.loaderCache) return this.loaderCache[domain]; let selectedLoader = null; if (this.config.domainMappings?.[domain]) { const mappedLoaderName = this.config.domainMappings[domain]; selectedLoader = this.loaders.find((loader)=>loader.getName() === mappedLoaderName) ?? null; } selectedLoader ??= this.loaders.find((loader)=>loader.canHandle(source)) ?? null; this.cacheLoader(domain, selectedLoader); return selectedLoader; } load(config) { const cacheKey = this.createTransformCacheKey(config); if (this.transformCache[cacheKey]) return this.transformCache[cacheKey]; const loader = this.findLoader(config.src); if (!loader) { console.warn(`No loader found for URL: ${config.src}. Returning original URL.`); return config.src; } const normalizedConfig = { ...config, quality: config.quality ?? this.config.defaultQuality }; const transformedUrl = loader.load(normalizedConfig); this.cacheTransform(cacheKey, transformedUrl); return transformedUrl; } createNextImageLoader() { return (params)=>this.load({ quality: params.quality, src: params.src, width: params.width }); } getStats() { return { domainMappings: this.config.domainMappings ?? {}, loaderNames: this.loaders.map((loader)=>loader.getName()), totalLoaders: this.loaders.length }; } clear() { this.loaders.length = 0; this.clearLoaderCache(); this.clearTransformCache(); } extractDomain(url) { try { const urlObject = new URL(url); return urlObject.hostname.toLowerCase(); } catch { return ""; } } cacheLoader(domain, loader) { if (Object.keys(this.loaderCache).length >= this.maxCacheSize) this.clearLoaderCache(); this.loaderCache[domain] = loader; } clearLoaderCache() { for (const key of Object.keys(this.loaderCache))delete this.loaderCache[key]; } createTransformCacheKey(config) { const quality = config.quality ?? this.config.defaultQuality ?? 75; return `${config.src}|${config.width.toString()}|${quality.toString()}`; } cacheTransform(key, transformedUrl) { if (Object.keys(this.transformCache).length >= this.maxCacheSize) this.clearTransformCache(); this.transformCache[key] = transformedUrl; } clearTransformCache() { for (const key of Object.keys(this.transformCache))delete this.transformCache[key]; } } const defaultImageLoaderFactory = new ImageLoaderFactory(); export { ImageLoaderFactory, defaultImageLoaderFactory };