@think-grid-labs/snapbolt 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +36 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +69 -0
- package/dist/index.test.d.ts +1 -0
- package/dist/index.test.js +56 -0
- package/package.json +57 -0
package/README.md
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# @think-grid-labs/snapbolt
|
|
2
|
+
|
|
3
|
+
A high-performance image optimization toolkit powered by Rust and WebAssembly.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
This toolkit provides professional-grade image optimization (resizing and JPEG/WebP encoding) that runs entirely on the client side.
|
|
7
|
+
|
|
8
|
+
## Quick Start
|
|
9
|
+
|
|
10
|
+
### 1. Install
|
|
11
|
+
```bash
|
|
12
|
+
npm install @think-grid-labs/snapbolt
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
### 2. Sync WASM Binary
|
|
16
|
+
You must ensure the `.wasm` file is available in your project's `public` folder:
|
|
17
|
+
```bash
|
|
18
|
+
npx @think-grid-labs/snapbolt-cli sync ./public
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
### 3. Use with React
|
|
22
|
+
```tsx
|
|
23
|
+
import { useImageOptimizer } from '@think-grid-labs/snapbolt';
|
|
24
|
+
|
|
25
|
+
const SmartImage = ({ src }) => {
|
|
26
|
+
const { optimizedUrl, loading } = useImageOptimizer(src, 75, 300);
|
|
27
|
+
return <img src={optimizedUrl || src} alt="Optimized" />;
|
|
28
|
+
};
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Features
|
|
32
|
+
- **Client-Side Optimization**: Zero server cost.
|
|
33
|
+
- **Blazing Fast**: Powered by a Rust core.
|
|
34
|
+
- **React Ready**: Easy-to-use hooks.
|
|
35
|
+
|
|
36
|
+
For full documentation, visit our [GitHub Repository](https://github.com/ThinkGrid-Labs/snapbolt).
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { useState, useEffect } from 'react';
|
|
2
|
+
// Dynamic import for the Wasm module
|
|
3
|
+
const loadWasm = async () => {
|
|
4
|
+
// We import from the local pkg directory (built via wasm-pack)
|
|
5
|
+
return import('../pkg/snapbolt.js');
|
|
6
|
+
};
|
|
7
|
+
export const useImageOptimizer = (src, quality = 80) => {
|
|
8
|
+
const [state, setState] = useState({
|
|
9
|
+
optimizedUrl: null,
|
|
10
|
+
loading: false,
|
|
11
|
+
error: null,
|
|
12
|
+
});
|
|
13
|
+
useEffect(() => {
|
|
14
|
+
let mounted = true;
|
|
15
|
+
let currentUrl = null;
|
|
16
|
+
const process = async () => {
|
|
17
|
+
if (!src)
|
|
18
|
+
return;
|
|
19
|
+
setState(prev => ({ ...prev, loading: true, error: null }));
|
|
20
|
+
try {
|
|
21
|
+
// 1. Get Blob
|
|
22
|
+
let blob;
|
|
23
|
+
if (typeof src === 'string') {
|
|
24
|
+
const resp = await fetch(src);
|
|
25
|
+
if (!resp.ok)
|
|
26
|
+
throw new Error(`Failed to fetch image: ${resp.statusText}`);
|
|
27
|
+
blob = await resp.blob();
|
|
28
|
+
}
|
|
29
|
+
else {
|
|
30
|
+
blob = src;
|
|
31
|
+
}
|
|
32
|
+
// 2. Load bytes
|
|
33
|
+
const buffer = await blob.arrayBuffer();
|
|
34
|
+
const bytes = new Uint8Array(buffer);
|
|
35
|
+
// 3. Wasm Optimize
|
|
36
|
+
const wasm = await loadWasm();
|
|
37
|
+
const optimizedBytes = wasm.optimize_image_sync(bytes, quality);
|
|
38
|
+
// 4. Create Object URL
|
|
39
|
+
const optimizedBlob = new Blob([optimizedBytes], { type: 'image/webp' });
|
|
40
|
+
const url = URL.createObjectURL(optimizedBlob);
|
|
41
|
+
currentUrl = url; // Store locally for cleanup
|
|
42
|
+
if (mounted) {
|
|
43
|
+
setState({
|
|
44
|
+
optimizedUrl: url,
|
|
45
|
+
loading: false,
|
|
46
|
+
error: null
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
catch (err) {
|
|
51
|
+
if (mounted) {
|
|
52
|
+
setState(prev => ({
|
|
53
|
+
...prev,
|
|
54
|
+
loading: false,
|
|
55
|
+
error: err.message || 'Unknown error'
|
|
56
|
+
}));
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
process();
|
|
61
|
+
return () => {
|
|
62
|
+
mounted = false;
|
|
63
|
+
if (currentUrl) {
|
|
64
|
+
URL.revokeObjectURL(currentUrl);
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
}, [src, quality]);
|
|
68
|
+
return state;
|
|
69
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/** @vitest-environment jsdom */
|
|
2
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
3
|
+
import { renderHook, waitFor } from '@testing-library/react';
|
|
4
|
+
import { useImageOptimizer } from './index';
|
|
5
|
+
// Mock Fetch
|
|
6
|
+
global.fetch = vi.fn();
|
|
7
|
+
// Mock URL.createObjectURL/revokeObjectURL
|
|
8
|
+
if (typeof window !== 'undefined') {
|
|
9
|
+
window.URL.createObjectURL = vi.fn(() => 'blob:mock-url');
|
|
10
|
+
window.URL.revokeObjectURL = vi.fn();
|
|
11
|
+
}
|
|
12
|
+
// Mock the Wasm Module import
|
|
13
|
+
vi.mock('../pkg/snapbolt.js', () => ({
|
|
14
|
+
init: vi.fn().mockResolvedValue({}),
|
|
15
|
+
optimize_image_sync: vi.fn().mockReturnValue(new Uint8Array([1, 2, 3])),
|
|
16
|
+
}));
|
|
17
|
+
describe('useImageOptimizer', () => {
|
|
18
|
+
beforeEach(() => {
|
|
19
|
+
vi.clearAllMocks();
|
|
20
|
+
global.fetch.mockReset();
|
|
21
|
+
});
|
|
22
|
+
it('should optimize image smoothly', async () => {
|
|
23
|
+
const mockBlob = new Blob(['test'], { type: 'image/png' });
|
|
24
|
+
mockBlob.arrayBuffer = vi.fn().mockResolvedValue(new ArrayBuffer(4));
|
|
25
|
+
global.fetch.mockResolvedValue({
|
|
26
|
+
ok: true,
|
|
27
|
+
blob: () => Promise.resolve(mockBlob),
|
|
28
|
+
});
|
|
29
|
+
const { result } = renderHook(() => useImageOptimizer('test.png', 80));
|
|
30
|
+
await waitFor(() => expect(result.current.loading).toBe(false), { timeout: 4000 });
|
|
31
|
+
expect(result.current.optimizedUrl).toBe('blob:mock-url');
|
|
32
|
+
expect(result.current.error).toBeNull();
|
|
33
|
+
});
|
|
34
|
+
it('should handle fetch errors', async () => {
|
|
35
|
+
global.fetch.mockResolvedValue({
|
|
36
|
+
ok: false,
|
|
37
|
+
statusText: 'Not Found',
|
|
38
|
+
});
|
|
39
|
+
const { result } = renderHook(() => useImageOptimizer('invalid.png', 80));
|
|
40
|
+
await waitFor(() => expect(result.current.loading).toBe(false), { timeout: 4000 });
|
|
41
|
+
expect(result.current.error).toContain('Failed to fetch image');
|
|
42
|
+
expect(result.current.optimizedUrl).toBeNull();
|
|
43
|
+
});
|
|
44
|
+
it('should cleanup on unmount', async () => {
|
|
45
|
+
const mockBlob = new Blob(['test'], { type: 'image/png' });
|
|
46
|
+
mockBlob.arrayBuffer = vi.fn().mockResolvedValue(new ArrayBuffer(4));
|
|
47
|
+
global.fetch.mockResolvedValue({
|
|
48
|
+
ok: true,
|
|
49
|
+
blob: () => Promise.resolve(mockBlob),
|
|
50
|
+
});
|
|
51
|
+
const { unmount } = renderHook(() => useImageOptimizer('cleanup.png', 80));
|
|
52
|
+
await waitFor(() => expect(window.URL.createObjectURL).toHaveBeenCalled(), { timeout: 3000 });
|
|
53
|
+
unmount();
|
|
54
|
+
expect(window.URL.revokeObjectURL).toHaveBeenCalled();
|
|
55
|
+
});
|
|
56
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@think-grid-labs/snapbolt",
|
|
3
|
+
"version": "0.1.3",
|
|
4
|
+
"description": "Unified image optimization toolkit for the browser and React",
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"module": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"files": [
|
|
9
|
+
"dist",
|
|
10
|
+
"pkg",
|
|
11
|
+
"README.md"
|
|
12
|
+
],
|
|
13
|
+
"exports": {
|
|
14
|
+
".": {
|
|
15
|
+
"types": "./dist/index.d.ts",
|
|
16
|
+
"import": "./dist/index.js",
|
|
17
|
+
"default": "./dist/index.js"
|
|
18
|
+
},
|
|
19
|
+
"./browser": {
|
|
20
|
+
"types": "./pkg/snapbolt.d.ts",
|
|
21
|
+
"import": "./pkg/snapbolt.js",
|
|
22
|
+
"default": "./pkg/snapbolt.js"
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
"scripts": {
|
|
26
|
+
"build": "npm run build:wasm && npm run build:ts",
|
|
27
|
+
"build:wasm": "npx wasm-pack build --target web --out-dir pkg --release",
|
|
28
|
+
"build:ts": "tsc",
|
|
29
|
+
"test": "vitest"
|
|
30
|
+
},
|
|
31
|
+
"keywords": [
|
|
32
|
+
"react",
|
|
33
|
+
"react-component",
|
|
34
|
+
"nextjs",
|
|
35
|
+
"nextjs-component",
|
|
36
|
+
"image-optimization",
|
|
37
|
+
"wasm",
|
|
38
|
+
"webp"
|
|
39
|
+
],
|
|
40
|
+
"author": "DennisP <dennis@thinkgrid-labs.com>",
|
|
41
|
+
"license": "MIT",
|
|
42
|
+
"peerDependencies": {
|
|
43
|
+
"react": ">=16.8.0"
|
|
44
|
+
},
|
|
45
|
+
"devDependencies": {
|
|
46
|
+
"typescript": "^5.0.0",
|
|
47
|
+
"@types/react": "^18.0.0",
|
|
48
|
+
"concurrently": "^8.0.0",
|
|
49
|
+
"vitest": "^0.34.0",
|
|
50
|
+
"@testing-library/react": "^14.0.0",
|
|
51
|
+
"jsdom": "^22.0.0",
|
|
52
|
+
"@types/node": "^20.0.0"
|
|
53
|
+
},
|
|
54
|
+
"publishConfig": {
|
|
55
|
+
"access": "public"
|
|
56
|
+
}
|
|
57
|
+
}
|