@place-framework/place-block-image 1.0.0

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.
Files changed (53) hide show
  1. package/README.md +117 -0
  2. package/dist/constants/index.d.ts +9 -0
  3. package/dist/constants/index.d.ts.map +1 -0
  4. package/dist/constants/index.js +17 -0
  5. package/dist/constants/index.js.map +1 -0
  6. package/dist/index.d.ts +3 -0
  7. package/dist/index.d.ts.map +1 -0
  8. package/dist/index.js +7 -0
  9. package/dist/index.js.map +1 -0
  10. package/dist/templates/react-jsx.d.ts +2 -0
  11. package/dist/templates/react-jsx.d.ts.map +1 -0
  12. package/dist/templates/react-jsx.js +29 -0
  13. package/dist/templates/react-jsx.js.map +1 -0
  14. package/dist/templates/react-tsx.d.ts +2 -0
  15. package/dist/templates/react-tsx.d.ts.map +1 -0
  16. package/dist/templates/react-tsx.js +35 -0
  17. package/dist/templates/react-tsx.js.map +1 -0
  18. package/dist/templates/shared/index.d.ts +6 -0
  19. package/dist/templates/shared/index.d.ts.map +1 -0
  20. package/dist/templates/shared/index.js +49 -0
  21. package/dist/templates/shared/index.js.map +1 -0
  22. package/dist/templates/shared/react.d.ts +10 -0
  23. package/dist/templates/shared/react.d.ts.map +1 -0
  24. package/dist/templates/shared/react.js +48 -0
  25. package/dist/templates/shared/react.js.map +1 -0
  26. package/dist/templates/vue.d.ts +2 -0
  27. package/dist/templates/vue.d.ts.map +1 -0
  28. package/dist/templates/vue.js +100 -0
  29. package/dist/templates/vue.js.map +1 -0
  30. package/dist/templates.d.ts +5 -0
  31. package/dist/templates.d.ts.map +1 -0
  32. package/dist/templates.js +32 -0
  33. package/dist/templates.js.map +1 -0
  34. package/dist/utils/index.d.ts +8 -0
  35. package/dist/utils/index.d.ts.map +1 -0
  36. package/dist/utils/index.js +32 -0
  37. package/dist/utils/index.js.map +1 -0
  38. package/dist/webpack-plugin.d.ts +49 -0
  39. package/dist/webpack-plugin.d.ts.map +1 -0
  40. package/dist/webpack-plugin.js +259 -0
  41. package/dist/webpack-plugin.js.map +1 -0
  42. package/package.json +49 -0
  43. package/src/constants/index.ts +14 -0
  44. package/src/index.ts +4 -0
  45. package/src/templates/react-jsx.ts +27 -0
  46. package/src/templates/react-tsx.ts +33 -0
  47. package/src/templates/shared/index.ts +47 -0
  48. package/src/templates/shared/react.ts +51 -0
  49. package/src/templates/vue.ts +98 -0
  50. package/src/templates.ts +29 -0
  51. package/src/utils/index.ts +35 -0
  52. package/src/webpack-plugin.ts +273 -0
  53. package/tsconfig.json +20 -0
