chaincss 2.0.6 → 2.1.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 (159) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/CODE_OF_CONDUCT.md +21 -0
  3. package/CONTRIBUTING.md +28 -0
  4. package/README.md +454 -231
  5. package/demo/demo/node_modules/caniuse-db/fulldata-json/data-2.0.json +1 -0
  6. package/demo/index.html +16 -0
  7. package/demo/package.json +20 -0
  8. package/demo/src/App.tsx +117 -0
  9. package/demo/src/chaincss-barrel.ts +9 -0
  10. package/demo/src/main.tsx +8 -0
  11. package/demo/src/styles.chain.ts +300 -0
  12. package/demo/vite.config.ts +46 -0
  13. package/dist/cli/commands/build.d.ts +0 -1
  14. package/dist/cli/commands/cache.d.ts +1 -0
  15. package/dist/cli/commands/init.d.ts +6 -3
  16. package/dist/cli/commands/timeline.d.ts +0 -1
  17. package/dist/cli/commands/watch.d.ts +0 -1
  18. package/dist/cli/index.d.ts +0 -1
  19. package/dist/cli/index.js +3213 -5296
  20. package/dist/cli/types.d.ts +51 -20
  21. package/dist/cli/utils/config-loader.d.ts +0 -1
  22. package/dist/cli/utils/file-utils.d.ts +27 -3
  23. package/dist/cli/utils/logger.d.ts +0 -1
  24. package/dist/compiler/Chain.d.ts +215 -0
  25. package/dist/compiler/animations.d.ts +76 -0
  26. package/dist/compiler/atomic-optimizer.d.ts +47 -12
  27. package/dist/compiler/breakpoints.d.ts +46 -0
  28. package/dist/compiler/btt.d.ts +36 -60
  29. package/dist/compiler/cache-manager.d.ts +58 -4
  30. package/dist/compiler/commonProps.d.ts +0 -1
  31. package/dist/compiler/content-addressable-cache.d.ts +78 -0
  32. package/dist/compiler/helpers.d.ts +54 -0
  33. package/dist/compiler/index.d.ts +16 -9
  34. package/dist/compiler/index.js +4450 -4316
  35. package/dist/compiler/prefixer.d.ts +17 -1
  36. package/dist/compiler/shorthands.d.ts +28 -0
  37. package/dist/compiler/suggestions.d.ts +43 -0
  38. package/dist/compiler/theme-contract.d.ts +16 -27
  39. package/dist/compiler/token-resolver.d.ts +69 -0
  40. package/dist/compiler/tokens.d.ts +33 -8
  41. package/dist/core/auto-detector.d.ts +34 -0
  42. package/dist/core/common-utils.d.ts +97 -0
  43. package/dist/core/compiler.d.ts +63 -23
  44. package/dist/core/constants.d.ts +137 -36
  45. package/dist/core/smart-chain.d.ts +3 -0
  46. package/dist/core/types.d.ts +122 -15
  47. package/dist/core/utils.d.ts +134 -17
  48. package/dist/index.d.ts +52 -8
  49. package/dist/index.js +7090 -5578
  50. package/dist/plugins/vite.d.ts +7 -5
  51. package/dist/plugins/vite.js +2964 -25641
  52. package/dist/plugins/webpack.d.ts +24 -1
  53. package/dist/plugins/webpack.js +209 -72
  54. package/dist/runtime/Chain.d.ts +32 -0
  55. package/dist/runtime/auto-hooks.d.ts +11 -0
  56. package/dist/runtime/hmr.d.ts +22 -2
  57. package/dist/runtime/index.d.ts +3 -2
  58. package/dist/runtime/index.js +3649 -301
  59. package/dist/runtime/injector.d.ts +39 -71
  60. package/dist/runtime/react.d.ts +17 -12
  61. package/dist/runtime/svelte.d.ts +15 -0
  62. package/dist/runtime/types.d.ts +126 -4
  63. package/dist/runtime/utils.d.ts +0 -1
  64. package/dist/runtime/vue.d.ts +34 -14
  65. package/package.json +59 -66
  66. package/src/cli/commands/build.ts +133 -0
  67. package/src/cli/commands/cache.ts +371 -0
  68. package/src/cli/commands/init.ts +230 -0
  69. package/src/cli/commands/timeline.ts +435 -0
  70. package/src/cli/commands/watch.ts +211 -0
  71. package/src/cli/index.ts +226 -0
  72. package/src/cli/types.ts +100 -0
  73. package/src/cli/utils/config-loader.ts +174 -0
  74. package/src/cli/utils/file-utils.ts +139 -0
  75. package/src/cli/utils/logger.ts +74 -0
  76. package/src/compiler/Chain.ts +831 -0
  77. package/src/compiler/animations.ts +517 -0
  78. package/src/compiler/atomic-optimizer.ts +786 -0
  79. package/src/compiler/breakpoints.ts +347 -0
  80. package/src/compiler/btt.ts +1147 -0
  81. package/src/compiler/cache-manager.ts +446 -0
  82. package/src/compiler/commonProps.ts +18 -0
  83. package/src/compiler/content-addressable-cache.ts +478 -0
  84. package/src/compiler/helpers.ts +407 -0
  85. package/src/compiler/index.ts +72 -0
  86. package/src/compiler/prefixer.ts +724 -0
  87. package/src/compiler/shorthands.ts +558 -0
  88. package/src/compiler/suggestions.ts +436 -0
  89. package/src/compiler/theme-contract.ts +197 -0
  90. package/src/compiler/token-resolver.ts +241 -0
  91. package/src/compiler/tokens.ts +612 -0
  92. package/src/core/auto-detector.ts +187 -0
  93. package/src/core/common-utils.ts +423 -0
  94. package/src/core/compiler.ts +835 -0
  95. package/src/core/constants.ts +424 -0
  96. package/src/core/index.ts +107 -0
  97. package/src/core/smart-chain.ts +163 -0
  98. package/src/core/types.ts +257 -0
  99. package/src/core/utils.ts +598 -0
  100. package/src/index.ts +208 -0
  101. package/src/plugins/vite.d.ts +316 -0
  102. package/src/plugins/vite.ts +424 -0
  103. package/src/plugins/webpack.d.ts +289 -0
  104. package/src/plugins/webpack.ts +416 -0
  105. package/src/runtime/Chain.ts +242 -0
  106. package/src/runtime/auto-hooks.tsx +127 -0
  107. package/src/runtime/auto-vue.ts +72 -0
  108. package/src/runtime/hmr.ts +212 -0
  109. package/src/runtime/index.ts +82 -0
  110. package/src/runtime/injector.ts +273 -0
  111. package/src/runtime/react.tsx +269 -0
  112. package/src/runtime/svelte.ts +15 -0
  113. package/src/runtime/types.ts +256 -0
  114. package/src/runtime/utils.ts +128 -0
  115. package/src/runtime/vite-env.d.ts +120 -0
  116. package/src/runtime/vue.ts +231 -0
  117. package/tsconfig.build.json +41 -0
  118. package/tsconfig.json +25 -0
  119. package/tsconfig.runtimes.json +18 -0
  120. package/dist/cli/cli.cjs +0 -7
  121. package/dist/cli/commands/build.d.ts.map +0 -1
  122. package/dist/cli/commands/compile.d.ts +0 -3
  123. package/dist/cli/commands/compile.d.ts.map +0 -1
  124. package/dist/cli/commands/init.d.ts.map +0 -1
  125. package/dist/cli/commands/timeline.d.ts.map +0 -1
  126. package/dist/cli/commands/watch.d.ts.map +0 -1
  127. package/dist/cli/index.d.ts.map +0 -1
  128. package/dist/cli/types.d.ts.map +0 -1
  129. package/dist/cli/utils/config-loader.d.ts.map +0 -1
  130. package/dist/cli/utils/file-utils.d.ts.map +0 -1
  131. package/dist/cli/utils/logger.d.ts.map +0 -1
  132. package/dist/compiler/atomic-optimizer.d.ts.map +0 -1
  133. package/dist/compiler/btt.d.ts.map +0 -1
  134. package/dist/compiler/cache-manager.d.ts.map +0 -1
  135. package/dist/compiler/commonProps.d.ts.map +0 -1
  136. package/dist/compiler/index.d.ts.map +0 -1
  137. package/dist/compiler/prefixer.d.ts.map +0 -1
  138. package/dist/compiler/theme-contract.d.ts.map +0 -1
  139. package/dist/compiler/tokens.d.ts.map +0 -1
  140. package/dist/compiler/types.d.ts +0 -57
  141. package/dist/compiler/types.d.ts.map +0 -1
  142. package/dist/core/compiler.d.ts.map +0 -1
  143. package/dist/core/constants.d.ts.map +0 -1
  144. package/dist/core/index.d.ts +0 -4
  145. package/dist/core/index.d.ts.map +0 -1
  146. package/dist/core/types.d.ts.map +0 -1
  147. package/dist/core/utils.d.ts.map +0 -1
  148. package/dist/index.d.ts.map +0 -1
  149. package/dist/plugins/vite.d.ts.map +0 -1
  150. package/dist/plugins/webpack.d.ts.map +0 -1
  151. package/dist/runtime/hmr.d.ts.map +0 -1
  152. package/dist/runtime/index.d.ts.map +0 -1
  153. package/dist/runtime/injector.d.ts.map +0 -1
  154. package/dist/runtime/react.d.ts.map +0 -1
  155. package/dist/runtime/react.js +0 -270
  156. package/dist/runtime/types.d.ts.map +0 -1
  157. package/dist/runtime/utils.d.ts.map +0 -1
  158. package/dist/runtime/vue.d.ts.map +0 -1
  159. package/dist/runtime/vue.js +0 -232
