styimat 1.7.0 → 1.9.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.
- package/README.md +61 -5
- package/convert-styimat.js +2 -2
- package/package.json +1 -1
- package/styimat.js +368 -56
- package/styimat.min.js +145 -23
package/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
[](https://gitee.com/wxy6987/styimat/members)
|
|
4
4
|
[](https://www.npmjs.com/package/styimat)
|
|
5
5
|
[](LICENSE)
|
|
6
|
-
](https://www.npmjs.com/package/styimat)
|
|
7
7
|
|
|
8
8
|
一个功能强大的 CSS 变量预处理库,支持嵌套规则、Lab/LCH 颜色空间转换、Display P3 广色域和增强的数学计算功能。
|
|
9
9
|
|
|
@@ -22,7 +22,7 @@ npm install
|
|
|
22
22
|
```
|
|
23
23
|
|
|
24
24
|
### 2. 直接下载 ZIP
|
|
25
|
-
|
|
25
|
+
[](https://gitee.com/wxy6987/styimat/repository/archive/main.zip)
|
|
26
26
|
|
|
27
27
|
### 3. NPM 安装
|
|
28
28
|
```bash
|
|
@@ -44,7 +44,7 @@ npm install styimat
|
|
|
44
44
|
- **广色域显示** - 自动检测并支持 Display P3 广色域显示器
|
|
45
45
|
- **变量系统** - 类似 Sass 的变量系统,支持作用域变量
|
|
46
46
|
-️ **嵌套规则** - 支持 Sass 风格的嵌套选择器
|
|
47
|
-
- **十六进制语法** - 支持 `lab#
|
|
47
|
+
- **十六进制语法** - 支持 `lab#LLAABB` 和 `lch#LLCCHHH` 简洁语法
|
|
48
48
|
- **增强数学计算** - 支持 `math()` 函数和复杂数学表达式
|
|
49
49
|
- **轻量高效** - 无依赖,压缩后仅约 20KB
|
|
50
50
|
- **多种使用方式** - 浏览器、Node.js、命令行均可使用
|
|
@@ -272,7 +272,63 @@ $font-family: 'Inter', sans-serif;
|
|
|
272
272
|
margin: $local-var;
|
|
273
273
|
|
|
274
274
|
&::before {
|
|
275
|
-
content: $$ctx;
|
|
275
|
+
content: $$ctx; // 获取元素属性ctx
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
### 导入语法
|
|
281
|
+
|
|
282
|
+
#### 您可以在CSS文件的开头使用导入文件来导入其他css文件,应该以`@import开头`:
|
|
283
|
+
|
|
284
|
+
```css
|
|
285
|
+
@import "head.css";
|
|
286
|
+
@import url("tail.css");
|
|
287
|
+
|
|
288
|
+
/* 然后写您的CSS代码 */
|
|
289
|
+
$primary-color: lab#32a852;
|
|
290
|
+
$spacing: math(16px * 2 + 4px);
|
|
291
|
+
|
|
292
|
+
body {
|
|
293
|
+
color: $primary-color;
|
|
294
|
+
padding: $spacing;
|
|
295
|
+
|
|
296
|
+
.container {
|
|
297
|
+
margin: math($spacing / 2);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
#### 还可以使用style语法:
|
|
303
|
+
```html
|
|
304
|
+
<!--这样就自动处理好了-->
|
|
305
|
+
<style src="head.css" e></style>
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
### 配置头语法
|
|
309
|
+
|
|
310
|
+
您可以在CSS文件的开头使用配置头来设置预处理选项,配置行以 `#` 开头:
|
|
311
|
+
|
|
312
|
+
```css
|
|
313
|
+
#indentSize 4
|
|
314
|
+
#autoProcessStyleTags true
|
|
315
|
+
#preserveOriginal true
|
|
316
|
+
#enableP3 false
|
|
317
|
+
#rootSelector :root
|
|
318
|
+
#enableNesting true
|
|
319
|
+
#enableMath true
|
|
320
|
+
#mathPrecision 8
|
|
321
|
+
|
|
322
|
+
/* 然后写您的CSS代码 */
|
|
323
|
+
$primary-color: lab#32a852;
|
|
324
|
+
$spacing: math(16px * 2 + 4px);
|
|
325
|
+
|
|
326
|
+
body {
|
|
327
|
+
color: $primary-color;
|
|
328
|
+
padding: $spacing;
|
|
329
|
+
|
|
330
|
+
.container {
|
|
331
|
+
margin: math($spacing / 2);
|
|
276
332
|
}
|
|
277
333
|
}
|
|
278
334
|
```
|
|
@@ -556,7 +612,7 @@ $primary-transparent: lab(55 190 0 / 0.8);
|
|
|
556
612
|
欢迎贡献代码!请遵循以下步骤:
|
|
557
613
|
|
|
558
614
|
1. **Fork 本仓库**
|
|
559
|
-
[](https://gitee.com/wxy6987/styimat/members)
|
|
560
616
|
2. **创建特性分支**
|
|
561
617
|
```bash
|
|
562
618
|
git checkout -b feature/AmazingFeature
|
package/convert-styimat.js
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
const fs = require('fs');
|
|
10
10
|
const path = require('path');
|
|
11
11
|
|
|
12
|
-
function main() {
|
|
12
|
+
async function main() {
|
|
13
13
|
const args = process.argv.slice(2);
|
|
14
14
|
|
|
15
15
|
let inputContent = '';
|
|
@@ -70,7 +70,7 @@ function main() {
|
|
|
70
70
|
}
|
|
71
71
|
|
|
72
72
|
// 执行转换
|
|
73
|
-
const outputContent = huecss.convert(inputContent);
|
|
73
|
+
const outputContent = await huecss.convert(inputContent);
|
|
74
74
|
|
|
75
75
|
// 输出结果
|
|
76
76
|
if (outputFile) {
|
package/package.json
CHANGED
package/styimat.js
CHANGED
|
@@ -9,6 +9,8 @@
|
|
|
9
9
|
* 支持 lab# 和 lch# 十六进制语法
|
|
10
10
|
* 支持 Display P3 广色域显示器
|
|
11
11
|
* 增强 math() 函数,支持复杂数学计算
|
|
12
|
+
* 支持配置头
|
|
13
|
+
* 支持 @import 语法
|
|
12
14
|
*/
|
|
13
15
|
(function(root, factory) {
|
|
14
16
|
if (typeof define === 'function' && define.amd) {
|
|
@@ -27,7 +29,7 @@
|
|
|
27
29
|
rootSelector: ':root',
|
|
28
30
|
variablePrefix: '--',
|
|
29
31
|
preserveOriginal: false,
|
|
30
|
-
indentSize:
|
|
32
|
+
indentSize: 4,
|
|
31
33
|
enableNesting: true,
|
|
32
34
|
autoProcessStyleTags: true,
|
|
33
35
|
styleTagAttribute: 'e',
|
|
@@ -36,10 +38,80 @@
|
|
|
36
38
|
enableP3: true,
|
|
37
39
|
enableMath: true, // 启用math()函数增强
|
|
38
40
|
mathPrecision: 6, // 数学计算精度
|
|
41
|
+
importBaseUrl: '', // @import 的基础URL
|
|
42
|
+
importCache: true, // 缓存导入的文件
|
|
43
|
+
importTimeout: 5000, // 导入超时时间(毫秒)
|
|
39
44
|
};
|
|
40
45
|
|
|
41
46
|
// 全局P3支持检测结果
|
|
42
47
|
let p3Supported = null;
|
|
48
|
+
|
|
49
|
+
// 导入文件缓存
|
|
50
|
+
const importCache = new Map();
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* 解析配置头
|
|
54
|
+
* @param {string} cssText - CSS文本
|
|
55
|
+
* @returns {Object} {config: 配置对象, css: 清理后的CSS}
|
|
56
|
+
*/
|
|
57
|
+
function parseConfigHeader(cssText) {
|
|
58
|
+
const config = { ...defaultConfig };
|
|
59
|
+
const lines = cssText.split('\n');
|
|
60
|
+
const cleanLines = [];
|
|
61
|
+
|
|
62
|
+
for (let line of lines) {
|
|
63
|
+
const trimmed = line.trim();
|
|
64
|
+
|
|
65
|
+
// 检查是否是配置行(以 # 开头)
|
|
66
|
+
if (trimmed.startsWith('#')) {
|
|
67
|
+
const configLine = trimmed.substring(1).trim();
|
|
68
|
+
const firstSpace = configLine.indexOf(' ');
|
|
69
|
+
|
|
70
|
+
if (firstSpace !== -1) {
|
|
71
|
+
const key = configLine.substring(0, firstSpace).trim();
|
|
72
|
+
const value = configLine.substring(firstSpace + 1).trim();
|
|
73
|
+
|
|
74
|
+
// 转换配置值
|
|
75
|
+
const configKey = camelCase(key);
|
|
76
|
+
|
|
77
|
+
if (configKey in config) {
|
|
78
|
+
// 根据值的类型进行转换
|
|
79
|
+
if (value === 'true' || value === 'false') {
|
|
80
|
+
config[configKey] = value === 'true';
|
|
81
|
+
} else if (!isNaN(value) && value.trim() !== '') {
|
|
82
|
+
// 数字类型
|
|
83
|
+
config[configKey] = Number(value);
|
|
84
|
+
} else {
|
|
85
|
+
// 字符串类型
|
|
86
|
+
config[configKey] = value;
|
|
87
|
+
}
|
|
88
|
+
} else {
|
|
89
|
+
console.warn(`未知的配置项: ${key}`);
|
|
90
|
+
}
|
|
91
|
+
} else {
|
|
92
|
+
console.warn(`无效的配置行: ${trimmed}`);
|
|
93
|
+
}
|
|
94
|
+
} else {
|
|
95
|
+
cleanLines.push(line);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return {
|
|
100
|
+
config,
|
|
101
|
+
css: cleanLines.join('\n')
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* 将连字符分隔的字符串转换为驼峰命名
|
|
107
|
+
* @param {string} str - 输入字符串
|
|
108
|
+
* @returns {string} 驼峰命名字符串
|
|
109
|
+
*/
|
|
110
|
+
function camelCase(str) {
|
|
111
|
+
return str.replace(/-([a-z])/g, function(match, letter) {
|
|
112
|
+
return letter.toUpperCase();
|
|
113
|
+
});
|
|
114
|
+
}
|
|
43
115
|
|
|
44
116
|
/**
|
|
45
117
|
* 检测浏览器是否支持 Display P3
|
|
@@ -958,10 +1030,10 @@
|
|
|
958
1030
|
|
|
959
1031
|
// 更新缩进级别
|
|
960
1032
|
if (line.includes('{')) {
|
|
961
|
-
currentIndent +=
|
|
1033
|
+
currentIndent += config.indentSize;
|
|
962
1034
|
}
|
|
963
1035
|
if (line.includes('}')) {
|
|
964
|
-
currentIndent = Math.max(0, currentIndent -
|
|
1036
|
+
currentIndent = Math.max(0, currentIndent - config.indentSize);
|
|
965
1037
|
}
|
|
966
1038
|
}
|
|
967
1039
|
|
|
@@ -987,7 +1059,6 @@
|
|
|
987
1059
|
|
|
988
1060
|
// 计算缩进级别(假设使用空格缩进)
|
|
989
1061
|
const indent = line.match(/^(\s*)/)[0].length;
|
|
990
|
-
const indentLevel = Math.floor(indent / config.indentSize);
|
|
991
1062
|
|
|
992
1063
|
// 处理规则块结束
|
|
993
1064
|
if (trimmed === '}') {
|
|
@@ -1015,8 +1086,7 @@
|
|
|
1015
1086
|
const rule = {
|
|
1016
1087
|
selector: selector,
|
|
1017
1088
|
properties: [],
|
|
1018
|
-
children: []
|
|
1019
|
-
indentLevel: indentLevel
|
|
1089
|
+
children: []
|
|
1020
1090
|
};
|
|
1021
1091
|
|
|
1022
1092
|
// 推入堆栈
|
|
@@ -1064,7 +1134,7 @@
|
|
|
1064
1134
|
for (const rule of rules) {
|
|
1065
1135
|
// 构建完整选择器
|
|
1066
1136
|
const isAt = rule.selector.startsWith("@");
|
|
1067
|
-
let fullSelector = (isParentAt ?
|
|
1137
|
+
let fullSelector = (isParentAt ? " ".repeat(config.indentSize) : '') + rule.selector;
|
|
1068
1138
|
|
|
1069
1139
|
if (isAt) {
|
|
1070
1140
|
fullSelector = rule.selector + " {\n";
|
|
@@ -1090,10 +1160,10 @@
|
|
|
1090
1160
|
parsed.forEach(obj => {
|
|
1091
1161
|
const key = Object.keys(obj)[0];
|
|
1092
1162
|
const value = processCSSValue(obj[key], config);
|
|
1093
|
-
result += (isParentAt ?
|
|
1163
|
+
result += (isParentAt ? " ".repeat(config.indentSize) : '') + " ".repeat(config.indentSize) +`${key}: ${value};\n`;
|
|
1094
1164
|
});
|
|
1095
1165
|
}
|
|
1096
|
-
result += isParentAt ? '
|
|
1166
|
+
result += isParentAt ? " ".repeat(config.indentSize) + '}\n' : '}\n\n';
|
|
1097
1167
|
}
|
|
1098
1168
|
|
|
1099
1169
|
// 递归处理子规则
|
|
@@ -1106,7 +1176,7 @@
|
|
|
1106
1176
|
}
|
|
1107
1177
|
}
|
|
1108
1178
|
|
|
1109
|
-
return result.trim() +
|
|
1179
|
+
return result.trim() + "\n\n";
|
|
1110
1180
|
}
|
|
1111
1181
|
|
|
1112
1182
|
// 替换变量值中的变量引用
|
|
@@ -1146,7 +1216,7 @@
|
|
|
1146
1216
|
replaceVariableUsesInValue(value, variables),
|
|
1147
1217
|
config
|
|
1148
1218
|
);
|
|
1149
|
-
return
|
|
1219
|
+
return " ".repeat(config.indentSize) + `--${name}: ${processedValue};`;
|
|
1150
1220
|
})
|
|
1151
1221
|
.join('\n');
|
|
1152
1222
|
|
|
@@ -1172,7 +1242,7 @@
|
|
|
1172
1242
|
const vars = selectorVariables.get(currentSelector);
|
|
1173
1243
|
const indent = line.match(/^(\s*)/)[0];
|
|
1174
1244
|
for (const [varName, varValue] of Object.entries(vars)) {
|
|
1175
|
-
result +=
|
|
1245
|
+
result += ' --' + varName + ': ' + varValue + ';\n';
|
|
1176
1246
|
}
|
|
1177
1247
|
}
|
|
1178
1248
|
currentSelector = null;
|
|
@@ -1185,38 +1255,201 @@
|
|
|
1185
1255
|
return result.trim();
|
|
1186
1256
|
}
|
|
1187
1257
|
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1258
|
+
/**
|
|
1259
|
+
* 处理 @import 语句
|
|
1260
|
+
* @param {string} cssText - CSS文本
|
|
1261
|
+
* @param {Object} config - 配置对象
|
|
1262
|
+
* @returns {Promise<string>} 处理后的CSS文本
|
|
1263
|
+
*/
|
|
1264
|
+
async function processImports(cssText, config) {
|
|
1265
|
+
// 匹配所有@import语句
|
|
1266
|
+
const importRegex = /@import\s+(?:url\()?["']([^"']+)["'](?:\))?[^;]*;/g;
|
|
1267
|
+
|
|
1268
|
+
// 递归处理函数
|
|
1269
|
+
const processRecursive = async (text) => {
|
|
1270
|
+
// 使用replace更简单的方式
|
|
1271
|
+
const importPromises = [];
|
|
1272
|
+
|
|
1273
|
+
// 使用replace的回调函数收集所有import的promise
|
|
1274
|
+
const processedText = text.replace(importRegex, (match, url) => {
|
|
1275
|
+
const importPromise = fetchImportContent(url, config)
|
|
1276
|
+
.then(importedCSS => {
|
|
1277
|
+
// 递归处理导入的CSS中的import
|
|
1278
|
+
return processImports(importedCSS, config);
|
|
1279
|
+
})
|
|
1280
|
+
.catch(error => {
|
|
1281
|
+
console.warn(`无法导入CSS文件: ${url}`, error);
|
|
1282
|
+
// 导入失败时返回空字符串
|
|
1283
|
+
return '';
|
|
1284
|
+
});
|
|
1285
|
+
|
|
1286
|
+
importPromises.push(importPromise);
|
|
1287
|
+
|
|
1288
|
+
// 返回占位符
|
|
1289
|
+
return `__IMPORT_PLACEHOLDER_${importPromises.length - 1}__`;
|
|
1290
|
+
});
|
|
1291
|
+
|
|
1292
|
+
// 等待所有import完成
|
|
1293
|
+
if (importPromises.length > 0) {
|
|
1294
|
+
const importedContents = await Promise.all(importPromises);
|
|
1295
|
+
|
|
1296
|
+
// 替换占位符为实际内容
|
|
1297
|
+
let finalText = processedText;
|
|
1298
|
+
for (let i = 0; i < importedContents.length; i++) {
|
|
1299
|
+
finalText = finalText.replace(`__IMPORT_PLACEHOLDER_${i}__`, importedContents[i]);
|
|
1300
|
+
}
|
|
1301
|
+
|
|
1302
|
+
return finalText;
|
|
1303
|
+
}
|
|
1304
|
+
|
|
1305
|
+
return processedText;
|
|
1306
|
+
};
|
|
1307
|
+
|
|
1308
|
+
return processRecursive(cssText);
|
|
1309
|
+
}
|
|
1310
|
+
|
|
1311
|
+
/**
|
|
1312
|
+
* 获取导入的CSS内容
|
|
1313
|
+
* @param {string} url - CSS文件URL
|
|
1314
|
+
* @param {Object} config - 配置对象
|
|
1315
|
+
* @returns {Promise<string>} CSS内容
|
|
1316
|
+
*/
|
|
1317
|
+
async function fetchImportContent(url, config) {
|
|
1318
|
+
// 检查缓存
|
|
1319
|
+
if (config.importCache && importCache.has(url)) {
|
|
1320
|
+
return importCache.get(url);
|
|
1321
|
+
}
|
|
1322
|
+
|
|
1323
|
+
// 构建完整的URL
|
|
1324
|
+
const fullUrl = config.importBaseUrl
|
|
1325
|
+
? new URL(url, config.importBaseUrl).href
|
|
1326
|
+
: url;
|
|
1191
1327
|
|
|
1192
|
-
|
|
1193
|
-
const { globalVariables, selectorVariables, cssWithoutVars } =
|
|
1194
|
-
extractVariablesAndCSS(cssText, config);
|
|
1328
|
+
let content;
|
|
1195
1329
|
|
|
1196
|
-
//
|
|
1197
|
-
|
|
1198
|
-
|
|
1330
|
+
// 检查是否为Node.js环境
|
|
1331
|
+
const isNode = typeof process !== 'undefined' &&
|
|
1332
|
+
process.versions &&
|
|
1333
|
+
process.versions.node;
|
|
1334
|
+
|
|
1335
|
+
if (isNode && typeof require !== 'undefined') {
|
|
1336
|
+
// Node.js环境:使用fs模块
|
|
1337
|
+
try {
|
|
1338
|
+
const fs = require('fs');
|
|
1339
|
+
const path = require('path');
|
|
1340
|
+
|
|
1341
|
+
// 处理相对路径
|
|
1342
|
+
let filePath = fullUrl;
|
|
1343
|
+
if (fullUrl.startsWith('.')) {
|
|
1344
|
+
filePath = path.join(process.cwd(), fullUrl);
|
|
1345
|
+
}
|
|
1346
|
+
|
|
1347
|
+
content = fs.readFileSync(filePath, 'utf-8');
|
|
1348
|
+
} catch (error) {
|
|
1349
|
+
throw new Error(`Node.js文件读取失败: ${fullUrl} - ${error.message}`);
|
|
1350
|
+
}
|
|
1351
|
+
} else {
|
|
1352
|
+
// 浏览器环境:使用fetch
|
|
1199
1353
|
try {
|
|
1200
|
-
|
|
1354
|
+
const controller = new AbortController();
|
|
1355
|
+
const timeoutId = setTimeout(() => controller.abort(), config.importTimeout);
|
|
1356
|
+
|
|
1357
|
+
const response = await fetch(fullUrl, {
|
|
1358
|
+
signal: controller.signal,
|
|
1359
|
+
headers: {
|
|
1360
|
+
'Accept': 'text/css'
|
|
1361
|
+
}
|
|
1362
|
+
});
|
|
1363
|
+
|
|
1364
|
+
clearTimeout(timeoutId);
|
|
1365
|
+
|
|
1366
|
+
if (!response.ok) {
|
|
1367
|
+
throw new Error(`HTTP错误: ${response.status} ${response.statusText}`);
|
|
1368
|
+
}
|
|
1369
|
+
|
|
1370
|
+
content = await response.text();
|
|
1201
1371
|
} catch (error) {
|
|
1202
|
-
|
|
1372
|
+
if (error.name === 'AbortError') {
|
|
1373
|
+
throw new Error(`导入超时: ${fullUrl}`);
|
|
1374
|
+
}
|
|
1375
|
+
throw new Error(`无法获取CSS: ${fullUrl} - ${error.message}`);
|
|
1203
1376
|
}
|
|
1204
1377
|
}
|
|
1205
1378
|
|
|
1206
|
-
//
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
// 4. 注入选择器局部变量
|
|
1210
|
-
let finalCSS = processedCSS;
|
|
1211
|
-
if (selectorVariables.size > 0) {
|
|
1212
|
-
finalCSS = injectSelectorVariables(processedCSS, selectorVariables, config);
|
|
1379
|
+
// 缓存内容
|
|
1380
|
+
if (config.importCache) {
|
|
1381
|
+
importCache.set(url, content);
|
|
1213
1382
|
}
|
|
1214
1383
|
|
|
1215
|
-
|
|
1216
|
-
|
|
1384
|
+
return content;
|
|
1385
|
+
}
|
|
1386
|
+
|
|
1387
|
+
/**
|
|
1388
|
+
* 主转换函数
|
|
1389
|
+
* @param {string} cssText - CSS文本
|
|
1390
|
+
* @param {Object} customConfig - 自定义配置
|
|
1391
|
+
* @returns {string|Promise<string>} 转换后的CSS(如果是异步则为Promise)
|
|
1392
|
+
*/
|
|
1393
|
+
function convert(cssText, customConfig = {}) {
|
|
1394
|
+
// 处理同步和异步版本
|
|
1395
|
+
const syncConvert = (cssText, config) => {
|
|
1396
|
+
// 先解析配置头
|
|
1397
|
+
const { config: headerConfig, css: cleanedCSS } = parseConfigHeader(cssText);
|
|
1398
|
+
const finalConfig = { ...defaultConfig, ...customConfig,...headerConfig };
|
|
1399
|
+
|
|
1400
|
+
// 1. 提取变量定义(区分全局和选择器局部)
|
|
1401
|
+
const { globalVariables, selectorVariables, cssWithoutVars } =
|
|
1402
|
+
extractVariablesAndCSS(cleanedCSS, finalConfig);
|
|
1403
|
+
|
|
1404
|
+
// 2. 解析嵌套规则(如果启用)
|
|
1405
|
+
let processedCSS = cssWithoutVars.trim();
|
|
1406
|
+
if (finalConfig.enableNesting && cssWithoutVars.includes('{')) {
|
|
1407
|
+
try {
|
|
1408
|
+
processedCSS = parseNestedRules(cssWithoutVars, finalConfig);
|
|
1409
|
+
} catch (error) {
|
|
1410
|
+
console.warn('嵌套解析失败,使用原始CSS:', error);
|
|
1411
|
+
}
|
|
1412
|
+
}
|
|
1413
|
+
|
|
1414
|
+
// 3. 生成根规则(全局变量)
|
|
1415
|
+
const rootRule = generateRootRule(globalVariables, finalConfig);
|
|
1416
|
+
|
|
1417
|
+
// 4. 注入选择器局部变量
|
|
1418
|
+
let finalCSS = processedCSS;
|
|
1419
|
+
if (selectorVariables.size > 0) {
|
|
1420
|
+
finalCSS = injectSelectorVariables(processedCSS, selectorVariables, finalConfig);
|
|
1421
|
+
}
|
|
1422
|
+
|
|
1423
|
+
// 5. 替换变量使用
|
|
1424
|
+
finalCSS = replaceVariableUses(finalCSS, globalVariables, selectorVariables, finalConfig);
|
|
1425
|
+
|
|
1426
|
+
// 6. 组合结果
|
|
1427
|
+
return rootRule + finalCSS;
|
|
1428
|
+
};
|
|
1429
|
+
|
|
1430
|
+
// 检查是否有@import语句(需要异步处理)
|
|
1431
|
+
const hasImports = cssText && /@import\s+(?:url\()?["']([^"']+)["']/i.test(cssText);
|
|
1432
|
+
const finalConfig = { ...defaultConfig, ...customConfig };
|
|
1217
1433
|
|
|
1218
|
-
|
|
1219
|
-
|
|
1434
|
+
if (!hasImports) {
|
|
1435
|
+
// 没有@import,同步处理
|
|
1436
|
+
return syncConvert(cssText, finalConfig);
|
|
1437
|
+
} else {
|
|
1438
|
+
// 有@import,异步处理
|
|
1439
|
+
return (async () => {
|
|
1440
|
+
try {
|
|
1441
|
+
// 处理@import
|
|
1442
|
+
const processedWithImports = await processImports(cssText, finalConfig);
|
|
1443
|
+
|
|
1444
|
+
// 同步处理剩余部分
|
|
1445
|
+
return syncConvert(processedWithImports, finalConfig);
|
|
1446
|
+
} catch (error) {
|
|
1447
|
+
console.error('@import处理失败:', error);
|
|
1448
|
+
// 如果导入失败,尝试同步处理原始CSS
|
|
1449
|
+
return syncConvert(cleanedCSS, finalConfig);
|
|
1450
|
+
}
|
|
1451
|
+
})();
|
|
1452
|
+
}
|
|
1220
1453
|
}
|
|
1221
1454
|
|
|
1222
1455
|
// 自动处理带有 e 属性的 style 标签
|
|
@@ -1225,22 +1458,35 @@
|
|
|
1225
1458
|
const styleTags = document.querySelectorAll(`style[${config.styleTagAttribute || 'e'}]`);
|
|
1226
1459
|
|
|
1227
1460
|
styleTags.forEach(styleTag => {
|
|
1228
|
-
|
|
1229
|
-
const convertedCSS = convert(originalCSS, config);
|
|
1461
|
+
let originalCSS = styleTag.textContent;
|
|
1230
1462
|
|
|
1231
|
-
//
|
|
1232
|
-
const
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1463
|
+
// 处理@import(如果是异步的)
|
|
1464
|
+
const processStyleTag = async () => {
|
|
1465
|
+
try {
|
|
1466
|
+
if (styleTag.getAttribute("src")){
|
|
1467
|
+
originalCSS = "@import url(\""+styleTag.getAttribute("src")+"\");";
|
|
1468
|
+
}
|
|
1469
|
+
const convertedCSS = await convert(originalCSS, config);
|
|
1470
|
+
|
|
1471
|
+
// 创建新的 style 标签
|
|
1472
|
+
const newStyleTag = document.createElement('style');
|
|
1473
|
+
newStyleTag.textContent = convertedCSS;
|
|
1474
|
+
|
|
1475
|
+
// 插入到原标签后面
|
|
1476
|
+
styleTag.parentNode.insertBefore(newStyleTag, styleTag.nextSibling);
|
|
1477
|
+
|
|
1478
|
+
// 可选:移除原标签
|
|
1479
|
+
if (!config.preserveOriginal) {
|
|
1480
|
+
styleTag.remove();
|
|
1481
|
+
} else {
|
|
1482
|
+
styleTag.style.display = 'none';
|
|
1483
|
+
}
|
|
1484
|
+
} catch (error) {
|
|
1485
|
+
console.error('处理style标签失败:', error);
|
|
1486
|
+
}
|
|
1487
|
+
};
|
|
1237
1488
|
|
|
1238
|
-
|
|
1239
|
-
if (!config.preserveOriginal) {
|
|
1240
|
-
styleTag.remove();
|
|
1241
|
-
} else {
|
|
1242
|
-
styleTag.style.display = 'none';
|
|
1243
|
-
}
|
|
1489
|
+
processStyleTag();
|
|
1244
1490
|
});
|
|
1245
1491
|
|
|
1246
1492
|
return styleTags.length;
|
|
@@ -1255,11 +1501,31 @@
|
|
|
1255
1501
|
const config = { ...defaultConfig, ...customConfig };
|
|
1256
1502
|
|
|
1257
1503
|
if (cssText) {
|
|
1258
|
-
|
|
1259
|
-
const
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1504
|
+
// 检查是否有@import
|
|
1505
|
+
const hasImports = /@import\s+(?:url\()?["']([^"']+)["']/i.test(cssText);
|
|
1506
|
+
|
|
1507
|
+
if (hasImports) {
|
|
1508
|
+
// 异步处理
|
|
1509
|
+
return (async () => {
|
|
1510
|
+
try {
|
|
1511
|
+
const converted = await convert(cssText, config);
|
|
1512
|
+
const styleEl = document.createElement('style');
|
|
1513
|
+
styleEl.textContent = converted;
|
|
1514
|
+
document.head.appendChild(styleEl);
|
|
1515
|
+
return styleEl;
|
|
1516
|
+
} catch (error) {
|
|
1517
|
+
console.error('应用CSS失败:', error);
|
|
1518
|
+
return null;
|
|
1519
|
+
}
|
|
1520
|
+
})();
|
|
1521
|
+
} else {
|
|
1522
|
+
// 同步处理
|
|
1523
|
+
const converted = convert(cssText, config);
|
|
1524
|
+
const styleEl = document.createElement('style');
|
|
1525
|
+
styleEl.textContent = converted;
|
|
1526
|
+
document.head.appendChild(styleEl);
|
|
1527
|
+
return styleEl;
|
|
1528
|
+
}
|
|
1263
1529
|
} else {
|
|
1264
1530
|
if (document.readyState === 'loading') {
|
|
1265
1531
|
document.addEventListener('DOMContentLoaded', () => {
|
|
@@ -1284,6 +1550,40 @@
|
|
|
1284
1550
|
// 重新检测P3支持(如果配置变化)
|
|
1285
1551
|
detectP3Support: detectP3Support,
|
|
1286
1552
|
|
|
1553
|
+
// 导入相关方法
|
|
1554
|
+
imports: {
|
|
1555
|
+
/**
|
|
1556
|
+
* 清除导入缓存
|
|
1557
|
+
*/
|
|
1558
|
+
clearCache: function() {
|
|
1559
|
+
importCache.clear();
|
|
1560
|
+
},
|
|
1561
|
+
|
|
1562
|
+
/**
|
|
1563
|
+
* 设置导入基础URL
|
|
1564
|
+
* @param {string} baseUrl - 基础URL
|
|
1565
|
+
*/
|
|
1566
|
+
setBaseUrl: function(baseUrl) {
|
|
1567
|
+
defaultConfig.importBaseUrl = baseUrl;
|
|
1568
|
+
},
|
|
1569
|
+
|
|
1570
|
+
/**
|
|
1571
|
+
* 启用/禁用导入缓存
|
|
1572
|
+
* @param {boolean} enabled - 是否启用缓存
|
|
1573
|
+
*/
|
|
1574
|
+
setCacheEnabled: function(enabled) {
|
|
1575
|
+
defaultConfig.importCache = enabled;
|
|
1576
|
+
},
|
|
1577
|
+
|
|
1578
|
+
/**
|
|
1579
|
+
* 设置导入超时时间
|
|
1580
|
+
* @param {number} timeout - 超时时间(毫秒)
|
|
1581
|
+
*/
|
|
1582
|
+
setTimeout: function(timeout) {
|
|
1583
|
+
defaultConfig.importTimeout = timeout;
|
|
1584
|
+
}
|
|
1585
|
+
},
|
|
1586
|
+
|
|
1287
1587
|
// 数学计算工具方法
|
|
1288
1588
|
math: {
|
|
1289
1589
|
/**
|
|
@@ -1461,7 +1761,13 @@
|
|
|
1461
1761
|
|
|
1462
1762
|
// 如果传入CSS文本,则编译并返回结果
|
|
1463
1763
|
if (typeof firstArg === 'string') {
|
|
1464
|
-
|
|
1764
|
+
const result = convert(firstArg, { ...defaultConfig, ...args[1] });
|
|
1765
|
+
|
|
1766
|
+
// 如果结果是Promise,返回Promise
|
|
1767
|
+
if (result && typeof result.then === 'function') {
|
|
1768
|
+
return result;
|
|
1769
|
+
}
|
|
1770
|
+
return result;
|
|
1465
1771
|
}
|
|
1466
1772
|
|
|
1467
1773
|
// 如果传入配置对象,则应用配置
|
|
@@ -1500,12 +1806,18 @@
|
|
|
1500
1806
|
}
|
|
1501
1807
|
|
|
1502
1808
|
// 使用convert函数处理
|
|
1503
|
-
|
|
1809
|
+
const result = convert(cssText, defaultConfig);
|
|
1810
|
+
|
|
1811
|
+
// 如果结果是Promise,返回Promise
|
|
1812
|
+
if (result && typeof result.then === 'function') {
|
|
1813
|
+
return result;
|
|
1814
|
+
}
|
|
1815
|
+
return result;
|
|
1504
1816
|
}
|
|
1505
1817
|
|
|
1506
1818
|
// 将API的所有方法复制到主函数上
|
|
1507
1819
|
Object.assign(styimat, api);
|
|
1508
|
-
|
|
1820
|
+
Object.setPrototypeOf(styimat, Function.prototype);
|
|
1509
1821
|
|
|
1510
1822
|
// 自动初始化
|
|
1511
1823
|
if (typeof window !== 'undefined') {
|
package/styimat.min.js
CHANGED
|
@@ -9,6 +9,8 @@
|
|
|
9
9
|
* 支持 lab# 和 lch# 十六进制语法
|
|
10
10
|
* 支持 Display P3 广色域显示器
|
|
11
11
|
* 增强 math() 函数,支持复杂数学计算
|
|
12
|
+
* 支持配置头
|
|
13
|
+
* 支持 @import 语法
|
|
12
14
|
*/
|
|
13
15
|
(function(root,factory){if(typeof define==="function"&&define.amd){
|
|
14
16
|
// AMD 支持 (RequireJS)
|
|
@@ -18,10 +20,35 @@ module.exports=factory()}else{
|
|
|
18
20
|
// 浏览器全局变量
|
|
19
21
|
root.styimat=factory()}})(typeof self!=="undefined"?self:this,function(){
|
|
20
22
|
// 默认配置
|
|
21
|
-
let defaultConfig={rootSelector:":root",variablePrefix:"--",preserveOriginal:false,indentSize:
|
|
22
|
-
mathPrecision:6
|
|
23
|
+
let defaultConfig={rootSelector:":root",variablePrefix:"--",preserveOriginal:false,indentSize:4,enableNesting:true,autoProcessStyleTags:true,styleTagAttribute:"e",convertLabToRGB:true,convertLchToRGB:true,enableP3:true,enableMath:true,// 启用math()函数增强
|
|
24
|
+
mathPrecision:6,// 数学计算精度
|
|
25
|
+
importBaseUrl:"",// @import 的基础URL
|
|
26
|
+
importCache:true,// 缓存导入的文件
|
|
27
|
+
importTimeout:5e3};
|
|
23
28
|
// 全局P3支持检测结果
|
|
24
29
|
let p3Supported=null;
|
|
30
|
+
// 导入文件缓存
|
|
31
|
+
const importCache=new Map;
|
|
32
|
+
/**
|
|
33
|
+
* 解析配置头
|
|
34
|
+
* @param {string} cssText - CSS文本
|
|
35
|
+
* @returns {Object} {config: 配置对象, css: 清理后的CSS}
|
|
36
|
+
*/function parseConfigHeader(cssText){const config={...defaultConfig};const lines=cssText.split("\n");const cleanLines=[];for(let line of lines){const trimmed=line.trim();
|
|
37
|
+
// 检查是否是配置行(以 # 开头)
|
|
38
|
+
if(trimmed.startsWith("#")){const configLine=trimmed.substring(1).trim();const firstSpace=configLine.indexOf(" ");if(firstSpace!==-1){const key=configLine.substring(0,firstSpace).trim();const value=configLine.substring(firstSpace+1).trim();
|
|
39
|
+
// 转换配置值
|
|
40
|
+
const configKey=camelCase(key);if(configKey in config){
|
|
41
|
+
// 根据值的类型进行转换
|
|
42
|
+
if(value==="true"||value==="false"){config[configKey]=value==="true"}else if(!isNaN(value)&&value.trim()!==""){
|
|
43
|
+
// 数字类型
|
|
44
|
+
config[configKey]=Number(value)}else{
|
|
45
|
+
// 字符串类型
|
|
46
|
+
config[configKey]=value}}else{console.warn(`未知的配置项: ${key}`)}}else{console.warn(`无效的配置行: ${trimmed}`)}}else{cleanLines.push(line)}}return{config:config,css:cleanLines.join("\n")}}
|
|
47
|
+
/**
|
|
48
|
+
* 将连字符分隔的字符串转换为驼峰命名
|
|
49
|
+
* @param {string} str - 输入字符串
|
|
50
|
+
* @returns {string} 驼峰命名字符串
|
|
51
|
+
*/function camelCase(str){return str.replace(/-([a-z])/g,function(match,letter){return letter.toUpperCase()})}
|
|
25
52
|
/**
|
|
26
53
|
* 检测浏览器是否支持 Display P3
|
|
27
54
|
* @returns {boolean} 是否支持P3
|
|
@@ -350,12 +377,12 @@ if(trimmed==="}"){if(inSelectorBlock){
|
|
|
350
377
|
// 在选择器结束前插入变量声明
|
|
351
378
|
if(currentSelector&&selectorVariables.has(currentSelector)){const vars=selectorVariables.get(currentSelector);const indent=" ".repeat(currentIndent);for(const[varName,varValue]of Object.entries(vars)){cssWithoutVars+=`${indent} --${varName}: ${varValue};\n`}}}inSelectorBlock=false;currentSelector=null}cssWithoutVars+=line+"\n";
|
|
352
379
|
// 更新缩进级别
|
|
353
|
-
if(line.includes("{")){currentIndent+=
|
|
380
|
+
if(line.includes("{")){currentIndent+=config.indentSize}if(line.includes("}")){currentIndent=Math.max(0,currentIndent-config.indentSize)}}return{globalVariables:globalVariables,selectorVariables:selectorVariables,cssWithoutVars:cssWithoutVars.trim()}}
|
|
354
381
|
// 修复的嵌套解析函数
|
|
355
382
|
function parseNestedRules(cssText,config){const lines=cssText.split("\n");const stack=[];// 存储规则信息:{selector, properties, children}
|
|
356
383
|
const rootRules=[];let currentIndent=0;for(let i=0;i<lines.length;i++){const line=lines[i];const trimmed=line.trim();if(!trimmed)continue;
|
|
357
384
|
// 计算缩进级别(假设使用空格缩进)
|
|
358
|
-
const indent=line.match(/^(\s*)/)[0].length;
|
|
385
|
+
const indent=line.match(/^(\s*)/)[0].length;
|
|
359
386
|
// 处理规则块结束
|
|
360
387
|
if(trimmed==="}"){if(stack.length>0){const rule=stack.pop();
|
|
361
388
|
// 如果这个规则有父规则,添加到父规则的children中
|
|
@@ -365,7 +392,7 @@ rootRules.push(rule)}}continue}
|
|
|
365
392
|
// 处理规则开始
|
|
366
393
|
if(trimmed.endsWith("{")){const selector=trimmed.slice(0,-1).trim();
|
|
367
394
|
// 创建新规则
|
|
368
|
-
const rule={selector:selector,properties:[],children:[]
|
|
395
|
+
const rule={selector:selector,properties:[],children:[]};
|
|
369
396
|
// 推入堆栈
|
|
370
397
|
stack.push(rule);continue}
|
|
371
398
|
// 处理属性行(不包含 { 或 } 的行)
|
|
@@ -379,15 +406,15 @@ return convertRulesToCSS(rootRules,config)}
|
|
|
379
406
|
// 将规则树转换为CSS字符串
|
|
380
407
|
function convertRulesToCSS(rules,config,parentSelector=""){let result="";const isParentAt=parentSelector.startsWith("@");for(const rule of rules){
|
|
381
408
|
// 构建完整选择器
|
|
382
|
-
const isAt=rule.selector.startsWith("@");let fullSelector=(isParentAt?"
|
|
409
|
+
const isAt=rule.selector.startsWith("@");let fullSelector=(isParentAt?" ".repeat(config.indentSize):"")+rule.selector;if(isAt){fullSelector=rule.selector+" {\n"}if(parentSelector){
|
|
383
410
|
// 处理 & 选择器
|
|
384
411
|
if(fullSelector.includes("&")){fullSelector=fullSelector.replace(/&/g,parentSelector)}else if(fullSelector.trim().startsWith(":")){fullSelector=parentSelector+fullSelector}else{fullSelector=parentSelector+(isParentAt?"":" ")+fullSelector}}
|
|
385
412
|
// 如果有属性,生成规则块
|
|
386
413
|
if(rule.properties.length>0){result+=(isAt?"":fullSelector)+" {\n";for(const prop of rule.properties){
|
|
387
414
|
// 再次处理属性值(确保math()函数和颜色被转换)
|
|
388
|
-
const parsed=parseSingleLineCSS(prop);parsed.forEach(obj=>{const key=Object.keys(obj)[0];const value=processCSSValue(obj[key],config);result+=(isParentAt?"
|
|
415
|
+
const parsed=parseSingleLineCSS(prop);parsed.forEach(obj=>{const key=Object.keys(obj)[0];const value=processCSSValue(obj[key],config);result+=(isParentAt?" ".repeat(config.indentSize):"")+" ".repeat(config.indentSize)+`${key}: ${value};\n`})}result+=isParentAt?" ".repeat(config.indentSize)+"}\n":"}\n\n"}
|
|
389
416
|
// 递归处理子规则
|
|
390
|
-
if(rule.children&&rule.children.length>0){result+=convertRulesToCSS(rule.children,config,fullSelector)}if(isParentAt){result+="}\n\n"}}return result.trim()+
|
|
417
|
+
if(rule.children&&rule.children.length>0){result+=convertRulesToCSS(rule.children,config,fullSelector)}if(isParentAt){result+="}\n\n"}}return result.trim()+"\n\n"}
|
|
391
418
|
// 替换变量值中的变量引用
|
|
392
419
|
function replaceVariableUsesInValue(value,variables){return value.replace(/\$([a-zA-Z0-9_-]+)/g,(match,varName)=>{if(variables[varName]){return`var(--${varName})`}return match})}
|
|
393
420
|
// 替换变量使用
|
|
@@ -397,43 +424,134 @@ result=result.replace(/(?:\$\$|\$)([a-zA-Z0-9_-]+)/g,(match,varName)=>match.star
|
|
|
397
424
|
// 最后处理所有的LAB、LCH颜色和math()函数
|
|
398
425
|
result=processCSSValue(result,config);return result}
|
|
399
426
|
// 生成根规则
|
|
400
|
-
function generateRootRule(variables,config){if(Object.keys(variables).length===0){return""}const declarations=Object.entries(variables).map(([name,value])=>{const processedValue=processCSSValue(replaceVariableUsesInValue(value,variables),config);return
|
|
427
|
+
function generateRootRule(variables,config){if(Object.keys(variables).length===0){return""}const declarations=Object.entries(variables).map(([name,value])=>{const processedValue=processCSSValue(replaceVariableUsesInValue(value,variables),config);return" ".repeat(config.indentSize)+`--${name}: ${processedValue};`}).join("\n");return`${config.rootSelector} {\n${declarations}\n}\n\n`}
|
|
401
428
|
// 处理选择器内部的变量
|
|
402
429
|
function injectSelectorVariables(cssText,selectorVariables,config){let result="";const lines=cssText.split("\n");let currentSelector=null;for(let line of lines){const trimmed=line.trim();if(trimmed.endsWith("{")){currentSelector=trimmed.slice(0,-1).trim()}if(trimmed==="}"&¤tSelector){
|
|
403
430
|
// 在选择器结束前插入变量声明
|
|
404
|
-
if(selectorVariables.has(currentSelector)){const vars=selectorVariables.get(currentSelector);const indent=line.match(/^(\s*)/)[0];for(const[varName,varValue]of Object.entries(vars)){result+=
|
|
431
|
+
if(selectorVariables.has(currentSelector)){const vars=selectorVariables.get(currentSelector);const indent=line.match(/^(\s*)/)[0];for(const[varName,varValue]of Object.entries(vars)){result+=" --"+varName+": "+varValue+";\n"}}currentSelector=null}
|
|
405
432
|
// 使用全局处理函数处理所有math()和颜色
|
|
406
433
|
result+=processCSSValue(line,config)+"\n"}return result.trim()}
|
|
407
|
-
|
|
408
|
-
|
|
434
|
+
/**
|
|
435
|
+
* 处理 @import 语句
|
|
436
|
+
* @param {string} cssText - CSS文本
|
|
437
|
+
* @param {Object} config - 配置对象
|
|
438
|
+
* @returns {Promise<string>} 处理后的CSS文本
|
|
439
|
+
*/async function processImports(cssText,config){
|
|
440
|
+
// 匹配所有@import语句
|
|
441
|
+
const importRegex=/@import\s+(?:url\()?["']([^"']+)["'](?:\))?[^;]*;/g;
|
|
442
|
+
// 递归处理函数
|
|
443
|
+
const processRecursive=async text=>{
|
|
444
|
+
// 使用replace更简单的方式
|
|
445
|
+
const importPromises=[];
|
|
446
|
+
// 使用replace的回调函数收集所有import的promise
|
|
447
|
+
const processedText=text.replace(importRegex,(match,url)=>{const importPromise=fetchImportContent(url,config).then(importedCSS=>processImports(importedCSS,config)).catch(error=>{console.warn(`无法导入CSS文件: ${url}`,error);
|
|
448
|
+
// 导入失败时返回空字符串
|
|
449
|
+
return""});importPromises.push(importPromise);
|
|
450
|
+
// 返回占位符
|
|
451
|
+
return`__IMPORT_PLACEHOLDER_${importPromises.length-1}__`});
|
|
452
|
+
// 等待所有import完成
|
|
453
|
+
if(importPromises.length>0){const importedContents=await Promise.all(importPromises);
|
|
454
|
+
// 替换占位符为实际内容
|
|
455
|
+
let finalText=processedText;for(let i=0;i<importedContents.length;i++){finalText=finalText.replace(`__IMPORT_PLACEHOLDER_${i}__`,importedContents[i])}return finalText}return processedText};return processRecursive(cssText)}
|
|
456
|
+
/**
|
|
457
|
+
* 获取导入的CSS内容
|
|
458
|
+
* @param {string} url - CSS文件URL
|
|
459
|
+
* @param {Object} config - 配置对象
|
|
460
|
+
* @returns {Promise<string>} CSS内容
|
|
461
|
+
*/async function fetchImportContent(url,config){
|
|
462
|
+
// 检查缓存
|
|
463
|
+
if(config.importCache&&importCache.has(url)){return importCache.get(url)}
|
|
464
|
+
// 构建完整的URL
|
|
465
|
+
const fullUrl=config.importBaseUrl?new URL(url,config.importBaseUrl).href:url;let content;
|
|
466
|
+
// 检查是否为Node.js环境
|
|
467
|
+
const isNode=typeof process!=="undefined"&&process.versions&&process.versions.node;if(isNode&&typeof require!=="undefined"){
|
|
468
|
+
// Node.js环境:使用fs模块
|
|
469
|
+
try{const fs=require("fs");const path=require("path");
|
|
470
|
+
// 处理相对路径
|
|
471
|
+
let filePath=fullUrl;if(fullUrl.startsWith(".")){filePath=path.join(process.cwd(),fullUrl)}content=fs.readFileSync(filePath,"utf-8")}catch(error){throw new Error(`Node.js文件读取失败: ${fullUrl} - ${error.message}`)}}else{
|
|
472
|
+
// 浏览器环境:使用fetch
|
|
473
|
+
try{const controller=new AbortController;const timeoutId=setTimeout(()=>controller.abort(),config.importTimeout);const response=await fetch(fullUrl,{signal:controller.signal,headers:{Accept:"text/css"}});clearTimeout(timeoutId);if(!response.ok){throw new Error(`HTTP错误: ${response.status} ${response.statusText}`)}content=await response.text()}catch(error){if(error.name==="AbortError"){throw new Error(`导入超时: ${fullUrl}`)}throw new Error(`无法获取CSS: ${fullUrl} - ${error.message}`)}}
|
|
474
|
+
// 缓存内容
|
|
475
|
+
if(config.importCache){importCache.set(url,content)}return content}
|
|
476
|
+
/**
|
|
477
|
+
* 主转换函数
|
|
478
|
+
* @param {string} cssText - CSS文本
|
|
479
|
+
* @param {Object} customConfig - 自定义配置
|
|
480
|
+
* @returns {string|Promise<string>} 转换后的CSS(如果是异步则为Promise)
|
|
481
|
+
*/function convert(cssText,customConfig={}){
|
|
482
|
+
// 处理同步和异步版本
|
|
483
|
+
const syncConvert=(cssText,config)=>{
|
|
484
|
+
// 先解析配置头
|
|
485
|
+
const{config:headerConfig,css:cleanedCSS}=parseConfigHeader(cssText);const finalConfig={...defaultConfig,...customConfig,...headerConfig};
|
|
409
486
|
// 1. 提取变量定义(区分全局和选择器局部)
|
|
410
|
-
const{globalVariables:globalVariables,selectorVariables:selectorVariables,cssWithoutVars:cssWithoutVars}=extractVariablesAndCSS(
|
|
487
|
+
const{globalVariables:globalVariables,selectorVariables:selectorVariables,cssWithoutVars:cssWithoutVars}=extractVariablesAndCSS(cleanedCSS,finalConfig);
|
|
411
488
|
// 2. 解析嵌套规则(如果启用)
|
|
412
|
-
let processedCSS=cssWithoutVars.trim();if(
|
|
489
|
+
let processedCSS=cssWithoutVars.trim();if(finalConfig.enableNesting&&cssWithoutVars.includes("{")){try{processedCSS=parseNestedRules(cssWithoutVars,finalConfig)}catch(error){console.warn("嵌套解析失败,使用原始CSS:",error)}}
|
|
413
490
|
// 3. 生成根规则(全局变量)
|
|
414
|
-
const rootRule=generateRootRule(globalVariables,
|
|
491
|
+
const rootRule=generateRootRule(globalVariables,finalConfig);
|
|
415
492
|
// 4. 注入选择器局部变量
|
|
416
|
-
let finalCSS=processedCSS;if(selectorVariables.size>0){finalCSS=injectSelectorVariables(processedCSS,selectorVariables,
|
|
493
|
+
let finalCSS=processedCSS;if(selectorVariables.size>0){finalCSS=injectSelectorVariables(processedCSS,selectorVariables,finalConfig)}
|
|
417
494
|
// 5. 替换变量使用
|
|
418
|
-
finalCSS=replaceVariableUses(finalCSS,globalVariables,selectorVariables,
|
|
495
|
+
finalCSS=replaceVariableUses(finalCSS,globalVariables,selectorVariables,finalConfig);
|
|
419
496
|
// 6. 组合结果
|
|
420
|
-
return rootRule+finalCSS}
|
|
497
|
+
return rootRule+finalCSS};
|
|
498
|
+
// 检查是否有@import语句(需要异步处理)
|
|
499
|
+
const hasImports=cssText&&/@import\s+(?:url\()?["']([^"']+)["']/i.test(cssText);const finalConfig={...defaultConfig,...customConfig};if(!hasImports){
|
|
500
|
+
// 没有@import,同步处理
|
|
501
|
+
return syncConvert(cssText,finalConfig)}else{
|
|
502
|
+
// 有@import,异步处理
|
|
503
|
+
return(async()=>{try{
|
|
504
|
+
// 处理@import
|
|
505
|
+
const processedWithImports=await processImports(cssText,finalConfig);
|
|
506
|
+
// 同步处理剩余部分
|
|
507
|
+
return syncConvert(processedWithImports,finalConfig)}catch(error){console.error("@import处理失败:",error);
|
|
508
|
+
// 如果导入失败,尝试同步处理原始CSS
|
|
509
|
+
return syncConvert(cleanedCSS,finalConfig)}})()}}
|
|
421
510
|
// 自动处理带有 e 属性的 style 标签
|
|
422
|
-
function autoProcessStyleTags(customConfig={}){const config={...defaultConfig,...customConfig};const styleTags=document.querySelectorAll(`style[${config.styleTagAttribute||"e"}]`);styleTags.forEach(styleTag=>{
|
|
511
|
+
function autoProcessStyleTags(customConfig={}){const config={...defaultConfig,...customConfig};const styleTags=document.querySelectorAll(`style[${config.styleTagAttribute||"e"}]`);styleTags.forEach(styleTag=>{let originalCSS=styleTag.textContent;
|
|
512
|
+
// 处理@import(如果是异步的)
|
|
513
|
+
const processStyleTag=async()=>{try{if(styleTag.getAttribute("src")){originalCSS='@import url("'+styleTag.getAttribute("src")+'");'}const convertedCSS=await convert(originalCSS,config);
|
|
423
514
|
// 创建新的 style 标签
|
|
424
515
|
const newStyleTag=document.createElement("style");newStyleTag.textContent=convertedCSS;
|
|
425
516
|
// 插入到原标签后面
|
|
426
517
|
styleTag.parentNode.insertBefore(newStyleTag,styleTag.nextSibling);
|
|
427
518
|
// 可选:移除原标签
|
|
428
|
-
if(!config.preserveOriginal){styleTag.remove()}else{styleTag.style.display="none"}});return styleTags.length}function config(config={}){defaultConfig={...defaultConfig,...config}}
|
|
519
|
+
if(!config.preserveOriginal){styleTag.remove()}else{styleTag.style.display="none"}}catch(error){console.error("处理style标签失败:",error)}};processStyleTag()});return styleTags.length}function config(config={}){defaultConfig={...defaultConfig,...config}}
|
|
429
520
|
// 初始化自动处理
|
|
430
|
-
function apply(cssText,customConfig={}){const config={...defaultConfig,...customConfig};if(cssText){
|
|
521
|
+
function apply(cssText,customConfig={}){const config={...defaultConfig,...customConfig};if(cssText){
|
|
522
|
+
// 检查是否有@import
|
|
523
|
+
const hasImports=/@import\s+(?:url\()?["']([^"']+)["']/i.test(cssText);if(hasImports){
|
|
524
|
+
// 异步处理
|
|
525
|
+
return(async()=>{try{const converted=await convert(cssText,config);const styleEl=document.createElement("style");styleEl.textContent=converted;document.head.appendChild(styleEl);return styleEl}catch(error){console.error("应用CSS失败:",error);return null}})()}else{
|
|
526
|
+
// 同步处理
|
|
527
|
+
const converted=convert(cssText,config);const styleEl=document.createElement("style");styleEl.textContent=converted;document.head.appendChild(styleEl);return styleEl}}else{if(document.readyState==="loading"){document.addEventListener("DOMContentLoaded",()=>{autoProcessStyleTags(config)})}else{autoProcessStyleTags(config)}}}
|
|
431
528
|
// 返回公共 API
|
|
432
529
|
const api={convert:convert,apply:apply,config:config,version:"1.9.0",
|
|
433
530
|
// 检测P3支持
|
|
434
531
|
supportsP3:detectP3Support(),
|
|
435
532
|
// 重新检测P3支持(如果配置变化)
|
|
436
533
|
detectP3Support:detectP3Support,
|
|
534
|
+
// 导入相关方法
|
|
535
|
+
imports:{
|
|
536
|
+
/**
|
|
537
|
+
* 清除导入缓存
|
|
538
|
+
*/
|
|
539
|
+
clearCache:function(){importCache.clear()},
|
|
540
|
+
/**
|
|
541
|
+
* 设置导入基础URL
|
|
542
|
+
* @param {string} baseUrl - 基础URL
|
|
543
|
+
*/
|
|
544
|
+
setBaseUrl:function(baseUrl){defaultConfig.importBaseUrl=baseUrl},
|
|
545
|
+
/**
|
|
546
|
+
* 启用/禁用导入缓存
|
|
547
|
+
* @param {boolean} enabled - 是否启用缓存
|
|
548
|
+
*/
|
|
549
|
+
setCacheEnabled:function(enabled){defaultConfig.importCache=enabled},
|
|
550
|
+
/**
|
|
551
|
+
* 设置导入超时时间
|
|
552
|
+
* @param {number} timeout - 超时时间(毫秒)
|
|
553
|
+
*/
|
|
554
|
+
setTimeout:function(timeout){defaultConfig.importTimeout=timeout}},
|
|
437
555
|
// 数学计算工具方法
|
|
438
556
|
math:{
|
|
439
557
|
/**
|
|
@@ -493,7 +611,9 @@ return handleTemplateTag(...args)}
|
|
|
493
611
|
// 获取第一个参数
|
|
494
612
|
const firstArg=args[0];
|
|
495
613
|
// 如果传入CSS文本,则编译并返回结果
|
|
496
|
-
if(typeof firstArg==="string"){
|
|
614
|
+
if(typeof firstArg==="string"){const result=convert(firstArg,{...defaultConfig,...args[1]});
|
|
615
|
+
// 如果结果是Promise,返回Promise
|
|
616
|
+
if(result&&typeof result.then==="function"){return result}return result}
|
|
497
617
|
// 如果传入配置对象,则应用配置
|
|
498
618
|
if(typeof firstArg==="object"&&firstArg!==null){defaultConfig={...defaultConfig,...firstArg};return styimat}
|
|
499
619
|
// 如果没有参数,执行自动处理
|
|
@@ -505,7 +625,9 @@ let cssText=strings[0];for(let i=0;i<values.length;i++){
|
|
|
505
625
|
// 处理插值(支持字符串、数字、函数等)
|
|
506
626
|
const value=values[i];let result="";if(typeof value==="function"){result=value()}else if(Array.isArray(value)){result=value.join(" ")}else{result=String(value!=null?value:"")}cssText+=result+strings[i+1]}
|
|
507
627
|
// 使用convert函数处理
|
|
508
|
-
|
|
628
|
+
const result=convert(cssText,defaultConfig);
|
|
629
|
+
// 如果结果是Promise,返回Promise
|
|
630
|
+
if(result&&typeof result.then==="function"){return result}return result}
|
|
509
631
|
// 将API的所有方法复制到主函数上
|
|
510
632
|
Object.assign(styimat,api);Object.setPrototypeOf(styimat,Function.prototype);
|
|
511
633
|
// 自动初始化
|