@ecopages/image-processor 0.2.0-beta.31 → 0.2.0-beta.33

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