@ecopages/image-processor 0.2.1 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +28 -11
- package/package.json +26 -17
- package/src/component/html.js +12 -6
- package/src/component/jsx.d.ts +12 -0
- package/src/component/jsx.js +18 -0
- package/src/component/react.js +17 -10
- package/src/constants.js +5 -4
- package/src/image-plugins.d.ts +8 -13
- package/src/image-plugins.js +46 -29
- package/src/image-processor.d.ts +7 -1
- package/src/image-processor.js +148 -135
- package/src/image-renderer.d.ts +1 -1
- package/src/image-renderer.js +222 -220
- package/src/image-runtime-state.d.ts +24 -0
- package/src/image-runtime-state.js +65 -0
- package/src/image-types.d.ts +3 -0
- package/src/image-utils.js +109 -98
- package/src/index.js +3 -3
- package/src/plugin.d.ts +40 -29
- package/src/plugin.js +305 -257
- package/src/types.js +1 -0
- package/src/utils.js +24 -25
- package/CHANGELOG.md +0 -19
- package/src/bun-plugins.d.ts +0 -22
- package/src/bun-plugins.js +0 -33
package/src/image-utils.js
CHANGED
|
@@ -1,103 +1,114 @@
|
|
|
1
|
-
import { DEFAULT_LAYOUT } from
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
* This approach ensures that the browser will select the correct image variant based on the viewport width.
|
|
30
|
-
* @see https://developer.mozilla.org/en-US/docs/Learn/HTML/Multimedia_and_embedding/Responsive_images#resolution_switching_different_sizes
|
|
31
|
-
* @param {ImageVariant[]} variants - Array of processed image variants
|
|
32
|
-
* @returns {string} sizes attribute string
|
|
33
|
-
* @private
|
|
34
|
-
*/
|
|
35
|
-
static generateSizes(variants) {
|
|
36
|
-
if (!variants?.length) return "";
|
|
37
|
-
const sortedVariants = [...variants].sort((a, b) => b.width - a.width);
|
|
38
|
-
const [largest, ...rest] = sortedVariants;
|
|
39
|
-
const conditions = [
|
|
40
|
-
`(min-width: ${largest.width}px) ${largest.width}px`,
|
|
41
|
-
...rest.map((variant) => {
|
|
42
|
-
if (variant.width >= ImageUtils.BREAKPOINTS.desktop) {
|
|
43
|
-
return `(min-width: ${variant.width}px) ${ImageUtils.VIEWPORT_SIZES.desktop}`;
|
|
44
|
-
}
|
|
45
|
-
if (variant.width >= ImageUtils.BREAKPOINTS.tablet) {
|
|
46
|
-
return `(min-width: ${variant.width}px) ${ImageUtils.VIEWPORT_SIZES.tablet}`;
|
|
47
|
-
}
|
|
48
|
-
return null;
|
|
49
|
-
}).filter(Boolean),
|
|
50
|
-
ImageUtils.VIEWPORT_SIZES.mobile
|
|
51
|
-
];
|
|
52
|
-
return conditions.join(", ");
|
|
53
|
-
}
|
|
54
|
-
/**
|
|
55
|
-
* Generates a styles string based on image layout and config
|
|
56
|
-
* @param {ImageLayout} layout - Image layout type
|
|
57
|
-
* @param {LayoutAttributes} config - Image layout configuration
|
|
58
|
-
* @returns {string} styles string
|
|
59
|
-
* @private
|
|
60
|
-
*/
|
|
61
|
-
static generateLayoutStyles(config) {
|
|
62
|
-
const layout = config.layout || ImageUtils.DEFAULT_LAYOUT;
|
|
63
|
-
const styles = [["object-fit", "cover"]];
|
|
64
|
-
if (config.aspectRatio) {
|
|
65
|
-
styles.push(["aspect-ratio", config.aspectRatio]);
|
|
1
|
+
import { DEFAULT_LAYOUT } from './constants.js';
|
|
2
|
+
/**
|
|
3
|
+
* ImageUtils
|
|
4
|
+
* This class contains utility methods for working with images
|
|
5
|
+
* It contains methods for generating srcset, sizes and styles attributes for the image element
|
|
6
|
+
*/
|
|
7
|
+
export class ImageUtils {
|
|
8
|
+
static BREAKPOINTS = {
|
|
9
|
+
desktop: 1024,
|
|
10
|
+
tablet: 768,
|
|
11
|
+
};
|
|
12
|
+
static VIEWPORT_SIZES = {
|
|
13
|
+
desktop: '70vw',
|
|
14
|
+
tablet: '80vw',
|
|
15
|
+
mobile: '100vw',
|
|
16
|
+
};
|
|
17
|
+
static DEFAULT_LAYOUT = DEFAULT_LAYOUT;
|
|
18
|
+
/**
|
|
19
|
+
* Generates a srcset string from processed image variants using relative paths
|
|
20
|
+
* @param {ImageVariant[]} variants - Array of processed image variants
|
|
21
|
+
* @returns {string} srcset attribute string
|
|
22
|
+
* @private
|
|
23
|
+
*/
|
|
24
|
+
static generateSrcset(variants) {
|
|
25
|
+
return variants
|
|
26
|
+
.sort((a, b) => b.width - a.width)
|
|
27
|
+
.map(({ src, width }) => `${src} ${width}w`)
|
|
28
|
+
.join(', ');
|
|
66
29
|
}
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
30
|
+
/**
|
|
31
|
+
* Generates sizes attribute based on image variants.
|
|
32
|
+
* Sizes are generated based on the variant widths and breakpoints.
|
|
33
|
+
* Here we use a smart approach to generate sizes based on the variant widths.
|
|
34
|
+
* We start with the largest variant and set a min-width condition for its width.
|
|
35
|
+
* Then we add conditions for each variant based on the viewport width.
|
|
36
|
+
* Finally, we add a catch-all for the smallest screens.
|
|
37
|
+
* This approach ensures that the browser will select the correct image variant based on the viewport width.
|
|
38
|
+
* @see https://developer.mozilla.org/en-US/docs/Learn/HTML/Multimedia_and_embedding/Responsive_images#resolution_switching_different_sizes
|
|
39
|
+
* @param {ImageVariant[]} variants - Array of processed image variants
|
|
40
|
+
* @returns {string} sizes attribute string
|
|
41
|
+
* @private
|
|
42
|
+
*/
|
|
43
|
+
static generateSizes(variants) {
|
|
44
|
+
if (!variants?.length)
|
|
45
|
+
return '';
|
|
46
|
+
const sortedVariants = [...variants].sort((a, b) => b.width - a.width);
|
|
47
|
+
const [largest, ...rest] = sortedVariants;
|
|
48
|
+
const conditions = [
|
|
49
|
+
`(min-width: ${largest.width}px) ${largest.width}px`,
|
|
50
|
+
...rest
|
|
51
|
+
.map((variant) => {
|
|
52
|
+
if (variant.width >= ImageUtils.BREAKPOINTS.desktop) {
|
|
53
|
+
return `(min-width: ${variant.width}px) ${ImageUtils.VIEWPORT_SIZES.desktop}`;
|
|
54
|
+
}
|
|
55
|
+
if (variant.width >= ImageUtils.BREAKPOINTS.tablet) {
|
|
56
|
+
return `(min-width: ${variant.width}px) ${ImageUtils.VIEWPORT_SIZES.tablet}`;
|
|
57
|
+
}
|
|
58
|
+
return null;
|
|
59
|
+
})
|
|
60
|
+
.filter(Boolean),
|
|
61
|
+
ImageUtils.VIEWPORT_SIZES.mobile,
|
|
62
|
+
];
|
|
63
|
+
return conditions.join(', ');
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Generates a styles string based on image layout and config
|
|
67
|
+
* @param {ImageLayout} layout - Image layout type
|
|
68
|
+
* @param {LayoutAttributes} config - Image layout configuration
|
|
69
|
+
* @returns {string} styles string
|
|
70
|
+
* @private
|
|
71
|
+
*/
|
|
72
|
+
static generateLayoutStyles(config) {
|
|
73
|
+
const layout = config.layout || ImageUtils.DEFAULT_LAYOUT;
|
|
74
|
+
const styles = [['object-fit', 'cover']];
|
|
75
|
+
if (config.aspectRatio) {
|
|
76
|
+
styles.push(['aspect-ratio', config.aspectRatio]);
|
|
89
77
|
}
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
78
|
+
switch (layout) {
|
|
79
|
+
case 'fixed':
|
|
80
|
+
if (config.width) {
|
|
81
|
+
styles.push(['width', `${config.width}px`], ['min-width', `${config.width}px`]);
|
|
82
|
+
}
|
|
83
|
+
if (config.height) {
|
|
84
|
+
styles.push(['height', `${config.height}px`], ['min-height', `${config.height}px`]);
|
|
85
|
+
}
|
|
86
|
+
break;
|
|
87
|
+
case 'constrained':
|
|
88
|
+
styles.push(['width', '100%']);
|
|
89
|
+
if (config.width && config.height) {
|
|
90
|
+
styles.push(['max-width', `${config.width}px`], ['max-height', `${config.height}px`]);
|
|
91
|
+
if (!config.aspectRatio) {
|
|
92
|
+
styles.push(['aspect-ratio', `${config.width}/${config.height}`]);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
else if (config.height) {
|
|
96
|
+
styles.push(['height', `${config.height}px`], ['min-height', `${config.height}px`]);
|
|
97
|
+
}
|
|
98
|
+
else if (config.width) {
|
|
99
|
+
styles.push(['max-width', `${config.width}px`]);
|
|
100
|
+
}
|
|
101
|
+
else if (!config.aspectRatio) {
|
|
102
|
+
styles.push(['aspect-ratio', `${config.attributes.width}/${config.attributes.height}`]);
|
|
103
|
+
}
|
|
104
|
+
break;
|
|
105
|
+
case 'full-width':
|
|
106
|
+
styles.push(['width', '100%']);
|
|
107
|
+
if (config.height) {
|
|
108
|
+
styles.push(['height', `${config.height}px`], ['min-height', `${config.height}px`]);
|
|
109
|
+
}
|
|
110
|
+
break;
|
|
95
111
|
}
|
|
96
|
-
|
|
112
|
+
return styles;
|
|
97
113
|
}
|
|
98
|
-
return styles;
|
|
99
|
-
}
|
|
100
114
|
}
|
|
101
|
-
export {
|
|
102
|
-
ImageUtils
|
|
103
|
-
};
|
package/src/index.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export * from
|
|
2
|
-
export * from
|
|
3
|
-
export * from
|
|
1
|
+
export * from './image-processor.js';
|
|
2
|
+
export * from './plugin.js';
|
|
3
|
+
export * from './types.js';
|
package/src/plugin.d.ts
CHANGED
|
@@ -2,9 +2,7 @@
|
|
|
2
2
|
* ImageProcessorPlugin
|
|
3
3
|
* @module @ecopages/image-processor
|
|
4
4
|
*/
|
|
5
|
-
import { Processor, type ProcessorConfig } from '@ecopages/core/
|
|
6
|
-
import type { EcoBuildPlugin } from '@ecopages/core/build/build-types';
|
|
7
|
-
import { ImageProcessor } from './image-processor.js';
|
|
5
|
+
import { Processor, type EcoBuildPlugin, type ProcessorConfig } from '@ecopages/core/processor';
|
|
8
6
|
import type { ImageSize, ImageSpecifications } from './types.js';
|
|
9
7
|
/**
|
|
10
8
|
* Configuration for the image processor
|
|
@@ -33,19 +31,19 @@ export interface ImageProcessorConfig {
|
|
|
33
31
|
* ImageMap
|
|
34
32
|
* This is the representation of the image map in the virtual module
|
|
35
33
|
*/
|
|
36
|
-
export type ImageMap
|
|
34
|
+
export type { ImageMap } from './image-runtime-state.js';
|
|
37
35
|
/**
|
|
38
36
|
* ImageProcessorPlugin
|
|
39
37
|
* A Processor for optimizing images.
|
|
40
38
|
*/
|
|
41
39
|
export declare class ImageProcessorPlugin extends Processor<ImageProcessorConfig> {
|
|
42
40
|
private processor;
|
|
43
|
-
private buildContributionsPrepared;
|
|
44
41
|
private resolvedConfig?;
|
|
45
42
|
processedImages: Record<string, ImageSpecifications>;
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
43
|
+
private preparation?;
|
|
44
|
+
constructor(config: Omit<ProcessorConfig<ImageProcessorConfig>, 'name'>);
|
|
45
|
+
private handleImageChanges;
|
|
46
|
+
plugins(): EcoBuildPlugin[];
|
|
49
47
|
/**
|
|
50
48
|
* Replaces image-map contents without swapping the backing object.
|
|
51
49
|
*
|
|
@@ -55,46 +53,59 @@ export declare class ImageProcessorPlugin extends Processor<ImageProcessorConfig
|
|
|
55
53
|
*/
|
|
56
54
|
private replaceProcessedImages;
|
|
57
55
|
/**
|
|
58
|
-
*
|
|
59
|
-
* manifest.
|
|
56
|
+
* Resolves processor config and creates the sharp-backed worker instance.
|
|
60
57
|
*/
|
|
61
|
-
|
|
58
|
+
private initializeProcessor;
|
|
62
59
|
private getRuntimeVirtualModulePath;
|
|
63
|
-
private getGeneratedOutputPath;
|
|
64
|
-
private hasGeneratedOutputs;
|
|
65
|
-
private rehydrateGeneratedOutputs;
|
|
66
60
|
/**
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
* @returns
|
|
61
|
+
* Restores in-memory image state from previously generated dist artifacts.
|
|
62
|
+
*
|
|
63
|
+
* @returns `true` when state was restored without invoking sharp.
|
|
70
64
|
*/
|
|
71
|
-
private
|
|
65
|
+
private syncProcessedImagesFromDisk;
|
|
66
|
+
private ensureGeneratedOutputs;
|
|
67
|
+
private writeGeneratedFile;
|
|
72
68
|
/**
|
|
73
|
-
*
|
|
69
|
+
* Ensures optimized image outputs and virtual-module state exist on disk.
|
|
74
70
|
*/
|
|
75
71
|
setup(): Promise<void>;
|
|
72
|
+
private prepare;
|
|
73
|
+
/**
|
|
74
|
+
* Prepares the image map on first load of `ecopages:images` when `setup()` has not run in this process.
|
|
75
|
+
*
|
|
76
|
+
* @remarks
|
|
77
|
+
* The Lit static render worker skips Processor setup because the main thread already wrote the outputs, so
|
|
78
|
+
* the worker's map starts empty and is restored from those outputs here.
|
|
79
|
+
*/
|
|
80
|
+
private ensurePrepared;
|
|
76
81
|
/**
|
|
77
82
|
* Process images.
|
|
78
83
|
* @param images
|
|
79
84
|
*/
|
|
80
85
|
process(images: string[]): Promise<void>;
|
|
81
86
|
/**
|
|
82
|
-
*
|
|
83
|
-
*
|
|
87
|
+
* Deletes the optimized files and cache entry recorded for a removed source image.
|
|
88
|
+
*
|
|
89
|
+
* @remarks
|
|
90
|
+
* Variant files are named after a hash of their bytes, so the files to delete are
|
|
91
|
+
* the ones the recorded specification points at, not a pattern on the source name.
|
|
84
92
|
*/
|
|
85
93
|
deleteProcessedImagesbyPath(imagePath: string): Promise<void>;
|
|
86
94
|
/**
|
|
87
|
-
*
|
|
95
|
+
* Removes the cache entry and the output files of `previous` that `next` no longer uses.
|
|
96
|
+
*
|
|
97
|
+
* @remarks
|
|
98
|
+
* Identical encoded bytes share one hashed file across source images, so a file
|
|
99
|
+
* is only removed once no remaining specification references it.
|
|
88
100
|
*/
|
|
89
|
-
private
|
|
101
|
+
private removeSupersededOutputs;
|
|
90
102
|
/**
|
|
91
|
-
*
|
|
103
|
+
* Generate types for the virtual module.
|
|
92
104
|
*/
|
|
93
|
-
|
|
105
|
+
private generateTypes;
|
|
94
106
|
/**
|
|
95
|
-
*
|
|
96
|
-
* @returns The image processor instance.
|
|
107
|
+
* Writes the ambient declaration package for `ecopages:images`.
|
|
97
108
|
*/
|
|
98
|
-
|
|
109
|
+
private writeVirtualModuleTypes;
|
|
99
110
|
}
|
|
100
|
-
export declare const imageProcessorPlugin: (config: Omit<ProcessorConfig<ImageProcessorConfig>, "name"
|
|
111
|
+
export declare const imageProcessorPlugin: (config: Omit<ProcessorConfig<ImageProcessorConfig>, "name">) => ImageProcessorPlugin;
|