@fe-daily/libimagequant-wasm
Version:
WASM bindings for libimagequant image quantization library
270 lines (210 loc) • 7.26 kB
Markdown
# Next.js Integration Guide
This guide explains how to integrate `-daily/libimagequant-wasm` with Next.js applications.
## Problem
Next.js uses static analysis and bundling that doesn't work well with dynamic WASM imports using `import.meta.url`. You may encounter errors like:
```
Error: Cannot find module '/_next/static/media/libimagequant_wasm.a36650a2.js'
```
## Solutions
### Option 1: Pre-load WASM Module (Recommended)
Import the WASM module directly and pass it to the constructor:
```typescript
import LibImageQuant from '@fe-daily/libimagequant-wasm';
import wasmModule from '@fe-daily/libimagequant-wasm/wasm/libimagequant_wasm.js';
// Create quantizer with pre-loaded WASM module
const quantizer = new LibImageQuant({ wasmModule });
// Use normally
const result = await quantizer.quantizeImageData(imageData, {
maxColors: 64,
speed: 3
});
```
### Option 2: Use in Web Worker
Create a Web Worker to isolate the WASM loading:
```typescript
// worker.ts
import LibImageQuant from '@fe-daily/libimagequant-wasm';
let quantizer: LibImageQuant;
self.onmessage = async function(e) {
const { type, data } = e.data;
try {
if (type === 'init') {
quantizer = new LibImageQuant();
self.postMessage({ type: 'ready' });
return;
}
if (type === 'quantize') {
const result = await quantizer.quantizeImageData(data.imageData, data.options);
self.postMessage({ type: 'result', result });
}
} catch (error) {
self.postMessage({
type: 'error',
error: error instanceof Error ? error.message : String(error)
});
}
};
// main component
import { useCallback, useEffect, useRef } from 'react';
export function useImageQuantizer() {
const workerRef = useRef<Worker>();
useEffect(() => {
workerRef.current = new Worker(new URL('./worker.ts', import.meta.url));
return () => workerRef.current?.terminate();
}, []);
const quantize = useCallback((imageData: ImageData, options: any) => {
return new Promise((resolve, reject) => {
if (!workerRef.current) {
reject(new Error('Worker not initialized'));
return;
}
const handleMessage = (e: MessageEvent) => {
const { type, result, error } = e.data;
if (type === 'result') {
resolve(result);
} else if (type === 'error') {
reject(new Error(error));
}
workerRef.current?.removeEventListener('message', handleMessage);
};
workerRef.current.addEventListener('message', handleMessage);
workerRef.current.postMessage({ type: 'quantize', data: { imageData, options } });
});
}, []);
return { quantize };
}
```
### Option 3: Dynamic Import with Error Handling
Use dynamic imports with proper error handling:
```typescript
import { useCallback, useState } from 'react';
export function useImageQuantizer() {
const [quantizer, setQuantizer] = useState<any>(null);
const [loading, setLoading] = useState(false);
const initialize = useCallback(async () => {
if (quantizer) return quantizer;
setLoading(true);
try {
const { default: LibImageQuant } = await import('-daily/libimagequant-wasm');
const wasmModule = await import('-daily/libimagequant-wasm/wasm/libimagequant_wasm.js');
const instance = new LibImageQuant({ wasmModule });
setQuantizer(instance);
return instance;
} catch (error) {
console.error('Failed to initialize quantizer:', error);
throw error;
} finally {
setLoading(false);
}
}, [quantizer]);
const quantize = useCallback(async (imageData: ImageData, options: any) => {
const instance = await initialize();
return instance.quantizeImageData(imageData, options);
}, [initialize]);
return { quantize, loading };
}
```
## Next.js Configuration
You may need to configure Next.js to handle WASM files properly:
```javascript
// next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
webpack: (config, { isServer }) => {
// Handle WASM files
config.experiments = {
...config.experiments,
asyncWebAssembly: true,
};
// Handle .wasm files
config.module.rules.push({
test: /\.wasm$/,
type: 'webassembly/async',
});
// Fallback for Node.js modules in client-side
if (!isServer) {
config.resolve.fallback = {
...config.resolve.fallback,
fs: false,
path: false,
crypto: false,
};
}
return config;
},
};
module.exports = nextConfig;
```
## TypeScript Configuration
Update your `tsconfig.json` to handle WASM imports:
```json
{
"compilerOptions": {
"allowSyntheticDefaultImports": true,
"esModuleInterop": true,
"moduleResolution": "bundler"
}
}
```
## Example Component
```typescript
'use client';
import { useCallback, useState } from 'react';
import LibImageQuant from '@fe-daily/libimagequant-wasm';
import wasmModule from '@fe-daily/libimagequant-wasm/wasm/libimagequant_wasm.js';
export default function ImageQuantizer() {
const [result, setResult] = useState<any>(null);
const [loading, setLoading] = useState(false);
const handleFileUpload = useCallback(async (event: React.ChangeEvent<HTMLInputElement>) => {
const file = event.target.files?.[0];
if (!file) return;
setLoading(true);
try {
// Create canvas to get ImageData
const img = new Image();
img.onload = async () => {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d')!;
canvas.width = img.width;
canvas.height = img.height;
ctx.drawImage(img, 0, 0);
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
// Quantize with pre-loaded WASM module
const quantizer = new LibImageQuant({ wasmModule });
const quantResult = await quantizer.quantizeImageData(imageData, {
maxColors: 32,
speed: 3
});
setResult(quantResult);
};
img.src = URL.createObjectURL(file);
} catch (error) {
console.error('Quantization failed:', error);
} finally {
setLoading(false);
}
}, []);
return (
<div>
<input type="file" accept="image/*" onChange={handleFileUpload} />
{loading && <p>Processing...</p>}
{result && (
<div>
<p>Quantized to {result.paletteLength} colors</p>
<p>Quality: {Math.round(result.quality * 100)}%</p>
</div>
)}
</div>
);
}
```
## Troubleshooting
### Common Issues
1. **Module not found errors**: Ensure you're importing the WASM module correctly
2. **Build failures**: Check your Next.js configuration supports WASM
3. **Runtime errors**: Make sure you're running in a browser environment (use 'use client' directive)
### Tips
- Always use the pre-loaded WASM module approach for the most reliable Next.js integration
- Test your integration in both development and production builds
- Consider using Web Workers for heavy processing to avoid blocking the UI
- Make sure WASM files are properly served with correct MIME types in production