ipx 2.0.0-1 → 2.0.1
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 +32 -17
- package/dist/chunks/svgo-xss.cjs +116 -0
- package/dist/chunks/svgo-xss.mjs +114 -0
- package/dist/cli.cjs +3 -3
- package/dist/cli.mjs +3 -3
- package/dist/index.cjs +2 -2
- package/dist/index.d.cts +10 -12
- package/dist/index.d.mts +10 -12
- package/dist/index.d.ts +10 -12
- package/dist/index.mjs +2 -2
- package/dist/shared/{ipx.57fad794.mjs → ipx.4d79c6c9.mjs} +66 -31
- package/dist/shared/{ipx.680a50a5.cjs → ipx.ebaf2d0c.cjs} +65 -30
- package/package.json +15 -13
package/README.md
CHANGED
|
@@ -3,12 +3,12 @@
|
|
|
3
3
|
[![npm version][npm-version-src]][npm-version-href]
|
|
4
4
|
[![npm downloads][npm-downloads-src]][npm-downloads-href]
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
> [!NOTE]
|
|
7
|
+
> This is the active branch for IPX v2. Check out [ipx/v1](https://github.com/unjs/ipx/tree/v1) for v1 docs.
|
|
7
8
|
|
|
8
|
-
|
|
9
|
+
High performance, secure and easy-to-use image optimizer powered by [sharp](https://github.com/lovell/sharp) and [svgo](https://github.com/svg/svgo).
|
|
9
10
|
|
|
10
|
-
|
|
11
|
-
> This is the development branch for IPX v2. Check out [ipx/v1](https://github.com/unjs/ipx/tree/v1) for latest stable docs and [#71](https://github.com/unjs/ipx/issues/171) for v2 roadmap.
|
|
11
|
+
Used by [Nuxt Image](https://image.nuxt.com/) and [Netlify](https://www.npmjs.com/package/@netlify/ipx) and open to everyone!
|
|
12
12
|
|
|
13
13
|
## Using CLI
|
|
14
14
|
|
|
@@ -17,13 +17,13 @@ You can use `ipx` command to start server.
|
|
|
17
17
|
Using `npx`:
|
|
18
18
|
|
|
19
19
|
```bash
|
|
20
|
-
npx ipx
|
|
20
|
+
npx ipx serve --dir ./
|
|
21
21
|
```
|
|
22
22
|
|
|
23
23
|
Usin `bun`
|
|
24
24
|
|
|
25
25
|
```bash
|
|
26
|
-
bun x npx ipx
|
|
26
|
+
bun x npx ipx serve --dir ./
|
|
27
27
|
```
|
|
28
28
|
|
|
29
29
|
The default serve directory is the current working directory.
|
|
@@ -33,28 +33,32 @@ The default serve directory is the current working directory.
|
|
|
33
33
|
You can use IPX as a middleware or directly use IPX interface.
|
|
34
34
|
|
|
35
35
|
```ts
|
|
36
|
-
import {
|
|
37
|
-
createIPX,
|
|
38
|
-
createIPXMiddleware,
|
|
39
|
-
ipxFSStorage,
|
|
40
|
-
ipxHttpStorage,
|
|
41
|
-
} from "ipx";
|
|
36
|
+
import { createIPX, ipxFSStorage, ipxHttpStorage } from "ipx";
|
|
42
37
|
|
|
43
38
|
const ipx = createIPX({
|
|
44
39
|
storage: ipxFSStorage({ dir: "./public" }),
|
|
45
40
|
httpStorage: ipxHttpStorage({ domains: ["picsum.photos"] }),
|
|
46
41
|
});
|
|
47
|
-
|
|
48
|
-
const ipxMiddleware = createIPXMiddleware(ipx);
|
|
49
42
|
```
|
|
50
43
|
|
|
51
44
|
**Example**: Using with [unjs/h3](https://github.com/unjs/h3):
|
|
52
45
|
|
|
53
46
|
```js
|
|
54
|
-
import { createIPX, createIPXMiddleware } from "ipx";
|
|
55
47
|
import { listen } from "listhen";
|
|
48
|
+
import { createApp, toNodeListener } from "h3";
|
|
49
|
+
import {
|
|
50
|
+
createIPX,
|
|
51
|
+
ipxFSStorage,
|
|
52
|
+
ipxHttpStorage,
|
|
53
|
+
createIPXH3Handler,
|
|
54
|
+
} from "ipx";
|
|
55
|
+
|
|
56
|
+
const ipx = createIPX({
|
|
57
|
+
storage: ipxFSStorage({ dir: "./public" }),
|
|
58
|
+
httpStorage: ipxHttpStorage({ domains: ["picsum.photos"] }),
|
|
59
|
+
});
|
|
56
60
|
|
|
57
|
-
const app = createApp().use("/",
|
|
61
|
+
const app = createApp().use("/", createIPXH3Handler(ipx));
|
|
58
62
|
|
|
59
63
|
listen(toNodeListener(app));
|
|
60
64
|
```
|
|
@@ -64,8 +68,19 @@ listen(toNodeListener(app));
|
|
|
64
68
|
```js
|
|
65
69
|
import { listen } from "listhen";
|
|
66
70
|
import express from "express";
|
|
71
|
+
import {
|
|
72
|
+
createIPX,
|
|
73
|
+
ipxFSStorage,
|
|
74
|
+
ipxHttpStorage,
|
|
75
|
+
createIPXNodeServer,
|
|
76
|
+
} from "ipx";
|
|
77
|
+
|
|
78
|
+
const ipx = createIPX({
|
|
79
|
+
storage: ipxFSStorage({ dir: "./public" }),
|
|
80
|
+
httpStorage: ipxHttpStorage({ domains: ["picsum.photos"] }),
|
|
81
|
+
});
|
|
67
82
|
|
|
68
|
-
const app = express().use("/",
|
|
83
|
+
const app = express().use("/", createIPXNodeServer(ipx));
|
|
69
84
|
|
|
70
85
|
listen(app);
|
|
71
86
|
```
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const xss = {
|
|
4
|
+
name: "removeXSS",
|
|
5
|
+
fn() {
|
|
6
|
+
return {
|
|
7
|
+
element: {
|
|
8
|
+
enter: (node, parentNode) => {
|
|
9
|
+
if (node.name === "script") {
|
|
10
|
+
parentNode.children = parentNode.children.filter(
|
|
11
|
+
(child) => child !== node
|
|
12
|
+
);
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
for (const event of ALL_EVENTS) {
|
|
16
|
+
if (node.attributes[event] != null) {
|
|
17
|
+
delete node.attributes[event];
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
exit: (node, parentNode) => {
|
|
22
|
+
if (node.name !== "a") {
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
for (const attr of Object.keys(node.attributes)) {
|
|
26
|
+
if (attr === "href" || attr.endsWith(":href")) {
|
|
27
|
+
if (node.attributes[attr] == null || !node.attributes[attr].trimStart().startsWith("javascript:")) {
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
const index = parentNode.children.indexOf(node);
|
|
31
|
+
parentNode.children.splice(index, 1, ...node.children);
|
|
32
|
+
for (const child of node.children) {
|
|
33
|
+
Object.defineProperty(child, "parentNode", {
|
|
34
|
+
writable: true,
|
|
35
|
+
value: parentNode
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
const ALL_EVENTS = [
|
|
46
|
+
"onabort",
|
|
47
|
+
"onactivate",
|
|
48
|
+
"onbegin",
|
|
49
|
+
"oncancel",
|
|
50
|
+
"oncanplay",
|
|
51
|
+
"oncanplaythrough",
|
|
52
|
+
"onchange",
|
|
53
|
+
"onclick",
|
|
54
|
+
"onclose",
|
|
55
|
+
"oncopy",
|
|
56
|
+
"oncuechange",
|
|
57
|
+
"oncut",
|
|
58
|
+
"ondblclick",
|
|
59
|
+
"ondrag",
|
|
60
|
+
"ondragend",
|
|
61
|
+
"ondragenter",
|
|
62
|
+
"ondragleave",
|
|
63
|
+
"ondragover",
|
|
64
|
+
"ondragstart",
|
|
65
|
+
"ondrop",
|
|
66
|
+
"ondurationchange",
|
|
67
|
+
"onemptied",
|
|
68
|
+
"onend",
|
|
69
|
+
"onended",
|
|
70
|
+
"onerror",
|
|
71
|
+
"onfocus",
|
|
72
|
+
"onfocusin",
|
|
73
|
+
"onfocusout",
|
|
74
|
+
"oninput",
|
|
75
|
+
"oninvalid",
|
|
76
|
+
"onkeydown",
|
|
77
|
+
"onkeypress",
|
|
78
|
+
"onkeyup",
|
|
79
|
+
"onload",
|
|
80
|
+
"onloadeddata",
|
|
81
|
+
"onloadedmetadata",
|
|
82
|
+
"onloadstart",
|
|
83
|
+
"onmousedown",
|
|
84
|
+
"onmouseenter",
|
|
85
|
+
"onmouseleave",
|
|
86
|
+
"onmousemove",
|
|
87
|
+
"onmouseout",
|
|
88
|
+
"onmouseover",
|
|
89
|
+
"onmouseup",
|
|
90
|
+
"onmousewheel",
|
|
91
|
+
"onpaste",
|
|
92
|
+
"onpause",
|
|
93
|
+
"onplay",
|
|
94
|
+
"onplaying",
|
|
95
|
+
"onprogress",
|
|
96
|
+
"onratechange",
|
|
97
|
+
"onrepeat",
|
|
98
|
+
"onreset",
|
|
99
|
+
"onresize",
|
|
100
|
+
"onscroll",
|
|
101
|
+
"onseeked",
|
|
102
|
+
"onseeking",
|
|
103
|
+
"onselect",
|
|
104
|
+
"onshow",
|
|
105
|
+
"onstalled",
|
|
106
|
+
"onsubmit",
|
|
107
|
+
"onsuspend",
|
|
108
|
+
"ontimeupdate",
|
|
109
|
+
"ontoggle",
|
|
110
|
+
"onunload",
|
|
111
|
+
"onvolumechange",
|
|
112
|
+
"onwaiting",
|
|
113
|
+
"onzoom"
|
|
114
|
+
];
|
|
115
|
+
|
|
116
|
+
exports.xss = xss;
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
const xss = {
|
|
2
|
+
name: "removeXSS",
|
|
3
|
+
fn() {
|
|
4
|
+
return {
|
|
5
|
+
element: {
|
|
6
|
+
enter: (node, parentNode) => {
|
|
7
|
+
if (node.name === "script") {
|
|
8
|
+
parentNode.children = parentNode.children.filter(
|
|
9
|
+
(child) => child !== node
|
|
10
|
+
);
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
for (const event of ALL_EVENTS) {
|
|
14
|
+
if (node.attributes[event] != null) {
|
|
15
|
+
delete node.attributes[event];
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
exit: (node, parentNode) => {
|
|
20
|
+
if (node.name !== "a") {
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
for (const attr of Object.keys(node.attributes)) {
|
|
24
|
+
if (attr === "href" || attr.endsWith(":href")) {
|
|
25
|
+
if (node.attributes[attr] == null || !node.attributes[attr].trimStart().startsWith("javascript:")) {
|
|
26
|
+
continue;
|
|
27
|
+
}
|
|
28
|
+
const index = parentNode.children.indexOf(node);
|
|
29
|
+
parentNode.children.splice(index, 1, ...node.children);
|
|
30
|
+
for (const child of node.children) {
|
|
31
|
+
Object.defineProperty(child, "parentNode", {
|
|
32
|
+
writable: true,
|
|
33
|
+
value: parentNode
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
const ALL_EVENTS = [
|
|
44
|
+
"onabort",
|
|
45
|
+
"onactivate",
|
|
46
|
+
"onbegin",
|
|
47
|
+
"oncancel",
|
|
48
|
+
"oncanplay",
|
|
49
|
+
"oncanplaythrough",
|
|
50
|
+
"onchange",
|
|
51
|
+
"onclick",
|
|
52
|
+
"onclose",
|
|
53
|
+
"oncopy",
|
|
54
|
+
"oncuechange",
|
|
55
|
+
"oncut",
|
|
56
|
+
"ondblclick",
|
|
57
|
+
"ondrag",
|
|
58
|
+
"ondragend",
|
|
59
|
+
"ondragenter",
|
|
60
|
+
"ondragleave",
|
|
61
|
+
"ondragover",
|
|
62
|
+
"ondragstart",
|
|
63
|
+
"ondrop",
|
|
64
|
+
"ondurationchange",
|
|
65
|
+
"onemptied",
|
|
66
|
+
"onend",
|
|
67
|
+
"onended",
|
|
68
|
+
"onerror",
|
|
69
|
+
"onfocus",
|
|
70
|
+
"onfocusin",
|
|
71
|
+
"onfocusout",
|
|
72
|
+
"oninput",
|
|
73
|
+
"oninvalid",
|
|
74
|
+
"onkeydown",
|
|
75
|
+
"onkeypress",
|
|
76
|
+
"onkeyup",
|
|
77
|
+
"onload",
|
|
78
|
+
"onloadeddata",
|
|
79
|
+
"onloadedmetadata",
|
|
80
|
+
"onloadstart",
|
|
81
|
+
"onmousedown",
|
|
82
|
+
"onmouseenter",
|
|
83
|
+
"onmouseleave",
|
|
84
|
+
"onmousemove",
|
|
85
|
+
"onmouseout",
|
|
86
|
+
"onmouseover",
|
|
87
|
+
"onmouseup",
|
|
88
|
+
"onmousewheel",
|
|
89
|
+
"onpaste",
|
|
90
|
+
"onpause",
|
|
91
|
+
"onplay",
|
|
92
|
+
"onplaying",
|
|
93
|
+
"onprogress",
|
|
94
|
+
"onratechange",
|
|
95
|
+
"onrepeat",
|
|
96
|
+
"onreset",
|
|
97
|
+
"onresize",
|
|
98
|
+
"onscroll",
|
|
99
|
+
"onseeked",
|
|
100
|
+
"onseeking",
|
|
101
|
+
"onselect",
|
|
102
|
+
"onshow",
|
|
103
|
+
"onstalled",
|
|
104
|
+
"onsubmit",
|
|
105
|
+
"onsuspend",
|
|
106
|
+
"ontimeupdate",
|
|
107
|
+
"ontoggle",
|
|
108
|
+
"onunload",
|
|
109
|
+
"onvolumechange",
|
|
110
|
+
"onwaiting",
|
|
111
|
+
"onzoom"
|
|
112
|
+
];
|
|
113
|
+
|
|
114
|
+
export { xss };
|
package/dist/cli.cjs
CHANGED
|
@@ -3,11 +3,11 @@
|
|
|
3
3
|
const listhen = require('listhen');
|
|
4
4
|
const citty = require('citty');
|
|
5
5
|
const cli = require('listhen/cli');
|
|
6
|
-
const nodeFs = require('./shared/ipx.
|
|
6
|
+
const nodeFs = require('./shared/ipx.ebaf2d0c.cjs');
|
|
7
7
|
require('defu');
|
|
8
|
-
require('image-meta');
|
|
9
8
|
require('ufo');
|
|
10
9
|
require('h3');
|
|
10
|
+
require('image-meta');
|
|
11
11
|
require('destr');
|
|
12
12
|
require('@fastify/accept-negotiator');
|
|
13
13
|
require('etag');
|
|
@@ -15,7 +15,7 @@ require('ofetch');
|
|
|
15
15
|
require('pathe');
|
|
16
16
|
|
|
17
17
|
const name = "ipx";
|
|
18
|
-
const version = "2.0.
|
|
18
|
+
const version = "2.0.1";
|
|
19
19
|
const description = "High performance, secure and easy-to-use image optimizer.";
|
|
20
20
|
|
|
21
21
|
const serve = citty.defineCommand({
|
package/dist/cli.mjs
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { listen } from 'listhen';
|
|
2
2
|
import { defineCommand, runMain } from 'citty';
|
|
3
3
|
import { getArgs, parseArgs } from 'listhen/cli';
|
|
4
|
-
import { c as createIPX, g as ipxFSStorage, i as ipxHttpStorage, e as createIPXNodeServer } from './shared/ipx.
|
|
4
|
+
import { c as createIPX, g as ipxFSStorage, i as ipxHttpStorage, e as createIPXNodeServer } from './shared/ipx.4d79c6c9.mjs';
|
|
5
5
|
import 'defu';
|
|
6
|
-
import 'image-meta';
|
|
7
6
|
import 'ufo';
|
|
8
7
|
import 'h3';
|
|
8
|
+
import 'image-meta';
|
|
9
9
|
import 'destr';
|
|
10
10
|
import '@fastify/accept-negotiator';
|
|
11
11
|
import 'etag';
|
|
@@ -13,7 +13,7 @@ import 'ofetch';
|
|
|
13
13
|
import 'pathe';
|
|
14
14
|
|
|
15
15
|
const name = "ipx";
|
|
16
|
-
const version = "2.0.
|
|
16
|
+
const version = "2.0.1";
|
|
17
17
|
const description = "High performance, secure and easy-to-use image optimizer.";
|
|
18
18
|
|
|
19
19
|
const serve = defineCommand({
|
package/dist/index.cjs
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const nodeFs = require('./shared/ipx.
|
|
3
|
+
const nodeFs = require('./shared/ipx.ebaf2d0c.cjs');
|
|
4
4
|
require('defu');
|
|
5
|
-
require('image-meta');
|
|
6
5
|
require('ufo');
|
|
7
6
|
require('h3');
|
|
7
|
+
require('image-meta');
|
|
8
8
|
require('destr');
|
|
9
9
|
require('@fastify/accept-negotiator');
|
|
10
10
|
require('etag');
|
package/dist/index.d.cts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { Color, KernelEnum, Sharp, SharpOptions } from 'sharp';
|
|
2
|
+
import { ImageMeta } from 'image-meta';
|
|
3
|
+
import { Config } from 'svgo';
|
|
2
4
|
import * as h3 from 'h3';
|
|
3
5
|
import { Storage, Driver } from 'unstorage';
|
|
4
6
|
|
|
@@ -27,12 +29,6 @@ interface IPXStorage {
|
|
|
27
29
|
getMeta: (id: string, opts?: IPXStorageOptions) => MaybePromise<IPXStorageMeta | undefined>;
|
|
28
30
|
getData: (id: string, opts?: IPXStorageOptions) => MaybePromise<ArrayBuffer | undefined>;
|
|
29
31
|
}
|
|
30
|
-
interface ImageMeta {
|
|
31
|
-
width: number;
|
|
32
|
-
height: number;
|
|
33
|
-
type: string;
|
|
34
|
-
mimeType: string;
|
|
35
|
-
}
|
|
36
32
|
|
|
37
33
|
declare const quality: Handler;
|
|
38
34
|
declare const fit: Handler;
|
|
@@ -114,9 +110,9 @@ type IPXSourceMeta = {
|
|
|
114
110
|
type IPX = (id: string, modifiers?: Partial<Record<HandlerName | "f" | "format" | "a" | "animated", string>>, requestOptions?: any) => {
|
|
115
111
|
getSourceMeta: () => Promise<IPXSourceMeta>;
|
|
116
112
|
process: () => Promise<{
|
|
117
|
-
data: Buffer;
|
|
118
|
-
meta
|
|
119
|
-
format
|
|
113
|
+
data: Buffer | string;
|
|
114
|
+
meta?: ImageMeta;
|
|
115
|
+
format?: string;
|
|
120
116
|
}>;
|
|
121
117
|
};
|
|
122
118
|
type IPXOptions = {
|
|
@@ -125,14 +121,15 @@ type IPXOptions = {
|
|
|
125
121
|
sharpOptions?: SharpOptions;
|
|
126
122
|
storage: IPXStorage;
|
|
127
123
|
httpStorage?: IPXStorage;
|
|
124
|
+
svgo?: false | Config;
|
|
128
125
|
};
|
|
129
126
|
declare function createIPX(userOptions: IPXOptions): IPX;
|
|
130
127
|
|
|
131
|
-
declare function createIPXH3Handler(ipx: IPX): h3.EventHandler<h3.EventHandlerRequest, Promise<Buffer | {
|
|
128
|
+
declare function createIPXH3Handler(ipx: IPX): h3.EventHandler<h3.EventHandlerRequest, Promise<string | void | Buffer | {
|
|
132
129
|
error: {
|
|
133
130
|
message: string;
|
|
134
131
|
};
|
|
135
|
-
}
|
|
132
|
+
}>>;
|
|
136
133
|
declare function createIPXH3App(ipx: IPX): h3.App;
|
|
137
134
|
declare function createIPXWebServer(ipx: IPX): h3.WebHandler;
|
|
138
135
|
declare function createIPXNodeServer(ipx: IPX): h3.NodeListener;
|
|
@@ -143,6 +140,7 @@ type HTTPStorageOptions = {
|
|
|
143
140
|
maxAge?: number;
|
|
144
141
|
domains?: string | string[];
|
|
145
142
|
allowAllDomains?: boolean;
|
|
143
|
+
ignoreCacheControl?: boolean;
|
|
146
144
|
};
|
|
147
145
|
declare function ipxHttpStorage(_options?: HTTPStorageOptions): IPXStorage;
|
|
148
146
|
|
|
@@ -154,4 +152,4 @@ declare function ipxFSStorage(_options?: NodeFSSOptions): IPXStorage;
|
|
|
154
152
|
|
|
155
153
|
declare function unstorageToIPXStorage(storage: Storage | Driver, prefix: string): IPXStorage;
|
|
156
154
|
|
|
157
|
-
export { type HTTPStorageOptions, type Handler, type HandlerContext, type IPX, type IPXOptions, type IPXStorage, type IPXStorageMeta, type IPXStorageOptions, type
|
|
155
|
+
export { type HTTPStorageOptions, type Handler, type HandlerContext, type IPX, type IPXOptions, type IPXStorage, type IPXStorageMeta, type IPXStorageOptions, type NodeFSSOptions, createIPX, createIPXH3App, createIPXH3Handler, createIPXNodeServer, createIPXPlainServer, createIPXWebServer, ipxFSStorage, ipxHttpStorage, unstorageToIPXStorage };
|
package/dist/index.d.mts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { Color, KernelEnum, Sharp, SharpOptions } from 'sharp';
|
|
2
|
+
import { ImageMeta } from 'image-meta';
|
|
3
|
+
import { Config } from 'svgo';
|
|
2
4
|
import * as h3 from 'h3';
|
|
3
5
|
import { Storage, Driver } from 'unstorage';
|
|
4
6
|
|
|
@@ -27,12 +29,6 @@ interface IPXStorage {
|
|
|
27
29
|
getMeta: (id: string, opts?: IPXStorageOptions) => MaybePromise<IPXStorageMeta | undefined>;
|
|
28
30
|
getData: (id: string, opts?: IPXStorageOptions) => MaybePromise<ArrayBuffer | undefined>;
|
|
29
31
|
}
|
|
30
|
-
interface ImageMeta {
|
|
31
|
-
width: number;
|
|
32
|
-
height: number;
|
|
33
|
-
type: string;
|
|
34
|
-
mimeType: string;
|
|
35
|
-
}
|
|
36
32
|
|
|
37
33
|
declare const quality: Handler;
|
|
38
34
|
declare const fit: Handler;
|
|
@@ -114,9 +110,9 @@ type IPXSourceMeta = {
|
|
|
114
110
|
type IPX = (id: string, modifiers?: Partial<Record<HandlerName | "f" | "format" | "a" | "animated", string>>, requestOptions?: any) => {
|
|
115
111
|
getSourceMeta: () => Promise<IPXSourceMeta>;
|
|
116
112
|
process: () => Promise<{
|
|
117
|
-
data: Buffer;
|
|
118
|
-
meta
|
|
119
|
-
format
|
|
113
|
+
data: Buffer | string;
|
|
114
|
+
meta?: ImageMeta;
|
|
115
|
+
format?: string;
|
|
120
116
|
}>;
|
|
121
117
|
};
|
|
122
118
|
type IPXOptions = {
|
|
@@ -125,14 +121,15 @@ type IPXOptions = {
|
|
|
125
121
|
sharpOptions?: SharpOptions;
|
|
126
122
|
storage: IPXStorage;
|
|
127
123
|
httpStorage?: IPXStorage;
|
|
124
|
+
svgo?: false | Config;
|
|
128
125
|
};
|
|
129
126
|
declare function createIPX(userOptions: IPXOptions): IPX;
|
|
130
127
|
|
|
131
|
-
declare function createIPXH3Handler(ipx: IPX): h3.EventHandler<h3.EventHandlerRequest, Promise<Buffer | {
|
|
128
|
+
declare function createIPXH3Handler(ipx: IPX): h3.EventHandler<h3.EventHandlerRequest, Promise<string | void | Buffer | {
|
|
132
129
|
error: {
|
|
133
130
|
message: string;
|
|
134
131
|
};
|
|
135
|
-
}
|
|
132
|
+
}>>;
|
|
136
133
|
declare function createIPXH3App(ipx: IPX): h3.App;
|
|
137
134
|
declare function createIPXWebServer(ipx: IPX): h3.WebHandler;
|
|
138
135
|
declare function createIPXNodeServer(ipx: IPX): h3.NodeListener;
|
|
@@ -143,6 +140,7 @@ type HTTPStorageOptions = {
|
|
|
143
140
|
maxAge?: number;
|
|
144
141
|
domains?: string | string[];
|
|
145
142
|
allowAllDomains?: boolean;
|
|
143
|
+
ignoreCacheControl?: boolean;
|
|
146
144
|
};
|
|
147
145
|
declare function ipxHttpStorage(_options?: HTTPStorageOptions): IPXStorage;
|
|
148
146
|
|
|
@@ -154,4 +152,4 @@ declare function ipxFSStorage(_options?: NodeFSSOptions): IPXStorage;
|
|
|
154
152
|
|
|
155
153
|
declare function unstorageToIPXStorage(storage: Storage | Driver, prefix: string): IPXStorage;
|
|
156
154
|
|
|
157
|
-
export { type HTTPStorageOptions, type Handler, type HandlerContext, type IPX, type IPXOptions, type IPXStorage, type IPXStorageMeta, type IPXStorageOptions, type
|
|
155
|
+
export { type HTTPStorageOptions, type Handler, type HandlerContext, type IPX, type IPXOptions, type IPXStorage, type IPXStorageMeta, type IPXStorageOptions, type NodeFSSOptions, createIPX, createIPXH3App, createIPXH3Handler, createIPXNodeServer, createIPXPlainServer, createIPXWebServer, ipxFSStorage, ipxHttpStorage, unstorageToIPXStorage };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { Color, KernelEnum, Sharp, SharpOptions } from 'sharp';
|
|
2
|
+
import { ImageMeta } from 'image-meta';
|
|
3
|
+
import { Config } from 'svgo';
|
|
2
4
|
import * as h3 from 'h3';
|
|
3
5
|
import { Storage, Driver } from 'unstorage';
|
|
4
6
|
|
|
@@ -27,12 +29,6 @@ interface IPXStorage {
|
|
|
27
29
|
getMeta: (id: string, opts?: IPXStorageOptions) => MaybePromise<IPXStorageMeta | undefined>;
|
|
28
30
|
getData: (id: string, opts?: IPXStorageOptions) => MaybePromise<ArrayBuffer | undefined>;
|
|
29
31
|
}
|
|
30
|
-
interface ImageMeta {
|
|
31
|
-
width: number;
|
|
32
|
-
height: number;
|
|
33
|
-
type: string;
|
|
34
|
-
mimeType: string;
|
|
35
|
-
}
|
|
36
32
|
|
|
37
33
|
declare const quality: Handler;
|
|
38
34
|
declare const fit: Handler;
|
|
@@ -114,9 +110,9 @@ type IPXSourceMeta = {
|
|
|
114
110
|
type IPX = (id: string, modifiers?: Partial<Record<HandlerName | "f" | "format" | "a" | "animated", string>>, requestOptions?: any) => {
|
|
115
111
|
getSourceMeta: () => Promise<IPXSourceMeta>;
|
|
116
112
|
process: () => Promise<{
|
|
117
|
-
data: Buffer;
|
|
118
|
-
meta
|
|
119
|
-
format
|
|
113
|
+
data: Buffer | string;
|
|
114
|
+
meta?: ImageMeta;
|
|
115
|
+
format?: string;
|
|
120
116
|
}>;
|
|
121
117
|
};
|
|
122
118
|
type IPXOptions = {
|
|
@@ -125,14 +121,15 @@ type IPXOptions = {
|
|
|
125
121
|
sharpOptions?: SharpOptions;
|
|
126
122
|
storage: IPXStorage;
|
|
127
123
|
httpStorage?: IPXStorage;
|
|
124
|
+
svgo?: false | Config;
|
|
128
125
|
};
|
|
129
126
|
declare function createIPX(userOptions: IPXOptions): IPX;
|
|
130
127
|
|
|
131
|
-
declare function createIPXH3Handler(ipx: IPX): h3.EventHandler<h3.EventHandlerRequest, Promise<Buffer | {
|
|
128
|
+
declare function createIPXH3Handler(ipx: IPX): h3.EventHandler<h3.EventHandlerRequest, Promise<string | void | Buffer | {
|
|
132
129
|
error: {
|
|
133
130
|
message: string;
|
|
134
131
|
};
|
|
135
|
-
}
|
|
132
|
+
}>>;
|
|
136
133
|
declare function createIPXH3App(ipx: IPX): h3.App;
|
|
137
134
|
declare function createIPXWebServer(ipx: IPX): h3.WebHandler;
|
|
138
135
|
declare function createIPXNodeServer(ipx: IPX): h3.NodeListener;
|
|
@@ -143,6 +140,7 @@ type HTTPStorageOptions = {
|
|
|
143
140
|
maxAge?: number;
|
|
144
141
|
domains?: string | string[];
|
|
145
142
|
allowAllDomains?: boolean;
|
|
143
|
+
ignoreCacheControl?: boolean;
|
|
146
144
|
};
|
|
147
145
|
declare function ipxHttpStorage(_options?: HTTPStorageOptions): IPXStorage;
|
|
148
146
|
|
|
@@ -154,4 +152,4 @@ declare function ipxFSStorage(_options?: NodeFSSOptions): IPXStorage;
|
|
|
154
152
|
|
|
155
153
|
declare function unstorageToIPXStorage(storage: Storage | Driver, prefix: string): IPXStorage;
|
|
156
154
|
|
|
157
|
-
export { type HTTPStorageOptions, type Handler, type HandlerContext, type IPX, type IPXOptions, type IPXStorage, type IPXStorageMeta, type IPXStorageOptions, type
|
|
155
|
+
export { type HTTPStorageOptions, type Handler, type HandlerContext, type IPX, type IPXOptions, type IPXStorage, type IPXStorageMeta, type IPXStorageOptions, type NodeFSSOptions, createIPX, createIPXH3App, createIPXH3Handler, createIPXNodeServer, createIPXPlainServer, createIPXWebServer, ipxFSStorage, ipxHttpStorage, unstorageToIPXStorage };
|
package/dist/index.mjs
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
export { c as createIPX, b as createIPXH3App, a as createIPXH3Handler, e as createIPXNodeServer, f as createIPXPlainServer, d as createIPXWebServer, g as ipxFSStorage, i as ipxHttpStorage } from './shared/ipx.
|
|
1
|
+
export { c as createIPX, b as createIPXH3App, a as createIPXH3Handler, e as createIPXNodeServer, f as createIPXPlainServer, d as createIPXWebServer, g as ipxFSStorage, i as ipxHttpStorage } from './shared/ipx.4d79c6c9.mjs';
|
|
2
2
|
import 'defu';
|
|
3
|
-
import 'image-meta';
|
|
4
3
|
import 'ufo';
|
|
5
4
|
import 'h3';
|
|
5
|
+
import 'image-meta';
|
|
6
6
|
import 'destr';
|
|
7
7
|
import '@fastify/accept-negotiator';
|
|
8
8
|
import 'etag';
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { defu } from 'defu';
|
|
2
|
-
import { imageMeta } from 'image-meta';
|
|
3
2
|
import { withLeadingSlash, hasProtocol, joinURL, decode } from 'ufo';
|
|
4
|
-
import { createError, defineEventHandler, setResponseStatus, createApp, toWebHandler, toNodeListener, toPlainHandler, getRequestHeader, setResponseHeader } from 'h3';
|
|
3
|
+
import { createError, defineEventHandler, setResponseStatus, createApp, toWebHandler, toNodeListener, toPlainHandler, getRequestHeader, appendResponseHeader, send, getResponseHeader, setResponseHeader } from 'h3';
|
|
4
|
+
import { imageMeta } from 'image-meta';
|
|
5
5
|
import destr from 'destr';
|
|
6
6
|
import { negotiate } from '@fastify/accept-negotiator';
|
|
7
7
|
import getEtag from 'etag';
|
|
@@ -62,11 +62,11 @@ function applyHandler(context, pipe, handler, argumentsString) {
|
|
|
62
62
|
function clampDimensionsPreservingAspectRatio(sourceDimensions, desiredDimensions) {
|
|
63
63
|
const desiredAspectRatio = desiredDimensions.width / desiredDimensions.height;
|
|
64
64
|
let { width, height } = desiredDimensions;
|
|
65
|
-
if (width > sourceDimensions.width) {
|
|
65
|
+
if (sourceDimensions.width && width > sourceDimensions.width) {
|
|
66
66
|
width = sourceDimensions.width;
|
|
67
67
|
height = Math.round(sourceDimensions.width / desiredAspectRatio);
|
|
68
68
|
}
|
|
69
|
-
if (height > sourceDimensions.height) {
|
|
69
|
+
if (sourceDimensions.height && height > sourceDimensions.height) {
|
|
70
70
|
height = sourceDimensions.height;
|
|
71
71
|
width = Math.round(sourceDimensions.height * desiredAspectRatio);
|
|
72
72
|
}
|
|
@@ -317,7 +317,7 @@ const SUPPORTED_FORMATS = /* @__PURE__ */ new Set([
|
|
|
317
317
|
function createIPX(userOptions) {
|
|
318
318
|
const options = defu(userOptions, {
|
|
319
319
|
alias: getEnv("IPX_ALIAS") || {},
|
|
320
|
-
maxAge: getEnv("IPX_MAX_AGE")
|
|
320
|
+
maxAge: getEnv("IPX_MAX_AGE") ?? 60,
|
|
321
321
|
sharpOptions: {}
|
|
322
322
|
});
|
|
323
323
|
options.alias = Object.fromEntries(
|
|
@@ -331,6 +331,11 @@ function createIPX(userOptions) {
|
|
|
331
331
|
(r) => r.default || r
|
|
332
332
|
);
|
|
333
333
|
});
|
|
334
|
+
const getSVGO = cachedPromise(async () => {
|
|
335
|
+
const { optimize } = await import('svgo');
|
|
336
|
+
const { xss } = await import('../chunks/svgo-xss.mjs');
|
|
337
|
+
return { optimize, xss };
|
|
338
|
+
});
|
|
334
339
|
return function ipx(id, modifiers = {}, opts = {}) {
|
|
335
340
|
if (!id) {
|
|
336
341
|
throw createError({
|
|
@@ -362,8 +367,9 @@ function createIPX(userOptions) {
|
|
|
362
367
|
message: `Resource not found: ${id}`
|
|
363
368
|
});
|
|
364
369
|
}
|
|
370
|
+
const _maxAge = sourceMeta.maxAge ?? options.maxAge;
|
|
365
371
|
return {
|
|
366
|
-
maxAge: typeof
|
|
372
|
+
maxAge: typeof _maxAge === "string" ? Number.parseInt(_maxAge) : _maxAge,
|
|
367
373
|
mtime: sourceMeta.mtime ? new Date(sourceMeta.mtime) : void 0
|
|
368
374
|
};
|
|
369
375
|
});
|
|
@@ -394,13 +400,26 @@ function createIPX(userOptions) {
|
|
|
394
400
|
if (mFormat === "jpg") {
|
|
395
401
|
mFormat = "jpeg";
|
|
396
402
|
}
|
|
397
|
-
const format = mFormat && SUPPORTED_FORMATS.has(mFormat) ? mFormat : SUPPORTED_FORMATS.has(imageMeta$1.type) ? imageMeta$1.type : "jpeg";
|
|
403
|
+
const format = mFormat && SUPPORTED_FORMATS.has(mFormat) ? mFormat : SUPPORTED_FORMATS.has(imageMeta$1.type || "") ? imageMeta$1.type : "jpeg";
|
|
398
404
|
if (imageMeta$1.type === "svg" && !mFormat) {
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
405
|
+
if (options.svgo === false) {
|
|
406
|
+
return {
|
|
407
|
+
data: sourceData,
|
|
408
|
+
format: "svg+xml",
|
|
409
|
+
meta: imageMeta$1
|
|
410
|
+
};
|
|
411
|
+
} else {
|
|
412
|
+
const { optimize, xss } = await getSVGO();
|
|
413
|
+
const svg = optimize(sourceData.toString("utf8"), {
|
|
414
|
+
...options.svgo,
|
|
415
|
+
plugins: [xss, ...options.svgo?.plugins || []]
|
|
416
|
+
}).data;
|
|
417
|
+
return {
|
|
418
|
+
data: svg,
|
|
419
|
+
format: "svg+xml",
|
|
420
|
+
meta: imageMeta$1
|
|
421
|
+
};
|
|
422
|
+
}
|
|
404
423
|
}
|
|
405
424
|
const animated = modifiers.animated !== void 0 || modifiers.a !== void 0 || format === "gif";
|
|
406
425
|
const Sharp = await getSharp();
|
|
@@ -422,7 +441,7 @@ function createIPX(userOptions) {
|
|
|
422
441
|
for (const h of handlers) {
|
|
423
442
|
sharp = applyHandler(handlerContext, sharp, h.handler, h.args) || sharp;
|
|
424
443
|
}
|
|
425
|
-
if (SUPPORTED_FORMATS.has(format)) {
|
|
444
|
+
if (SUPPORTED_FORMATS.has(format || "")) {
|
|
426
445
|
sharp = sharp.toFormat(format, {
|
|
427
446
|
quality: handlerContext.quality,
|
|
428
447
|
progressive: format === "jpeg"
|
|
@@ -483,36 +502,45 @@ function createIPXH3Handler(ipx) {
|
|
|
483
502
|
delete modifiers.format;
|
|
484
503
|
if (autoFormat) {
|
|
485
504
|
modifiers.format = autoFormat;
|
|
486
|
-
|
|
505
|
+
appendResponseHeader(event, "vary", "Accept");
|
|
487
506
|
}
|
|
488
507
|
}
|
|
489
508
|
const img = ipx(id, modifiers);
|
|
490
509
|
const sourceMeta = await img.getSourceMeta();
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
setResponseHeader(event, "last-modified", sourceMeta.mtime.toUTCString());
|
|
497
|
-
}
|
|
510
|
+
sendResponseHeaderIfNotSet(
|
|
511
|
+
event,
|
|
512
|
+
"content-security-policy",
|
|
513
|
+
"default-src 'none'"
|
|
514
|
+
);
|
|
498
515
|
if (typeof sourceMeta.maxAge === "number") {
|
|
499
|
-
|
|
516
|
+
sendResponseHeaderIfNotSet(
|
|
500
517
|
event,
|
|
501
518
|
"cache-control",
|
|
502
519
|
`max-age=${+sourceMeta.maxAge}, public, s-maxage=${+sourceMeta.maxAge}`
|
|
503
520
|
);
|
|
504
521
|
}
|
|
522
|
+
if (sourceMeta.mtime) {
|
|
523
|
+
sendResponseHeaderIfNotSet(
|
|
524
|
+
event,
|
|
525
|
+
"last-modified",
|
|
526
|
+
sourceMeta.mtime.toUTCString()
|
|
527
|
+
);
|
|
528
|
+
const _ifModifiedSince = getRequestHeader(event, "if-modified-since");
|
|
529
|
+
if (_ifModifiedSince && new Date(_ifModifiedSince) >= sourceMeta.mtime) {
|
|
530
|
+
setResponseStatus(event, 304);
|
|
531
|
+
return send(event);
|
|
532
|
+
}
|
|
533
|
+
}
|
|
505
534
|
const { data, format } = await img.process();
|
|
506
535
|
const etag = getEtag(data);
|
|
507
|
-
|
|
536
|
+
sendResponseHeaderIfNotSet(event, "etag", etag);
|
|
508
537
|
if (etag && getRequestHeader(event, "if-none-match") === etag) {
|
|
509
538
|
setResponseStatus(event, 304);
|
|
510
|
-
return
|
|
539
|
+
return send(event);
|
|
511
540
|
}
|
|
512
541
|
if (format) {
|
|
513
|
-
|
|
542
|
+
sendResponseHeaderIfNotSet(event, "content-type", `image/${format}`);
|
|
514
543
|
}
|
|
515
|
-
setResponseHeader(event, "content-security-policy", "default-src 'none'");
|
|
516
544
|
return data;
|
|
517
545
|
};
|
|
518
546
|
return defineEventHandler(async (event) => {
|
|
@@ -543,6 +571,11 @@ function createIPXNodeServer(ipx) {
|
|
|
543
571
|
function createIPXPlainServer(ipx) {
|
|
544
572
|
return toPlainHandler(createIPXH3App(ipx));
|
|
545
573
|
}
|
|
574
|
+
function sendResponseHeaderIfNotSet(event, name, value) {
|
|
575
|
+
if (!getResponseHeader(event, name)) {
|
|
576
|
+
setResponseHeader(event, name, value);
|
|
577
|
+
}
|
|
578
|
+
}
|
|
546
579
|
function autoDetectFormat(acceptHeader, animated) {
|
|
547
580
|
if (animated) {
|
|
548
581
|
const acceptMime2 = negotiate(acceptHeader, ["image/webp", "image/gif"]);
|
|
@@ -600,11 +633,13 @@ function ipxHttpStorage(_options = {}) {
|
|
|
600
633
|
}
|
|
601
634
|
function parseResponse(response) {
|
|
602
635
|
let maxAge = defaultMaxAge;
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
636
|
+
if (_options.ignoreCacheControl) {
|
|
637
|
+
const _cacheControl = response.headers.get("cache-control");
|
|
638
|
+
if (_cacheControl) {
|
|
639
|
+
const m = _cacheControl.match(/max-age=(\d+)/);
|
|
640
|
+
if (m && m[1]) {
|
|
641
|
+
maxAge = Number.parseInt(m[1]);
|
|
642
|
+
}
|
|
608
643
|
}
|
|
609
644
|
}
|
|
610
645
|
let mtime;
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const defu = require('defu');
|
|
4
|
-
const imageMeta = require('image-meta');
|
|
5
4
|
const ufo = require('ufo');
|
|
6
5
|
const h3 = require('h3');
|
|
6
|
+
const imageMeta = require('image-meta');
|
|
7
7
|
const destr = require('destr');
|
|
8
8
|
const acceptNegotiator = require('@fastify/accept-negotiator');
|
|
9
9
|
const getEtag = require('etag');
|
|
@@ -69,11 +69,11 @@ function applyHandler(context, pipe, handler, argumentsString) {
|
|
|
69
69
|
function clampDimensionsPreservingAspectRatio(sourceDimensions, desiredDimensions) {
|
|
70
70
|
const desiredAspectRatio = desiredDimensions.width / desiredDimensions.height;
|
|
71
71
|
let { width, height } = desiredDimensions;
|
|
72
|
-
if (width > sourceDimensions.width) {
|
|
72
|
+
if (sourceDimensions.width && width > sourceDimensions.width) {
|
|
73
73
|
width = sourceDimensions.width;
|
|
74
74
|
height = Math.round(sourceDimensions.width / desiredAspectRatio);
|
|
75
75
|
}
|
|
76
|
-
if (height > sourceDimensions.height) {
|
|
76
|
+
if (sourceDimensions.height && height > sourceDimensions.height) {
|
|
77
77
|
height = sourceDimensions.height;
|
|
78
78
|
width = Math.round(sourceDimensions.height * desiredAspectRatio);
|
|
79
79
|
}
|
|
@@ -324,7 +324,7 @@ const SUPPORTED_FORMATS = /* @__PURE__ */ new Set([
|
|
|
324
324
|
function createIPX(userOptions) {
|
|
325
325
|
const options = defu.defu(userOptions, {
|
|
326
326
|
alias: getEnv("IPX_ALIAS") || {},
|
|
327
|
-
maxAge: getEnv("IPX_MAX_AGE")
|
|
327
|
+
maxAge: getEnv("IPX_MAX_AGE") ?? 60,
|
|
328
328
|
sharpOptions: {}
|
|
329
329
|
});
|
|
330
330
|
options.alias = Object.fromEntries(
|
|
@@ -338,6 +338,11 @@ function createIPX(userOptions) {
|
|
|
338
338
|
(r) => r.default || r
|
|
339
339
|
);
|
|
340
340
|
});
|
|
341
|
+
const getSVGO = cachedPromise(async () => {
|
|
342
|
+
const { optimize } = await import('svgo');
|
|
343
|
+
const { xss } = await import('../chunks/svgo-xss.cjs');
|
|
344
|
+
return { optimize, xss };
|
|
345
|
+
});
|
|
341
346
|
return function ipx(id, modifiers = {}, opts = {}) {
|
|
342
347
|
if (!id) {
|
|
343
348
|
throw h3.createError({
|
|
@@ -369,8 +374,9 @@ function createIPX(userOptions) {
|
|
|
369
374
|
message: `Resource not found: ${id}`
|
|
370
375
|
});
|
|
371
376
|
}
|
|
377
|
+
const _maxAge = sourceMeta.maxAge ?? options.maxAge;
|
|
372
378
|
return {
|
|
373
|
-
maxAge: typeof
|
|
379
|
+
maxAge: typeof _maxAge === "string" ? Number.parseInt(_maxAge) : _maxAge,
|
|
374
380
|
mtime: sourceMeta.mtime ? new Date(sourceMeta.mtime) : void 0
|
|
375
381
|
};
|
|
376
382
|
});
|
|
@@ -401,13 +407,26 @@ function createIPX(userOptions) {
|
|
|
401
407
|
if (mFormat === "jpg") {
|
|
402
408
|
mFormat = "jpeg";
|
|
403
409
|
}
|
|
404
|
-
const format = mFormat && SUPPORTED_FORMATS.has(mFormat) ? mFormat : SUPPORTED_FORMATS.has(imageMeta$1.type) ? imageMeta$1.type : "jpeg";
|
|
410
|
+
const format = mFormat && SUPPORTED_FORMATS.has(mFormat) ? mFormat : SUPPORTED_FORMATS.has(imageMeta$1.type || "") ? imageMeta$1.type : "jpeg";
|
|
405
411
|
if (imageMeta$1.type === "svg" && !mFormat) {
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
412
|
+
if (options.svgo === false) {
|
|
413
|
+
return {
|
|
414
|
+
data: sourceData,
|
|
415
|
+
format: "svg+xml",
|
|
416
|
+
meta: imageMeta$1
|
|
417
|
+
};
|
|
418
|
+
} else {
|
|
419
|
+
const { optimize, xss } = await getSVGO();
|
|
420
|
+
const svg = optimize(sourceData.toString("utf8"), {
|
|
421
|
+
...options.svgo,
|
|
422
|
+
plugins: [xss, ...options.svgo?.plugins || []]
|
|
423
|
+
}).data;
|
|
424
|
+
return {
|
|
425
|
+
data: svg,
|
|
426
|
+
format: "svg+xml",
|
|
427
|
+
meta: imageMeta$1
|
|
428
|
+
};
|
|
429
|
+
}
|
|
411
430
|
}
|
|
412
431
|
const animated = modifiers.animated !== void 0 || modifiers.a !== void 0 || format === "gif";
|
|
413
432
|
const Sharp = await getSharp();
|
|
@@ -429,7 +448,7 @@ function createIPX(userOptions) {
|
|
|
429
448
|
for (const h of handlers) {
|
|
430
449
|
sharp = applyHandler(handlerContext, sharp, h.handler, h.args) || sharp;
|
|
431
450
|
}
|
|
432
|
-
if (SUPPORTED_FORMATS.has(format)) {
|
|
451
|
+
if (SUPPORTED_FORMATS.has(format || "")) {
|
|
433
452
|
sharp = sharp.toFormat(format, {
|
|
434
453
|
quality: handlerContext.quality,
|
|
435
454
|
progressive: format === "jpeg"
|
|
@@ -490,36 +509,45 @@ function createIPXH3Handler(ipx) {
|
|
|
490
509
|
delete modifiers.format;
|
|
491
510
|
if (autoFormat) {
|
|
492
511
|
modifiers.format = autoFormat;
|
|
493
|
-
h3.
|
|
512
|
+
h3.appendResponseHeader(event, "vary", "Accept");
|
|
494
513
|
}
|
|
495
514
|
}
|
|
496
515
|
const img = ipx(id, modifiers);
|
|
497
516
|
const sourceMeta = await img.getSourceMeta();
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
h3.setResponseHeader(event, "last-modified", sourceMeta.mtime.toUTCString());
|
|
504
|
-
}
|
|
517
|
+
sendResponseHeaderIfNotSet(
|
|
518
|
+
event,
|
|
519
|
+
"content-security-policy",
|
|
520
|
+
"default-src 'none'"
|
|
521
|
+
);
|
|
505
522
|
if (typeof sourceMeta.maxAge === "number") {
|
|
506
|
-
|
|
523
|
+
sendResponseHeaderIfNotSet(
|
|
507
524
|
event,
|
|
508
525
|
"cache-control",
|
|
509
526
|
`max-age=${+sourceMeta.maxAge}, public, s-maxage=${+sourceMeta.maxAge}`
|
|
510
527
|
);
|
|
511
528
|
}
|
|
529
|
+
if (sourceMeta.mtime) {
|
|
530
|
+
sendResponseHeaderIfNotSet(
|
|
531
|
+
event,
|
|
532
|
+
"last-modified",
|
|
533
|
+
sourceMeta.mtime.toUTCString()
|
|
534
|
+
);
|
|
535
|
+
const _ifModifiedSince = h3.getRequestHeader(event, "if-modified-since");
|
|
536
|
+
if (_ifModifiedSince && new Date(_ifModifiedSince) >= sourceMeta.mtime) {
|
|
537
|
+
h3.setResponseStatus(event, 304);
|
|
538
|
+
return h3.send(event);
|
|
539
|
+
}
|
|
540
|
+
}
|
|
512
541
|
const { data, format } = await img.process();
|
|
513
542
|
const etag = getEtag__default(data);
|
|
514
|
-
|
|
543
|
+
sendResponseHeaderIfNotSet(event, "etag", etag);
|
|
515
544
|
if (etag && h3.getRequestHeader(event, "if-none-match") === etag) {
|
|
516
545
|
h3.setResponseStatus(event, 304);
|
|
517
|
-
return
|
|
546
|
+
return h3.send(event);
|
|
518
547
|
}
|
|
519
548
|
if (format) {
|
|
520
|
-
|
|
549
|
+
sendResponseHeaderIfNotSet(event, "content-type", `image/${format}`);
|
|
521
550
|
}
|
|
522
|
-
h3.setResponseHeader(event, "content-security-policy", "default-src 'none'");
|
|
523
551
|
return data;
|
|
524
552
|
};
|
|
525
553
|
return h3.defineEventHandler(async (event) => {
|
|
@@ -550,6 +578,11 @@ function createIPXNodeServer(ipx) {
|
|
|
550
578
|
function createIPXPlainServer(ipx) {
|
|
551
579
|
return h3.toPlainHandler(createIPXH3App(ipx));
|
|
552
580
|
}
|
|
581
|
+
function sendResponseHeaderIfNotSet(event, name, value) {
|
|
582
|
+
if (!h3.getResponseHeader(event, name)) {
|
|
583
|
+
h3.setResponseHeader(event, name, value);
|
|
584
|
+
}
|
|
585
|
+
}
|
|
553
586
|
function autoDetectFormat(acceptHeader, animated) {
|
|
554
587
|
if (animated) {
|
|
555
588
|
const acceptMime2 = acceptNegotiator.negotiate(acceptHeader, ["image/webp", "image/gif"]);
|
|
@@ -607,11 +640,13 @@ function ipxHttpStorage(_options = {}) {
|
|
|
607
640
|
}
|
|
608
641
|
function parseResponse(response) {
|
|
609
642
|
let maxAge = defaultMaxAge;
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
643
|
+
if (_options.ignoreCacheControl) {
|
|
644
|
+
const _cacheControl = response.headers.get("cache-control");
|
|
645
|
+
if (_cacheControl) {
|
|
646
|
+
const m = _cacheControl.match(/max-age=(\d+)/);
|
|
647
|
+
if (m && m[1]) {
|
|
648
|
+
maxAge = Number.parseInt(m[1]);
|
|
649
|
+
}
|
|
615
650
|
}
|
|
616
651
|
}
|
|
617
652
|
let mtime;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ipx",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.1",
|
|
4
4
|
"repository": "unjs/ipx",
|
|
5
5
|
"description": "High performance, secure and easy-to-use image optimizer.",
|
|
6
6
|
"license": "MIT",
|
|
@@ -40,31 +40,33 @@
|
|
|
40
40
|
"@fastify/accept-negotiator": "^1.1.0",
|
|
41
41
|
"citty": "^0.1.4",
|
|
42
42
|
"consola": "^3.2.3",
|
|
43
|
-
"defu": "^6.1.
|
|
44
|
-
"destr": "^2.0.
|
|
43
|
+
"defu": "^6.1.3",
|
|
44
|
+
"destr": "^2.0.2",
|
|
45
45
|
"etag": "^1.8.1",
|
|
46
46
|
"h3": "^1.8.2",
|
|
47
|
-
"image-meta": "^0.
|
|
47
|
+
"image-meta": "^0.2.0",
|
|
48
48
|
"listhen": "^1.5.5",
|
|
49
49
|
"ofetch": "^1.3.3",
|
|
50
50
|
"pathe": "^1.1.1",
|
|
51
51
|
"sharp": "^0.32.6",
|
|
52
|
-
"
|
|
53
|
-
"
|
|
52
|
+
"svgo": "^3.0.2",
|
|
53
|
+
"ufo": "^1.3.1",
|
|
54
|
+
"unstorage": "^1.9.0",
|
|
55
|
+
"xss": "^1.0.14"
|
|
54
56
|
},
|
|
55
57
|
"devDependencies": {
|
|
56
|
-
"@types/etag": "^1.8.
|
|
57
|
-
"@types/is-valid-path": "^0.1.
|
|
58
|
-
"@vitest/coverage-v8": "^0.34.
|
|
58
|
+
"@types/etag": "^1.8.2",
|
|
59
|
+
"@types/is-valid-path": "^0.1.1",
|
|
60
|
+
"@vitest/coverage-v8": "^0.34.6",
|
|
59
61
|
"changelogen": "^0.5.5",
|
|
60
|
-
"eslint": "^8.
|
|
62
|
+
"eslint": "^8.53.0",
|
|
61
63
|
"eslint-config-unjs": "^0.2.1",
|
|
62
|
-
"jiti": "^1.
|
|
64
|
+
"jiti": "^1.21.0",
|
|
63
65
|
"prettier": "^3.0.3",
|
|
64
66
|
"serve-handler": "^6.1.5",
|
|
65
67
|
"typescript": "^5.2.2",
|
|
66
68
|
"unbuild": "^2.0.0",
|
|
67
|
-
"vitest": "^0.34.
|
|
69
|
+
"vitest": "^0.34.6"
|
|
68
70
|
},
|
|
69
|
-
"packageManager": "pnpm@8.
|
|
71
|
+
"packageManager": "pnpm@8.10.2"
|
|
70
72
|
}
|