@ecopages/image-processor 0.2.0-beta.0 → 0.2.0-beta.10
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/package.json +4 -4
- package/src/image-processor.js +2 -2
- package/src/image-runtime-state.d.ts +20 -0
- package/src/image-runtime-state.js +58 -0
- package/src/plugin.d.ts +17 -10
- package/src/plugin.js +58 -33
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ecopages/image-processor",
|
|
3
|
-
"version": "0.2.0-beta.
|
|
3
|
+
"version": "0.2.0-beta.10",
|
|
4
4
|
"description": "Image processor, transform and optimize images for web",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"image",
|
|
@@ -11,14 +11,14 @@
|
|
|
11
11
|
"type": "module",
|
|
12
12
|
"repository": {
|
|
13
13
|
"type": "git",
|
|
14
|
-
"url": "https://github.com/ecopages/ecopages.git",
|
|
14
|
+
"url": "git+https://github.com/ecopages/ecopages.git",
|
|
15
15
|
"directory": "packages/processors/image-processor"
|
|
16
16
|
},
|
|
17
17
|
"peerDependencies": {
|
|
18
|
-
"@ecopages/core": "0.2.0-beta.
|
|
18
|
+
"@ecopages/core": "0.2.0-beta.10"
|
|
19
19
|
},
|
|
20
20
|
"dependencies": {
|
|
21
|
-
"@ecopages/file-system": "0.2.0-beta.
|
|
21
|
+
"@ecopages/file-system": "0.2.0-beta.10",
|
|
22
22
|
"@ecopages/logger": "^0.2.3",
|
|
23
23
|
"react": "^19.2.6",
|
|
24
24
|
"react-dom": "^19.2.6",
|
package/src/image-processor.js
CHANGED
|
@@ -37,10 +37,10 @@ class ImageProcessor {
|
|
|
37
37
|
if (this.config.cacheEnabled) {
|
|
38
38
|
const cached = await this.cacheManager.readCache(cacheKey);
|
|
39
39
|
if (cached) {
|
|
40
|
-
const mainFilePath = path.join(
|
|
40
|
+
const mainFilePath = path.join(this.config.outputDir, path.basename(cached.attributes.src));
|
|
41
41
|
const mainFileExists = fileSystem.exists(mainFilePath);
|
|
42
42
|
const variantsExist = cached.variants.every(
|
|
43
|
-
(
|
|
43
|
+
(variant) => fileSystem.exists(path.join(this.config.outputDir, path.basename(variant.src)))
|
|
44
44
|
);
|
|
45
45
|
if (mainFileExists && variantsExist) {
|
|
46
46
|
appLogger.debug(`Cache hit for ${imagePath}`);
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { ImageSpecifications } from './types.js';
|
|
2
|
+
export type ImageMap = Record<string, ImageSpecifications>;
|
|
3
|
+
export type ImageProcessorDiskConfig = {
|
|
4
|
+
sourceDir: string;
|
|
5
|
+
outputDir: string;
|
|
6
|
+
acceptedFormats?: string[];
|
|
7
|
+
};
|
|
8
|
+
/**
|
|
9
|
+
* Parses export bindings from the generated image virtual-module source file.
|
|
10
|
+
*/
|
|
11
|
+
export declare function parseVirtualModuleExports(content: string): Record<string, ImageSpecifications> | null;
|
|
12
|
+
export declare function getSourceImagePaths(resolvedConfig: ImageProcessorDiskConfig): Promise<string[]>;
|
|
13
|
+
/**
|
|
14
|
+
* Restores processed image state from generated dist artifacts when every source
|
|
15
|
+
* image has a matching virtual-module export and output files on disk.
|
|
16
|
+
*/
|
|
17
|
+
export declare function loadProcessedImagesFromDisk(options: {
|
|
18
|
+
resolvedConfig: ImageProcessorDiskConfig;
|
|
19
|
+
runtimeVirtualModulePath: string;
|
|
20
|
+
}): Promise<ImageMap | null>;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { fileSystem } from "@ecopages/file-system";
|
|
3
|
+
import { anyCaseToCamelCase } from "./utils.js";
|
|
4
|
+
function parseVirtualModuleExports(content) {
|
|
5
|
+
const exports = {};
|
|
6
|
+
const pattern = /export const (\w+) = ([\s\S]*?) as const;/g;
|
|
7
|
+
let match;
|
|
8
|
+
while ((match = pattern.exec(content)) !== null) {
|
|
9
|
+
try {
|
|
10
|
+
exports[match[1]] = JSON.parse(match[2]);
|
|
11
|
+
} catch {
|
|
12
|
+
return null;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
return exports;
|
|
16
|
+
}
|
|
17
|
+
async function getSourceImagePaths(resolvedConfig) {
|
|
18
|
+
const acceptedFormats = resolvedConfig.acceptedFormats ?? ["jpg", "jpeg", "png", "webp"];
|
|
19
|
+
return fileSystem.glob([`${resolvedConfig.sourceDir}/**/*.{${acceptedFormats.join(",")}}`]);
|
|
20
|
+
}
|
|
21
|
+
function getGeneratedOutputPath(outputDir, src) {
|
|
22
|
+
return path.join(outputDir, path.basename(src));
|
|
23
|
+
}
|
|
24
|
+
async function loadProcessedImagesFromDisk(options) {
|
|
25
|
+
const sourceImages = await getSourceImagePaths(options.resolvedConfig);
|
|
26
|
+
if (sourceImages.length === 0) {
|
|
27
|
+
return {};
|
|
28
|
+
}
|
|
29
|
+
if (!fileSystem.exists(options.runtimeVirtualModulePath)) {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
const exportsByName = parseVirtualModuleExports(fileSystem.readFileSync(options.runtimeVirtualModulePath));
|
|
33
|
+
if (!exportsByName) {
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
const imageMap = {};
|
|
37
|
+
for (const file of sourceImages) {
|
|
38
|
+
const basename = path.basename(file);
|
|
39
|
+
const exportName = anyCaseToCamelCase(basename);
|
|
40
|
+
const spec = exportsByName[exportName];
|
|
41
|
+
if (!spec) {
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
const outputPaths = [spec.attributes.src, ...spec.variants.map((variant) => variant.src)];
|
|
45
|
+
if (!outputPaths.every(
|
|
46
|
+
(src) => fileSystem.exists(getGeneratedOutputPath(options.resolvedConfig.outputDir, src))
|
|
47
|
+
)) {
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
imageMap[basename] = spec;
|
|
51
|
+
}
|
|
52
|
+
return imageMap;
|
|
53
|
+
}
|
|
54
|
+
export {
|
|
55
|
+
getSourceImagePaths,
|
|
56
|
+
loadProcessedImagesFromDisk,
|
|
57
|
+
parseVirtualModuleExports
|
|
58
|
+
};
|
package/src/plugin.d.ts
CHANGED
|
@@ -32,7 +32,7 @@ export interface ImageProcessorConfig {
|
|
|
32
32
|
* ImageMap
|
|
33
33
|
* This is the representation of the image map in the virtual module
|
|
34
34
|
*/
|
|
35
|
-
export type ImageMap
|
|
35
|
+
export type { ImageMap } from './image-runtime-state.js';
|
|
36
36
|
/**
|
|
37
37
|
* ImageProcessorPlugin
|
|
38
38
|
* A Processor for optimizing images.
|
|
@@ -54,22 +54,29 @@ export declare class ImageProcessorPlugin extends Processor<ImageProcessorConfig
|
|
|
54
54
|
*/
|
|
55
55
|
private replaceProcessedImages;
|
|
56
56
|
/**
|
|
57
|
-
*
|
|
58
|
-
|
|
57
|
+
* Resolves processor config and creates the sharp-backed worker instance.
|
|
58
|
+
*/
|
|
59
|
+
private initializeProcessor;
|
|
60
|
+
/**
|
|
61
|
+
* Prepares build-facing processor state before config build seals the app manifest.
|
|
62
|
+
*
|
|
63
|
+
* @remarks
|
|
64
|
+
* Image processing stays in {@link setup}; this hook only initializes config and
|
|
65
|
+
* restores any previously generated virtual-module state from disk.
|
|
59
66
|
*/
|
|
60
67
|
prepareBuildContributions(): Promise<void>;
|
|
61
68
|
private getRuntimeVirtualModulePath;
|
|
62
69
|
private getGeneratedOutputPath;
|
|
63
|
-
private hasGeneratedOutputs;
|
|
64
|
-
private rehydrateGeneratedOutputs;
|
|
65
70
|
/**
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
* @returns
|
|
71
|
+
* Restores in-memory image state from previously generated dist artifacts.
|
|
72
|
+
*
|
|
73
|
+
* @returns `true` when state was restored without invoking sharp.
|
|
69
74
|
*/
|
|
70
|
-
private
|
|
75
|
+
private syncProcessedImagesFromDisk;
|
|
76
|
+
private ensureGeneratedOutputs;
|
|
77
|
+
private writeGeneratedFile;
|
|
71
78
|
/**
|
|
72
|
-
*
|
|
79
|
+
* Ensures optimized image outputs and virtual-module state exist on disk.
|
|
73
80
|
*/
|
|
74
81
|
setup(): Promise<void>;
|
|
75
82
|
/**
|
package/src/plugin.js
CHANGED
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
import { Logger } from "@ecopages/logger";
|
|
9
9
|
import { createImagePlugin, createImagePluginBundler } from "./image-plugins.js";
|
|
10
10
|
import { ImageProcessor } from "./image-processor.js";
|
|
11
|
+
import { getSourceImagePaths, loadProcessedImagesFromDisk } from "./image-runtime-state.js";
|
|
11
12
|
import { anyCaseToCamelCase } from "./utils.js";
|
|
12
13
|
function resolveGeneratedPath(type, options) {
|
|
13
14
|
const { root, module, subPath } = options;
|
|
@@ -110,11 +111,10 @@ class ImageProcessorPlugin extends Processor {
|
|
|
110
111
|
Object.assign(this.processedImages, images);
|
|
111
112
|
}
|
|
112
113
|
/**
|
|
113
|
-
*
|
|
114
|
-
* manifest.
|
|
114
|
+
* Resolves processor config and creates the sharp-backed worker instance.
|
|
115
115
|
*/
|
|
116
|
-
|
|
117
|
-
if (this.
|
|
116
|
+
initializeProcessor() {
|
|
117
|
+
if (this.processor && this.resolvedConfig) {
|
|
118
118
|
return;
|
|
119
119
|
}
|
|
120
120
|
if (!this.context) {
|
|
@@ -138,12 +138,27 @@ class ImageProcessorPlugin extends Processor {
|
|
|
138
138
|
readCache: (key) => this.readCache(key),
|
|
139
139
|
writeCache: (key, data) => this.writeCache(key, data)
|
|
140
140
|
});
|
|
141
|
-
this.replaceProcessedImages(await this.processor.processDirectory());
|
|
142
141
|
if (this.watchConfig) {
|
|
143
142
|
this.watchConfig.paths = [config.sourceDir];
|
|
144
143
|
}
|
|
145
|
-
|
|
146
|
-
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Prepares build-facing processor state before config build seals the app manifest.
|
|
147
|
+
*
|
|
148
|
+
* @remarks
|
|
149
|
+
* Image processing stays in {@link setup}; this hook only initializes config and
|
|
150
|
+
* restores any previously generated virtual-module state from disk.
|
|
151
|
+
*/
|
|
152
|
+
async prepareBuildContributions() {
|
|
153
|
+
if (this.buildContributionsPrepared) {
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
if (process.env.ECOPAGES_LIT_STATIC_RENDER_WORKER === "true") {
|
|
157
|
+
this.buildContributionsPrepared = true;
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
this.initializeProcessor();
|
|
161
|
+
await this.syncProcessedImagesFromDisk();
|
|
147
162
|
this.buildContributionsPrepared = true;
|
|
148
163
|
}
|
|
149
164
|
getRuntimeVirtualModulePath() {
|
|
@@ -162,46 +177,58 @@ class ImageProcessorPlugin extends Processor {
|
|
|
162
177
|
}
|
|
163
178
|
return path.join(this.resolvedConfig.outputDir, path.basename(src));
|
|
164
179
|
}
|
|
165
|
-
|
|
180
|
+
/**
|
|
181
|
+
* Restores in-memory image state from previously generated dist artifacts.
|
|
182
|
+
*
|
|
183
|
+
* @returns `true` when state was restored without invoking sharp.
|
|
184
|
+
*/
|
|
185
|
+
async syncProcessedImagesFromDisk() {
|
|
166
186
|
if (!this.resolvedConfig) {
|
|
167
187
|
return false;
|
|
168
188
|
}
|
|
169
|
-
|
|
189
|
+
const sourceImages = await getSourceImagePaths(this.resolvedConfig);
|
|
190
|
+
const imageMap = await loadProcessedImagesFromDisk({
|
|
191
|
+
resolvedConfig: this.resolvedConfig,
|
|
192
|
+
runtimeVirtualModulePath: this.getRuntimeVirtualModulePath()
|
|
193
|
+
});
|
|
194
|
+
if (imageMap === null) {
|
|
170
195
|
return false;
|
|
171
196
|
}
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
197
|
+
if (sourceImages.length > 0 && Object.keys(imageMap).length !== sourceImages.length) {
|
|
198
|
+
return false;
|
|
199
|
+
}
|
|
200
|
+
this.replaceProcessedImages(imageMap);
|
|
201
|
+
if (Object.keys(imageMap).length > 0) {
|
|
202
|
+
this.generateTypes();
|
|
203
|
+
}
|
|
204
|
+
return true;
|
|
176
205
|
}
|
|
177
|
-
async
|
|
206
|
+
async ensureGeneratedOutputs() {
|
|
178
207
|
if (!this.processor) {
|
|
179
208
|
throw new Error("ImageProcessor not initialized");
|
|
180
209
|
}
|
|
181
|
-
if (this.
|
|
210
|
+
if (await this.syncProcessedImagesFromDisk()) {
|
|
182
211
|
return;
|
|
183
212
|
}
|
|
184
213
|
this.replaceProcessedImages(await this.processor.processDirectory());
|
|
185
|
-
this.dependencies = this.generateDependencies();
|
|
186
214
|
this.generateTypes();
|
|
187
215
|
}
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
*/
|
|
193
|
-
generateDependencies() {
|
|
194
|
-
const deps = [];
|
|
195
|
-
if (process.env.NODE_ENV === "development") {
|
|
216
|
+
writeGeneratedFile(filePath, content) {
|
|
217
|
+
fileSystem.ensureDir(path.dirname(filePath));
|
|
218
|
+
if (fileSystem.exists(filePath) && fileSystem.readFileSync(filePath) === content) {
|
|
219
|
+
return;
|
|
196
220
|
}
|
|
197
|
-
|
|
221
|
+
fileSystem.write(filePath, content);
|
|
198
222
|
}
|
|
199
223
|
/**
|
|
200
|
-
*
|
|
224
|
+
* Ensures optimized image outputs and virtual-module state exist on disk.
|
|
201
225
|
*/
|
|
202
226
|
async setup() {
|
|
203
|
-
|
|
204
|
-
|
|
227
|
+
if (process.env.ECOPAGES_LIT_STATIC_RENDER_WORKER === "true") {
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
this.initializeProcessor();
|
|
231
|
+
await this.ensureGeneratedOutputs();
|
|
205
232
|
}
|
|
206
233
|
/**
|
|
207
234
|
* Process images.
|
|
@@ -288,8 +315,7 @@ declare module "ecopages:images" {
|
|
|
288
315
|
module: this.name,
|
|
289
316
|
subPath: "virtual-module.d.ts"
|
|
290
317
|
});
|
|
291
|
-
|
|
292
|
-
fileSystem.write(typesDir, content);
|
|
318
|
+
this.writeGeneratedFile(typesDir, content);
|
|
293
319
|
logger.debug("Generated types for virtual module", { typesDir });
|
|
294
320
|
const indexTypesDir = resolveGeneratedPath("types", {
|
|
295
321
|
root: this.context.rootDir,
|
|
@@ -297,7 +323,7 @@ declare module "ecopages:images" {
|
|
|
297
323
|
subPath: "index.d.ts"
|
|
298
324
|
});
|
|
299
325
|
const indexContent = 'import "./virtual-module.d.js";';
|
|
300
|
-
|
|
326
|
+
this.writeGeneratedFile(indexTypesDir, indexContent);
|
|
301
327
|
logger.debug("Generated index types for virtual module", { indexTypesDir });
|
|
302
328
|
const runtimeVirtualModulePath = resolveGeneratedPath("cache", {
|
|
303
329
|
root: this.context.distDir,
|
|
@@ -307,8 +333,7 @@ declare module "ecopages:images" {
|
|
|
307
333
|
const runtimeModuleContent = Object.entries(this.processedImages).map(([key, value]) => {
|
|
308
334
|
return `export const ${anyCaseToCamelCase(key)} = ${JSON.stringify(value, null, 2)} as const;`;
|
|
309
335
|
}).join("\n\n");
|
|
310
|
-
|
|
311
|
-
fileSystem.write(runtimeVirtualModulePath, runtimeModuleContent);
|
|
336
|
+
this.writeGeneratedFile(runtimeVirtualModulePath, runtimeModuleContent);
|
|
312
337
|
logger.debug("Generated runtime virtual module for images", { runtimeVirtualModulePath });
|
|
313
338
|
}
|
|
314
339
|
/**
|