@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/plugin.js
CHANGED
|
@@ -1,19 +1,18 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
import
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
debug: process.env.ECOPAGES_LOGGER_DEBUG === "true"
|
|
1
|
+
/**
|
|
2
|
+
* ImageProcessorPlugin
|
|
3
|
+
* @module @ecopages/image-processor
|
|
4
|
+
*/
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import { fileSystem } from '@ecopages/file-system';
|
|
7
|
+
import { mergeProcessorOptions, Processor, resolveGeneratedPath, writeGeneratedTypesPackage, } from '@ecopages/core/processor';
|
|
8
|
+
import { Logger } from '@ecopages/logger';
|
|
9
|
+
import { isEcopagesEnvEnabled } from '@ecopages/core/processor';
|
|
10
|
+
import { createImagePlugin, createImagePluginBundler } from './image-plugins.js';
|
|
11
|
+
import { ImageProcessor } from './image-processor.js';
|
|
12
|
+
import { getOutputSources, getSourceImagePaths, loadProcessedImagesFromDisk, } from './image-runtime-state.js';
|
|
13
|
+
import { anyCaseToCamelCase } from './utils.js';
|
|
14
|
+
const logger = new Logger('[@ecopages/image-processor]', {
|
|
15
|
+
debug: isEcopagesEnvEnabled('ECOPAGES_LOGGER_DEBUG'),
|
|
17
16
|
});
|
|
18
17
|
const IMAGE_VIRTUAL_MODULE_TYPES = `/**
|
|
19
18
|
* ImageAttributes
|
|
@@ -60,216 +59,287 @@ type ImageSize = {
|
|
|
60
59
|
width: number;
|
|
61
60
|
label: string;
|
|
62
61
|
};`;
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
62
|
+
/**
|
|
63
|
+
* ImageProcessorPlugin
|
|
64
|
+
* A Processor for optimizing images.
|
|
65
|
+
*/
|
|
66
|
+
export class ImageProcessorPlugin extends Processor {
|
|
67
|
+
resolvedConfig;
|
|
68
|
+
processedImages = {};
|
|
69
|
+
preparation;
|
|
70
|
+
constructor(config) {
|
|
71
|
+
const acceptedFormats = config.options?.acceptedFormats ?? ['jpg', 'jpeg', 'png', 'webp'];
|
|
72
|
+
const extensionPatterns = acceptedFormats.map((format) => `*.${format.replace(/^\./, '')}`);
|
|
73
|
+
const defaultWatchConfig = {
|
|
74
|
+
paths: [],
|
|
75
|
+
extensions: acceptedFormats,
|
|
76
|
+
onChanges: async (changes) => this.handleImageChanges(changes),
|
|
77
|
+
onError: (error) => logger.error('Watcher error', { error }),
|
|
78
|
+
};
|
|
79
|
+
super({
|
|
80
|
+
...config,
|
|
81
|
+
name: 'ecopages-image-processor',
|
|
82
|
+
capabilities: [
|
|
83
|
+
{
|
|
84
|
+
kind: 'image',
|
|
85
|
+
extensions: extensionPatterns,
|
|
86
|
+
},
|
|
87
|
+
],
|
|
88
|
+
watch: config.watch ? mergeProcessorOptions(defaultWatchConfig, config.watch) : defaultWatchConfig,
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
async handleImageChanges({ created, changed, deleted }) {
|
|
92
|
+
const changedImages = [...created, ...changed];
|
|
93
|
+
if (changedImages.length > 0) {
|
|
94
|
+
await this.process(changedImages);
|
|
95
|
+
}
|
|
96
|
+
for (const imagePath of deleted) {
|
|
97
|
+
await this.deleteProcessedImagesbyPath(imagePath);
|
|
86
98
|
}
|
|
87
|
-
],
|
|
88
|
-
watch: config.watch ? deepMerge(defaultWatchConfig, config.watch) : defaultWatchConfig
|
|
89
|
-
});
|
|
90
|
-
}
|
|
91
|
-
get buildPlugins() {
|
|
92
|
-
return [createImagePluginBundler(this.processedImages)];
|
|
93
|
-
}
|
|
94
|
-
get plugins() {
|
|
95
|
-
return [createImagePlugin(this.processedImages)];
|
|
96
|
-
}
|
|
97
|
-
/**
|
|
98
|
-
* Replaces image-map contents without swapping the backing object.
|
|
99
|
-
*
|
|
100
|
-
* @remarks
|
|
101
|
-
* The build/runtime virtual-module plugins close over `processedImages`, so
|
|
102
|
-
* mutating the existing object keeps those plugins live after preparation.
|
|
103
|
-
*/
|
|
104
|
-
replaceProcessedImages(images) {
|
|
105
|
-
for (const key of Object.keys(this.processedImages)) {
|
|
106
|
-
delete this.processedImages[key];
|
|
107
99
|
}
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
return;
|
|
100
|
+
plugins() {
|
|
101
|
+
return [
|
|
102
|
+
{ ...createImagePlugin(this.processedImages, () => this.ensurePrepared()), environments: ['server'] },
|
|
103
|
+
{
|
|
104
|
+
...createImagePluginBundler(this.processedImages, () => this.ensurePrepared()),
|
|
105
|
+
environments: ['browser'],
|
|
106
|
+
},
|
|
107
|
+
];
|
|
117
108
|
}
|
|
118
|
-
|
|
119
|
-
|
|
109
|
+
/**
|
|
110
|
+
* Replaces image-map contents without swapping the backing object.
|
|
111
|
+
*
|
|
112
|
+
* @remarks
|
|
113
|
+
* The build/runtime virtual-module plugins close over `processedImages`, so
|
|
114
|
+
* mutating the existing object keeps those plugins live after preparation.
|
|
115
|
+
*/
|
|
116
|
+
replaceProcessedImages(images) {
|
|
117
|
+
for (const key of Object.keys(this.processedImages)) {
|
|
118
|
+
delete this.processedImages[key];
|
|
119
|
+
}
|
|
120
|
+
Object.assign(this.processedImages, images);
|
|
120
121
|
}
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
122
|
+
/**
|
|
123
|
+
* Resolves processor config and creates the sharp-backed worker instance.
|
|
124
|
+
*/
|
|
125
|
+
initializeProcessor() {
|
|
126
|
+
if (this.processor && this.resolvedConfig) {
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
if (!this.context) {
|
|
130
|
+
throw new Error('ImageProcessor requires context to be set');
|
|
131
|
+
}
|
|
132
|
+
logger.debug('Setting up image processor', {
|
|
133
|
+
srcDir: this.context.srcDir,
|
|
134
|
+
distDir: this.context.distDir,
|
|
135
|
+
});
|
|
136
|
+
const defaultConfig = {
|
|
137
|
+
sourceDir: `${this.context.srcDir}/public/assets/images`,
|
|
138
|
+
outputDir: `${this.context.distDir}/assets/optimized`,
|
|
139
|
+
publicPath: '/assets/optimized',
|
|
140
|
+
sizes: [],
|
|
141
|
+
quality: 80,
|
|
142
|
+
format: 'webp',
|
|
143
|
+
};
|
|
144
|
+
const config = this.options ? mergeProcessorOptions(defaultConfig, this.options) : defaultConfig;
|
|
145
|
+
this.resolvedConfig = config;
|
|
146
|
+
this.processor = new ImageProcessor(config, {
|
|
147
|
+
readCache: (key) => this.readCache(key),
|
|
148
|
+
writeCache: (key, data) => this.writeCache(key, data),
|
|
149
|
+
});
|
|
150
|
+
if (this.watchConfig) {
|
|
151
|
+
this.watchConfig.paths = [config.sourceDir];
|
|
152
|
+
}
|
|
142
153
|
}
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
154
|
+
getRuntimeVirtualModulePath() {
|
|
155
|
+
if (!this.context) {
|
|
156
|
+
throw new Error('ImageProcessor requires context to be set');
|
|
157
|
+
}
|
|
158
|
+
return resolveGeneratedPath('cache', {
|
|
159
|
+
root: this.context.distDir,
|
|
160
|
+
module: this.name,
|
|
161
|
+
subPath: 'virtual-module.ts',
|
|
162
|
+
});
|
|
150
163
|
}
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
164
|
+
/**
|
|
165
|
+
* Restores in-memory image state from previously generated dist artifacts.
|
|
166
|
+
*
|
|
167
|
+
* @returns `true` when state was restored without invoking sharp.
|
|
168
|
+
*/
|
|
169
|
+
async syncProcessedImagesFromDisk() {
|
|
170
|
+
if (!this.resolvedConfig) {
|
|
171
|
+
return false;
|
|
172
|
+
}
|
|
173
|
+
const sourceImages = await getSourceImagePaths(this.resolvedConfig);
|
|
174
|
+
const imageMap = await loadProcessedImagesFromDisk({
|
|
175
|
+
resolvedConfig: this.resolvedConfig,
|
|
176
|
+
runtimeVirtualModulePath: this.getRuntimeVirtualModulePath(),
|
|
177
|
+
});
|
|
178
|
+
if (imageMap === null) {
|
|
179
|
+
return false;
|
|
180
|
+
}
|
|
181
|
+
if (sourceImages.length > 0 && Object.keys(imageMap).length !== sourceImages.length) {
|
|
182
|
+
return false;
|
|
183
|
+
}
|
|
184
|
+
this.replaceProcessedImages(imageMap);
|
|
185
|
+
if (Object.keys(imageMap).length > 0) {
|
|
186
|
+
this.generateTypes();
|
|
187
|
+
}
|
|
188
|
+
return true;
|
|
160
189
|
}
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
190
|
+
async ensureGeneratedOutputs() {
|
|
191
|
+
if (!this.processor) {
|
|
192
|
+
throw new Error('ImageProcessor not initialized');
|
|
193
|
+
}
|
|
194
|
+
if (await this.syncProcessedImagesFromDisk()) {
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
this.replaceProcessedImages(await this.processor.processDirectory());
|
|
198
|
+
this.generateTypes();
|
|
166
199
|
}
|
|
167
|
-
|
|
168
|
-
|
|
200
|
+
writeGeneratedFile(filePath, content) {
|
|
201
|
+
fileSystem.ensureDir(path.dirname(filePath));
|
|
202
|
+
if (fileSystem.exists(filePath) && fileSystem.readFileSync(filePath) === content) {
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
fileSystem.write(filePath, content);
|
|
169
206
|
}
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
if (!this.processor) {
|
|
177
|
-
throw new Error("ImageProcessor not initialized");
|
|
207
|
+
/**
|
|
208
|
+
* Ensures optimized image outputs and virtual-module state exist on disk.
|
|
209
|
+
*/
|
|
210
|
+
async setup() {
|
|
211
|
+
this.preparation = this.prepare();
|
|
212
|
+
await this.preparation;
|
|
178
213
|
}
|
|
179
|
-
|
|
180
|
-
|
|
214
|
+
async prepare() {
|
|
215
|
+
this.initializeProcessor();
|
|
216
|
+
await this.ensureGeneratedOutputs();
|
|
181
217
|
}
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
const deps = [];
|
|
193
|
-
if (process.env.NODE_ENV === "development") {
|
|
218
|
+
/**
|
|
219
|
+
* Prepares the image map on first load of `ecopages:images` when `setup()` has not run in this process.
|
|
220
|
+
*
|
|
221
|
+
* @remarks
|
|
222
|
+
* The Lit static render worker skips Processor setup because the main thread already wrote the outputs, so
|
|
223
|
+
* the worker's map starts empty and is restored from those outputs here.
|
|
224
|
+
*/
|
|
225
|
+
ensurePrepared() {
|
|
226
|
+
this.preparation ??= this.prepare();
|
|
227
|
+
return this.preparation;
|
|
194
228
|
}
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
229
|
+
/**
|
|
230
|
+
* Process images.
|
|
231
|
+
* @param images
|
|
232
|
+
*/
|
|
233
|
+
async process(images) {
|
|
234
|
+
if (!this.processor) {
|
|
235
|
+
throw new Error('ImageProcessor not initialized');
|
|
236
|
+
}
|
|
237
|
+
logger.debug('Processing images', { count: images.length });
|
|
238
|
+
for (const image of images) {
|
|
239
|
+
try {
|
|
240
|
+
const result = await this.processor.processImage(image);
|
|
241
|
+
if (result) {
|
|
242
|
+
const key = path.basename(image);
|
|
243
|
+
const previous = this.processedImages[key];
|
|
244
|
+
this.processedImages[key] = result;
|
|
245
|
+
if (previous) {
|
|
246
|
+
await this.removeSupersededOutputs(previous, result);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
catch (error) {
|
|
251
|
+
logger.error('Failed to process image', { image, error });
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
this.generateTypes();
|
|
211
255
|
}
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
256
|
+
/**
|
|
257
|
+
* Deletes the optimized files and cache entry recorded for a removed source image.
|
|
258
|
+
*
|
|
259
|
+
* @remarks
|
|
260
|
+
* Variant files are named after a hash of their bytes, so the files to delete are
|
|
261
|
+
* the ones the recorded specification points at, not a pattern on the source name.
|
|
262
|
+
*/
|
|
263
|
+
async deleteProcessedImagesbyPath(imagePath) {
|
|
264
|
+
if (!this.processor) {
|
|
265
|
+
throw new Error('ImageProcessor not initialized');
|
|
266
|
+
}
|
|
267
|
+
logger.debug('Deleting processed images', { path: imagePath });
|
|
268
|
+
const key = path.basename(imagePath);
|
|
269
|
+
const previous = this.processedImages[key];
|
|
270
|
+
if (!previous) {
|
|
271
|
+
return;
|
|
218
272
|
}
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
273
|
+
delete this.processedImages[key];
|
|
274
|
+
await this.removeSupersededOutputs(previous);
|
|
275
|
+
this.generateTypes();
|
|
222
276
|
}
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
277
|
+
/**
|
|
278
|
+
* Removes the cache entry and the output files of `previous` that `next` no longer uses.
|
|
279
|
+
*
|
|
280
|
+
* @remarks
|
|
281
|
+
* Identical encoded bytes share one hashed file across source images, so a file
|
|
282
|
+
* is only removed once no remaining specification references it.
|
|
283
|
+
*/
|
|
284
|
+
async removeSupersededOutputs(previous, next) {
|
|
285
|
+
if (!this.resolvedConfig) {
|
|
286
|
+
throw new Error('ImageProcessor not initialized');
|
|
287
|
+
}
|
|
288
|
+
const referenced = new Set();
|
|
289
|
+
for (const spec of [...Object.values(this.processedImages), ...(next ? [next] : [])]) {
|
|
290
|
+
for (const src of getOutputSources(spec)) {
|
|
291
|
+
referenced.add(path.basename(src));
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
const files = getOutputSources(previous)
|
|
295
|
+
.map((src) => path.basename(src))
|
|
296
|
+
.filter((name) => !referenced.has(name));
|
|
297
|
+
for (const file of new Set(files)) {
|
|
298
|
+
const filePath = path.join(this.resolvedConfig.outputDir, file);
|
|
299
|
+
try {
|
|
300
|
+
await fileSystem.removeAsync(filePath);
|
|
301
|
+
logger.debug('Deleted processed image', { file: filePath });
|
|
302
|
+
}
|
|
303
|
+
catch (error) {
|
|
304
|
+
logger.error('Failed to delete processed image', { file: filePath, error });
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
if (previous.cacheKey !== next?.cacheKey) {
|
|
308
|
+
const cachePath = this.getCachePath(previous.cacheKey);
|
|
309
|
+
try {
|
|
310
|
+
await fileSystem.removeAsync(cachePath);
|
|
311
|
+
logger.debug('Deleted cache file for image', { cachePath });
|
|
312
|
+
}
|
|
313
|
+
catch (error) {
|
|
314
|
+
logger.error('Failed to delete cache file for image', { cachePath, error });
|
|
315
|
+
}
|
|
316
|
+
}
|
|
232
317
|
}
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
}
|
|
318
|
+
/**
|
|
319
|
+
* Generate types for the virtual module.
|
|
320
|
+
*/
|
|
321
|
+
generateTypes() {
|
|
322
|
+
this.writeVirtualModuleTypes(Object.keys(this.processedImages));
|
|
323
|
+
if (!this.context)
|
|
324
|
+
throw new Error('Processor is not configured correctly');
|
|
325
|
+
const runtimeVirtualModulePath = resolveGeneratedPath('cache', {
|
|
326
|
+
root: this.context.distDir,
|
|
327
|
+
module: this.name,
|
|
328
|
+
subPath: 'virtual-module.ts',
|
|
329
|
+
});
|
|
330
|
+
const runtimeModuleContent = Object.entries(this.processedImages)
|
|
331
|
+
.map(([key, value]) => {
|
|
332
|
+
return `export const ${anyCaseToCamelCase(key)} = ${JSON.stringify(value, null, 2)} as const;`;
|
|
249
333
|
})
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
const cachePath = this.getCachePath(cacheKey);
|
|
254
|
-
await fileSystem.removeAsync(cachePath);
|
|
255
|
-
logger.debug("Deleted cache file for image", { cachePath });
|
|
256
|
-
} catch (error) {
|
|
257
|
-
logger.error("Failed to delete cache file for image", { imagePath, error });
|
|
258
|
-
}
|
|
259
|
-
delete this.processedImages[path.basename(imagePath)];
|
|
260
|
-
this.generateTypes();
|
|
261
|
-
} catch (error) {
|
|
262
|
-
logger.error("Failed to delete processed images", { path: imagePath, error });
|
|
334
|
+
.join('\n\n');
|
|
335
|
+
this.writeGeneratedFile(runtimeVirtualModulePath, runtimeModuleContent);
|
|
336
|
+
logger.debug('Generated runtime virtual module for images', { runtimeVirtualModulePath });
|
|
263
337
|
}
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
if (!this.options?.outputDir) {
|
|
270
|
-
throw new Error("Output directory not set");
|
|
271
|
-
}
|
|
272
|
-
const content = `
|
|
338
|
+
/**
|
|
339
|
+
* Writes the ambient declaration package for `ecopages:images`.
|
|
340
|
+
*/
|
|
341
|
+
writeVirtualModuleTypes(imageKeys) {
|
|
342
|
+
const content = `
|
|
273
343
|
/**
|
|
274
344
|
* Do not edit manually. This file is auto-generated.
|
|
275
345
|
* This file contains the type definitions for the virtual module "ecopages:images".
|
|
@@ -278,55 +348,33 @@ class ImageProcessorPlugin extends Processor {
|
|
|
278
348
|
${IMAGE_VIRTUAL_MODULE_TYPES}
|
|
279
349
|
|
|
280
350
|
declare module "ecopages:images" {
|
|
281
|
-
${
|
|
351
|
+
${imageKeys.map((key) => `export const ${anyCaseToCamelCase(key)}: ImageSpecifications;`).join('\n ')}
|
|
282
352
|
}`;
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
}).join("\n\n");
|
|
308
|
-
fileSystem.ensureDir(path.dirname(runtimeVirtualModulePath));
|
|
309
|
-
fileSystem.write(runtimeVirtualModulePath, runtimeModuleContent);
|
|
310
|
-
logger.debug("Generated runtime virtual module for images", { runtimeVirtualModulePath });
|
|
311
|
-
}
|
|
312
|
-
/**
|
|
313
|
-
* Teardown the image processor.
|
|
314
|
-
*/
|
|
315
|
-
async teardown() {
|
|
316
|
-
logger.debug("Tearing down image processor");
|
|
317
|
-
}
|
|
318
|
-
/**
|
|
319
|
-
* Get the image processor instance.
|
|
320
|
-
* @returns The image processor instance.
|
|
321
|
-
*/
|
|
322
|
-
getProcessor() {
|
|
323
|
-
return this.processor;
|
|
324
|
-
}
|
|
353
|
+
if (!this.context)
|
|
354
|
+
throw new Error('Processor is not configured correctly');
|
|
355
|
+
const typesDir = resolveGeneratedPath('types', {
|
|
356
|
+
root: this.context.rootDir,
|
|
357
|
+
module: this.name,
|
|
358
|
+
subPath: 'virtual-module.d.ts',
|
|
359
|
+
});
|
|
360
|
+
this.writeGeneratedFile(typesDir, content);
|
|
361
|
+
logger.debug('Generated types for virtual module', { typesDir });
|
|
362
|
+
const indexTypesDir = resolveGeneratedPath('types', {
|
|
363
|
+
root: this.context.rootDir,
|
|
364
|
+
module: this.name,
|
|
365
|
+
subPath: 'index.d.ts',
|
|
366
|
+
});
|
|
367
|
+
const indexContent = 'import "./virtual-module.d.js";\n';
|
|
368
|
+
this.writeGeneratedFile(indexTypesDir, indexContent);
|
|
369
|
+
logger.debug('Generated index types for virtual module', { indexTypesDir });
|
|
370
|
+
writeGeneratedTypesPackage({
|
|
371
|
+
root: this.context.rootDir,
|
|
372
|
+
module: this.name,
|
|
373
|
+
packageName: '@types/ecopages-image-processor',
|
|
374
|
+
writeFile: (filePath, content) => this.writeGeneratedFile(filePath, content),
|
|
375
|
+
});
|
|
376
|
+
}
|
|
325
377
|
}
|
|
326
|
-
const imageProcessorPlugin = (config) => {
|
|
327
|
-
|
|
328
|
-
};
|
|
329
|
-
export {
|
|
330
|
-
ImageProcessorPlugin,
|
|
331
|
-
imageProcessorPlugin
|
|
378
|
+
export const imageProcessorPlugin = (config) => {
|
|
379
|
+
return new ImageProcessorPlugin(config);
|
|
332
380
|
};
|
package/src/types.js
CHANGED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/src/utils.js
CHANGED
|
@@ -1,27 +1,26 @@
|
|
|
1
|
-
function anyCaseToCamelCase(str) {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
1
|
+
export function anyCaseToCamelCase(str) {
|
|
2
|
+
if (!str)
|
|
3
|
+
return '';
|
|
4
|
+
let result = '';
|
|
5
|
+
let capitalize = false;
|
|
6
|
+
for (let i = 0; i < str.length; i++) {
|
|
7
|
+
const char = str[i];
|
|
8
|
+
if (/[0-9]/.test(char)) {
|
|
9
|
+
result += char;
|
|
10
|
+
capitalize = true;
|
|
11
|
+
continue;
|
|
12
|
+
}
|
|
13
|
+
if (/[a-zA-Z]/.test(char)) {
|
|
14
|
+
if (capitalize) {
|
|
15
|
+
result += char.toUpperCase();
|
|
16
|
+
capitalize = false;
|
|
17
|
+
}
|
|
18
|
+
else {
|
|
19
|
+
result += char.toLowerCase();
|
|
20
|
+
}
|
|
21
|
+
continue;
|
|
22
|
+
}
|
|
23
|
+
capitalize = true;
|
|
11
24
|
}
|
|
12
|
-
|
|
13
|
-
if (capitalize) {
|
|
14
|
-
result += char.toUpperCase();
|
|
15
|
-
capitalize = false;
|
|
16
|
-
} else {
|
|
17
|
-
result += char.toLowerCase();
|
|
18
|
-
}
|
|
19
|
-
continue;
|
|
20
|
-
}
|
|
21
|
-
capitalize = true;
|
|
22
|
-
}
|
|
23
|
-
return result;
|
|
25
|
+
return result;
|
|
24
26
|
}
|
|
25
|
-
export {
|
|
26
|
-
anyCaseToCamelCase
|
|
27
|
-
};
|