@@ -0,0 +1,273 @@
1
+ import * as fs from 'fs';
2
+ import * as path from 'path';
3
+ import { glob } from 'glob';
4
+ import sizeOf from 'image-size';
5
+ import { Compiler } from 'webpack';
6
+ import { getTemplate } from './templates';
7
+
8
+ export interface PlaceBlockImagePluginOptions {
9
+ imagePrefix?: string;
10
+ imageDir: string;
11
+ scssPath: string;
12
+ // Component generation options
13
+ componentPath?: string;
14
+ componentType?: 'tsx' | 'jsx' | 'vue';
15
+ generateComponent?: boolean;
16
+ }
17
+
18
+ export interface ImageDimensions {
19
+ width: number;
20
+ height: number;
21
+ filename: string;
22
+ className: string;
23
+ }
24
+
25
+ export class PlaceBlockImagePlugin {
26
+ private options: PlaceBlockImagePluginOptions & { imagePrefix: string };
27
+ private isGenerating: boolean = false;
28
+ private lastGenerationTime: number = 0;
29
+
30
+ constructor(options: PlaceBlockImagePluginOptions) {
31
+ this.options = {
32
+ imagePrefix: 'image-',
33
+ generateComponent: true,
34
+ componentType: 'tsx',
35
+ ...options
36
+ };
37
+ }
38
+
39
+ /**
40
+ * Generate CSS class name from filename
41
+ */
42
+ private generateClassName(filename: string): string {
43
+ const name = path.basename(filename, path.extname(filename));
44
+ // Convert to kebab-case and remove special characters
45
+ const cleanName = name
46
+ .toLowerCase()
47
+ .replace(/[^a-z0-9-]/g, '-')
48
+ .replace(/-+/g, '-')
49
+ .replace(/^-|-$/g, '');
50
+
51
+ return `${this.options.imagePrefix}${cleanName}`;
52
+ }
53
+
54
+ /**
55
+ * Scan directory for images and get their dimensions
56
+ */
57
+ private async scanImages(): Promise<ImageDimensions[]> {
58
+ const imageExtensions = ['jpg', 'jpeg', 'png', 'gif', 'svg', 'webp', 'bmp', 'ico', 'avif'];
59
+ const pattern = `${this.options.imageDir}/**/*.{${imageExtensions.join(',')}}`;
60
+
61
+ const files = await glob(pattern, { nodir: true });
62
+ const results: ImageDimensions[] = [];
63
+
64
+ for (const file of files) {
65
+ try {
66
+ const dimensions = sizeOf(file);
67
+
68
+ if (dimensions.width && dimensions.height) {
69
+ const relativePath = path.relative(this.options.imageDir, file);
70
+ const className = this.generateClassName(relativePath);
71
+
72
+ results.push({
73
+ width: dimensions.width,
74
+ height: dimensions.height,
75
+ filename: relativePath,
76
+ className
77
+ });
78
+ }
79
+ } catch (error) {
80
+ console.warn(`⚠️ Could not read dimensions for ${file}:`, error);
81
+ }
82
+ }
83
+
84
+ return results;
85
+ }
86
+
87
+ /**
88
+ * Generate SCSS with CSS custom properties and base class
89
+ */
90
+ private generateScss(images: ImageDimensions[]): string {
91
+ const wrapperClassName = `${this.options.imagePrefix}wrapper`;
92
+ const lines = [
93
+ '// Auto-generated image dimensions',
94
+ '// This file is generated by place-block-image webpack plugin',
95
+ '// Do not edit manually - changes will be overwritten',
96
+ '',
97
+ '// Base wrapper and image styles',
98
+ `.${wrapperClassName} {`,
99
+ ' display: block;',
100
+ ' aspect-ratio: calc(var(--p-width) / var(--p-height));',
101
+ '',
102
+ ' &,',
103
+ ' img {',
104
+ ' display: block;',
105
+ ' }',
106
+ '',
107
+ '// Ensure images fill the wrapper and maintain aspect ratio',
108
+ ' img {',
109
+ ' width: 100%;',
110
+ ' height: 100%;',
111
+ ' object-fit: cover;',
112
+ ' }',
113
+ '}',
114
+ '',
115
+ '// Image-specific dimensions',
116
+ ...images.map(img =>
117
+ `.${wrapperClassName}.${img.className} {\n --p-width: ${img.width}px;\n --p-height: ${img.height}px;\n}`
118
+ ),
119
+ ''
120
+ ];
121
+
122
+ return lines.join('\n');
123
+ }
124
+
125
+ /**
126
+ * Write SCSS file
127
+ */
128
+ private async writeScssFile(images: ImageDimensions[]): Promise<void> {
129
+ const scssContent = this.generateScss(images);
130
+ const outputDir = path.dirname(this.options.scssPath);
131
+
132
+ // Ensure output directory exists
133
+ if (!fs.existsSync(outputDir)) {
134
+ fs.mkdirSync(outputDir, { recursive: true });
135
+ }
136
+
137
+ fs.writeFileSync(this.options.scssPath, scssContent);
138
+ }
139
+
140
+ /**
141
+ * Generate and write component file
142
+ */
143
+ private async writeComponentFile(): Promise<void> {
144
+ if (!this.options.generateComponent || !this.options.componentPath || !this.options.componentType) {
145
+ return;
146
+ }
147
+
148
+ const componentFileName = `PlaceBlockImage.${this.options.componentType}`;
149
+ const componentFilePath = path.resolve(this.options.componentPath, componentFileName);
150
+
151
+ // Check if component already exists
152
+ if (fs.existsSync(componentFilePath)) {
153
+ console.log(`📦 Component already exists, skipping: ${componentFilePath}`);
154
+ return;
155
+ }
156
+
157
+ const componentContent = getTemplate(this.options.componentType, this.options.imagePrefix);
158
+ const outputDir = path.dirname(componentFilePath);
159
+
160
+ // Ensure output directory exists
161
+ if (!fs.existsSync(outputDir)) {
162
+ fs.mkdirSync(outputDir, { recursive: true });
163
+ }
164
+
165
+ fs.writeFileSync(componentFilePath, componentContent);
166
+ console.log(`📦 Generated component: ${componentFilePath}`);
167
+ }
168
+
169
+ /**
170
+ * Check if images have changed since last generation
171
+ */
172
+ private async shouldRegenerate(): Promise<boolean> {
173
+ if (!fs.existsSync(this.options.scssPath)) {
174
+ return true;
175
+ }
176
+
177
+ const scssStats = fs.statSync(this.options.scssPath);
178
+ const imageExtensions = ['jpg', 'jpeg', 'png', 'gif', 'svg', 'webp', 'bmp', 'ico', 'avif'];
179
+ const pattern = `${this.options.imageDir}/**/*.{${imageExtensions.join(',')}}`;
180
+
181
+ try {
182
+ const files = await glob(pattern, { nodir: true });
183
+
184
+ for (const file of files) {
185
+ const fileStats = fs.statSync(file);
186
+ if (fileStats.mtime > scssStats.mtime) {
187
+ return true;
188
+ }
189
+ }
190
+
191
+ return false;
192
+ } catch {
193
+ return true;
194
+ }
195
+ }
196
+
197
+ apply(compiler: Compiler): void {
198
+ // Use environment hook to run only once at startup
199
+ compiler.hooks.environment.tap('PlaceBlockImagePlugin', () => {
200
+ this.generateImageStyles();
201
+ });
202
+
203
+ // Also run on watchRun for file changes during development
204
+ compiler.hooks.watchRun.tapAsync('PlaceBlockImagePlugin', async (compiler, callback) => {
205
+ // Prevent running too frequently
206
+ const now = Date.now();
207
+ if (this.isGenerating || (now - this.lastGenerationTime) < 1000) {
208
+ callback();
209
+ return;
210
+ }
211
+
212
+ try {
213
+ const shouldRegenerate = await this.shouldRegenerate();
214
+ if (shouldRegenerate) {
215
+ await this.generateImageStyles();
216
+ }
217
+ callback();
218
+ } catch (error) {
219
+ console.error('❌ PlaceBlockImagePlugin error:', error);
220
+ callback();
221
+ }
222
+ });
223
+ }
224
+
225
+ private async generateImageStyles(): Promise<void> {
226
+ if (this.isGenerating) {
227
+ return;
228
+ }
229
+
230
+ this.isGenerating = true;
231
+ this.lastGenerationTime = Date.now();
232
+
233
+ try {
234
+ console.log('🖼️ PlaceBlockImagePlugin: Generating image styles...');
235
+
236
+ if (!fs.existsSync(this.options.imageDir)) {
237
+ console.warn(`⚠️ Image directory does not exist: ${this.options.imageDir}`);
238
+ return;
239
+ }
240
+
241
+ console.log(`🔍 Scanning images in: ${this.options.imageDir}`);
242
+ const images = await this.scanImages();
243
+
244
+ if (images.length === 0) {
245
+ console.log('⚠️ No images found');
246
+ return;
247
+ }
248
+
249
+ await this.writeScssFile(images);
250
+ await this.writeComponentFile();
251
+
252
+ console.log(`✅ Generated CSS custom properties for ${images.length} images`);
253
+ console.log(`📝 Output: ${this.options.scssPath}`);
254
+
255
+ // Log some examples
256
+ if (images.length > 0) {
257
+ console.log('📋 Generated classes:');
258
+ images.slice(0, 3).forEach(img => {
259
+ console.log(` .${img.className} { --p-width: ${img.width}; --p-height: ${img.height}; }`);
260
+ });
261
+ if (images.length > 3) {
262
+ console.log(` ... and ${images.length - 3} more`);
263
+ }
264
+ }
265
+ } catch (error) {
266
+ console.error('❌ PlaceBlockImagePlugin error:', error);
267
+ } finally {
268
+ this.isGenerating = false;
269
+ }
270
+ }
271
+ }
272
+
273
+ export default PlaceBlockImagePlugin;
package/tsconfig.json ADDED
@@ -0,0 +1,20 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2020",
4
+ "module": "commonjs",
5
+ "lib": ["ES2020"],
6
+ "outDir": "./dist",
7
+ "rootDir": "./src",
8
+ "strict": true,
9
+ "esModuleInterop": true,
10
+ "skipLibCheck": true,
11
+ "forceConsistentCasingInFileNames": true,
12
+ "declaration": true,
13
+ "declarationMap": true,
14
+ "sourceMap": true,
15
+ "moduleResolution": "node",
16
+ "resolveJsonModule": true
17
+ },
18
+ "include": ["src/**/*"],
19
+ "exclude": ["node_modules", "dist"]
20
+ }