wasm-image-optimization 1.2.2 → 1.2.4
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 +35 -289
- package/dist/cjs/node/node-worker.js +2 -2
- package/dist/esm/next-web-worker.js +3973 -0
- package/dist/esm/{web-worker.js → vite-web-worker.js} +3 -3
- package/dist/next/index.js +1 -7
- package/dist/next/index.js.map +1 -1
- package/dist/next-web-worker/_web-worker.d.ts +13 -0
- package/dist/next-web-worker/_web-worker.js +8 -0
- package/dist/next-web-worker/_web-worker.js.map +1 -0
- package/dist/{web-worker → next-web-worker}/index.js +1 -1
- package/dist/next-web-worker/index.js.map +1 -0
- package/dist/vite-web-worker/_web-worker.d.ts +13 -0
- package/dist/vite-web-worker/_web-worker.js +8 -0
- package/dist/vite-web-worker/_web-worker.js.map +1 -0
- package/dist/{esm/esm-worker.d.ts → vite-web-worker/index.d.ts} +2 -2
- package/dist/vite-web-worker/index.js +6 -0
- package/dist/vite-web-worker/index.js.map +1 -0
- package/package.json +8 -7
- package/dist/esm/esm-worker.js +0 -9
- package/dist/esm/esm-worker.js.map +0 -1
- package/dist/web-worker/index.js.map +0 -1
- /package/dist/{web-worker → next-web-worker}/index.d.ts +0 -0
package/README.md
CHANGED
|
@@ -17,6 +17,31 @@
|
|
|
17
17
|
- Web Worker (Browser) Multi process
|
|
18
18
|
`import { optimizeImage } from 'wasm-image-optimization/web-worker';`
|
|
19
19
|
|
|
20
|
+
## Samples
|
|
21
|
+
|
|
22
|
+
https://github.com/SoraKumo001/wasm-image-optimization-samples
|
|
23
|
+
|
|
24
|
+
### cloudflare-ogp
|
|
25
|
+
|
|
26
|
+
Sample for generating OGP images on Cloudflare Workers.
|
|
27
|
+
|
|
28
|
+
### deno-ogp
|
|
29
|
+
|
|
30
|
+
Sample for generating OGP images on Deno.
|
|
31
|
+
|
|
32
|
+
### cloudflare-image-optimization
|
|
33
|
+
|
|
34
|
+
Sample for image optimization on Cloudflare Workers.
|
|
35
|
+
|
|
36
|
+
### deno-image-optimization
|
|
37
|
+
|
|
38
|
+
Sample for image optimization on Deno.
|
|
39
|
+
|
|
40
|
+
### node-image-convert
|
|
41
|
+
|
|
42
|
+
Sample for converting image formats on Node.js.
|
|
43
|
+
Single-threaded and multi-threaded operation can be selected.
|
|
44
|
+
|
|
20
45
|
## WebWorker on Vite
|
|
21
46
|
|
|
22
47
|
- vite.config.ts
|
|
@@ -29,6 +54,16 @@ export default defineConfig(() => ({
|
|
|
29
54
|
}));
|
|
30
55
|
```
|
|
31
56
|
|
|
57
|
+
## Next.js on Backend API
|
|
58
|
+
|
|
59
|
+
```ts
|
|
60
|
+
import { optimizeImage } from "wasm-image-optimization";
|
|
61
|
+
|
|
62
|
+
…
|
|
63
|
+
|
|
64
|
+
export const runtime = "edge";
|
|
65
|
+
```
|
|
66
|
+
|
|
32
67
|
## function
|
|
33
68
|
|
|
34
69
|
```ts
|
|
@@ -68,292 +103,3 @@ optimizeImageExt({
|
|
|
68
103
|
- png
|
|
69
104
|
- webp
|
|
70
105
|
- avif(default)
|
|
71
|
-
|
|
72
|
-
## usage
|
|
73
|
-
|
|
74
|
-
### Node.js (CLI)
|
|
75
|
-
|
|
76
|
-
```ts
|
|
77
|
-
import fs from "node:fs";
|
|
78
|
-
import {
|
|
79
|
-
optimizeImage,
|
|
80
|
-
waitAll,
|
|
81
|
-
close,
|
|
82
|
-
} from "wasm-image-optimization/node-worker";
|
|
83
|
-
|
|
84
|
-
const formats = ["webp", "jpeg", "png", "avif"] as const;
|
|
85
|
-
|
|
86
|
-
const main = async () => {
|
|
87
|
-
fs.mkdirSync("./image_output", { recursive: true });
|
|
88
|
-
const files = fs.readdirSync("./images");
|
|
89
|
-
for (const file of files) {
|
|
90
|
-
const image = fs.readFileSync(`./images/${file}`);
|
|
91
|
-
for (const format of formats) {
|
|
92
|
-
optimizeImage({
|
|
93
|
-
image,
|
|
94
|
-
quality: 100,
|
|
95
|
-
format,
|
|
96
|
-
width: 1000,
|
|
97
|
-
}).then((encoded) => {
|
|
98
|
-
console.log(
|
|
99
|
-
!!encoded,
|
|
100
|
-
file,
|
|
101
|
-
format,
|
|
102
|
-
encoded && `${Math.floor(encoded.length / 1024)}KB`
|
|
103
|
-
);
|
|
104
|
-
if (encoded) {
|
|
105
|
-
const fileName = file.split(".")[0];
|
|
106
|
-
fs.promises.writeFile(`image_output/${fileName}.${format}`, encoded);
|
|
107
|
-
}
|
|
108
|
-
});
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
await waitAll(); // Wait for all workers to finish before exiting the program.
|
|
112
|
-
close(); // Close all workers
|
|
113
|
-
console.log("exit");
|
|
114
|
-
};
|
|
115
|
-
main();
|
|
116
|
-
```
|
|
117
|
-
|
|
118
|
-
### Next.js image optimization with Cloudflare
|
|
119
|
-
|
|
120
|
-
```ts
|
|
121
|
-
import { optimizeImage } from "wasm-image-optimization";
|
|
122
|
-
|
|
123
|
-
const isValidUrl = (url: string) => {
|
|
124
|
-
try {
|
|
125
|
-
new URL(url);
|
|
126
|
-
return true;
|
|
127
|
-
} catch (err) {
|
|
128
|
-
return false;
|
|
129
|
-
}
|
|
130
|
-
};
|
|
131
|
-
|
|
132
|
-
const handleRequest = async (
|
|
133
|
-
request: Request,
|
|
134
|
-
_env: {},
|
|
135
|
-
ctx: ExecutionContext
|
|
136
|
-
): Promise<Response> => {
|
|
137
|
-
const accept = request.headers.get("accept");
|
|
138
|
-
const isWebp =
|
|
139
|
-
accept
|
|
140
|
-
?.split(",")
|
|
141
|
-
.map((format) => format.trim())
|
|
142
|
-
.some((format) => ["image/webp", "*/*", "image/*"].includes(format)) ??
|
|
143
|
-
true;
|
|
144
|
-
const isAvif =
|
|
145
|
-
accept
|
|
146
|
-
?.split(",")
|
|
147
|
-
.map((format) => format.trim())
|
|
148
|
-
.some((format) => ["image/avif", "*/*", "image/*"].includes(format)) ??
|
|
149
|
-
true;
|
|
150
|
-
|
|
151
|
-
const url = new URL(request.url);
|
|
152
|
-
|
|
153
|
-
const params = url.searchParams;
|
|
154
|
-
const imageUrl = params.get("url");
|
|
155
|
-
if (!imageUrl || !isValidUrl(imageUrl)) {
|
|
156
|
-
return new Response("url is required", { status: 400 });
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
const cache = caches.default;
|
|
160
|
-
url.searchParams.append("webp", isWebp.toString());
|
|
161
|
-
const cacheKey = new Request(url.toString());
|
|
162
|
-
const cachedResponse = await cache.match(cacheKey);
|
|
163
|
-
if (cachedResponse) {
|
|
164
|
-
return cachedResponse;
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
const width = params.get("w");
|
|
168
|
-
const quality = params.get("q");
|
|
169
|
-
|
|
170
|
-
const [srcImage, contentType] = await fetch(imageUrl, {
|
|
171
|
-
cf: { cacheKey: imageUrl },
|
|
172
|
-
})
|
|
173
|
-
.then(async (res) =>
|
|
174
|
-
res.ok
|
|
175
|
-
? ([await res.arrayBuffer(), res.headers.get("content-type")] as const)
|
|
176
|
-
: []
|
|
177
|
-
)
|
|
178
|
-
.catch(() => []);
|
|
179
|
-
|
|
180
|
-
if (!srcImage) {
|
|
181
|
-
return new Response("image not found", { status: 404 });
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
if (contentType && ["image/svg+xml", "image/gif"].includes(contentType)) {
|
|
185
|
-
const response = new Response(srcImage, {
|
|
186
|
-
headers: {
|
|
187
|
-
"Content-Type": contentType,
|
|
188
|
-
"Cache-Control": "public, max-age=31536000, immutable",
|
|
189
|
-
},
|
|
190
|
-
});
|
|
191
|
-
ctx.waitUntil(cache.put(cacheKey, response.clone()));
|
|
192
|
-
return response;
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
const format = isAvif
|
|
196
|
-
? "avif"
|
|
197
|
-
: isWebp
|
|
198
|
-
? "webp"
|
|
199
|
-
: contentType === "image/jpeg"
|
|
200
|
-
? "jpeg"
|
|
201
|
-
: "png";
|
|
202
|
-
const image = await optimizeImage({
|
|
203
|
-
image: srcImage,
|
|
204
|
-
width: width ? parseInt(width) : undefined,
|
|
205
|
-
quality: quality ? parseInt(quality) : undefined,
|
|
206
|
-
format,
|
|
207
|
-
});
|
|
208
|
-
const response = new Response(image, {
|
|
209
|
-
headers: {
|
|
210
|
-
"Content-Type": `image/${format}`,
|
|
211
|
-
"Cache-Control": "public, max-age=31536000, immutable",
|
|
212
|
-
date: new Date().toUTCString(),
|
|
213
|
-
},
|
|
214
|
-
});
|
|
215
|
-
ctx.waitUntil(cache.put(cacheKey, response.clone()));
|
|
216
|
-
return response;
|
|
217
|
-
};
|
|
218
|
-
|
|
219
|
-
export default {
|
|
220
|
-
fetch: handleRequest,
|
|
221
|
-
};
|
|
222
|
-
```
|
|
223
|
-
|
|
224
|
-
- next.config.js
|
|
225
|
-
|
|
226
|
-
```js
|
|
227
|
-
/**
|
|
228
|
-
* @type { import("next").NextConfig}
|
|
229
|
-
*/
|
|
230
|
-
const config = {
|
|
231
|
-
images: {
|
|
232
|
-
path: "https://xxx.yyy.workers.dev/",
|
|
233
|
-
},
|
|
234
|
-
};
|
|
235
|
-
export default config;
|
|
236
|
-
```
|
|
237
|
-
|
|
238
|
-
### Deno Deploy
|
|
239
|
-
|
|
240
|
-
#### Parameters
|
|
241
|
-
|
|
242
|
-
| Name | Type | Description |
|
|
243
|
-
| ---- | ------ | ----------- |
|
|
244
|
-
| url | string | Image URL |
|
|
245
|
-
| q | number | Quality |
|
|
246
|
-
| w | number | Width |
|
|
247
|
-
|
|
248
|
-
https://xxxx.deno.dev/?url=https://xxx.png&q=80&w=200
|
|
249
|
-
|
|
250
|
-
```ts
|
|
251
|
-
import { optimizeImage } from "npm:wasm-image-optimization/esm";
|
|
252
|
-
|
|
253
|
-
const isValidUrl = (url: string) => {
|
|
254
|
-
try {
|
|
255
|
-
new URL(url);
|
|
256
|
-
return true;
|
|
257
|
-
} catch (_e) {
|
|
258
|
-
return false;
|
|
259
|
-
}
|
|
260
|
-
};
|
|
261
|
-
|
|
262
|
-
const isType = (accept: string | null, type: string) => {
|
|
263
|
-
return (
|
|
264
|
-
accept
|
|
265
|
-
?.split(",")
|
|
266
|
-
.map((format) => format.trim())
|
|
267
|
-
.some((format) => [`image/${type}`, "*/*", "image/*"].includes(format)) ??
|
|
268
|
-
true
|
|
269
|
-
);
|
|
270
|
-
};
|
|
271
|
-
|
|
272
|
-
Deno.serve(async (request) => {
|
|
273
|
-
const url = new URL(request.url);
|
|
274
|
-
const params = url.searchParams;
|
|
275
|
-
const type = ["avif", "webp", "png", "jpeg"].find(
|
|
276
|
-
(v) => v === params.get("type")
|
|
277
|
-
) as "avif" | "webp" | "png" | "jpeg" | undefined;
|
|
278
|
-
const accept = request.headers.get("accept");
|
|
279
|
-
const isAvif = isType(accept, "avif");
|
|
280
|
-
const isWebp = isType(accept, "webp");
|
|
281
|
-
|
|
282
|
-
const cache = await caches.open(
|
|
283
|
-
`image-${isAvif ? "-avif" : ""}${isWebp ? "-webp" : ""}`
|
|
284
|
-
);
|
|
285
|
-
|
|
286
|
-
const cached = await cache.match(request);
|
|
287
|
-
if (cached) {
|
|
288
|
-
return cached;
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
const imageUrl = params.get("url");
|
|
292
|
-
if (!imageUrl || !isValidUrl(imageUrl)) {
|
|
293
|
-
return new Response("url is required", { status: 400 });
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
if (isAvif) {
|
|
297
|
-
url.searchParams.append("avif", isAvif.toString());
|
|
298
|
-
} else if (isWebp) {
|
|
299
|
-
url.searchParams.append("webp", isWebp.toString());
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
const cacheKey = new Request(url.toString());
|
|
303
|
-
const cachedResponse = await cache.match(cacheKey);
|
|
304
|
-
if (cachedResponse) {
|
|
305
|
-
return cachedResponse;
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
const width = params.get("w");
|
|
309
|
-
const quality = params.get("q");
|
|
310
|
-
|
|
311
|
-
const [srcImage, contentType] = await fetch(imageUrl)
|
|
312
|
-
.then(async (res) =>
|
|
313
|
-
res.ok
|
|
314
|
-
? ([await res.arrayBuffer(), res.headers.get("content-type")] as const)
|
|
315
|
-
: []
|
|
316
|
-
)
|
|
317
|
-
.catch(() => []);
|
|
318
|
-
|
|
319
|
-
if (!srcImage) {
|
|
320
|
-
return new Response("image not found", { status: 404 });
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
if (contentType && ["image/svg+xml", "image/gif"].includes(contentType)) {
|
|
324
|
-
const response = new Response(srcImage, {
|
|
325
|
-
headers: {
|
|
326
|
-
"Content-Type": contentType,
|
|
327
|
-
"Cache-Control": "public, max-age=31536000, immutable",
|
|
328
|
-
},
|
|
329
|
-
});
|
|
330
|
-
cache.put(request, response.clone());
|
|
331
|
-
return response;
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
const format =
|
|
335
|
-
type ??
|
|
336
|
-
(isAvif
|
|
337
|
-
? "avif"
|
|
338
|
-
: isWebp
|
|
339
|
-
? "webp"
|
|
340
|
-
: contentType === "image/jpeg"
|
|
341
|
-
? "jpeg"
|
|
342
|
-
: "png");
|
|
343
|
-
const image = await optimizeImage({
|
|
344
|
-
image: srcImage,
|
|
345
|
-
width: width ? parseInt(width) : undefined,
|
|
346
|
-
quality: quality ? parseInt(quality) : undefined,
|
|
347
|
-
format,
|
|
348
|
-
});
|
|
349
|
-
const response = new Response(image, {
|
|
350
|
-
headers: {
|
|
351
|
-
"Content-Type": `image/${format}`,
|
|
352
|
-
"Cache-Control": "public, max-age=31536000, immutable",
|
|
353
|
-
date: new Date().toUTCString(),
|
|
354
|
-
},
|
|
355
|
-
});
|
|
356
|
-
cache.put(request, response.clone());
|
|
357
|
-
return response;
|
|
358
|
-
});
|
|
359
|
-
```
|
|
@@ -4,9 +4,9 @@ var __commonJS = (cb, mod) => function __require() {
|
|
|
4
4
|
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
|
5
5
|
};
|
|
6
6
|
|
|
7
|
-
// node_modules/.pnpm/worker-lib@2.0.
|
|
7
|
+
// node_modules/.pnpm/worker-lib@2.0.2/node_modules/worker-lib/dist/cjs/node.js
|
|
8
8
|
var require_node = __commonJS({
|
|
9
|
-
"node_modules/.pnpm/worker-lib@2.0.
|
|
9
|
+
"node_modules/.pnpm/worker-lib@2.0.2/node_modules/worker-lib/dist/cjs/node.js"(exports2) {
|
|
10
10
|
"use strict";
|
|
11
11
|
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
12
12
|
exports2.initWorker = exports2.createWorker = void 0;
|