astro-lqip 1.8.1 → 1.8.2

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 CHANGED
@@ -1,46 +1,55 @@
1
1
  <a href="https://astro-lqip.web.app/">
2
- <img src="./assets/logo.png" alt="Astro LQIP Logo" width="200" height="200" />
2
+ ![astro-lqip](https://raw.githubusercontent.com/felixicaza/astro-lqip/HEAD/.github/assets/astro-lqip.jpg)
3
3
  </a>
4
4
 
5
5
  # 🖼️ astro-lqip
6
6
 
7
- [![GitHub Release](https://img.shields.io/github/v/release/felixicaza/astro-lqip?logo=npm)](https://npmx.dev/package/astro-lqip)
8
- [![GitHub License](https://img.shields.io/github/license/felixicaza/astro-lqip)](https://github.com/felixicaza/astro-lqip/blob/main/LICENSE)
7
+ [![npm version](https://img.shields.io/npm/v/astro-lqip?color=4dae5f&logo=npm&logoColor=888888&labelColor=ffffff)](https://npmx.dev/package/astro-lqip)
8
+ ![GitHub actions workflow tests status](https://img.shields.io/github/actions/workflow/status/felixicaza/astro-lqip/tests.yml?color=4dae5f&logo=rocket&logoColor=888888&label=tests&labelColor=ffffff)
9
+ [![license](https://img.shields.io/github/license/felixicaza/astro-lqip?color=4dae5f&logo=googledocs&logoColor=888888&labelColor=ffffff)](https://github.com/felixicaza/astro-lqip/blob/main/LICENSE)
9
10
 
10
- Native extended Astro components for generating low-quality image placeholders (LQIP), compatible with `<img>`, `<picture>` and CSS background images.
11
+ Native extended Astro components for generating low-quality image placeholders (LQIP), compatible with `<Image>`, `<Picture>` and CSS background images.
11
12
 
12
13
  ## ✨ Features
13
14
  - 🖼️ Supports both `<Image>` and `<Picture>` components and CSS background images.
14
15
  - 🎨 Multiple LQIP techniques: base64, solid color, CSS via gradients and SVG.
15
- - 🚀 Easy to use, just replace the native Astro components with the ones from [astro-lqip](https://astro-lqip.web.app/).
16
+ - 🚀 Easy to use, just replace the native Astro components by [astro-lqip](https://astro-lqip.web.app/).
16
17
  - ⚡️ Support images as static imports or using string paths.
17
18
  - 🔧 Fully compatible with [Astro's image optimization features](https://docs.astro.build/en/guides/images/).
18
19
  - 🌍 Supports both local and remote images.
19
20
  - ⚙️ Supports SSR mode with [Node Adapter](https://docs.astro.build/en/guides/integrations-guide/node/).
20
21
 
21
- ## ⬇️ Installation
22
+ ## 📦 Installation
22
23
 
23
- NPM:
24
+ You can install [`astro-lqip`](https://astro-lqip.web.app/) using npm:
24
25
 
25
- ```bash
26
- npm install astro-lqip
26
+ ```sh
27
+ $ npm install astro-lqip
27
28
  ```
28
29
 
29
- PNPM:
30
+ <details>
31
+ <summary>Using a different package manager?</summary>
32
+ <br/>
30
33
 
31
- ```bash
32
- pnpm add astro-lqip
33
- ```
34
+ Using pnpm:
35
+ ```sh
36
+ $ pnpm add astro-lqip
37
+ ```
34
38
 
35
- Yarn:
39
+ Using yarn:
40
+ ```sh
41
+ $ yarn add astro-lqip
42
+ ```
36
43
 
37
- ```bash
38
- yarn add astro-lqip
39
- ```
44
+ Using bun:
45
+ ```sh
46
+ $ bun add astro-lqip
47
+ ```
48
+ </details>
40
49
 
41
50
  ## 🚀 Usage
42
51
 
43
- In your current Astro project, just replace the import of the native Astro `<Image>` or `<Picture>` components with the ones provided by [astro-lqip](https://astro-lqip.web.app/) or import the `<Background>` component for CSS background images.
52
+ In your current Astro project, just replace the import of the native Astro `<Image>` or `<Picture>` components by [astro-lqip](https://astro-lqip.web.app/) or import the `<Background>` component for optimized CSS background images.
44
53
 
45
54
  ### Image
46
55
 
@@ -92,219 +101,294 @@ import backgroundImage from '/src/assets/images/background-image.png';
92
101
 
93
102
  > [!TIP]
94
103
  > Since version `1.6.0`, you can also put the image path as string directly in the `src` prop. Support absolute paths in `src`, relative paths and alias.
95
-
96
- Example with absolute path:
97
-
98
- ```astro
99
- ---
100
- import { Image, Picture, Background } from 'astro-lqip/components';
101
- ---
102
-
103
- <Image src="/src/assets/images/image.png" alt="Cover Image" width={220} height={220} />
104
- <Picture src="/src/assets/images/other-image.png" alt="Other Image" width={220} height={220} />
105
- <Background src="/src/assets/images/background-image.png">
106
- <section>
107
- <p>Optimized background</p>
108
- </section>
109
- </Background>
110
-
111
- <style>
112
- section {
113
- background-image: var(--background);
114
- background-size: cover;
115
- background-position: center;
116
- }
117
- </style>
118
- ```
119
-
120
- Example with relative path:
121
-
122
- ```astro
123
- ---
124
- import { Image, Picture, Background } from 'astro-lqip/components';
125
- ---
126
-
127
- <!-- assuming you are on the path `/src/pages/index.astro` -->
128
- <Image src="../assets/images/image.png" alt="Cover Image" width={220} height={220} />
129
- <Picture src="../assets/images/other-image.png" alt="Other Image" width={220} height={220} />
130
- <Background src="../assets/images/background-image.png">
131
- <section>
132
- <p>Optimized background</p>
133
- </section>
134
- </Background>
135
-
136
- <style>
137
- section {
138
- background-image: var(--background);
139
- background-size: cover;
140
- background-position: center;
141
- }
142
- </style>
143
- ```
144
-
145
- Example with alias:
146
-
147
- ```astro
148
- ---
149
- import { Image, Picture, Background } from 'astro-lqip/components';
150
- ---
151
-
152
- <Image src="@/assets/images/image.png" alt="Cover Image" width={220} height={220} />
153
- <Picture src="@/assets/images/other-image.png" alt="Other Image" width={220} height={220} />
154
- <Background src="@/assets/images/background-image.png">
155
- <section>
156
- <p>Optimized background</p>
157
- </section>
158
- </Background>
159
-
160
- <style>
161
- section {
162
- background-image: var(--background);
163
- background-size: cover;
164
- background-position: center;
165
- }
166
- </style>
167
- ```
104
+ >
105
+ >
106
+ > <details>
107
+ > <summary>Example with absolute path</summary>
108
+ > <br/>
109
+ >
110
+ > ```astro
111
+ > ---
112
+ > import { Image, Picture, Background } from 'astro-lqip/components';
113
+ > ---
114
+ >
115
+ > <Image src="/src/assets/images/image.png" alt="Cover Image" width={220} height={220} />
116
+ > <Picture src="/src/assets/images/other-image.png" alt="Other Image" width={220} height={220} />
117
+ > <Background src="/src/assets/images/background-image.png">
118
+ > <section>
119
+ > <p>Optimized background</p>
120
+ > </section>
121
+ > </Background>
122
+ >
123
+ > <style>
124
+ > section {
125
+ > background-image: var(--background);
126
+ > background-size: cover;
127
+ > background-position: center;
128
+ > }
129
+ > </style>
130
+ > ```
131
+ > </details>
132
+ >
133
+ > <details>
134
+ > <summary>Example with relative path</summary>
135
+ > <br/>
136
+ >
137
+ > ```astro
138
+ > ---
139
+ > import { Image, Picture, Background } from 'astro-lqip/components';
140
+ > ---
141
+ >
142
+ > <!-- assuming you are on the path `/src/pages/index.astro` -->
143
+ > <Image src="../assets/images/image.png" alt="Cover Image" width={220} height={220} />
144
+ > <Picture src="../assets/images/other-image.png" alt="Other Image" width={220} height={220} />
145
+ > <Background src="../assets/images/background-image.png">
146
+ > <section>
147
+ > <p>Optimized background</p>
148
+ > </section>
149
+ > </Background>
150
+ >
151
+ > <style>
152
+ > section {
153
+ > background-image: var(--background);
154
+ > background-size: cover;
155
+ > background-position: center;
156
+ > }
157
+ > </style>
158
+ > ```
159
+ > </details>
160
+ >
161
+ > <details>
162
+ > <summary>Example with alias</summary>
163
+ > <br/>
164
+ >
165
+ > ```astro
166
+ > ---
167
+ > import { Image, Picture, Background } from 'astro-lqip/components';
168
+ > ---
169
+ >
170
+ > <Image src="@/assets/images/image.png" alt="Cover Image" width={220} height={220} />
171
+ > <Picture src="@/assets/images/other-image.png" alt="Other Image" width={220} height={220} />
172
+ > <Background src="@/assets/images/background-image.png">
173
+ > <section>
174
+ > <p>Optimized background</p>
175
+ > </section>
176
+ > </Background>
177
+ >
178
+ > <style>
179
+ > section {
180
+ > background-image: var(--background);
181
+ > background-size: cover;
182
+ > background-position: center;
183
+ > }
184
+ > </style>
185
+ > ```
186
+ > </details>
168
187
 
169
188
  Learn how to configure path aliasing in the [Astro documentation](https://docs.astro.build/en/guides/typescript/#import-aliases). If you want more examples of uses you can see the [Usage Tips](https://astro-lqip.web.app/usage-tips/) page.
170
189
 
171
190
  ## ⚙️ Props
172
191
 
173
- ### Image and Picture
192
+ ### 🔩 Image and Picture
174
193
 
175
194
  Both `<Image>` and `<Picture>` components support all the props of the [native Astro components](https://docs.astro.build/en/reference/modules/astro-assets/), but adds a couple of props for LQIP management:
176
195
 
177
- - `lqip`: The LQIP type to use. It can be one of the following:
178
- - `base64`: Generates a Base64-encoded LQIP image. (default option)
179
- - `color`: Generates a solid color placeholder. Not compatible with `lqipSize`.
180
- - `css`: Generates a CSS-based LQIP image.
181
- - `svg`: Generates an SVG-based LQIP image.
182
- - `lqipSize`: The size of the LQIP image, which can be any number from `4` to `64`. (default is 4)
196
+ #### `lqip` (string) optional
197
+ The LQIP type to use. It can be one of the following:
198
+ - `base64` (default) Base64-encoded LQIP image
199
+ - `color` Solid color placeholder
200
+ - `css` CSS-based LQIP image
201
+ - `svg` SVG-based LQIP image
202
+ - `false` — Disables LQIP generation
203
+
204
+ #### `lqipSize` (number) — optional (default 4)
205
+ The size of the LQIP image, which can be any number from `4` to `64`.
183
206
 
184
207
  > [!WARNING]
185
208
  > A high value for `lqipSize` can significantly increase the total size of your website.
186
209
 
187
- Example:
210
+ <details>
211
+ <summary>Example</summary>
212
+ <br/>
188
213
 
189
- ```astro
190
- ---
191
- import { Image, Picture } from 'astro-lqip/components';
214
+ ```astro
215
+ ---
216
+ import { Image, Picture } from 'astro-lqip/components';
192
217
 
193
- import image from '/src/assets/images/image.png';
194
- import otherImage from '/src/assets/images/other-image.png';
195
- ---
218
+ import image from '/src/assets/images/image.png';
219
+ import otherImage from '/src/assets/images/other-image.png';
220
+ ---
196
221
 
197
- <Image src={image} alt="Cover Image" width={220} height={220} lqip="svg" lqipSize={10} />
198
- <Picture src={otherImage} alt="Other Image" width={220} height={220} lqip="css" lqipSize={7} />
199
- ```
222
+ <Image src={image} alt="Cover Image" width={220} height={220} lqip="svg" lqipSize={10} />
223
+ <Picture src={otherImage} alt="Other Image" width={220} height={220} lqip="css" lqipSize={7} />
224
+ ```
225
+ </details>
200
226
 
201
227
  > [!TIP]
202
- > For the `<Image>` component, a `parentAttributes` prop similar to `pictureAttributes` has been added.
228
+ > For the `<Image>` component, a `parentAttributes` prop similar to [`pictureAttributes`](https://docs.astro.build/en/reference/modules/astro-assets/#pictureattributes) has been added.
203
229
 
204
- ### Background
230
+ <details>
231
+ <summary>Example</summary>
232
+ <br/>
205
233
 
206
- The `<Background>` component supports the following props:
207
-
208
- - `src` (required): The source of the background image located in `src` folder. It can be a static import, absolute path, relative path, alias path or remote URL.
209
- - `lqip`: The LQIP type to use. It can be one of the following:
210
- - `base64`: Generates a Base64-encoded LQIP image. (default option)
211
- - `color`: Generates a solid color placeholder.
212
- - `cssVariable`: A string that represents the name of the CSS variable to store the background data.
213
- - By default, the background data is stored in a CSS variable named `--background`.
214
- - For responsive backgrounds, the CSS variable names are generated based on the provided widths, following the pattern `--background-small` (lower than 768px), `--background-medium` (768px to 1199px), `--background-large` (1200px to 1919px) and `--background-xlarge` (1920px and above). `--background` is also generated for the largest image for backward compatibility.
215
- - If the `cssVariable` prop is provided, the generated CSS variable names will follow the pattern `--{cssVariable}-small`, `--{cssVariable}-medium`, `--{cssVariable}-large` and `--{cssVariable}-xlarge`.
216
- - `format`: The image format to use for the background. It can be one of the following in string or an array of strings. If an array is provided, this generates multiple background images with the native [`image-set()`](https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Values/image/image-set) CSS function, which allows the browser to choose the best format to use based on its support:
217
- - `avif`
218
- - `webp` (default option)
219
- - `jpeg`
220
- - `png`
221
- - `widths`: An array of numbers that represents the widths to use for responsive background images.
222
- - `width`: The width to use for the background image. This prop is ignored if the `widths` prop is provided.
223
- - `height`: The height to use for the background image. This prop is ignored if the `widths` prop is provided.
224
- - `quality`: The quality to use for the background image. It can be a number from `1` to `100`.
225
- - `fit`: The fit to use for the background image. It can be one of the following:
226
- - `cover`
227
- - `contain`
228
- - `fill`
229
- - `inside`
230
- - `outside`
231
-
232
- Example:
234
+ ```astro
235
+ ---
236
+ import { Image } from 'astro-lqip/components';
233
237
 
234
- ```astro
235
- ---
236
- import { Background } from 'astro-lqip/components';
237
- import backgroundImage from '/src/assets/images/background-image.png';
238
- ---
238
+ import image from '/src/assets/images/image.png';
239
+ ---
239
240
 
240
- <Background src={backgroundImage} lqip="color" cssVariable="--bg-lqip" format={["avif", "webp"]} width={500} height={300} quality={80} fit="cover">
241
- <section>
242
- <p>Optimized background</p>
243
- </section>
244
- </Background>
241
+ <Image
242
+ src={image}
243
+ alt="Cover Image"
244
+ width={220}
245
+ height={220}
246
+ lqip="svg"
247
+ lqipSize={10}
248
+ parentAttributes={{ style: "background-color: red;" }}
249
+ />
250
+ ```
251
+ </details>
245
252
 
246
- <style>
247
- section {
248
- background-image: var(--bg-lqip);
249
- background-size: cover;
250
- background-position: center;
251
- }
252
- </style>
253
- ```
253
+ ### 🔩 Background
254
254
 
255
- Example with responsive background:
256
-
257
- ```astro
258
- ---
259
- import { Background } from 'astro-lqip/components';
260
- import backgroundImage from '/src/assets/images/background-image.png';
261
- ---
262
-
263
- <Background src={backgroundImage} format="avif" widths={[475, 1000, 1536, 2100]}>
264
- <section>
265
- <p>Optimized background</p>
266
- </section>
267
- </Background>
268
-
269
- <style>
270
- section {
271
- background-image: var(--background-small); /* 475px */
272
- background-size: cover;
273
- background-position: center;
274
- }
255
+ The `<Background>` component supports the following props:
275
256
 
276
- @media (width >= 768px) {
257
+ #### `src` (string) required
258
+ The source of the background image located in `src` folder. It can be a static import, absolute path, relative path, alias path or remote URL.
259
+
260
+ #### `lqip` — optional
261
+ The LQIP type to use. It can be one of the following:
262
+ - `base64` (default) — Base64-encoded LQIP image
263
+ - `color` — Solid color placeholder
264
+ - `css` — CSS-based LQIP image
265
+ - `svg` — SVG-based LQIP image
266
+ - `false` — Disables LQIP generation
267
+
268
+ #### `cssVariable` (string) — optional
269
+ Represents the name of the CSS variable to store the background data.
270
+
271
+ - By default, the background data is stored in a CSS variable named `--background`.
272
+ - For responsive backgrounds, the CSS variable names are generated based on the provided widths, following the pattern:
273
+ - `--background-small` (lower than 768px)
274
+ - `--background-medium` (768px to 1199px)
275
+ - `--background-large` (1200px to 1919px)
276
+ - `--background-xlarge` (1920px and above), `--background` is also generated for the largest image for backward compatibility.
277
+ - If the `cssVariable` prop is provided, the generated CSS variable names will follow the pattern:
278
+ - `--{cssVariable}-small`
279
+ - `--{cssVariable}-medium`
280
+ - `--{cssVariable}-large`
281
+ - `--{cssVariable}-xlarge`, `--{cssVariable}` is also generated for the largest image for backward compatibility.
282
+
283
+ #### `format` (string | array&lt;string&gt;) — optional
284
+ The image format to use for the background. It can be one of the following in string or an array of strings. If an array is provided, this generates multiple background images with the native [`image-set()`](https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Values/image/image-set) CSS function, which allows the browser to choose the best format to use based on its support:
285
+ - `avif`
286
+ - `webp` (default)
287
+ - `jpeg`
288
+ - `png`
289
+
290
+
291
+ #### `widths` (array) — optional
292
+ An array of numbers that represents the widths to use for responsive background images.
293
+
294
+ #### `width` (number) — optional
295
+ The width to use for the background image. This prop is ignored if the `widths` prop is provided.
296
+
297
+ #### `height` (number) — optional
298
+ The height to use for the background image. This prop is ignored if the `widths` prop is provided.
299
+
300
+ #### `quality` (number) — optional
301
+ The quality to use for the background image. It can be a number from `1` to `100`.
302
+
303
+ #### `fit` (string) — optional
304
+ The fit to use for the background image. It can be one of the following:
305
+ - `cover` (default)
306
+ - `contain`
307
+ - `fill`
308
+ - `inside`
309
+ - `outside`
310
+
311
+ <details>
312
+ <summary>Example</summary>
313
+ <br/>
314
+
315
+ ```astro
316
+ ---
317
+ import { Background } from 'astro-lqip/components';
318
+ import backgroundImage from '/src/assets/images/background-image.png';
319
+ ---
320
+
321
+ <Background src={backgroundImage} lqip="color" cssVariable="--bg-lqip" format={["avif", "webp"]} width={500} height={300} quality={80} fit="cover">
322
+ <section>
323
+ <p>Optimized background</p>
324
+ </section>
325
+ </Background>
326
+
327
+ <style>
277
328
  section {
278
- background-image: var(--background-medium); /* 1000px */
329
+ background-image: var(--bg-lqip);
330
+ background-size: cover;
331
+ background-position: center;
279
332
  }
280
- }
281
-
282
- @media (width >= 1200px) {
333
+ </style>
334
+ ```
335
+ </details>
336
+
337
+ <details>
338
+ <summary>Example with responsive background</summary>
339
+ <br/>
340
+
341
+ ```astro
342
+ ---
343
+ import { Background } from 'astro-lqip/components';
344
+ import backgroundImage from '/src/assets/images/background-image.png';
345
+ ---
346
+
347
+ <Background src={backgroundImage} format="avif" widths={[475, 1000, 1536, 2100]}>
348
+ <section>
349
+ <p>Optimized background</p>
350
+ </section>
351
+ </Background>
352
+
353
+ <style>
283
354
  section {
284
- background-image: var(--background-large); /* 1536px */
355
+ background-image: var(--background-small); /* 475px */
356
+ background-size: cover;
357
+ background-position: center;
285
358
  }
286
- }
287
359
 
288
- @media (width >= 1920px) {
289
- section {
290
- /* or var(--background), since it's the default variable for the largest image */
291
- background-image: var(--background-xlarge); /* 2100px */
360
+ @media (width >= 768px) {
361
+ section {
362
+ background-image: var(--background-medium); /* 1000px */
363
+ }
364
+ }
365
+
366
+ @media (width >= 1200px) {
367
+ section {
368
+ background-image: var(--background-large); /* 1536px */
369
+ }
292
370
  }
293
- }
294
- </style>
295
- ```
371
+
372
+ @media (width >= 1920px) {
373
+ section {
374
+ /* or var(--background), since it's the default variable for the largest image */
375
+ background-image: var(--background-xlarge); /* 2100px */
376
+ }
377
+ }
378
+ </style>
379
+ ```
380
+ </details>
296
381
 
297
382
  > [!NOTE]
298
383
  > The `lqipSize` prop is not compatible with this component, to avoid large CSS outputs.
299
384
 
300
385
  ## 💡 Knowledge
301
-
302
386
  Since this integration is built on top of Astro native `<Image>` and `<Picture>` components, you can refer to the [Astro documentation](https://docs.astro.build/en/guides/images/) for more information on how to use it.
303
387
 
304
388
  For some simple tips, visit the [Usage Tips](https://astro-lqip.web.app/usage-tips/) page.
305
389
 
306
390
  ## 🤝 Contributing
307
- If you wish to contribute to this project, you can do so by reading the [contribution guide](https://github.com/felixicaza/astro-lqip/blob/main/CONTRIBUTING.md).
391
+ Contributions to this library are welcome! If you have any ideas for improvements or new features, please feel free to open an issue or submit a pull request. I appreciate your help in making [`astro-lqip`](https://astro-lqip.web.app/) better for everyone. Please read the [CONTRIBUTING.md](https://github.com/felixicaza/astro-lqip/blob/main/CONTRIBUTING.md).
308
392
 
309
393
  ## 📄 License
310
- This project is licensed under the MIT License. See the [license file](https://github.com/felixicaza/astro-lqip/blob/main/LICENSE) for more details.
394
+ This project is licensed under the MIT License. See the [LICENSE](https://github.com/felixicaza/astro-lqip/blob/main/LICENSE) file for details.
@@ -1 +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}}
1
+ @layer astro-lqip{[data-astro-lqip-bg]{display:contents}[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}}}
package/dist/index.d.mts CHANGED
@@ -4,7 +4,7 @@ import { ImageMetadata } from "astro";
4
4
  import { ComponentProps, HTMLAttributes } from "astro/types";
5
5
 
6
6
  //#region src/types/lqip.type.d.ts
7
- type LqipType = 'color' | 'css' | 'base64' | 'svg';
7
+ type LqipType = 'color' | 'css' | 'base64' | 'svg' | false;
8
8
  //#endregion
9
9
  //#region src/types/style.type.d.ts
10
10
  type StylePrimitive = string | number | undefined;
@@ -48,44 +48,56 @@ type ImageOutputFormat = ValidOutputFormats[number] | (string & {});
48
48
  type ImageFit = 'fill' | 'contain' | 'cover' | 'none' | 'scale-down' | (string & {});
49
49
  interface ImageTransform {
50
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.
51
+ * Source image used to generate the background asset.
52
+ * @remarks
53
+ * Accepts a relative path, absolute path, alias, or Astro `ImageMetadata`.
53
54
  */
54
55
  src: string | ImageMetadata;
55
56
  /**
56
- * CSS custom property that will receive the generated background.
57
- * (defaults to --background)
57
+ * CSS custom property that receives the generated background value.
58
+ * @defaultValue '--background'
58
59
  */
59
60
  cssVariable?: string;
60
61
  /**
61
- * LQIP placeholder strategy for Background (defaults to 'base64').
62
- * Only 'base64' and 'color' are supported to keep CSS-friendly values.
62
+ * Placeholder strategy used while the final background asset is loading.
63
+ * @remarks
64
+ * Only `'base64'` and `'color'` are supported because they can be represented
65
+ * as CSS-friendly values for background rendering.
66
+ * @defaultValue 'base64'
63
67
  */
64
- lqip?: 'base64' | 'color';
68
+ lqip?: 'base64' | 'color' | false;
65
69
  /**
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)
70
+ * Output format or ordered list of formats for generated assets.
71
+ * @remarks
72
+ * When an array is provided, the first entry is treated as the preferred
73
+ * fallback format.
74
+ * @defaultValue 'webp'
69
75
  */
70
76
  format?: ImageOutputFormat | ImageOutputFormat[] | undefined;
71
77
  /**
72
- * Explicit widths used for responsive variants.
78
+ * Explicit widths used to generate responsive variants.
73
79
  */
74
80
  widths?: number[] | undefined;
75
81
  /**
76
- * Single width fallback when `widths` is omitted.
82
+ * Target width for generated assets.
83
+ * @remarks
84
+ * Omitted when `widths` is provided.
77
85
  */
78
86
  width?: number | undefined;
79
87
  /**
80
88
  * Target height for generated assets.
89
+ * @remarks
90
+ * Omitted when `widths` is provided, as height is determined by the aspect ratio of the source image.
81
91
  */
82
92
  height?: number | undefined;
83
93
  /**
84
- * Compression quality preset or numeric percentage.
94
+ * Compression quality applied to generated assets.
95
+ * @remarks
96
+ * Accepts either a named quality preset or a numeric percentage.
85
97
  */
86
98
  quality?: ImageQuality | undefined;
87
99
  /**
88
- * Object-fit behavior applied during resizing.
100
+ * Object-fit behavior applied during image resizing.
89
101
  */
90
102
  fit?: ImageFit | undefined;
91
103
  }
@@ -96,15 +108,17 @@ type GetSVGReturn = GetPlaiceholderReturn['svg'];
96
108
  //#region src/types/props.type.d.ts
97
109
  type Props = {
98
110
  /**
99
- * LQIP type.
100
- * This can be 'color', 'css', 'svg' or 'base64'.
101
- * The default value is 'base64'.
111
+ * Placeholder strategy used to generate the low-quality image preview.
112
+ * @remarks
113
+ * Supported values are `'color'`, `'css'`, `'svg'`, `'base64'`, or `false`.
114
+ * @defaultValue 'base64'
102
115
  */
103
116
  lqip?: LqipType;
104
117
  /**
105
- * Size of the LQIP image in pixels.
106
- * This value should be between 4 and 64.
107
- * The default value is 4.
118
+ * Pixel size used to generate the low-quality image preview.
119
+ * @remarks
120
+ * Expected to be between `4` and `64`.
121
+ * @defaultValue 4
108
122
  */
109
123
  lqipSize?: number;
110
124
  };
@@ -116,25 +130,52 @@ type SVGNodeAttrs = {
116
130
  } & Record<string, string | number>;
117
131
  type SVGNode = [string, SVGNodeAttrs, SVGNode[]];
118
132
  //#endregion
119
- //#region src/components/Picture.d.ts
120
- type AstroPictureProps = ComponentProps<typeof Picture$1>;
121
- type Props$3 = AstroPictureProps & Props;
133
+ //#region src/components/Background.d.ts
134
+ interface BackgroundProps extends ImageTransform {}
122
135
  //#endregion
123
136
  //#region src/components/Image.d.ts
124
- type Props$2 = (LocalImageProps | RemoteImageProps) & Props & {
137
+ type ImageProps = (LocalImageProps | RemoteImageProps) & Props & {
138
+ /**
139
+ * HTML attributes applied to the wrapper div rendered around the image
140
+ * when LQIP is enabled.
141
+ */
125
142
  parentAttributes?: HTMLAttributes<'div'>;
126
143
  };
127
144
  //#endregion
128
- //#region src/components/Background.d.ts
129
- type Props$1 = ImageTransform;
145
+ //#region src/components/Picture.d.ts
146
+ type AstroPictureProps = ComponentProps<typeof Picture$1>;
147
+ type PictureProps = AstroPictureProps & Props;
130
148
  //#endregion
131
149
  //#region src/index.d.ts
132
150
  type AstroTypedComponent<Props> = ((props: Props) => unknown) & {
133
151
  isAstroComponentFactory?: boolean;
134
152
  moduleId?: string;
135
153
  };
136
- declare const Image: AstroTypedComponent<Props$2>;
137
- declare const Picture: AstroTypedComponent<Props$3>;
138
- declare const Background: AstroTypedComponent<Props$1>;
154
+ /**
155
+ * Astro component that renders a wrapper element with an optimized background image.
156
+ *
157
+ * @remarks
158
+ * The component resolves the configured source, generates LQIP styles, and applies
159
+ * them inline as a CSS background. Child content is rendered via the default slot.
160
+ */
161
+ declare const Background: AstroTypedComponent<BackgroundProps>;
162
+ /**
163
+ * Astro component that extends astro:assets Image with LQIP behavior.
164
+ *
165
+ * @remarks
166
+ * When lqip is enabled, the component renders a wrapper with placeholder
167
+ * styles and fades the placeholder out on image load.
168
+ * When lqip is false, it delegates to Astro Image rendering directly.
169
+ */
170
+ declare const Image: AstroTypedComponent<ImageProps>;
171
+ /**
172
+ * Astro component that extends `astro:assets` Picture with LQIP behavior.
173
+ *
174
+ * @remarks
175
+ * When `lqip` is enabled, this component applies placeholder styles to
176
+ * `pictureAttributes` and fades them out once the image loads.
177
+ * When `lqip` is `false`, it delegates to Astro's Picture renderer.
178
+ */
179
+ declare const Picture: AstroTypedComponent<PictureProps>;
139
180
  //#endregion
140
181
  export { Background, ComponentsOptions, GetSVGReturn, GlobMap, Image, ImagePath, ImageTransform, ImportModule, LqipType, Picture, Props, ResolvedImage, SVGNode, StyleAttrs, StyleInput, StyleMap };
package/dist/index.mjs CHANGED
@@ -1,18 +1,8 @@
1
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
- `})}
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";import{createHash as T}from"node:crypto";const E=`[astro-lqip]`,D=process.cwd(),O=y(D,`src`),k=y(D,`public`),A=y(D,`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`]),j=Object.assign({}),M=new Map,N=new Map;function P(e){if(!e)return;let t=e.toLowerCase();(t.includes(`/public/`)||e.startsWith(k))&&console.warn(`${E} 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(`${E} 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 F(e){return typeof e==`object`&&!!e}function ae(e){return F(e)&&typeof e.then==`function`}function I(e){return F(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 L(e){let t=se(e.trim()).replace(/\\/g,`/`);return t.startsWith(`@/`)?`src/${t.slice(2)}`:t.startsWith(`~@/`)?`src/${t.slice(3)}`:t}function R(e){return e.startsWith(`./`)||e.startsWith(`../`)}function z(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(D)?!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 B(e){let t=b(D,e);return!e||t.startsWith(`..`)||t.includes(`..${S}`)||t.includes(`node_modules`)?null:e}function de(e){let t=b(D,e).replace(/\\/g,`/`);return!t||t.startsWith(`../`)?null:`/${t}`}function fe(e,t){let n=L(e),r=new Set;if(n.startsWith(`/src/`)?r.add(n):n.startsWith(`src/`)&&r.add(`/${n}`),t&&R(n)){let e=de(x(t,n));e?.startsWith(`/src/`)&&r.add(e)}if(R(n)){let e=z(n);e&&r.add(e.startsWith(`src/`)?`/${e}`:`/src/${e}`);let t=`/${e}`;for(let e of Object.keys(j))e.endsWith(t)&&r.add(e)}return!n.startsWith(`/`)&&!n.startsWith(`src/`)&&!R(n)&&r.add(`/src/${n}`),Array.from(r)}function pe(e,t){let n=L(e),r=new Set,i=n.startsWith(`/`)?n.slice(1):n;if(t&&R(n)){let e=B(x(t,n));e&&r.add(e)}if(R(n)){let e=z(n);if(e){let t=B(y(O,e));t&&r.add(t);let n=B(y(k,e));n&&r.add(n)}}if(n.startsWith(`/src/`)?r.add(y(D,n.slice(1))):n.startsWith(`src/`)&&r.add(y(D,n)),n.startsWith(`/public/`)?r.add(y(D,n.slice(1))):n.startsWith(`public/`)&&r.add(y(D,n)),!n.startsWith(`/`)){let e=B(y(O,n));e&&r.add(e);let t=B(y(D,n));t&&r.add(t)}else if(i){let e=B(y(D,i));e&&r.add(e)}return Array.from(r)}async function me(e){if(!e)return null;let t=`${O}::${e}`;if(M.has(t))return M.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(O);return M.set(t,r??null),r??null}function he(e,t){if(e!=null)return String(e).toLowerCase();let n=v(t).replace(`.`,``);return n?n.toLowerCase():void 0}function ge(e,t,n,r){return`/@fs${e.replace(/\\/g,`/`)}?origWidth=${t}&origHeight=${n}&origFormat=${r}`}function _e(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 ve(e){let t=e.replace(/\\/g,`/`),n=t.lastIndexOf(`/_astro/`);if(n!==-1)return t.slice(n);let r=b(A,e).replace(/\\/g,`/`),i=r.indexOf(`.prerender/`);return i===-1?`/${r}`:`/${r.slice(i+11)}`}async function ye(e){if(N.has(e))return N.get(e)??null;if(!l(A))return N.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(_e(e,i.name))return r}}let n=await t(A);if(!n)return N.set(e,null),null;let r=ve(n);return N.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=he(t?.format,e);if(!n||!r||!i)return console.warn(`${E} Missing metadata for "${e}".`),null;let a=ee?ge(e,n,r,i):await ye(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(`${E} Failed to derive metadata for "${e}".`,t),null}}async function be(e,t){let n=fe(e,t);for(let e of n){let t=j[e];if(!t)continue;let n=await t(),r=n.default??n;if(I(r))return P(r.src),r}return null}async function xe(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 P(e),t}let r=L(e).split(`/`).pop();if(!r)return null;let i=await me(r);if(!i||!l(i))return null;let a=await V(i);return a?(P(i),a):null}async function H(e){if(e==null)return null;if(ae(e)){let t=await e,n=t.default??t;return I(n)?(P(n.src),n):typeof n==`string`?(P(n),n):null}if(F(e)){let t=e;return P(typeof t.src==`string`?t.src:void 0),I(t)?t:null}if(typeof e==`string`){if(oe(e))return e;let t=L(e),n=R(t)?ue():null;return await be(t,n)||await xe(t,n)||null}return null}function Se(e){return process.platform===`win32`&&/^\/[A-Za-z]:\//.test(e)?e.slice(1):e}function Ce(){return typeof process<`u`&&!!process.versions?.node}async function we(e){if(Ce())try{return await f(e)}catch{return}}function Te(e,t=4){let n=Number(e);return Number.isFinite(n)?Math.min(64,Math.max(4,Math.round(n))):t}async function U(e,t,n,r){try{let i=Se(e),a=Te(n),o=await we(i);if(!o){console.warn(`${E} 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(`${E} LQIP (${t}) successfully generated!`):console.log(`${E} LQIP (${t}) successfully generated for:`,e),c}catch(n){console.error(`${E} Error generating LQIP (${t}) in:`,e,`
4
+ `,n);return}}function W(e){return/^https?:\/\//.test(e)}const G=y(process.cwd(),`node_modules`,`.cache`,`astro-lqip`),Ee=[`jpg`,`jpeg`,`png`,`webp`,`avif`],De=[`src`],Oe=/\.[A-Za-z0-9_-]{5,}\.[A-Za-z0-9]+$/,ke=/^(.+?)\.[A-Za-z0-9_-]{5,}\.[A-Za-z0-9]+$/,K=(()=>{try{let e=import.meta.env?.BASE_URL??`/`;return!e||e===`/`?`/`:e.endsWith(`/`)?e.slice(0,-1):e}catch{return`/`}})(),q=new Map;function Ae(e){if(typeof e!=`string`)return e;let t=e.indexOf(`?`),n=t>=0?e.slice(0,t):e;return K!==`/`&&n.startsWith(K)&&(n=n.slice(K.length)||`/`),n.startsWith(`/`)||(n=`/${n}`),n}async function J(){l(G)||await d(G,{recursive:!0})}function je(e){try{return u(e).mtimeMs}catch{return}}function Me(e,t,n,r){let i=r===void 0?`${e}:${t}:${n}`:`${e}:${t}:${n}:${r}`;return`lqip-${T(`sha256`).update(i).digest(`hex`).slice(0,16)}.json`}async function Ne(e){let t=y(G,e);try{let e=await f(t,`utf-8`);return JSON.parse(e)}catch{return}}async function Pe(e,t){await J(),await h(y(G,e),JSON.stringify(t))}function Fe(e){let t=e.split(`/`).pop()||``,n=t.match(ke);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(q.has(e))return q.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(Ee.some(t=>a===`${e}.${t}`))return i}}for(let t of De){let r=y(process.cwd(),t);if(l(r)){let t=await n(r);if(t)return q.set(e,t),t}}q.set(e,null)}async function Y(e,t,n,r){if(!e?.src||t===!1)return;let i;W(e.src)?i=void 0:r&&e.src.startsWith(`/@fs/`)&&(i=e.src.replace(/^\/@fs/,``).split(`?`)[0]);let a=i?je(i):void 0,o=Me(e.src,t,n,a),s=await Ne(o);if(s!==void 0)return s;let c;if(W(e.src)){await J();let i=await fetch(e.src);if(!i.ok)return;let a=await i.arrayBuffer(),s=Buffer.from(a),l=y(G,`temp-${o.replace(`.json`,``)}-${Math.random().toString(36).slice(2)}.jpg`);await h(l,s);try{c=await U(l,t,n,r)}finally{try{await m(l)}catch{}}}else if(r&&e.src.startsWith(`/@fs/`)){let i=e.src.replace(/^\/@fs/,``).split(`?`)[0];c=await U(i,t,n,r)}else if(!r){let i=e.src,a=Ae(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)){c=await U(i,t,n,r);break}}if(c===void 0){let e=a.split(`/`).pop()??``;if(Oe.test(e)){let e=Fe(a),i=await Ie(e);i?(console.log(`${E} fallback recursive source found:`,i),c=await U(i,t,n,r)):console.warn(`${E} original source not found recursively for basename:`,e)}}}return c!==void 0&&await Pe(o,c),c}async function Le({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(`${E} Could not resolve background image in "${e}"`);let h=typeof m==`string`&&Ve(m)?m:null;if(h&&(!f||!p)){try{let{width:e,height:t}=await c(h);f??=e,p??=t}catch(e){console.warn(`${E} Failed to infer remote background size for "${h}".`,e)}if(!f||!p)throw Error(`${E} 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,_;if(u!==!1){let e=await Y(typeof m==`string`?{src:m}:m,u,8,d);_=Ke(u,typeof e==`string`?e:void 0)}let v=Be((Array.isArray(n)?n:[n]).filter(e=>typeof e==`string`&&e.trim().length>0)),y=v.length?v:[`webp`],b=Array.isArray(n),x={src:g,widths:r,width:f,height:p,quality:o,fit:l},S=await Promise.all(y.map(e=>s({...x,format:e}))),C=S.map((e,t)=>({format:y[t],mimeType:He(y[t]),fallbackSrc:e.src,sources:Ue(e.srcSet?.attribute)})),w=Re(t),T=C[0]?.sources??[],D=Array.isArray(r)&&r.length>0,O=e=>Ge({formatSources:C,optimizedImages:S,isFormatArray:b,selector:e,layer:u===!1?void 0:_});return{style:D?We({referenceSources:T,baseVariable:w,createValue:O}):`${w}: ${O()}`,resolvedSrc:m}}function Re(e){let t=e.trim();return t?t.startsWith(`--`)?t:`--${t}`:`--background`}function ze(e){return e.trim().toLowerCase()}function Be(e){let t=[`avif`,`webp`,`png`,`jpeg`,`jpg`,`svg`],n=new Set;return e.map(e=>ze(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 Ve(e){return typeof e==`string`&&/^https?:\/\//.test(e)}function He(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 Ue(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 We({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 X(e,t){return t?.(e)??e.sources[e.sources.length-1]??{url:e.fallbackSrc}}function Z(e,t){return e&&t?`${e}, ${t}`:e||t||``}function Ge({formatSources:e,optimizedImages:t,isFormatArray:n,selector:r,layer:i}){if(!e.length){let e=t[0]?.src??``;return Z(e?`url('${e}')`:``,i)}if(!n){let t=e[0];return Z(`url('${X(t,r).url??t.fallbackSrc}')`,i)}return Z(`image-set(${e.map(e=>`url('${X(e,r).url??e.fallbackSrc}') type('${e.mimeType}')`).join(`, `)})`,i)}function Ke(e,t){if(!(!e||!t))return e===`color`?`linear-gradient(${t}, ${t})`:`url("${t}")`}const qe=e({factory:async(e,t,a)=>{let o=import.meta.env.MODE===`development`,{style:s}=await Le({...t,isDevelopment:o}),c=await n(e,a.default);return r`<div ${i({style:s,"data-astro-lqip-bg":``})}>${c}</div>`}}),Je=/[A-Z]/g;function Ye(e){return e.replace(Je,e=>`-${e.toLowerCase()}`)}function Q(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(`${Ye(n)}:${r}`);return t.length>0?t.join(`;`):void 0}function $([e,t,n=[]]){let r=``;for(let[e,n]of Object.entries(t||{}))e===`style`&&n&&typeof n==`object`?r+=` style="${Q(n)}"`:n!==void 0&&(r+=` ${e}="${n}"`);return n.length>0?`<${e}${r}>${n.map($).join(``)}</${e}>`:`<${e}${r} />`}function Xe(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}')`}}}async function Ze({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;if(t===!1)return{lqipImage:void 0,svgHTML:``,lqipStyle:{},combinedStyle:{...r},resolvedSrc:o};let s;o&&(s=await Y(typeof o==`string`?{src:o}:o,t,n,a));let c=``;t===`svg`&&Array.isArray(s)&&(c=$(s));let l=Xe(t,s,c);for(let e of Object.keys(r))i.includes(e)&&console.warn(`${E} 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 Qe=e({factory:async(e,n)=>{let{class:o,lqip:s=`base64`,lqipSize:c=4,parentAttributes:l={},...u}=n;if(s===!1){let n=await H(u.src);return t(e,`Image`,a,{...u,class:o,src:n??u.src})}let{combinedStyle:d,resolvedSrc:f}=await Ze({src:u.src,lqip:s,lqipSize:c,styleProps:l.style??{},forbiddenVars:[],isDevelopment:import.meta.env.MODE===`development`});return r`
5
+ <div ${i({...l,class:[l.class,o].filter(Boolean).join(` `)||void 0,"data-astro-lqip":``,style:Q(d)})}>
6
+ ${t(e,`Image`,a,{...u,class:o,src:f??u.src,onload:`parentElement.style.setProperty("--z-index", 1);parentElement.style.setProperty("--opacity", 0);`})}
13
7
  </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};
8
+ `}}),$e=e({factory:async(e,n)=>{let{class:r,lqip:i=`base64`,lqipSize:a=4,pictureAttributes:s={},...c}=n;if(i===!1){let n=await H(c.src);return await t(e,`Picture`,o,{...c,class:r,src:n??c.src,pictureAttributes:s})}let{combinedStyle:l,resolvedSrc:u}=await Ze({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:`parentElement.style.setProperty("--z-index", 1);parentElement.style.setProperty("--opacity", 0);`})}}),et=qe,tt=Qe,nt=$e;export{et as Background,tt as Image,nt as Picture};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "astro-lqip",
3
- "version": "1.8.1",
3
+ "version": "1.8.2",
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",
@@ -49,29 +49,32 @@
49
49
  },
50
50
  "devDependencies": {
51
51
  "@eslint/js": "9.39.4",
52
- "@tsdown/css": "0.21.7",
53
- "@types/node": "25.5.0",
54
- "@typescript-eslint/parser": "8.57.0",
55
- "astro": "6.1.2",
52
+ "@tsdown/css": "0.21.10",
53
+ "@types/node": "25.6.0",
54
+ "@typescript-eslint/parser": "8.59.0",
55
+ "astro": "6.1.9",
56
56
  "bumpp": "11.0.1",
57
57
  "concurrently": "9.2.1",
58
58
  "eslint": "9.39.4",
59
- "eslint-plugin-astro": "1.6.0",
59
+ "eslint-plugin-astro": "1.7.0",
60
60
  "eslint-plugin-jsonc": "3.1.2",
61
61
  "eslint-plugin-jsx-a11y": "6.10.2",
62
- "eslint-plugin-package-json": "0.90.1",
62
+ "eslint-plugin-package-json": "0.91.1",
63
63
  "eslint-plugin-yml": "3.3.1",
64
- "globals": "17.4.0",
64
+ "globals": "17.5.0",
65
65
  "jiti": "2.6.1",
66
66
  "lightningcss": "1.32.0",
67
- "nano-staged": "0.9.0",
67
+ "nano-staged": "1.0.2",
68
68
  "neostandard": "0.13.0",
69
69
  "simple-git-hooks": "2.13.1",
70
- "tsdown": "0.21.7"
70
+ "tsdown": "0.21.10"
71
71
  },
72
72
  "peerDependencies": {
73
73
  "astro": ">=3.3.0"
74
74
  },
75
+ "engines": {
76
+ "node": ">=22"
77
+ },
75
78
  "scripts": {
76
79
  "build": "tsdown",
77
80
  "build:docs": "pnpm --filter ./docs -r build",
@@ -85,12 +88,14 @@
85
88
  "release": "bumpp",
86
89
  "sync:types": " pnpm --filter \"./tests/fixtures/**\" astro sync",
87
90
  "test": "pnpm test:fixtures",
88
- "test:fixtures": "pnpm --filter \"./tests/fixtures/**\" -r build",
91
+ "test:fixtures": "pnpm --filter \"./tests/fixtures/**\" -r --workspace-concurrency=1 build",
89
92
  "test:fixtures:custom-base": "pnpm --filter \"./tests/fixtures/custom-base\" build",
90
93
  "test:fixtures:default": "pnpm --filter \"./tests/fixtures/default\" build",
94
+ "test:fixtures:mdx": "pnpm --filter \"./tests/fixtures/mdx\" build",
91
95
  "test:fixtures:preview": "pnpm --filter \"./tests/fixtures/**\" -r preview",
92
96
  "test:fixtures:preview:custom-base": "pnpm --filter \"./tests/fixtures/custom-base\" preview",
93
97
  "test:fixtures:preview:default": "pnpm --filter \"./tests/fixtures/default\" preview",
98
+ "test:fixtures:preview:mdx": "pnpm --filter \"./tests/fixtures/mdx\" preview",
94
99
  "test:fixtures:preview:ssr": "pnpm --filter \"./tests/fixtures/ssr-node\" preview",
95
100
  "test:fixtures:preview:ssr:custom-base": "pnpm --filter \"./tests/fixtures/ssr-node-custom-base\" preview",
96
101
  "test:fixtures:ssr": "pnpm --filter \"./tests/fixtures/ssr-node\" build",