@@ -0,0 +1,724 @@
1
+ // chaincss/src/compiler/prefixer.ts
2
+ // Dynamic imports for optional dependencies
3
+ import type { ProcessOptions, Result } from 'postcss';
4
+
5
+ // Safe import helper — returns null if module not available
6
+ // This prevents Vite from crashing on optional dependencies
7
+ async function safeImport(moduleName: string): Promise<any> {
8
+ try {
9
+ // Use Function constructor to hide from Vite's static analyzer
10
+ const importFn = new Function('path', 'return import(path)');
11
+ return await importFn(moduleName);
12
+ } catch {
13
+ return null;
14
+ }
15
+ }
16
+
17
+
18
+ // Types for optional dependencies
19
+ type PostCSS = any;
20
+ type Browserslist = any;
21
+ type CaniuseData = any;
22
+ type Autoprefixer = any;
23
+
24
+ // Declare variables for optional dependencies (initially null)
25
+ let postcss: PostCSS | null = null;
26
+ let browserslist: Browserslist | null = null;
27
+ let caniuse: any = null;
28
+ let autoprefixer: Autoprefixer | null = null;
29
+
30
+ // Lazy loading flags
31
+ let postcssLoaded = false;
32
+ let browserslistLoaded = false;
33
+ let caniuseLoaded = false;
34
+ let autoprefixerLoaded = false;
35
+ let loadingPromises: Map<string, Promise<any>> = new Map();
36
+
37
+ // Lazy load functions with better error handling
38
+ async function loadPostcss() {
39
+ if (postcss) return postcss;
40
+ if (loadingPromises.has('postcss')) return loadingPromises.get('postcss');
41
+
42
+ const promise = (async () => {
43
+ if (!postcssLoaded) {
44
+ try {
45
+ const module = await import('postcss');
46
+ postcss = module.default || module;
47
+ } catch (err) {
48
+ if (process.env.DEBUG) {
49
+ console.warn('postcss not installed, using lightweight prefixing');
50
+ }
51
+ }
52
+ postcssLoaded = true;
53
+ }
54
+ return postcss;
55
+ })();
56
+
57
+ loadingPromises.set('postcss', promise);
58
+ return promise;
59
+ }
60
+
61
+ async function loadBrowserslist() {
62
+ if (browserslist) return browserslist;
63
+ if (loadingPromises.has('browserslist')) return loadingPromises.get('browserslist');
64
+
65
+ const promise = (async () => {
66
+ if (!browserslistLoaded) {
67
+ try {
68
+ const module = await import(/* @vite-ignore */ 'browserslist');
69
+ browserslist = module.default || module;
70
+ } catch (err) {
71
+ if (process.env.DEBUG) {
72
+ console.warn('browserslist not installed');
73
+ }
74
+ }
75
+ browserslistLoaded = true;
76
+ }
77
+ return browserslist;
78
+ })();
79
+
80
+ loadingPromises.set('browserslist', promise);
81
+ return promise;
82
+ }
83
+
84
+ async function loadCaniuse() {
85
+ if (caniuse) return caniuse;
86
+ if (loadingPromises.has('caniuse')) return loadingPromises.get('caniuse');
87
+
88
+ const promise = (async () => {
89
+ if (!caniuseLoaded) {
90
+ try {
91
+ // @ts-ignore
92
+ const caniuseModule = await safeImport("caniuse-db/fulldata-json/data-2.0.json");
93
+ caniuse = caniuseModule.default || caniuseModule;
94
+ } catch (err) {
95
+ if (process.env.DEBUG) {
96
+ console.warn('caniuse-db not installed, lightweight prefixing will be limited');
97
+ }
98
+ }
99
+ caniuseLoaded = true;
100
+ }
101
+ return caniuse;
102
+ })();
103
+
104
+ loadingPromises.set('caniuse', promise);
105
+ return promise;
106
+ }
107
+
108
+ async function loadAutoprefixer() {
109
+ if (autoprefixer) return autoprefixer;
110
+ if (loadingPromises.has('autoprefixer')) return loadingPromises.get('autoprefixer');
111
+
112
+ const promise = (async () => {
113
+ if (!autoprefixerLoaded) {
114
+ try {
115
+ // @ts-ignore - autoprefixer is optional
116
+ const module = await import('autoprefixer');
117
+ autoprefixer = module.default || module;
118
+ } catch (err) {
119
+ if (process.env.DEBUG) {
120
+ console.warn('autoprefixer not installed');
121
+ }
122
+ }
123
+ autoprefixerLoaded = true;
124
+ }
125
+ return autoprefixer;
126
+ })();
127
+
128
+ loadingPromises.set('autoprefixer', promise);
129
+ return promise;
130
+ }
131
+
132
+ // Types
133
+ export interface PrefixerConfig {
134
+ browsers?: string[];
135
+ enabled?: boolean;
136
+ mode?: 'auto' | 'full' | 'lightweight';
137
+ sourceMap?: boolean;
138
+ sourceMapInline?: boolean;
139
+ remove?: boolean; // Remove outdated prefixes
140
+ add?: boolean; // Add missing prefixes
141
+ verbose?: boolean;
142
+ flexbox?: boolean | 'no-2009'; // Flexbox support
143
+ grid?: boolean | 'autoplace' | 'no-autoplace'; // Grid support
144
+ }
145
+
146
+ export interface PrefixerResult {
147
+ css: string;
148
+ map: string | null;
149
+ warnings?: string[];
150
+ }
151
+
152
+ export interface ProcessOptionsWithPaths {
153
+ from?: string;
154
+ to?: string;
155
+ map?: boolean | object;
156
+ }
157
+
158
+ export interface CaniuseFeature {
159
+ title: string;
160
+ description: string;
161
+ stats: Record<string, Record<string, string>>;
162
+ spec?: string;
163
+ status?: string;
164
+ }
165
+
166
+ // Built-in prefix map for lightweight mode
167
+ const LIGHTWEIGHT_PREFIX_MAP: Record<string, Record<string, string[]>> = {
168
+ // Transform properties
169
+ 'transform': {
170
+ 'webkit': ['-webkit-transform'],
171
+ 'ms': ['-ms-transform']
172
+ },
173
+ 'transform-origin': {
174
+ 'webkit': ['-webkit-transform-origin'],
175
+ 'ms': ['-ms-transform-origin']
176
+ },
177
+ 'transform-style': {
178
+ 'webkit': ['-webkit-transform-style']
179
+ },
180
+ 'perspective': {
181
+ 'webkit': ['-webkit-perspective']
182
+ },
183
+ 'backface-visibility': {
184
+ 'webkit': ['-webkit-backface-visibility']
185
+ },
186
+
187
+ // Transitions & Animations
188
+ 'transition': {
189
+ 'webkit': ['-webkit-transition']
190
+ },
191
+ 'transition-property': {
192
+ 'webkit': ['-webkit-transition-property']
193
+ },
194
+ 'transition-duration': {
195
+ 'webkit': ['-webkit-transition-duration']
196
+ },
197
+ 'transition-timing-function': {
198
+ 'webkit': ['-webkit-transition-timing-function']
199
+ },
200
+ 'animation': {
201
+ 'webkit': ['-webkit-animation']
202
+ },
203
+ 'animation-name': {
204
+ 'webkit': ['-webkit-animation-name']
205
+ },
206
+ 'animation-duration': {
207
+ 'webkit': ['-webkit-animation-duration']
208
+ },
209
+ 'animation-timing-function': {
210
+ 'webkit': ['-webkit-animation-timing-function']
211
+ },
212
+ 'animation-delay': {
213
+ 'webkit': ['-webkit-animation-delay']
214
+ },
215
+ 'animation-iteration-count': {
216
+ 'webkit': ['-webkit-animation-iteration-count']
217
+ },
218
+ 'animation-direction': {
219
+ 'webkit': ['-webkit-animation-direction']
220
+ },
221
+ 'animation-fill-mode': {
222
+ 'webkit': ['-webkit-animation-fill-mode']
223
+ },
224
+
225
+ // Filters
226
+ 'filter': {
227
+ 'webkit': ['-webkit-filter']
228
+ },
229
+ 'backdrop-filter': {
230
+ 'webkit': ['-webkit-backdrop-filter']
231
+ },
232
+
233
+ // Box properties
234
+ 'box-shadow': {
235
+ 'webkit': ['-webkit-box-shadow']
236
+ },
237
+ 'box-sizing': {
238
+ 'webkit': ['-webkit-box-sizing'],
239
+ 'moz': ['-moz-box-sizing']
240
+ },
241
+ 'border-radius': {
242
+ 'webkit': ['-webkit-border-radius'],
243
+ 'moz': ['-moz-border-radius']
244
+ },
245
+
246
+ // User interface
247
+ 'user-select': {
248
+ 'webkit': ['-webkit-user-select'],
249
+ 'moz': ['-moz-user-select'],
250
+ 'ms': ['-ms-user-select']
251
+ },
252
+ 'appearance': {
253
+ 'webkit': ['-webkit-appearance'],
254
+ 'moz': ['-moz-appearance']
255
+ },
256
+
257
+ // Text
258
+ 'text-fill-color': {
259
+ 'webkit': ['-webkit-text-fill-color']
260
+ },
261
+ 'text-stroke': {
262
+ 'webkit': ['-webkit-text-stroke']
263
+ },
264
+ 'text-stroke-color': {
265
+ 'webkit': ['-webkit-text-stroke-color']
266
+ },
267
+ 'text-stroke-width': {
268
+ 'webkit': ['-webkit-text-stroke-width']
269
+ },
270
+ 'background-clip': {
271
+ 'webkit': ['-webkit-background-clip']
272
+ },
273
+
274
+ // Masks
275
+ 'mask-image': {
276
+ 'webkit': ['-webkit-mask-image']
277
+ },
278
+ 'mask-clip': {
279
+ 'webkit': ['-webkit-mask-clip']
280
+ },
281
+ 'mask-composite': {
282
+ 'webkit': ['-webkit-mask-composite']
283
+ },
284
+ 'mask-origin': {
285
+ 'webkit': ['-webkit-mask-origin']
286
+ },
287
+ 'mask-position': {
288
+ 'webkit': ['-webkit-mask-position']
289
+ },
290
+ 'mask-repeat': {
291
+ 'webkit': ['-webkit-mask-repeat']
292
+ },
293
+ 'mask-size': {
294
+ 'webkit': ['-webkit-mask-size']
295
+ }
296
+ };
297
+
298
+ // Special value prefixes for lightweight mode
299
+ const LIGHTWEIGHT_VALUE_PREFIXES: Record<string, Record<string, string[]>> = {
300
+ 'display': {
301
+ 'flex': ['-webkit-flex', '-ms-flexbox'],
302
+ 'inline-flex': ['-webkit-inline-flex', '-ms-inline-flexbox'],
303
+ 'grid': ['-ms-grid'],
304
+ 'inline-grid': ['-ms-inline-grid']
305
+ },
306
+ 'position': {
307
+ 'sticky': ['-webkit-sticky']
308
+ }
309
+ };
310
+
311
+ // Main class
312
+ export class ChainCSSPrefixer {
313
+ config: Required<PrefixerConfig>;
314
+ hasBuiltInDeps: boolean;
315
+ hasAutoprefixer: boolean;
316
+ prefixerMode: 'auto' | 'full' | 'lightweight';
317
+ caniuseData: Record<string, CaniuseFeature> | null;
318
+ commonProperties: string[];
319
+ specialValues: Record<string, string[]>;
320
+ browserPrefixMap: Record<string, string>;
321
+ targetBrowsers: string[] | null;
322
+ private warnings: string[] = [];
323
+
324
+ constructor(config: PrefixerConfig = {}) {
325
+ this.config = {
326
+ browsers: config.browsers || ['> 0.5%', 'last 2 versions', 'not dead'],
327
+ enabled: config.enabled !== false,
328
+ mode: config.mode || 'auto',
329
+ sourceMap: config.sourceMap !== false,
330
+ sourceMapInline: config.sourceMapInline || false,
331
+ remove: config.remove !== false,
332
+ add: config.add !== false,
333
+ verbose: config.verbose || false,
334
+ flexbox: config.flexbox !== false,
335
+ grid: config.grid || 'autoplace'
336
+ };
337
+
338
+ this.hasBuiltInDeps = false;
339
+ this.hasAutoprefixer = false;
340
+ this.prefixerMode = config.mode || 'auto';
341
+ this.caniuseData = null;
342
+ this.commonProperties = this.getCommonProperties();
343
+ this.specialValues = {
344
+ 'display': ['flex', 'inline-flex', 'grid', 'inline-grid'],
345
+ 'background-clip': ['text'],
346
+ 'position': ['sticky']
347
+ };
348
+ this.browserPrefixMap = {
349
+ 'chrome': 'webkit',
350
+ 'safari': 'webkit',
351
+ 'firefox': 'moz',
352
+ 'ie': 'ms',
353
+ 'edge': 'webkit',
354
+ 'ios_saf': 'webkit',
355
+ 'and_chr': 'webkit',
356
+ 'android': 'webkit',
357
+ 'opera': 'webkit',
358
+ 'op_mob': 'webkit',
359
+ 'samsung': 'webkit',
360
+ 'and_ff': 'moz'
361
+ };
362
+ this.targetBrowsers = null;
363
+ }
364
+
365
+ async determineMode(): Promise<'auto' | 'full' | 'lightweight'> {
366
+ if (this.config.mode === 'full') {
367
+ const hasAutoprefixer = !!(await loadAutoprefixer());
368
+ if (!hasAutoprefixer && this.config.verbose) {
369
+ console.warn('⚠️ Full mode requested but autoprefixer not installed. Falling back to lightweight mode.');
370
+ console.warn(' To use full mode: npm install autoprefixer postcss caniuse-db browserslist\n');
371
+ }
372
+ return hasAutoprefixer ? 'full' : 'lightweight';
373
+ }
374
+ if (this.config.mode === 'lightweight') {
375
+ return 'lightweight';
376
+ }
377
+ if (this.config.mode === 'auto') {
378
+ const hasAutoprefixer = !!(await loadAutoprefixer());
379
+ if (this.config.verbose) {
380
+ console.log(`🔧 Prefixer mode: ${hasAutoprefixer ? 'full' : 'lightweight'}`);
381
+ }
382
+ return hasAutoprefixer ? 'full' : 'lightweight';
383
+ }
384
+ return 'lightweight';
385
+ }
386
+
387
+ async process(cssString: string, options: ProcessOptionsWithPaths = {}): Promise<PrefixerResult> {
388
+ this.warnings = [];
389
+
390
+ if (!this.config.enabled) {
391
+ return { css: cssString, map: null, warnings: [] };
392
+ }
393
+
394
+ try {
395
+ const mode = await this.determineMode();
396
+
397
+ if (mode === 'full') {
398
+ return await this.processWithAutoprefixer(cssString, options);
399
+ }
400
+
401
+ return await this.processWithBuiltIn(cssString, options);
402
+ } catch (err) {
403
+ const errorMsg = (err as Error).message;
404
+ this.warnings.push(`Prefixer error: ${errorMsg}`);
405
+ if (this.config.verbose) {
406
+ console.error('Prefixer error:', errorMsg);
407
+ }
408
+ return { css: cssString, map: null, warnings: this.warnings };
409
+ }
410
+ }
411
+
412
+ private async processWithAutoprefixer(
413
+ cssString: string,
414
+ options: ProcessOptionsWithPaths
415
+ ): Promise<PrefixerResult> {
416
+ const autoprefixerModule = await loadAutoprefixer();
417
+ const postcssModule = await loadPostcss();
418
+
419
+ if (!autoprefixerModule || !postcssModule) {
420
+ this.warnings.push('Autoprefixer or PostCSS not available, falling back to lightweight mode');
421
+ return await this.processWithBuiltIn(cssString, options);
422
+ }
423
+
424
+ const from = options.from || 'input.css';
425
+ const to = options.to || 'output.css';
426
+
427
+ try {
428
+ const result = await postcssModule([
429
+ autoprefixerModule({
430
+ overrideBrowserslist: this.config.browsers,
431
+ remove: this.config.remove,
432
+ add: this.config.add,
433
+ flexbox: this.config.flexbox,
434
+ grid: this.config.grid
435
+ })
436
+ ]).process(cssString, {
437
+ from,
438
+ to,
439
+ map: this.config.sourceMap ? {
440
+ inline: this.config.sourceMapInline,
441
+ annotation: false,
442
+ sourcesContent: true
443
+ } : false
444
+ });
445
+
446
+ if (result.warnings && this.config.verbose) {
447
+ result.warnings().forEach((warning: any) => {
448
+ this.warnings.push(warning.toString());
449
+ });
450
+ }
451
+
452
+ return {
453
+ css: result.css,
454
+ map: result.map ? result.map.toString() : null,
455
+ warnings: this.warnings
456
+ };
457
+ } catch (err) {
458
+ this.warnings.push(`Autoprefixer processing error: ${(err as Error).message}`);
459
+ return { css: cssString, map: null, warnings: this.warnings };
460
+ }
461
+ }
462
+
463
+ private async processWithBuiltIn(
464
+ cssString: string,
465
+ options: ProcessOptionsWithPaths
466
+ ): Promise<PrefixerResult> {
467
+ // Use lightweight prefixing
468
+ const prefixed = this.lightweightPrefix(cssString);
469
+
470
+ return {
471
+ css: prefixed,
472
+ map: null,
473
+ warnings: this.warnings
474
+ };
475
+ }
476
+
477
+ private lightweightPrefix(cssString: string): string {
478
+ let result = cssString;
479
+
480
+ // Process declarations
481
+ const declRegex = /([\w-]+)\s*:\s*([^;]+);/g;
482
+ let match;
483
+
484
+ while ((match = declRegex.exec(cssString)) !== null) {
485
+ const [fullMatch, prop, value] = match;
486
+ const trimmedProp = prop.trim();
487
+ const trimmedValue = value.trim();
488
+
489
+ // Check if property needs prefixing
490
+ const prefixes = LIGHTWEIGHT_PREFIX_MAP[trimmedProp];
491
+ if (prefixes && this.config.add) {
492
+ for (const [prefix, prefixedProps] of Object.entries(prefixes)) {
493
+ for (const prefixedProp of prefixedProps) {
494
+ const prefixedDecl = `${prefixedProp}: ${trimmedValue};`;
495
+ result = result.replace(fullMatch, `${prefixedDecl}\n${fullMatch}`);
496
+ }
497
+ }
498
+ }
499
+
500
+ // Check if value needs special prefixing
501
+ const valuePrefixes = LIGHTWEIGHT_VALUE_PREFIXES[trimmedProp];
502
+ if (valuePrefixes && valuePrefixes[trimmedValue] && this.config.add) {
503
+ for (const prefixedValue of valuePrefixes[trimmedValue]) {
504
+ const prefixedDecl = `${trimmedProp}: ${prefixedValue};`;
505
+ result = result.replace(fullMatch, `${prefixedDecl}\n${fullMatch}`);
506
+ }
507
+ }
508
+ }
509
+
510
+ // Handle keyframes with prefixes
511
+ const keyframesRegex = /@keyframes\s+(\w+)\s*\{([^}]+)\}/g;
512
+ while ((match = keyframesRegex.exec(cssString)) !== null) {
513
+ const [fullMatch, name, frames] = match;
514
+ const webkitKeyframes = `@-webkit-keyframes ${name} {${frames}}`;
515
+ result = result.replace(fullMatch, `${webkitKeyframes}\n${fullMatch}`);
516
+ }
517
+
518
+ return result;
519
+ }
520
+
521
+ private createBuiltInPlugin(): (root: any) => void {
522
+ return (root: any) => {
523
+ root.walkDecls((decl: any) => {
524
+ this.processBuiltInDeclaration(decl);
525
+ });
526
+ };
527
+ }
528
+
529
+ private processBuiltInDeclaration(decl: any): void {
530
+ const { prop, value } = decl;
531
+
532
+ if (this.commonProperties.includes(prop) && this.config.add) {
533
+ this.addPrefixesFromCaniuse(decl);
534
+ }
535
+
536
+ if (this.specialValues[prop]?.includes(value) && this.config.add) {
537
+ this.addSpecialValuePrefixes(decl);
538
+ }
539
+
540
+ if (!this.config.remove) return;
541
+
542
+ // Remove outdated prefixed versions
543
+ const unprefixedProp = prop.replace(/^-(webkit|moz|ms|o)-/, '');
544
+ if (unprefixedProp !== prop && this.commonProperties.includes(unprefixedProp)) {
545
+ // Check if we should keep this prefix
546
+ const shouldKeep = this.shouldKeepPrefix(prop, unprefixedProp);
547
+ if (!shouldKeep) {
548
+ decl.remove();
549
+ }
550
+ }
551
+ }
552
+
553
+ private shouldKeepPrefix(prop: string, unprefixed: string): boolean {
554
+ // Check if browser still needs this prefix
555
+ if (!this.targetBrowsers) return true;
556
+
557
+ const prefix = prop.match(/^-(webkit|moz|ms|o)-/)?.[1];
558
+ if (!prefix) return true;
559
+
560
+ // For modern browsers, many prefixes are no longer needed
561
+ const modernBrowsers = ['chrome >= 80', 'firefox >= 80', 'safari >= 13', 'edge >= 80'];
562
+ const needsPrefix = this.targetBrowsers.some(browser => {
563
+ return modernBrowsers.includes(browser);
564
+ });
565
+
566
+ return !needsPrefix;
567
+ }
568
+
569
+ private addPrefixesFromCaniuse(decl: any): void {
570
+ if (!this.caniuseData) return;
571
+
572
+ const feature = this.findFeature(decl.prop);
573
+ if (!feature) return;
574
+
575
+ const prefixes = new Set<string>();
576
+
577
+ this.targetBrowsers?.forEach(browser => {
578
+ const [id, versionStr] = browser.split(' ');
579
+ const version = parseFloat(versionStr.split('-')[0]);
580
+ const stats = feature.stats[id];
581
+
582
+ if (stats) {
583
+ const versions = Object.keys(stats)
584
+ .map(v => parseFloat(v.split('-')[0]))
585
+ .filter(v => !isNaN(v))
586
+ .sort((a, b) => a - b);
587
+
588
+ const closestVersion = versions.find(v => v <= version) || versions[0];
589
+
590
+ if (closestVersion) {
591
+ const support = stats[closestVersion.toString()];
592
+ if (support && support.includes('x')) {
593
+ const prefix = this.browserPrefixMap[id.split('-')[0]];
594
+ if (prefix) prefixes.add(prefix);
595
+ }
596
+ }
597
+ }
598
+ });
599
+
600
+ prefixes.forEach(prefix => {
601
+ decl.cloneBefore({
602
+ prop: `-${prefix}-${decl.prop}`,
603
+ value: decl.value
604
+ });
605
+ });
606
+ }
607
+
608
+ private addSpecialValuePrefixes(decl: any): void {
609
+ const { prop, value } = decl;
610
+
611
+ if (prop === 'display') {
612
+ if (value === 'flex' || value === 'inline-flex') {
613
+ decl.cloneBefore({ prop: 'display', value: `-webkit-${value}` });
614
+ decl.cloneBefore({
615
+ prop: 'display',
616
+ value: value === 'flex' ? '-ms-flexbox' : '-ms-inline-flexbox'
617
+ });
618
+ }
619
+ if (value === 'grid' || value === 'inline-grid') {
620
+ decl.cloneBefore({
621
+ prop: 'display',
622
+ value: value === 'grid' ? '-ms-grid' : '-ms-inline-grid'
623
+ });
624
+ }
625
+ }
626
+
627
+ if (prop === 'background-clip' && value === 'text') {
628
+ decl.cloneBefore({ prop: '-webkit-background-clip', value: 'text' });
629
+ }
630
+
631
+ if (prop === 'position' && value === 'sticky') {
632
+ decl.cloneBefore({ prop: 'position', value: '-webkit-sticky' });
633
+ }
634
+ }
635
+
636
+ private findFeature(property: string): CaniuseFeature | null {
637
+ if (!this.caniuseData) return null;
638
+
639
+ const featureMap: Record<string, string> = {
640
+ 'transform': 'transforms2d',
641
+ 'transform-origin': 'transforms2d',
642
+ 'transform-style': 'transforms3d',
643
+ 'perspective': 'transforms3d',
644
+ 'backface-visibility': 'transforms3d',
645
+ 'transition': 'css-transitions',
646
+ 'animation': 'css-animation',
647
+ 'backdrop-filter': 'backdrop-filter',
648
+ 'filter': 'css-filters',
649
+ 'user-select': 'user-select-none',
650
+ 'appearance': 'css-appearance',
651
+ 'mask-image': 'css-masks',
652
+ 'box-shadow': 'css-boxshadow',
653
+ 'border-radius': 'border-radius',
654
+ 'text-fill-color': 'text-stroke',
655
+ 'text-stroke': 'text-stroke',
656
+ 'background-clip': 'background-img-opts',
657
+ 'flex': 'flexbox',
658
+ 'flex-grow': 'flexbox',
659
+ 'flex-shrink': 'flexbox',
660
+ 'flex-basis': 'flexbox',
661
+ 'justify-content': 'flexbox',
662
+ 'align-items': 'flexbox',
663
+ 'grid': 'css-grid',
664
+ 'grid-template': 'css-grid',
665
+ 'grid-column': 'css-grid',
666
+ 'grid-row': 'css-grid'
667
+ };
668
+
669
+ const featureId = featureMap[property];
670
+ return featureId ? (this.caniuseData[featureId] as CaniuseFeature) : null;
671
+ }
672
+
673
+ private getCommonProperties(): string[] {
674
+ return [
675
+ 'transform', 'transform-origin', 'transform-style',
676
+ 'transition', 'transition-property', 'transition-duration', 'transition-timing-function',
677
+ 'animation', 'animation-name', 'animation-duration', 'animation-timing-function',
678
+ 'animation-delay', 'animation-iteration-count', 'animation-direction',
679
+ 'animation-fill-mode', 'animation-play-state',
680
+ 'backdrop-filter', 'filter',
681
+ 'user-select', 'appearance',
682
+ 'text-fill-color', 'text-stroke', 'text-stroke-color', 'text-stroke-width',
683
+ 'background-clip',
684
+ 'mask-image', 'mask-clip', 'mask-composite', 'mask-origin',
685
+ 'mask-position', 'mask-repeat', 'mask-size',
686
+ 'box-shadow', 'border-radius', 'box-sizing',
687
+ 'display', 'flex', 'flex-grow', 'flex-shrink', 'flex-basis',
688
+ 'justify-content', 'align-items', 'align-self', 'align-content',
689
+ 'grid', 'grid-template', 'grid-column', 'grid-row', 'gap',
690
+ 'column-gap', 'row-gap'
691
+ ];
692
+ }
693
+
694
+ // Utility method to check if a browser needs a specific prefix
695
+ needsPrefix(property: string, browser: string, version: number): boolean {
696
+ const feature = this.findFeature(property);
697
+ if (!feature) return false;
698
+
699
+ const stats = feature.stats[browser];
700
+ if (!stats) return false;
701
+
702
+ const support = stats[version.toString()];
703
+ return support ? support.includes('x') : false;
704
+ }
705
+
706
+ // Get all available prefixes for a property
707
+ getAvailablePrefixes(property: string): string[] {
708
+ const prefixes = LIGHTWEIGHT_PREFIX_MAP[property];
709
+ if (!prefixes) return [];
710
+
711
+ return Object.keys(prefixes);
712
+ }
713
+
714
+ // Reset the prefixer state
715
+ reset(): void {
716
+ this.warnings = [];
717
+ this.targetBrowsers = null;
718
+ this.hasAutoprefixer = false;
719
+ this.hasBuiltInDeps = false;
720
+ }
721
+ }
722
+
723
+ // ESM Export
724
+ export { ChainCSSPrefixer as default };