@ubean/image 0.2.1 → 0.3.0

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.
@@ -82,11 +82,11 @@ declare const NuxtImg: import("vue").DefineComponent<import("vue").ExtractPropTy
82
82
  noModule: BooleanConstructor;
83
83
  sizesList: PropType<number[]>;
84
84
  }>> & Readonly<{}>, {
85
- noModule: boolean;
86
85
  alt: string;
87
86
  loading: "lazy" | "eager";
88
87
  placeholder: string | number | boolean;
89
88
  preload: boolean;
89
+ noModule: boolean;
90
90
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
91
91
  declare const NuxtPicture: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
92
92
  src: {
package/dist/index.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import { a as ImageModifiers, c as ImageProvider, d as ResolvedImage, i as ImageFormat, l as ImageSize, n as ImageCTX, o as ImageOptions, r as ImageFit, s as ImagePosition, t as CreateImageOptions, u as ImageSrcsetItem } from "./types-DosomQo8.js";
2
2
  import { NuxtImg, NuxtPicture } from "./components.js";
3
3
  import ubeanImagePlugin, { UbeanImageOptions } from "./vite.js";
4
+ import { ServeIpxOptions, ServeIpxResult, createIpxHonoHandler, parseIpxModifiers, resolveLocalImage, serveIpxRequest } from "./ipx.js";
4
5
  import { C as resolveAlias, D as staticProvider, E as srcSetToString, O as validateDomain, S as isRemoteUrl, _ as getPlaceholder, a as useImage, b as ipxProvider, c as cloudinaryProvider, d as createImageContext, f as createImageHash, g as detectFormat, h as defaultScreens, i as resolveImage, l as createDensitySrcSet, m as createStaticUrl, n as defineImagePreset, o as buildImgAttributes, p as createSrcSet, r as getImageContext, s as builtinProviders, t as configureImageRuntime, u as createIPXUrl, x as isDataUrl, y as imgixProvider } from "./runtime-B5AxhRTR.js";
5
6
  //#region src/index.d.ts
6
7
  declare const UbeanImg: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
@@ -177,4 +178,4 @@ declare const _default: {
177
178
  install(app: any, options?: any): void;
178
179
  };
179
180
  //#endregion
180
- export { type CreateImageOptions, type ImageCTX, type ImageFit, type ImageFormat, type ImageModifiers, type ImageOptions, type ImagePosition, type ImageProvider, type ImageSize, type ImageSrcsetItem, NuxtImg, NuxtPicture, type ResolvedImage, type UbeanImageOptions, UbeanImg, UbeanPicture, buildImgAttributes, builtinProviders, cloudinaryProvider, configureImageRuntime, createDensitySrcSet, createIPXUrl, createImageContext, createImageHash, createSrcSet, createStaticUrl, _default as default, defaultScreens, defineImagePreset, detectFormat, getImageContext, getPlaceholder, imgixProvider, ipxProvider, isDataUrl, isRemoteUrl, resolveAlias, resolveImage, srcSetToString, staticProvider, ubeanImagePlugin, useImage, validateDomain };
181
+ export { type CreateImageOptions, type ImageCTX, type ImageFit, type ImageFormat, type ImageModifiers, type ImageOptions, type ImagePosition, type ImageProvider, type ImageSize, type ImageSrcsetItem, NuxtImg, NuxtPicture, type ResolvedImage, type ServeIpxOptions, type ServeIpxResult, type UbeanImageOptions, UbeanImg, UbeanPicture, buildImgAttributes, builtinProviders, cloudinaryProvider, configureImageRuntime, createDensitySrcSet, createIPXUrl, createImageContext, createImageHash, createIpxHonoHandler, createSrcSet, createStaticUrl, _default as default, defaultScreens, defineImagePreset, detectFormat, getImageContext, getPlaceholder, imgixProvider, ipxProvider, isDataUrl, isRemoteUrl, parseIpxModifiers, resolveAlias, resolveImage, resolveLocalImage, serveIpxRequest, srcSetToString, staticProvider, ubeanImagePlugin, useImage, validateDomain };
package/dist/index.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { C as resolveAlias, D as staticProvider, E as srcSetToString, O as validateDomain, S as isRemoteUrl, _ as getPlaceholder, a as useImage, b as ipxProvider, c as cloudinaryProvider, d as createImageContext, f as createImageHash, g as detectFormat, h as defaultScreens, i as resolveImage, l as createDensitySrcSet, m as createStaticUrl, n as defineImagePreset, o as buildImgAttributes, p as createSrcSet, r as getImageContext, s as builtinProviders, t as configureImageRuntime, u as createIPXUrl, x as isDataUrl, y as imgixProvider } from "./runtime-Cf5VBwx5.js";
2
2
  import { NuxtImg, NuxtPicture } from "./components.js";
3
+ import { createIpxHonoHandler, parseIpxModifiers, resolveLocalImage, serveIpxRequest } from "./ipx.js";
3
4
  import ubeanImagePlugin from "./vite.js";
4
5
  //#region src/index.ts
5
6
  const UbeanImg = NuxtImg;
@@ -12,4 +13,4 @@ var src_default = { install(app, options) {
12
13
  app.component("NuxtPicture", NuxtPicture);
13
14
  } };
14
15
  //#endregion
15
- export { NuxtImg, NuxtPicture, UbeanImg, UbeanPicture, buildImgAttributes, builtinProviders, cloudinaryProvider, configureImageRuntime, createDensitySrcSet, createIPXUrl, createImageContext, createImageHash, createSrcSet, createStaticUrl, src_default as default, defaultScreens, defineImagePreset, detectFormat, getImageContext, getPlaceholder, imgixProvider, ipxProvider, isDataUrl, isRemoteUrl, resolveAlias, resolveImage, srcSetToString, staticProvider, ubeanImagePlugin, useImage, validateDomain };
16
+ export { NuxtImg, NuxtPicture, UbeanImg, UbeanPicture, buildImgAttributes, builtinProviders, cloudinaryProvider, configureImageRuntime, createDensitySrcSet, createIPXUrl, createImageContext, createImageHash, createIpxHonoHandler, createSrcSet, createStaticUrl, src_default as default, defaultScreens, defineImagePreset, detectFormat, getImageContext, getPlaceholder, imgixProvider, ipxProvider, isDataUrl, isRemoteUrl, parseIpxModifiers, resolveAlias, resolveImage, resolveLocalImage, serveIpxRequest, srcSetToString, staticProvider, ubeanImagePlugin, useImage, validateDomain };
package/dist/ipx.d.ts ADDED
@@ -0,0 +1,28 @@
1
+ //#region src/ipx.d.ts
2
+ interface ServeIpxOptions {
3
+ rootDir: string;
4
+ staticDir?: string;
5
+ isDev?: boolean;
6
+ }
7
+ interface ServeIpxResult {
8
+ status: number;
9
+ headers: Record<string, string>;
10
+ body?: Buffer | Uint8Array | string;
11
+ redirect?: string;
12
+ }
13
+ declare function parseIpxModifiers(str: string): Record<string, unknown>;
14
+ declare function resolveLocalImage(root: string, staticDir: string, src: string): string | undefined;
15
+ declare function ipxContentType(src: string, format?: string): string;
16
+ /**
17
+ * Serve `/_ipx/<modifiers>/<src>` the same way in Vite dev and production Hono.
18
+ * `pathname` is the part after `/_ipx` (e.g. `/_/photo.jpg` or `/w_800/photo.jpg`).
19
+ */
20
+ declare function serveIpxRequest(pathname: string, options: ServeIpxOptions): Promise<ServeIpxResult>;
21
+ declare function createIpxHonoHandler(options: ServeIpxOptions): (c: {
22
+ req: {
23
+ path: string;
24
+ };
25
+ redirect: (location: string, status?: 301 | 302) => Response;
26
+ }) => Promise<Response>;
27
+ //#endregion
28
+ export { ServeIpxOptions, ServeIpxResult, createIpxHonoHandler, ipxContentType, parseIpxModifiers, resolveLocalImage, serveIpxRequest };
package/dist/ipx.js ADDED
@@ -0,0 +1,190 @@
1
+ import { existsSync, readFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ //#region src/ipx.ts
4
+ function parseIpxModifiers(str) {
5
+ const mods = {};
6
+ const parts = str.split(",");
7
+ for (const part of parts) {
8
+ if (part === "_") continue;
9
+ if (part === "fh") {
10
+ mods.flip = mods.flip === "v" ? "hv" : "h";
11
+ continue;
12
+ }
13
+ if (part === "fv") {
14
+ mods.flip = mods.flip === "h" ? "hv" : "v";
15
+ continue;
16
+ }
17
+ if (part === "grayscale") {
18
+ mods.grayscale = true;
19
+ continue;
20
+ }
21
+ if (part === "negate") {
22
+ mods.negate = true;
23
+ continue;
24
+ }
25
+ if (part === "trim") {
26
+ mods.trim = true;
27
+ continue;
28
+ }
29
+ if (part === "enlarge") {
30
+ mods.enlarge = true;
31
+ continue;
32
+ }
33
+ const [key, ...valueParts] = part.split("_");
34
+ const value = valueParts.join("_");
35
+ switch (key) {
36
+ case "w":
37
+ mods.width = parseInt(value, 10);
38
+ break;
39
+ case "h":
40
+ mods.height = parseInt(value, 10);
41
+ break;
42
+ case "f":
43
+ mods.fit = value;
44
+ break;
45
+ case "p":
46
+ mods.position = decodeURIComponent(value);
47
+ break;
48
+ case "fm":
49
+ mods.format = value;
50
+ break;
51
+ case "q":
52
+ mods.quality = parseInt(value, 10);
53
+ break;
54
+ case "blur":
55
+ mods.blur = parseFloat(value);
56
+ break;
57
+ case "sharpen":
58
+ mods.sharpen = parseFloat(value);
59
+ break;
60
+ case "rot":
61
+ mods.rotate = parseInt(value, 10);
62
+ break;
63
+ case "bg":
64
+ mods.background = decodeURIComponent(value);
65
+ break;
66
+ case "br":
67
+ mods.brightness = parseFloat(value);
68
+ break;
69
+ case "con":
70
+ mods.contrast = parseFloat(value);
71
+ break;
72
+ case "sat": mods.saturation = parseFloat(value);
73
+ }
74
+ }
75
+ return mods;
76
+ }
77
+ function resolveLocalImage(root, staticDir, src) {
78
+ const relative = src.replace(/^\/+/, "");
79
+ if (relative.includes("..")) return void 0;
80
+ return [join(root, staticDir, relative), join(root, relative)].find((path) => existsSync(path));
81
+ }
82
+ function ipxContentType(src, format) {
83
+ return {
84
+ webp: "image/webp",
85
+ avif: "image/avif",
86
+ jpg: "image/jpeg",
87
+ jpeg: "image/jpeg",
88
+ png: "image/png",
89
+ gif: "image/gif",
90
+ svg: "image/svg+xml"
91
+ }[format || src.split(".").pop()?.toLowerCase() || ""] || "image/jpeg";
92
+ }
93
+ async function tryIpxTransform(filePath, modifiers) {
94
+ if (Object.keys(modifiers).length === 0) return void 0;
95
+ try {
96
+ const ipx = await import("ipx");
97
+ if (typeof ipx.createIPX !== "function") return void 0;
98
+ const dir = join(filePath, "..");
99
+ const storage = ipx.ipxFSStorage ? ipx.ipxFSStorage({ dir }) : { dir };
100
+ const instance = ipx.createIPX({
101
+ storage,
102
+ httpStorage: false
103
+ });
104
+ const parts = [];
105
+ if (typeof modifiers.width === "number") parts.push(`s_${modifiers.width}x${typeof modifiers.height === "number" ? modifiers.height : "_"}`);
106
+ if (typeof modifiers.format === "string") parts.push(`f_${modifiers.format}`);
107
+ if (typeof modifiers.quality === "number") parts.push(`q_${modifiers.quality}`);
108
+ const result = await instance(`${parts.join(",") || "_"}/${filePath.split("/").pop()}`);
109
+ return Buffer.isBuffer(result.data) ? result.data : Buffer.from(result.data);
110
+ } catch {
111
+ return;
112
+ }
113
+ }
114
+ /**
115
+ * Serve `/_ipx/<modifiers>/<src>` the same way in Vite dev and production Hono.
116
+ * `pathname` is the part after `/_ipx` (e.g. `/_/photo.jpg` or `/w_800/photo.jpg`).
117
+ */
118
+ async function serveIpxRequest(pathname, options) {
119
+ const cacheControl = options.isDev ? "no-cache" : "public, max-age=31536000, immutable";
120
+ const parts = decodeURIComponent(pathname).split("/").filter(Boolean);
121
+ if (parts.length < 2) return {
122
+ status: 400,
123
+ headers: { "Content-Type": "text/plain" },
124
+ body: "Invalid IPX request"
125
+ };
126
+ const modifiers = parts[0] === "_" ? {} : parseIpxModifiers(parts[0]);
127
+ let src = parts.slice(1).join("/");
128
+ if (src.startsWith("https:/") && !src.startsWith("https://")) src = src.replace(/^https:\//, "https://");
129
+ else if (src.startsWith("http:/") && !src.startsWith("http://")) src = src.replace(/^http:\//, "http://");
130
+ const headers = {
131
+ "Content-Type": ipxContentType(src, typeof modifiers.format === "string" ? modifiers.format : void 0),
132
+ "Cache-Control": cacheControl
133
+ };
134
+ if (src.startsWith("http://") || src.startsWith("https://")) return {
135
+ status: 302,
136
+ headers: {
137
+ ...headers,
138
+ "X-IPX-Mode": "redirect",
139
+ Location: src
140
+ },
141
+ redirect: src
142
+ };
143
+ const local = resolveLocalImage(options.rootDir, options.staticDir || "public", src);
144
+ if (!local) return {
145
+ status: 404,
146
+ headers: { "Content-Type": "text/plain" },
147
+ body: "Image not found"
148
+ };
149
+ const transformed = await tryIpxTransform(local, modifiers);
150
+ if (transformed) return {
151
+ status: 200,
152
+ headers: {
153
+ ...headers,
154
+ "X-IPX-Mode": "transform"
155
+ },
156
+ body: transformed
157
+ };
158
+ const mode = Object.keys(modifiers).length > 0 ? "passthrough" : "file";
159
+ const body = readFileSync(local);
160
+ return {
161
+ status: 200,
162
+ headers: {
163
+ ...headers,
164
+ "X-IPX-Mode": mode,
165
+ "Content-Length": String(body.length)
166
+ },
167
+ body
168
+ };
169
+ }
170
+ /** Node `Buffer` is `Uint8Array<ArrayBufferLike>`; fetch `BodyInit` requires `ArrayBuffer`. */
171
+ function toBodyInit(body) {
172
+ if (body == null) return void 0;
173
+ if (typeof body === "string") return body;
174
+ const { buffer, byteOffset, byteLength } = body;
175
+ if (buffer instanceof ArrayBuffer) return new Uint8Array(buffer, byteOffset, byteLength);
176
+ return Uint8Array.from(body);
177
+ }
178
+ function createIpxHonoHandler(options) {
179
+ return async (c) => {
180
+ const path = c.req.path.startsWith("/_ipx") ? c.req.path.slice(5) || "/" : c.req.path;
181
+ const result = await serveIpxRequest(path.startsWith("/") ? path : `/${path}`, options);
182
+ if (result.redirect) return c.redirect(result.redirect, 302);
183
+ return new Response(toBodyInit(result.body), {
184
+ status: result.status,
185
+ headers: result.headers
186
+ });
187
+ };
188
+ }
189
+ //#endregion
190
+ export { createIpxHonoHandler, ipxContentType, parseIpxModifiers, resolveLocalImage, serveIpxRequest };
package/dist/vite.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { t as configureImageRuntime } from "./runtime-Cf5VBwx5.js";
2
+ import { serveIpxRequest } from "./ipx.js";
2
3
  import { defu } from "defu";
3
4
  //#region src/vite.ts
4
5
  const defaultOptions = {
@@ -12,11 +13,13 @@ const RESOLVED_VIRTUAL_IMAGE = `\0${VIRTUAL_IMAGE}`;
12
13
  function ubeanImagePlugin(userOptions = {}) {
13
14
  const options = defu(userOptions, defaultOptions);
14
15
  let isDev = false;
16
+ let projectRoot = process.cwd();
15
17
  return {
16
18
  name: "ubean:image",
17
19
  enforce: "pre",
18
20
  configResolved(resolvedConfig) {
19
21
  isDev = resolvedConfig.command === "serve";
22
+ projectRoot = resolvedConfig.root;
20
23
  },
21
24
  resolveId(id) {
22
25
  if (id === VIRTUAL_IMAGE) return RESOLVED_VIRTUAL_IMAGE;
@@ -45,20 +48,24 @@ export default imageConfig;
45
48
  const ipxHandler = async (req, res, next) => {
46
49
  if (!req.url) return next();
47
50
  const url = new URL(req.url, "http://localhost");
48
- const parts = decodeURIComponent(url.pathname).split("/").filter(Boolean);
49
- if (parts.length < 2) {
50
- res.statusCode = 400;
51
- res.end("Invalid IPX request");
52
- return;
53
- }
54
- const modifiers = parts[0] === "_" ? {} : parseModifiers(parts[0]);
55
- const src = parts.slice(1).join("/");
56
51
  try {
57
- res.setHeader("Content-Type", getContentType(src, modifiers.format));
58
- res.setHeader("Cache-Control", isDev ? "no-cache" : "public, max-age=31536000, immutable");
59
- res.statusCode = 302;
60
- res.setHeader("Location", src.startsWith("http") ? src : `/${src}`);
61
- res.end();
52
+ const result = await serveIpxRequest(url.pathname, {
53
+ rootDir: projectRoot,
54
+ staticDir: options.staticDir || "public",
55
+ isDev
56
+ });
57
+ for (const [key, value] of Object.entries(result.headers)) res.setHeader(key, value);
58
+ if (result.redirect) {
59
+ res.statusCode = result.status;
60
+ res.end();
61
+ return;
62
+ }
63
+ res.statusCode = result.status;
64
+ if (Buffer.isBuffer(result.body) || result.body instanceof Uint8Array) {
65
+ res.end(result.body);
66
+ return;
67
+ }
68
+ res.end(result.body ?? "");
62
69
  } catch (err) {
63
70
  next(err);
64
71
  }
@@ -85,89 +92,5 @@ export default imageConfig;
85
92
  }
86
93
  };
87
94
  }
88
- function parseModifiers(str) {
89
- const mods = {};
90
- const parts = str.split(",");
91
- for (const part of parts) {
92
- if (part === "_") continue;
93
- if (part === "fh") {
94
- mods.flip = mods.flip === "v" ? "hv" : "h";
95
- continue;
96
- }
97
- if (part === "fv") {
98
- mods.flip = mods.flip === "h" ? "hv" : "v";
99
- continue;
100
- }
101
- if (part === "grayscale") {
102
- mods.grayscale = true;
103
- continue;
104
- }
105
- if (part === "negate") {
106
- mods.negate = true;
107
- continue;
108
- }
109
- if (part === "trim") {
110
- mods.trim = true;
111
- continue;
112
- }
113
- if (part === "enlarge") {
114
- mods.enlarge = true;
115
- continue;
116
- }
117
- const [key, ...valueParts] = part.split("_");
118
- const value = valueParts.join("_");
119
- switch (key) {
120
- case "w":
121
- mods.width = parseInt(value, 10);
122
- break;
123
- case "h":
124
- mods.height = parseInt(value, 10);
125
- break;
126
- case "f":
127
- mods.fit = value;
128
- break;
129
- case "p":
130
- mods.position = decodeURIComponent(value);
131
- break;
132
- case "fm":
133
- mods.format = value;
134
- break;
135
- case "q":
136
- mods.quality = parseInt(value, 10);
137
- break;
138
- case "blur":
139
- mods.blur = parseFloat(value);
140
- break;
141
- case "sharpen":
142
- mods.sharpen = parseFloat(value);
143
- break;
144
- case "rot":
145
- mods.rotate = parseInt(value, 10);
146
- break;
147
- case "bg":
148
- mods.background = decodeURIComponent(value);
149
- break;
150
- case "br":
151
- mods.brightness = parseFloat(value);
152
- break;
153
- case "con":
154
- mods.contrast = parseFloat(value);
155
- break;
156
- case "sat": mods.saturation = parseFloat(value);
157
- }
158
- }
159
- return mods;
160
- }
161
- function getContentType(src, format) {
162
- return {
163
- webp: "image/webp",
164
- avif: "image/avif",
165
- jpg: "image/jpeg",
166
- jpeg: "image/jpeg",
167
- png: "image/png",
168
- gif: "image/gif",
169
- svg: "image/svg+xml"
170
- }[format || src.split(".").pop()?.toLowerCase() || ""] || "image/jpeg";
171
- }
172
95
  //#endregion
173
96
  export { ubeanImagePlugin as default, ubeanImagePlugin };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ubean/image",
3
- "version": "0.2.1",
3
+ "version": "0.3.0",
4
4
  "description": "Image optimization and transformation for ubean",
5
5
  "files": [
6
6
  "dist"