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