@askrjs/vite 0.0.9 → 0.0.11
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 +42 -0
- package/dist/image-node.d.ts +10 -0
- package/dist/image-node.js +56 -0
- package/dist/image-node.js.map +1 -0
- package/dist/image-options-B-QnEqPc.js +83 -0
- package/dist/image-options-B-QnEqPc.js.map +1 -0
- package/dist/image-types-BQpLyBLt.d.ts +48 -0
- package/dist/image.d.ts +12 -0
- package/dist/image.js +29 -0
- package/dist/image.js.map +1 -0
- package/dist/index.d.ts +4 -1
- package/dist/index.js +579 -4
- package/dist/index.js.map +1 -1
- package/dist/server.js +3 -3
- package/package.json +23 -2
package/README.md
CHANGED
|
@@ -12,6 +12,8 @@ framework's JSX and template conventions inside a normal Vite app.
|
|
|
12
12
|
|
|
13
13
|
```bash
|
|
14
14
|
npm install -D @askrjs/vite vite
|
|
15
|
+
# or
|
|
16
|
+
npm install -D @askrjs/vite vite-plus
|
|
15
17
|
npm install @askrjs/askr
|
|
16
18
|
```
|
|
17
19
|
|
|
@@ -27,6 +29,46 @@ export default defineConfig({
|
|
|
27
29
|
});
|
|
28
30
|
```
|
|
29
31
|
|
|
32
|
+
## Responsive images
|
|
33
|
+
|
|
34
|
+
Responsive image processing is opt in and requires the optional `sharp` peer:
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
npm install -D sharp@^0.35.3
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
```ts
|
|
41
|
+
// vite.config.ts
|
|
42
|
+
export default defineConfig({
|
|
43
|
+
plugins: [askr({ images: true })],
|
|
44
|
+
});
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
```tsx
|
|
48
|
+
import { Image, image } from "@askrjs/vite/image";
|
|
49
|
+
|
|
50
|
+
const hero = image(new URL("./hero.jpg", import.meta.url));
|
|
51
|
+
|
|
52
|
+
<Image image={hero} alt="Mountain ridge" sizes="(min-width: 60rem) 50vw, 100vw" />;
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
The build emits content-hashed AVIF, WebP, and source-format variants at
|
|
56
|
+
320, 640, 960, 1280, and 1920 pixels without upscaling. Defaults are AVIF 50,
|
|
57
|
+
WebP 75, JPEG 82, and lossless PNG; plugin defaults and each declaration can
|
|
58
|
+
override widths, formats, and quality. Per-image `fit: "cover"` declarations
|
|
59
|
+
must also provide an `aspectRatio` and may provide a crop `position`.
|
|
60
|
+
|
|
61
|
+
`Image` requires `alt`, includes intrinsic dimensions, forwards ordinary image
|
|
62
|
+
attributes, and does not choose eager or lazy loading for the application. SVG,
|
|
63
|
+
animated, unsupported, and already-small declarations pass through unchanged.
|
|
64
|
+
Only files explicitly declared with `image(new URL(..., import.meta.url))` are
|
|
65
|
+
processed; `public/` and undeclared files are never rewritten.
|
|
66
|
+
|
|
67
|
+
The client build writes checked metadata below
|
|
68
|
+
`node_modules/.cache/@askrjs/vite/images`. Run it before direct Node SSR/SSG
|
|
69
|
+
imports so the server uses exactly the URLs and dimensions emitted by Vite.
|
|
70
|
+
Missing or stale metadata fails with an instruction to rebuild.
|
|
71
|
+
|
|
30
72
|
## Document ownership
|
|
31
73
|
|
|
32
74
|
Vite is the sole owner of the HTML document. A server-rendered template must
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
|
|
2
|
+
import { a as ImageProps, c as ResponsiveImageSource, i as ImagePipelineOptions, n as ImageOptions, o as ImageQualityOptions, r as ImageOutputFormat, s as ResponsiveImage, t as ImageFit } from "./image-types-BQpLyBLt.js";
|
|
3
|
+
import { Image } from "./image.js";
|
|
4
|
+
//#region src/image-node.d.ts
|
|
5
|
+
/** Resolve the exact responsive metadata produced by the preceding client build. */
|
|
6
|
+
declare function image(source: URL, options?: ImageOptions): ResponsiveImage;
|
|
7
|
+
/** Accept Vite's transformed declaration during direct Node module evaluation. */
|
|
8
|
+
declare function image(source: ResponsiveImage): ResponsiveImage;
|
|
9
|
+
//#endregion
|
|
10
|
+
export { Image, type ImageFit, type ImageOptions, type ImageOutputFormat, type ImagePipelineOptions, type ImageProps, type ImageQualityOptions, type ResponsiveImage, type ResponsiveImageSource, image };
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
|
|
2
|
+
import { r as IMAGE_METADATA_PATH, t as normalizeDeclarationOptions } from "./image-options-B-QnEqPc.js";
|
|
3
|
+
import { Image } from "./image.js";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { createHash } from "node:crypto";
|
|
6
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
7
|
+
import { fileURLToPath } from "node:url";
|
|
8
|
+
//#region src/image-node.ts
|
|
9
|
+
function hash(value) {
|
|
10
|
+
return createHash("sha256").update(value).digest("hex");
|
|
11
|
+
}
|
|
12
|
+
function declarationKey(sourcePath, options) {
|
|
13
|
+
return hash(`${sourcePath}\0${JSON.stringify(normalizeDeclarationOptions(options))}`);
|
|
14
|
+
}
|
|
15
|
+
function metadataCandidates(sourcePath) {
|
|
16
|
+
const candidates = [];
|
|
17
|
+
const seen = /* @__PURE__ */ new Set();
|
|
18
|
+
for (const start of [path.dirname(sourcePath), process.cwd()]) {
|
|
19
|
+
let directory = path.resolve(start);
|
|
20
|
+
while (!seen.has(directory)) {
|
|
21
|
+
seen.add(directory);
|
|
22
|
+
candidates.push(path.join(directory, IMAGE_METADATA_PATH));
|
|
23
|
+
const parent = path.dirname(directory);
|
|
24
|
+
if (parent === directory) break;
|
|
25
|
+
directory = parent;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return candidates;
|
|
29
|
+
}
|
|
30
|
+
function readMetadata(sourcePath) {
|
|
31
|
+
const metadataPath = metadataCandidates(sourcePath).find(existsSync);
|
|
32
|
+
if (!metadataPath) throw new Error("@askrjs/vite image metadata is missing. Run the Vite client build with askr({ images: true }) before direct Node SSR/SSG imports.");
|
|
33
|
+
let record;
|
|
34
|
+
try {
|
|
35
|
+
record = JSON.parse(readFileSync(metadataPath, "utf8"));
|
|
36
|
+
} catch (error) {
|
|
37
|
+
throw new Error(`@askrjs/vite could not read checked image metadata at ${metadataPath}.`, { cause: error });
|
|
38
|
+
}
|
|
39
|
+
if (record.version !== 2 || !record.entries) throw new Error(`@askrjs/vite image metadata at ${metadataPath} is incompatible. Re-run the Vite client build.`);
|
|
40
|
+
return record;
|
|
41
|
+
}
|
|
42
|
+
function isResponsiveImage(value) {
|
|
43
|
+
return Boolean(value && typeof value === "object" && value.__askrImage === true);
|
|
44
|
+
}
|
|
45
|
+
function image(source, options = {}) {
|
|
46
|
+
if (isResponsiveImage(source)) return Object.freeze(source);
|
|
47
|
+
if (!(source instanceof URL) || source.protocol !== "file:") throw new TypeError("@askrjs/vite image() requires a file URL created with import.meta.url.");
|
|
48
|
+
const sourcePath = path.resolve(fileURLToPath(source));
|
|
49
|
+
const entry = readMetadata(sourcePath).entries[declarationKey(sourcePath, options)];
|
|
50
|
+
if (!entry) throw new Error(`@askrjs/vite has no built image declaration for ${sourcePath}. Re-run the Vite client build after changing image() options.`);
|
|
51
|
+
const sourceHash = hash(readFileSync(sourcePath));
|
|
52
|
+
if (entry.sourcePath !== sourcePath || entry.sourceHash !== sourceHash) throw new Error(`@askrjs/vite image metadata for ${sourcePath} is stale. Re-run the Vite client build before SSR/SSG.`);
|
|
53
|
+
return Object.freeze(entry.image);
|
|
54
|
+
}
|
|
55
|
+
//#endregion
|
|
56
|
+
export { Image, image };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"image-node.js","names":[],"sources":["../src/image-node.ts"],"sourcesContent":["import { createHash } from \"node:crypto\";\nimport { existsSync, readFileSync } from \"node:fs\";\nimport path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { Image } from \"./image\";\nimport {\n IMAGE_METADATA_PATH,\n IMAGE_METADATA_VERSION,\n type ImageMetadataRecord,\n} from \"./image-metadata\";\nimport { normalizeDeclarationOptions } from \"./image-options\";\nimport type { ImageOptions, ResponsiveImage } from \"./image-types\";\n\nfunction hash(value: string | Buffer): string {\n return createHash(\"sha256\").update(value).digest(\"hex\");\n}\n\nfunction declarationKey(sourcePath: string, options: ImageOptions): string {\n return hash(`${sourcePath}\\0${JSON.stringify(normalizeDeclarationOptions(options))}`);\n}\n\nfunction metadataCandidates(sourcePath: string): string[] {\n const candidates: string[] = [];\n const seen = new Set<string>();\n for (const start of [path.dirname(sourcePath), process.cwd()]) {\n let directory = path.resolve(start);\n while (!seen.has(directory)) {\n seen.add(directory);\n candidates.push(path.join(directory, IMAGE_METADATA_PATH));\n const parent = path.dirname(directory);\n if (parent === directory) break;\n directory = parent;\n }\n }\n return candidates;\n}\n\nfunction readMetadata(sourcePath: string): ImageMetadataRecord {\n const metadataPath = metadataCandidates(sourcePath).find(existsSync);\n if (!metadataPath) {\n throw new Error(\n \"@askrjs/vite image metadata is missing. Run the Vite client build with askr({ images: true }) before direct Node SSR/SSG imports.\",\n );\n }\n let record: ImageMetadataRecord;\n try {\n record = JSON.parse(readFileSync(metadataPath, \"utf8\")) as ImageMetadataRecord;\n } catch (error) {\n throw new Error(`@askrjs/vite could not read checked image metadata at ${metadataPath}.`, {\n cause: error,\n });\n }\n if (record.version !== IMAGE_METADATA_VERSION || !record.entries) {\n throw new Error(\n `@askrjs/vite image metadata at ${metadataPath} is incompatible. Re-run the Vite client build.`,\n );\n }\n return record;\n}\n\nfunction isResponsiveImage(value: unknown): value is ResponsiveImage {\n return Boolean(\n value && typeof value === \"object\" && (value as Partial<ResponsiveImage>).__askrImage === true,\n );\n}\n\n/** Resolve the exact responsive metadata produced by the preceding client build. */\nexport function image(source: URL, options?: ImageOptions): ResponsiveImage;\n/** Accept Vite's transformed declaration during direct Node module evaluation. */\nexport function image(source: ResponsiveImage): ResponsiveImage;\nexport function image(source: URL | ResponsiveImage, options: ImageOptions = {}): ResponsiveImage {\n if (isResponsiveImage(source)) return Object.freeze(source);\n if (!(source instanceof URL) || source.protocol !== \"file:\") {\n throw new TypeError(\"@askrjs/vite image() requires a file URL created with import.meta.url.\");\n }\n const sourcePath = path.resolve(fileURLToPath(source));\n const record = readMetadata(sourcePath);\n const entry = record.entries[declarationKey(sourcePath, options)];\n if (!entry) {\n throw new Error(\n `@askrjs/vite has no built image declaration for ${sourcePath}. Re-run the Vite client build after changing image() options.`,\n );\n }\n const sourceHash = hash(readFileSync(sourcePath));\n if (entry.sourcePath !== sourcePath || entry.sourceHash !== sourceHash) {\n throw new Error(\n `@askrjs/vite image metadata for ${sourcePath} is stale. Re-run the Vite client build before SSR/SSG.`,\n );\n }\n return Object.freeze(entry.image);\n}\n\nexport { Image };\nexport type {\n ImageFit,\n ImageOptions,\n ImageOutputFormat,\n ImagePipelineOptions,\n ImageProps,\n ImageQualityOptions,\n ResponsiveImage,\n ResponsiveImageSource,\n} from \"./image-types\";\n"],"mappings":";;;;;;;;AAaA,SAAS,KAAK,OAAgC;CAC5C,OAAO,WAAW,QAAQ,CAAC,CAAC,OAAO,KAAK,CAAC,CAAC,OAAO,KAAK;AACxD;AAEA,SAAS,eAAe,YAAoB,SAA+B;CACzE,OAAO,KAAK,GAAG,WAAW,IAAI,KAAK,UAAU,4BAA4B,OAAO,CAAC,GAAG;AACtF;AAEA,SAAS,mBAAmB,YAA8B;CACxD,MAAM,aAAuB,CAAC;CAC9B,MAAM,uBAAO,IAAI,IAAY;CAC7B,KAAK,MAAM,SAAS,CAAC,KAAK,QAAQ,UAAU,GAAG,QAAQ,IAAI,CAAC,GAAG;EAC7D,IAAI,YAAY,KAAK,QAAQ,KAAK;EAClC,OAAO,CAAC,KAAK,IAAI,SAAS,GAAG;GAC3B,KAAK,IAAI,SAAS;GAClB,WAAW,KAAK,KAAK,KAAK,WAAW,mBAAmB,CAAC;GACzD,MAAM,SAAS,KAAK,QAAQ,SAAS;GACrC,IAAI,WAAW,WAAW;GAC1B,YAAY;EACd;CACF;CACA,OAAO;AACT;AAEA,SAAS,aAAa,YAAyC;CAC7D,MAAM,eAAe,mBAAmB,UAAU,CAAC,CAAC,KAAK,UAAU;CACnE,IAAI,CAAC,cACH,MAAM,IAAI,MACR,mIACF;CAEF,IAAI;CACJ,IAAI;EACF,SAAS,KAAK,MAAM,aAAa,cAAc,MAAM,CAAC;CACxD,SAAS,OAAO;EACd,MAAM,IAAI,MAAM,yDAAyD,aAAa,IAAI,EACxF,OAAO,MACT,CAAC;CACH;CACA,IAAI,OAAO,YAAA,KAAsC,CAAC,OAAO,SACvD,MAAM,IAAI,MACR,kCAAkC,aAAa,gDACjD;CAEF,OAAO;AACT;AAEA,SAAS,kBAAkB,OAA0C;CACnE,OAAO,QACL,SAAS,OAAO,UAAU,YAAa,MAAmC,gBAAgB,IAC5F;AACF;AAMA,SAAgB,MAAM,QAA+B,UAAwB,CAAC,GAAoB;CAChG,IAAI,kBAAkB,MAAM,GAAG,OAAO,OAAO,OAAO,MAAM;CAC1D,IAAI,EAAE,kBAAkB,QAAQ,OAAO,aAAa,SAClD,MAAM,IAAI,UAAU,wEAAwE;CAE9F,MAAM,aAAa,KAAK,QAAQ,cAAc,MAAM,CAAC;CAErD,MAAM,QADS,aAAa,UACT,CAAC,CAAC,QAAQ,eAAe,YAAY,OAAO;CAC/D,IAAI,CAAC,OACH,MAAM,IAAI,MACR,mDAAmD,WAAW,+DAChE;CAEF,MAAM,aAAa,KAAK,aAAa,UAAU,CAAC;CAChD,IAAI,MAAM,eAAe,cAAc,MAAM,eAAe,YAC1D,MAAM,IAAI,MACR,mCAAmC,WAAW,wDAChD;CAEF,OAAO,OAAO,OAAO,MAAM,KAAK;AAClC"}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
|
|
2
|
+
//#region src/image-metadata.ts
|
|
3
|
+
const IMAGE_METADATA_PATH = "node_modules/.cache/@askrjs/vite/images/metadata.json";
|
|
4
|
+
//#endregion
|
|
5
|
+
//#region src/image-options.ts
|
|
6
|
+
const DEFAULT_IMAGE_WIDTHS = [
|
|
7
|
+
320,
|
|
8
|
+
640,
|
|
9
|
+
960,
|
|
10
|
+
1280,
|
|
11
|
+
1920
|
|
12
|
+
];
|
|
13
|
+
const DEFAULT_IMAGE_FORMATS = [
|
|
14
|
+
"avif",
|
|
15
|
+
"webp",
|
|
16
|
+
"source"
|
|
17
|
+
];
|
|
18
|
+
const DEFAULT_IMAGE_QUALITY = {
|
|
19
|
+
avif: 50,
|
|
20
|
+
webp: 75,
|
|
21
|
+
jpeg: 82,
|
|
22
|
+
png: 100
|
|
23
|
+
};
|
|
24
|
+
function widths(value) {
|
|
25
|
+
const normalized = [...new Set(value ?? DEFAULT_IMAGE_WIDTHS)].sort((left, right) => left - right);
|
|
26
|
+
if (normalized.length === 0 || normalized.some((width) => !Number.isSafeInteger(width) || width < 1)) throw new Error("@askrjs/vite image widths must be positive integers.");
|
|
27
|
+
return normalized;
|
|
28
|
+
}
|
|
29
|
+
function formats(value) {
|
|
30
|
+
const normalized = [...new Set(value ?? DEFAULT_IMAGE_FORMATS)];
|
|
31
|
+
if (normalized.length === 0 || normalized.some((format) => !DEFAULT_IMAGE_FORMATS.includes(format))) throw new Error("@askrjs/vite image formats must use avif, webp, or source.");
|
|
32
|
+
return normalized;
|
|
33
|
+
}
|
|
34
|
+
function ratio(value) {
|
|
35
|
+
if (value === void 0) return void 0;
|
|
36
|
+
const resolved = typeof value === "number" ? value : value.width / value.height;
|
|
37
|
+
if (!Number.isFinite(resolved) || resolved <= 0) throw new Error("@askrjs/vite image aspectRatio must be a positive finite ratio.");
|
|
38
|
+
return resolved;
|
|
39
|
+
}
|
|
40
|
+
function imageFit(value) {
|
|
41
|
+
const resolved = value ?? "inside";
|
|
42
|
+
if (resolved !== "inside" && resolved !== "cover") throw new Error("@askrjs/vite image fit must be \"inside\" or \"cover\".");
|
|
43
|
+
return resolved;
|
|
44
|
+
}
|
|
45
|
+
function cropPosition(value) {
|
|
46
|
+
if (value === void 0) return void 0;
|
|
47
|
+
if (typeof value !== "string" || value.trim().length === 0) throw new Error("@askrjs/vite image position must be a non-empty string.");
|
|
48
|
+
return value.trim();
|
|
49
|
+
}
|
|
50
|
+
function quality(global, local) {
|
|
51
|
+
const normalized = {
|
|
52
|
+
...DEFAULT_IMAGE_QUALITY,
|
|
53
|
+
...global,
|
|
54
|
+
...local
|
|
55
|
+
};
|
|
56
|
+
for (const [format, value] of Object.entries(normalized)) if (!Number.isSafeInteger(value) || value < 1 || value > 100) throw new Error(`@askrjs/vite ${format} image quality must be an integer from 1 through 100.`);
|
|
57
|
+
return normalized;
|
|
58
|
+
}
|
|
59
|
+
function normalizeImageOptions(global = {}, local = {}) {
|
|
60
|
+
const fit = imageFit(local.fit);
|
|
61
|
+
const aspectRatio = ratio(local.aspectRatio);
|
|
62
|
+
if (fit === "cover" && aspectRatio === void 0) throw new Error("@askrjs/vite image fit \"cover\" requires an aspectRatio.");
|
|
63
|
+
return {
|
|
64
|
+
widths: widths(local.widths ?? global.widths),
|
|
65
|
+
formats: formats(local.formats ?? global.formats),
|
|
66
|
+
quality: quality(global.quality, local.quality),
|
|
67
|
+
fit,
|
|
68
|
+
...aspectRatio === void 0 ? {} : { aspectRatio },
|
|
69
|
+
position: cropPosition(local.position) ?? "centre"
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
function normalizeDeclarationOptions(options = {}) {
|
|
73
|
+
return {
|
|
74
|
+
...options.widths ? { widths: widths(options.widths) } : {},
|
|
75
|
+
...options.formats ? { formats: formats(options.formats) } : {},
|
|
76
|
+
...options.quality ? { quality: Object.fromEntries(Object.entries(options.quality).sort(([a], [b]) => a.localeCompare(b))) } : {},
|
|
77
|
+
...options.fit === void 0 ? {} : { fit: imageFit(options.fit) },
|
|
78
|
+
...options.aspectRatio === void 0 ? {} : { aspectRatio: ratio(options.aspectRatio) },
|
|
79
|
+
...options.position === void 0 ? {} : { position: cropPosition(options.position) }
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
//#endregion
|
|
83
|
+
export { normalizeImageOptions as n, IMAGE_METADATA_PATH as r, normalizeDeclarationOptions as t };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"image-options-B-QnEqPc.js","names":[],"sources":["../src/image-metadata.ts","../src/image-options.ts"],"sourcesContent":["import type { ImageOptions, ResponsiveImage } from \"./image-types\";\n\nexport const IMAGE_METADATA_VERSION = 2;\nexport const IMAGE_METADATA_PATH = \"node_modules/.cache/@askrjs/vite/images/metadata.json\";\n\nexport interface ImageMetadataEntry {\n sourcePath: string;\n sourceHash: string;\n declarationOptions: ImageOptions;\n transformKey: string;\n encoder: string;\n image: ResponsiveImage;\n}\n\nexport interface ImageMetadataRecord {\n version: typeof IMAGE_METADATA_VERSION;\n entries: Record<string, ImageMetadataEntry>;\n}\n","import type {\n ImageFit,\n ImageOptions,\n ImageOutputFormat,\n ImagePipelineOptions,\n NormalizedImageOptions,\n} from \"./image-types\";\n\nexport const DEFAULT_IMAGE_WIDTHS = [320, 640, 960, 1280, 1920] as const;\nexport const DEFAULT_IMAGE_FORMATS = [\"avif\", \"webp\", \"source\"] as const;\nexport const DEFAULT_IMAGE_QUALITY = {\n avif: 50,\n webp: 75,\n jpeg: 82,\n png: 100,\n} as const;\n\nfunction widths(value: readonly number[] | undefined): number[] {\n const normalized = [...new Set(value ?? DEFAULT_IMAGE_WIDTHS)].sort(\n (left, right) => left - right,\n );\n if (\n normalized.length === 0 ||\n normalized.some((width) => !Number.isSafeInteger(width) || width < 1)\n ) {\n throw new Error(\"@askrjs/vite image widths must be positive integers.\");\n }\n return normalized;\n}\n\nfunction formats(value: readonly ImageOutputFormat[] | undefined): ImageOutputFormat[] {\n const normalized = [...new Set(value ?? DEFAULT_IMAGE_FORMATS)];\n if (\n normalized.length === 0 ||\n normalized.some((format) => !DEFAULT_IMAGE_FORMATS.includes(format))\n ) {\n throw new Error(\"@askrjs/vite image formats must use avif, webp, or source.\");\n }\n return normalized;\n}\n\nfunction ratio(value: ImageOptions[\"aspectRatio\"]): number | undefined {\n if (value === undefined) return undefined;\n const resolved = typeof value === \"number\" ? value : value.width / value.height;\n if (!Number.isFinite(resolved) || resolved <= 0) {\n throw new Error(\"@askrjs/vite image aspectRatio must be a positive finite ratio.\");\n }\n return resolved;\n}\n\nfunction imageFit(value: unknown): ImageFit {\n const resolved = value ?? \"inside\";\n if (resolved !== \"inside\" && resolved !== \"cover\") {\n throw new Error('@askrjs/vite image fit must be \"inside\" or \"cover\".');\n }\n return resolved;\n}\n\nfunction cropPosition(value: unknown): string | undefined {\n if (value === undefined) return undefined;\n if (typeof value !== \"string\" || value.trim().length === 0) {\n throw new Error(\"@askrjs/vite image position must be a non-empty string.\");\n }\n return value.trim();\n}\n\nfunction quality(\n global: ImagePipelineOptions[\"quality\"],\n local: ImageOptions[\"quality\"],\n): NormalizedImageOptions[\"quality\"] {\n const normalized = { ...DEFAULT_IMAGE_QUALITY, ...global, ...local };\n for (const [format, value] of Object.entries(normalized)) {\n if (!Number.isSafeInteger(value) || value < 1 || value > 100) {\n throw new Error(\n `@askrjs/vite ${format} image quality must be an integer from 1 through 100.`,\n );\n }\n }\n return normalized;\n}\n\nexport function normalizeImageOptions(\n global: ImagePipelineOptions = {},\n local: ImageOptions = {},\n): NormalizedImageOptions {\n const fit = imageFit(local.fit);\n const aspectRatio = ratio(local.aspectRatio);\n if (fit === \"cover\" && aspectRatio === undefined) {\n throw new Error('@askrjs/vite image fit \"cover\" requires an aspectRatio.');\n }\n return {\n widths: widths(local.widths ?? global.widths),\n formats: formats(local.formats ?? global.formats),\n quality: quality(global.quality, local.quality),\n fit,\n ...(aspectRatio === undefined ? {} : { aspectRatio }),\n position: cropPosition(local.position) ?? \"centre\",\n };\n}\n\nexport function normalizeDeclarationOptions(options: ImageOptions = {}): ImageOptions {\n return {\n ...(options.widths ? { widths: widths(options.widths) } : {}),\n ...(options.formats ? { formats: formats(options.formats) } : {}),\n ...(options.quality\n ? {\n quality: Object.fromEntries(\n Object.entries(options.quality).sort(([a], [b]) => a.localeCompare(b)),\n ),\n }\n : {}),\n ...(options.fit === undefined ? {} : { fit: imageFit(options.fit) }),\n ...(options.aspectRatio === undefined ? {} : { aspectRatio: ratio(options.aspectRatio) }),\n ...(options.position === undefined ? {} : { position: cropPosition(options.position) }),\n };\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
|
|
2
|
+
//#region src/image-types.d.ts
|
|
3
|
+
type ImageOutputFormat = "avif" | "webp" | "source";
|
|
4
|
+
type ImageFit = "inside" | "cover";
|
|
5
|
+
interface ImageQualityOptions {
|
|
6
|
+
avif?: number;
|
|
7
|
+
webp?: number;
|
|
8
|
+
jpeg?: number;
|
|
9
|
+
png?: number;
|
|
10
|
+
}
|
|
11
|
+
interface ImagePipelineOptions {
|
|
12
|
+
widths?: readonly number[];
|
|
13
|
+
formats?: readonly ImageOutputFormat[];
|
|
14
|
+
quality?: ImageQualityOptions;
|
|
15
|
+
}
|
|
16
|
+
interface ImageOptions {
|
|
17
|
+
widths?: readonly number[];
|
|
18
|
+
formats?: readonly ImageOutputFormat[];
|
|
19
|
+
quality?: ImageQualityOptions;
|
|
20
|
+
fit?: ImageFit;
|
|
21
|
+
/** Required output width/height ratio when fit is cover. */
|
|
22
|
+
aspectRatio?: number | {
|
|
23
|
+
width: number;
|
|
24
|
+
height: number;
|
|
25
|
+
};
|
|
26
|
+
/** Sharp-compatible crop position. Defaults to centre. */
|
|
27
|
+
position?: string;
|
|
28
|
+
}
|
|
29
|
+
interface ResponsiveImageSource {
|
|
30
|
+
type: string;
|
|
31
|
+
srcset: string;
|
|
32
|
+
}
|
|
33
|
+
interface ResponsiveImage {
|
|
34
|
+
readonly __askrImage: true;
|
|
35
|
+
readonly src: string;
|
|
36
|
+
readonly srcset?: string;
|
|
37
|
+
readonly width: number;
|
|
38
|
+
readonly height: number;
|
|
39
|
+
readonly sources: readonly ResponsiveImageSource[];
|
|
40
|
+
}
|
|
41
|
+
interface ImageProps {
|
|
42
|
+
image: ResponsiveImage;
|
|
43
|
+
alt: string;
|
|
44
|
+
sizes?: string;
|
|
45
|
+
[attribute: string]: unknown;
|
|
46
|
+
}
|
|
47
|
+
//#endregion
|
|
48
|
+
export { ImageProps as a, ResponsiveImageSource as c, ImagePipelineOptions as i, ImageOptions as n, ImageQualityOptions as o, ImageOutputFormat as r, ResponsiveImage as s, ImageFit as t };
|
package/dist/image.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
|
|
2
|
+
import { a as ImageProps, c as ResponsiveImageSource, i as ImagePipelineOptions, n as ImageOptions, o as ImageQualityOptions, r as ImageOutputFormat, s as ResponsiveImage, t as ImageFit } from "./image-types-BQpLyBLt.js";
|
|
3
|
+
import { jsxs } from "@askrjs/askr/jsx-runtime";
|
|
4
|
+
//#region src/image.d.ts
|
|
5
|
+
/** Declare a source-controlled image for the opt-in Vite image pipeline. */
|
|
6
|
+
declare function image(source: URL, options?: ImageOptions): ResponsiveImage;
|
|
7
|
+
/** @internal Build-time replacement signature. */
|
|
8
|
+
declare function image(source: ResponsiveImage): ResponsiveImage;
|
|
9
|
+
/** Render responsive metadata as accessible picture/source/img markup. */
|
|
10
|
+
declare function Image(props: ImageProps): ReturnType<typeof jsxs>;
|
|
11
|
+
//#endregion
|
|
12
|
+
export { Image, type ImageFit, type ImageOptions, type ImageOutputFormat, type ImagePipelineOptions, type ImageProps, type ImageQualityOptions, type ResponsiveImage, type ResponsiveImageSource, image };
|
package/dist/image.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
|
|
2
|
+
import { jsx, jsxs } from "@askrjs/askr/jsx-runtime";
|
|
3
|
+
//#region src/image.ts
|
|
4
|
+
function isResponsiveImage(value) {
|
|
5
|
+
return Boolean(value && typeof value === "object" && value.__askrImage === true);
|
|
6
|
+
}
|
|
7
|
+
function image(source, _options = {}) {
|
|
8
|
+
if (isResponsiveImage(source)) return Object.freeze(source);
|
|
9
|
+
throw new Error("@askrjs/vite image metadata is unavailable. Enable askr({ images: true }) and run this module through Vite before browser execution.");
|
|
10
|
+
}
|
|
11
|
+
/** Render responsive metadata as accessible picture/source/img markup. */
|
|
12
|
+
function Image(props) {
|
|
13
|
+
const { image: declaration, alt, sizes, ...attributes } = props;
|
|
14
|
+
return jsxs("picture", { children: [...declaration.sources.map((source) => jsx("source", {
|
|
15
|
+
type: source.type,
|
|
16
|
+
srcset: source.srcset,
|
|
17
|
+
...sizes ? { sizes } : {}
|
|
18
|
+
})), jsx("img", {
|
|
19
|
+
...attributes,
|
|
20
|
+
src: declaration.src,
|
|
21
|
+
...declaration.srcset ? { srcset: declaration.srcset } : {},
|
|
22
|
+
...sizes ? { sizes } : {},
|
|
23
|
+
width: declaration.width,
|
|
24
|
+
height: declaration.height,
|
|
25
|
+
alt
|
|
26
|
+
})] });
|
|
27
|
+
}
|
|
28
|
+
//#endregion
|
|
29
|
+
export { Image, image };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"image.js","names":["createElements","createElement"],"sources":["../src/image.ts"],"sourcesContent":["import { jsx as createElement, jsxs as createElements } from \"@askrjs/askr/jsx-runtime\";\nimport type { ImageOptions, ImageProps, ResponsiveImage } from \"./image-types\";\n\nfunction isResponsiveImage(value: unknown): value is ResponsiveImage {\n return Boolean(\n value && typeof value === \"object\" && (value as Partial<ResponsiveImage>).__askrImage === true,\n );\n}\n\n/** Declare a source-controlled image for the opt-in Vite image pipeline. */\nexport function image(source: URL, options?: ImageOptions): ResponsiveImage;\n/** @internal Build-time replacement signature. */\nexport function image(source: ResponsiveImage): ResponsiveImage;\nexport function image(source: URL | ResponsiveImage, _options: ImageOptions = {}): ResponsiveImage {\n if (isResponsiveImage(source)) return Object.freeze(source);\n throw new Error(\n \"@askrjs/vite image metadata is unavailable. Enable askr({ images: true }) and run this module through Vite before browser execution.\",\n );\n}\n\n/** Render responsive metadata as accessible picture/source/img markup. */\nexport function Image(props: ImageProps): ReturnType<typeof createElements> {\n const { image: declaration, alt, sizes, ...attributes } = props;\n return createElements(\"picture\", {\n children: [\n ...declaration.sources.map((source) =>\n createElement(\"source\", {\n type: source.type,\n srcset: source.srcset,\n ...(sizes ? { sizes } : {}),\n }),\n ),\n createElement(\"img\", {\n ...attributes,\n src: declaration.src,\n ...(declaration.srcset ? { srcset: declaration.srcset } : {}),\n ...(sizes ? { sizes } : {}),\n width: declaration.width,\n height: declaration.height,\n alt,\n }),\n ],\n });\n}\n\nexport type {\n ImageFit,\n ImageOptions,\n ImageOutputFormat,\n ImagePipelineOptions,\n ImageProps,\n ImageQualityOptions,\n ResponsiveImage,\n ResponsiveImageSource,\n} from \"./image-types\";\n"],"mappings":";;;AAGA,SAAS,kBAAkB,OAA0C;CACnE,OAAO,QACL,SAAS,OAAO,UAAU,YAAa,MAAmC,gBAAgB,IAC5F;AACF;AAMA,SAAgB,MAAM,QAA+B,WAAyB,CAAC,GAAoB;CACjG,IAAI,kBAAkB,MAAM,GAAG,OAAO,OAAO,OAAO,MAAM;CAC1D,MAAM,IAAI,MACR,sIACF;AACF;;AAGA,SAAgB,MAAM,OAAsD;CAC1E,MAAM,EAAE,OAAO,aAAa,KAAK,OAAO,GAAG,eAAe;CAC1D,OAAOA,KAAe,WAAW,EAC/B,UAAU,CACR,GAAG,YAAY,QAAQ,KAAK,WAC1BC,IAAc,UAAU;EACtB,MAAM,OAAO;EACb,QAAQ,OAAO;EACf,GAAI,QAAQ,EAAE,MAAM,IAAI,CAAC;CAC3B,CAAC,CACH,GACAA,IAAc,OAAO;EACnB,GAAG;EACH,KAAK,YAAY;EACjB,GAAI,YAAY,SAAS,EAAE,QAAQ,YAAY,OAAO,IAAI,CAAC;EAC3D,GAAI,QAAQ,EAAE,MAAM,IAAI,CAAC;EACzB,OAAO,YAAY;EACnB,QAAQ,YAAY;EACpB;CACF,CAAC,CACH,EACF,CAAC;AACH"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
|
|
2
|
+
import { i as ImagePipelineOptions } from "./image-types-BQpLyBLt.js";
|
|
2
3
|
//#region src/jsx-plugin.d.ts
|
|
3
4
|
interface AskrVitePluginOptions {
|
|
4
5
|
transformJsx?: boolean;
|
|
5
6
|
optimizeTemplates?: boolean;
|
|
6
7
|
ssrPrecompile?: boolean;
|
|
8
|
+
/** Opt into declared responsive image transforms. */
|
|
9
|
+
images?: boolean | ImagePipelineOptions;
|
|
7
10
|
}
|
|
8
11
|
/**
|
|
9
12
|
* Portable public plugin identity.
|
|
@@ -17,4 +20,4 @@ interface AskrVitePlugin {
|
|
|
17
20
|
}
|
|
18
21
|
declare function askrVitePlugin(options?: AskrVitePluginOptions): AskrVitePlugin;
|
|
19
22
|
//#endregion
|
|
20
|
-
export { type AskrVitePlugin, type AskrVitePluginOptions, askrVitePlugin as askr, askrVitePlugin, askrVitePlugin as default };
|
|
23
|
+
export { type AskrVitePlugin, type AskrVitePluginOptions, type ImagePipelineOptions, askrVitePlugin as askr, askrVitePlugin, askrVitePlugin as default };
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
|
|
2
|
+
import { n as normalizeImageOptions, r as IMAGE_METADATA_PATH, t as normalizeDeclarationOptions } from "./image-options-B-QnEqPc.js";
|
|
2
3
|
import { createRequire } from "node:module";
|
|
3
|
-
import
|
|
4
|
+
import fs from "node:fs/promises";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
import { createHash } from "node:crypto";
|
|
4
7
|
//#region src/template-optimizer.ts
|
|
5
8
|
function optimizeTemplateOutput(code) {
|
|
6
9
|
const hoists = /* @__PURE__ */ new Map();
|
|
@@ -21,11 +24,568 @@ function optimizeTemplateOutput(code) {
|
|
|
21
24
|
}).join("\n")}\n${optimized}`;
|
|
22
25
|
}
|
|
23
26
|
//#endregion
|
|
27
|
+
//#region src/image-lexical.ts
|
|
28
|
+
const IMAGE_IMPORT = /from\s+["']@askrjs\/vite\/image["']/g;
|
|
29
|
+
function lexicalCodeMask(source) {
|
|
30
|
+
const mask = new Uint8Array(source.length);
|
|
31
|
+
let state = "code";
|
|
32
|
+
let escaped = false;
|
|
33
|
+
for (let index = 0; index < source.length; index += 1) {
|
|
34
|
+
const character = source[index];
|
|
35
|
+
const next = source[index + 1];
|
|
36
|
+
if (state === "code") {
|
|
37
|
+
if (character === "/" && next === "/") {
|
|
38
|
+
state = "line-comment";
|
|
39
|
+
index += 1;
|
|
40
|
+
} else if (character === "/" && next === "*") {
|
|
41
|
+
state = "block-comment";
|
|
42
|
+
index += 1;
|
|
43
|
+
} else if (character === "'") state = "single";
|
|
44
|
+
else if (character === "\"") state = "double";
|
|
45
|
+
else if (character === "`") state = "template";
|
|
46
|
+
else mask[index] = 1;
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
if (state === "line-comment") {
|
|
50
|
+
if (character === "\n" || character === "\r") {
|
|
51
|
+
state = "code";
|
|
52
|
+
mask[index] = 1;
|
|
53
|
+
}
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
if (state === "block-comment") {
|
|
57
|
+
if (character === "*" && next === "/") {
|
|
58
|
+
state = "code";
|
|
59
|
+
index += 1;
|
|
60
|
+
}
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
if (escaped) escaped = false;
|
|
64
|
+
else if (character === "\\") escaped = true;
|
|
65
|
+
else if (state === "single" && character === "'" || state === "double" && character === "\"" || state === "template" && character === "`") state = "code";
|
|
66
|
+
}
|
|
67
|
+
return mask;
|
|
68
|
+
}
|
|
69
|
+
function imageDeclarationMask(code) {
|
|
70
|
+
const mask = lexicalCodeMask(code);
|
|
71
|
+
IMAGE_IMPORT.lastIndex = 0;
|
|
72
|
+
for (let match = IMAGE_IMPORT.exec(code); match; match = IMAGE_IMPORT.exec(code)) if (mask[match.index]) return mask;
|
|
73
|
+
}
|
|
74
|
+
//#endregion
|
|
75
|
+
//#region src/image-inspection.ts
|
|
76
|
+
function positiveDimensions(width, height, format, transformable, animated = false) {
|
|
77
|
+
return width > 0 && height > 0 ? {
|
|
78
|
+
width,
|
|
79
|
+
height,
|
|
80
|
+
format,
|
|
81
|
+
transformable,
|
|
82
|
+
animated
|
|
83
|
+
} : void 0;
|
|
84
|
+
}
|
|
85
|
+
function inspectPng(source) {
|
|
86
|
+
if (source.length < 24 || !source.subarray(0, 8).equals(Buffer.from([
|
|
87
|
+
137,
|
|
88
|
+
80,
|
|
89
|
+
78,
|
|
90
|
+
71,
|
|
91
|
+
13,
|
|
92
|
+
10,
|
|
93
|
+
26,
|
|
94
|
+
10
|
|
95
|
+
]))) return;
|
|
96
|
+
return positiveDimensions(source.readUInt32BE(16), source.readUInt32BE(20), "png", true, source.includes(Buffer.from("acTL")));
|
|
97
|
+
}
|
|
98
|
+
function inspectGif(source) {
|
|
99
|
+
if (source.length < 10 || !/^GIF8[79]a$/.test(source.subarray(0, 6).toString("ascii"))) return;
|
|
100
|
+
return positiveDimensions(source.readUInt16LE(6), source.readUInt16LE(8), "gif", false, true);
|
|
101
|
+
}
|
|
102
|
+
function exifOrientation(segment) {
|
|
103
|
+
if (segment.length < 14 || segment.subarray(0, 6).toString("binary") !== "Exif\0\0") return;
|
|
104
|
+
const tiff = 6;
|
|
105
|
+
const byteOrder = segment.subarray(tiff, 8).toString("ascii");
|
|
106
|
+
const littleEndian = byteOrder === "II";
|
|
107
|
+
if (!littleEndian && byteOrder !== "MM") return void 0;
|
|
108
|
+
const read16 = (offset) => {
|
|
109
|
+
const absolute = tiff + offset;
|
|
110
|
+
if (absolute < tiff || absolute + 2 > segment.length) return void 0;
|
|
111
|
+
return littleEndian ? segment.readUInt16LE(absolute) : segment.readUInt16BE(absolute);
|
|
112
|
+
};
|
|
113
|
+
const read32 = (offset) => {
|
|
114
|
+
const absolute = tiff + offset;
|
|
115
|
+
if (absolute < tiff || absolute + 4 > segment.length) return void 0;
|
|
116
|
+
return littleEndian ? segment.readUInt32LE(absolute) : segment.readUInt32BE(absolute);
|
|
117
|
+
};
|
|
118
|
+
if (read16(2) !== 42) return void 0;
|
|
119
|
+
const directoryOffset = read32(4);
|
|
120
|
+
if (directoryOffset === void 0) return void 0;
|
|
121
|
+
const entries = read16(directoryOffset);
|
|
122
|
+
if (entries === void 0) return void 0;
|
|
123
|
+
for (let index = 0; index < entries; index += 1) {
|
|
124
|
+
const entry = directoryOffset + 2 + index * 12;
|
|
125
|
+
if (read16(entry) !== 274 || read16(entry + 2) !== 3 || read32(entry + 4) !== 1) continue;
|
|
126
|
+
const orientation = read16(entry + 8);
|
|
127
|
+
return orientation && orientation >= 1 && orientation <= 8 ? orientation : void 0;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
function inspectJpeg(source) {
|
|
131
|
+
if (source.length < 4 || source[0] !== 255 || source[1] !== 216) return void 0;
|
|
132
|
+
const startOfFrame = /* @__PURE__ */ new Set([
|
|
133
|
+
192,
|
|
134
|
+
193,
|
|
135
|
+
194,
|
|
136
|
+
195,
|
|
137
|
+
197,
|
|
138
|
+
198,
|
|
139
|
+
199,
|
|
140
|
+
201,
|
|
141
|
+
202,
|
|
142
|
+
203,
|
|
143
|
+
205,
|
|
144
|
+
206,
|
|
145
|
+
207
|
|
146
|
+
]);
|
|
147
|
+
let orientation;
|
|
148
|
+
let offset = 2;
|
|
149
|
+
while (offset + 8 < source.length) {
|
|
150
|
+
if (source[offset] !== 255) {
|
|
151
|
+
offset += 1;
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
while (source[offset] === 255) offset += 1;
|
|
155
|
+
const marker = source[offset++];
|
|
156
|
+
if (marker === 216 || marker === 217 || marker === 1) continue;
|
|
157
|
+
if (marker >= 208 && marker <= 215) continue;
|
|
158
|
+
if (offset + 2 > source.length) break;
|
|
159
|
+
const length = source.readUInt16BE(offset);
|
|
160
|
+
if (length < 2 || offset + length > source.length) break;
|
|
161
|
+
if (marker === 225) orientation = exifOrientation(source.subarray(offset + 2, offset + length)) ?? orientation;
|
|
162
|
+
if (startOfFrame.has(marker) && length >= 7) {
|
|
163
|
+
const width = source.readUInt16BE(offset + 5);
|
|
164
|
+
const height = source.readUInt16BE(offset + 3);
|
|
165
|
+
return positiveDimensions(orientation !== void 0 && orientation >= 5 ? height : width, orientation !== void 0 && orientation >= 5 ? width : height, "jpeg", true);
|
|
166
|
+
}
|
|
167
|
+
offset += length;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
function inspectSvg(source) {
|
|
171
|
+
const openingTag = source.subarray(0, 16384).toString("utf8").match(/<svg\b[^>]*>/i)?.[0];
|
|
172
|
+
if (!openingTag) return void 0;
|
|
173
|
+
const numberAttribute = (name) => {
|
|
174
|
+
const value = openingTag.match(new RegExp(`\\b${name}\\s*=\\s*["']\\s*([0-9.]+)`, "i"))?.[1];
|
|
175
|
+
const parsed = value ? Number(value) : NaN;
|
|
176
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : void 0;
|
|
177
|
+
};
|
|
178
|
+
let width = numberAttribute("width");
|
|
179
|
+
let height = numberAttribute("height");
|
|
180
|
+
const viewBox = openingTag.match(/\bviewBox\s*=\s*["']\s*[-+0-9.e]+[ ,]+[-+0-9.e]+[ ,]+([-+0-9.e]+)[ ,]+([-+0-9.e]+)/i)?.slice(1).map(Number);
|
|
181
|
+
if (!width && viewBox?.[0] && viewBox[0] > 0) width = viewBox[0];
|
|
182
|
+
if (!height && viewBox?.[1] && viewBox[1] > 0) height = viewBox[1];
|
|
183
|
+
if (!width && height && viewBox?.[0] && viewBox?.[1]) width = height * (viewBox[0] / viewBox[1]);
|
|
184
|
+
if (!height && width && viewBox?.[0] && viewBox?.[1]) height = width / (viewBox[0] / viewBox[1]);
|
|
185
|
+
return width && height ? positiveDimensions(width, height, "svg", false) : void 0;
|
|
186
|
+
}
|
|
187
|
+
function inspectWebp(source) {
|
|
188
|
+
if (source.length < 30 || source.subarray(0, 4).toString("ascii") !== "RIFF" || source.subarray(8, 12).toString("ascii") !== "WEBP") return;
|
|
189
|
+
const chunk = source.subarray(12, 16).toString("ascii");
|
|
190
|
+
if (chunk === "VP8X") return positiveDimensions(1 + source.readUIntLE(24, 3), 1 + source.readUIntLE(27, 3), "webp", false, Boolean(source[20] & 2));
|
|
191
|
+
if (chunk === "VP8 ") return positiveDimensions(source.readUInt16LE(26) & 16383, source.readUInt16LE(28) & 16383, "webp", false);
|
|
192
|
+
if (chunk === "VP8L" && source[20] === 47) {
|
|
193
|
+
const bits = source.readUInt32LE(21);
|
|
194
|
+
return positiveDimensions(1 + (bits & 16383), 1 + (bits >>> 14 & 16383), "webp", false);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
function inspectAvif(source) {
|
|
198
|
+
if (source.length < 24 || source.subarray(4, 8).toString("ascii") !== "ftyp") return void 0;
|
|
199
|
+
const typeOffset = source.indexOf(Buffer.from("ispe"));
|
|
200
|
+
if (typeOffset < 0 || typeOffset + 16 > source.length) return void 0;
|
|
201
|
+
return positiveDimensions(source.readUInt32BE(typeOffset + 8), source.readUInt32BE(typeOffset + 12), "avif", false);
|
|
202
|
+
}
|
|
203
|
+
function inspectSource(sourcePath, source) {
|
|
204
|
+
const inspection = inspectPng(source) ?? inspectGif(source) ?? inspectJpeg(source) ?? inspectSvg(source) ?? inspectWebp(source) ?? inspectAvif(source);
|
|
205
|
+
if (!inspection) throw new Error(`@askrjs/vite could not determine intrinsic dimensions for pass-through image ${sourcePath}.`);
|
|
206
|
+
return inspection;
|
|
207
|
+
}
|
|
208
|
+
//#endregion
|
|
209
|
+
//#region src/image-transform.ts
|
|
210
|
+
function hash(value) {
|
|
211
|
+
return createHash("sha256").update(value).digest("hex");
|
|
212
|
+
}
|
|
213
|
+
function declarationKey(sourcePath, options) {
|
|
214
|
+
return hash(`${sourcePath}\0${JSON.stringify(normalizeDeclarationOptions(options))}`);
|
|
215
|
+
}
|
|
216
|
+
const defaultSharpLoader = async () => (await import("sharp")).default;
|
|
217
|
+
async function loadSharp(loader) {
|
|
218
|
+
try {
|
|
219
|
+
return await loader();
|
|
220
|
+
} catch (error) {
|
|
221
|
+
if (error.code === "ERR_MODULE_NOT_FOUND") throw new Error("@askrjs/vite responsive images require the optional peer \"sharp\". Install sharp@^0.35.3 and rebuild.", { cause: error });
|
|
222
|
+
throw error;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
function sourceFormat(format) {
|
|
226
|
+
if (format === "jpeg" || format === "jpg") return "jpeg";
|
|
227
|
+
return format === "png" ? "png" : void 0;
|
|
228
|
+
}
|
|
229
|
+
function extension(format) {
|
|
230
|
+
return format === "jpeg" ? "jpg" : format;
|
|
231
|
+
}
|
|
232
|
+
function mime$1(format) {
|
|
233
|
+
return format === "jpeg" ? "image/jpeg" : `image/${format}`;
|
|
234
|
+
}
|
|
235
|
+
function outputName(sourcePath, width, format) {
|
|
236
|
+
return `${path.basename(sourcePath, path.extname(sourcePath))}-${width}.${extension(format)}`;
|
|
237
|
+
}
|
|
238
|
+
function outputHeight(width, sourceWidth, sourceHeight, aspectRatio) {
|
|
239
|
+
return Math.max(1, Math.round(width / (aspectRatio ?? sourceWidth / sourceHeight)));
|
|
240
|
+
}
|
|
241
|
+
async function encodedVariant(sharp, source, cacheDir, transformKey, width, height, format, options) {
|
|
242
|
+
const cachePath = path.join(cacheDir, `${transformKey}-${width}.${extension(format)}`);
|
|
243
|
+
const cached = await fs.readFile(cachePath).catch(() => void 0);
|
|
244
|
+
if (cached) return cached;
|
|
245
|
+
let pipeline = sharp(source, { animated: false }).autoOrient();
|
|
246
|
+
pipeline = options.fit === "cover" ? pipeline.resize({
|
|
247
|
+
width,
|
|
248
|
+
height,
|
|
249
|
+
fit: "cover",
|
|
250
|
+
position: options.position
|
|
251
|
+
}) : pipeline.resize({
|
|
252
|
+
width,
|
|
253
|
+
fit: "inside",
|
|
254
|
+
withoutEnlargement: true
|
|
255
|
+
});
|
|
256
|
+
if (format === "avif") pipeline = pipeline.avif({ quality: options.quality.avif });
|
|
257
|
+
else if (format === "webp") pipeline = pipeline.webp({ quality: options.quality.webp });
|
|
258
|
+
else if (format === "jpeg") pipeline = pipeline.jpeg({ quality: options.quality.jpeg });
|
|
259
|
+
else pipeline = pipeline.png(options.quality.png === 100 ? { compressionLevel: 9 } : {
|
|
260
|
+
compressionLevel: 9,
|
|
261
|
+
quality: options.quality.png
|
|
262
|
+
});
|
|
263
|
+
const encoded = await pipeline.toBuffer();
|
|
264
|
+
await fs.mkdir(cacheDir, { recursive: true });
|
|
265
|
+
await fs.writeFile(cachePath, encoded);
|
|
266
|
+
return encoded;
|
|
267
|
+
}
|
|
268
|
+
function variantFormats(formats, fallback) {
|
|
269
|
+
const output = [];
|
|
270
|
+
for (const format of formats) {
|
|
271
|
+
const resolved = format === "source" ? fallback : format;
|
|
272
|
+
if (!output.includes(resolved)) output.push(resolved);
|
|
273
|
+
}
|
|
274
|
+
if (!output.includes(fallback)) output.push(fallback);
|
|
275
|
+
return output;
|
|
276
|
+
}
|
|
277
|
+
function passThroughImage(context, sourcePath, source, width, height, format) {
|
|
278
|
+
const fallback = {
|
|
279
|
+
referenceId: context.emitFile({
|
|
280
|
+
type: "asset",
|
|
281
|
+
name: path.basename(sourcePath),
|
|
282
|
+
source
|
|
283
|
+
}),
|
|
284
|
+
width,
|
|
285
|
+
height,
|
|
286
|
+
format,
|
|
287
|
+
mime: mime$1(format)
|
|
288
|
+
};
|
|
289
|
+
return {
|
|
290
|
+
fallback,
|
|
291
|
+
variants: [fallback]
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
async function processImage(context, sourcePath, declarationOptions, globalOptions, cacheDir, sharpLoader) {
|
|
295
|
+
const source = await fs.readFile(sourcePath);
|
|
296
|
+
const sourceHash = hash(source);
|
|
297
|
+
const normalized = normalizeImageOptions(globalOptions, declarationOptions);
|
|
298
|
+
const inspection = inspectSource(sourcePath, source);
|
|
299
|
+
const key = declarationKey(sourcePath, declarationOptions);
|
|
300
|
+
if (!inspection.transformable || inspection.animated || normalized.fit === "inside" && inspection.width <= normalized.widths[0]) {
|
|
301
|
+
const encoder = "passthrough";
|
|
302
|
+
return {
|
|
303
|
+
declarationKey: key,
|
|
304
|
+
sourcePath,
|
|
305
|
+
sourceHash,
|
|
306
|
+
declarationOptions: normalizeDeclarationOptions(declarationOptions),
|
|
307
|
+
transformKey: hash(`${sourceHash}\0${JSON.stringify(normalized)}\0${encoder}`),
|
|
308
|
+
encoder,
|
|
309
|
+
...passThroughImage(context, sourcePath, source, inspection.width, inspection.height, inspection.format),
|
|
310
|
+
passThrough: true
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
const sharp = await loadSharp(sharpLoader);
|
|
314
|
+
const metadata = await sharp(source, { animated: true }).metadata();
|
|
315
|
+
const sourceWidth = metadata.autoOrient.width ?? metadata.width;
|
|
316
|
+
const sourceHeight = (metadata.pages ?? 1) > 1 ? metadata.pageHeight ?? metadata.height : metadata.autoOrient.height ?? metadata.height;
|
|
317
|
+
if (!sourceWidth || !sourceHeight) throw new Error(`@askrjs/vite could not determine intrinsic dimensions for ${sourcePath}.`);
|
|
318
|
+
const encoder = `sharp@${sharp.versions.sharp}`;
|
|
319
|
+
const transformKey = hash(`${sourceHash}\0${JSON.stringify(normalized)}\0${encoder}`);
|
|
320
|
+
const fallbackFormat = sourceFormat(metadata.format);
|
|
321
|
+
if ((metadata.pages ?? 1) > 1 || !fallbackFormat) return {
|
|
322
|
+
declarationKey: key,
|
|
323
|
+
sourcePath,
|
|
324
|
+
sourceHash,
|
|
325
|
+
declarationOptions: normalizeDeclarationOptions(declarationOptions),
|
|
326
|
+
transformKey,
|
|
327
|
+
encoder,
|
|
328
|
+
...passThroughImage(context, sourcePath, source, sourceWidth, sourceHeight, fallbackFormat ?? inspection.format),
|
|
329
|
+
passThrough: true
|
|
330
|
+
};
|
|
331
|
+
const maximumWidth = normalized.fit === "cover" ? Math.min(sourceWidth, Math.floor(sourceHeight * normalized.aspectRatio)) : sourceWidth;
|
|
332
|
+
if (maximumWidth < 1) throw new Error(`@askrjs/vite cannot crop ${sourcePath} to aspect ratio ${normalized.aspectRatio} without upscaling.`);
|
|
333
|
+
const widths = normalized.widths.filter((width) => width <= maximumWidth);
|
|
334
|
+
if (!widths.includes(maximumWidth)) widths.push(maximumWidth);
|
|
335
|
+
widths.sort((left, right) => left - right);
|
|
336
|
+
const variants = [];
|
|
337
|
+
for (const format of variantFormats(normalized.formats, fallbackFormat)) for (const width of widths) {
|
|
338
|
+
const height = outputHeight(width, sourceWidth, sourceHeight, normalized.fit === "cover" ? normalized.aspectRatio : void 0);
|
|
339
|
+
const buffer = await encodedVariant(sharp, source, cacheDir, transformKey, width, height, format, normalized);
|
|
340
|
+
variants.push({
|
|
341
|
+
referenceId: context.emitFile({
|
|
342
|
+
type: "asset",
|
|
343
|
+
name: outputName(sourcePath, width, format),
|
|
344
|
+
source: buffer
|
|
345
|
+
}),
|
|
346
|
+
width,
|
|
347
|
+
height,
|
|
348
|
+
format,
|
|
349
|
+
mime: mime$1(format)
|
|
350
|
+
});
|
|
351
|
+
}
|
|
352
|
+
return {
|
|
353
|
+
declarationKey: key,
|
|
354
|
+
sourcePath,
|
|
355
|
+
sourceHash,
|
|
356
|
+
declarationOptions: normalizeDeclarationOptions(declarationOptions),
|
|
357
|
+
transformKey,
|
|
358
|
+
encoder,
|
|
359
|
+
fallback: variants.filter((variant) => variant.format === fallbackFormat).at(-1),
|
|
360
|
+
variants,
|
|
361
|
+
passThrough: false
|
|
362
|
+
};
|
|
363
|
+
}
|
|
364
|
+
//#endregion
|
|
365
|
+
//#region src/image-pipeline.ts
|
|
366
|
+
const IMAGE_CALL = /\bimage\s*\(\s*new\s+URL\s*\(\s*(["'])([^"']+)\1\s*,\s*import\.meta\.url\s*\)/g;
|
|
367
|
+
function staticJson(source, id) {
|
|
368
|
+
let output = "";
|
|
369
|
+
for (let index = 0; index < source.length;) {
|
|
370
|
+
const character = source[index];
|
|
371
|
+
if (/\s/.test(character) || "{}[]:".includes(character)) {
|
|
372
|
+
output += character;
|
|
373
|
+
index += 1;
|
|
374
|
+
continue;
|
|
375
|
+
}
|
|
376
|
+
if (character === ",") {
|
|
377
|
+
let next = index + 1;
|
|
378
|
+
while (/\s/.test(source[next] ?? "")) next += 1;
|
|
379
|
+
if (source[next] !== "}" && source[next] !== "]") output += character;
|
|
380
|
+
index += 1;
|
|
381
|
+
continue;
|
|
382
|
+
}
|
|
383
|
+
if (character === "\"" || character === "'") {
|
|
384
|
+
let end = index + 1;
|
|
385
|
+
while (end < source.length && source[end] !== character) {
|
|
386
|
+
if (source[end] === "\\") throw new Error(`@askrjs/vite image() option strings in ${id} cannot contain escape sequences.`);
|
|
387
|
+
end += 1;
|
|
388
|
+
}
|
|
389
|
+
if (end >= source.length) throw new Error(`@askrjs/vite found an unterminated image option in ${id}.`);
|
|
390
|
+
output += JSON.stringify(source.slice(index + 1, end));
|
|
391
|
+
index = end + 1;
|
|
392
|
+
continue;
|
|
393
|
+
}
|
|
394
|
+
const number = /^-?(?:0|[1-9]\d*)(?:\.\d+)?(?:e[+-]?\d+)?/i.exec(source.slice(index));
|
|
395
|
+
if (number) {
|
|
396
|
+
output += number[0];
|
|
397
|
+
index += number[0].length;
|
|
398
|
+
continue;
|
|
399
|
+
}
|
|
400
|
+
const identifier = /^[A-Za-z_]\w*/.exec(source.slice(index));
|
|
401
|
+
if (identifier) {
|
|
402
|
+
let next = index + identifier[0].length;
|
|
403
|
+
while (/\s/.test(source[next] ?? "")) next += 1;
|
|
404
|
+
if (source[next] === ":") output += JSON.stringify(identifier[0]);
|
|
405
|
+
else if ([
|
|
406
|
+
"true",
|
|
407
|
+
"false",
|
|
408
|
+
"null"
|
|
409
|
+
].includes(identifier[0])) output += identifier[0];
|
|
410
|
+
else throw new Error(`@askrjs/vite image() options in ${id} must not reference ${identifier[0]}; use a static object literal.`);
|
|
411
|
+
index += identifier[0].length;
|
|
412
|
+
continue;
|
|
413
|
+
}
|
|
414
|
+
throw new Error(`@askrjs/vite image() options in ${id} must be a static object literal so builds and direct SSG imports agree.`);
|
|
415
|
+
}
|
|
416
|
+
return output;
|
|
417
|
+
}
|
|
418
|
+
function staticOptions(source, id) {
|
|
419
|
+
const trimmed = source.trim();
|
|
420
|
+
if (!trimmed) return {};
|
|
421
|
+
let value;
|
|
422
|
+
try {
|
|
423
|
+
value = JSON.parse(staticJson(trimmed, id));
|
|
424
|
+
} catch (error) {
|
|
425
|
+
if (error instanceof Error && error.message.startsWith("@askrjs/vite")) throw error;
|
|
426
|
+
throw new Error(`@askrjs/vite could not parse image() options in ${id}.`, { cause: error });
|
|
427
|
+
}
|
|
428
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error(`@askrjs/vite image() options in ${id} must be an object literal.`);
|
|
429
|
+
return value;
|
|
430
|
+
}
|
|
431
|
+
function findCallEnd(code, start) {
|
|
432
|
+
let depth = 1;
|
|
433
|
+
let quote = "";
|
|
434
|
+
let escaped = false;
|
|
435
|
+
for (let index = start; index < code.length; index += 1) {
|
|
436
|
+
const character = code[index];
|
|
437
|
+
if (quote) {
|
|
438
|
+
if (escaped) escaped = false;
|
|
439
|
+
else if (character === "\\") escaped = true;
|
|
440
|
+
else if (character === quote) quote = "";
|
|
441
|
+
continue;
|
|
442
|
+
}
|
|
443
|
+
if (character === "\"" || character === "'") {
|
|
444
|
+
quote = character;
|
|
445
|
+
continue;
|
|
446
|
+
}
|
|
447
|
+
if (character === "(") depth += 1;
|
|
448
|
+
if (character === ")") {
|
|
449
|
+
depth -= 1;
|
|
450
|
+
if (depth === 0) return index;
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
return -1;
|
|
454
|
+
}
|
|
455
|
+
function declarations(code, id) {
|
|
456
|
+
const mask = imageDeclarationMask(code);
|
|
457
|
+
if (!mask) return [];
|
|
458
|
+
const output = [];
|
|
459
|
+
IMAGE_CALL.lastIndex = 0;
|
|
460
|
+
for (let match = IMAGE_CALL.exec(code); match; match = IMAGE_CALL.exec(code)) {
|
|
461
|
+
if (!mask[match.index]) continue;
|
|
462
|
+
const afterUrl = IMAGE_CALL.lastIndex;
|
|
463
|
+
const end = findCallEnd(code, afterUrl);
|
|
464
|
+
if (end < 0) throw new Error(`@askrjs/vite found an unterminated image() call in ${id}.`);
|
|
465
|
+
const remainder = code.slice(afterUrl, end).trim();
|
|
466
|
+
if (remainder && !remainder.startsWith(",")) throw new Error(`@askrjs/vite could not parse image() declaration in ${id}.`);
|
|
467
|
+
output.push({
|
|
468
|
+
start: match.index,
|
|
469
|
+
end: end + 1,
|
|
470
|
+
source: match[2],
|
|
471
|
+
options: staticOptions(remainder.replace(/^,/, ""), id)
|
|
472
|
+
});
|
|
473
|
+
IMAGE_CALL.lastIndex = end + 1;
|
|
474
|
+
}
|
|
475
|
+
return output;
|
|
476
|
+
}
|
|
477
|
+
function urlExpression(referenceId) {
|
|
478
|
+
return `import.meta.ROLLUP_FILE_URL_${referenceId}`;
|
|
479
|
+
}
|
|
480
|
+
function mime(format) {
|
|
481
|
+
return format === "jpeg" ? "image/jpeg" : `image/${format}`;
|
|
482
|
+
}
|
|
483
|
+
function srcsetExpression(variants) {
|
|
484
|
+
return `[${variants.map((variant) => `${urlExpression(variant.referenceId)} + ${JSON.stringify(` ${variant.width}w`)}`).join(",")}].join(", ")`;
|
|
485
|
+
}
|
|
486
|
+
function imageExpression(processed) {
|
|
487
|
+
const fallbackVariants = processed.variants.filter((variant) => variant.format === processed.fallback.format);
|
|
488
|
+
const sources = processed.passThrough ? [] : ["avif", "webp"].flatMap((format) => {
|
|
489
|
+
const variants = processed.variants.filter((variant) => variant.format === format);
|
|
490
|
+
return variants.length === 0 ? [] : [`{type:${JSON.stringify(mime(format))},srcset:${srcsetExpression(variants)}}`];
|
|
491
|
+
});
|
|
492
|
+
return `image({__askrImage:true,src:${urlExpression(processed.fallback.referenceId)},${processed.passThrough ? "" : `srcset:${srcsetExpression(fallbackVariants)},`}width:${processed.fallback.width},height:${processed.fallback.height},sources:[${sources.join(",")}]})`;
|
|
493
|
+
}
|
|
494
|
+
function builtUrl(base, fileName) {
|
|
495
|
+
if (/^https?:\/\//.test(base)) return new URL(fileName, base.endsWith("/") ? base : `${base}/`).href;
|
|
496
|
+
if (base === "" || base === "./" || base === ".") return `${base === "." ? "./" : base}${fileName}`;
|
|
497
|
+
return `${base.endsWith("/") ? base : `${base}/`}${fileName}`;
|
|
498
|
+
}
|
|
499
|
+
function responsiveMetadata(processed, fileName, base) {
|
|
500
|
+
const url = (variant) => builtUrl(base, fileName(variant.referenceId));
|
|
501
|
+
const srcset = (variants) => variants.map((variant) => `${url(variant)} ${variant.width}w`).join(", ");
|
|
502
|
+
const fallbackVariants = processed.variants.filter((variant) => variant.format === processed.fallback.format);
|
|
503
|
+
return {
|
|
504
|
+
__askrImage: true,
|
|
505
|
+
src: url(processed.fallback),
|
|
506
|
+
...processed.passThrough ? {} : { srcset: srcset(fallbackVariants) },
|
|
507
|
+
width: processed.fallback.width,
|
|
508
|
+
height: processed.fallback.height,
|
|
509
|
+
sources: processed.passThrough ? [] : ["avif", "webp"].flatMap((format) => {
|
|
510
|
+
const variants = processed.variants.filter((variant) => variant.format === format);
|
|
511
|
+
return variants.length === 0 ? [] : [{
|
|
512
|
+
type: mime(format),
|
|
513
|
+
srcset: srcset(variants)
|
|
514
|
+
}];
|
|
515
|
+
})
|
|
516
|
+
};
|
|
517
|
+
}
|
|
518
|
+
var ImagePipeline = class {
|
|
519
|
+
#options;
|
|
520
|
+
#processed = /* @__PURE__ */ new Map();
|
|
521
|
+
#sharpLoader;
|
|
522
|
+
#config;
|
|
523
|
+
constructor(options, sharpLoader = defaultSharpLoader) {
|
|
524
|
+
this.#options = options;
|
|
525
|
+
this.#sharpLoader = sharpLoader;
|
|
526
|
+
}
|
|
527
|
+
configure(config) {
|
|
528
|
+
this.#config = config;
|
|
529
|
+
}
|
|
530
|
+
async transform(context, code, id) {
|
|
531
|
+
const found = declarations(code, id);
|
|
532
|
+
if (found.length === 0) return void 0;
|
|
533
|
+
if (!this.#config) throw new Error("@askrjs/vite image pipeline was not configured.");
|
|
534
|
+
if (this.#config.command !== "build") throw new Error("@askrjs/vite responsive image declarations currently require a Vite build.");
|
|
535
|
+
const cacheDir = path.join(this.#config.root, "node_modules/.cache/@askrjs/vite/images");
|
|
536
|
+
let transformed = code;
|
|
537
|
+
for (const declaration of [...found].reverse()) {
|
|
538
|
+
const sourcePath = path.resolve(path.dirname(id.split("?", 1)[0]), declaration.source);
|
|
539
|
+
const key = declarationKey(sourcePath, declaration.options);
|
|
540
|
+
let processed = this.#processed.get(key);
|
|
541
|
+
if (!processed) {
|
|
542
|
+
processed = await processImage(context, sourcePath, declaration.options, this.#options, cacheDir, this.#sharpLoader);
|
|
543
|
+
this.#processed.set(key, processed);
|
|
544
|
+
}
|
|
545
|
+
transformed = `${transformed.slice(0, declaration.start)}${imageExpression(processed)}${transformed.slice(declaration.end)}`;
|
|
546
|
+
}
|
|
547
|
+
return transformed;
|
|
548
|
+
}
|
|
549
|
+
async writeMetadata(context) {
|
|
550
|
+
if (!this.#config || this.#processed.size === 0) return;
|
|
551
|
+
const entries = {};
|
|
552
|
+
for (const processed of [...this.#processed.values()].sort((left, right) => left.declarationKey.localeCompare(right.declarationKey))) entries[processed.declarationKey] = {
|
|
553
|
+
sourcePath: processed.sourcePath,
|
|
554
|
+
sourceHash: processed.sourceHash,
|
|
555
|
+
declarationOptions: processed.declarationOptions,
|
|
556
|
+
transformKey: processed.transformKey,
|
|
557
|
+
encoder: processed.encoder,
|
|
558
|
+
image: responsiveMetadata(processed, (referenceId) => context.getFileName(referenceId), this.#config.base)
|
|
559
|
+
};
|
|
560
|
+
const record = {
|
|
561
|
+
version: 2,
|
|
562
|
+
entries
|
|
563
|
+
};
|
|
564
|
+
const metadataPath = path.join(this.#config.root, IMAGE_METADATA_PATH);
|
|
565
|
+
await fs.mkdir(path.dirname(metadataPath), { recursive: true });
|
|
566
|
+
await fs.writeFile(metadataPath, `${JSON.stringify(record, null, 2)}\n`, "utf8");
|
|
567
|
+
}
|
|
568
|
+
};
|
|
569
|
+
//#endregion
|
|
24
570
|
//#region src/jsx-plugin.ts
|
|
25
571
|
const require = createRequire(import.meta.url);
|
|
572
|
+
let transformWithOxcPromise;
|
|
573
|
+
async function loadTransformWithOxc() {
|
|
574
|
+
transformWithOxcPromise ??= (async () => {
|
|
575
|
+
for (const packageName of ["vite-plus", "vite"]) try {
|
|
576
|
+
const buildTool = await import(packageName);
|
|
577
|
+
if (typeof buildTool.transformWithOxc === "function") return buildTool.transformWithOxc;
|
|
578
|
+
} catch (error) {
|
|
579
|
+
if (error.code !== "ERR_MODULE_NOT_FOUND") throw error;
|
|
580
|
+
}
|
|
581
|
+
throw new Error("@askrjs/vite requires either vite or vite-plus to transform JSX.");
|
|
582
|
+
})();
|
|
583
|
+
return transformWithOxcPromise;
|
|
584
|
+
}
|
|
26
585
|
function askrVitePlugin(options = {}) {
|
|
27
586
|
const shouldTransform = options.transformJsx ?? true;
|
|
28
587
|
const shouldOptimizeTemplates = options.optimizeTemplates ?? false;
|
|
588
|
+
const imagePipeline = options.images ? new ImagePipeline(options.images === true ? {} : options.images) : void 0;
|
|
29
589
|
return {
|
|
30
590
|
name: "askr:vite",
|
|
31
591
|
enforce: "pre",
|
|
@@ -71,10 +631,19 @@ function askrVitePlugin(options = {}) {
|
|
|
71
631
|
}
|
|
72
632
|
};
|
|
73
633
|
},
|
|
634
|
+
configResolved(config) {
|
|
635
|
+
imagePipeline?.configure(config);
|
|
636
|
+
},
|
|
74
637
|
async transform(code, id) {
|
|
75
|
-
if (
|
|
638
|
+
if (id.includes("node_modules")) return null;
|
|
639
|
+
const imageTransformed = imagePipeline ? await imagePipeline.transform(this, code, id) : void 0;
|
|
640
|
+
const input = imageTransformed ?? code;
|
|
641
|
+
if (!shouldTransform || !/\.(jsx|tsx)$/.test(id)) return imageTransformed ? {
|
|
642
|
+
code: imageTransformed,
|
|
643
|
+
map: null
|
|
644
|
+
} : null;
|
|
76
645
|
try {
|
|
77
|
-
const result = await
|
|
646
|
+
const result = await (await loadTransformWithOxc())(input, id, {
|
|
78
647
|
lang: id.endsWith(".tsx") ? "tsx" : "jsx",
|
|
79
648
|
jsx: {
|
|
80
649
|
runtime: "automatic",
|
|
@@ -88,8 +657,14 @@ function askrVitePlugin(options = {}) {
|
|
|
88
657
|
map: result.map
|
|
89
658
|
};
|
|
90
659
|
} catch {
|
|
91
|
-
return
|
|
660
|
+
return imageTransformed ? {
|
|
661
|
+
code: imageTransformed,
|
|
662
|
+
map: null
|
|
663
|
+
} : null;
|
|
92
664
|
}
|
|
665
|
+
},
|
|
666
|
+
async writeBundle() {
|
|
667
|
+
await imagePipeline?.writeMetadata(this);
|
|
93
668
|
}
|
|
94
669
|
};
|
|
95
670
|
}
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../src/template-optimizer.ts","../src/jsx-plugin.ts"],"sourcesContent":["export function optimizeTemplateOutput(code: string): string {\n const hoists = new Map<string, string>();\n let hoistIndex = 0;\n const optimized = code.replace(\n /\\b(class|className|style):\\s*(\"([^\"\\\\]|\\\\.)*\")/g,\n (fullMatch: string, key: string, literal: string) => {\n const cacheKey = `${key}:${literal}`;\n let identifier = hoists.get(cacheKey);\n if (!identifier) {\n if (code.split(fullMatch).length - 1 < 2) return fullMatch;\n identifier = `__askrStaticLiteral${hoistIndex++}`;\n hoists.set(cacheKey, identifier);\n }\n return `${key}: ${identifier}`;\n },\n );\n if (hoists.size === 0) return code;\n const declarations = Array.from(hoists.entries()).map(([cacheKey, name]) => {\n const literal = cacheKey.slice(cacheKey.indexOf(\":\") + 1);\n return `const ${name} = ${literal};`;\n });\n return `${declarations.join(\"\\n\")}\\n${optimized}`;\n}\n","import { createRequire } from \"node:module\";\nimport { transformWithOxc, type Plugin } from \"vite\";\nimport { optimizeTemplateOutput } from \"./template-optimizer\";\n\nconst require = createRequire(import.meta.url);\n\nexport interface AskrVitePluginOptions {\n transformJsx?: boolean;\n optimizeTemplates?: boolean;\n ssrPrecompile?: boolean;\n}\n\n/**\n * Portable public plugin identity.\n *\n * Vite-compatible runners only require the plugin name. Keeping versioned Vite\n * hook types out of the installed declaration prevents consumers such as\n * vite-plus from comparing two distinct copies of Vite's recursive Plugin type.\n */\nexport interface AskrVitePlugin {\n readonly name: \"askr:vite\";\n}\n\nexport function askrVitePlugin(options: AskrVitePluginOptions = {}): AskrVitePlugin {\n const shouldTransform = options.transformJsx ?? true;\n const shouldOptimizeTemplates = options.optimizeTemplates ?? false;\n const plugin = {\n name: \"askr:vite\" as const,\n enforce: \"pre\",\n config() {\n let askrEntry: string | null = null;\n try {\n askrEntry = require.resolve(\"@askrjs/askr\");\n } catch {\n askrEntry = null;\n }\n return {\n define: {\n __ASKR_OPTIMIZE_TEMPLATES__: JSON.stringify(shouldOptimizeTemplates),\n },\n resolve: {\n alias: askrEntry ? [{ find: \"@askrjs/askr\", replacement: askrEntry }] : [],\n dedupe: [\"@askrjs/askr\"],\n },\n optimizeDeps: {\n include: [\n \"@askrjs/askr\",\n \"@askrjs/askr/router\",\n \"@askrjs/askr/boot\",\n \"@askrjs/askr/control\",\n \"@askrjs/askr/fx\",\n \"@askrjs/askr/resources\",\n \"@askrjs/askr/data\",\n \"@askrjs/askr/foundations\",\n \"@askrjs/askr/foundations/icon\",\n \"@askrjs/askr/foundations/interactions\",\n \"@askrjs/askr/foundations/state\",\n \"@askrjs/askr/foundations/structures\",\n \"@askrjs/askr/foundations/utilities\",\n \"@askrjs/askr/jsx-runtime\",\n \"@askrjs/askr/jsx-dev-runtime\",\n ],\n },\n oxc: {\n jsx: { runtime: \"automatic\", importSource: \"@askrjs/askr\" },\n jsxInject: \"import { jsx, jsxs, Fragment } from '@askrjs/askr/jsx-runtime';\",\n },\n };\n },\n async transform(code, id) {\n if (!shouldTransform || !/\\.(jsx|tsx)$/.test(id) || id.includes(\"node_modules\")) return null;\n try {\n const result = await transformWithOxc(code, id, {\n lang: id.endsWith(\".tsx\") ? \"tsx\" : \"jsx\",\n jsx: { runtime: \"automatic\", importSource: \"@askrjs/askr\" },\n sourcemap: true,\n });\n if (!result?.code) return null;\n return {\n code: shouldOptimizeTemplates ? optimizeTemplateOutput(result.code) : result.code,\n map: result.map,\n };\n } catch {\n return null;\n }\n },\n } satisfies Plugin;\n return plugin;\n}\n"],"mappings":";;;;AAAA,SAAgB,uBAAuB,MAAsB;CAC3D,MAAM,yBAAS,IAAI,IAAoB;CACvC,IAAI,aAAa;CACjB,MAAM,YAAY,KAAK,QACrB,oDACC,WAAmB,KAAa,YAAoB;EACnD,MAAM,WAAW,GAAG,IAAI,GAAG;EAC3B,IAAI,aAAa,OAAO,IAAI,QAAQ;EACpC,IAAI,CAAC,YAAY;GACf,IAAI,KAAK,MAAM,SAAS,CAAC,CAAC,SAAS,IAAI,GAAG,OAAO;GACjD,aAAa,sBAAsB;GACnC,OAAO,IAAI,UAAU,UAAU;EACjC;EACA,OAAO,GAAG,IAAI,IAAI;CACpB,CACF;CACA,IAAI,OAAO,SAAS,GAAG,OAAO;CAK9B,OAAO,GAJc,MAAM,KAAK,OAAO,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,UAAU,UAAU;EAE1E,OAAO,SAAS,KAAK,KADL,SAAS,MAAM,SAAS,QAAQ,GAAG,IAAI,CACvB,EAAE;CACpC,CACqB,CAAC,CAAC,KAAK,IAAI,EAAE,IAAI;AACxC;;;AClBA,MAAM,UAAU,cAAc,OAAO,KAAK,GAAG;AAmB7C,SAAgB,eAAe,UAAiC,CAAC,GAAmB;CAClF,MAAM,kBAAkB,QAAQ,gBAAgB;CAChD,MAAM,0BAA0B,QAAQ,qBAAqB;CA8D7D,OAAO;EA5DL,MAAM;EACN,SAAS;EACT,SAAS;GACP,IAAI,YAA2B;GAC/B,IAAI;IACF,YAAY,QAAQ,QAAQ,cAAc;GAC5C,QAAQ;IACN,YAAY;GACd;GACA,OAAO;IACL,QAAQ,EACN,6BAA6B,KAAK,UAAU,uBAAuB,EACrE;IACA,SAAS;KACP,OAAO,YAAY,CAAC;MAAE,MAAM;MAAgB,aAAa;KAAU,CAAC,IAAI,CAAC;KACzE,QAAQ,CAAC,cAAc;IACzB;IACA,cAAc,EACZ,SAAS;KACP;KACA;KACA;KACA;KACA;KACA;KACA;KACA;KACA;KACA;KACA;KACA;KACA;KACA;KACA;IACF,EACF;IACA,KAAK;KACH,KAAK;MAAE,SAAS;MAAa,cAAc;KAAe;KAC1D,WAAW;IACb;GACF;EACF;EACA,MAAM,UAAU,MAAM,IAAI;GACxB,IAAI,CAAC,mBAAmB,CAAC,eAAe,KAAK,EAAE,KAAK,GAAG,SAAS,cAAc,GAAG,OAAO;GACxF,IAAI;IACF,MAAM,SAAS,MAAM,iBAAiB,MAAM,IAAI;KAC9C,MAAM,GAAG,SAAS,MAAM,IAAI,QAAQ;KACpC,KAAK;MAAE,SAAS;MAAa,cAAc;KAAe;KAC1D,WAAW;IACb,CAAC;IACD,IAAI,CAAC,QAAQ,MAAM,OAAO;IAC1B,OAAO;KACL,MAAM,0BAA0B,uBAAuB,OAAO,IAAI,IAAI,OAAO;KAC7E,KAAK,OAAO;IACd;GACF,QAAQ;IACN,OAAO;GACT;EACF;CAEU;AACd"}
|
|
1
|
+
{"version":3,"file":"index.js","names":["mime","#options","#processed","#sharpLoader","#config"],"sources":["../src/template-optimizer.ts","../src/image-lexical.ts","../src/image-inspection.ts","../src/image-transform.ts","../src/image-pipeline.ts","../src/jsx-plugin.ts"],"sourcesContent":["export function optimizeTemplateOutput(code: string): string {\n const hoists = new Map<string, string>();\n let hoistIndex = 0;\n const optimized = code.replace(\n /\\b(class|className|style):\\s*(\"([^\"\\\\]|\\\\.)*\")/g,\n (fullMatch: string, key: string, literal: string) => {\n const cacheKey = `${key}:${literal}`;\n let identifier = hoists.get(cacheKey);\n if (!identifier) {\n if (code.split(fullMatch).length - 1 < 2) return fullMatch;\n identifier = `__askrStaticLiteral${hoistIndex++}`;\n hoists.set(cacheKey, identifier);\n }\n return `${key}: ${identifier}`;\n },\n );\n if (hoists.size === 0) return code;\n const declarations = Array.from(hoists.entries()).map(([cacheKey, name]) => {\n const literal = cacheKey.slice(cacheKey.indexOf(\":\") + 1);\n return `const ${name} = ${literal};`;\n });\n return `${declarations.join(\"\\n\")}\\n${optimized}`;\n}\n","const IMAGE_IMPORT = /from\\s+[\"']@askrjs\\/vite\\/image[\"']/g;\n\nfunction lexicalCodeMask(source: string): Uint8Array {\n const mask = new Uint8Array(source.length);\n let state: \"code\" | \"single\" | \"double\" | \"template\" | \"line-comment\" | \"block-comment\" = \"code\";\n let escaped = false;\n for (let index = 0; index < source.length; index += 1) {\n const character = source[index]!;\n const next = source[index + 1];\n if (state === \"code\") {\n if (character === \"/\" && next === \"/\") {\n state = \"line-comment\";\n index += 1;\n } else if (character === \"/\" && next === \"*\") {\n state = \"block-comment\";\n index += 1;\n } else if (character === \"'\") state = \"single\";\n else if (character === '\"') state = \"double\";\n else if (character === \"`\") state = \"template\";\n else mask[index] = 1;\n continue;\n }\n if (state === \"line-comment\") {\n if (character === \"\\n\" || character === \"\\r\") {\n state = \"code\";\n mask[index] = 1;\n }\n continue;\n }\n if (state === \"block-comment\") {\n if (character === \"*\" && next === \"/\") {\n state = \"code\";\n index += 1;\n }\n continue;\n }\n if (escaped) escaped = false;\n else if (character === \"\\\\\") escaped = true;\n else if (\n (state === \"single\" && character === \"'\") ||\n (state === \"double\" && character === '\"') ||\n (state === \"template\" && character === \"`\")\n ) {\n state = \"code\";\n }\n }\n return mask;\n}\n\nexport function imageDeclarationMask(code: string): Uint8Array | undefined {\n const mask = lexicalCodeMask(code);\n IMAGE_IMPORT.lastIndex = 0;\n for (let match = IMAGE_IMPORT.exec(code); match; match = IMAGE_IMPORT.exec(code)) {\n if (mask[match.index]) return mask;\n }\n return undefined;\n}\n","export type AssetFormat = \"avif\" | \"webp\" | \"jpeg\" | \"png\" | \"gif\" | \"svg\";\n\nexport interface SourceInspection {\n width: number;\n height: number;\n format: AssetFormat;\n transformable: boolean;\n animated: boolean;\n}\n\nfunction positiveDimensions(\n width: number,\n height: number,\n format: AssetFormat,\n transformable: boolean,\n animated = false,\n): SourceInspection | undefined {\n return width > 0 && height > 0 ? { width, height, format, transformable, animated } : undefined;\n}\n\nfunction inspectPng(source: Buffer): SourceInspection | undefined {\n if (\n source.length < 24 ||\n !source.subarray(0, 8).equals(Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]))\n ) {\n return undefined;\n }\n return positiveDimensions(\n source.readUInt32BE(16),\n source.readUInt32BE(20),\n \"png\",\n true,\n source.includes(Buffer.from(\"acTL\")),\n );\n}\n\nfunction inspectGif(source: Buffer): SourceInspection | undefined {\n if (source.length < 10 || !/^GIF8[79]a$/.test(source.subarray(0, 6).toString(\"ascii\"))) {\n return undefined;\n }\n return positiveDimensions(source.readUInt16LE(6), source.readUInt16LE(8), \"gif\", false, true);\n}\n\nfunction exifOrientation(segment: Buffer): number | undefined {\n if (segment.length < 14 || segment.subarray(0, 6).toString(\"binary\") !== \"Exif\\0\\0\") {\n return undefined;\n }\n const tiff = 6;\n const byteOrder = segment.subarray(tiff, tiff + 2).toString(\"ascii\");\n const littleEndian = byteOrder === \"II\";\n if (!littleEndian && byteOrder !== \"MM\") return undefined;\n const read16 = (offset: number): number | undefined => {\n const absolute = tiff + offset;\n if (absolute < tiff || absolute + 2 > segment.length) return undefined;\n return littleEndian ? segment.readUInt16LE(absolute) : segment.readUInt16BE(absolute);\n };\n const read32 = (offset: number): number | undefined => {\n const absolute = tiff + offset;\n if (absolute < tiff || absolute + 4 > segment.length) return undefined;\n return littleEndian ? segment.readUInt32LE(absolute) : segment.readUInt32BE(absolute);\n };\n if (read16(2) !== 42) return undefined;\n const directoryOffset = read32(4);\n if (directoryOffset === undefined) return undefined;\n const entries = read16(directoryOffset);\n if (entries === undefined) return undefined;\n for (let index = 0; index < entries; index += 1) {\n const entry = directoryOffset + 2 + index * 12;\n if (read16(entry) !== 0x0112 || read16(entry + 2) !== 3 || read32(entry + 4) !== 1) {\n continue;\n }\n const orientation = read16(entry + 8);\n return orientation && orientation >= 1 && orientation <= 8 ? orientation : undefined;\n }\n return undefined;\n}\n\nfunction inspectJpeg(source: Buffer): SourceInspection | undefined {\n if (source.length < 4 || source[0] !== 0xff || source[1] !== 0xd8) return undefined;\n const startOfFrame = new Set([\n 0xc0, 0xc1, 0xc2, 0xc3, 0xc5, 0xc6, 0xc7, 0xc9, 0xca, 0xcb, 0xcd, 0xce, 0xcf,\n ]);\n let orientation: number | undefined;\n let offset = 2;\n while (offset + 8 < source.length) {\n if (source[offset] !== 0xff) {\n offset += 1;\n continue;\n }\n while (source[offset] === 0xff) offset += 1;\n const marker = source[offset++]!;\n if (marker === 0xd8 || marker === 0xd9 || marker === 0x01) continue;\n if (marker >= 0xd0 && marker <= 0xd7) continue;\n if (offset + 2 > source.length) break;\n const length = source.readUInt16BE(offset);\n if (length < 2 || offset + length > source.length) break;\n if (marker === 0xe1) {\n orientation = exifOrientation(source.subarray(offset + 2, offset + length)) ?? orientation;\n }\n if (startOfFrame.has(marker) && length >= 7) {\n const width = source.readUInt16BE(offset + 5);\n const height = source.readUInt16BE(offset + 3);\n return positiveDimensions(\n orientation !== undefined && orientation >= 5 ? height : width,\n orientation !== undefined && orientation >= 5 ? width : height,\n \"jpeg\",\n true,\n );\n }\n offset += length;\n }\n return undefined;\n}\n\nfunction inspectSvg(source: Buffer): SourceInspection | undefined {\n const openingTag = source\n .subarray(0, 16_384)\n .toString(\"utf8\")\n .match(/<svg\\b[^>]*>/i)?.[0];\n if (!openingTag) return undefined;\n const numberAttribute = (name: string): number | undefined => {\n const value = openingTag.match(new RegExp(`\\\\b${name}\\\\s*=\\\\s*[\"']\\\\s*([0-9.]+)`, \"i\"))?.[1];\n const parsed = value ? Number(value) : Number.NaN;\n return Number.isFinite(parsed) && parsed > 0 ? parsed : undefined;\n };\n let width = numberAttribute(\"width\");\n let height = numberAttribute(\"height\");\n const viewBox = openingTag\n .match(/\\bviewBox\\s*=\\s*[\"']\\s*[-+0-9.e]+[ ,]+[-+0-9.e]+[ ,]+([-+0-9.e]+)[ ,]+([-+0-9.e]+)/i)\n ?.slice(1)\n .map(Number);\n if (!width && viewBox?.[0] && viewBox[0] > 0) width = viewBox[0];\n if (!height && viewBox?.[1] && viewBox[1] > 0) height = viewBox[1];\n if (!width && height && viewBox?.[0] && viewBox?.[1]) width = height * (viewBox[0] / viewBox[1]);\n if (!height && width && viewBox?.[0] && viewBox?.[1]) height = width / (viewBox[0] / viewBox[1]);\n return width && height ? positiveDimensions(width, height, \"svg\", false) : undefined;\n}\n\nfunction inspectWebp(source: Buffer): SourceInspection | undefined {\n if (\n source.length < 30 ||\n source.subarray(0, 4).toString(\"ascii\") !== \"RIFF\" ||\n source.subarray(8, 12).toString(\"ascii\") !== \"WEBP\"\n ) {\n return undefined;\n }\n const chunk = source.subarray(12, 16).toString(\"ascii\");\n if (chunk === \"VP8X\") {\n const width = 1 + source.readUIntLE(24, 3);\n const height = 1 + source.readUIntLE(27, 3);\n return positiveDimensions(width, height, \"webp\", false, Boolean(source[20]! & 0x02));\n }\n if (chunk === \"VP8 \") {\n return positiveDimensions(\n source.readUInt16LE(26) & 0x3fff,\n source.readUInt16LE(28) & 0x3fff,\n \"webp\",\n false,\n );\n }\n if (chunk === \"VP8L\" && source[20] === 0x2f) {\n const bits = source.readUInt32LE(21);\n return positiveDimensions(1 + (bits & 0x3fff), 1 + ((bits >>> 14) & 0x3fff), \"webp\", false);\n }\n return undefined;\n}\n\nfunction inspectAvif(source: Buffer): SourceInspection | undefined {\n if (source.length < 24 || source.subarray(4, 8).toString(\"ascii\") !== \"ftyp\") return undefined;\n const typeOffset = source.indexOf(Buffer.from(\"ispe\"));\n if (typeOffset < 0 || typeOffset + 16 > source.length) return undefined;\n return positiveDimensions(\n source.readUInt32BE(typeOffset + 8),\n source.readUInt32BE(typeOffset + 12),\n \"avif\",\n false,\n );\n}\n\nexport function inspectSource(sourcePath: string, source: Buffer): SourceInspection {\n const inspection =\n inspectPng(source) ??\n inspectGif(source) ??\n inspectJpeg(source) ??\n inspectSvg(source) ??\n inspectWebp(source) ??\n inspectAvif(source);\n if (!inspection) {\n throw new Error(\n `@askrjs/vite could not determine intrinsic dimensions for pass-through image ${sourcePath}.`,\n );\n }\n return inspection;\n}\n","import { createHash } from \"node:crypto\";\nimport fs from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { inspectSource, type AssetFormat } from \"./image-inspection\";\nimport { normalizeDeclarationOptions, normalizeImageOptions } from \"./image-options\";\nimport type { ImageOptions, ImageOutputFormat, ImagePipelineOptions } from \"./image-types\";\n\ntype SharpFactory = (typeof import(\"sharp\"))[\"default\"];\nexport type SharpLoader = () => Promise<SharpFactory>;\ntype TransformFormat = \"avif\" | \"webp\" | \"jpeg\" | \"png\";\n\nexport interface EmittedVariant {\n referenceId: string;\n width: number;\n height: number;\n format: AssetFormat;\n mime: string;\n}\n\nexport interface AssetPluginContext {\n emitFile(file: { type: \"asset\"; name: string; source: string | Uint8Array }): string;\n getFileName(referenceId: string): string;\n}\n\nexport interface ProcessedImage {\n declarationKey: string;\n sourcePath: string;\n sourceHash: string;\n declarationOptions: ImageOptions;\n transformKey: string;\n encoder: string;\n fallback: EmittedVariant;\n variants: EmittedVariant[];\n passThrough: boolean;\n}\n\nfunction hash(value: string | Buffer): string {\n return createHash(\"sha256\").update(value).digest(\"hex\");\n}\n\nexport function declarationKey(sourcePath: string, options: ImageOptions): string {\n return hash(`${sourcePath}\\0${JSON.stringify(normalizeDeclarationOptions(options))}`);\n}\n\nexport const defaultSharpLoader: SharpLoader = async () => (await import(\"sharp\")).default;\n\nasync function loadSharp(loader: SharpLoader): Promise<SharpFactory> {\n try {\n return await loader();\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === \"ERR_MODULE_NOT_FOUND\") {\n throw new Error(\n '@askrjs/vite responsive images require the optional peer \"sharp\". Install sharp@^0.35.3 and rebuild.',\n { cause: error },\n );\n }\n throw error;\n }\n}\n\nfunction sourceFormat(format: string | undefined): \"jpeg\" | \"png\" | undefined {\n if (format === \"jpeg\" || format === \"jpg\") return \"jpeg\";\n return format === \"png\" ? \"png\" : undefined;\n}\n\nfunction extension(format: AssetFormat): string {\n return format === \"jpeg\" ? \"jpg\" : format;\n}\n\nfunction mime(format: AssetFormat): string {\n return format === \"jpeg\" ? \"image/jpeg\" : `image/${format}`;\n}\n\nfunction outputName(sourcePath: string, width: number, format: EmittedVariant[\"format\"]): string {\n const basename = path.basename(sourcePath, path.extname(sourcePath));\n return `${basename}-${width}.${extension(format)}`;\n}\n\nfunction outputHeight(\n width: number,\n sourceWidth: number,\n sourceHeight: number,\n aspectRatio: number | undefined,\n): number {\n return Math.max(1, Math.round(width / (aspectRatio ?? sourceWidth / sourceHeight)));\n}\n\nasync function encodedVariant(\n sharp: SharpFactory,\n source: Buffer,\n cacheDir: string,\n transformKey: string,\n width: number,\n height: number,\n format: TransformFormat,\n options: ReturnType<typeof normalizeImageOptions>,\n): Promise<Buffer> {\n const cachePath = path.join(cacheDir, `${transformKey}-${width}.${extension(format)}`);\n const cached = await fs.readFile(cachePath).catch(() => undefined);\n if (cached) return cached;\n\n let pipeline = sharp(source, { animated: false }).autoOrient();\n pipeline =\n options.fit === \"cover\"\n ? pipeline.resize({ width, height, fit: \"cover\", position: options.position })\n : pipeline.resize({ width, fit: \"inside\", withoutEnlargement: true });\n if (format === \"avif\") pipeline = pipeline.avif({ quality: options.quality.avif });\n else if (format === \"webp\") pipeline = pipeline.webp({ quality: options.quality.webp });\n else if (format === \"jpeg\") pipeline = pipeline.jpeg({ quality: options.quality.jpeg });\n else {\n pipeline = pipeline.png(\n options.quality.png === 100\n ? { compressionLevel: 9 }\n : { compressionLevel: 9, quality: options.quality.png },\n );\n }\n const encoded = await pipeline.toBuffer();\n await fs.mkdir(cacheDir, { recursive: true });\n await fs.writeFile(cachePath, encoded);\n return encoded;\n}\n\nfunction variantFormats(\n formats: readonly ImageOutputFormat[],\n fallback: \"jpeg\" | \"png\",\n): Array<\"avif\" | \"webp\" | \"jpeg\" | \"png\"> {\n const output: Array<\"avif\" | \"webp\" | \"jpeg\" | \"png\"> = [];\n for (const format of formats) {\n const resolved = format === \"source\" ? fallback : format;\n if (!output.includes(resolved)) output.push(resolved);\n }\n if (!output.includes(fallback)) output.push(fallback);\n return output;\n}\n\nfunction passThroughImage(\n context: AssetPluginContext,\n sourcePath: string,\n source: Buffer,\n width: number,\n height: number,\n format: AssetFormat,\n): { fallback: EmittedVariant; variants: EmittedVariant[] } {\n const referenceId = context.emitFile({ type: \"asset\", name: path.basename(sourcePath), source });\n const fallback = { referenceId, width, height, format, mime: mime(format) };\n return { fallback, variants: [fallback] };\n}\n\nexport async function processImage(\n context: AssetPluginContext,\n sourcePath: string,\n declarationOptions: ImageOptions,\n globalOptions: ImagePipelineOptions,\n cacheDir: string,\n sharpLoader: SharpLoader,\n): Promise<ProcessedImage> {\n const source = await fs.readFile(sourcePath);\n const sourceHash = hash(source);\n const normalized = normalizeImageOptions(globalOptions, declarationOptions);\n const inspection = inspectSource(sourcePath, source);\n const key = declarationKey(sourcePath, declarationOptions);\n const shouldPassThroughWithoutSharp =\n !inspection.transformable ||\n inspection.animated ||\n (normalized.fit === \"inside\" && inspection.width <= normalized.widths[0]!);\n if (shouldPassThroughWithoutSharp) {\n const encoder = \"passthrough\";\n return {\n declarationKey: key,\n sourcePath,\n sourceHash,\n declarationOptions: normalizeDeclarationOptions(declarationOptions),\n transformKey: hash(`${sourceHash}\\0${JSON.stringify(normalized)}\\0${encoder}`),\n encoder,\n ...passThroughImage(\n context,\n sourcePath,\n source,\n inspection.width,\n inspection.height,\n inspection.format,\n ),\n passThrough: true,\n };\n }\n const sharp = await loadSharp(sharpLoader);\n const metadata = await sharp(source, { animated: true }).metadata();\n const sourceWidth = metadata.autoOrient.width ?? metadata.width;\n const sourceHeight =\n (metadata.pages ?? 1) > 1\n ? (metadata.pageHeight ?? metadata.height)\n : (metadata.autoOrient.height ?? metadata.height);\n if (!sourceWidth || !sourceHeight) {\n throw new Error(`@askrjs/vite could not determine intrinsic dimensions for ${sourcePath}.`);\n }\n const encoder = `sharp@${sharp.versions.sharp}`;\n const transformKey = hash(`${sourceHash}\\0${JSON.stringify(normalized)}\\0${encoder}`);\n const fallbackFormat = sourceFormat(metadata.format);\n\n const shouldPassThrough = (metadata.pages ?? 1) > 1 || !fallbackFormat;\n if (shouldPassThrough) {\n return {\n declarationKey: key,\n sourcePath,\n sourceHash,\n declarationOptions: normalizeDeclarationOptions(declarationOptions),\n transformKey,\n encoder,\n ...passThroughImage(\n context,\n sourcePath,\n source,\n sourceWidth,\n sourceHeight,\n fallbackFormat ?? inspection.format,\n ),\n passThrough: true,\n };\n }\n\n const maximumWidth =\n normalized.fit === \"cover\"\n ? Math.min(sourceWidth, Math.floor(sourceHeight * normalized.aspectRatio!))\n : sourceWidth;\n if (maximumWidth < 1) {\n throw new Error(\n `@askrjs/vite cannot crop ${sourcePath} to aspect ratio ${normalized.aspectRatio} without upscaling.`,\n );\n }\n const widths = normalized.widths.filter((width) => width <= maximumWidth);\n if (!widths.includes(maximumWidth)) widths.push(maximumWidth);\n widths.sort((left, right) => left - right);\n const variants: EmittedVariant[] = [];\n for (const format of variantFormats(normalized.formats, fallbackFormat)) {\n for (const width of widths) {\n const height = outputHeight(\n width,\n sourceWidth,\n sourceHeight,\n normalized.fit === \"cover\" ? normalized.aspectRatio : undefined,\n );\n const buffer = await encodedVariant(\n sharp,\n source,\n cacheDir,\n transformKey,\n width,\n height,\n format,\n normalized,\n );\n variants.push({\n referenceId: context.emitFile({\n type: \"asset\",\n name: outputName(sourcePath, width, format),\n source: buffer,\n }),\n width,\n height,\n format,\n mime: mime(format),\n });\n }\n }\n return {\n declarationKey: key,\n sourcePath,\n sourceHash,\n declarationOptions: normalizeDeclarationOptions(declarationOptions),\n transformKey,\n encoder,\n fallback: variants.filter((variant) => variant.format === fallbackFormat).at(-1)!,\n variants,\n passThrough: false,\n };\n}\n","import fs from \"node:fs/promises\";\nimport path from \"node:path\";\nimport type { ResolvedConfig } from \"vite\";\nimport { imageDeclarationMask } from \"./image-lexical\";\nimport {\n IMAGE_METADATA_PATH,\n IMAGE_METADATA_VERSION,\n type ImageMetadataEntry,\n type ImageMetadataRecord,\n} from \"./image-metadata\";\nimport type { ImageOptions, ImagePipelineOptions, ResponsiveImage } from \"./image-types\";\nimport {\n declarationKey,\n defaultSharpLoader,\n processImage,\n type AssetPluginContext,\n type EmittedVariant,\n type ProcessedImage,\n type SharpLoader,\n} from \"./image-transform\";\n\ninterface Declaration {\n start: number;\n end: number;\n source: string;\n options: ImageOptions;\n}\n\nconst IMAGE_CALL = /\\bimage\\s*\\(\\s*new\\s+URL\\s*\\(\\s*([\"'])([^\"']+)\\1\\s*,\\s*import\\.meta\\.url\\s*\\)/g;\n\nfunction staticJson(source: string, id: string): string {\n let output = \"\";\n for (let index = 0; index < source.length;) {\n const character = source[index]!;\n if (/\\s/.test(character) || \"{}[]:\".includes(character)) {\n output += character;\n index += 1;\n continue;\n }\n if (character === \",\") {\n let next = index + 1;\n while (/\\s/.test(source[next] ?? \"\")) next += 1;\n if (source[next] !== \"}\" && source[next] !== \"]\") output += character;\n index += 1;\n continue;\n }\n if (character === '\"' || character === \"'\") {\n let end = index + 1;\n while (end < source.length && source[end] !== character) {\n if (source[end] === \"\\\\\") {\n throw new Error(\n `@askrjs/vite image() option strings in ${id} cannot contain escape sequences.`,\n );\n }\n end += 1;\n }\n if (end >= source.length) {\n throw new Error(`@askrjs/vite found an unterminated image option in ${id}.`);\n }\n output += JSON.stringify(source.slice(index + 1, end));\n index = end + 1;\n continue;\n }\n const number = /^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?(?:e[+-]?\\d+)?/i.exec(source.slice(index));\n if (number) {\n output += number[0];\n index += number[0].length;\n continue;\n }\n const identifier = /^[A-Za-z_]\\w*/.exec(source.slice(index));\n if (identifier) {\n let next = index + identifier[0].length;\n while (/\\s/.test(source[next] ?? \"\")) next += 1;\n if (source[next] === \":\") output += JSON.stringify(identifier[0]);\n else if ([\"true\", \"false\", \"null\"].includes(identifier[0])) output += identifier[0];\n else {\n throw new Error(\n `@askrjs/vite image() options in ${id} must not reference ${identifier[0]}; use a static object literal.`,\n );\n }\n index += identifier[0].length;\n continue;\n }\n throw new Error(\n `@askrjs/vite image() options in ${id} must be a static object literal so builds and direct SSG imports agree.`,\n );\n }\n return output;\n}\n\nfunction staticOptions(source: string, id: string): ImageOptions {\n const trimmed = source.trim();\n if (!trimmed) return {};\n let value: unknown;\n try {\n value = JSON.parse(staticJson(trimmed, id));\n } catch (error) {\n if (error instanceof Error && error.message.startsWith(\"@askrjs/vite\")) throw error;\n throw new Error(`@askrjs/vite could not parse image() options in ${id}.`, { cause: error });\n }\n if (!value || typeof value !== \"object\" || Array.isArray(value)) {\n throw new Error(`@askrjs/vite image() options in ${id} must be an object literal.`);\n }\n return value as ImageOptions;\n}\n\nfunction findCallEnd(code: string, start: number): number {\n let depth = 1;\n let quote = \"\";\n let escaped = false;\n for (let index = start; index < code.length; index += 1) {\n const character = code[index]!;\n if (quote) {\n if (escaped) escaped = false;\n else if (character === \"\\\\\") escaped = true;\n else if (character === quote) quote = \"\";\n continue;\n }\n if (character === '\"' || character === \"'\") {\n quote = character;\n continue;\n }\n if (character === \"(\") depth += 1;\n if (character === \")\") {\n depth -= 1;\n if (depth === 0) return index;\n }\n }\n return -1;\n}\n\nfunction declarations(code: string, id: string): Declaration[] {\n const mask = imageDeclarationMask(code);\n if (!mask) return [];\n const output: Declaration[] = [];\n IMAGE_CALL.lastIndex = 0;\n for (let match = IMAGE_CALL.exec(code); match; match = IMAGE_CALL.exec(code)) {\n if (!mask[match.index]) continue;\n const afterUrl = IMAGE_CALL.lastIndex;\n const end = findCallEnd(code, afterUrl);\n if (end < 0) throw new Error(`@askrjs/vite found an unterminated image() call in ${id}.`);\n const remainder = code.slice(afterUrl, end).trim();\n if (remainder && !remainder.startsWith(\",\")) {\n throw new Error(`@askrjs/vite could not parse image() declaration in ${id}.`);\n }\n output.push({\n start: match.index,\n end: end + 1,\n source: match[2]!,\n options: staticOptions(remainder.replace(/^,/, \"\"), id),\n });\n IMAGE_CALL.lastIndex = end + 1;\n }\n return output;\n}\n\nfunction urlExpression(referenceId: string): string {\n return `import.meta.ROLLUP_FILE_URL_${referenceId}`;\n}\n\nfunction mime(format: EmittedVariant[\"format\"]): string {\n return format === \"jpeg\" ? \"image/jpeg\" : `image/${format}`;\n}\n\nfunction srcsetExpression(variants: readonly EmittedVariant[]): string {\n return `[${variants.map((variant) => `${urlExpression(variant.referenceId)} + ${JSON.stringify(` ${variant.width}w`)}`).join(\",\")}].join(\", \")`;\n}\n\nfunction imageExpression(processed: ProcessedImage): string {\n const fallbackVariants = processed.variants.filter(\n (variant) => variant.format === processed.fallback.format,\n );\n const modernFormats = [\"avif\", \"webp\"] as const;\n const sources = processed.passThrough\n ? []\n : modernFormats.flatMap((format) => {\n const variants = processed.variants.filter((variant) => variant.format === format);\n return variants.length === 0\n ? []\n : [`{type:${JSON.stringify(mime(format))},srcset:${srcsetExpression(variants)}}`];\n });\n return `image({__askrImage:true,src:${urlExpression(processed.fallback.referenceId)},${\n processed.passThrough ? \"\" : `srcset:${srcsetExpression(fallbackVariants)},`\n }width:${processed.fallback.width},height:${processed.fallback.height},sources:[${sources.join(\",\")}]})`;\n}\n\nfunction builtUrl(base: string, fileName: string): string {\n if (/^https?:\\/\\//.test(base))\n return new URL(fileName, base.endsWith(\"/\") ? base : `${base}/`).href;\n if (base === \"\" || base === \"./\" || base === \".\")\n return `${base === \".\" ? \"./\" : base}${fileName}`;\n return `${base.endsWith(\"/\") ? base : `${base}/`}${fileName}`;\n}\n\nfunction responsiveMetadata(\n processed: ProcessedImage,\n fileName: (referenceId: string) => string,\n base: string,\n): ResponsiveImage {\n const url = (variant: EmittedVariant) => builtUrl(base, fileName(variant.referenceId));\n const srcset = (variants: readonly EmittedVariant[]) =>\n variants.map((variant) => `${url(variant)} ${variant.width}w`).join(\", \");\n const fallbackVariants = processed.variants.filter(\n (variant) => variant.format === processed.fallback.format,\n );\n return {\n __askrImage: true,\n src: url(processed.fallback),\n ...(processed.passThrough ? {} : { srcset: srcset(fallbackVariants) }),\n width: processed.fallback.width,\n height: processed.fallback.height,\n sources: processed.passThrough\n ? []\n : ([\"avif\", \"webp\"] as const).flatMap((format) => {\n const variants = processed.variants.filter((variant) => variant.format === format);\n return variants.length === 0 ? [] : [{ type: mime(format), srcset: srcset(variants) }];\n }),\n };\n}\n\nexport class ImagePipeline {\n readonly #options: ImagePipelineOptions;\n readonly #processed = new Map<string, ProcessedImage>();\n readonly #sharpLoader: SharpLoader;\n #config: ResolvedConfig | undefined;\n\n constructor(options: ImagePipelineOptions, sharpLoader: SharpLoader = defaultSharpLoader) {\n this.#options = options;\n this.#sharpLoader = sharpLoader;\n }\n\n configure(config: ResolvedConfig): void {\n this.#config = config;\n }\n\n async transform(\n context: AssetPluginContext,\n code: string,\n id: string,\n ): Promise<string | undefined> {\n const found = declarations(code, id);\n if (found.length === 0) return undefined;\n if (!this.#config) throw new Error(\"@askrjs/vite image pipeline was not configured.\");\n if (this.#config.command !== \"build\") {\n throw new Error(\"@askrjs/vite responsive image declarations currently require a Vite build.\");\n }\n const cacheDir = path.join(this.#config.root, \"node_modules/.cache/@askrjs/vite/images\");\n let transformed = code;\n for (const declaration of [...found].reverse()) {\n const sourcePath = path.resolve(path.dirname(id.split(\"?\", 1)[0]!), declaration.source);\n const key = declarationKey(sourcePath, declaration.options);\n let processed = this.#processed.get(key);\n if (!processed) {\n processed = await processImage(\n context,\n sourcePath,\n declaration.options,\n this.#options,\n cacheDir,\n this.#sharpLoader,\n );\n this.#processed.set(key, processed);\n }\n transformed = `${transformed.slice(0, declaration.start)}${imageExpression(processed)}${transformed.slice(declaration.end)}`;\n }\n return transformed;\n }\n\n async writeMetadata(context: AssetPluginContext): Promise<void> {\n if (!this.#config || this.#processed.size === 0) return;\n const entries: Record<string, ImageMetadataEntry> = {};\n for (const processed of [...this.#processed.values()].sort((left, right) =>\n left.declarationKey.localeCompare(right.declarationKey),\n )) {\n entries[processed.declarationKey] = {\n sourcePath: processed.sourcePath,\n sourceHash: processed.sourceHash,\n declarationOptions: processed.declarationOptions,\n transformKey: processed.transformKey,\n encoder: processed.encoder,\n image: responsiveMetadata(\n processed,\n (referenceId) => context.getFileName(referenceId),\n this.#config.base,\n ),\n };\n }\n const record: ImageMetadataRecord = {\n version: IMAGE_METADATA_VERSION,\n entries,\n };\n const metadataPath = path.join(this.#config.root, IMAGE_METADATA_PATH);\n await fs.mkdir(path.dirname(metadataPath), { recursive: true });\n await fs.writeFile(metadataPath, `${JSON.stringify(record, null, 2)}\\n`, \"utf8\");\n }\n}\n","import { createRequire } from \"node:module\";\nimport type { Plugin } from \"vite\";\nimport { optimizeTemplateOutput } from \"./template-optimizer\";\nimport { ImagePipeline } from \"./image-pipeline\";\nimport type { ImagePipelineOptions } from \"./image-types\";\n\nconst require = createRequire(import.meta.url);\ntype TransformWithOxc = (typeof import(\"vite\"))[\"transformWithOxc\"];\nlet transformWithOxcPromise: Promise<TransformWithOxc> | undefined;\n\nasync function loadTransformWithOxc(): Promise<TransformWithOxc> {\n transformWithOxcPromise ??= (async () => {\n for (const packageName of [\"vite-plus\", \"vite\"] as const) {\n try {\n const buildTool = await import(packageName);\n if (typeof buildTool.transformWithOxc === \"function\") {\n return buildTool.transformWithOxc as TransformWithOxc;\n }\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== \"ERR_MODULE_NOT_FOUND\") throw error;\n }\n }\n throw new Error(\"@askrjs/vite requires either vite or vite-plus to transform JSX.\");\n })();\n return transformWithOxcPromise;\n}\n\nexport interface AskrVitePluginOptions {\n transformJsx?: boolean;\n optimizeTemplates?: boolean;\n ssrPrecompile?: boolean;\n /** Opt into declared responsive image transforms. */\n images?: boolean | ImagePipelineOptions;\n}\n\n/**\n * Portable public plugin identity.\n *\n * Vite-compatible runners only require the plugin name. Keeping versioned Vite\n * hook types out of the installed declaration prevents consumers such as\n * vite-plus from comparing two distinct copies of Vite's recursive Plugin type.\n */\nexport interface AskrVitePlugin {\n readonly name: \"askr:vite\";\n}\n\nexport function askrVitePlugin(options: AskrVitePluginOptions = {}): AskrVitePlugin {\n const shouldTransform = options.transformJsx ?? true;\n const shouldOptimizeTemplates = options.optimizeTemplates ?? false;\n const imagePipeline = options.images\n ? new ImagePipeline(options.images === true ? {} : options.images)\n : undefined;\n const plugin = {\n name: \"askr:vite\" as const,\n enforce: \"pre\",\n config() {\n let askrEntry: string | null = null;\n try {\n askrEntry = require.resolve(\"@askrjs/askr\");\n } catch {\n askrEntry = null;\n }\n return {\n define: {\n __ASKR_OPTIMIZE_TEMPLATES__: JSON.stringify(shouldOptimizeTemplates),\n },\n resolve: {\n alias: askrEntry ? [{ find: \"@askrjs/askr\", replacement: askrEntry }] : [],\n dedupe: [\"@askrjs/askr\"],\n },\n optimizeDeps: {\n include: [\n \"@askrjs/askr\",\n \"@askrjs/askr/router\",\n \"@askrjs/askr/boot\",\n \"@askrjs/askr/control\",\n \"@askrjs/askr/fx\",\n \"@askrjs/askr/resources\",\n \"@askrjs/askr/data\",\n \"@askrjs/askr/foundations\",\n \"@askrjs/askr/foundations/icon\",\n \"@askrjs/askr/foundations/interactions\",\n \"@askrjs/askr/foundations/state\",\n \"@askrjs/askr/foundations/structures\",\n \"@askrjs/askr/foundations/utilities\",\n \"@askrjs/askr/jsx-runtime\",\n \"@askrjs/askr/jsx-dev-runtime\",\n ],\n },\n oxc: {\n jsx: { runtime: \"automatic\", importSource: \"@askrjs/askr\" },\n jsxInject: \"import { jsx, jsxs, Fragment } from '@askrjs/askr/jsx-runtime';\",\n },\n };\n },\n configResolved(config) {\n imagePipeline?.configure(config);\n },\n async transform(code, id) {\n if (id.includes(\"node_modules\")) return null;\n const imageTransformed = imagePipeline\n ? await imagePipeline.transform(this, code, id)\n : undefined;\n const input = imageTransformed ?? code;\n if (!shouldTransform || !/\\.(jsx|tsx)$/.test(id)) {\n return imageTransformed ? { code: imageTransformed, map: null } : null;\n }\n try {\n const transformWithOxc = await loadTransformWithOxc();\n const result = await transformWithOxc(input, id, {\n lang: id.endsWith(\".tsx\") ? \"tsx\" : \"jsx\",\n jsx: { runtime: \"automatic\", importSource: \"@askrjs/askr\" },\n sourcemap: true,\n });\n if (!result?.code) return null;\n return {\n code: shouldOptimizeTemplates ? optimizeTemplateOutput(result.code) : result.code,\n map: result.map,\n };\n } catch {\n return imageTransformed ? { code: imageTransformed, map: null } : null;\n }\n },\n async writeBundle() {\n await imagePipeline?.writeMetadata(this);\n },\n } satisfies Plugin;\n return plugin;\n}\n"],"mappings":"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"}
|
package/dist/server.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
|
|
2
|
-
import { parseFragment, serialize } from "parse5";
|
|
3
|
-
import { createNodeHandler } from "@askrjs/node";
|
|
4
2
|
import { readFile } from "node:fs/promises";
|
|
5
|
-
import { existsSync } from "node:fs";
|
|
6
3
|
import { resolve } from "node:path";
|
|
4
|
+
import { existsSync } from "node:fs";
|
|
5
|
+
import { parseFragment, serialize } from "parse5";
|
|
6
|
+
import { createNodeHandler } from "@askrjs/node";
|
|
7
7
|
//#region src/server/head.ts
|
|
8
8
|
const htmlNamespace = "http://www.w3.org/1999/xhtml";
|
|
9
9
|
const linkAttributes = /* @__PURE__ */ new Set([
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@askrjs/vite",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.11",
|
|
4
4
|
"description": "Vite plugin for Askr JSX and template transforms",
|
|
5
5
|
"homepage": "https://github.com/askrjs/askr-vite#readme",
|
|
6
6
|
"bugs": {
|
|
@@ -31,6 +31,13 @@
|
|
|
31
31
|
"types": "./dist/server.d.ts",
|
|
32
32
|
"import": "./dist/server.js"
|
|
33
33
|
},
|
|
34
|
+
"./image": {
|
|
35
|
+
"types": "./dist/image.d.ts",
|
|
36
|
+
"node": "./dist/image-node.js",
|
|
37
|
+
"browser": "./dist/image.js",
|
|
38
|
+
"import": "./dist/image.js",
|
|
39
|
+
"default": "./dist/image.js"
|
|
40
|
+
},
|
|
34
41
|
"./package.json": "./package.json"
|
|
35
42
|
},
|
|
36
43
|
"publishConfig": {
|
|
@@ -59,6 +66,7 @@
|
|
|
59
66
|
"@askrjs/server": ">=0.0.1 <0.1.0",
|
|
60
67
|
"@types/node": "^26.0.0",
|
|
61
68
|
"publint": "^0.3.21",
|
|
69
|
+
"sharp": "^0.35.3",
|
|
62
70
|
"typescript": "^6.0.3",
|
|
63
71
|
"vite": "^8.0.16",
|
|
64
72
|
"vite-plus": "^0.2.4",
|
|
@@ -66,7 +74,20 @@
|
|
|
66
74
|
},
|
|
67
75
|
"peerDependencies": {
|
|
68
76
|
"@askrjs/askr": ">=0.0.52 <0.1.0",
|
|
69
|
-
"
|
|
77
|
+
"sharp": "^0.35.3",
|
|
78
|
+
"vite": "^8.0.1",
|
|
79
|
+
"vite-plus": "^0.2.4"
|
|
80
|
+
},
|
|
81
|
+
"peerDependenciesMeta": {
|
|
82
|
+
"sharp": {
|
|
83
|
+
"optional": true
|
|
84
|
+
},
|
|
85
|
+
"vite": {
|
|
86
|
+
"optional": true
|
|
87
|
+
},
|
|
88
|
+
"vite-plus": {
|
|
89
|
+
"optional": true
|
|
90
|
+
}
|
|
70
91
|
},
|
|
71
92
|
"engines": {
|
|
72
93
|
"node": ">=18"
|