astro-lqip 1.8.0 → 1.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/astro-lqip.css +1 -0
- package/dist/index.d.mts +140 -0
- package/dist/index.mjs +18 -0
- package/package.json +36 -9
- package/src/components/Background.astro +0 -21
- package/src/components/Image.astro +0 -45
- package/src/components/Picture.astro +0 -41
- package/src/constants/index.ts +0 -1
- package/src/index.ts +0 -3
- package/src/styles/background.css +0 -3
- package/src/styles/lqip.css +0 -37
- package/src/types/components-options.type.ts +0 -10
- package/src/types/image-path.type.ts +0 -4
- package/src/types/image-transform.type.ts +0 -54
- package/src/types/index.ts +0 -7
- package/src/types/lqip.type.ts +0 -1
- package/src/types/plaiceholder.type.ts +0 -3
- package/src/types/props.type.ts +0 -16
- package/src/types/svg-node.type.ts +0 -7
- package/src/utils/generateLqip.ts +0 -77
- package/src/utils/getLqip.ts +0 -189
- package/src/utils/getLqipStyle.ts +0 -17
- package/src/utils/renderSVGNode.ts +0 -25
- package/src/utils/resolveImagePath.ts +0 -128
- package/src/utils/useLqipBackground.ts +0 -298
- package/src/utils/useLqipImage.ts +0 -50
|
@@ -0,0 +1 @@
|
|
|
1
|
+
@layer astro-lqip{[data-astro-lqip]{--opacity:1;--z-index:0;isolation:isolate;width:fit-content;height:fit-content;line-height:0;display:inline-block;position:relative;overflow:clip;&:after{content:"";pointer-events:none;opacity:var(--opacity);z-index:var(--z-index);background:var(--lqip-background);background-position:50%;background-size:cover;transition:opacity 1s;position:absolute;inset:0}& img{z-index:1;position:relative}@media (scripting:none){--opacity:0;--z-index:1}}[data-astro-lqip-bg]{display:contents}}
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { LocalImageProps, Picture as Picture$1, RemoteImageProps } from "astro:assets";
|
|
2
|
+
import { GetPlaiceholderReturn } from "plaiceholder";
|
|
3
|
+
import { ImageMetadata } from "astro";
|
|
4
|
+
import { ComponentProps, HTMLAttributes } from "astro/types";
|
|
5
|
+
|
|
6
|
+
//#region src/types/lqip.type.d.ts
|
|
7
|
+
type LqipType = 'color' | 'css' | 'base64' | 'svg';
|
|
8
|
+
//#endregion
|
|
9
|
+
//#region src/types/style.type.d.ts
|
|
10
|
+
type StylePrimitive = string | number | undefined;
|
|
11
|
+
type StyleMap<TExtra = never> = Record<string, StylePrimitive | TExtra>;
|
|
12
|
+
type StyleInput<TExtra = never> = StyleMap<TExtra> | string;
|
|
13
|
+
//#endregion
|
|
14
|
+
//#region src/types/components-options.type.d.ts
|
|
15
|
+
type ComponentsOptions = {
|
|
16
|
+
src: string | object;
|
|
17
|
+
lqip: LqipType;
|
|
18
|
+
lqipSize: number;
|
|
19
|
+
styleProps: StyleMap;
|
|
20
|
+
forbiddenVars: string[];
|
|
21
|
+
isDevelopment: boolean | undefined;
|
|
22
|
+
};
|
|
23
|
+
//#endregion
|
|
24
|
+
//#region src/types/image-path.type.d.ts
|
|
25
|
+
type ImagePath = string | {
|
|
26
|
+
src: string;
|
|
27
|
+
} | Promise<{
|
|
28
|
+
default: {
|
|
29
|
+
src: string;
|
|
30
|
+
};
|
|
31
|
+
}>;
|
|
32
|
+
type ResolvedImage = {
|
|
33
|
+
src: string;
|
|
34
|
+
width?: number;
|
|
35
|
+
height?: number;
|
|
36
|
+
[k: string]: unknown;
|
|
37
|
+
};
|
|
38
|
+
type ImportModule = Record<string, unknown> & {
|
|
39
|
+
default?: unknown;
|
|
40
|
+
};
|
|
41
|
+
type GlobMap = Record<string, () => Promise<ImportModule>>;
|
|
42
|
+
//#endregion
|
|
43
|
+
//#region src/types/image-transform.type.d.ts
|
|
44
|
+
type ValidOutputFormats = ['avif', 'png', 'webp', 'jpeg', 'jpg', 'svg'];
|
|
45
|
+
type ImageQualityPreset = 'low' | 'mid' | 'high' | 'max' | (string & {});
|
|
46
|
+
type ImageQuality = ImageQualityPreset | number;
|
|
47
|
+
type ImageOutputFormat = ValidOutputFormats[number] | (string & {});
|
|
48
|
+
type ImageFit = 'fill' | 'contain' | 'cover' | 'none' | 'scale-down' | (string & {});
|
|
49
|
+
interface ImageTransform {
|
|
50
|
+
/**
|
|
51
|
+
* Path of the image that will be used as the background.
|
|
52
|
+
* It can be a relative path, an absolute path, an alias or ImageMetadata.
|
|
53
|
+
*/
|
|
54
|
+
src: string | ImageMetadata;
|
|
55
|
+
/**
|
|
56
|
+
* CSS custom property that will receive the generated background.
|
|
57
|
+
* (defaults to --background)
|
|
58
|
+
*/
|
|
59
|
+
cssVariable?: string;
|
|
60
|
+
/**
|
|
61
|
+
* LQIP placeholder strategy for Background (defaults to 'base64').
|
|
62
|
+
* Only 'base64' and 'color' are supported to keep CSS-friendly values.
|
|
63
|
+
*/
|
|
64
|
+
lqip?: 'base64' | 'color';
|
|
65
|
+
/**
|
|
66
|
+
* Specifies one or more output formats (first entry is preferred fallback).
|
|
67
|
+
* Accepts a single `ImageOutputFormat` or an ordered array.
|
|
68
|
+
* (defaults to webp)
|
|
69
|
+
*/
|
|
70
|
+
format?: ImageOutputFormat | ImageOutputFormat[] | undefined;
|
|
71
|
+
/**
|
|
72
|
+
* Explicit widths used for responsive variants.
|
|
73
|
+
*/
|
|
74
|
+
widths?: number[] | undefined;
|
|
75
|
+
/**
|
|
76
|
+
* Single width fallback when `widths` is omitted.
|
|
77
|
+
*/
|
|
78
|
+
width?: number | undefined;
|
|
79
|
+
/**
|
|
80
|
+
* Target height for generated assets.
|
|
81
|
+
*/
|
|
82
|
+
height?: number | undefined;
|
|
83
|
+
/**
|
|
84
|
+
* Compression quality preset or numeric percentage.
|
|
85
|
+
*/
|
|
86
|
+
quality?: ImageQuality | undefined;
|
|
87
|
+
/**
|
|
88
|
+
* Object-fit behavior applied during resizing.
|
|
89
|
+
*/
|
|
90
|
+
fit?: ImageFit | undefined;
|
|
91
|
+
}
|
|
92
|
+
//#endregion
|
|
93
|
+
//#region src/types/plaiceholder.type.d.ts
|
|
94
|
+
type GetSVGReturn = GetPlaiceholderReturn['svg'];
|
|
95
|
+
//#endregion
|
|
96
|
+
//#region src/types/props.type.d.ts
|
|
97
|
+
type Props = {
|
|
98
|
+
/**
|
|
99
|
+
* LQIP type.
|
|
100
|
+
* This can be 'color', 'css', 'svg' or 'base64'.
|
|
101
|
+
* The default value is 'base64'.
|
|
102
|
+
*/
|
|
103
|
+
lqip?: LqipType;
|
|
104
|
+
/**
|
|
105
|
+
* Size of the LQIP image in pixels.
|
|
106
|
+
* This value should be between 4 and 64.
|
|
107
|
+
* The default value is 4.
|
|
108
|
+
*/
|
|
109
|
+
lqipSize?: number;
|
|
110
|
+
};
|
|
111
|
+
//#endregion
|
|
112
|
+
//#region src/types/svg-node.type.d.ts
|
|
113
|
+
type StyleAttrs = StyleInput<GetSVGReturn>;
|
|
114
|
+
type SVGNodeAttrs = {
|
|
115
|
+
style?: StyleAttrs;
|
|
116
|
+
} & Record<string, string | number>;
|
|
117
|
+
type SVGNode = [string, SVGNodeAttrs, SVGNode[]];
|
|
118
|
+
//#endregion
|
|
119
|
+
//#region src/components/Picture.d.ts
|
|
120
|
+
type AstroPictureProps = ComponentProps<typeof Picture$1>;
|
|
121
|
+
type Props$3 = AstroPictureProps & Props;
|
|
122
|
+
//#endregion
|
|
123
|
+
//#region src/components/Image.d.ts
|
|
124
|
+
type Props$2 = (LocalImageProps | RemoteImageProps) & Props & {
|
|
125
|
+
parentAttributes?: HTMLAttributes<'div'>;
|
|
126
|
+
};
|
|
127
|
+
//#endregion
|
|
128
|
+
//#region src/components/Background.d.ts
|
|
129
|
+
type Props$1 = ImageTransform;
|
|
130
|
+
//#endregion
|
|
131
|
+
//#region src/index.d.ts
|
|
132
|
+
type AstroTypedComponent<Props> = ((props: Props) => unknown) & {
|
|
133
|
+
isAstroComponentFactory?: boolean;
|
|
134
|
+
moduleId?: string;
|
|
135
|
+
};
|
|
136
|
+
declare const Image: AstroTypedComponent<Props$2>;
|
|
137
|
+
declare const Picture: AstroTypedComponent<Props$3>;
|
|
138
|
+
declare const Background: AstroTypedComponent<Props$1>;
|
|
139
|
+
//#endregion
|
|
140
|
+
export { Background, ComponentsOptions, GetSVGReturn, GlobMap, Image, ImagePath, ImageTransform, ImportModule, LqipType, Picture, Props, ResolvedImage, SVGNode, StyleAttrs, StyleInput, StyleMap };
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import './astro-lqip.css';
|
|
2
|
+
import{createComponent as e,renderComponent as t,renderSlotToString as n,renderTemplate as r,spreadAttributes as i}from"astro/runtime/server/index.js";import{Image as a,Picture as o,getImage as s,inferRemoteSize as c}from"astro:assets";import{existsSync as l,statSync as u}from"node:fs";import{mkdir as d,readFile as f,readdir as p,unlink as m,writeFile as h}from"node:fs/promises";import{basename as g,dirname as _,extname as v,join as y,relative as b,resolve as x,sep as S}from"node:path";import{fileURLToPath as C}from"node:url";import{getPlaiceholder as w}from"plaiceholder";const T=`[astro-lqip]`,E=process.cwd(),D=y(E,`src`),O=y(E,`public`),k=y(E,`dist`),ee=import.meta.env?.MODE===`development`,te=/(file:\/\/[^\s)]+|\/[^\s)]+|[A-Za-z]:[^\s)]+):\d+:\d+/,ne=[`${S}node_modules${S}`,`${S}dist${S}`,`${S}.astro${S}`,`${S}.prerender${S}`],re=new Set([`node_modules`,`dist`,`.astro`]),ie=new Set([`.astro`,`.vite`]),A=Object.assign({}),j=new Map,M=new Map;function N(e){if(!e)return;let t=e.toLowerCase();(t.includes(`/public/`)||e.startsWith(O))&&console.warn(`${T} Warning: image resolved from /public. Images should not be placed in /public - move them to /src so Astro can process them correctly.`),(t.endsWith(`.webp`)||t.endsWith(`.avif`))&&console.warn(`${T} Warning: image is in ${t.endsWith(`.webp`)?`webp`:`avif`} format. These formats are usually already optimized; using this component to re-process them may degrade quality.`)}function P(e){return typeof e==`object`&&!!e}function ae(e){return P(e)&&typeof e.then==`function`}function F(e){return P(e)&&typeof e.src==`string`}function oe(e){return/^https?:\/\//.test(e)}function se(e){let t=e.indexOf(`?`),n=e.indexOf(`#`),r=e.length;return t!==-1&&(r=Math.min(r,t)),n!==-1&&(r=Math.min(r,n)),e.slice(0,r)}function I(e){let t=se(e.trim()).replace(/\\/g,`/`);return t.startsWith(`@/`)?`src/${t.slice(2)}`:t.startsWith(`~@/`)?`src/${t.slice(3)}`:t}function L(e){return e.startsWith(`./`)||e.startsWith(`../`)}function R(e){let t=e;for(;t.startsWith(`./`)||t.startsWith(`../`);)t=t.startsWith(`./`)?t.slice(2):t.slice(3);return t}function ce(e){let t=e.match(te);if(!t?.[1])return null;let n=t[1];if(!n.startsWith(`file://`))return n;try{return C(n)}catch{return n.replace(/^file:\/\//,``)}}function le(e){return!e||!e.startsWith(`/`)&&!/^[A-Za-z]:/.test(e)||!e.startsWith(E)?!1:!ne.some(t=>e.includes(t))}function ue(){let e=Error().stack;if(!e)return null;for(let t of e.split(`
|
|
3
|
+
`).slice(2)){let e=ce(t);if(e&&le(e))return _(e)}return null}function z(e){let t=b(E,e);return!e||t.startsWith(`..`)||t.includes(`..${S}`)||t.includes(`node_modules`)?null:e}function de(e){let t=b(E,e).replace(/\\/g,`/`);return!t||t.startsWith(`../`)?null:`/${t}`}function fe(e,t){let n=I(e),r=new Set;if(n.startsWith(`/src/`)?r.add(n):n.startsWith(`src/`)&&r.add(`/${n}`),t&&L(n)){let e=de(x(t,n));e?.startsWith(`/src/`)&&r.add(e)}if(L(n)){let e=R(n);e&&r.add(e.startsWith(`src/`)?`/${e}`:`/src/${e}`);let t=`/${e}`;for(let e of Object.keys(A))e.endsWith(t)&&r.add(e)}return!n.startsWith(`/`)&&!n.startsWith(`src/`)&&!L(n)&&r.add(`/src/${n}`),Array.from(r)}function pe(e,t){let n=I(e),r=new Set,i=n.startsWith(`/`)?n.slice(1):n;if(t&&L(n)){let e=z(x(t,n));e&&r.add(e)}if(L(n)){let e=R(n);if(e){let t=z(y(D,e));t&&r.add(t);let n=z(y(O,e));n&&r.add(n)}}if(n.startsWith(`/src/`)?r.add(y(E,n.slice(1))):n.startsWith(`src/`)&&r.add(y(E,n)),n.startsWith(`/public/`)?r.add(y(E,n.slice(1))):n.startsWith(`public/`)&&r.add(y(E,n)),!n.startsWith(`/`)){let e=z(y(D,n));e&&r.add(e);let t=z(y(E,n));t&&r.add(t)}else if(i){let e=z(y(E,i));e&&r.add(e)}return Array.from(r)}async function B(e){if(!e)return null;let t=`${D}::${e}`;if(j.has(t))return j.get(t)??null;async function n(t){let r;try{r=await p(t,{withFileTypes:!0})}catch{return}for(let i of r){let r=y(t,i.name);if(i.isDirectory()){if(re.has(i.name))continue;let e=await n(r);if(e)return e}else if(i.name===e)return r}}let r=await n(D);return j.set(t,r??null),r??null}function me(e,t){if(e!=null)return String(e).toLowerCase();let n=v(t).replace(`.`,``);return n?n.toLowerCase():void 0}function he(e,t,n,r){return`/@fs${e.replace(/\\/g,`/`)}?origWidth=${t}&origHeight=${n}&origFormat=${r}`}function ge(e,t){let n=g(e);if(t===n)return!0;let r=v(n),i=r?n.slice(0,-r.length):n;return t.startsWith(`${i}.`)&&t.endsWith(r)}function _e(e){let t=e.replace(/\\/g,`/`),n=t.lastIndexOf(`/_astro/`);if(n!==-1)return t.slice(n);let r=b(k,e).replace(/\\/g,`/`),i=r.indexOf(`.prerender/`);return i===-1?`/${r}`:`/${r.slice(i+11)}`}async function ve(e){if(M.has(e))return M.get(e)??null;if(!l(k))return M.set(e,null),null;async function t(n){let r;try{r=await p(n,{withFileTypes:!0})}catch{return}for(let i of r){let r=y(n,i.name);if(i.isDirectory()){if(ie.has(i.name))continue;let e=await t(r);if(e)return e}else if(ge(e,i.name))return r}}let n=await t(k);if(!n)return M.set(e,null),null;let r=_e(n);return M.set(e,r),r}async function V(e){try{let{metadata:t}=await w(await f(e),{size:4}),n=t?.width??0,r=t?.height??0,i=me(t?.format,e);if(!n||!r||!i)return console.warn(`${T} Missing metadata for "${e}".`),null;let a=ee?he(e,n,r,i):await ve(e);if(!a)return null;let o={src:a,width:n,height:r,format:i};return Object.defineProperty(o,`fsPath`,{value:e,enumerable:!1,configurable:!1,writable:!1}),o}catch(t){return console.warn(`${T} Failed to derive metadata for "${e}".`,t),null}}async function ye(e,t){let n=fe(e,t);for(let e of n){let t=A[e];if(!t)continue;let n=await t(),r=n.default??n;if(F(r))return N(r.src),r}return null}async function be(e,t){let n=pe(e,t);for(let e of n){if(!e||!l(e))continue;let t=await V(e);if(t)return N(e),t}let r=I(e).split(`/`).pop();if(!r)return null;let i=await B(r);if(!i||!l(i))return null;let a=await V(i);return a?(N(i),a):null}async function H(e){if(e==null)return null;if(ae(e)){let t=await e,n=t.default??t;return F(n)?(N(n.src),n):typeof n==`string`?(N(n),n):null}if(P(e)){let t=e;return N(typeof t.src==`string`?t.src:void 0),F(t)?t:null}if(typeof e==`string`){if(oe(e))return e;let t=I(e),n=L(t)?ue():null;return await ye(t,n)||await be(t,n)||null}return null}const xe=/[A-Z]/g;function Se(e){return e.replace(xe,e=>`-${e.toLowerCase()}`)}function U(e){if(e==null)return;if(typeof e==`string`)return e;let t=[];for(let[n,r]of Object.entries(e))r!=null&&t.push(`${Se(n)}:${r}`);return t.length>0?t.join(`;`):void 0}function W([e,t,n=[]]){let r=``;for(let[e,n]of Object.entries(t||{}))e===`style`&&n&&typeof n==`object`?r+=` style="${U(n)}"`:n!==void 0&&(r+=` ${e}="${n}"`);return n.length>0?`<${e}${r}>${n.map(W).join(``)}</${e}>`:`<${e}${r} />`}function Ce(e,t,n=``){if(!t)return{};switch(e){case`css`:return{"--lqip-background":t};case`svg`:return{"--lqip-background":`url('data:image/svg+xml;utf8,${encodeURIComponent(n)}')`};case`color`:return{"--lqip-background":t};default:return{"--lqip-background":`url('${t}')`}}}function we(e){return process.platform===`win32`&&/^\/[A-Za-z]:\//.test(e)?e.slice(1):e}function Te(){return typeof process<`u`&&!!process.versions?.node}async function Ee(e){if(Te())try{return await f(e)}catch{return}}function De(e,t=4){let n=Number(e);return Number.isFinite(n)?Math.min(64,Math.max(4,Math.round(n))):t}async function G(e,t,n,r){try{let i=we(e),a=De(n),o=await Ee(i);if(!o){console.warn(`${T} image not found for:`,e);return}let s=await w(o,{size:a}),c;switch(t){case`color`:c=s.color?.hex;break;case`css`:c=typeof s.css==`object`&&s.css.backgroundImage?s.css.backgroundImage:String(s.css);break;case`svg`:c=s.svg;break;default:c=s.base64;break}return r?console.log(`${T} LQIP (${t}) successfully generated!`):console.log(`${T} LQIP (${t}) successfully generated for:`,e),c}catch(n){console.error(`${T} Error generating LQIP (${t}) in:`,e,`
|
|
4
|
+
`,n);return}}function Oe(e){return/^https?:\/\//.test(e)}const K=y(process.cwd(),`node_modules`,`.cache`,`astro-lqip`),ke=[`jpg`,`jpeg`,`png`,`webp`,`avif`],Ae=[`src`],je=/\.[A-Za-z0-9_-]{5,}\.[A-Za-z0-9]+$/,Me=/^(.+?)\.[A-Za-z0-9_-]{5,}\.[A-Za-z0-9]+$/,q=(()=>{try{let e=import.meta.env?.BASE_URL??`/`;return!e||e===`/`?`/`:e.endsWith(`/`)?e.slice(0,-1):e}catch{return`/`}})(),J=new Map;function Ne(e){if(typeof e!=`string`)return e;let t=e.indexOf(`?`),n=t>=0?e.slice(0,t):e;return q!==`/`&&n.startsWith(q)&&(n=n.slice(q.length)||`/`),n.startsWith(`/`)||(n=`/${n}`),n}async function Pe(){l(K)||await d(K,{recursive:!0})}function Fe(e){let t=e.split(`/`).pop()||``,n=t.match(Me);if(n)return n[1];let r=t.split(`.`);return r.length>=3?r.slice(0,r.length-2).join(`.`):r[0]}async function Ie(e){if(!e)return;if(J.has(e))return J.get(e)||void 0;let t=new Set([`node_modules`,`dist`,`.astro`]);async function n(r){let i;try{i=await p(r)}catch{return}for(let a of i){let i=y(r,a),o;try{o=u(i)}catch{continue}if(o.isDirectory()){if(t.has(a))continue;let e=await n(i);if(e)return e}else if(ke.some(t=>a===`${e}.${t}`))return i}}for(let t of Ae){let r=y(process.cwd(),t);if(l(r)){let t=await n(r);if(t)return J.set(e,t),t}}J.set(e,null)}async function Y(e,t,n,r){if(e?.src){if(Oe(e.src)){await Pe();let i=await fetch(e.src);if(!i.ok)return;let a=await i.arrayBuffer(),o=Buffer.from(a),s=y(K,`astro-lqip-${Math.random().toString(36).slice(2)}.jpg`);await h(s,o);try{return await G(s,t,n,r)}finally{await m(s)}}if(r&&e.src.startsWith(`/@fs/`)){let i=e.src.replace(/^\/@fs/,``).split(`?`)[0];return await G(i,t,n,r)}if(!r){let i=e.src,a=Ne(i),o=a.replace(/^\//,``);if(o){let e=[y(process.cwd(),`dist`,`client`,o),y(process.cwd(),`dist`,o)];for(let i of e)if(l(i))return await G(i,t,n,r)}let s=a.split(`/`).pop()??``;if(je.test(s)){let e=Fe(a),i=await Ie(e);if(i)return console.log(`${T} fallback recursive source found:`,i),await G(i,t,n,r);console.warn(`${T} original source not found recursively for basename:`,e)}}}}async function X({src:e,lqip:t=`base64`,lqipSize:n=4,styleProps:r={},forbiddenVars:i=[`--lqip-background`,`--z-index`,`--opacity`],isDevelopment:a}){let o=await H(e)??null,s;o&&(s=await Y(typeof o==`string`?{src:o}:o,t,n,a));let c=``;t===`svg`&&Array.isArray(s)&&(c=W(s));let l=Ce(t,s,c);for(let e of Object.keys(r))i.includes(e)&&console.warn(`${T} The CSS variable “${e}” should not be passed in style because it can override the functionality of LQIP.`);let u={...r,...l};return{lqipImage:s,svgHTML:c,lqipStyle:l,combinedStyle:u,resolvedSrc:o}}const Le=e({factory:async(e,n)=>{let{class:r,lqip:i=`base64`,lqipSize:a=4,pictureAttributes:s={},...c}=n,{combinedStyle:l,resolvedSrc:u}=await X({src:c.src,lqip:i,lqipSize:a,styleProps:s.style??{},forbiddenVars:[],isDevelopment:import.meta.env.MODE===`development`});return await t(e,`Picture`,o,{...c,class:r,src:u??c.src,pictureAttributes:{"data-astro-lqip":``,...s,style:l},onload:`
|
|
5
|
+
parentElement.style.setProperty("--z-index", 1);
|
|
6
|
+
parentElement.style.setProperty("--opacity", 0);
|
|
7
|
+
`})}}),Re=e({factory:async(e,n)=>{let{class:o,lqip:s=`base64`,lqipSize:c=4,parentAttributes:l={},...u}=n,{combinedStyle:d,resolvedSrc:f}=await X({src:u.src,lqip:s,lqipSize:c,styleProps:l.style??{},forbiddenVars:[],isDevelopment:import.meta.env.MODE===`development`});return r`
|
|
8
|
+
<div ${i({...l,class:[l.class,o].filter(Boolean).join(` `)||void 0,"data-astro-lqip":``,style:U(d)})}>
|
|
9
|
+
${t(e,`Image`,a,{...u,class:o,src:f??u.src,onload:`
|
|
10
|
+
parentElement.style.setProperty("--z-index", 1);
|
|
11
|
+
parentElement.style.setProperty("--opacity", 0);
|
|
12
|
+
`})}
|
|
13
|
+
</div>
|
|
14
|
+
`}});async function ze({src:e,cssVariable:t=`--background`,format:n=`webp`,widths:r,width:i,height:a,quality:o,fit:l,lqip:u=`base64`,isDevelopment:d}){let f=i,p=a,m=await H(e);if(!m)throw Error(`${T} Could not resolve background image in "${e}"`);let h=typeof m==`string`&&Ue(m)?m:null;if(h&&(!f||!p)){try{let{width:e,height:t}=await c(h);f??=e,p??=t}catch(e){console.warn(`${T} Failed to infer remote background size for "${h}".`,e)}if(!f||!p)throw Error(`${T} Remote background images require width and height. Provide both props or ensure the URL is reachable in your Astro config 'image.domains'.`)}let g=m,_,v=typeof m==`string`?{src:m}:m;if(v){let e=await Y(v,u,12,d);_=qe(u,typeof e==`string`?e:void 0)}let y=He((Array.isArray(n)?n:[n]).filter(e=>typeof e==`string`&&e.trim().length>0)),b=y.length?y:[`webp`],x=Array.isArray(n),S={src:g,widths:r,width:f,height:p,quality:o,fit:l},C=await Promise.all(b.map(e=>s({...S,format:e}))),w=C.map((e,t)=>({format:b[t],mimeType:Z(b[t]),fallbackSrc:e.src,sources:We(e.srcSet?.attribute)})),E=Be(t),D=w[0]?.sources??[],O=Array.isArray(r)&&r.length>0,k=e=>Ke({formatSources:w,optimizedImages:C,isFormatArray:x,selector:e,layer:_});return{style:O?Ge({referenceSources:D,baseVariable:E,createValue:k}):`${E}: ${k()}`,resolvedSrc:m}}function Be(e){let t=e.trim();return t?t.startsWith(`--`)?t:`--${t}`:`--background`}function Ve(e){return e.trim().toLowerCase()}function He(e){let t=[`avif`,`webp`,`png`,`jpeg`,`jpg`,`svg`],n=new Set;return e.map(e=>Ve(e)).filter(e=>!e||n.has(e)?!1:(n.add(e),!0)).sort((e,n)=>{let r=t.indexOf(e),i=t.indexOf(n),a=r===-1?2**53-1:r,o=i===-1?2**53-1:i;return a===o?e.localeCompare(n):a-o})}function Ue(e){return typeof e==`string`&&/^https?:\/\//.test(e)}function Z(e){switch(e){case`avif`:return`image/avif`;case`webp`:return`image/webp`;case`png`:return`image/png`;case`jpeg`:case`jpg`:return`image/jpeg`;case`svg`:return`image/svg+xml`;default:return`image/${e}`}}function We(e){if(!e)return[];let t=e.split(`,`).map(e=>e.trim()).filter(Boolean).map(e=>{let t=e.lastIndexOf(` `);if(t===-1)return{url:e};let n=e.slice(0,t),r=e.slice(t+1).match(/(?<value>\d+(?:\.\d+)?)(?<unit>[wx])/i);return{url:n,width:r?.groups?.unit?.toLowerCase()===`w`?Number.parseFloat(r.groups.value):void 0}}),n=new Map(t.map(e=>[e.url,e]));return Array.from(n.values()).sort((e,t)=>(e.width??0)-(t.width??0))}function Ge({referenceSources:e,baseVariable:t,createValue:n}){if(!e.length)return`${t}: ${n()}`;let r=[`${t}: ${n(e=>e.sources[e.sources.length-1])}`],i=[{suffix:`-small`,match:e=>typeof e==`number`&&e<768},{suffix:`-medium`,match:e=>typeof e==`number`&&e>=768&&e<=1200},{suffix:`-large`,match:e=>typeof e==`number`&&e>1200&&e<=1920},{suffix:`-xlarge`,match:e=>typeof e==`number`&&e>1920}];for(let a of i){let i=[...e].reverse().find(e=>a.match(e.width));if(!i||typeof i.width!=`number`)continue;let o=n(e=>e.sources.find(e=>e.width===i.width));r.push(`${t}${a.suffix}: ${o}`)}return r.join(`; `)}function Q(e,t){return t?.(e)??e.sources[e.sources.length-1]??{url:e.fallbackSrc}}function $(e,t){return e&&t?`${e}, ${t}`:e||t||``}function Ke({formatSources:e,optimizedImages:t,isFormatArray:n,selector:r,layer:i}){if(!e.length){let e=t[0]?.src??``;return $(e?`url('${e}')`:``,i)}if(!n){let t=e[0];return $(`url('${Q(t,r).url??t.fallbackSrc}')`,i)}return $(`image-set(${e.map(e=>`url('${Q(e,r).url??e.fallbackSrc}') type('${e.mimeType}')`).join(`, `)})`,i)}function qe(e,t){if(!(!e||!t))return e===`color`?`linear-gradient(${t}, ${t})`:`url("${t}")`}const Je=e({factory:async(e,t,a)=>{let o=import.meta.env.MODE===`development`,{style:s}=await ze({...t,isDevelopment:o}),c=await n(e,a.default);return r`
|
|
15
|
+
<div data-astro-lqip-bg ${i({style:s})}>
|
|
16
|
+
${c}
|
|
17
|
+
</div>
|
|
18
|
+
`}}),Ye=Re,Xe=Le,Ze=Je;export{Ze as Background,Ye as Image,Xe as Picture};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "astro-lqip",
|
|
3
|
-
"version": "1.8.
|
|
3
|
+
"version": "1.8.1",
|
|
4
4
|
"description": "🖼️ Native extended Astro components for generating low-quality image placeholders (LQIP), compatible with <img>, <picture> and CSS background images.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"astro",
|
|
@@ -23,11 +23,19 @@
|
|
|
23
23
|
"license": "MIT",
|
|
24
24
|
"author": "Felix Icaza",
|
|
25
25
|
"type": "module",
|
|
26
|
+
"types": "./dist/index.d.mts",
|
|
26
27
|
"exports": {
|
|
27
|
-
"
|
|
28
|
+
".": {
|
|
29
|
+
"types": "./dist/index.d.mts",
|
|
30
|
+
"import": "./dist/index.mjs"
|
|
31
|
+
},
|
|
32
|
+
"./components": {
|
|
33
|
+
"types": "./dist/index.d.mts",
|
|
34
|
+
"import": "./dist/index.mjs"
|
|
35
|
+
}
|
|
28
36
|
},
|
|
29
37
|
"files": [
|
|
30
|
-
"
|
|
38
|
+
"dist"
|
|
31
39
|
],
|
|
32
40
|
"sideEffects": false,
|
|
33
41
|
"simple-git-hooks": {
|
|
@@ -41,9 +49,12 @@
|
|
|
41
49
|
},
|
|
42
50
|
"devDependencies": {
|
|
43
51
|
"@eslint/js": "9.39.4",
|
|
52
|
+
"@tsdown/css": "0.21.7",
|
|
53
|
+
"@types/node": "25.5.0",
|
|
44
54
|
"@typescript-eslint/parser": "8.57.0",
|
|
45
|
-
"astro": "6.
|
|
46
|
-
"bumpp": "
|
|
55
|
+
"astro": "6.1.2",
|
|
56
|
+
"bumpp": "11.0.1",
|
|
57
|
+
"concurrently": "9.2.1",
|
|
47
58
|
"eslint": "9.39.4",
|
|
48
59
|
"eslint-plugin-astro": "1.6.0",
|
|
49
60
|
"eslint-plugin-jsonc": "3.1.2",
|
|
@@ -52,22 +63,38 @@
|
|
|
52
63
|
"eslint-plugin-yml": "3.3.1",
|
|
53
64
|
"globals": "17.4.0",
|
|
54
65
|
"jiti": "2.6.1",
|
|
66
|
+
"lightningcss": "1.32.0",
|
|
55
67
|
"nano-staged": "0.9.0",
|
|
56
68
|
"neostandard": "0.13.0",
|
|
57
|
-
"simple-git-hooks": "2.13.1"
|
|
69
|
+
"simple-git-hooks": "2.13.1",
|
|
70
|
+
"tsdown": "0.21.7"
|
|
58
71
|
},
|
|
59
72
|
"peerDependencies": {
|
|
60
73
|
"astro": ">=3.3.0"
|
|
61
74
|
},
|
|
62
75
|
"scripts": {
|
|
76
|
+
"build": "tsdown",
|
|
63
77
|
"build:docs": "pnpm --filter ./docs -r build",
|
|
64
|
-
"
|
|
78
|
+
"build:watch": "tsdown --watch",
|
|
79
|
+
"dev": "concurrently \"pnpm dev:lib\" \"pnpm dev:fixtures\"",
|
|
65
80
|
"dev:docs": "pnpm --filter ./docs -r dev",
|
|
81
|
+
"dev:fixtures": "pnpm --filter \"./tests/fixtures/**\" -r --parallel dev",
|
|
82
|
+
"dev:lib": "tsdown --watch",
|
|
66
83
|
"lint": "eslint --cache .",
|
|
67
84
|
"lint:fix": "eslint --cache --fix .",
|
|
68
|
-
"preview": "pnpm -r preview",
|
|
69
85
|
"release": "bumpp",
|
|
86
|
+
"sync:types": " pnpm --filter \"./tests/fixtures/**\" astro sync",
|
|
87
|
+
"test": "pnpm test:fixtures",
|
|
70
88
|
"test:fixtures": "pnpm --filter \"./tests/fixtures/**\" -r build",
|
|
71
|
-
"test:fixtures:
|
|
89
|
+
"test:fixtures:custom-base": "pnpm --filter \"./tests/fixtures/custom-base\" build",
|
|
90
|
+
"test:fixtures:default": "pnpm --filter \"./tests/fixtures/default\" build",
|
|
91
|
+
"test:fixtures:preview": "pnpm --filter \"./tests/fixtures/**\" -r preview",
|
|
92
|
+
"test:fixtures:preview:custom-base": "pnpm --filter \"./tests/fixtures/custom-base\" preview",
|
|
93
|
+
"test:fixtures:preview:default": "pnpm --filter \"./tests/fixtures/default\" preview",
|
|
94
|
+
"test:fixtures:preview:ssr": "pnpm --filter \"./tests/fixtures/ssr-node\" preview",
|
|
95
|
+
"test:fixtures:preview:ssr:custom-base": "pnpm --filter \"./tests/fixtures/ssr-node-custom-base\" preview",
|
|
96
|
+
"test:fixtures:ssr": "pnpm --filter \"./tests/fixtures/ssr-node\" build",
|
|
97
|
+
"test:fixtures:ssr:custom-base": "pnpm --filter \"./tests/fixtures/ssr-node-custom-base\" build",
|
|
98
|
+
"typecheck": "tsc --noEmit"
|
|
72
99
|
}
|
|
73
100
|
}
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
import type { ImageTransform } from '../types'
|
|
3
|
-
|
|
4
|
-
import { useLqipBackground } from '../utils/useLqipBackground'
|
|
5
|
-
|
|
6
|
-
import '../styles/background.css'
|
|
7
|
-
|
|
8
|
-
type Props = ImageTransform
|
|
9
|
-
|
|
10
|
-
const props = Astro.props as Props
|
|
11
|
-
const isDevelopment = import.meta.env.MODE === 'development'
|
|
12
|
-
|
|
13
|
-
const { style: backgroundStyle } = await useLqipBackground({
|
|
14
|
-
...props,
|
|
15
|
-
isDevelopment
|
|
16
|
-
})
|
|
17
|
-
---
|
|
18
|
-
|
|
19
|
-
<div data-astro-lqip-bg style={backgroundStyle}>
|
|
20
|
-
<slot />
|
|
21
|
-
</div>
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
import type { LocalImageProps, RemoteImageProps } from 'astro:assets'
|
|
3
|
-
import type { HTMLAttributes } from 'astro/types'
|
|
4
|
-
import type { Props as LqipProps } from '../types'
|
|
5
|
-
|
|
6
|
-
import { useLqipImage } from '../utils/useLqipImage'
|
|
7
|
-
|
|
8
|
-
import { Image as ImageComponent } from 'astro:assets'
|
|
9
|
-
|
|
10
|
-
import '../styles/lqip.css'
|
|
11
|
-
|
|
12
|
-
type Props = (LocalImageProps | RemoteImageProps) & LqipProps & {
|
|
13
|
-
parentAttributes?: HTMLAttributes<'div'>
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
const { class: className, lqip = 'base64', lqipSize = 4, parentAttributes = {}, ...props } = Astro.props as Props
|
|
17
|
-
|
|
18
|
-
const isDevelopment = import.meta.env.MODE === 'development'
|
|
19
|
-
|
|
20
|
-
const { combinedStyle, resolvedSrc } = await useLqipImage({
|
|
21
|
-
src: props.src,
|
|
22
|
-
lqip,
|
|
23
|
-
lqipSize,
|
|
24
|
-
styleProps: (parentAttributes.style ?? {}) as Record<string, string | number | undefined>,
|
|
25
|
-
forbiddenVars: [],
|
|
26
|
-
isDevelopment
|
|
27
|
-
})
|
|
28
|
-
|
|
29
|
-
const componentProps = {
|
|
30
|
-
...props,
|
|
31
|
-
src: resolvedSrc ?? props.src
|
|
32
|
-
}
|
|
33
|
-
const combinedParentAttributes = {
|
|
34
|
-
...parentAttributes,
|
|
35
|
-
style: combinedStyle
|
|
36
|
-
}
|
|
37
|
-
---
|
|
38
|
-
|
|
39
|
-
<div class={className} data-astro-lqip {...combinedParentAttributes}>
|
|
40
|
-
<ImageComponent
|
|
41
|
-
{...componentProps as LocalImageProps | RemoteImageProps}
|
|
42
|
-
class={className}
|
|
43
|
-
onload="parentElement.style.setProperty('--z-index', 1), parentElement.style.setProperty('--opacity', 0)"
|
|
44
|
-
/>
|
|
45
|
-
</div>
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
import type { Props as AstroPictureProps } from 'astro/components/Picture.astro'
|
|
3
|
-
import type { Props as LqipProps } from '../types'
|
|
4
|
-
|
|
5
|
-
import { useLqipImage } from '../utils/useLqipImage'
|
|
6
|
-
|
|
7
|
-
import { Picture as PictureComponent } from 'astro:assets'
|
|
8
|
-
|
|
9
|
-
import '../styles/lqip.css'
|
|
10
|
-
|
|
11
|
-
type Props = AstroPictureProps & LqipProps
|
|
12
|
-
|
|
13
|
-
const { class: className, lqip = 'base64', lqipSize = 4, pictureAttributes = {}, ...props } = Astro.props as Props
|
|
14
|
-
|
|
15
|
-
const isDevelopment = import.meta.env.MODE === 'development'
|
|
16
|
-
|
|
17
|
-
const { combinedStyle, resolvedSrc } = await useLqipImage({
|
|
18
|
-
src: props.src,
|
|
19
|
-
lqip,
|
|
20
|
-
lqipSize,
|
|
21
|
-
styleProps: (pictureAttributes.style ?? {}) as Record<string, string | number | undefined>,
|
|
22
|
-
forbiddenVars: [],
|
|
23
|
-
isDevelopment
|
|
24
|
-
})
|
|
25
|
-
|
|
26
|
-
const componentProps = {
|
|
27
|
-
...props,
|
|
28
|
-
src: resolvedSrc ?? props.src
|
|
29
|
-
}
|
|
30
|
-
const combinedPictureAttributes = {
|
|
31
|
-
...pictureAttributes,
|
|
32
|
-
style: combinedStyle
|
|
33
|
-
}
|
|
34
|
-
---
|
|
35
|
-
|
|
36
|
-
<PictureComponent
|
|
37
|
-
{...(componentProps as AstroPictureProps)}
|
|
38
|
-
class={className}
|
|
39
|
-
pictureAttributes={{ 'data-astro-lqip': '', ...combinedPictureAttributes }}
|
|
40
|
-
onload="parentElement.style.setProperty('--z-index', 1), parentElement.style.setProperty('--opacity', 0)"
|
|
41
|
-
/>
|
package/src/constants/index.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export const PREFIX = '[astro-lqip]'
|
package/src/index.ts
DELETED
package/src/styles/lqip.css
DELETED
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
[data-astro-lqip] {
|
|
2
|
-
--opacity: 1;
|
|
3
|
-
--z-index: 0;
|
|
4
|
-
|
|
5
|
-
position: relative;
|
|
6
|
-
display: inline-block;
|
|
7
|
-
width: fit-content;
|
|
8
|
-
height: fit-content;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
[data-astro-lqip]::after {
|
|
12
|
-
content: "";
|
|
13
|
-
inset: 0;
|
|
14
|
-
width: 100%;
|
|
15
|
-
height: 100%;
|
|
16
|
-
position: absolute;
|
|
17
|
-
pointer-events: none;
|
|
18
|
-
transition: opacity 1s;
|
|
19
|
-
opacity: var(--opacity);
|
|
20
|
-
z-index: var(--z-index);
|
|
21
|
-
background: var(--lqip-background);
|
|
22
|
-
background-size: cover;
|
|
23
|
-
background-position: 50% 50%;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
[data-astro-lqip] img {
|
|
27
|
-
z-index: 1;
|
|
28
|
-
position: relative;
|
|
29
|
-
overflow: hidden;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
@media (scripting: none) {
|
|
33
|
-
[data-astro-lqip] {
|
|
34
|
-
--opacity: 0;
|
|
35
|
-
--z-index: 1;
|
|
36
|
-
}
|
|
37
|
-
}
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import type { LqipType } from './lqip.type'
|
|
2
|
-
|
|
3
|
-
export type ComponentsOptions = {
|
|
4
|
-
src: string | object
|
|
5
|
-
lqip: LqipType
|
|
6
|
-
lqipSize: number
|
|
7
|
-
styleProps: Record<string, string | number | undefined>
|
|
8
|
-
forbiddenVars: string[]
|
|
9
|
-
isDevelopment: boolean | undefined
|
|
10
|
-
}
|
|
@@ -1,4 +0,0 @@
|
|
|
1
|
-
export type ImagePath = string | { src: string } | Promise<{ default: { src: string } }>
|
|
2
|
-
export type ResolvedImage = { src: string, width?: number, height?: number, [k: string]: unknown }
|
|
3
|
-
export type ImportModule = Record<string, unknown> & { default?: unknown }
|
|
4
|
-
export type GlobMap = Record<string, () => Promise<ImportModule>>
|
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* This file defines the types for the ImageTransform, which is used to optimize images for the Background component.
|
|
3
|
-
* Extracted from Astro's Types, reference: https://github.com/withastro/astro/blob/2dcd8d54c6fb00183228d757bf684e67c79029d8/packages/astro/src/assets/types.ts#L82
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
type ValidOutputFormats = ['avif', 'png', 'webp', 'jpeg', 'jpg', 'svg']
|
|
7
|
-
type ImageQualityPreset = 'low' | 'mid' | 'high' | 'max' | (string & {})
|
|
8
|
-
type ImageQuality = ImageQualityPreset | number
|
|
9
|
-
type ImageOutputFormat = ValidOutputFormats[number] | (string & {})
|
|
10
|
-
type ImageFit = 'fill' | 'contain' | 'cover' | 'none' | 'scale-down' | (string & {})
|
|
11
|
-
|
|
12
|
-
export interface ImageTransform {
|
|
13
|
-
/**
|
|
14
|
-
* Path of the image that will be used as the background.
|
|
15
|
-
* It can be a relative path, an absolute path, an alias or ImageMetadata.
|
|
16
|
-
*/
|
|
17
|
-
src: string | ImageMetadata
|
|
18
|
-
/**
|
|
19
|
-
* CSS custom property that will receive the generated background.
|
|
20
|
-
* (defaults to --background)
|
|
21
|
-
*/
|
|
22
|
-
cssVariable?: string
|
|
23
|
-
/**
|
|
24
|
-
* LQIP placeholder strategy for Background (defaults to 'base64').
|
|
25
|
-
* Only 'base64' and 'color' are supported to keep CSS-friendly values.
|
|
26
|
-
*/
|
|
27
|
-
lqip?: 'base64' | 'color'
|
|
28
|
-
/**
|
|
29
|
-
* Specifies one or more output formats (first entry is preferred fallback).
|
|
30
|
-
* Accepts a single `ImageOutputFormat` or an ordered array.
|
|
31
|
-
* (defaults to webp)
|
|
32
|
-
*/
|
|
33
|
-
format?: ImageOutputFormat | ImageOutputFormat[] | undefined
|
|
34
|
-
/**
|
|
35
|
-
* Explicit widths used for responsive variants.
|
|
36
|
-
*/
|
|
37
|
-
widths?: number[] | undefined
|
|
38
|
-
/**
|
|
39
|
-
* Single width fallback when `widths` is omitted.
|
|
40
|
-
*/
|
|
41
|
-
width?: number | undefined
|
|
42
|
-
/**
|
|
43
|
-
* Target height for generated assets.
|
|
44
|
-
*/
|
|
45
|
-
height?: number | undefined
|
|
46
|
-
/**
|
|
47
|
-
* Compression quality preset or numeric percentage.
|
|
48
|
-
*/
|
|
49
|
-
quality?: ImageQuality | undefined
|
|
50
|
-
/**
|
|
51
|
-
* Object-fit behavior applied during resizing.
|
|
52
|
-
*/
|
|
53
|
-
fit?: ImageFit | undefined
|
|
54
|
-
}
|
package/src/types/index.ts
DELETED
package/src/types/lqip.type.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export type LqipType = 'color' | 'css' | 'base64' | 'svg'
|
package/src/types/props.type.ts
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import type { LqipType } from './lqip.type'
|
|
2
|
-
|
|
3
|
-
export type Props = {
|
|
4
|
-
/**
|
|
5
|
-
* LQIP type.
|
|
6
|
-
* This can be 'color', 'css', 'svg' or 'base64'.
|
|
7
|
-
* The default value is 'base64'.
|
|
8
|
-
*/
|
|
9
|
-
lqip?: LqipType
|
|
10
|
-
/**
|
|
11
|
-
* Size of the LQIP image in pixels.
|
|
12
|
-
* This value should be between 4 and 64.
|
|
13
|
-
* The default value is 4.
|
|
14
|
-
*/
|
|
15
|
-
lqipSize?: number
|
|
16
|
-
}
|
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
import { readFile } from 'node:fs/promises'
|
|
2
|
-
|
|
3
|
-
import type { GetSVGReturn, LqipType } from '../types'
|
|
4
|
-
|
|
5
|
-
import { PREFIX } from '../constants'
|
|
6
|
-
|
|
7
|
-
import { getPlaiceholder } from 'plaiceholder'
|
|
8
|
-
|
|
9
|
-
function normalizeFsPath(path: string) {
|
|
10
|
-
if (process.platform === 'win32' && /^\/[A-Za-z]:\//.test(path)) {
|
|
11
|
-
return path.slice(1)
|
|
12
|
-
}
|
|
13
|
-
return path
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
function isNode() {
|
|
17
|
-
return typeof process !== 'undefined' && !!process.versions?.node
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
async function readIfExists(path: string): Promise<Buffer | undefined> {
|
|
21
|
-
if (!isNode()) return undefined
|
|
22
|
-
try {
|
|
23
|
-
return await readFile(path)
|
|
24
|
-
} catch {
|
|
25
|
-
return undefined
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export async function generateLqip(
|
|
30
|
-
imagePath: string,
|
|
31
|
-
lqipType: LqipType,
|
|
32
|
-
lqipSize: number,
|
|
33
|
-
isDevelopment: boolean | undefined
|
|
34
|
-
) {
|
|
35
|
-
try {
|
|
36
|
-
const normalizedPath = normalizeFsPath(imagePath)
|
|
37
|
-
|
|
38
|
-
const buffer = await readIfExists(normalizedPath)
|
|
39
|
-
|
|
40
|
-
if (!buffer) {
|
|
41
|
-
console.warn(`${PREFIX} image not found for:`, imagePath)
|
|
42
|
-
return undefined
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
const plaiceholderResult = await getPlaiceholder(buffer, { size: lqipSize })
|
|
46
|
-
let lqipValue: string | GetSVGReturn | undefined
|
|
47
|
-
|
|
48
|
-
switch (lqipType) {
|
|
49
|
-
case 'color':
|
|
50
|
-
lqipValue = plaiceholderResult.color?.hex
|
|
51
|
-
break
|
|
52
|
-
case 'css':
|
|
53
|
-
lqipValue = typeof plaiceholderResult.css === 'object' && plaiceholderResult.css.backgroundImage
|
|
54
|
-
? plaiceholderResult.css.backgroundImage
|
|
55
|
-
: String(plaiceholderResult.css)
|
|
56
|
-
break
|
|
57
|
-
case 'svg':
|
|
58
|
-
lqipValue = plaiceholderResult.svg
|
|
59
|
-
break
|
|
60
|
-
case 'base64':
|
|
61
|
-
default:
|
|
62
|
-
lqipValue = plaiceholderResult.base64
|
|
63
|
-
break
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
if (isDevelopment) {
|
|
67
|
-
console.log(`${PREFIX} LQIP (${lqipType}) successfully generated!`)
|
|
68
|
-
} else {
|
|
69
|
-
console.log(`${PREFIX} LQIP (${lqipType}) successfully generated for:`, imagePath)
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
return lqipValue
|
|
73
|
-
} catch (err) {
|
|
74
|
-
console.error(`${PREFIX} Error generating LQIP (${lqipType}) in:`, imagePath, '\n', err)
|
|
75
|
-
return undefined
|
|
76
|
-
}
|
|
77
|
-
}
|